CryptoReal
CASE FILE — Jun 4, 2026

How a Fake Denom String Let an Attacker Walk Off With $5.4M From Gravity Bridge

Gravity Bridge, the Ethereum-Cosmos connector marketed as a decentralized alternative to multisig-based bridges, was emptied on May 29th. No private key was stolen and no signature scheme was broken. Instead, an attacker convinced the bridge's own token registry to trust a fabricated piece of data.

By manufacturing worthless tokens on Osmosis, folding real Ethereum custody-contract addresses into a fake denomination string, and feeding that string into a permissionless registration function, the attacker corrupted the mapping the bridge uses to match Cosmos assets to their Ethereum equivalents. Once that mapping was poisoned, everything downstream operated normally: validators reviewed and signed the resulting withdrawal batches exactly as designed, with no way of knowing the registry entries behind those signatures had been tampered with.

The result was a clean sweep of $4.3M in USDC, 274 WETH, $434K in USDT, and 14.16 PAXG — gone before any public warning appeared. By the time analyst Specter raised the alarm, the assets were already in transit.

Notably, the safeguard that could have stopped this already existed in the bridge's own codebase — it simply wasn't invoked at the point where it mattered. The bridge stayed offline well after the incident, with the team offering no postmortem and no disclosure of the exact entry point.

Reaching that point required a single, cheap preparatory step: registering as a Gravity chain validator with a stake of just 80 GRAV — enough to submit claims alongside genuine validators, without needing meaningful voting power.

Every signature in the process was legitimate. Every withdrawal batch checked out. The bridge behaved precisely as coded; it simply never questioned whether the data it was acting on was genuine.

Credit: CoinTelegraph, Specter, QuillAudits, Gravity Bridge, PeckShield, ForgeAudit, Popeye, TheBlock, Blockchain News, AMB Crypto, Least Authority.

01The first alarm

On-chain analyst Specter was the first to publicly flag the incident. On May 29th, the post was unequivocal: "It appears the Gravity Bridge - bridge contract key may have been compromised, resulting in the theft of $5.4M."

The alert named four stolen assets, published two addresses tied to the theft, and identified the vulnerable contract — but it landed only after the funds had already moved.

Primary theft wallet: 0x7B582033061b96cC3F9421e73a749ED7C62da1F9

Secondary theft wallet: 0x4d3ca32e687e871a58b78AcAc73bE59AC37C7A47

Vulnerable contract: 0xa4108aa1ec4967f8b52220a4f7e94a8201f2d906

PeckShield corroborated the figure shortly after, citing roughly $5.4M drained, including $4.3M in USDC and 274 ETH.

Early analysis from security firms leaned toward a compromised contract key or signing pathway — a plausible read given what the Ethereum-side transaction history showed. What none of the initial reports captured was the Cosmos-side setup that had quietly preceded the Ethereum withdrawals.

Gravity Bridge's own communication came in two short posts. The first, roughly seven hours after Specter's initial alert, read: "There was an unfortunate incident on Gravity. Validators should halt their validators and orchestrators while this incident is being investigated." Ninety minutes later, a follow-up added: "Thanks to the swift action of validators, the bridge is currently halted while investigations continue."

That was the extent of it — no loss figure, no technical explanation, and no mention of the 282-day-dormant wallet, the reduced validator set, or the sequence of events researchers had already reconstructed publicly. TheBlock reported it could not immediately reach the Gravity Bridge team for comment. Independent researchers had mapped the mechanics within hours; the project itself offered two sentences.

02Building the poisoned registry entry

The exploit actually began on Cosmos, well before the attacker touched Osmosis or Ethereum, with the creation of a minimal validator presence.

The attacker registered a Gravity chain validator under the moniker julia666, self-delegating just 80 GRAV — address gravityvaloper1rdwckpx2p3mwu4k8xdz8vmd4x3mqx4v9ars8k. That token amount was sufficient to clear the checkOrchestratorValidatorInSet check that gates who may submit claims, letting the attacker's orchestrator submit MsgERC20DeployedClaim messages alongside legitimate validators, without holding any real voting weight.

Create-validator transaction: F6AA34E8D01C55A5F1E38310E816DBB4BEE25B2FDB29ACD7ED1E25352BA62009

Orchestrator Ethereum key: 0x91B52e07132a49DAD1B7a3939a51547f79468Ec7

With that foothold in place, the attacker moved to Osmosis. Its tokenfactory module allows any address to mint arbitrary tokens at no cost. Using an Osmosis address, the attacker created four counterfeit tokens, each shadowing a real asset held in Gravity Bridge's Ethereum custody: USDC, USDT, WETH, and PAXG.

Attacker address on Osmosis: osmo1m9athjzah02f2mnrgtcke7e5ya3zpvw8lccuss

These four tokens carried no value of their own. After minting, they were IBC-transferred to Gravity chain over channel-144, each landing with a standard ibc/HASH denom identifier.

Once the tokens arrived on Gravity chain, the attacker shifted to Ethereum, where Gravity Bridge exposes a permissionless deployERC20() function that anyone can call to register a Cosmos-native token for use on Ethereum. The attacker called it four times from a single Ethereum address, each call supplying a manipulated _cosmosDenom argument.

Attacker address on Ethereum: 0x73E95aE5f3b87e02D4547AFE86d0d466e9450d6B

Instead of the plain ibc/HASH value Gravity had actually assigned to each token, the attacker inserted the real Ethereum contract address of the corresponding custody asset as a path segment within the denom string itself. The USDC call, for instance, passed:

ibc/C92D312D79D9C44B6C6F94AF40FFCB30A334D87F08952D6ED9904E3E83A9F50C/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/transfer/channel-10/factory/osmo1m9athjzah02f2mnrgtcke7e5ya3zpvw8lccuss

The call deployed a fresh CosmosERC20 wrapper contract and emitted an ERC20DeployedEvent carrying that fabricated string unchanged — the function performed no validation whatsoever on what _cosmosDenom actually contained.

Fake USDC deploy: 0x92b2a61d88b3f68d7d3b5cba4a12f53e0c226287702d72de80a60515987e3532

Fake USDT deploy: 0x364ebefab8e7c8ddd425447349cc99dcd75ab1751612f7ef9c60c20fdb9a2bb6

Sums referenced in this case file

Fake WETH deploy: 0x871433af303743d61b3f2a38174e9e6655fdc8937c0689122685509dcabbf066

Fake PAXG deploy: 0x67a181c333cb75231613c125215168a1eacbb4021d07cf0d91ca8bbaa65f8245

Gravity validators monitoring Ethereum picked up these events and submitted MsgERC20DeployedClaim acknowledgments back to Gravity chain, where handleErc20Deployed wrote the fabricated denom into the bridge's trusted denom-to-ERC20 registry — poisoning the exact lookup table the bridge relies on for withdrawals.

MsgERC20DeployedClaim - USDC: 786C41F0B9DA6EB8F59A1BC3299504AE47EFC4BBAB13673E391E307DB1BCEE49

MsgERC20DeployedClaim - USDT: 9DF358F96E2BDE91FA96D1CC1CBEBE42E5CA2DD2F5745CD2ADA2591751101A9D

MsgERC20DeployedClaim - WETH: 388112D6C4BEDD74D34157AEC689195B942A8EDC4F476C4230C638CDF399E499

MsgERC20DeployedClaim - PAXG: 14BC7A377F959973613473A5EFB623D4011AED86BE00D68B782DB6D6720F9A37

The two coding gaps that made it possible

The first weakness sits in deployERC20() on Gravity.sol: the function is permissionless by design and accepts _cosmosDenom as an arbitrary string with no content checks at all. The attacker exploited that openness directly, embedding the genuine USDC contract address inside the fake denom string, and the function accepted it without objection.

The second — and more interesting — weakness is in handleErc20Deployed on the Gravity chain side. This function does check denomination metadata, confirming that a deployed ERC20's name, symbol, and decimals line up with what's registered for that denom. But that check offered no real protection, because the underlying IBC metadata is generated automatically and predictably — meaning the attacker could satisfy it by design rather than by accident.

When the attacker's fake Osmosis tokens were IBC-transferred to Gravity chain, ibc-go automatically invoked SetDenomMetadata on the receiving chain, assigning deterministic values: a name of "<path> IBC token," a symbol equal to the uppercased base denom, and zero decimals. Because those values were fully predictable in advance, the attacker simply set the _name and _symbol parameters of the deployERC20() call to match them, while decimals defaulted to zero regardless. Every metadata check passed — not because the system failed to check anything, but because the thing it checked was exactly what the attacker had engineered it to check.

In effect, the bridge treated auto-generated IBC metadata as a reliable proof of identity, a Cosmos-side trust and registry-collision failure whose consequences only became visible once the Ethereum withdrawal batches went out. What was missing was a collision check inside handleErc20Deployed that would have rejected a registration conflicting with an existing entry. That kind of lookup existed elsewhere in the same source file — it just wasn't called from this particular function.

For what it's worth, the Rust-based orchestrator component does not appear to be at fault; it relayed the wrapper contract address correctly, straight from topics[1] of the Ethereum event log. The actual failure lives in the Cosmos-side claim-handling logic, which trusted predictable metadata and skipped the cross-check that would have caught the collision. A related safeguard existed in the codebase — it simply wasn't wired into the function that needed it.

03Turning the poisoned entry into a payout

Once the registry was corrupted, the attacker submitted MsgBatchSendToEthClaim withdrawal requests. DenomToERC20Lookup returned the real, legitimate token addresses for the poisoned entries — because that's exactly what had been written into the registry. Gravity generated outgoing batches numbered 41572 through 41575, validators signed them as usual, and relayers forwarded them to the Ethereum custody contract, which verified the signatures and executed safeTransfer() for each asset — releasing 4,349,701 USDC, 434,072 USDT, 274.34 WETH, and 14.16 PAXG to the attacker, with no alert triggered anywhere in the process.

USDC drain ($4.3M): 0xfce883a8f9a4f3479cce1368b99287973ab40451ac092f5a41e1e09eecab5044

USDT drain ($434K): 0x469274f4edd45ec3284bf60de8eb30086222745f8f8c8b6a955137feed41281d

WETH drain (274 ETH — $495K): 0x59e52302c53e862fcf833b61eb851ff66e098e3d29db19fd66e5a04734eeb84b

PAXG drain ($64K): 0xd3cdfa10be4f0cde6b3f294856a8bdd840983dcef29a5dc24c70c36e2d7f9ef0

Afterward, the attacker converted the proceeds to ETH and began moving them. Funds flowed from the primary holding wallet (0x4d3ca32e687e871a58b78AcAc73bE59AC37C7A47) into a dedicated laundering address (0xc8c71ae4261e55a66d9967f2ac252be4e669f562), which has since been routing ETH into Tornado Cash in 10-and-100-ETH deposits, in batches spread across several days.

Primary theft wallet: 0x7B582033061b96cC3F9421e73a749ED7C62da1F9

Secondary theft wallet (primary fund holder): 0x4d3ca32e687e871a58b78AcAc73bE59AC37C7A47

Tornado Cash routing address: 0xc8c71ae4261e55a66d9967f2ac252be4e669f562

Other attacker EOAs: 0x73e95ae5f3b87e02d4547afe86d0d466e9450d6b, 0xDaf38ad7f8F7431978d6bFffa78A23e4c5f010c6, 0x91B52e07132a49DAD1B7a3939a51547f79468Ec7

The theft itself was fast and clean; the laundering has been slower and deliberate. Tornado Cash requires no KYC and has no operator to compel for records — each deposit vanishes into the pool, and the on-chain trail effectively stops there. As of this writing, the attacker has issued no statement and the funds continue moving into the mixer in stages.

04The fallout

Gravity Bridge's public statement on May 30th — "Thanks to the swift action of validators, the bridge is currently halted while investigations continue" — remains, to date, the extent of the team's disclosure. There has been no restart date, no compensation plan, no postmortem, and no explanation of how a fabricated denom string slipped through a permissionless function or how the registry was corrupted without setting off any alarm.

Markets reacted modestly: GRAV fell roughly 4% on the news before partially recovering to $0.0009851 by May 31st, a 1.01% gain on the day that some read as early stabilization. Market capitalization at that point stood at approximately $958,510. More severe was the liquidity impact: TVL fell 47%, from $11.82 million to $6.2 million, in a single day — the protocol had little in the way of reserves to cushion the loss.

There was no white-hat bounty offer, no on-chain message to the attacker, no wallet-flagging effort, and no visible coordination with exchanges. What the bridge offered instead was a halt notice with no recovery plan attached — which, absent any roadmap, reads less like a pause than an indefinite stop.

05Part of a rough year for bridges

Gravity Bridge's loss arrives inside a difficult stretch for cross-chain infrastructure generally. PeckShield had already tallied eight major bridge exploits totaling $328.6 million in 2026 by mid-May — before this incident even occurred. In April, KelpDAO lost approximately $290 million in an attack attributed to the Lazarus Group.

Against those numbers, Gravity Bridge's $5.4M loss is comparatively small. What sets it apart is the combination of factors involved: a permissionless registration function with no input validation, a claim handler that wrote incoming data straight into the registry without checking it against existing ERC20 mappings, a 2022 Least Authority audit that predates this class of cross-chain attack and was never refreshed to account for it, and a post-incident response that gave affected users no bounty, no postmortem, and no timeline.

06What actually broke

Gravity Bridge wasn't beaten by a clever exploit of a single bug — it fell through a gap between two components that should have cross-checked each other and didn't: a permissionless Ethereum-side input path, and a Cosmos-side claim handler that trusted what it was given.

deployERC20() accepted arbitrary string input without restriction. handleErc20Deployed did check metadata, but never cross-referenced the incoming registration against the bridge's existing custody-asset registry — despite a usable lookup for exactly that purpose sitting elsewhere in the same file, correctly called in one function and never called in the other.

Gravity Bridge's own GitHub documentation is explicit about the underlying design: the bridge accepts event-based claims once the validator set signs off on them, rather than independently re-verifying the underlying Ethereum event. In this case, validators attested honestly to what they observed; the claim-processing logic then committed a corrupted mapping to state. Every signature was valid. Every batch was properly formed. The assets moved were real. A 2022 Least Authority audit had reviewed the codebase years before this particular failure mode had been demonstrated in the wild.

The silence that followed the exploit mirrored the silence that preceded it: two brief posts, a halt notice, and — as of this writing — no postmortem, no compensation commitment, and no acknowledgment of the specific gap: a missing cross-check, an unrestricted entry point, and a registry willing to accept whatever it was handed.

The basic principle at stake isn't new or obscure — anything crossing a chain boundary should be treated as untrusted input, full stop. Gravity Bridge accepted a fabricated string from an anonymous caller, committed it to a registry that gated access to millions of dollars in real assets, and never ran the comparison that would have caught the conflict. The relevant safeguard existed in the codebase; it simply wasn't positioned where it needed to be. The deeper question isn't how the attacker located the gap — it's how three years of live operation passed without anyone else finding it first.

Bridge AttackGravity Bridge
Investigation alerts

Get new scam files the moment we publish them — usually 2–3 emails a week.

Enter a valid email address.

No spam, unsubscribe anytime. We never sell your data. Crypto assets are volatile and high-risk; nothing here is financial advice.

You're on the list. Watch your inbox for the next scam file.