
On March 22, 2026, an attacker minted approximately 80 million USR against $100,000 of USDC, then extracted approximately 25 million dollars of ETH from Resolv Labs over the next 17 minutes. USR's price collapsed from $1 to roughly $0.025. Both Halborn and QuillAudits published detailed post-mortems within the week.
This was not a smart-contract bug in the traditional sense. The Solidity compiled cleanly. The on-chain logic did exactly what it was told. The exploit lived in the line where on-chain logic accepted instructions from off-chain code that it should not have trusted to issue them.
Six weeks later, the relevant question is no longer "what did Resolv get wrong." It is: "what is the same class of failure called in every other synthetic dollar that ships, and how do you check whether a protocol you are about to deposit into has it." This post is a forensic read of the Resolv mint path, the architectural class of failure it represents, and a reading of Kerne's own mint path under the same questions.
The mechanism, decomposed
The Resolv mint mechanism worked like this in normal operation. A user wanted USR. They sent USDC to a contract entry point. An off-chain Resolv backend observed the deposit, computed the USR amount, signed an instruction with a backend-held key (the SERVICE_ROLE in their AWS KMS configuration), and called completeSwap on the on-chain contract. completeSwap accepted the instruction if the signature matched the configured SERVICE_ROLE address, and minted the requested amount of USR to the user.
The exploit: an attacker compromised the SERVICE_ROLE signing key. They sent $100,000 of USDC into the deposit entry. They then signed and submitted a completeSwap instruction calling for 80 million USR against that deposit. The on-chain contract verified the signature, observed it matched the configured SERVICE_ROLE, and minted. There was no on-chain check that the requested mint amount was bounded by the deposit. There was no per-block, per-day, or per-call cap. There was no second signer. The off-chain key was sufficient.
That is the entire exploit, end to end. The $25 million extracted afterward was the attacker swapping the unbacked USR through Resolv's secondary markets before the team could pause the contracts.
The class of failure
The architectural class of failure here is not "Resolv had a bad key-management process." Plenty of protocols have had keys compromised. The class of failure is: a single off-chain key is the sole gate on an unbounded on-chain state change.
Three properties combined to make the Resolv vector exploitable:
- The on-chain mint amount was set by the off-chain caller, not derived from on-chain state. The contract did not verify the relationship between USDC deposited and USR minted; it took the requested amount as authoritative.
- There was no per-call or per-block ceiling on the mint quantity. A single transaction could mint any number, including amounts orders of magnitude larger than the protocol's total backing.
- The privileged role required exactly one signature, and the signer was an automated off-chain process whose key lived in a cloud KMS rather than in cold-stored multisig hardware.
Any one of these three would have been recoverable. Two of them combined would have been damaging but bounded. All three together made the protocol's entire collateral pool extractable in seventeen minutes by anyone who got the one key.
This is not a Resolv-specific failure mode. Several active synthetic-dollar protocols ship with at least one of the three properties present. Two of them is more common than the public discourse suggests. The Resolv exploit is the canonical case study because the third property closed the loop and made it visible at scale.
Three questions for any synthetic dollar
The lesson the Resolv post-mortem teaches is not "do not trust off-chain code." Off-chain code is unavoidable in any protocol with a CEX hedge, an oracle pipeline, or a queue-based withdrawal mechanism. The lesson is: when off-chain code controls an on-chain state change that mints supply, the on-chain side must constrain what off-chain can request.
When evaluating a synthetic dollar, three on-chain questions get you most of the way to a verdict.
- What is the on-chain authority required to mint the supply token, and how is it gated? AccessControl with a named role is better than Ownable with a single key. A multi-signature requirement is better than a single. A timelock on the role-grant is better than no timelock.
- Is the mint amount derived from on-chain state, or is it set by the caller? An on-chain price oracle plus a deposit balance check is verifiable. A "completeSwap with off-chain calculated amount" is a Resolv vector.
- Is there a per-call, per-block, or per-day cap on mint quantity? A protocol that can mint arbitrarily many supply tokens in a single transaction is one bug away from a Resolv outcome. A protocol that caps mint at a fraction of TVL per day, even if every other gate fails, has a containment perimeter.
Each of these is one cast call, against the relevant chain, against the relevant contract. Anyone who reads the answers correctly can answer the question for any protocol they are considering.
Kerne's mint path under the same three questions
Kerne's kUSD mint path runs the same three checks as follows.
Authority. AccessControl with two named roles: DEFAULT_ADMIN_ROLE and MINTER_ROLE. DEFAULT_ADMIN_ROLE is held by a 2-of-3 Gnosis Safe multisig at 0x52d3E450bA6c299B1B07298F1E87DD74732D4877. MINTER_ROLE is held by exactly two contracts: KerneVault at 0x8005bc7A86AD904C20fd62788ABED7546c1cF2AC and KUSDPSM at 0xFf3025ec18e301855aB0f36Ec6ECa115a29A5Fbc. Both are on-chain contracts, not off-chain keys.
Mint amount. The user-facing mint path is KUSDPSM.swapStableForKUSD. It reads the USDC the user deposited from the contract's own balance, applies a 10 basis point fee defined as a contract constant, and mints the resulting kUSD. There is no caller-supplied mint quantity, no off-chain "this is what you are getting" signal, no SERVICE_ROLE equivalent. The function is a pure on-chain calculation.
Caps. KUSDPSM enforces an on-chain mintingEnabled flag that the multisig can flip in one transaction if anything looks off. The Phase 2 kUSDMinter contract, which is in source but not yet deployed, additionally caps mint at a configurable per-day amount as a defense-in-depth ceiling. For Genesis Window scale this is intentionally conservative.
These three are checkable in three commands against Base mainnet:
$(cast keccak "MINTER_ROLE") 0xFf30...5Fbc
$(cast keccak "DEFAULT_ADMIN_ROLE") 0x52d3...4877
The first command verifies KUSDPSM is the only non-vault holder of the mint privilege. The second verifies the mint surface is currently active. The third verifies role-administration is gated by the multisig, not a single EOA. We assert all three values continuously via /api/risk-status (rendered in human-readable form at /risk) and an hourly CI workflow at .github/workflows/onchain-role-smoke.yml; if any flips, the workflow fails and the homepage banner reflects it.
What we still owe
We are not implying Kerne has nothing left to harden. The 201-finding internal adversarial audit we ran on May 8 catalogues 36 findings rated critical, 51 high, 51 medium, 37 low, and 26 informational across the full contract set. The kUSDMinter contract referenced above is in source but not yet deployed. The KerneVerificationNode contract for cross-chain attestation is also in source and not yet deployed. Several access-control hardening items from the audit's section on default thresholds, role-admin self-loops, and single-key STRATEGIST_ROLE on adapter contracts remain on the remediation queue.
The point of this post is not to claim immunity. It is to demonstrate a posture difference: every claim about how Kerne's mint path works is verifiable in three commands; every gap is named with the remediation status visible. The Resolv exploit was a class of failure that would have been detectable to any reader running those three commands beforehand. Synthetic-dollar protocols that survive long-term will be the ones whose readers do.
Conclusion
Resolv was a profitable basis-trade product before March 22. The mechanism worked. The yield was real. The team was competent. The thing that ended it was not a market move or a wrong bet. It was an architectural choice that placed an unbounded mint authority behind a single off-chain key, and the absence of any on-chain ceiling that would have limited the blast radius when that key was inevitably compromised.
Every synthetic-dollar protocol designer reads that post-mortem and decides what to do about it. The right answer is not "we have better key management than Resolv did." The right answer is to make the mint path checkable from the chain, with no off-chain authority that can mint more than a verifiable function of on-chain deposit balance, and with caps that contain blast radius even if every other defense fails.
That is the bar Kerne is built around. The full open-finding list at /security names what we still need to do to fully meet it. The verification surface at /api/risk-status reports continuously on whether we are meeting it today. Three commands, no claims.
For the Resolv post-mortems referenced in this piece, see the public reports from Halborn and QuillAudits. For the on-chain check commands above against Base mainnet, see Exit Triggers and Emergency Runbook. For Kerne's full open-finding list and remediation cadence, see Security and Audits.
Ready to deposit?
Mint kUSD with USDC at the live PSM, 1:1 backing, 10 bps fee.
Or deposit WETH for kLP shares earning the live APY today. Genesis Phase: 0% protocol performance fee.