This EIP introduces temporary storage: a new contract-accessible key-value store that persists across transactions and blocks, but is automatically cleared at a protocol-defined schedule. Two new opcodes are added:
TMPSTORE(key, value) to write temporary storage for the executing contract.TMPLOAD(key) to read temporary storage for the executing contract.Temporary storage is intended for data that does not need indefinite retention, providing a safer alternative to using permanent state for ephemeral data and enabling bounded growth of this class of state.

Figure 1: Over 60% of the storage slots are written once and never accessed again onchain.
Permanent contract storage is costly because it increases long-term node resource requirements (disk, I/O, state maintenance). However, many applications write data that is only valuable for a bounded time window.
This EIP provides:
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
| Name | Value | Description |
|---|---|---|
FORK_BLOCK |
TBD | Activation block of this EIP |
TEMP_STORAGE_PERIOD |
TBD | The number of blocks that the temporary storage holds for a given period |
TEMP_STORAGE_SYSTEM_ADDRESS_0 |
TBD | First reserved address used to store temporary storage |
TEMP_STORAGE_SYSTEM_ADDRESS_1 |
TBD | Second reserved address used to store temporary storage |
TMPLOAD_GAS |
TBD | The gas cost of a warm read at temporary storage slot |
COLD_TMPLOAD_COST |
TBD | The gas cost surchage of a cold access at the temporary storage |
TMPSTORE_SET_GAS |
TBD | The gas cost of setting a slot from zero to non-zero at the temporary storage |
TMPSTORE_RESET_GAS |
TBD | The gas cost of changing a non-zero slot to another value at the temporary storage |
Temporary storage is backed by two reserved system accounts at TEMP_STORAGE_SYSTEM_ADDRESS_0 and TEMP_STORAGE_SYSTEM_ADDRESS_1.
The temporary storage keyspace is a mapping of (contract_address, key) -> value, where:
derived_slot_key = keccak256(contract_address || key)
Temporary storage values are stored in the regular storage tries of the system accounts using derived_slot_key as the slot key.
Temporary storage is organized into periods, each of length TEMP_STORAGE_PERIOD blocks after activation.
For blocks with block.number >= FORK_BLOCK, define:
period_index(block_number) = floor((block_number - FORK_BLOCK) / TEMP_STORAGE_PERIOD)
Define the current and previous system accounts for a block B (where B.number >= FORK_BLOCK) as:
current_index = period_index(B.number) % 2
if current_index == 0:
CURRENT_SYSADDR = TEMP_STORAGE_SYSTEM_ADDRESS_0
PREVIOUS_SYSADDR = TEMP_STORAGE_SYSTEM_ADDRESS_1
else if current_index == 1:
CURRENT_SYSADDR = TEMP_STORAGE_SYSTEM_ADDRESS_1
PREVIOUS_SYSADDR = TEMP_STORAGE_SYSTEM_ADDRESS_0
At the first block of each new period, the protocol MUST clear the storage of the system account that becomes the current.
When processing a block B (with parent P) where both are >= FORK_BLOCK, if period_index(B.number) > period_index(P.number), then before executing transactions in B, the protocol MUST perform:
CURRENT_SYSADDR.storageRoot = EMPTY_TRIE_ROOTwhere CURRENT_SYSADDR is computed from B.number as specified above.
No other accounts are modified by the rollover.
Effect: After rollover, entries written in the immediately previous period remain readable via PREVIOUS_SYSADDR for one additional period, while entries older than one period are removed.
Add 2 new opcodes as follows:
TMPLOAD(key)Compute derived_slot_key from (contract_address, key). Then load derived_slot_key from CURRENT_SYSADDR first. If it doesn't exist (i.e. value == 0 ), then load derived_slot_key from PREV_SYSADDR.
TMPSTORE(key,value)Compute derived_slot_key from (contract_address, key). Then:
value != 0, store in the storage of CURRENT_SYSADDR.value == 0, delete slot from both CURRENT_SYSADDR and PREV_SYSADDR.Rationale: Because reads fall back from current to previous, clearing only the current store would allow an older previous-period value to be returned. Clearing both ensures TMPSTORE(key,0) makes subsequent TMPLOAD(key) return 0 (until a new non-zero is written).
The call rules should follow SSTORE and SLOAD.
The gas rules should follow the conventions in EIP-2929 and EIP-2200 referencing SSTORE and SLOAD, except that:
SSTORE and SLOAD, but higher than TSLOAD and TSTORETMPLOAD MAY perform up to 2 storage reads (current then previous). Gas MUST account for this behaviour.TMPSTORE is more expensive than creation/modification as the storage root of both system accounts are updated. Gas MUST account for this behaviour.At FORK_BLOCK, the chain MUST treat TEMP_STORAGE_SYSTEM_ADDRESS_0 and TEMP_STORAGE_SYSTEM_ADDRESS_1 as reserved addresses:
nonce = 1balance = 0codeHash = EMPTY_CODE_HASHstorageRoot = EMPTY_TRIE_ROOTNo contract code is deployed at this address.
EIP-1153 (transient storage) is discarded after every transaction, which is ideal for intra-tx operations. This EIP targets a different class of use cases where data must persist across multiple transactions/blocks, but does not need indefinite retention.
Two-period rollover provides a stronger usability guarantee:
TEMP_STORAGE_PERIOD after it is written (it will be readable in the next period via the previous-period system account).This provides a simple mental model for contract developers. That is, if an entry is stored in temporary storage, it will at least last for TEMP_STORAGE_PERIOD.
In contrast, a single global rollover makes effective lifetime depend on when data is written within the period: a write near the end of a period could be cleared almost immediately, which may not be developer-friendly.
Using two reserved system accounts allows constant-time rollover:
storageRoot = EMPTY_TRIE_ROOT.This EIP requires a hard fork to implement.
It does not change behavior of any existing opcodes. Therefore, it is backward compatible with all existing contract accounts.
Copyright and related rights waived via CC0.