CellsViewHelper

Description:

Smart contract deployed on Ethereum with Factory features.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/CellsViewHelper.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

/// @title Cells View Helper (maximal, user-scoped, one-shot)
/// @notice One-shot views for Cells/Cell state + owner ENS + chat tails to minimize RPC overhead for UIs.
contract CellsViewHelper {
    /// -----------------------------------------------------------------------
    /// Config
    /// -----------------------------------------------------------------------
    address constant CELLS = 0x000000000022Edf13B917B80B4c0B52fab2eC902;

    // External helper for reverse ENS (must return "" for no-name, never revert)
    address constant CHECK_THE_CHAIN = 0x0000000000cDC1F8d393415455E382c30FBc0a84;

    // Fixed token set in UI-preferred order
    uint256 constant NUM_TOKENS = 9;
    address constant T_ETH = address(0);
    address constant T_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    address constant T_USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
    address constant T_WBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
    address constant T_wstETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0;
    address constant T_rETH = 0xae78736Cd615f374D3085123A210448E74Fc6393;
    address constant T_ENS = 0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72;
    address constant T_LINK = 0x514910771AF9Ca656af840dff83E8264EcF986CA;
    address constant T_PEPE = 0x6982508145454Ce325dDbE47a25d4ec3d2311933;

    uint8 constant D_ETH = 18;
    uint8 constant D_USDC = 6;
    uint8 constant D_USDT = 6;
    uint8 constant D_WBTC = 8;
    uint8 constant D_wstETH = 18;
    uint8 constant D_rETH = 18;
    uint8 constant D_ENS = 18;
    uint8 constant D_LINK = 18;
    uint8 constant D_PEPE = 18;

    // Roles
    uint8 constant ROLE_OWNER0 = 0;
    uint8 constant ROLE_OWNER1 = 1;
    uint8 constant ROLE_GUARD = 3; // UI label: "owner3"
    uint8 constant ROLE_NONE = 255;

    // Selectors
    bytes4 constant ERC20_BALANCE_OF = 0x70a08231; // balanceOf(address)
    bytes4 constant CELL_OWNERS = bytes4(keccak256("owners(uint256)"));
    bytes4 constant CELL_ALLOWANCE = bytes4(keccak256("allowance(address,address)"));
    bytes4 constant CELLS_COUNT = bytes4(keccak256("getCellCount()"));
    bytes4 constant CELLS_AT = bytes4(keccak256("cells(uint256)"));

    // Chat selectors on Cell
    bytes4 constant CELL_CHAT_COUNT = bytes4(keccak256("getChatCount()"));
    bytes4 constant CELL_MESSAGES = bytes4(keccak256("messages(uint256)"));

    // ENS helper
    bytes4 constant WHAT_IS_THE_NAME_OF = bytes4(keccak256("whatIsTheNameOf(address)"));

    /// -----------------------------------------------------------------------
    /// Types
    /// -----------------------------------------------------------------------

    struct CellBasic {
        address cell;
        address[3] owners;
        uint256[NUM_TOKENS] balances; // index 0 is ETH
    }

    struct UserCellState {
        address cell;
        uint8 role; // 0,1,3
        address[3] owners; // <- included for completeness/compatibility
        uint256[NUM_TOKENS] balances; // Cell's balances
        uint256[NUM_TOKENS] allowanceToUser; // allowance(token, user)
        uint256[NUM_TOKENS] allowanceToOtherOwner; // allowance(token, other 1/2 owner). Zero if guardian.
    }

    /// @dev Maximal UI payload per cell for a connected user
    struct CellDeep {
        address cell;
        address[3] owners; // owner0, owner1, guardian
        string[3] ownerENS; // reverse ENS for owners ("" if none or includeENS=false)
        uint8 userRole; // 0,1,3 or 255
        uint256[NUM_TOKENS] balances; // cell token balances
        uint256[NUM_TOKENS] allowanceToUser;
        uint256[NUM_TOKENS] allowanceToOtherOwner;
        uint256 chatCount; // total message count
        string[] lastMessages; // tail, length <= maxChatTail (can be empty)
    }

    /// -----------------------------------------------------------------------
    /// Token meta (for correct decimal handling client-side)
    /// -----------------------------------------------------------------------

    function getTokenList()
        external
        pure
        returns (address[NUM_TOKENS] memory addrs, uint8[NUM_TOKENS] memory decs)
    {
        addrs[0] = T_ETH;
        decs[0] = D_ETH;
        addrs[1] = T_USDC;
        decs[1] = D_USDC;
        addrs[2] = T_USDT;
        decs[2] = D_USDT;
        addrs[3] = T_WBTC;
        decs[3] = D_WBTC;
        addrs[4] = T_wstETH;
        decs[4] = D_wstETH;
        addrs[5] = T_rETH;
        decs[5] = D_rETH;
        addrs[6] = T_ENS;
        decs[6] = D_ENS;
        addrs[7] = T_LINK;
        decs[7] = D_LINK;
        addrs[8] = T_PEPE;
        decs[8] = D_PEPE;
    }

    /// -----------------------------------------------------------------------
    /// Global snapshots (owners + balances) with pagination (back-compat)
    /// -----------------------------------------------------------------------

    function getCellsStateRange(uint256 start, uint256 count)
        public
        view
        returns (CellBasic[] memory out)
    {
        (uint256 s, uint256 n) = _boundedRange(start, count);
        out = new CellBasic[](n);

        for (uint256 i; i != n;) {
            address cell = _getCellAt(s + i);

            address[3] memory os = _owners3(cell);
            uint256[NUM_TOKENS] memory bals = _balancesOf(cell);

            out[i] = CellBasic({cell: cell, owners: os, balances: bals});
            unchecked {
                ++i;
            }
        }
    }

    function getRecentCellsState(uint256 maxCount) external view returns (CellBasic[] memory out) {
        uint256 total = _getCellCount();
        if (maxCount == 0) return out;
        uint256 n = total > maxCount ? maxCount : total;
        uint256 start = total - n;
        return getCellsStateRange(start, n);
    }

    /// -----------------------------------------------------------------------
    /// Per-user one-shot state (back-compat; now includes owners)
    /// -----------------------------------------------------------------------

    function getUserCellsStateRange(address user, uint256 start, uint256 count)
        public
        view
        returns (UserCellState[] memory out)
    {
        (uint256 s, uint256 n) = _boundedRange(start, count);
        if (n == 0) return out;

        address[] memory matched = new address[](n);
        uint8[] memory roles = new uint8[](n);
        uint256 m;

        for (uint256 i; i != n;) {
            address cell = _getCellAt(s + i);
            uint8 roleIdx = _roleInCell(cell, user);
            if (roleIdx != ROLE_NONE) {
                matched[m] = cell;
                roles[m] = roleIdx;
                unchecked {
                    ++m;
                }
            }
            unchecked {
                ++i;
            }
        }
        if (m == 0) return out;

        out = new UserCellState[](m);
        for (uint256 j; j != m;) {
            address cell = matched[j];
            uint8 roleIdx = roles[j];

            address[3] memory os = _owners3(cell);
            uint8 role = roleIdx < 2 ? roleIdx : ROLE_GUARD;

            uint256[NUM_TOKENS] memory bals = _balancesOf(cell);
            uint256[NUM_TOKENS] memory toUser = _allowancesAll(cell, user);

            uint256[NUM_TOKENS] memory toOther;
            if (roleIdx < 2) {
                address other = os[roleIdx ^ 1];
                toOther = _allowancesAll(cell, other);
            }

            out[j] = UserCellState({
                cell: cell,
                role: role,
                owners: os,
                balances: bals,
                allowanceToUser: toUser,
                allowanceToOtherOwner: toOther
            });
            unchecked {
                ++j;
            }
        }
    }

    function getRecentUserCellsState(address user, uint256 maxCount)
        external
        view
        returns (UserCellState[] memory out)
    {
        uint256 total = _getCellCount();
        if (maxCount == 0) return out;
        uint256 n = total > maxCount ? maxCount : total;
        uint256 start = total - n;
        return getUserCellsStateRange(user, start, n);
    }

    /// -----------------------------------------------------------------------
    /// New: maximal per-user one-shot state (owners + ENS + balances + allowances + chat tail)
    /// -----------------------------------------------------------------------

    /// @notice Deep, user-scoped snapshot over [start .. start+count) of Cells array.
    /// @param user          The connected user to scope allowances/role to.
    /// @param start         Start index in Cells.cells[] (0-based).
    /// @param count         Max number of indices to scan from start.
    /// @param maxChatTail   Tail length of messages to include per cell (0 to skip).
    /// @param includeENS    If true, resolves owner ENS with CheckTheChain (3 extra calls per cell).
    function getUserCellsDeepRange(
        address user,
        uint256 start,
        uint256 count,
        uint8 maxChatTail,
        bool includeENS
    ) public view returns (CellDeep[] memory out) {
        (uint256 s, uint256 n) = _boundedRange(start, count);
        if (n == 0) return out;

        // Pass 1: filter membership & cache role indices
        address[] memory matched = new address[](n);
        uint8[] memory roleIdxs = new uint8[](n);
        uint256 m;
        for (uint256 i; i != n;) {
            address cell = _getCellAt(s + i);
            uint8 r = _roleInCell(cell, user);
            if (r != ROLE_NONE) {
                matched[m] = cell;
                roleIdxs[m] = r;
                unchecked {
                    ++m;
                }
            }
            unchecked {
                ++i;
            }
        }
        if (m == 0) return out;

        // Pass 2: build full UI payload
        out = new CellDeep[](m);
        for (uint256 j; j != m;) {
            address cell = matched[j];
            uint8 roleIdx = roleIdxs[j];

            // Owners
            address[3] memory os = _owners3(cell);

            // ENS (explicitly initialize to empty strings if not included)
            string[3] memory ens;
            if (includeENS) {
                ens[0] = _ensName(os[0]);
                ens[1] = _ensName(os[1]);
                ens[2] = _ensName(os[2]);
            } else {
                ens[0] = "";
                ens[1] = "";
                ens[2] = "";
            }

            // Balances & allowances
            uint256[NUM_TOKENS] memory bals = _balancesOf(cell);
            uint256[NUM_TOKENS] memory toUser = _allowancesAll(cell, user);

            uint256[NUM_TOKENS] memory toOther;
            if (roleIdx < 2) {
                address other = os[roleIdx ^ 1];
                toOther = _allowancesAll(cell, other);
            }
            uint8 role = roleIdx < 2 ? roleIdx : ROLE_GUARD;

            // Chat tail (always initialize dynamic array)
            string[] memory tail;
            uint256 chatCount = _chatCount(cell);
            if (maxChatTail != 0 && chatCount != 0) {
                uint256 k = chatCount > maxChatTail ? uint256(maxChatTail) : chatCount;
                tail = new string[](k);
                uint256 startIdx = chatCount - k;
                for (uint256 t; t != k;) {
                    tail[t] = _messageAt(cell, startIdx + t);
                    unchecked {
                        ++t;
                    }
                }
            }

            // Assign
            CellDeep memory row;
            row.cell = cell;
            row.owners = os;
            row.ownerENS = ens;
            row.userRole = role;
            row.balances = bals;
            row.allowanceToUser = toUser;
            row.allowanceToOtherOwner = toOther;
            row.chatCount = chatCount;
            row.lastMessages = tail;

            out[j] = row;
            unchecked {
                ++j;
            }
        }
    }

    /// @notice Deep, user-scoped snapshot over the most recent Cells.
    function getRecentUserCellsDeep(
        address user,
        uint256 maxCount,
        uint8 maxChatTail,
        bool includeENS
    ) external view returns (CellDeep[] memory out) {
        uint256 total = _getCellCount();
        if (maxCount == 0) return out;
        uint256 n = total > maxCount ? maxCount : total;
        uint256 start = total - n;
        return getUserCellsDeepRange(user, start, n, maxChatTail, includeENS);
    }

    /// -----------------------------------------------------------------------
    /// Small helpers for delta-refreshes & batched lookups
    /// -----------------------------------------------------------------------

    /// @notice Batched approved[] lookups so UI avoids N separate RPCs.
    function getApprovedBatch(address cell, bytes32[] calldata hashes)
        external
        view
        returns (address[] memory out)
    {
        out = new address[](hashes.length);
        for (uint256 i; i != hashes.length;) {
            (bool ok, bytes memory data) =
                cell.staticcall(abi.encodeWithSignature("approved(bytes32)", hashes[i]));
            if (ok && data.length >= 32) {
                out[i] = abi.decode(data, (address));
            }
            unchecked {
                ++i;
            }
        }
    }

    /// @notice Get last N messages without re-pulling the world.
    function getRecentMessages(address cell, uint256 maxCount)
        external
        view
        returns (string[] memory out)
    {
        uint256 count = _chatCount(cell);
        if (count == 0 || maxCount == 0) return out;
        uint256 n = count > maxCount ? maxCount : count;
        uint256 start = count - n;
        out = _messagesRange(cell, start, n);
    }

    /// @notice Fetch a messages slice (for delta appends).
    function getMessagesRange(address cell, uint256 start, uint256 count)
        external
        view
        returns (string[] memory out)
    {
        uint256 len = _chatCount(cell);
        if (start >= len || count == 0) return out;
        uint256 end = start + count;
        if (end > len) end = len;
        uint256 n = end - start;
        out = _messagesRange(cell, start, n);
    }

    /// Optional passthrough (handy in UIs)
    function getCellsCount() external view returns (uint256) {
        return _getCellCount();
    }

    /// -----------------------------------------------------------------------
    /// Internals: bounds, owners, balances, allowances, chat, ENS
    /// -----------------------------------------------------------------------

    function _boundedRange(uint256 start, uint256 count)
        internal
        view
        returns (uint256 s, uint256 n)
    {
        uint256 total = _getCellCount();
        if (start >= total || count == 0) return (0, 0);
        s = start;
        uint256 end = start + count;
        if (end > total) end = total;
        n = end - start;
    }

    function _getCellCount() internal view returns (uint256 count) {
        (bool ok, bytes memory data) = CELLS.staticcall(abi.encodeWithSelector(CELLS_COUNT));
        if (ok && data.length >= 32) count = abi.decode(data, (uint256));
    }

    function _getCellAt(uint256 idx) internal view returns (address cell) {
        (bool ok, bytes memory data) = CELLS.staticcall(abi.encodeWithSelector(CELLS_AT, idx));
        if (ok && data.length >= 32) cell = abi.decode(data, (address));
    }

    function _ownerAt(address cell, uint256 slot) internal view returns (address o) {
        (bool ok, bytes memory data) = cell.staticcall(abi.encodeWithSelector(CELL_OWNERS, slot));
        if (ok && data.length >= 32) o = abi.decode(data, (address));
    }

    function _owners3(address cell) internal view returns (address[3] memory os) {
        os[0] = _ownerAt(cell, 0);
        os[1] = _ownerAt(cell, 1);
        os[2] = _ownerAt(cell, 2);
    }

    /// @dev returns 0/1/2 if in cell (owner0/owner1/guardian), else 255.
    function _roleInCell(address cell, address user) internal view returns (uint8 r) {
        address o0 = _ownerAt(cell, 0);
        if (user == o0) return ROLE_OWNER0;
        address o1 = _ownerAt(cell, 1);
        if (user == o1) return ROLE_OWNER1;
        address og = _ownerAt(cell, 2);
        if (user == og) return 2; // guardian => mapped to ROLE_GUARD by caller
        return ROLE_NONE;
    }

    function _balancesOf(address cell) internal view returns (uint256[NUM_TOKENS] memory b) {
        b[0] = cell.balance; // ETH
        b[1] = _erc20Bal(T_USDC, cell);
        b[2] = _erc20Bal(T_USDT, cell);
        b[3] = _erc20Bal(T_WBTC, cell);
        b[4] = _erc20Bal(T_wstETH, cell);
        b[5] = _erc20Bal(T_rETH, cell);
        b[6] = _erc20Bal(T_ENS, cell);
        b[7] = _erc20Bal(T_LINK, cell);
        b[8] = _erc20Bal(T_PEPE, cell);
    }

    function _allowancesAll(address cell, address spender)
        internal
        view
        returns (uint256[NUM_TOKENS] memory a)
    {
        a[0] = _cellAllowance(cell, T_ETH, spender);
        a[1] = _cellAllowance(cell, T_USDC, spender);
        a[2] = _cellAllowance(cell, T_USDT, spender);
        a[3] = _cellAllowance(cell, T_WBTC, spender);
        a[4] = _cellAllowance(cell, T_wstETH, spender);
        a[5] = _cellAllowance(cell, T_rETH, spender);
        a[6] = _cellAllowance(cell, T_ENS, spender);
        a[7] = _cellAllowance(cell, T_LINK, spender);
        a[8] = _cellAllowance(cell, T_PEPE, spender);
    }

    function _erc20Bal(address token, address account) internal view returns (uint256 bal) {
        if (token == address(0)) return account.balance;
        (bool ok, bytes memory data) =
            token.staticcall(abi.encodeWithSelector(ERC20_BALANCE_OF, account));
        if (ok && data.length >= 32) bal = abi.decode(data, (uint256));
    }

    function _cellAllowance(address cell, address token, address spender)
        internal
        view
        returns (uint256 amt)
    {
        (bool ok, bytes memory data) =
            cell.staticcall(abi.encodeWithSelector(CELL_ALLOWANCE, token, spender));
        if (ok && data.length >= 32) amt = abi.decode(data, (uint256));
    }

    function _chatCount(address cell) internal view returns (uint256 cnt) {
        (bool ok, bytes memory data) = cell.staticcall(abi.encodeWithSelector(CELL_CHAT_COUNT));
        if (ok && data.length >= 32) cnt = abi.decode(data, (uint256));
    }

    function _messagesRange(address cell, uint256 start, uint256 n)
        internal
        view
        returns (string[] memory out)
    {
        out = new string[](n);
        for (uint256 i; i != n;) {
            out[i] = _messageAt(cell, start + i);
            unchecked {
                ++i;
            }
        }
    }

    function _messageAt(address cell, uint256 idx) internal view returns (string memory s) {
        (bool ok, bytes memory data) = cell.staticcall(abi.encodeWithSelector(CELL_MESSAGES, idx));
        if (!ok || data.length < 64) return "";
        try this._decodeString(data) returns (string memory t) {
            return t;
        } catch {
            return "";
        }
    }

    /// @dev Calls CheckTheChain.whatIsTheNameOf(user); returns "" on any failure.
    function _ensName(address user) internal view returns (string memory name) {
        if (user == address(0)) return "";
        (bool ok, bytes memory data) =
            CHECK_THE_CHAIN.staticcall(abi.encodeWithSelector(WHAT_IS_THE_NAME_OF, user));
        if (!ok || data.length < 64) return "";
        try this._decodeString(data) returns (string memory s) {
            return s;
        } catch {
            return "";
        }
    }

    /// @dev helper to permit try/catch on abi.decode (must be external)
    function _decodeString(bytes memory data) external pure returns (string memory s) {
        s = abi.decode(data, (string));
    }
}
"
    }
  },
  "settings": {
    "remappings": [
      "@solady/=lib/solady/",
      "@forge/=lib/forge-std/src/",
      "forge-std/=lib/forge-std/src/",
      "solady/=lib/solady/src/"
    ],
    "optimizer": {
      "enabled": true,
      "runs": 9999999
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "ipfs",
      "appendCBOR": true
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "evmVersion": "cancun",
    "viaIR": true
  }
}}

Tags:
Factory|addr:0x8722c33b3c60fb03fd4e7c3de8891f472dec18a9|verified:true|block:23746101|tx:0x79720e5bc0f8b04e7ab397d6646d9f628c43d734ed9966ebe4ca6748b84bbfe2|first_check:1762517274

Submitted on: 2025-11-07 13:07:55

Comments

Log in to comment.

No comments yet.