Skip to content
On this page

L1-7: Dead Man's Switch

1. Problem

Create a contract where users deposit ETH and set up a periodic check-in mechanism. If a user fails to check in within the configured time interval, a designated beneficiary can withdraw their funds. The user themselves can always withdraw.

2. Why

Real-world scenario: Cryptocurrency holders may lose access to their assets due to accidents (death, disappearance, lost private keys). A Dead Man's Switch provides a "digital inheritance" mechanism:

  • The operator periodically checks in to prove they are "still active"
  • Upon loss of contact, funds automatically transfer to pre-designated beneficiaries
  • No need to trust a third-party custodian

3. Solution

Core Mechanism

  • Each user sets their own checkInInterval
  • deposit() / checkIn() updates lastCheckIn[user]
  • The account holder can always withdraw
  • Beneficiaries can withdraw only when block.timestamp > lastCheckIn[account] + checkInInterval[account]

Data Structures

solidity
mapping(address => uint256) public balanceOf;
mapping(address => uint256) public lastCheckIn;
mapping(address => uint256) public checkInInterval;
mapping(address => address[]) public beneficiaries;

4. Pitfalls Encountered

4.1 Beneficiaries do not need to set an interval

If a user does not set checkInInterval, then deadline = lastCheckIn + 0 = lastCheckIn, which means block.timestamp > deadline is always true — beneficiaries can withdraw immediately.

4.2 Auto-check-in on deposit

deposit() should also update lastCheckIn; otherwise, a new deposit could immediately be considered "inactive."

4.3 Beneficiary list management

removeBeneficiary needs to delete an element from the array. Use the swap-and-pop pattern for O(1).

5. Why the Pitfalls Exist

5.1

checkInInterval defaults to 0. lastCheckIn + 0 = lastCheckIn, so any block.timestamp > lastCheckIn satisfies the condition. An explicit check for interval == 0 is required.

5.2

If depositing does not update lastCheckIn, a user who just deposited a large amount of ETH could have it withdrawn by a beneficiary (because the previous interval may have already elapsed).

5.3

Solidity arrays have no built-in delete method. Using list[i] = list[last]; list.pop() is the optimal solution.

6. How to Solve the Pitfalls

solidity
function withdraw(address account, uint256 amount) external {
    if (!isAccountHolder) {
        uint256 interval = checkInInterval[account];
        if (interval == 0) revert NotExpired(); // Must be explicitly set
        if (block.timestamp <= lastCheckIn[account] + interval) revert NotExpired();
    }
    // ...
}

7. Technical Takeaways

PointDescription
Time-conditional access controlblock.timestamp comparison
Beneficiary patternMultiple beneficiaries share withdrawal permission
Check-in mechanismAuto-renewal on every interaction
Array swap-and-popO(1) deletion
receive() depositsDirect ETH transfers handled automatically

Built with AiAda