The NEAR Hack

How a trusted signer paid out a corrupt ledger, which controls were missing, and what every financial institution running a ledger next to a pool of wallets should take away from this incident.

Thibault de Lachèze-Murel
Thibault de Lachèze-Murel

On 30 September and 1 October 2026, a thief withdrew 3,865,000 USDT from a vault on BNB Chain that holds deposits for NEAR Intents, in five withdrawals over about six hours. Nobody stole a key, and nobody fooled a signer. The signers did exactly what they were built to do. They checked that each withdrawal was recorded on the NEAR side, confirmed it, and signed. The record they checked was real. The balance behind it was not.

This post is a companion to our analysis of the Bitget hack, and the two incidents are mirror images. At Bitget, the signer trusted the wrong host. At NEAR Intents, the signer was right and the ledger was wrong. Together they mark out the two sides of the same boundary. A signer must verify authority before it signs, and the state it verifies against must itself be trustworthy. Bitget proved the first. This incident proves the second.

Two things before we begin. NEAR Intents is the victim of a crime, and nothing here is about people. The amount is small by the standards of this industry, and the response was unusually good, which I will say plainly in its place. The reason the incident deserves a post is the class it belongs to. Any institution that keeps an internal ledger of who owns what, in front of pooled wallets that hold the actual assets, carries exactly the risk that failed here. That describes most custody setups, most exchanges, and any core banking system for digital assets, including ours.

A note on evidence. NEAR Intents published a notice on the day, a postmortem two weeks later, and its code changes are public. Bitquery traced the money onchain. There is no Mandiant or SlowMist report here, so the evidence base is thinner than for Bitget. I mark claims as confirmed when they come from NEAR Intents, its repository or the chain, and as reported when they come from analysts or the press.

What happened

NEAR Intents is a cross-chain swap and settlement protocol built on the NEAR blockchain. It is not the NEAR chain itself. Users deposit tokens from other chains into lockers, which are contracts that hold the deposits, and NEAR Intents keeps an internal ledger of what each user is owed. Solvers, the parties that fill users’ swap requests, settle against that ledger. When a user withdraws, NEAR Intents records the withdrawal with the HOT Bridge, which it uses to move tokens across chains. The bridge’s validator nodes check that the withdrawal is recorded, sign it with a shared key, and the locker on the destination chain pays out.

The attack worked by corrupting the ledger. Over several days, the attacker made 23 deposits designed to fail in a specific way. The tokens went back to the attacker, but the ledger kept the credit. About $3.8 million of balance that no tokens backed built up in the attacker’s account. The attacker then withdrew it, and because the vault held other users’ deposits, the vault paid.

Date and time (UTC)Event
13 to 15 January 2026A bug bounty report on a similar third party call feature is fixed on mainnet within two days. Never exploited. Confirmed, postmortem as relayed
28 SepThe attacker’s BNB Chain wallet is funded with gas and makes a 10 USDT deposit. Reported, Bitquery
Over several days, before the withdrawals (dates not disclosed)23 deposits into the NEAR Intents contract, each carrying a notification pointing at a contract built to fail. Each leaves a ledger credit while the tokens are refunded. Confirmed, postmortem
29 Sep, 16:14 to 1 Oct, 05:54The attacker withdraws the ghost balance through the Omni infrastructure on the NEAR side. Confirmed, postmortem as relayed
30 Sep, 18:57 and 20:05Test withdrawals of 10 and 11 USDT from the BNB Chain vault. Confirmed onchain
30 Sep, 23:54800,000 USDT withdrawn. Then 1.2 million at 00:24, 1.5 million at 00:50, 330,000 at 01:46 and 35,000 at 06:08 on 1 October. Confirmed onchain
30 Sep, 23:57Swaps from USDT into BNB begin, three minutes after the first large withdrawal. Confirmed onchain
1 Oct, from about 01:14Starting with 650 BNB through CoW Swap, about $822,000 of the stolen funds passes through NEAR Intents’ own swap service over the next few hours, while the drain is still running. Reported, Bitquery
1 Oct, 02:26 to 12:01The funds move to Bitcoin through Chainflip and THORChain, and are parked in four wallets. Reported, Bitquery
1 Oct, 08:04Withdrawal problems identified, two hours after the last withdrawal. Confirmed, postmortem as relayed
1 Oct, 08:33The contract is paused, 29 minutes after identification. Confirmed, postmortem as relayed
1 Oct, 12:53First public notice. Services stopped, about $3.8 million lost, users to be fully compensated, eleven chains paused, law enforcement informed. Confirmed
1 Oct, eveningA regression test and three hardening changes are merged and tagged as release v0.4.3. Confirmed, repository
2 Oct, 00:18NEAR Intents’ general manager posts “We have identified you, sir,” with three recovery addresses and a 48-hour window. Confirmed
2 Oct, 10:22The attacker’s wallet replies onchain, willing to cooperate. Confirmed onchain
2 Oct, 14:31 to 15:0534.589 BTC arrives at the recovery address. Confirmed onchain
2 Oct, 15:52NEAR Intents states the funds are returned in full and closes the investigation. Confirmed
2 Oct, 20:31The fix is deployed on mainnet. Contract-side deposits reopen at 20:38. Confirmed, postmortem as relayed
7 OctA second critical report on the same feature arrives through the bug bounty program, and the researcher is paid $300,000. The deposit notification feature is removed. Confirmed, postmortem as relayed
16 OctThe official postmortem is published. Confirmed

A few numbers frame the incident. The loss was about $3.8 million, all of it USDT on BNB Chain. In the two days before the theft, the vault’s largest single stablecoin payout had been under 400,000, and three of the five withdrawals exceeded that within an hour. The ledger was out of balance for nearly forty hours by the postmortem’s own window, and about eight hours passed between the first large withdrawal and identification. From identification to pause took 29 minutes. The Bitcoin that came back covered roughly 78 percent of the stated loss at the time. NEAR Intents states the remainder was returned too and that all affected users were made whole. One independent analyst reports they were paid from the team’s treasury before the recovery. The terms of the return were not disclosed. The attacker has not been named by anyone, and should not be.

How it worked

The attack is best understood as a chain of six steps.

The attacker built a contract designed to fail. NEAR Intents let a depositor attach a notification to a deposit, which made the system call a third party contract during the deposit flow. The attacker’s contract failed every time it was called. Confirmed.

Failed deposits left ghost credits. On NEAR, a transfer to a contract is not a single step. The ledger credit happens first, and the token movement becomes final later, in an asynchronous callback. If the third party call fails, NEAR Intents’ rollback is supposed to remove the credit and tell the token contract how much to send back. If the rollback itself fails, the token contract treats the whole deposit as failed and refunds the full amount, while the credit written earlier stays. That is what happened. The tokens went home and the credit stayed. Twenty-three times. This gap is confirmed by the postmortem. The exact reason the rollback failed is not stated there, but the repository states it. The regression test merged on 1 October says the rollback’s refund log grew past NEAR’s log size limit and the whole callback failed, “as happened to the deployed revision”. That evening’s fix caps deposits at ten tokens, which puts that log out of reach. Later changes tightened the same code paths and then removed the feature. My reading, and it is a reconstruction rather than the postmortem’s finding, is that the attacker padded each deposit with enough token entries that the rollback could not finish, so the token contract treated the deposit as failed and refunded it, while the credit written earlier stood.

The ledger said users owned more than the vault held. Nothing noticed. Monitoring compared the total tokens on the original chain with the total on the NEAR side, but never checked whether the sum of users’ internal balances matched the tokens the contract actually held. The ledger was insolvent by $3.8 million, and because the vault held far more than that from other users, every withdrawal was still executed normally. Confirmed as to the gap.

A correct signer paid out. Withdrawals on NEAR Intents are enqueued with a unique nonce, a number used once, and the user’s bridge tokens are burned. Validator nodes, running multi-party computation inside trusted execution environments, attest that the withdrawal exists on the NEAR side and sign it. The locker on BNB Chain pays on a valid signature and an unused nonce. Every one of the thief’s seven withdrawals carried a signed authorization in the same form as everyone else’s. The only visible difference was that the thief submitted them itself rather than through the usual relay wallet, which the design allows. Nothing in the request told the vault what the user’s real balance was. Confirmed, by the bridge’s published design and onchain.

Test, then drain. Two tiny withdrawals, then five large ones in six hours, three of them above anything the vault had paid out in days. No human step and no cap stood in the way. Confirmed onchain.

Cash out. The money was swapped to BNB, spread across 32 wallets, and mostly converted to Bitcoin through Chainflip and THORChain. About a fifth of it moved through NEAR Intents’ own service while the drain was still running, which shows how little the system knew about its own state at that moment. Confirmed onchain.

The dimensions of the failure

Ledger integrity. This is the heart of it. The internal ledger credited a deposit before the deposit was final, and the path that should have reversed the credit when the deposit failed did not work. A ledger that can be credited by an action the chain later undoes is not a ledger. It is a promise waiting to be broken.

Solvency monitoring. The one invariant that mattered, that entitlements never exceed holdings, was never checked. Monitoring watched the seam between two chains instead of the product’s own books. The ledger was insolvent for nearly forty hours and the first alarm came from withdrawals, not from reconciliation.

The signing boundary. The validators here did more than Bitget’s signer did. They verified the withdrawal record the corrupt ledger had produced, inside a trusted execution environment, before signing. That is a stronger design, and it signed $3.8 million of theft anyway, because the state it verified was false. Verification inside the signing boundary is necessary. The integrity of what that boundary reads is a separate control, and nobody owns it.

Payout controls. Three payouts far above the vault’s recent ceiling within an hour, and five in six hours, with no cap, no velocity rule and no human in the path. The locker, by design, held no balance information and could not have known. The limits that would have bounded the loss had to live somewhere else, and they lived nowhere.

External calls in the deposit flow. The deposit notification feature let an attacker’s contract run inside a critical path. A feature that calls untrusted code during a balance change needs to treat that code’s failure as hostile. Here, the failure was ignored. Three critical findings on this feature or a close variant arrived in nine months: a bug bounty report in January, the attack itself, and a report on 7 October. After the third, the feature was removed.

Detection and response. Detection took about eight hours from the first large withdrawal and happened after the last one. What is public is that withdrawal problems were identified at 08:04. Illia Polosukhin said NEAR Intents’ SHIELD security layer flagged the unusual activity. What it compared, when it fired, and when the affected route actually stopped have not been disclosed. Once identified, the response was fast. 29 minutes to pause, users made whole, a fix deployed the next day, the feature removed within a week. That part deserves credit.

Postmortem and reconciliation. The postmortem came fifteen days after the incident. The investigation was closed on 2 October on the strength of the Bitcoin return, without a public reconciliation of the full amount or the terms of the return. “Returned in full” is NEAR Intents’ statement, and it may well be true, but the public record only shows the Bitcoin.

What to do about it

In order of impact, for any institution that runs a ledger in front of pooled wallets.

  1. Credit only on finality. An internal ledger credits a deposit when the chain has confirmed it, under the confirmation rule for that chain, and never before. If your system credits earlier, the rollback path must be proven to mirror exactly what the chain will do on failure.
  2. Enforce the solvency invariant continuously. The sum of what you owe your users must never exceed what your wallets hold, per asset, per chain. Check it on every change, not on a schedule, and pause withdrawals automatically when it breaks.
  3. Put limits on the payout path that the ledger cannot corrupt. Per transaction caps, velocity caps per wallet, allowlists, and a human quorum above a threshold. A correct signer paying a corrupt ledger is still bound by what the wallet is allowed to pay.
  4. Treat every external call inside a balance change as hostile input. Fail closed when a callback errors, and never let a silent failure leave a credit standing.
  5. Monitor the invariants your own ledger creates, not only the seams between systems. A bridge balance check would never have caught this. A solvency check would have caught it on the first deposit.
  6. Rehearse the pause. 29 minutes from identification to pause is good. Make sure yours is as good, and make sure it can stop one asset or one route while the rest keeps running.
  7. Retire features that keep producing critical reports. Three in nine months is a signal, not a coincidence.
  8. Publish the postmortem, and reconcile publicly when funds come back. Closing an investigation on a partial public record leaves your users guessing.

How DFNS approaches it

This incident touches the core banking layer more than the signing layer. For each control below, we explain what the platform does and where the responsibility sits with the client.

Where balances come from. A DFNS wallet balance is read from the chain by our indexer, not from an internal ledger. For incoming transfers, the platform distinguishes detected from confirmed, and confirmation follows a threshold set per network. Unlike wallets, our vaults add a ledger on top and they were built around this distinction. An incoming balance is a separate state from an available balance and nothing becomes available until the chain has confirmed it. Every balance in a vault is the sum of entries you can list, and every entry points at the onchain event or operation that created it. If an event is ever missed on a wallet, Transaction Replay brings it back by hash through the same pipeline and with the same webhook as first-time indexing.

Reconciliation and the solvency invariant. Where a client runs its own omnibus ledger, a single record of what each of its users is owed, in front of DFNS wallets, the solvency check between that ledger and the wallets is the client’s to build (for now; contact product@dfns.co for more information on our roadmap). DFNS gives it the material. Signed webhooks for every transfer, signature, policy and onchain event, history endpoints, vault balance ledgers that decompose into entries, and SIEM Export, which puts the signed-action audit trail beside them in the client’s own monitoring. The invariant itself, and the automatic pause when it breaks, belong to the client’s system, because only the client knows what it owes.

Limits the ledger cannot corrupt. For organization-managed wallets, the Policy Engine evaluates every transfer before signing, with per transaction amount limits, velocity limits by amount and count over windows from one minute to thirty days, evaluated per source wallet, recipient allowlists, and approval quorums. These rules read the transaction, not the client’s ledger, so a corrupt ledger cannot lift them. Policies can be scoped by wallet tag, so treasury and payout wallets carry their own caps. Contract calls where the recipient or amount cannot be determined fail closed. The hole is the exemptions. An internal transfer path scoped out of the limits for speed reproduces this incident’s payout pattern.

What the Governance Engine verifies, and what it does not. Before a signer participates, the Governance Engine re-verifies the user action signature and checks the amount, asset, source, destination and timing against governed records and the policy in force. In the DFNS hosted model, this is an integrity check performed by DFNS. In hybrid and on-premises deployments, the client runs the signers and the Governance Engine itself. The engine does not read a client’s external ledger, and it cannot know whether that ledger is right. Its job is to make sure the platform’s own records and policies were not tampered with. That is exactly the boundary this incident draws. The engine protects the integrity of what the signer reads inside DFNS. It cannot protect the integrity of a balance sheet the client keeps elsewhere.

Pausing one thing without stopping everything. A client can block signing on its wallets with no DFNS action in the path, either across the organization or by tag, so the affected wallets stop while routine operations continue. The block takes effect on the next signing request. Vault outflows stop when the transfer permission is revoked.

External calls and third party routes. DFNS does not credit client balances from an intent network’s ledger: balances come from the chain. Cross-chain USDC can move through Circle’s CCTP for example, which burns on the source chain and mints on the destination, and swaps, including UniswapX’s intent-based orders, run through integrated providers under policy. A contract call from a wallet under a recipient allowlist or amount rule fails closed: it is blocked or sent for approval, because the engine cannot read its recipient or amount.

Issuance. For clients that issue stablecoins or tokenized assets, the Tokenization Engine runs minting and burning as policy-governed operations with approval quorums, so a mint cannot happen outside the issuer’s own rules. Whether a mint is allowed only against confirmed collateral is a rule the issuer sets, through its own approvers or a service account that checks the collateral. The platform enforces that approval before anything is signed.

Change control. Changes to the platform run under the change control our SOC 2 and ISO certifications cover, with independent penetration tests. None of that finds every asynchronous logic bug, and I would not claim it does. What it does is make sure every change is reviewed and traceable, and every finding we receive is tracked to closure.

Dangers, limits and nuances

Everything in this section applies to any provider, whatever its architecture.

No signer can fix a ledger. The validators here verified authorization inside a trusted execution environment and still paid out a lie. Any platform that promises its signing boundary makes theft impossible is overstating what signing can do. A signer that can authorize a payment cannot know whether the balance behind the payment is real. That is a different control, and it has to be owned by whoever runs the ledger.

Omnibus ledgers are where this risk lives. The moment an institution records entitlements separately from holdings, it has created an invariant that can break. The invariant is cheap to check and expensive to skip. Most institutions skip it because the numbers have always matched, which is exactly the condition under which nobody notices when they stop matching.

Limits bound loss, they do not detect lies. Caps and velocity rules would have turned a $3.8 million incident into a far smaller one. They would not have told anyone the ledger was corrupt. Detection and bounding are separate problems and need separate controls.

Fast recovery is the exception. This incident ended with the funds back in a day, after a public ultimatum and an onchain reply. That outcome depended on an attacker who chose to cooperate, for reasons nobody has disclosed. No provider and no institution can plan on it. Most thefts of this kind recover little or nothing, and industry trackers put the share of stolen funds recovered in the low single digits of percent in most years.

A quiet period is not evidence of safety. The ledger was insolvent for nearly forty hours while the product kept serving ordinary users, paying routine withdrawals, even settling the attacker’s own swaps. Systems fail quietly far more often than they fail loudly. The only way to know your books balance is to check.

Speed against control. Crediting on finality makes deposits slower. Solvency checks on every change cost compute. A quorum on large payouts means someone has to be awake. Every control here trades speed for certainty, and the right answer depends on what an institution is holding and for whom.

A checklist for this week

  • Write down the exact moment your ledger credits a deposit. If it’s before the chain confirms it, document what reverses the credit on failure and test that path.
  • Compute the sum of what you owe your users against what your wallets hold, per asset and per chain. If you cannot produce that number in under an hour, that’s a problem.
  • Turn that computation into a continuous check with an automatic pause.
  • List every feature that lets external code run inside a balance change and decide whether each one is worth its risk.
  • Put caps and velocity limits on your payout wallets, including the ones exempted “because they are internal.”
  • Time your pause, by asset and by route, with the person on call doing it.
  • Count the critical reports on each feature in the past year. Retire anything with more than one.
  • Decide how you would reconcile and publish if funds came back after a theft, before it happens.

Appendix

Step at NEAR IntentsWhat bounds it on DFNSWhere the control stops
Ledger credited before the token movement was finalWallet balances read from chain, detected and confirmed states kept separate, vault balances only available after confirmationApplies to DFNS wallets and vaults. A ledger the client keeps elsewhere follows the client’s rules
Liabilities exceeded holdings for nearly forty hours with no alarmWebhooks, history endpoints, vault entry ledgers supply the data for a continuous solvency checkThe check and the automatic pause are the client’s to build. Only the client knows what it owes
A correct signer paid against corrupt statePolicy Engine limits read the transaction, not the ledger. Amount caps, velocity per wallet, allowlists, quorumsBounds the loss. Cannot detect that a ledger is lying
Three payouts above the recent ceiling within an hour, no humanPer transaction and per window caps, quorum above a threshold, scoped by tag to payout walletsInternal exemptions reproduce the hole
Attacker’s contract called inside the deposit flowBalances change only on onchain events the indexer reads, never on a callback’s resultApplies to DFNS wallets and vaults. Callbacks in a client’s own contracts are the client’s to fail closed
Monitoring watched the bridge, not the booksWebhooks put every onchain event in the client’s monitoring; the audit log and SIEM Export add every signed actionThe right invariant is the client’s to define
29 minutes from identification to pauseClient-triggered block, organization-wide or by tag, effective on the next signing requestOutbound only. Nothing already broadcast. Wallets only: vault outflows stop by permission revocation
Routine withdrawals and swaps kept flowing during the drainFreeze by tag so affected wallets stop while others runRequires tagging discipline
A feature with a history of critical reports stayed in production until the thirdChange control under SOC 2 and ISO, independent penetration testsPaperwork does not find asynchronous logic bugs
Recovery by ultimatum, investigation closed without full reconciliationIncident process with a named Incident Commander, exportable audit log for reconciliationNo provider can promise recovery. This outcome is the exception

Sources

Related articles

Contact us