# Decessor reference contract

> **NOTICE.** `Decessor.sol` is a reference implementation. It has **not been audited**.
> Do not delegate an address that holds anything of value to it. The production address and
> its code hash will be announced only by [@DecessorETH](https://x.com/DecessorETH).

`Decessor.sol` is an EIP-7702 delegate target: an externally owned account (EOA) points its
code at it and keeps its address and its key. It compiles with `solc ^0.8.24` (checked with
0.8.28, optimizer on, no warnings) and has no dependencies.

`../js/sim.js` mirrors this contract function by function (same names, same `require()`
order, same revert strings, same constants) and is unit tested in `../tests/sim.test.js`,
including a test that reads this file and checks the constants and revert strings still match.
Keep the two in sync.

## The mechanism

```
configure(successors[], silence)        -> owner only ; silence >= 30 days
execute(to, value, data) / pulse()      -> owner only ; lastSeen = now
claim()                                 -> successor[i] only ; require now > lastSeen + silence + i * CASCADE_DELAY ; graceEnd = now + 30 days
cancel()                                -> owner only ; graceEnd = 0 (any owner transaction also cancels)
take()                                  -> claimant only ; require graceEnd != 0 && now > graceEnd ; control -> successor ; fee -> treasury
```

| Constant         | Value      | Meaning                                                          |
| ---------------- | ---------- | ---------------------------------------------------------------- |
| `GRACE`          | 30 days    | Wait between `claim()` and `take()`; the owner can cancel throughout |
| `CASCADE_DELAY`  | 90 days    | Extra wait per later successor                                    |
| `MIN_SILENCE`    | 30 days    | Shortest silence                                                  |
| `MAX_SILENCE`    | 3650 days  | Longest silence                                                   |
| `MAX_SUCCESSORS` | 3          | Length of the cascade                                             |
| `FEE_BPS`        | 50         | 0.5% of the ETH held by the address, charged at `take()` only     |
| `TREASURY`       | immutable  | Fee recipient, set once in the constructor                        |

States: `ACTIVE`, `CLAIMABLE`, `IN_GRACE`, `PASSED`.

## The EIP-7702 flow

1. **Deploy once.** `new Decessor(treasury)` is deployed a single time. Its runtime code hash
   is what owners verify. Nobody can change it afterwards: no owner, no proxy, no pause.
2. **Delegate.** The owner signs an EIP-7702 authorization `(chain_id, address = Decessor, nonce)`
   and includes it in a type-4 transaction. The EOA's code becomes the delegation indicator
   `0xef0100 ‖ Decessor`. The address, the key and every balance stay exactly where they were.
3. **Configure.** The owner sends a transaction **to their own address** calling
   `configure(successors, silence)`. Under 7702 that call arrives with
   `msg.sender == address(this)`, which is how the code recognises the owner. Delegation and
   configuration can share one transaction.
4. **Live.** Every owner call through the code (`configure`, `pulse`, `execute`, `cancel`) runs
   `_seen()`: `lastSeen = block.timestamp`, and any pending claim is cancelled.
5. **Claim.** Once `block.timestamp > lastSeen + silence + i * CASCADE_DELAY`, successor `i`
   may call `claim()` on the owner's address. `graceEnd = now + GRACE`. Nothing moves.
6. **Take.** After `graceEnd`, the claimant calls `take()`. `controller = claimant`, the fee
   (`balance * FEE_BPS / 10_000`) goes to `TREASURY`, and from then on `execute()` obeys the
   controller only. Owner functions revert with `Decessor: passed`.
7. **Revoke.** The owner signs an authorization for `address(0)`; the account's code is cleared
   and it is a plain EOA again. Storage is not cleared (see below).

Constructor code never runs for delegating EOAs. That is fine: they only read the immutable
`TREASURY` (embedded in the runtime bytecode) and their own namespaced storage.

## Storage layout (ERC-7201)

Under 7702 the code runs in the EOA's storage, which other delegates may have written before
or may write later. All state therefore lives in one namespaced struct, never at low slots.

```
namespace : decessor.storage.v1
slot S    : keccak256(abi.encode(uint256(keccak256("decessor.storage.v1")) - 1)) & ~bytes32(uint256(0xff))
          = 0xfa8125a9fac28d1f6f4b05fe454526c3d5323babf90eb8663d81994abd87e200
```

| Slot                    | Bytes   | Field        | Type        | Notes                                              |
| ----------------------- | ------- | ------------ | ----------- | -------------------------------------------------- |
| `S`                     | 0..31   | `successors` | `address[]` | array length                                       |
| `keccak256(S) + i`      | 12..31  | `successors[i]` | `address` | element `i`, in claim order                        |
| `S + 1`                 | 0..7    | `silence`    | `uint64`    | seconds                                            |
| `S + 1`                 | 8..15   | `lastSeen`   | `uint64`    | timestamp of the last owner call                   |
| `S + 1`                 | 16..23  | `graceEnd`   | `uint64`    | `0` = no pending claim                             |
| `S + 1`                 | 24      | `claimant`   | `uint8`     | index into `successors`, valid while `graceEnd != 0` |
| `S + 2`                 | 12..31  | `controller` | `address`   | successor who took control; `0` = not passed       |

Byte offsets count from the least significant end of the slot, as Solidity packs them.

**Revocation leaves this storage in place.** If an owner delegates to Decessor again later,
their old successors and an old `lastSeen` reappear, so `configure()` should be the first call
after re-delegating (it rewrites the successors and resets the clock).

## Functions, checks in order

| Function    | Caller            | Checks (each reverts with the string shown)                                                                                           | Effects |
| ----------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `configure` | owner             | `not owner`, `passed`, `bad successor count`, `silence too short`, `silence too long`, then per address `zero successor`, `self successor`, `duplicate successor` | `_seen()`, store successors and silence |
| `pulse`     | owner             | `not owner`, `passed`                                                                                                                 | `_seen()` |
| `execute`   | owner, then controller | before take: `not owner`, `passed`; after take: `not controller`                                                                 | before take `_seen()`; then the call, bubbling up its revert data |
| `cancel`    | owner             | `not owner`, `passed`, `no claim`                                                                                                     | `_seen()` |
| `claim`     | successor `i`     | `passed`, `not configured`, `not successor`, `claim pending`, `silence not over`                                                       | `claimant = i`, `graceEnd = now + GRACE` |
| `take`      | claimant          | `passed`, `no claim`, `not claimant`, `grace not over`                                                                                | `controller = claimant`, `graceEnd = 0`, fee to `TREASURY` |

All revert strings are prefixed with `Decessor: `. Only `Decessor: zero treasury` (constructor)
and `Decessor: fee transfer failed` (treasury refusing ETH) cannot happen in the simulation.

Views: `state()`, `owner()` (the EOA, then the controller), `getSuccessors()`, `config()`.

Events: `Configured`, `Seen`, `Claimed`, `Cancelled`, `Passed`, `Executed`.

## Receiving assets

A delegated EOA has code, so ETH transfers and "safe" token transfers call into it. The
contract implements `receive()`, `onERC721Received`, `onERC1155Received`,
`onERC1155BatchReceived` and `supportsInterface` so the address keeps receiving ETH, NFTs and
multi-tokens. Receiving never touches the clock.

## Known limitations

- **An EOA key cannot be removed.** After `take()`, the original key, if it still exists, can
  still sign transactions directly or re-delegate. Decessor is designed for keys that are lost
  and owners who are gone, not for revoking a key someone still holds.
- **Theft is not loss.** A stolen key can do everything the owner can, including reconfigure.
- **A pending claimant cannot be replaced.** If the claimant never calls `take()`, a later
  successor cannot claim over them; the owner can still cancel at any time.
- **One delegate per EOA.** Delegating to Decessor replaces any other 7702 delegation, such as a
  wallet's own smart-account mode.
- **`execute()` makes a single call.** There is no batching in this reference version.
- **Not audited, not formally verified.** Experimental software; bugs are possible.

## Compiling

```bash
solc --optimize --bin --abi contracts/Decessor.sol
```

The code uses no opcodes newer than Cancun; the delegation itself needs a chain with EIP-7702
(Ethereum mainnet since the Pectra upgrade).
