The $25M Resolv USR Exploit: Why Your Minting Function's Off-Chain Signer Is the Biggest Single Point of Failure in DeFi
On March 22, 2026, at 2:21 AM UTC, an attacker turned roughly $200,000 in USDC into $25 million in extracted ETH. No flash loan. No reentrancy. No oracle manipulation. They compromised a single AWS KMS key that controlled Resolv Labs' stablecoin minting function — and the smart contract had zero on-chain guardrails to stop what happened next.
80 million unbacked USR tokens were minted in two transactions. USR crashed from $1.00 to $0.025 in 17 minutes. Lending vaults on Morpho, Euler, and Curve took collateral damage. And every DeFi protocol using privileged off-chain signers for critical operations should be asking: could this happen to us?
The answer, for most, is yes.
How Resolv's Minting Worked (And Didn't)
Resolv's USR stablecoin used a two-step minting flow:
-
requestSwap()— User deposits USDC, creating a pending mint request -
completeSwap()— An off-chain service (theSERVICE_ROLE) calls back to finalize how much USR to mint
The critical design flaw: the contract enforced a minimum USR output but no maximum. There was no on-chain ratio check between deposited collateral and minted tokens. No price oracle validation. No minting cap. Whatever the SERVICE_ROLE key authorized, the contract executed.
// Simplified Resolv minting logic (VULNERABLE)
function completeSwap(
uint256 requestId,
uint256 usrAmount, // No upper bound!
bytes calldata signature
) external {
Request storage req = requests[requestId];
require(req.status == Status.Pending, "Invalid request");
// Verify SERVICE_ROLE signature
bytes32 hash = keccak256(abi.encode(requestId, usrAmount));
require(verifySignature(hash, signature, SERVICE_ROLE), "Bad sig");
// Mint whatever amount the signer says
// No check: usrAmount <= req.usdcDeposited * MAX_RATIO
// No check: usrAmount <= GLOBAL_MINT_CAP
// No check: totalSupply + usrAmount <= SUPPLY_CEILING
usr.mint(msg.sender, usrAmount); // 🔴 50,000,000 USR? Sure.
req.status = Status.Completed;
}
To make things worse, the SERVICE_ROLE was a plain externally owned address (EOA) — not a multisig. The admin role used a multisig, but the key with the power to mint arbitrary amounts did not.
The Attack Chain
Step 1: AWS KMS Compromise
The attacker gained access to Resolv's AWS Key Management Service environment where the SERVICE_ROLE private key was stored. The exact vector hasn't been fully disclosed, but common AWS KMS compromise paths include:
- Stolen IAM credentials (phished or leaked)
- Overly permissive IAM policies allowing
kms:Signfrom unexpected roles - Compromised CI/CD pipeline with KMS access
- Session token hijacking from a developer's machine
Step 2: The Over-Mint
With the signing key, the attacker:
- Deposited ~$100K–$200K USDC via
requestSwap() - Called
completeSwap()with the compromised key, authorizing 50 million USR for the first request - Repeated for another 30 million USR
Total: 80 million USR minted against ~$200K in collateral. A 400x over-mint.
Step 3: The Cashout (17 Minutes)
The attacker converted USR → wstUSR (wrapped staked USR) → stablecoins → ETH across Curve, KyberSwap, and Velodrome. Multiple failed transactions on-chain show them racing against liquidity. Final haul: ~11,400 ETH (~$24M) plus ~20M wstUSR still held.
The Pattern: Off-Chain Signer as God Mode
This isn't unique to Resolv. An enormous number of DeFi protocols delegate critical operations to off-chain signers:
| Operation | Who Uses Off-Chain Signers | Risk |
|---|---|---|
| Token minting | Stablecoin protocols, bridges | Infinite mint |
| Price feeds | Custom oracle implementations | Price manipulation |
| Withdrawal approval | Centralized exchange hot wallets | Fund drain |
| Upgrade authorization | Proxy admin patterns | Logic replacement |
| Batch settlement | L2 sequencers, payment channels | State corruption |
The fundamental problem: off-chain signers move the trust boundary from \"trust the code\" to \"trust the infrastructure running the code.\" And infrastructure is almost always easier to compromise than audited smart contracts.
The Resolv-Step Finance Connection
One week before Resolv, Step Finance lost $40M through compromised executive devices. Both exploits share the same root cause: a single compromised private key with disproportionate on-chain authority. The difference is cosmetic — Step Finance stored keys on laptops, Resolv stored them in AWS KMS.
The DeFi industry is facing a key management crisis. In Q1 2026 alone, private key compromises have caused more dollar damage than all smart contract bugs combined.
Defense Pattern 1: On-Chain Minting Invariants
The most critical fix is also the simplest: enforce minting bounds on-chain, regardless of what the off-chain signer authorizes.
contract SecureMinting {
uint256 public constant MAX_MINT_RATIO = 1.05e18; // 105% max
uint256 public constant SINGLE_MINT_CAP = 1_000_000e18; // 1M per tx
uint256 public constant DAILY_MINT_CAP = 10_000_000e18; // 10M per day
uint256 public dailyMinted;
uint256 public dailyResetTimestamp;
function completeSwap(
uint256 requestId,
uint256 mintAmount,
bytes calldata signature
) external {
Request storage req = requests[requestId];
// Invariant 1: Mint ratio cannot exceed MAX_MINT_RATIO
uint256 maxAllowed = (req.collateralAmount * MAX_MINT_RATIO) / 1e18;
require(mintAmount <= maxAllowed, "Exceeds mint ratio");
// Invariant 2: Single transaction cap
require(mintAmount <= SINGLE_MINT_CAP, "Exceeds single mint cap");
// Invariant 3: Daily aggregate cap
_resetDailyIfNeeded();
dailyMinted += mintAmount;
require(dailyMinted <= DAILY_MINT_CAP, "Exceeds daily cap");
// Invariant 4: Total supply ceiling
require(
usr.totalSupply() + mintAmount <= TOTAL_SUPPLY_CEILING,
"Exceeds supply ceiling"
);
// Only THEN verify the signature and mint
require(verifySignature(...), "Bad sig");
usr.mint(msg.sender, mintAmount);
}
function _resetDailyIfNeeded() internal {
if (block.timestamp >= dailyResetTimestamp + 1 days) {
dailyMinted = 0;
dailyResetTimestamp = block.timestamp;
}
}
}
Even if the attacker had the signing key, a 105% mint ratio cap would have limited the damage to ~$210K — the approximate value of their deposit.
Defense Pattern 2: Tiered Signing Authority
Replace the single SERVICE_ROLE EOA with a tiered system where the required authorization escalates with the transaction size:
contract TieredMinting {
// Tier 1: Automated signer (EOA/KMS) — small mints only
uint256 public constant TIER1_LIMIT = 100_000e18; // $100K
address public automatedSigner;
// Tier 2: Multisig — medium mints
uint256 public constant TIER2_LIMIT = 1_000_000e18; // $1M
address public multisig; // 3-of-5 Gnosis Safe
// Tier 3: Timelock + Multisig — large mints
uint256 public constant TIMELOCK_DELAY = 24 hours;
function completeMint(uint256 amount, bytes calldata auth) external {
if (amount <= TIER1_LIMIT) {
// Automated: single signature sufficient
require(verifySigner(auth, automatedSigner), "Bad T1 sig");
} else if (amount <= TIER2_LIMIT) {
// Requires multisig approval
require(verifySigner(auth, multisig), "Bad T2 sig");
} else {
// Must go through timelock — 24h delay
require(
timelockQueue[requestId].timestamp + TIMELOCK_DELAY
<= block.timestamp,
"Timelock not expired"
);
require(verifySigner(auth, multisig), "Bad T3 sig");
}
_executeMint(amount);
}
}
Defense Pattern 3: Circuit Breaker with Anomaly Detection
Implement an on-chain circuit breaker that automatically pauses minting when anomalous patterns are detected:
contract CircuitBreaker {
uint256 public constant VELOCITY_WINDOW = 1 hours;
uint256 public constant VELOCITY_LIMIT = 5_000_000e18; // $5M/hour
bool public circuitBroken;
modifier circuitCheck() {
require(!circuitBroken, "Circuit breaker active");
_;
_checkVelocity();
}
function _checkVelocity() internal {
uint256 windowStart = block.timestamp - VELOCITY_WINDOW;
uint256 windowTotal = 0;
for (uint i = recentMints.length; i > 0; i--) {
if (recentTimestamps[i-1] < windowStart) break;
windowTotal += recentMints[i-1];
}
if (windowTotal > VELOCITY_LIMIT) {
circuitBroken = true;
emit CircuitBreakerTriggered(windowTotal, block.timestamp);
}
}
}
Defense Pattern 4: KMS Key Isolation for DeFi
For protocols that must use cloud KMS, the key management architecture needs defense-in-depth:
┌─────────────────────────────────────────────────┐
│ AWS Account: resolv-minting-prod │
│ (Isolated account — no other workloads) │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ KMS Key: usr-minting-signer │ │
│ │ Policy: │ │
│ │ - Only Lambda role can call kms:Sign │ │
│ │ - CloudTrail logging (immutable) │ │
│ │ - Alert on ANY kms:Sign call │ │
│ │ - Rate limit: 10 calls/minute │ │
│ │ - Deny kms:Sign from console/CLI │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ Lambda: mint-authorizer │ │
│ │ - Validates collateral ratio │ │
│ │ - Checks against daily caps │ │
│ │ - Requires Step Functions approval flow │ │
│ │ for amounts > $100K │ │
│ │ - Signs only after all checks pass │ │
│ └──────────────────────────────────────────┘ │
│ │
│ GuardDuty + CloudTrail → SNS → PagerDuty │
└─────────────────────────────────────────────────┘
Key principles:
- Dedicated AWS account — no shared infrastructure, no lateral movement
-
IAM least privilege — only one Lambda function can invoke
kms:Sign - Rate limiting at the KMS level — even a compromised Lambda can't sign rapidly
- Immutable audit trail — CloudTrail logs shipped to a separate account
The Solana Equivalent: PDA Authority Hardening
Solana protocols face the same pattern with Program Derived Addresses (PDAs) and authority keys:
// VULNERABLE: Single EOA mint authority
#[account(
mut,
constraint = mint.mint_authority == COption::Some(authority.key())
)]
pub mint: Account<'info, Mint>,
pub authority: Signer<'info>, // Single key controls all minting
// HARDENED: PDA authority with on-chain constraints
#[account(
mut,
constraint = mint.mint_authority == COption::Some(mint_authority_pda.key())
)]
pub mint: Account<'info, Mint>,
#[account(
seeds = [b"mint_authority", config.key().as_ref()],
bump = config.mint_authority_bump,
)]
pub mint_authority_pda: AccountInfo<'info>,
#[account(
constraint = config.daily_minted + amount <= config.daily_cap
@ ErrorCode::DailyCapExceeded,
constraint = amount <= config.per_tx_cap
@ ErrorCode::TxCapExceeded,
)]
pub config: Account<'info, MintConfig>,
The PDA-based approach ensures that minting authority lives entirely on-chain, governed by program logic rather than a single compromised key.
The 8-Point Privileged Signer Audit Checklist
For any protocol using off-chain signers for critical operations:
Key Management
- [ ] No EOA for privileged roles — use multisig (M-of-N) for any role that can mint, burn, upgrade, or transfer significant value
- [ ] KMS isolation — dedicated cloud account, least-privilege IAM, rate-limited signing
- [ ] Key rotation schedule — automated rotation with zero-downtime handoff
On-Chain Guardrails
- [ ] Bound all privileged operations — per-transaction caps, daily caps, supply ceilings
- [ ] Validate ratios on-chain — never trust off-chain computation for collateral/mint ratios
- [ ] Circuit breakers — automatic pause on anomalous velocity or depeg detection
Monitoring
- [ ] Real-time alerting — every privileged function call triggers an alert with amount + caller + context
- [ ] Honeypot requests — periodically submit known-bad requests to verify the system rejects them
Takeaway
The Resolv exploit is the clearest case study yet for a simple principle: on-chain code must enforce its own invariants, regardless of what off-chain systems tell it to do.
The contract's job is to be the last line of defense, not a rubber stamp for whatever a privileged key signs. A MAX_MINT_RATIO check — literally three lines of Solidity — would have reduced the damage from $25 million to essentially zero.
Every DeFi protocol should audit not just their smart contracts, but the trust assumptions their contracts make about off-chain signers. If a single key compromise can drain your protocol, you don't have a security architecture. You have a single point of failure with extra steps.
This analysis is part of the DeFi Security Research series by DreamWork Security. Sources: Chainalysis Hexagate analysis, DeFiPrime investigation, D2 Finance on-chain analysis, PeckShield alerts (March 22, 2026).
Top comments (0)