The Numbers Don't Lie: Code Exploits Are Losing to Social Engineering
Something shifted in crypto security in early 2026 — and most builders haven't noticed yet.
According to NOMINIS's monthly intelligence reports, authorization abuse — not smart contract exploits — is now the primary attack surface in crypto. In January 2026, a single phishing attack involving malicious token approvals accounted for $282 million, roughly 75% of all crypto losses that month. February's $49.3 million in losses told the same story: the Step Finance compromise, caused by executive device compromise leading to malicious transaction approvals, represented over 60% of the month's damage.
The uncomfortable truth? We've gotten good at auditing contracts. Reentrancy guards, formal verification, invariant testing — all battle-tested. But the industry's collective blind spot is the user-facing authorization layer: the moment a human signs something in their wallet.
This article dissects how Permit2 signature phishing works under the hood, why it's so effective, and what both builders and users can do to defend against it.
From approve() to Permit2: A Brief History of Token Approvals
To understand the attack, you need to understand the infrastructure it exploits.
The Original ERC-20 Model
The traditional ERC-20 approval flow requires two on-chain transactions:
1. User calls token.approve(spender, amount) → on-chain tx
2. Spender calls token.transferFrom(user, recipient, amount) → on-chain tx
This is expensive (two gas fees) and creates infinite approval footprints. Most dApps request type(uint256).max approval to avoid repeated transactions, leaving permanent standing permissions across the blockchain.
EIP-2612: The permit() Function
EIP-2612 introduced off-chain signatures for approvals:
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v, bytes32 r, bytes32 s
) external;
Users sign an off-chain message; anyone can submit it on-chain. Better UX, lower gas — but the off-chain nature means the approval happens invisibly. There's no on-chain record of the user's intent until the signature is consumed.
Uniswap's Permit2: Universal Off-Chain Approvals
Permit2 extended this pattern universally. Instead of each token implementing permit(), users grant a one-time approve() to the Permit2 contract, which then manages all subsequent approvals via off-chain signatures:
1. User approves Permit2 contract once → on-chain tx (one-time)
2. For each dApp interaction: User signs off-chain Permit2 message
3. dApp calls Permit2.permitTransferFrom() with signature → transfers tokens
The Permit2 contract at 0x000000000022D473030F116dDEE9F6B43aC78BA3 is deployed across Ethereum, Arbitrum, Optimism, Polygon, Base, and BSC. It holds universal approval over users' tokens once that initial approve() is granted.
This is the attack surface.
Anatomy of a Permit2 Phishing Attack
Here's the step-by-step kill chain that drained $6.3 million in January 2026 alone — a 207% increase from December:
Step 1: The Lure
The attacker creates a convincing phishing site mimicking a legitimate dApp. Common vectors:
-
Typosquatted domains:
uniswap-claim.cominstead ofapp.uniswap.org - Compromised frontend DNS: The BonkFun attack hijacked the legitimate domain itself
- Airdrop claim pages: "Claim your $TOKEN allocation" with urgent countdown timers
- NFT listing sites: "Sign to list your NFT" that actually signs a Permit2 transfer
Step 2: The Signature Request
The phishing site presents an eth_signTypedData_v4 request. This is an EIP-712 typed structured data signature that looks like a standard dApp interaction to most wallet UIs:
{
"types": {
"PermitBatch": [
{"name": "details", "type": "PermitDetails[]"},
{"name": "spender", "type": "address"},
{"name": "sigDeadline", "type": "uint256"}
],
"PermitDetails": [
{"name": "token", "type": "address"},
{"name": "amount", "type": "uint160"},
{"name": "expiration", "type": "uint48"},
{"name": "nonce", "type": "uint48"}
]
},
"primaryType": "PermitBatch",
"domain": {
"name": "Permit2",
"verifyingContract": "0x000000000022D473030F116dDEE9F6B43aC78BA3"
},
"message": {
"details": [
{
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"amount": "1461501637330902918203684832716283019655932542975",
"expiration": "1748736000",
"nonce": "0"
}
],
"spender": "0xATTACKER_ADDRESS",
"sigDeadline": "1711036800"
}
}
Notice:
-
amountistype(uint160).max— infinite approval -
expirationis set months into the future -
PermitBatchcan authorize multiple tokens in a single signature - The spender is the attacker's contract, not a legitimate protocol
Most wallet UIs display this as "Sign message" with minimal context. Users trained to "sign to interact" click through without reading the structured data.
Step 3: Silent Extraction
The attacker now holds a valid off-chain signature. They can execute permit() + transferFrom() on the Permit2 contract at any time before the deadline:
// Attacker's contract
function steal(
ISignatureTransfer.PermitBatchTransferFrom calldata permit,
ISignatureTransfer.SignatureTransferDetails[] calldata transferDetails,
bytes calldata signature
) external {
PERMIT2.permitTransferFrom(
permit,
transferDetails,
victim, // owner who signed
signature // the phished signature
);
}
The critical problem: the victim's on-chain transaction history shows nothing. The approval exists only as an off-chain signature. When the attacker finally drains the wallet, the transferFrom appears in the attacker's transaction history, not the victim's outgoing transactions.
Step 4: Delayed Execution
Sophisticated attackers don't drain immediately. The Permit2 signature remains valid until sigDeadline, giving attackers a window to:
- Wait for the victim to accumulate more tokens
- Batch multiple victim signatures into a single drain transaction
- Time the drain for gas-optimal conditions
- Create forensic confusion by separating the phishing event from the loss event
Why Traditional Defenses Fail
Approval Scanners Miss Off-Chain Signatures
Tools like Revoke.cash and Etherscan's Token Approval Checker scan on-chain approve() events. Permit2 signatures are off-chain until executed — they don't exist on the blockchain until the attacker uses them. A user can check their approvals, see nothing suspicious, and still have outstanding Permit2 signatures waiting to be exploited.
Hardware Wallets Don't Help (Much)
A Ledger or Trezor will show the EIP-712 signature data, but the display is limited to raw hex or minimally formatted structured data. Most users see:
Sign typed message
Domain: Permit2
Spender: 0x7a3b...4f2e
Without understanding Permit2 internals, this is meaningless. The hardware wallet faithfully asks the user to confirm — and they do.
Simulation Tools Have Gaps
Transaction simulation services like Blowfish, Pocket Universe, and Fire can detect some Permit2 phishing patterns. However:
- They work on transactions, not signatures — and Permit2 phishing targets the signature step
- Pattern matching requires known attacker addresses or contract patterns
- Novel phishing contracts evade signature-based detection until flagged
- Some wallets (especially mobile) don't integrate simulation extensions
The Solana Parallel: Authorization Abuse Without Permit2
Solana doesn't have Permit2, but it has its own authorization abuse problem. The ecosystem's setAuthority instruction allows reassigning token account ownership:
// Malicious instruction in a phishing transaction
spl_token::instruction::set_authority(
&spl_token::id(),
victim_token_account,
Some(&attacker_pubkey),
AuthorityType::AccountOwner,
victim_pubkey,
&[],
)?;
Once the victim signs a transaction containing this instruction (bundled with legitimate-looking instructions), the attacker becomes the permanent owner of that token account. No further signatures needed.
The Step Finance compromise in February 2026 demonstrated the enterprise variant: compromised executive devices led to malicious transaction approvals that drained approximately 261,854 SOL ($30 million). The attack targeted people with signing authority, not the smart contracts themselves.
Defense-in-Depth: What Actually Works
For Builders
1. Implement Permit2 Amount Limits
Stop requesting type(uint160).max. Request exactly the amount needed:
// BAD: Infinite approval
uint160 amount = type(uint160).max;
// GOOD: Exact amount needed
uint160 amount = uint160(depositAmount);
2. Short Expiration Windows
Set expiration to minutes, not months:
// BAD: 30-day expiration
uint48 expiration = uint48(block.timestamp + 30 days);
// GOOD: 30-minute expiration
uint48 expiration = uint48(block.timestamp + 30 minutes);
3. Human-Readable Signature Contexts
Use EIP-712 domain separators that clearly identify your dApp:
bytes32 DOMAIN_SEPARATOR = keccak256(abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256("YourDAppName - Token Deposit"), // Descriptive name
keccak256("1"),
block.chainid,
address(this)
));
4. Frontend Warning Layers
Display clear, non-dismissible warnings when users are about to sign Permit2 messages:
⚠️ You are authorizing transfer of 1,000 USDC to [Contract Name]
This approval expires in 30 minutes.
[Show Details] [Reject] [Approve]
For Users
1. Revoke Permit2's Master Approval
The nuclear option: revoke the Permit2 contract's base approve() on your tokens. This breaks all Permit2-based dApps but eliminates the attack surface entirely. Use Revoke.cash to target 0x000000000022D473030F116dDEE9F6B43aC78BA3.
2. Use Dedicated Signing Wallets
Separate your holdings wallet from your interaction wallet. Only the interaction wallet has Permit2 approvals. Move funds out after each session.
3. Verify Before Signing
For every eth_signTypedData request, check:
- Is the
verifyingContractthe legitimate Permit2 address? - Is the
spendera contract you recognize? - Is the
amountreasonable for the operation? - Is the
expirationshort-lived?
4. Browser Extension Defenses
Install Pocket Universe, Blowfish Protect, or similar transaction/signature simulation tools. They're not perfect, but they catch known patterns and flag suspicious spender addresses.
For Protocol Teams
1. Multi-Sig with Timelock for Hot Wallet Operations
The Step Finance and Bitrefill attacks exploited single points of human failure. Implement:
- Multi-sig requirements for any transfer above a threshold
- Timelock delays on large withdrawals
- Geographic/device-based signing restrictions
- Regular rotation of signing authority
2. Anomaly Detection on Authorization Patterns
Monitor for unusual authorization activity:
- Multiple
setAuthoritycalls in a single transaction - Permit2 signatures with
type(uint160).maxamounts from your frontend - New spender addresses appearing in your protocol's Permit2 flow
The Road Ahead: ERC-7715 and Intent-Based Approvals
The Ethereum community is actively working on this problem. Several proposals aim to replace the current approve/permit model:
ERC-7715 (Session Keys): Grants dApps temporary, scoped permissions through session keys rather than broad token approvals. Each session key specifies exactly which functions can be called, with what parameters, and for how long.
Account Abstraction (ERC-4337) + Permit Guards: Smart contract wallets can implement custom validation logic that rejects Permit2 signatures exceeding configurable thresholds or targeting unknown spenders.
Intent-Based Architectures: Instead of approving token transfers, users express intents ("swap 100 USDC for ETH at market price") that solvers fulfill. The user never grants direct transfer approval.
Until these ship at scale, the current Permit2 infrastructure remains the dominant — and exploited — standard.
Conclusion: The Most Dangerous Signature Is the One You Don't Understand
The data is clear: 2026's biggest crypto losses come from authorization abuse, not reentrancy bugs. The industry has spent years hardening smart contracts while leaving the human-wallet interface largely undefended.
The fix isn't purely technical. It requires:
- Builders to implement minimal-permission, short-lived approvals
- Wallet providers to surface Permit2 signature details in human-readable formats
- Users to treat every signature request as a potential fund transfer
- Protocol teams to assume their operators will be compromised and design accordingly
The smartest contract in the world can't save you if you sign away your tokens before it's ever called.
This analysis is part of an ongoing research series on DeFi security. Follow for weekly deep dives into the vulnerabilities, tools, and practices shaping Web3 security.
Top comments (0)