DEV Community

Cover image for Your App Will Meet a Freeze. Design for It
Azalea
Azalea

Posted on

Your App Will Meet a Freeze. Design for It

Last week Binance lost about $6.3 billion in roughly 20 minutes. Within hours, stablecoin issuers froze around $3 billion of it, Binance paused withdrawals for everyone, and analytics firms labeled the attacker's Bitcoin wallets in public.

Most of us will never run an exchange that size. But almost every crypto app touches at least one of the mechanisms that showed up that week: a hot wallet, a stablecoin with a blacklist, a deposit from a flagged address, a "withdrawals paused" banner. Each of them is a state your product needs to handle on purpose.

Here's what I'd take from it as a builder.

1. Watch your hot wallets for EIP-7702 delegations

Binance co-CEO Richard Teng said the attacker used an EIP-7702 delegation to gain execution authority over a hot wallet and bypass normal approvals, Cointelegraph reported. Binance says no private keys leaked.

EIP-7702 lets an externally owned account point to contract code. Once that happens, the address behaves partly like a smart contract. The spec marks a delegated account by setting its code to a fixed prefix, 0xef0100, followed by the 20-byte address of the delegate.

That makes it cheap to monitor. If an address you control suddenly has code, something changed:

import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";

const client = createPublicClient({ chain: mainnet, transport: http() });

export async function checkDelegation(address: `0x${string}`) {
  const code = await client.getCode({ address });
  if (!code || code === "0x") return { delegated: false };
  if (code.startsWith("0xef0100")) {
    return { delegated: true, delegate: `0x${code.slice(8, 48)}` };
  }
  return { delegated: true, delegate: "unknown contract code" };
}
Enter fullscreen mode Exit fullscreen mode

Run that on every hot wallet on a schedule, and alert on any change. It won't stop an attack, but it can shorten the gap between "something happened" and "someone noticed".

2. Check the issuer's blacklist before you pay out

Tether froze more than $1.5 billion in USDT during the hack and Circle more than $800 million in USDC. Both tokens have blacklist logic in their contracts. If your app sends stablecoins, an address can become unpayable at any moment. A blacklisted USDC address can't send or receive, so a transfer to it reverts.

Check before you build the transaction, not after it fails. Both contracts expose a read method, but the names differ by one letter: USDC uses isBlacklisted, USDT uses isBlackListed. Easy to get wrong, so keep the name per token:

const BLACKLIST_FN: Record<string, "isBlacklisted" | "isBlackListed"> = {
  USDC: "isBlacklisted",
  USDT: "isBlackListed",
};

export async function canReceive(
  symbol: "USDC" | "USDT",
  token: `0x${string}`,
  to: `0x${string}`,
) {
  const fn = BLACKLIST_FN[symbol];
  const abi = [{
    name: fn, type: "function", stateMutability: "view",
    inputs: [{ name: "account", type: "address" }],
    outputs: [{ type: "bool" }],
  }] as const;

  try {
    const blocked = await client.readContract({
      address: token, abi, functionName: fn, args: [to],
    });
    return !blocked;
  } catch {
    return null; // can't read it: fall back to simulating the transfer
  }
}
Enter fullscreen mode Exit fullscreen mode

The null branch still matters. Other stablecoins use other names or none at all, so when the check isn't available, simulate the transfer with eth_call and treat a revert as "not payable right now".

And tell the user plainly. "This address can't receive USDT" is better than a generic failure, and much better than a stuck payout.

3. Treat tainted deposits as a normal state

Bitcoin can't be frozen, so the 18,270 BTC from the hack can move whenever the attacker wants. What stops them is everyone else. Exchanges and swap services screen deposits and can refuse coins linked to flagged addresses.

If you accept deposits, some will eventually come from addresses that become flagged after the fact. Model that up front:

type DepositStatus =
  | { state: "confirming"; confirmations: number; required: number }
  | { state: "credited" }
  | { state: "under_review"; reason: "risk_screening"; since: string }
  | { state: "returned"; txHash: string };
Enter fullscreen mode Exit fullscreen mode

under_review needs its own screen with a clear explanation and an expected timeline. Users who see "pending" for two days with no reason assume the worst, and they're usually right to.

4. Withdrawals reopen per asset, not all at once

Binance paused withdrawals on October 29 and reopened them on November 1 in phases, starting with about 15 major assets and networks, CoinDesk reported. Hundreds of smaller tokens stayed locked.

That's the realistic recovery pattern after any incident: some chains come back first, others later. A single global "withdrawals enabled" flag can't express that. Store status per asset and per network, and show it next to each balance:

type WithdrawalStatus = Record<
  `${string}:${string}`, // e.g. "USDT:tron"
  { enabled: boolean; reason?: string; updatedAt: string }
>;
Enter fullscreen mode Exit fullscreen mode

In the first nine and a half hours after the reopening, users pulled about $4.65 billion out of Binance, according to Bitcoin.com News. Plan your queue and rate limits for the day the gate opens, not for a normal Tuesday.

5. Know where privacy coins still fit

During the hack week, Monero rose about 5.7%, and a reply that kept showing up under news posts was "XMR has no freeze switch". That's true, and it's also why regulated services keep dropping it. Kraken ended Monero trading for EEA clients in 2024. From July 10, 2027, Article 79 of the EU's Anti-Money Laundering Regulation bars regulated crypto providers from keeping accounts that allow anonymous transactions, including through coins like Monero.

If your product touches XMR, check where your users are before you ship a feature around it. This overview of whether it's safe and legal to exchange Monero for Ethereum has a country-by-country summary that's a reasonable starting point for that conversation with your legal team. It also flags the UX trap: after a swap to ETH, the user is on a public chain, and any address linked to their name is visible.

A short checklist

  • Monitor hot wallets for unexpected code, including EIP-7702 delegations.
  • Check stablecoin blacklists before building a payout, and simulate when you can't.
  • Give flagged deposits their own state and their own screen.
  • Keep withdrawal status per asset and per network.
  • Size your withdrawal queue for the reopening, not the average day.
  • Map which assets you can legally support in each market before you add them.

Freezes, pauses and flags aren't edge cases anymore. Last week all of them happened at once, to the biggest exchange in the world. Your app should already know what to show when one of them happens to a user.

Sources: Cointelegraph live coverage, CoinDesk, Bitcoin.com News, EIP-7702, EUR-Lex: Regulation (EU) 2024/1624.

Top comments (0)