Skip to content

Storage Layout

DISCLAIMER // NFA // DYOR

This analysis is based on observations of the contract behavior. We are not smart contract security experts. This document aims to explain what the contract appears to do based on the code. It should not be considered a comprehensive security audit or financial advice. Always verify critical information independently and consult with blockchain security professionals for important decisions.

⊙ generated by robots | curated by humans

METADATA
Contract Address 0x00000000...feAaBC (etherscan)
Network Ethereum Mainnet
Analysis Date 2026-05-02

Variables

SLOW — Persistent Storage (declaration order)

Slot indices below follow Solidity's declaration order. Mappings consume one slot for the base hash; entries are computed at keccak256(key . slot). Solady's ERC1155 base contract does not use sequential slots — it stores _balances, _operatorApprovals, and the URI under fixed keccak256 namespaces, so they do not collide with SLOW's sequentially-allocated slots below.

SLOT VARIABLE NAME TYPE CURRENT VALUE / NOTES PURPOSE
0 guardianApproved mapping(address => mapping(uint256 => bool)) empty for all observed users Per-user table of pre-approved transferIds. Set by approveTransfer, cleared on consumption or by revokeApproval.
1 _outboundTransfers mapping(address => EnumerableSetLib.Uint256Set) empty for all observed users Set of currently-pending transferIds where the user is the sender. Used by getOutboundTransfers / outboundTransferAt.
2 _inboundTransfers mapping(address => EnumerableSetLib.Uint256Set) empty for all observed users Set of currently-pending transferIds where the user is the recipient. Anyone can grow this set on another user via dust deposits; no ACL on the deposit recipient.
3 unlockedBalances mapping(address => mapping(uint256 => uint256)) 0 for all observed (user, id) pairs Spendable wrapper balances per (user, id) — credited on unlock (post-expiry), reverse, clawback, and immediate (delay=0) deposits. Decremented on withdrawFrom and safeTransferFrom.
4 pendingTransfers mapping(uint256 => PendingTransfer) 0 for all observed transferIds Live escrow records keyed by transferId. Struct: (uint96 timestamp, address from, address to, uint256 id, uint256 amount).
5 lastGuardianChange mapping(address => uint256) 0 for all observed users Timestamp of the user's last setGuardian (first-time set) or commitGuardian (rotation). Mixed into every transferId preimage so a guardian rotation atomically invalidates dangling approvals.
6 pendingGuardian mapping(address => PendingGuardian) 0 for all observed users Staged guardian rotation. Struct: (address guardian, uint96 effectiveAt). effectiveAt != 0 means a rotation is in flight.
7 guardians mapping(address => address) 0 for all observed users Currently active guardian per user. Zero means no guardian is set.
8 nonces mapping(address => uint256) 4 for 0x1C0Aa8cC...855A20 (deployer); 0 everywhere else observed Per-user counter incremented on every nonce-bumping op (safeTransferFrom with delay or guardian, withdrawFrom with guardian, delayed _finishDeposit). Mixed into transferId preimages to make approvals single-use.

SLOW — Immutable Storage (deploy-time constants in code)

NAME TYPE VALUE PURPOSE
gate address (public) 0xb8B546b9...9ef3D4 (etherscan) CREATE2-deployed SLOWGate companion. Sole authorized caller of claimTipped.
htmlChunk1 address (internal) 0x745c9c91...21723e (etherscan) First SSTORE2 chunk of the on-chain dapp HTML. Read by html().
htmlChunk2 address (internal) 0x62a765fc...9863f8 (etherscan) Second SSTORE2 chunk. Concatenated with htmlChunk1 in html().

SLOW — Compile-time Constants

NAME VALUE PURPOSE
_GUARDIAN_CHANGE_DELAY 1 days (86400 s) Veto window for guardian rotation.
_CLAWBACK_GRACE 30 days (2592000 s) Wait after timelock expiry before sender can clawback.
_OP_TRANSFER 0 Op-type byte mixed into the preimage of safeTransferFrom transferIds.
_OP_WITHDRAW 1 Op-type byte mixed into the preimage of withdrawFrom transferIds.

SLOW — Inherited Storage (Solady)

The Solady ERC-1155 base does not use sequential slot allocation; it derives slot keys from fixed namespaces to avoid colliding with the inheriting contract's own variables. Notable inherited storage:

  • _balances[id][owner] — ERC-1155 balance table (used for the wrapper supply per id).
  • _operatorApprovals[owner][operator] — ERC-1155 operator-approval flag (used by unlock, claim, reverse, clawback to check isApprovedForAll).
  • The inherited Multicallable and ReentrancyGuardTransient keep no persistent storage; the latter uses transient storage.

SLOW — Transient Storage (TSTORE / TLOAD, EIP-1153)

SLOT PURPOSE
Solady-derived Reentrancy lock used by every nonReentrant external entry point (setGuardian, unlock, claim, claimTipped, depositTo, depositToWithTip, withdrawFrom, safeTransferFrom, reverse, clawback). Cleared automatically at transaction end.

SLOWGate — Persistent Storage

SLOT VARIABLE NAME TYPE CURRENT VALUE / NOTES PURPOSE
0 tips mapping(uint256 => Tip) empty (no tips currently held) Posted relayer tips keyed by transferId. Struct: (uint96 amount, address sender) — packs into one slot.

SLOWGate — Immutable Storage

NAME TYPE VALUE PURPOSE
slow SLOW (public) 0x00000000...feAaBC (etherscan) (the parent contract) Set in the gate constructor as SLOW(msg.sender) — locked to the address that deployed it.

Diagrams

graph TB
    subgraph SLOWStorage["SLOW Persistent Storage (declaration order)"]
        S0["Slot 0: guardianApproved<br/>mapping(user → mapping(transferId → bool))"]
        S1["Slot 1: _outboundTransfers<br/>mapping(user → Uint256Set)"]
        S2["Slot 2: _inboundTransfers<br/>mapping(user → Uint256Set)<br/>(can be grown by anyone via dust deposits)"]
        S3["Slot 3: unlockedBalances<br/>mapping(user → mapping(id → uint256))"]
        S4["Slot 4: pendingTransfers<br/>mapping(transferId → PendingTransfer)"]
        S5["Slot 5: lastGuardianChange<br/>mapping(user → uint256)"]
        S6["Slot 6: pendingGuardian<br/>mapping(user → PendingGuardian)"]
        S7["Slot 7: guardians<br/>mapping(user → address)"]
        S8["Slot 8: nonces<br/>mapping(user → uint256)"]
    end

    subgraph SLOWImmut["SLOW Immutables"]
        Gate["gate → 0xb8B546b9...9ef3D4"]
        Chunk1["htmlChunk1 → 0x745c9c91...21723e (SSTORE2)"]
        Chunk2["htmlChunk2 → 0x62a765fc...9863f8 (SSTORE2)"]
    end

    subgraph SoladyInherit["Inherited (Solady, fixed namespaces)"]
        Bal["_balances[id][owner] (ERC-1155 supply)"]
        Op["_operatorApprovals[owner][operator]"]
    end

    subgraph GateStorage["SLOWGate Storage"]
        T0["Slot 0: tips<br/>mapping(transferId → Tip(uint96 amount, address sender))"]
        SlowImm["immutable slow → SLOW (parent)"]
    end

    subgraph Transient["Transient Storage (EIP-1153, Solady)"]
        Reent["nonReentrant lock (cleared at tx end)"]
    end

    Gate -.->|deployed via CREATE2 from SLOW constructor| GateStorage

    style S4 fill:#ffe1e1
    style S7 fill:#fff4e1
    style S8 fill:#fff4e1
    style T0 fill:#e1f5ff
    style Reent fill:#e1f5ff

Composite Id Encoding

The ERC-1155 token id encodes both the underlying token address and the timelock delay:

bit:   255                                   160  159                                          0
        |                                       |  |                                            |
        |  delay (96 bits, seconds, uint96)     |  |  token address (160 bits; 0 = native ETH)  |

Round-trip helpers:

  • encodeId(token, delay) = uint256(uint160(token)) | (uint256(delay) << 160)
  • decodeId(id) = (address(uint160(id)), id >> 160)

delay == 0 means the wrapper mints unlocked (no escrow row created); delay > 0 mints the wrapper to to and creates a pendingTransfers[transferId] row simultaneously.

Read-Verification Commands

C=0x000000000000888741B254d37e1b27128AfEAaBC
G=0xb8B546b93a82f4Aa6f0345142dF5679B659ef3D4

# IMMUTABLES & METADATA
cast call $C "gate()(address)"
cast call $C "name()(string)"
cast call $C "symbol()(string)"

# PUBLIC MAPPING GETTERS (per-user reads — substitute USER)
USER=0x1C0Aa8cCD568d90d61659F060D1bFb1e6f855A20
cast call $C "nonces(address)(uint256)"             $USER
cast call $C "guardians(address)(address)"          $USER
cast call $C "lastGuardianChange(address)(uint256)" $USER
cast call $C "pendingGuardian(address)(address,uint96)" $USER

# PENDING TRANSFER LOOKUP (per transferId)
TID=0x...
cast call $C "pendingTransfers(uint256)(uint96,address,address,uint256,uint256)" $TID

# OUTBOUND / INBOUND TRANSFER ENUMERATION
cast call $C "outboundTransferCount(address)(uint256)" $USER
cast call $C "inboundTransferCount(address)(uint256)"  $USER
cast call $C "outboundTransferAt(address,uint256)(uint256)" $USER 0
cast call $C "inboundTransferAt(address,uint256)(uint256)"  $USER 0
cast call $C "getOutboundTransfers(address)(uint256[])" $USER  # bounded by gas — paginate large sets
cast call $C "getInboundTransfers(address)(uint256[])"  $USER  # same caveat

# GATE STATE
cast call $G "slow()(address)"
cast call $G "tips(uint256)(uint96,address)" $TID

# CONTRACT BALANCES
cast balance $C
cast balance $G