The $821,920 Ghost Sale: Reversing NemTokenV2 to Find Where Intent Was Mistaken for Settlement

HasuPanda
Miners

The $821,920 Ghost Sale: Reversing NemTokenV2 to Find Where Intent Was Mistaken for Settlement

SlowMist flagged it at 821,920 dollars. A token called NEM was drained. Within an hour the timeline filled with panicked posts about the NEM blockchain — XEM, the 2015 L1, the old warhorse. People started checking their XEM bags. Some sold. The confusion was total.

The contract name in the alert was not XEM. It was NemTokenV2.

That version suffix is the tell. Version suffixes belong to tokens that get recompiled, redeployed, re-tuned — ERC-20 and BEP-20 clones iterated by a deployer chasing a narrative. Nobody redeploys a consensus layer and stamps "V2" on it like a firmware patch. So I ignored the headline, pulled the function names, and started reversing.

The two functions that mattered were skim() and flushLpSellRecycle(). The first is a public, permissionless function that has existed in Uniswap V2 Pair contracts since 2020. The second is a custom function that only makes sense inside a tax-token economy. Put them in the same contract and you do not have a bug. You have a semantics collision — two abstractions that assume different things about what a token transfer means, sitting one function call apart.

The attacker never exploited a cryptographic flaw. They exploited a grammatical one. The contract read "a token entered the pool" and heard "a sale was completed." Those are not the same sentence.

By the time I finished tracing the logic, the shape of the failure was obvious. This was not a sophisticated heist. It was a $821,920 withdrawal from a vending machine that had been told, in code, that pressing the button was the same as paying.

Context: Two NEMs, One Pool, and a Function That Was Never Meant to Be a Weapon

Before the analysis, the disambiguation, because getting this wrong produces exactly the wrong panic.

There are two things called NEM in this story. There is the NEM blockchain — token symbol XEM, a 2015-era Layer 1 with its own consensus and its own history. And there is NemTokenV2, a custom token deployed on an EVM-compatible smart contract chain, sitting inside a NEM/USDT liquidity pool that is a fork of the Uniswap V2 pair architecture. The SlowMist alert concerns the second. The first is innocent, and anyone who sold XEM on the headline was trading a rumor, not a fact.

The evidence for this is not ambiguous. NemTokenV2 carries a version number, which is standard practice for iterated token contracts and unheard of for base-layer protocols. The function skim() is a Uniswap V2 Pair standard — it exists in the Pair contract, not in any L1 client. The function flushLpSellRecycle() is a custom business function that can only live inside a token contract that performs tax or reflection logic. And the reported outcome — "sold NEM and extracted USDT" — is DEX pool behavior, not centralized exchange matching. Four independent signals, all pointing at an application-layer token on a smart contract chain.

This matters because the wrong reading produces the wrong lesson. If you believe XEM's consensus layer was breached, you conclude that Layer 1s are fragile and you rebalance your whole portfolio. If you read it correctly, you conclude that a specific class of token contract is structurally broken and you learn to recognize it. The first conclusion is fear. The second is a filter.

Now the mechanics, because the attack only makes sense once you understand what these two functions actually do.

A Uniswap V2 Pair contract holds two assets and maintains two internal accounting variables — reserve0 and reserve1. These are not the same as the actual token balances of the Pair address. They are the protocol's belief about how much it holds, updated only during swaps, mints, and burns, or when someone explicitly calls sync(). The gap between the real balance and the reserve is a known, permanent feature. If someone mistakenly transfers tokens directly to the Pair contract, the balance exceeds the reserve. Those excess tokens are stranded — the pricing curve does not know about them.

skim() exists to fix that. It is a permissionless function that lets anyone sweep the excess — the difference between the real balance and the reserve — to any address. It has no access control. It was designed as a courtesy, a way to recover mistakenly sent funds. In normal operation it is harmless, because in normal operation the reserve tracks the balance closely and the excess is trivial.

Now the second abstraction. Tax tokens — reflection tokens, sell-tax tokens, "buy-back-and-distribute" tokens — all share a common pattern. When a user sells, the contract takes a cut, routes it to liquidity, and uses the proceeds for something: dividends to holders, buyback-and-burn, treasury. The function flushLpSellRecycle() is one instance of that pattern. The name decomposes cleanly: flush the LP, on a sell, recycle the proceeds. Someone sells, the contract converts the tax into USDT, and redistributes.

Here is where the two abstractions collide. To run its recycling logic, the token contract needs to know when a sale happened. The clean way to know is to check that the counterparty actually paid — verify the USDT balance increment on the Pair, confirm settlement. The dirty way — and I have seen this in audits more times than I want to count — is to assume that any transfer of the token into the Pair constitutes a sale, because in the happy path, that is how sales look.

NemTokenV2 took the dirty path. It treated a raw transfer() into the Pair as a completed sell. And because skim() lets anyone pull back an excess transfer, the attacker could manufacture that "sale" at zero cost, over and over.

Core: The Attack Chain, the Root Cause, and the Template That Keeps Failing

Reversing the stack to find the original intent, the sequence reconstructs as follows. I am marking confidence because the alert is a short-form bulletin — it gives the root cause in one sentence and omits the transaction hash, the contract address, and the attacker address. So the chain below is an inference built from function semantics and known patterns, not an independently verified trace. I will be explicit about where the inference gets thin.

Step one. The attacker transfers NEM directly to the NEM/USDT Pair contract — a raw transfer(), not a swap(). This is the move that sets everything in motion, and it costs the attacker nothing but gas, because the tokens are going to come right back.

Step two. NemTokenV2 observes the incoming transfer and, following its flawed assumption, records it as a sale. The sell counter increments. But the contract does not check whether the Pair received any USDT in return. There was no USDT. There was never going to be any USDT. The "sale" is a bookkeeping fiction — the accounting equivalent of writing a receipt for a payment that was never made.

Step three. The attacker calls Pair.skim(). The Pair sees that its real NEM balance exceeds its reserve by exactly the amount just transferred. skim() sweeps that excess back to the attacker. Net result: the sale counter went up by one, and the attacker's NEM balance is unchanged. Zero-cost volume fabrication. A ghost sale.

Step four. The attacker repeats steps one through three, interleaved with 42 calls to flushLpSellRecycle(). Forty-two is not a round number. It is a loop count — the signature of a script or a helper contract running a batch, not a human clicking through a UI. Whoever wrote this attack understood the contract's internal state machine well enough to know exactly how many cycles would push the recycling logic past its thresholds.

Step five. The accumulated fake sales trigger the recycling logic repeatedly. Each invocation pulls from the LP, and the repeated draining breaks the original reserve structure. The NEM side of the pool gets thin.

Step six. A thin NEM reserve means NEM is now scarce inside the pool, and scarcity on an AMM curve means a higher marginal price. The attacker sells their remaining (real) NEM into the artificially inflated pool and extracts USDT. That is the 821,920 dollars.

The root cause sits entirely in step two, and it is worth naming precisely. The contract conflated a state-change intent with a settled funds transfer. It read a token arriving and heard a payment completing. In DeFi, any logic that infers business meaning from a balance change must verify the counter-asset actually settled — otherwise that meaning can be forged for free. This is not a subtle point. It is the difference between "the user says they sold" and "the user sold." NemTokenV2 built its entire recycling economy on the first, and treated it as the second.

I want to dwell on step three, because the instinct is to blame skim(), and that instinct is wrong. skim() is not a vulnerability. It is a standard, audited, battle-tested function that does exactly what it was designed to do. The abstraction layer that is skim() hid complexity — the gap between balance and reserve — but it did not hide the error underneath. The error was in the token contract that assumed a raw transfer was a sale. skim() merely gave the attacker a clean way to erase the evidence of their fake sale. This is the pattern I keep finding: the weapon is always a legitimate function; the flaw is always the assumption the surrounding code makes about what that function's side effects mean. When I audited the 0x protocol back in 2017, the unsigned integer overflow I found in fillOrder was not exotic either — it was ordinary arithmetic that assumed a precondition nobody had enforced. The lesson has not changed in nine years. Legitimate primitives, illegal assumptions.

The economics of the attack confirm the technical read. flushLpSellRecycle() — the very existence of a function that withdraws value from the LP on a sell trigger — tells you what this token is. It is a liquidity-consumption model dressed as a value-distribution model. A sustainable tax token redistributes transaction fees between participants; the fees are real, the redistribution is zero-sum but honest. A consumption token pulls from the LP principal itself, and the "dividends" are just the pool being eaten. In the first model, the pool can persist indefinitely. In the second, the pool is a countdown clock. The attacker did not break the economics. They accelerated a failure that was already scheduled.

This is the same structural illness I spent four weeks dissecting in the Terra/Luna loop after May 2022. The peg mechanism was not attacked from outside; it was mathematically destined to invert, and the crash just revealed the timetable. flushLpSellRecycle has the same signature at a much smaller scale. It works while new buyers keep feeding the pool, and it fails first when the feeding stops. In a bear market, these structures do not decline. They vanish.

What compounds the failure is the absence of any settlement check downstream. If "LP transfer equals sale" was left unverified in the sell-trigger path, then every other function that reads the same data source is suspect by association — fee calculation, dividend snapshots, holder limits, any logic that trusts the sell counter. The attacker chose the cheapest path, but the cheapness of the path is itself diagnostic: it tells you the contract was assembled from a fast-launch template with a thin layer of custom logic, not designed as a system. I have seen the template. It circulates on forums, it compiles cleanly, it passes a smoke test, and it has never once been adversarially tested. Code that runs is not code that is safe. Those are two different properties and only one of them can be verified by watching the green checkmark appear.

On supply structure, the honest answer is that it is not assessable from public sources. Total supply, circulating supply, team allocation, unlock schedule, treasury address — none disclosed in the alert, and no evidence of on-chain verifiable locks. For a token whose only claim to value is its narrative, an unverifiable distribution is not a missing data point. It is a verdict. An asset whose ownership you cannot trace on-chain has no investable content, regardless of its price chart. The price you see is a story someone is telling; the allocation you cannot see is the part that decides whether you get to leave.

On market impact, the number itself sets the ceiling. 821,920 dollars is noise. It is below the monthly security budget of a mid-sized protocol. There is no contagion vector here, no systemic exposure, no balance sheet on the other side of this that matters. The event's significance is entirely taxonomic — it is another specimen of a species that keeps reproducing, and the useful work is classifying it, not pricing it.

One more inference, flagged as low-to-medium confidence. The 42 precise cycles and the accurate prediction of the custom function's behavior suggest more than a casual attacker reading verified source. It suggests someone who either held the source before deployment, understood the deployer's intent directly, or had access to the deployment keys' mental model. Internal-actor or leaked-source hypotheses deserve a place on the suspect list, not because insiders are common, but because the precision here is unusual for an opportunistic script kiddie. The beneficiary of flushLpSellRecycle — where the recycled USDT actually goes — is also worth tracing. If the receiving address is hardcoded to the deployer or set through a mutable state variable, then the "holder dividends" narrative was packaging, and the pool was always a personal withdrawal account with a community-facing label.

Contrarian: The Real Blind Spot Is Monitoring Asymmetry, Not This One Token

Here is the counter-intuitive part, and it is where most post-mortems stop short.

Everyone will read this as a story about a badly written token. That reading is correct and useless. The genuinely interesting signal is why SlowMist flagged an 821,920-dollar event at all — an amount that should be beneath the noise floor of a serious monitoring operation. The answer, if I read the alert's granularity correctly, is that their detection rule fired on function-call frequency anomalies, not on value at risk. Forty-two rapid invocations of a sensitive custom function is a pattern. Patterns are detectable regardless of dollar size. The size of the loss is irrelevant to the trigger; the shape of the on-chain activity is the trigger.

That is the transferable insight, and it inverts the usual security advice. Projects spend their budget on audits — point-in-time reviews that certify a snapshot of code. But this attack used no novel technique. It used a standard function in a standard way against a non-standard assumption. No audit would have caught the economic interaction unless the auditor was specifically hunting for the "raw transfer misread as sale" pattern. What would have caught it, in real time, is a monitor watching skim, sync, and flush* call frequency against a baseline. The defense that scales is not the audit that happened last quarter. It is the monitor that never sleeps.

The $821,920 Ghost Sale: Reversing NemTokenV2 to Find Where Intent Was Mistaken for Settlement

The second blind spot is more uncomfortable. The market's implicit belief is that "listed on a DEX" equals "tradeable." This event is a clean counterexample. Before the attack, NEM could be bought and sold freely. It had a price. It had a chart. It had everything a tradeable asset appears to have — except depth, except audit, except transparency, except any counterparty who would still be there when you wanted out. The existence of liquidity is not the reliability of liquidity. A single pool with thin reserves is a single point of failure wearing the costume of a market. When I traced 40% of popular NFT collections to centralized IPFS nodes back in 2021 and argued that true ownership was an illusion, it was the same error in a different costume: mistaking the appearance of decentralization infrastructure for its substance. A token with one shallow pool and no downstream integration is not a market. It is a hostage.

The third blind spot is temporal. Attacks of this class are not instantaneous from the attacker's side — the attacker had to accumulate NEM first to have "remaining NEM" to dump in step six. That accumulation leaves on-chain footprints: sustained small buys from a single address, paired with anomalous LP balance fluctuations as the attacker rehearsed the skim loop. A monitor trained on the right signals could have seen the position being built and the pool being probed before the dump. The post-mortem is always cheaper to write than the pre-mortem is to run, and that asymmetry is exactly backwards from where the value is.

Takeaway: The Failure Mode Is a Template, and Templates Propagate

Truth is not consensus; truth is verifiable code — and the code here said, in plain Solidity, that a transfer is a sale. That sentence will be compiled again. The template that produced NemTokenV2 is not unique to this deployment; it is a genre, and the genre has one defining flaw: it trusts intent where it should verify settlement. My forecast is unglamorous and deterministic. As long as fast-launch tax tokens keep shipping with unverified sell-detection, the same ghost-sale primitive will reappear, and the amounts will stay small enough to be ignored and large enough to zero out whoever was holding. The question worth asking is not whether the next one will happen — it is whether the next monitoring dashboard will watch skim frequency, or whether the market will keep waiting for the audit that certifies yesterday's code while the attacker rehearses tomorrow's loop.

Market Prices

BTC Bitcoin
$80,363.7 -0.05%
ETH Ethereum
$2,330.59 +0.05%
SOL Solana
$101.46 +0.74%
BNB BNB Chain
$741.03 -0.01%
XRP XRP Ledger
$1.29 -0.18%
DOGE Dogecoin
$0.0772 +0.93%
ADA Cardano
$0.2049 +0.79%
AVAX Avalanche
$8.96 +1.81%
DOT Polkadot
$1.03 +0.95%
LINK Chainlink
$11.46 +1.00%

Fear & Greed

50

Neutral

Market Sentiment

7x24h Flash News

More >
{{快讯列表(10)}} {{loop}}
{{快讯时间}}

{{快讯内容}}

{{快讯标签}}
{{/loop}} {{/快讯列表}}

Event Calendar

{{年份}}
30
04
upgrade Celestia Mainnet Upgrade

Improves data availability sampling efficiency

08
04
upgrade Solana Firedancer

Independent validator client goes live on mainnet

22
03
unlock Optimism Unlock

Circulating supply increases by about 2%

12
05
halving BCH Halving

Block reward halving event

18
03
unlock Sui Token Unlock

Team and early investor shares released

15
04
halving Bitcoin Halving

Block reward reduced to 3.125 BTC

10
05
upgrade Ethereum Pectra Upgrade

Raises validator limit and account abstraction

28
03
unlock Arbitrum Token Unlock

92 million ARB released

Tools

All →

Altseason Index

40

Bitcoin Season

BTC Dominance Altseason

Gas Tracker

Ethereum 28 Gwei
BNB Chain 3 Gwei
Polygon 42 Gwei
Arbitrum 0.5 Gwei
Optimism 0.3 Gwei

Market Cap

All →
1
Bitcoin
BTC
$80,363.7
1
Ethereum
ETH
$2,330.59
1
Solana
SOL
$101.46
1
BNB Chain
BNB
$741.03
1
XRP Ledger
XRP
$1.29
1
Dogecoin
DOGE
$0.0772
1
Cardano
ADA
$0.2049
1
Avalanche
AVAX
$8.96
1
Polkadot
DOT
$1.03
1
Chainlink
LINK
$11.46

🐋 Whale Tracker

🔴
0xcff0...987c
6h ago
Out
194.05 BTC
🟢
0xf163...4481
1d ago
In
17,980 BNB
🟢
0x8cad...f15f
2m ago
In
3,057 ETH

💡 Smart Money

0x238b...153b
Early Investor
+$4.5M
63%
0x19c5...af08
Institutional Custody
+$2.2M
77%
0xabe3...57eb
Top DeFi Miner
+$0.2M
75%