ERC-8238: Coercion-Resistant Vault

Discussion topic for ERC: Coercion-Resistant Vault Standard

Update Log

  • 2026-04-02: initial draft, commit

  • 2026-04-02: add ERC-20 token support with independent per-token spending limits

  • 2026-04-02: add ERC-4337 smart account implementation (IAccount, validateUserOp)

  • 2026-04-02: add DeFi execution via whitelisted targets (execute, executeBatch, approveToken)

  • 2026-04-02: add timelocked whitelist management

External Reviews

None as of 2026-04-02.

Outstanding Issues

  • 2026-04-02: Should first-time token spending limit configuration be immediate or timelocked? Currently immediate for usability, discussion welcome

  • 2026-04-02: Fee-on-transfer and rebasing token handling — current recommendation is to check actual received amounts and use wrapped versions (e.g. wstETH), tracking issue

  • 2026-04-02: Should the standard define a recommended minimum timelock duration (e.g. 24h)?

  • 2026-04-02: Emergency social recovery mechanism — leave to implementations or include in standard?


Problem

Physical attacks against cryptocurrency holders (“$5 wrench attacks”) increased 169% in 2025, with over 70 confirmed cases resulting in $40M+ stolen (source). Current self-custody wallets grant immediate, irreversible access to the entire balance, making crypto holders uniquely vulnerable compared to traditional finance where bank vaults have delayed openings.

Proposed solution

An ERC standard for ERC-4337 smart account wallets with:

  • Hot balance with per-epoch spending limit (e.g., 0.5 ETH/24h) — immediate, no friction

  • Cold vault with configurable timelock (24-72h) — the bulk of funds

  • Multisig bypass (2-of-3 guardians) for legitimate urgent access

  • Cancellable withdrawals during the timelock window (by owner or guardians)

  • Timelocked configuration changes — attackers can’t force the victim to raise limits

  • Full ERC-20 support with independent per-token spending limits

  • DeFi execution via whitelisted target contracts (approve + swap, LP, staking)

  • Batch operations for multi-step DeFi interactions (atomic approve + swap in one tx)

The key insight

Under coercion, the victim can truthfully say: “I can only send you 0.5 ETH right now. The rest is locked for 72 hours. You can verify this on-chain.”

Unlike duress/decoy wallets, there is no deception required — the constraint is verifiable and the victim doesn’t need to perform theater under extreme stress. The mechanism works regardless of attacker sophistication.

This is a smart account, not a lockbox

The vault implements IAccount (ERC-4337 v0.7) and works as a fully functional smart wallet:

  • UserOperations signed by the owner’s key, validated via ECDSA on-chain

  • Hot balance operations flow through the EntryPoint like any smart wallet

  • DeFi interactions via execute() and executeBatch() — only whitelisted protocol contracts can be called

  • Token approvals via approveToken() — only whitelisted spenders

  • Adding a new target to the whitelist requires the full timelock (attacker can’t whitelist a drainer and execute immediately)

  • Removing a whitelisted target is instant (more restrictive = more secure)

Funds remain fully usable for daily spending and DeFi. The protection only constrains what happens under duress.

Interface architecture

The standard defines two interfaces:

  • ICoercionResistantVault — core ETH support, timelocks, guardians, multisig, DeFi execution

  • ICoercionResistantVaultTokens — ERC-20 extension with per-token spending limits

Both share the same timelock duration, guardian set, multisig configuration, and whitelist. The separation allows simpler implementations that only need ETH support.

Why existing approaches fall short

Approach Problem This standard
Duress/decoy wallet Attacker may know and escalate violence On-chain verifiable constraint, no deception
Pure multisig Friction for daily use Hot balance + DeFi execution work instantly
Pure timelock Can’t spend anything immediately Rate-limited hot balance for daily use
Hardware wallet Full access under physical coercion Smart contract enforces limits regardless

Pre-draft & reference implementation

Full ERC draft + Solidity reference implementation (ERC-4337 smart account, ~700 lines):

https://github.com/DeFiRe-business/eip-proposal-5wrench

Feedback requested

  1. Is per-epoch rate limiting the right approach vs a fixed “hot pool” that needs manual refilling?

  2. Is the two-interface split (core ETH + token extension) the right separation?

  3. Should first-time token configuration be immediate or also timelocked?

  4. Should this standard define a recommended minimum timelock (e.g., 24h)?

  5. How should fee-on-transfer and rebasing tokens be handled?

  6. Should the standard include an emergency social recovery mechanism, or leave that to implementations?

  7. Is the whitelisted DeFi execution model (execute + executeBatch + approveToken) the right abstraction, or should it be more granular (e.g., per-function selectors)?

  8. Any concerns about the ERC-4337 integration — particularly the signature validation approach and EntryPoint interaction?

Looking forward to your thoughts.

Hey @cmayorga, Thanks the proposal.

I have few questions or clarifications, please check below.

1. What happens when the epochDuration is changed mid-epoch? Maybe a bit more clarity on this would be helpful in the documentation.
2. I see that there is no specification on maximum concurrent withdrawal requests. Should there be a limit to prevent DoS via storage bloat?
3. There can be a case where guardians are removed, and the count falls below multisigThreshold. The spec should require threshold adjustment or prevent removal.
4. Can we add a mechanism to temporarily pause the vault from any txns if the owners suspect some suspicious activity? Atleast one/two of the multisig can invoke this.

1 Like

Hey @HarishKumarGunjalli, thank you for the thoughtful review — these are exactly the edge cases the spec needs to close before moving to draft. Responding inline:

1. epochDuration changes mid-epoch

Good catch, this behavior isn’t documented. In the reference implementation, the epoch reset is lazy: _resetEpochIfExpired() compares block.timestamp >= currentEpochStart + epochDuration on the next hot spend. So if you’re 10 minutes into a 24h epoch and reduce epochDuration to 1h, the next hotSpend call will detect the epoch as expired and reset it — effectively the new duration takes effect immediately, and the spending counter resets.

I’ll add this explicitly to the spec. There’s also a subtler question worth discussing: should an epoch duration decrease be timelocked like spending limit increases? Right now decreases are immediate (considered “more secure”) but a shorter epoch with the same limit means a higher effective spending rate (e.g., 1 ETH/24h → 1 ETH/1h is a 24x effective increase). This is actually an attack vector I hadn’t considered — will add to Outstanding Issues and likely move epoch duration decreases behind the timelock.

2. Max concurrent withdrawal requests

You’re right, there’s no cap on nextRequestId. The damage is bounded — only the owner can create requests, and they can cancel their own — but storage bloat is a legitimate concern, especially if the owner’s key is compromised in a non-coercion scenario. Two options:

  • Hard cap on concurrent unresolved requests (e.g., MAX_PENDING = 32). New requests revert if the count is at cap.
  • Require a small ETH bond per request, refunded on execution or cancellation.

I lean toward the hard cap for simplicity. Will implement in the next revision.

3. Guardian removal dropping below threshold

This is a real bug. setMultisigThreshold() validates threshold <= guardianList.length, but executeGuardianChange() does not adjust the threshold when removing a guardian. If you have 3 guardians with threshold 2 and remove two, you end up with 1 guardian and an unreachable threshold of 2 — multisig bypass becomes impossible, and cold vault withdrawals can only exit via timelock.

Fix: executeGuardianChange() will revert if removing a guardian would leave guardianList.length < multisigThreshold, forcing the owner to explicitly reduce the threshold first via setMultisigThreshold(). Implementing this today.

4. Emergency pause

I really like this. The current “panic button” is cancelWithdrawal(), but it only works on pending requests — it doesn’t stop the owner from spending the hot balance or interacting with DeFi via execute(). A temporary pause invokable by any single guardian would add a real “freeze everything now” option when suspicious activity is detected.

Initial design I’m considering:

  • Any single guardian can pause the vault for up to 24h
  • Pause blocks hotSpend, hotSpendToken, execute, executeBatch, and new withdrawal requests
  • Cancelling pending withdrawals remains available (it’s a safety action)
  • After 24h, the pause auto-expires unless extended by multisig
  • Owner can contest a pause with multisig approval from other guardians

Will draft this as part of the core spec and circulate for review.

All four points will be reflected in the next revision of the reference implementation and the spec. Your contributions will be credited in the ERC’s Acknowledgments section — thanks again for taking the time to review this carefully.

1 Like

v2 implementation landed — all four points from @HarishKumarGunjalli’s review are now in the reference implementation and the spec.

Commit: feat: address v2 feedback from @iHarishKumar · DeFiRe-business/eip-proposal-5wrench@2373ada · GitHub

Summary of changes:

  1. Epoch duration decreases are now timelockedsetSpendingLimit() and setTokenSpendingLimit() detect both limit increases and duration decreases, and route either through the timelock. The attack vector (e.g., 1 ETH/24h → 1 ETH/1h being a 24x effective spending rate increase) is closed. Added to the Rationale section with explicit reasoning.

  2. MAX_PENDING_WITHDRAWALS cap — new constant set to 32, with a pendingWithdrawalCount counter that increments on request creation and decrements on execution/cancellation. requestWithdrawal() and requestTokenWithdrawal() revert with TooManyPendingWithdrawals at the cap. Prevents storage bloat DoS even if the owner key is compromised.

  3. Guardian removal guardexecuteGuardianChange() now reverts with GuardianRemovalWouldBreakThreshold if removing a guardian would leave guardianList.length < multisigThreshold. Owner must explicitly reduce the threshold first. The unreachable-state bug is closed.

  4. Emergency pause mechanism — new constants MAX_PAUSE_DURATION = 24 hours, new state pausedUntil, new functions pause() and unpause(), and a whenNotPaused modifier applied to all value-moving operations (hotSpend, hotSpendToken, requestWithdrawal, requestTokenWithdrawal, executeWithdrawal, execute, executeBatch, approveToken). Cancellation and approval paths remain callable during pause as safety actions. Design:

    • Any single guardian can pause (instant — no consensus needed during an active attack)
    • Pause auto-expires after 24h unless extended
    • Unpause before expiry requires multisig approval (scoped to the current pause to prevent stale approvals)
    • Calling pause() again while paused resets the auto-expiry timer, allowing sustained freezing

Also added a new Security Consideration on pause griefing — the 24h auto-expiry bounds worst-case downtime from a malicious guardian.

Acknowledgments section added to the ERC document crediting Harish — this will persist in the canonical ERC once it’s merged into ethereum/EIPs with a formal number.

Thanks again @HarishKumarGunjalli — this round of feedback materially improved the security model. The spec is stronger for it.

1 Like

Formal PR submitted to ethereum/ERCs – PR #1703:

Since the last update, the proposal has been significantly strengthened with end-to-end evidence that the design works in practice:

Foundry test suite (71/71 passing)

  • Core unit tests: hot spend rate-limiting, cold vault timelock flow, multisig bypass, emergency pause (24h auto-expiry + multisig unpause), MAX_PENDING_WITHDRAWALS cap, config timelock semantics (limit increases, epoch duration decreases, timelock decreases all delayed; security-increasing changes immediate)
  • ERC-20 tests: per-token independent spending limits, first-time-config immediacy, token cold withdrawals
  • DeFi execution tests with MockDexRouter: whitelist timelocking, execute() target gating, approveToken() spender gating, atomic executeBatch(), and the critical invariant that value-preserving swaps do NOT consume the hot spending budget
  • Sepolia fork integration test: wraps ETH into WETH via execute() against the real WETH9 contract, optional Uniswap V3 swap

Sepolia deployment (verified on Etherscan)
Contract: Address: 0x7B36499A...4416cD0Ab | Etherscan Sepolia

Successfully executed against live Sepolia infrastructure:

  • WETH9 wrap via execute(WETH, value, deposit())
  • Uniswap V3 swap WETH → USDC via execute(router, 0, exactInputSingle(...)) – vault retained USDC custody, hot spending counter stayed at zero
  • Cold withdrawal with real timelock countdown + execution after expiry

Live interactive demo

Connects to MetaMask on Sepolia. Anyone can test all vault flows: deposit, hot spend, cold withdrawal with timelock countdown, and the Uniswap V3 swap – all from the browser.

Development repository with full source, tests, deploy scripts, and demo:

Looking forward to editor feedback on PR #1703. Happy to address any format or content requests.