hd.harry denley

Case studyWhite-hat recoveries2021–2022

WHG_1 gets there first.

This address (0x69670b0c1b100739812415dd474804bb32b3aeca) is one that I use to send all compromised funds I come across in the space. It has been involved in returning ~$1M to users.

By not using the public txpool, I was able to rescue assets before a bad actor was able to after compromising the private key

Case 09 · the biggest oneBlock 13,386,366
  1. #3Gas in0.0090 ETH from the funding addressView transaction 0xc588…5254 ↗
  2. #4Sweep1,141,159.2 HEX → WHG_1View transaction 0x8962…3d68 ↗

Two transactions, one block: the gas arrives and is spent before anyone else can touch it.

returned to users
~$1M
public cases
17
largest single case (Case 09)
$490k
sweeps in the same block as their gas
17/17
transaction types
6

How a rescue lands

The attacker has the same private key. Any ETH sent to the wallet for gas can be swept before it's spent, so the trick is never leaving it there.

April–July 2021 · Cases 02–05

Before London

The compromised wallet's transactions paid a 0 gwei gas price, so the wallet never needed ETH at all. In the same block, the funding address paid the block producer instead, through Flashbots' FlashbotsCheckAndSend contract.

Case 02Block 12,336,920
  1. #0Sweep · 0 gwei5,990.14 SWAP → WHG_1View transaction 0x8264…fcf8 ↗
  2. #1Miner payment0.025 ETH to the block producer via FlashbotsCheckAndSendView transaction 0xa9b8…ce7d ↗

August 2021 onwards · Cases 06–17

After London

EIP-1559 (the London fork, block 12,965,000) made 0-gwei transactions invalid. So each step became a pair: the funding address sends ETH for gas, and the compromised wallet spends it in the very next transaction of the same block.

Case 13Block 13,646,819
  1. #0Gas in0.0097 ETH from the funding addressView transaction 0xde63…89fd ↗
  2. #1Sweep16,660 XTM → WHG_1View transaction 0x093e…ee8d ↗

Six ways value gets stuck

Every case is one of these shapes. Pick one to see what it is and how it was moved.

Staking withdrawals

What it is
Tokens sitting in a staking contract can't be moved until the contract releases them. The attacker holds the same private key, so the moment they're released they're fair game for whoever moves first.
On-chain
Two calls from the compromised wallet. First the unlock: executeWithdrawal on TrustSwap staking, withdrawDelegated on The Graph staking, stakeEnd on HEX, or unstake on Staked TrueFi. Then an ERC-20 transfer of the released tokens to WHG_1.
Why it was risky
Both calls need ETH for gas in a wallet the attacker is watching, and an unlock broadcast to the public mempool announces that tokens are about to become movable.
How it was handled
Each call landed in the same block as the ETH that paid for it. Case 09 is the biggest: stakeEnd on HEX in block 13,386,363, then 1,141,159.2 HEX to WHG_1 three blocks later.
  1. Funding addressETH for gasCompromised wallet
  2. Compromised walletunlock callStaking contract
  3. Staking contracttokens releasedCompromised wallet
  4. Compromised wallettransfer()WHG_1

9 cases · $593.1k as written in the cases

The case files

Each case as I wrote it, with the transactions decoded underneath. Every hash links to Etherscan.

  1. Case 01

    The hand-back$76k

    I rescued a total of $76k from the compromised address. The assets were bridged over from Optimistic to Ethereum Mainnet and swept to WHG_1.

    • 4,763.53 SNX→ 0xef33…0003
    1 on-chain step · the hand-back out of WHG_1
    1. Hand-backBlock 12,318,583 · #2404,763.53 SNX → 0xef33…0003View transaction 0x4a76…f01b ↗
  2. Case 02

    Staking withdrawals$24k

    I rescued a total of $24k from the compromised address. The assets were unstaked from the TrustSwap staking contract and swept to WHG_1.

    • 5,990.14 SWAP→ WHG_1
    3 on-chain steps · every sweep in the same block as its gas
    1. UnlockBlock 12,336,882 · #0executeWithdrawal on TrustSwap stakingView transaction 0x879c…b179 ↗
    2. SweepBlock 12,336,920 · #05,990.14 SWAP → WHG_1View transaction 0x8264…fcf8 ↗
    3. Miner paymentBlock 12,336,920 · #10.025 ETH to the block producer via FlashbotsCheckAndSendView transaction 0xa9b8…ce7d ↗

    Compromised wallet 0xe38f…904f

  3. Case 03

    Staking withdrawals$19.2k

    I rescued a total of $19.2k from the compromised address. The assets were unstaked from TheGraph staking contract and swept to WHG_1.

    • 15,013.37 GRT→ WHG_1
    3 on-chain steps · every sweep in the same block as its gas
    1. UnlockBlock 12,419,886withdrawDelegated on The Graph stakingView transaction 0xd48b…6e3a ↗
    2. SweepBlock 12,419,991 · #015,013.37 GRT → WHG_1View transaction 0x818b…9221 ↗
    3. Miner paymentBlock 12,419,991 · #10.031 ETH to the block producer via FlashbotsCheckAndSendView transaction 0xaa60…296a ↗

    Compromised wallet 0x3b54…e767

  4. Case 04

    Staking withdrawals$2.9k

    I rescued a total of $2.9k from the compromised address. The assets were unstaked from the TrustSwap staking contract and swept to WHG_1.

    • 4,014.86 SWAP→ WHG_1
    3 on-chain steps · every sweep in the same block as its gas
    1. UnlockBlock 12,781,834 · #0executeWithdrawal on TrustSwap stakingView transaction 0x6207…656f ↗
    2. SweepBlock 12,781,837 · #04,014.86 SWAP → WHG_1View transaction 0x8469…5589 ↗
    3. Miner paymentBlock 12,781,837 · #10.01 ETH to the block producer via FlashbotsCheckAndSendView transaction 0xd4c1…0f33 ↗

    Compromised wallet 0x82b8…c419

  5. Case 05

    Staking withdrawals$9.5k

    I rescued a total of $9.5k from the compromised address. The assets were unstaked from TheGraph staking contract and swept to WHG_1.

    • 6,016.21 GRT→ WHG_1
    • 10,193.79 GRT→ WHG_1
    6 on-chain steps · every sweep in the same block as its gas
    1. UnlockBlock 12,829,303 · #5withdrawDelegated on The Graph stakingView transaction 0xbd49…c3e9 ↗
    2. SweepBlock 12,829,313 · #06,016.21 GRT → WHG_1View transaction 0xe200…2adc ↗
    3. Miner paymentBlock 12,829,313 · #10.005 ETH to the block producer via FlashbotsCheckAndSendView transaction 0x8ebe…1b9a ↗
    4. UnlockBlock 12,829,319 · #1withdrawDelegated on The Graph stakingView transaction 0x8eda…5472 ↗
    5. SweepBlock 12,829,321 · #110,193.79 GRT → WHG_1View transaction 0x66a9…467d ↗
    6. Miner paymentBlock 12,829,321 · #20.005 ETH to the block producer via FlashbotsCheckAndSendView transaction 0xd4e0…e2a6 ↗

    Compromised wallet 0xae97…42e4

  6. Case 06

    Staking withdrawals$20.6k

    I rescued a total of $20.6k from the compromised address. The assets were unstaked from TheGraph staking contract and swept to WHG_1.

    • 27,696.34 GRT→ WHG_1
    4 on-chain steps · every sweep in the same block as its gas
    1. Gas inBlock 12,995,351 · #30.0068 ETH from the funding addressView transaction 0xd424…5935 ↗
    2. UnlockBlock 12,995,351 · #4withdrawDelegated on The Graph stakingView transaction 0x66fe…09bd ↗
    3. Gas inBlock 12,995,354 · #20.0023 ETH from the funding addressView transaction 0x525f…e9de ↗
    4. SweepBlock 12,995,354 · #327,696.34 GRT → WHG_1View transaction 0xff79…2d43 ↗

    Compromised wallet 0xc80e…79fb

  7. Case 07

    Staking withdrawals$10.4k

    I rescued a total of $10.4k from the compromised address. The assets were unstaked from the TrustSwap staking contract and swept to WHG_1.

    • 9,399.88 SWAP→ WHG_1
    4 on-chain steps · every sweep in the same block as its gas
    1. Gas inBlock 13,262,889 · #20.0261 ETH from the funding addressView transaction 0x6941…594a ↗
    2. UnlockBlock 13,262,889 · #3executeWithdrawal on TrustSwap stakingView transaction 0x0af1…b8b9 ↗
    3. Gas inBlock 13,262,898 · #10.0117 ETH from the funding addressView transaction 0x1083…064c ↗
    4. SweepBlock 13,262,898 · #29,399.88 SWAP → WHG_1View transaction 0x5d48…5f20 ↗

    Compromised wallet 0xe097…e1e0

  8. Case 08

    Staking withdrawals$7.7k

    I rescued a total of $7.7k from the compromised address. The assets were unstaked from TheGraph staking contract and swept to WHG_1.

    • 10,884.62 GRT→ WHG_1
    4 on-chain steps · every sweep in the same block as its gas
    1. Gas inBlock 13,334,240 · #20.0202 ETH from the funding addressView transaction 0x376a…e6b4 ↗
    2. UnlockBlock 13,334,240 · #3withdrawDelegated on The Graph stakingView transaction 0xca62…b71d ↗
    3. Gas inBlock 13,334,248 · #00.0065 ETH from the funding addressView transaction 0xc98d…eaf9 ↗
    4. SweepBlock 13,334,248 · #110,884.62 GRT → WHG_1View transaction 0xc397…715b ↗

    Compromised wallet 0x8a09…c3a9

  9. Case 09

    Staking withdrawals$490k

    I rescued a total of $490k from the compromised address. The assets were unstaked from the HEX staking contract and swept to WHG_1.

    • 1,141,159.2 HEX→ WHG_1
    4 on-chain steps · every sweep in the same block as its gas
    1. Gas inBlock 13,386,363 · #00.0255 ETH from the funding addressView transaction 0xd288…56e2 ↗
    2. UnlockBlock 13,386,363 · #1stakeEnd on the HEX contractView transaction 0x16bb…35ed ↗
    3. Gas inBlock 13,386,366 · #30.0090 ETH from the funding addressView transaction 0xc588…5254 ↗
    4. SweepBlock 13,386,366 · #41,141,159.2 HEX → WHG_1View transaction 0x8962…3d68 ↗

    Compromised wallet 0xe097…e1e0

  10. Case 10

    Staking withdrawals$8.8k

    I rescued a total of $8.8k from the compromised address. The assets were unstaked from the TruFi staking contract and swept to WHG_1.

    • 20,402.77 TRU→ WHG_1
    4 on-chain steps · every sweep in the same block as its gas
    1. Gas inBlock 13,406,749 · #110.0599 ETH from the funding addressView transaction 0x41ee…ed3f ↗
    2. UnlockBlock 13,406,749 · #12unstake on Staked TrueFi (stkTRU)View transaction 0xd67f…e67d ↗
    3. Gas inBlock 13,406,755 · #00.0112 ETH from the funding addressView transaction 0x4586…6702 ↗
    4. SweepBlock 13,406,755 · #120,402.77 TRU → WHG_1View transaction 0x67a5…38f0 ↗

    Compromised wallet 0x470b…c5b9

  11. Case 11

    The hand-back$3.5k

    I rescued a total of $3.5k from the compromised address. The assets representing the staked amount in the TruFi staking contract were swept to WHG_1.

    • 2,106 stkTRU→ 0x4a88…1e3f
    1 on-chain step · the hand-back out of WHG_1
    1. Hand-backBlock 13,646,583 · #1102,106 stkTRU → 0x4a88…1e3fView transaction 0xcdd0…ce40 ↗
  12. Case 12

    The hand-back$1.2k

    I rescued a total of $1.2k from the compromised address.

    • 2.1137 PBR→ 0x75b8…1c30
    • 0.2349 PBR→ Zero address
    • 467.35 PBR→ 0x4a88…1e3f
    1 on-chain step · the hand-back out of WHG_1
    1. Hand-backBlock 13,646,439 · #146467.35 PBR → 0x4a88…1e3fView transaction 0xbe16…846c ↗
  13. Case 14

    Receipt tokens$46.9k

    I rescued a total of $46.9k from the compromised address. It required redeeming tokens on the MantraDAO ZENTREST contract to unlock the value.

    • 216,695.03 OM→ WHG_1
    4 on-chain steps · every sweep in the same block as its gas
    1. Gas inBlock 13,646,992 · #50.0488 ETH from the funding addressView transaction 0x3a7a…1b4e ↗
    2. UnlockBlock 13,646,992 · #6redeem on ZENTEREST OMView transaction 0xe3e1…9dab ↗
    3. Gas inBlock 13,646,995 · #00.0135 ETH from the funding addressView transaction 0xe3af…5655 ↗
    4. SweepBlock 13,646,995 · #1216,695.03 OM → WHG_1View transaction 0x4996…b05c ↗

    Compromised wallet 0x222c…eedb

  14. Case 15

    Staked liquidity$15k

    I rescued a total of $15k from the compromised address. It required transferring a liquidity position on SushiSwap to WHG_1.

    • 3.2202 SLP→ WHG_1
    • 3.2886 SLP→ WHG_1
    10 on-chain steps · every sweep in the same block as its gas
    1. Gas inBlock 13,680,551 · #00.0430 ETH from the funding addressView transaction 0x7888…9658 ↗
    2. UnlockBlock 13,680,551 · #1unstake on the Illuvium core poolView transaction 0x9bfd…1144 ↗
    3. Gas inBlock 13,680,563 · #10.0147 ETH from the funding addressView transaction 0xa2a5…a69f ↗
    4. SweepBlock 13,680,563 · #23.2202 SLP → WHG_1View transaction 0x4890…bb8f ↗
    5. Gas inBlock 13,680,677 · #00.0535 ETH from the funding addressView transaction 0x5db5…d621 ↗
    6. UnlockBlock 13,680,677 · #1processRewards on the Illuvium core poolView transaction 0x5d03…77db ↗
    7. Gas inBlock 13,680,685 · #30.0366 ETH from the funding addressView transaction 0xfd3e…ccc6 ↗
    8. UnlockBlock 13,680,685 · #4unstake on the Illuvium core poolView transaction 0xe30a…2f81 ↗
    9. Gas inBlock 13,680,688 · #00.0090 ETH from the funding addressView transaction 0xaaa2…ac9b ↗
    10. SweepBlock 13,680,688 · #13.2886 SLP → WHG_1View transaction 0x4425…e885 ↗

    Compromised wallet 0x6214…9798

  15. Case 17

    NFTs and grants$31k + $160k

    I rescued 1x Doodles NFT (floor ~12E / $31k) and an NFT representing an investment token lockup worth ~$160k.

    6 on-chain steps · every sweep in the same block as its gas
    1. Gas inBlock 13,998,872 · #00.0841 ETH from the funding addressView transaction 0x871a…4566 ↗
    2. UnlockBlock 13,998,872 · #1acceptGrant on the Goldfinch MerkleDistributorView transaction 0x515e…4dfc ↗
    3. Gas inBlock 13,998,874 · #00.1609 ETH from the funding addressView transaction 0x0311…230e ↗
    4. SweepBlock 13,998,874 · #1Goldfinch V2 Community Rewards Tokens #206 → WHG_1View transaction 0x662c…78d5 ↗
    5. Gas inBlock 13,999,519 · #210.0146 ETH from the funding addressView transaction 0x91bf…a5ef ↗
    6. SweepBlock 13,999,519 · #22Doodles #9038 → WHG_1View transaction 0x4816…0dab ↗

    Compromised wallet 0x2fa4…85d1

Steps, blocks and amounts are decoded from the public transactions: receipts and Transfer logs, with call names from verified contracts. The funding address 0x44a3…77f2 appears in every rescue from Case 02 to Case 17.

Next case studyKyberCommunityPool A user interface and smart contract that allowed users to stake their $KNC