BLOCKCHAIN AI.NEWS

Security · Analysis

$736,442 Left Chainflip Through Refunds for Swaps That Already Paid

An attacker got Chainflip's Tron USDT route to pay against the same deposit twice, six times over. Chainflip's public code shows that on Tron, unlike on Ethereum, it reads swap instructions from a transaction memo. Its own account of the attack leaves one step unexplained.

Editorial illustration: a chrome coin in a frosted glass tray with a blank tag, and two chrome pipes leading away from it, one dripping gold and one glowing blue
✓ Disclosed by Chainflip (Sep 13) · Reported by crypto.news and The Crypto Times · Code read by this desk: Chainflip's Tron vault-swap witnesser and Ethereum Vault.sol · Transaction format from the TRON Developer Hub

Early on Saturday, September 12, someone found a way to make Chainflip pay for the same Tron deposit twice. They did it eight times in about ninety minutes, starting small and roughly doubling the size of each round. By the time Chainflip noticed, because outgoing USDT payouts had started to fail, six unauthorized payouts had gone out, worth 736,442.17 USDT. That's according to the post-incident note the cross-chain swap protocol published at 10:11 UTC on Sunday. A seventh swap, worth 115,654.41 USDT, was caught while still pending, and those funds stayed in the vault.

Chainflip paused the whole network, not only the Tron route. It says it has a fix and expects to stay offline "until Monday at the earliest." It also says users who were affected will be made whole, though it hasn't said where the money will come from. crypto.news quoted the protocol's post on X: "All other funds are unaffected and secure."

What Chainflip says happened

Chainflip's explanation is short. On Tron, it reads a swap's instructions from the memo attached to the transaction. On its other chains, instructions arrive through dedicated contract functions. According to Chainflip, the attacker attached a new memo to a transaction its validators had already signed. The system read that memo as a separate swap, decided the swap had failed, and issued a refund. But the original deposit had already been paid out. So the refund was a second payment against the same money.

According to crypto.news, Chainflip hasn't published transaction hashes, attacker addresses or a per-round breakdown. As of Sunday evening, this desk had found no independent analysis from a security firm. So the details below come from Chainflip's own statement and its public source code. Neither has been checked against the chain.

Where the memo comes in

Chainflip's backend is open source, and the Tron witnessing code is a single file, vault_swaps_witnessing.rs. It was added in the Tron integration commit of May 7 and hasn't changed since. This desk read it. The code watches each Tron block for native TRX arriving at the vault address and for token Transfer events whose recipient is the vault. It ignores the sender. For every match, it fetches the full transaction by ID, takes the memo from the transaction's raw_data.data field, and decodes it into a swap instruction: output asset, destination address, optional cross-chain message, and a parameters blob. The file's own test shows that blob carrying refund settings, including a refund address.

Ethereum works differently. A vault swap there is a call to xSwapToken on Chainflip's Vault contract. In a single function, the contract pulls the tokens from the caller and emits a SwapToken event that carries the amount, the instruction and the sender together. The money and the instruction come from one call.

How a Chainflip vault swap tells the protocol what to do

Ethereum vaultTron vault
What the deposit isA call to the Vault contract's xSwapTokenAny transfer whose recipient is the vault address
Where the instruction livesArguments in that same call, echoed in a SwapToken eventThe memo field of whichever transaction carried the transfer
Sender recorded by the witnesserYes, msg.sender in the eventNo, the from field is ignored
Compiled by this desk from Vault.sol and vault_swaps_witnessing.rs on the public main branches as of Sep 13, 21:00 UTC. Deposit-channel swaps, which don't use memos, aren't shown. This is what the public code does. It isn't a confirmed description of the path used in the attack.

None of that proves how the attack worked. What it does show is why Tron was different. On Tron, the only thing tying a deposit to an instruction is that they share a transaction ID. Whoever wrote the transaction wrote the memo.

The step that needs explaining

That brings up a puzzle in Chainflip's own wording. TRON's developer documentation calls raw_data "the signed payload." It lists data as "an optional memo" inside it, and it says a signature is computed over a SHA-256 hash of the serialized raw_data. Read literally, a memo is part of what a Tron signature covers. You can't add one to an already-signed transaction without breaking the signature.

So "attach a new memo to a transaction that Chainflip validators had already signed" is probably shorthand for something more specific. It could mean a signed payload used somewhere Chainflip didn't expect, or a validator-signed transfer landing in a transaction someone else wrote. This desk isn't going to guess. Chainflip has promised a fuller technical report, and this is the sentence it most needs to spell out.

The fix isn't public yet either. The latest push to the public repository's main branch was on September 11 at 15:41 UTC, the day before the attack, and as of Sunday evening no commit touching the Tron witnesser had appeared. That's normal. Projects routinely ship security fixes privately and publish the code after the patched version is running.

A young route with real volume

Tron is a recent addition. Its integration code only merged in May. In a September 10 post announcing a USDT-TRC20 lending market, the team said Tron swaps had processed $25.63 million across 956 swaps in their first thirty days and called it "one of the strongest launches Chainflip has run." By this desk's arithmetic, the six payouts equal about 2.9% of that first month's volume. Chainflip hasn't linked the new lending market to the attack, and nothing in its account suggests a connection.

Chainflip says it has flagged the taken funds with "relevant parties" so they can be traced as they move. It hasn't said whether any of the USDT has been frozen, or how much it expects to get back.

The Take

A refund is a payout. Any code path that sends money back has to be keyed to the deposit it's refunding, and has to know whether that deposit has already been spent. It can't be keyed to whatever instruction happened to arrive with it. Chainflip's Ethereum design gets this almost for free, because one contract call ties the money, the sender and the instruction together. Its Tron design has to rebuild that link by hand from a transfer log and a memo. By Chainflip's own account, that's the route where one deposit got counted twice. Chainflip's response so far has been fast and plain: a same-day note, a whole-network pause, and a promise to repay users. The test now is the report. It should explain exactly what "already signed" meant, show the transactions, and say whether any other chain it supports reads instructions the same way.

More on the subject