Skip to content
A near-black wall of teal safe-deposit drawers; one drawer is pulled open by a gloved hand and has no back panel, so teal ingots from the shared cavity behind the wall tip into it, glowing red
exploitsOctober 16, 20263 min read

UNCX Vesting V2 $1.49M: A Stale Share Counter and a Donation Drained Four Chains

Alex Rybalko
Alex RybalkoCo-Founder & CEO

Updated on October 16, 2026

TL;DR

On October 14, 2026, an attacker drained UNCX Network's Vesting V2 contract on Ethereum, and by the next day the same bug had been hit on four chains. Blockaid's running tally is about $1.49M across 11 drain transactions: roughly $969K on BNB Chain, $242K on Ethereum, $169K on Robinhood Chain and $114K on Base, taken by the original attacker, copycats and MEV bots. The root cause, as described by ExVul, is share accounting in VestingManager. release() subtracts claimed shares from the pool-wide total but leaves them in the position's own counter, and cancel() later sizes its removal from that stale counter. Once the share price has moved since the original deposit, which the attacker arranged with a direct token donation according to ExVul, that removal exceeds what the position still backs, and already-released shares come out of the total a second time. A shrunken denominator means every remaining share is paid a larger slice of a balance that belongs to other projects' vesting schedules. UNCX says 10 tokens are affected, LP lockers and legacy vesting are not, and it has published no postmortem yet.


What Happened to UNCX Vesting V2?

UNCX, formerly Unicrypt, sells token locks and vesting schedules to launch teams. Vesting V2 keeps every schedule for a given token in one contract and tracks ownership with internal shares, the same idea as a vault: deposit tokens, receive shares, redeem shares for a proportional cut of the balance.

The Ethereum deployment sits at 0xa98f06312b7614523d0f5e725e15fd20fb1b99f5, and its verified source on Sourcify is an exact match for the deployed VestingManager.

The attacker's address, 0x78c9b11853e4f183a34c63ffd1924d2319a2f5b3, was funded, deployed a helper contract at 14:50:11 UTC, and sent the first successful drain transaction at 15:49:59 UTC in block 26191745, according to its transaction history on Blockscout. ExVul priced that transaction at "≈$87.7K at parent-block spot prices" across five ERC-20s (EDGEN, ZC, PEPECOIN, LO0P and PAAL) and warned that "the same accounting path remained live at the latest checked block."

By October 15 the bug had been used on BNB Chain, Robinhood Chain and Base as well. UNCX's own list names 10 tokens: six on Ethereum (pepecoin, EDGEN, ZC, X, LO0P, PAAL), IMO on Base, BTCLE on BNB Chain, and CASHCAT and RKST on Robinhood Chain.


How Did the Share Double-Debit Work?

A share-based pool pays out shares * balance / totalShares. Shrink totalShares without shrinking the balance, and every share left in the pool is suddenly worth more. Whoever redeems next collects the difference, and it comes out of other users' deposits.

Vesting V2 tracks shares in two places: _totalTokenShares[token], the pool-wide denominator, and _depositedShares[vestingId], a per-position counter. The lines that matter, quoted from the verified source with unrelated lines omitted (the two excerpt header comments are ours):

// release(), abridged excerpt from the verified source
        // Calculate amount proportional to shares
        uint256 balance = IERC20(schedule.token).balanceOf(address(this));
        uint256 amount = (releasableShares * balance) / _totalTokenShares[schedule.token];
 
        // Update shares state
        schedule.released += releasableShares;
        _totalTokenShares[schedule.token] -= releasableShares;
 
// cancel(), abridged excerpt from the verified source
        uint256 vestedAmount = getVestedAmount(vestingId);
        uint256 remainingAmount = schedule.totalAmount - vestedAmount;
        // ...
            vestedShare = vestedAmount * _depositedShares[vestingId] / schedule.totalAmount;
        // ...
        uint256 sharesToRemove = _depositedShares[vestingId] - vestedShare;
        // ...
            _totalTokenShares[schedule.token] -= sharesToRemove;

release() records the claim in schedule.released and reduces the global total. It never touches _depositedShares[vestingId]. cancel() then works out sharesToRemove from _depositedShares[vestingId], the lifetime figure, without netting out schedule.released. vestedShare is a token-amount ratio (vestedAmount / totalAmount) applied to that lifetime share count. ExVul's reading is that after a release followed by topUpVesting(), a cancel removes already-released shares from the global total a second time. In our reconstruction below, how many extra shares it removes depends on how far the share ratio moved between the original deposit and the top-up, which is where the donation comes in.

ExVul says the attacker magnified this with a direct token donation, but its alert does not publish the arithmetic. What follows is our own reconstruction from the verified source, so treat it as our inference. Take one attacker position that deposited a tokens, has fully vested and released them, and was then topped up with b more tokens on a later tranche:

  1. The original deposit is priced at one share ratio. _setupShares mints a tokens at the ratio of the moment, call it r shares per token.
  2. A donation lowers the ratio. _updateShareRatio sets the ratio to total shares over balance. Sending tokens straight to the contract raises the balance and mints no shares, so the next deposit gets fewer shares per token, r'.
  3. The top-up is priced at the new ratio. _updateTopUpState adds b * r' shares to both the position counter and the global total.
  4. cancel() splits shares by token amounts. It treats a / (a + b) of the position counter as vested and removes the rest, b * (a*r + b*r') / (a + b) shares, from the global total. Because the counter still holds the released a*r shares at the old, higher ratio, that figure comes out larger than the b * r' the top-up added, and the gap grows with how far the donation pushed r' below r.
  5. The tokens go back in full. cancel() returns all b unvested tokens to the creator. The global total has lost more shares than those tokens ever carried, and the extra shares are deleted from the denominator only.

Repeating the loop shrinks the denominator further. The attacker then releases a second position it owns, and release() pays it shares * balance / total against a total that is now too small. The balance it divides is shared with every other schedule for that token.

The drain transaction's token transfers show the pattern for EDGEN. Deposits that pay the 0.1% fee alternate with large payouts back to the exploit contract, and one transfer of about 49.7M EDGEN goes in with no fee taken at all, which we read as the donation (an inference from the transfer list, not a decoded trace).

This is a close cousin of the donation-inflation class we covered in the Summer.fi ERC-4626 incident: a share price that something other than a mint or a burn can move.


The Attack, Step by Step

The Ethereum leg, reconstructed from the attacker's on-chain history and ExVul's analysis. Times are UTC on October 14 unless noted.

StepTimeActionSource
114:47Attacker EOA receives 0.1 ETH, then 0.9 ETH and 2.5 ETH more from the same funding addressBlockscout history
214:50Deploys helper contract 0x6cEbDf09...F26c; Blockaid's systems flag this pre-exploit activityBlockscout, Crypto Times
315:49Drain transaction: repeated create, top-up, release and cancel cycles across five tokens through exploit contract 0xf6d07225...c35eBlockscout tx, ExVul
415:58 to 16:25Four more attempts against the same entry point, all revertedBlockscout history
516:38 to 16:41Sweeps the drained tokens and ETH from the exploit contract to the EOABlockscout history
616:49 to 17:01Token approvals and router execute calls, consistent with swapping out of the drained tokens (inferred)Blockscout history
717:09Sends 3.5 ETH back to the funding address, exactly what it had receivedBlockscout history
8Oct 15Blockaid's update counts drains on BNB Chain, Robinhood Chain and Base as well, by the original attacker, copycats and MEV bots; total about $1.49MBlockaid

The same code was exploitable on every deployment, and the other chains are where most of the money went. Blockaid's update lists the drains but publishes no per-chain timestamps, so the chart shows the branches without an order:

rendering diagram…

Why Is It $86K in One Place and $1.49M in Another?

The two figures cover different scopes. Blockaid's first alert, quoted in the same thread, put it at "~$86k drained from the shared vesting pool so far across 1 drain tx." ExVul's figure for that transaction is $87.7K at parent-block spot prices, and it says the exploit contract retained about $85.6K. Neither firm states its price source in the alert, so we read the $1-2K spread as pricing method and treat the first Ethereum drain as roughly $86K to $88K.

The $1.49M is Blockaid's later total across all 11 drains on four chains. The Crypto Times notes it uses token values at the time of each drain, so it can move. BNB Chain alone is close to two-thirds of it. Blockaid's per-transaction list in the same thread splits Ethereum's $242K into the main drain ($86K), a separate drain of the X token ($152K) and ZC copycats.

The realized number is smaller again. These are thin, small-cap tokens, and selling a large block of one moves its price against the seller. The original attacker's address holds 20.24 ETH after swapping out and returning its funding. At CoinGecko's October 14 ETH price of about $2,497, that is roughly $50,500 (our arithmetic). UNCX's own bounty offer lines up with it: a message in the calldata of an October 15 transaction to the attacker offers a 10% bounty and asks for 18.207 ETH back, which is 90% of the address's 20.24 ETH. (The transaction itself reverted, but its input data is on-chain and readable.)

We treat $1.49M as the gross loss to the affected projects at drain-time prices, and about 20 ETH as what the first attacker actually walked away with on Ethereum. UNCX has not published a final figure.

Two small details from the same address history. Someone sent the attacker a 1-wei transaction whose input decodes to a request for spare ETH. And within hours of the drain, dust arrived from lookalike addresses copying the first and last characters of addresses the attacker had just transacted with, the address-poisoning pattern.


Why Could Nobody Switch It Off?

Vesting V2 has no pause. The verified source has no Pausable, no whenNotPaused guard and no emergency stop. ExVul's alert warned that the same accounting path remained live, and the contract gave UNCX no direct way to close it on any chain.

UNCX contained it with the levers it had. Its incident update lists flat fees "exceeding 1,000 ETH per lock," removal of the client paths that let fees be bypassed, and token fees raised to 5%, "the maximum permitted by the Vesting V2 contracts," to make repeated top-ups uneconomic. With no pause available, the fee settings became the circuit breaker, and they reach the exploit path because vesting creation and top-ups both charge fees.

By the time UNCX announced those fees, its own statement already described the incident as affecting Vesting V2 "across several chains."


What Does This Mean Beyond UNCX?

Two design choices turned one accounting bug into ten tokens on four chains. Pooled custody let one attacker position draw on schedules from unrelated teams and investors. Identical deployments meant the root cause ExVul published for Ethereum described BNB Chain, Robinhood Chain and Base just as well.


What Should Operators Do Now?

  1. Write the share invariant down and fuzz it. For any pooled vault, vesting or staking contract: the global share total must equal the sum of outstanding per-position shares, after every function, in every order.

  2. Update every counter in the same function that changes the quantity. release() changed what a position owned and updated only one of the two places that record it. Prefer one source of truth, or make the second a pure derivation.

  3. Ship a real pause, scoped and tested. A role-gated pause on release, cancel and top-up costs a few lines.

  4. Pre-stage the containment transaction on every chain. Vesting V2 ran on at least four chains, and $1.25M of the $1.49M was lost on the three other than Ethereum. Have the fee or pause call drafted per deployment, with signers who can send it within the hour.

  5. Isolate tenants where the product allows it. Here one token's pool held schedules from unrelated teams. Per-schedule balance accounting limits a share bug to the position that triggered it.

  6. Alert on the shapes this attack made, and decide the response in advance. A contract deployed by an address funded minutes earlier, followed by one transaction that cycles five tokens through create, top-up and cancel. A fee-free transfer of tokens straight into the vesting contract, like the 49.7M EDGEN donation. If the contract cannot pause, write down which admin lever the alert pulls, on which chains, and who holds the key.


What Could Monitoring Have Changed?

On Ethereum, one signal came before the money moved, and it is a noisy one. Three events in this incident were detectable on-chain:

  • 14:47 to 14:50 UTC. A fresh address received 0.1 ETH and, under three minutes later, deployed a helper contract. The Crypto Times reports Blockaid's systems flagged pre-exploit activity at 14:50 UTC.
  • 15:49 UTC. One transaction cycled five tokens (EDGEN, ZC, PEPECOIN, LO0P and PAAL) through repeated vesting create, top-up and cancel calls against the same contract.
  • Inside that transaction. A transfer of about 49.7M EDGEN into the vesting contract with no matching fee transfer, which we infer was a direct donation rather than a deposit through the contract's own functions.

Fresh addresses deploy contracts all day, so the first signal alone would not justify touching a production contract. The second and third are specific to this bug, and they fire at 15:49, in the same block as the Ethereum loss.

The response lever did not need a pause. setFlatFee and setFeePercentage are owner functions in the verified source, and the exploit path runs through vesting creation and top-ups that those fees gate. UNCX used them after the fact to contain the incident. Using them pre-emptively is untested, but it is the only lever this contract offered.

For the other three chains, $1.25M between them, the answer depends on timing nobody has published. The Crypto Times puts Blockaid's first public alert at about 16:26 UTC, when Blockaid counted one drain transaction, and the BNB Chain, Robinhood Chain and Base drains first appear in Blockaid's count the next day. Whether a fee raise could have protected any of them is unknown: nobody has published their timestamps, and the fee lever was only ever used after the fact.


Would an Audit Have Caught It?

Yes, if the review fuzzed the share accounting across call orders. The flaw is a broken equality between two counters that only shows up after release, donation, top-up and cancel run in sequence, and a stateful fuzzer finds that sequence once the invariant is written down and the harness includes a direct token transfer among its actions. Our Solidity audits pair line-by-line review with Foundry fuzz campaigns written against the protocol's own invariants, and "pool share total equals the sum of live positions" is exactly that kind of property. Written down and fuzzed, that property is where a double-debit like this one would surface as a failing test before deploy, on the first chain, with no copycats watching.


Frequently Asked Questions

How much was stolen in the UNCX Vesting V2 exploit?

About $1.49M across Ethereum, BNB Chain, Robinhood Chain and Base, per Blockaid, priced at token values when each drain happened. The widely quoted $86K covers only the first Ethereum transaction. The original attacker's address holds 20.24 ETH, roughly $50,500 at the October 14 ETH price.

Were UNCX liquidity lockers affected?

No. UNCX says all LP lockers and its legacy vesting contracts are unaffected, and the incident is isolated to Vesting V2.

What was the root cause of the UNCX exploit?

A share accounting error in VestingManager. release() reduced the pool's total shares but not the position's own counter, and cancel() sized its removal from that stale counter. After a direct token donation moved the share price, which ExVul says magnified the debit, that removal took already-released shares out of the total a second time, and the remaining shares claimed other users' tokens. The step-by-step arithmetic is our reconstruction from the verified source, not a published finding.

Has any money been returned?

Not as of UNCX's October 15 statement, which said no funds had been returned by the primary attacker or the copycats on any chain. UNCX is offering a 10% bounty on-chain and is coordinating with law enforcement and SEAL 911.

Could the UNCX exploit have been paused?

No. The Vesting V2 source has no pause function. UNCX contained it by raising flat fees above 1,000 ETH per lock and token fees to the 5% maximum.


Sources / References

Alex Rybalko
Alex Rybalko

Co-Founder & CEO

Co-Founder of SigIntZero. Security architecture and threat modeling for protocols and distributed systems.