Accounts
0x00...64e6
0x00...64e6

0x00...64e6

$500
This contract's source code is verified!
Contract Metadata
Compiler
0.8.20+commit.a1b79de6
Language
Solidity
Contract Source Code
File 1 of 4: EventsAndErrors.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.17 .0;

interface EventsAndErrors {
    error MsgEmpty();
    error InvalidRange();
    error InvalidStartIndex();
    error InvalidEndIndex();

    event MessageSent(
        address indexed sender,
        string indexed topic,
        uint256 messageIndex
    );

    event MessageSentViaApp(
        address indexed app,
        address indexed sender,
        string indexed topic,
        uint256 messageIndex
    );
}
Contract Source Code
File 2 of 4: INet.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.17 .0;

interface INet {
    struct Message {
        address app;
        address sender;
        uint256 timestamp;
        bytes data;
        string text;
        string topic;
    }

    function sendMessageViaApp(
        address sender,
        string calldata text,
        string calldata topic,
        bytes calldata extraData
    ) external;

    function sendMessage(
        string calldata text,
        string calldata topic,
        bytes calldata extraData
    ) external;

    // **************
    // Fetch Messages
    // **************

    // Fetch message indexes

    function getMessageIdxForApp(
        uint256 idx,
        address app
    ) external view returns (uint256);

    function getMessageIdxForAppUser(
        uint256 idx,
        address app,
        address user
    ) external view returns (uint256);

    function getMessageIdxForAppTopic(
        uint256 idx,
        address app,
        string calldata topic
    ) external view returns (uint256);

    function getMessageIdxForAppUserTopic(
        uint256 idx,
        address app,
        address user,
        string calldata topic
    ) external view returns (uint256);

    // Fetch single message

    function getMessage(uint256 idx) external view returns (Message memory);

    function getMessageForApp(
        uint256 idx,
        address app
    ) external view returns (Message memory);

    function getMessageForAppUser(
        uint256 idx,
        address app,
        address user
    ) external view returns (Message memory);

    function getMessageForAppTopic(
        uint256 idx,
        address app,
        string calldata topic
    ) external view returns (Message memory);

    function getMessageForAppUserTopic(
        uint256 idx,
        address app,
        address user,
        string calldata topic
    ) external view returns (Message memory);

    // Fetch multiple messages

    function getMessagesInRange(
        uint256 startIdx,
        uint256 endIdx
    ) external view returns (Message[] memory);

    function getMessagesInRangeForApp(
        uint256 startIdx,
        uint256 endIdx,
        address app
    ) external view returns (Message[] memory);

    function getMessagesInRangeForAppUser(
        uint256 startIdx,
        uint256 endIdx,
        address app,
        address user
    ) external view returns (Message[] memory);

    function getMessagesInRangeForAppTopic(
        uint256 startIdx,
        uint256 endIdx,
        address app,
        string calldata topic
    ) external view returns (Message[] memory);

    function getMessagesInRangeForAppUserTopic(
        uint256 startIdx,
        uint256 endIdx,
        address app,
        address user,
        string calldata topic
    ) external view returns (Message[] memory);

    // **************
    // Message counts
    // **************

    function getTotalMessagesCount() external view returns (uint256);

    function getTotalMessagesForAppCount(
        address app
    ) external view returns (uint256);

    function getTotalMessagesForAppUserCount(
        address app,
        address user
    ) external view returns (uint256);

    function getTotalMessagesForAppTopicCount(
        address app,
        string calldata topic
    ) external view returns (uint256);

    function getTotalMessagesForAppUserTopicCount(
        address app,
        address user,
        string calldata topic
    ) external view returns (uint256);
}
Contract Source Code
File 3 of 4: Net.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.17 .0;

import {EventsAndErrors} from "./EventsAndErrors.sol";
import {INet} from "./INet.sol";
import {SSTORE2} from "@solady/utils/SSTORE2.sol";

/// @title Net
/// @author Aspyn Palatnick (aspyn.eth, stuckinaboot.eth)
/// @notice Fully decentralized onchain messaging protocol
contract Net is INet, EventsAndErrors {
    // Use a single global mapping to map hashes to message indexes
    mapping(bytes32 hashVal => uint256[] messageIndexes)
        public hashToMessageIndexes;

    address[] public messagePointers;

    bytes32 constant ZERO_HASH = keccak256(abi.encodePacked(address(0)));

    // Empty topic "" will not impact a hash, which could result in collisions
    // between hash values that use topic and don't use topic. For that reason,
    // we prefix the relevant hash topic keys with these values to ensure collisions don't occur
    // Example if this prefix didn't exist:
    // keccak256(abi.encodePacked(address(0))) == keccak256(abi.encodePacked(address(0), "" /* where "" represents topic */)) evaluates to true
    uint256 constant APP_TOPIC_HASH_PREFIX = 1;
    uint256 constant APP_USER_TOPIC_HASH_PREFIX = 2;

    // ************
    // Send message
    // ************

    /// @notice Send message via app
    /// @param sender message sender
    /// @param text message text
    /// @param topic message topic
    /// @param data message data
    function sendMessageViaApp(
        address sender,
        string calldata text,
        string calldata topic,
        bytes calldata data
    ) external {
        // Revert if message length is none to prevent empty messages
        if (bytes(text).length == 0 && bytes(data).length == 0) {
            revert MsgEmpty();
        }

        // Track message index in topic and user mappings
        uint256 messagesLength = messagePointers.length;

        // App messages
        hashToMessageIndexes[keccak256(abi.encodePacked(msg.sender))].push(
            messagesLength
        );

        // App-user messages
        hashToMessageIndexes[keccak256(abi.encodePacked(msg.sender, sender))]
            .push(messagesLength);

        // App-topic messages
        hashToMessageIndexes[
            // msg.sender is the app id
            keccak256(
                abi.encodePacked(APP_TOPIC_HASH_PREFIX, msg.sender, topic)
            )
        ].push(messagesLength);

        // App-user-topic messages
        hashToMessageIndexes[
            keccak256(
                abi.encodePacked(
                    APP_USER_TOPIC_HASH_PREFIX,
                    msg.sender,
                    sender,
                    topic
                )
            )
        ].push(messagesLength);

        // Emit message sent using current messages length as the index
        emit MessageSentViaApp(msg.sender, sender, topic, messagesLength);

        // Store message
        messagePointers.push(
            SSTORE2.write(
                abi.encode(
                    // App
                    msg.sender,
                    // Sender
                    sender,
                    // Timestamp
                    block.timestamp,
                    // Data
                    data,
                    // Text
                    text,
                    // Topic
                    topic
                )
            )
        );
    }

    /// @notice Send message
    /// @param text message text
    /// @param topic message topic
    /// @param data message data
    function sendMessage(
        string calldata text,
        string calldata topic,
        bytes calldata data
    ) external {
        // Revert if message length is none to prevent empty messages
        if (bytes(text).length == 0 && bytes(data).length == 0) {
            revert MsgEmpty();
        }

        // Track message index in topic and user mappings
        uint256 messagesLength = messagePointers.length;

        // address(0) is used to represent messages sent from "no app"
        hashToMessageIndexes[ZERO_HASH].push(messagesLength);
        hashToMessageIndexes[
            keccak256(
                abi.encodePacked(APP_TOPIC_HASH_PREFIX, address(0), topic)
            )
        ].push(messagesLength);
        hashToMessageIndexes[
            keccak256(abi.encodePacked(address(0), msg.sender))
        ].push(messagesLength);
        hashToMessageIndexes[
            keccak256(
                abi.encodePacked(
                    APP_USER_TOPIC_HASH_PREFIX,
                    address(0),
                    msg.sender,
                    topic
                )
            )
        ].push(messagesLength);

        // Emit message sent using current messages length as the index
        emit MessageSent(msg.sender, topic, messagesLength);

        // Store message
        messagePointers.push(
            SSTORE2.write(
                abi.encode(
                    // App
                    address(0),
                    // Sender
                    msg.sender,
                    // Timestamp
                    block.timestamp,
                    // Data
                    data,
                    // Text
                    text,
                    // Topic
                    topic
                )
            )
        );
    }

    // **************
    // Fetch Messages
    // **************

    // Fetch message indexes

    /// @notice Get message pointer index for app
    /// @param idx message index
    /// @param app app
    /// @return index index
    function getMessageIdxForApp(
        uint256 idx,
        address app
    ) external view returns (uint256) {
        return hashToMessageIndexes[keccak256(abi.encodePacked(app))][idx];
    }

    /// @notice Get message pointer index for app user
    /// @param idx message index
    /// @param app app
    /// @param user user
    /// @return index index
    function getMessageIdxForAppUser(
        uint256 idx,
        address app,
        address user
    ) external view returns (uint256) {
        return
            hashToMessageIndexes[keccak256(abi.encodePacked(app, user))][idx];
    }

    /// @notice Get message pointer index for app topic
    /// @param idx message index
    /// @param app app
    /// @param topic topic
    /// @return index index
    function getMessageIdxForAppTopic(
        uint256 idx,
        address app,
        string calldata topic
    ) external view returns (uint256) {
        return
            hashToMessageIndexes[
                keccak256(abi.encodePacked(APP_TOPIC_HASH_PREFIX, app, topic))
            ][idx];
    }

    /// @notice Get message pointer index for app user topic
    /// @param idx message index
    /// @param app app
    /// @param user user
    /// @param topic topic
    /// @return index index
    function getMessageIdxForAppUserTopic(
        uint256 idx,
        address app,
        address user,
        string calldata topic
    ) external view returns (uint256) {
        return
            hashToMessageIndexes[
                keccak256(
                    abi.encodePacked(
                        APP_USER_TOPIC_HASH_PREFIX,
                        app,
                        user,
                        topic
                    )
                )
            ][idx];
    }

    // Fetch single message

    /// @notice Decode encoded message
    /// @param encodedMessage encoded message
    /// @return decodedMessage decoded message
    function decodeMessage(
        bytes memory encodedMessage
    ) public pure returns (Message memory) {
        Message memory message;
        (
            message.app,
            message.sender,
            message.timestamp,
            message.data,
            message.text,
            message.topic
        ) = abi.decode(
            encodedMessage,
            (
                // App
                address,
                // Sender
                address,
                // Timestamp
                uint256,
                // Data
                bytes,
                // Text
                string,
                // Topic
                string
            )
        );
        return message;
    }

    /// @notice Decode message at index in message pointers
    /// @param idx index
    /// @return decodedMessage decoded message
    function decodeMessageAtIndex(
        uint256 idx
    ) public view returns (Message memory) {
        return decodeMessage(SSTORE2.read(messagePointers[idx]));
    }

    /// @notice Get message
    /// @param idx index
    /// @return message message
    function getMessage(uint256 idx) external view returns (Message memory) {
        return decodeMessageAtIndex(idx);
    }

    /// @notice Get message for app
    /// @param idx index
    /// @param app app
    /// @return message message
    function getMessageForApp(
        uint256 idx,
        address app
    ) external view returns (Message memory) {
        return
            decodeMessageAtIndex(
                hashToMessageIndexes[keccak256(abi.encodePacked(app))][idx]
            );
    }

    /// @notice Get message for app user
    /// @param idx index
    /// @param app app
    /// @param user user
    /// @return message message
    function getMessageForAppUser(
        uint256 idx,
        address app,
        address user
    ) external view returns (Message memory) {
        return
            decodeMessageAtIndex(
                hashToMessageIndexes[keccak256(abi.encodePacked(app, user))][
                    idx
                ]
            );
    }

    /// @notice Get message for app topic
    /// @param idx index
    /// @param app app
    /// @param topic topic
    /// @return message message
    function getMessageForAppTopic(
        uint256 idx,
        address app,
        string calldata topic
    ) external view returns (Message memory) {
        return
            decodeMessageAtIndex(
                hashToMessageIndexes[
                    keccak256(
                        abi.encodePacked(APP_TOPIC_HASH_PREFIX, app, topic)
                    )
                ][idx]
            );
    }

    /// @notice Get message for app user topic
    /// @param idx index
    /// @param app app
    /// @param user user
    /// @param topic topic
    /// @return message message
    function getMessageForAppUserTopic(
        uint256 idx,
        address app,
        address user,
        string calldata topic
    ) external view returns (Message memory) {
        return
            decodeMessageAtIndex(
                hashToMessageIndexes[
                    keccak256(
                        abi.encodePacked(
                            APP_USER_TOPIC_HASH_PREFIX,
                            app,
                            user,
                            topic
                        )
                    )
                ][idx]
            );
    }

    // Fetch multiple messages

    /// @notice Get messages in range
    /// @param startIdx start index
    /// @param endIdx end index
    /// @return messages list of messages
    function getMessagesInRange(
        uint256 startIdx,
        uint256 endIdx
    ) external view returns (Message[] memory) {
        if (startIdx >= endIdx) {
            revert InvalidRange();
        }
        uint256 querySetLength = messagePointers.length;
        if (startIdx + 1 > querySetLength) {
            revert InvalidStartIndex();
        }
        if (endIdx > querySetLength) {
            revert InvalidEndIndex();
        }

        Message[] memory messagesSlice = new Message[](endIdx - startIdx);
        uint256 idxInMessages = startIdx;
        unchecked {
            for (; idxInMessages < endIdx; ) {
                messagesSlice[idxInMessages - startIdx] = decodeMessageAtIndex(
                    idxInMessages
                );
                ++idxInMessages;
            }
        }

        return messagesSlice;
    }

    /// @notice Get messages in range for hash
    /// @param startIdx start index
    /// @param endIdx end index
    /// @param hashVal hash
    /// @return messages list of messages
    function getMessagesInRangeForHash(
        uint256 startIdx,
        uint256 endIdx,
        bytes32 hashVal
    ) public view returns (Message[] memory) {
        if (startIdx >= endIdx) {
            revert InvalidRange();
        }
        uint256 querySetLength = hashToMessageIndexes[hashVal].length;
        if (startIdx + 1 > querySetLength) {
            revert InvalidStartIndex();
        }
        if (endIdx > querySetLength) {
            revert InvalidEndIndex();
        }

        Message[] memory messagesSlice = new Message[](endIdx - startIdx);
        uint256 idxInMessages = startIdx;
        unchecked {
            for (; idxInMessages < endIdx; ) {
                messagesSlice[idxInMessages - startIdx] = decodeMessageAtIndex(
                    hashToMessageIndexes[hashVal][idxInMessages]
                );
                ++idxInMessages;
            }
        }

        return messagesSlice;
    }

    /// @notice Get messages in range for app
    /// @param startIdx start index
    /// @param endIdx end index
    /// @param app app
    /// @return messages list of messages
    function getMessagesInRangeForApp(
        uint256 startIdx,
        uint256 endIdx,
        address app
    ) external view returns (Message[] memory) {
        return
            getMessagesInRangeForHash(
                startIdx,
                endIdx,
                keccak256(abi.encodePacked(app))
            );
    }

    /// @notice Get messages in range for app user
    /// @param startIdx start index
    /// @param endIdx end index
    /// @param app app
    /// @param user user
    /// @return messages list of messages
    function getMessagesInRangeForAppUser(
        uint256 startIdx,
        uint256 endIdx,
        address app,
        address user
    ) external view returns (Message[] memory) {
        return
            getMessagesInRangeForHash(
                startIdx,
                endIdx,
                keccak256(abi.encodePacked(app, user))
            );
    }

    /// @notice Get messages in range for app topic
    /// @param startIdx start index
    /// @param endIdx end index
    /// @param app app
    /// @param topic topic
    /// @return messages list of messages
    function getMessagesInRangeForAppTopic(
        uint256 startIdx,
        uint256 endIdx,
        address app,
        string calldata topic
    ) external view returns (Message[] memory) {
        return
            getMessagesInRangeForHash(
                startIdx,
                endIdx,
                keccak256(abi.encodePacked(APP_TOPIC_HASH_PREFIX, app, topic))
            );
    }

    /// @notice Get messages in range for app user topic
    /// @param startIdx start index
    /// @param endIdx end index
    /// @param app app
    /// @param user user
    /// @param topic topic
    /// @return messages list of messages
    function getMessagesInRangeForAppUserTopic(
        uint256 startIdx,
        uint256 endIdx,
        address app,
        address user,
        string calldata topic
    ) external view returns (Message[] memory) {
        return
            getMessagesInRangeForHash(
                startIdx,
                endIdx,
                keccak256(
                    abi.encodePacked(
                        APP_USER_TOPIC_HASH_PREFIX,
                        app,
                        user,
                        topic
                    )
                )
            );
    }

    // **************
    // Message counts
    // **************

    /// @notice Get total messages count
    /// @return count count
    function getTotalMessagesCount() external view returns (uint256) {
        return messagePointers.length;
    }

    /// @notice Get total messages for hash count
    /// @param hashVal hash
    /// @return count count
    function getTotalMessagesForHashCount(
        bytes32 hashVal
    ) public view returns (uint256) {
        return hashToMessageIndexes[hashVal].length;
    }

    /// @notice Get total messages for app count
    /// @param app app
    /// @return count count
    function getTotalMessagesForAppCount(
        address app
    ) external view returns (uint256) {
        return getTotalMessagesForHashCount(keccak256(abi.encodePacked(app)));
    }

    /// @notice Get total messages for app user count
    /// @param app app
    /// @param user user
    /// @return count count
    function getTotalMessagesForAppUserCount(
        address app,
        address user
    ) external view returns (uint256) {
        return
            getTotalMessagesForHashCount(
                keccak256(abi.encodePacked(app, user))
            );
    }

    /// @notice Get total messages for app topic count
    /// @param app app
    /// @param topic topic
    /// @return count count
    function getTotalMessagesForAppTopicCount(
        address app,
        string calldata topic
    ) external view returns (uint256) {
        return
            getTotalMessagesForHashCount(
                keccak256(abi.encodePacked(APP_TOPIC_HASH_PREFIX, app, topic))
            );
    }

    /// @notice Get total messages for app user topic count
    /// @param app app
    /// @param user user
    /// @param topic topic
    /// @return count count
    function getTotalMessagesForAppUserTopicCount(
        address app,
        address user,
        string calldata topic
    ) external view returns (uint256) {
        return
            getTotalMessagesForHashCount(
                keccak256(
                    abi.encodePacked(
                        APP_USER_TOPIC_HASH_PREFIX,
                        app,
                        user,
                        topic
                    )
                )
            );
    }
}
Contract Source Code
File 4 of 4: SSTORE2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Read and write to persistent storage at a fraction of the cost.
/// @author Solady (https://github.com/vectorized/solmady/blob/main/src/utils/SSTORE2.sol)
/// @author Saw-mon-and-Natalie (https://github.com/Saw-mon-and-Natalie)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol)
library SSTORE2 {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev We skip the first byte as it's a STOP opcode,
    /// which ensures the contract can't be called.
    uint256 internal constant DATA_OFFSET = 1;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                        CUSTOM ERRORS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Unable to deploy the storage contract.
    error DeploymentFailed();

    /// @dev The storage contract address is invalid.
    error InvalidPointer();

    /// @dev Attempt to read outside of the storage contract's bytecode bounds.
    error ReadOutOfBounds();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         WRITE LOGIC                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Writes `data` into the bytecode of a storage contract and returns its address.
    function write(bytes memory data) internal returns (address pointer) {
        /// @solidity memory-safe-assembly
        assembly {
            let originalDataLength := mload(data)

            // Add 1 to data size since we are prefixing it with a STOP opcode.
            let dataSize := add(originalDataLength, DATA_OFFSET)

            /**
             * ------------------------------------------------------------------------------+
             * Opcode      | Mnemonic        | Stack                   | Memory              |
             * ------------------------------------------------------------------------------|
             * 61 dataSize | PUSH2 dataSize  | dataSize                |                     |
             * 80          | DUP1            | dataSize dataSize       |                     |
             * 60 0xa      | PUSH1 0xa       | 0xa dataSize dataSize   |                     |
             * 3D          | RETURNDATASIZE  | 0 0xa dataSize dataSize |                     |
             * 39          | CODECOPY        | dataSize                | [0..dataSize): code |
             * 3D          | RETURNDATASIZE  | 0 dataSize              | [0..dataSize): code |
             * F3          | RETURN          |                         | [0..dataSize): code |
             * 00          | STOP            |                         |                     |
             * ------------------------------------------------------------------------------+
             * @dev Prefix the bytecode with a STOP opcode to ensure it cannot be called.
             * Also PUSH2 is used since max contract size cap is 24,576 bytes which is less than 2 ** 16.
             */
            mstore(
                // Do a out-of-gas revert if `dataSize` is more than 2 bytes.
                // The actual EVM limit may be smaller and may change over time.
                add(data, gt(dataSize, 0xffff)),
                // Left shift `dataSize` by 64 so that it lines up with the 0000 after PUSH2.
                or(0xfd61000080600a3d393df300, shl(0x40, dataSize))
            )

            // Deploy a new contract with the generated creation code.
            pointer := create(0, add(data, 0x15), add(dataSize, 0xa))

            // If `pointer` is zero, revert.
            if iszero(pointer) {
                // Store the function selector of `DeploymentFailed()`.
                mstore(0x00, 0x30116425)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            // Restore original length of the variable size `data`.
            mstore(data, originalDataLength)
        }
    }

    /// @dev Writes `data` into the bytecode of a storage contract with `salt`
    /// and returns its deterministic address.
    function writeDeterministic(bytes memory data, bytes32 salt)
        internal
        returns (address pointer)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let originalDataLength := mload(data)
            let dataSize := add(originalDataLength, DATA_OFFSET)

            mstore(
                // Do a out-of-gas revert if `dataSize` is more than 2 bytes.
                // The actual EVM limit may be smaller and may change over time.
                add(data, gt(dataSize, 0xffff)),
                // Left shift `dataSize` by 64 so that it lines up with the 0000 after PUSH2.
                or(0xfd61000080600a3d393df300, shl(0x40, dataSize))
            )

            // Deploy a new contract with the generated creation code.
            pointer := create2(0, add(data, 0x15), add(dataSize, 0xa), salt)

            // If `pointer` is zero, revert.
            if iszero(pointer) {
                // Store the function selector of `DeploymentFailed()`.
                mstore(0x00, 0x30116425)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            // Restore original length of the variable size `data`.
            mstore(data, originalDataLength)
        }
    }

    /// @dev Returns the initialization code hash of the storage contract for `data`.
    /// Used for mining vanity addresses with create2crunch.
    function initCodeHash(bytes memory data) internal pure returns (bytes32 hash) {
        /// @solidity memory-safe-assembly
        assembly {
            let originalDataLength := mload(data)
            let dataSize := add(originalDataLength, DATA_OFFSET)

            // Do a out-of-gas revert if `dataSize` is more than 2 bytes.
            // The actual EVM limit may be smaller and may change over time.
            returndatacopy(returndatasize(), returndatasize(), shr(16, dataSize))

            mstore(data, or(0x61000080600a3d393df300, shl(0x40, dataSize)))

            hash := keccak256(add(data, 0x15), add(dataSize, 0xa))

            // Restore original length of the variable size `data`.
            mstore(data, originalDataLength)
        }
    }

    /// @dev Returns the address of the storage contract for `data`
    /// deployed with `salt` by `deployer`.
    /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
    function predictDeterministicAddress(bytes memory data, bytes32 salt, address deployer)
        internal
        pure
        returns (address predicted)
    {
        bytes32 hash = initCodeHash(data);
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and store the bytecode hash.
            mstore8(0x00, 0xff) // Write the prefix.
            mstore(0x35, hash)
            mstore(0x01, shl(96, deployer))
            mstore(0x15, salt)
            predicted := keccak256(0x00, 0x55)
            // Restore the part of the free memory pointer that has been overwritten.
            mstore(0x35, 0)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         READ LOGIC                         */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns all the `data` from the bytecode of the storage contract at `pointer`.
    function read(address pointer) internal view returns (bytes memory data) {
        /// @solidity memory-safe-assembly
        assembly {
            let pointerCodesize := extcodesize(pointer)
            if iszero(pointerCodesize) {
                // Store the function selector of `InvalidPointer()`.
                mstore(0x00, 0x11052bb4)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Offset all indices by 1 to skip the STOP opcode.
            let size := sub(pointerCodesize, DATA_OFFSET)

            // Get the pointer to the free memory and allocate
            // enough 32-byte words for the data and the length of the data,
            // then copy the code to the allocated memory.
            // Masking with 0xffe0 will suffice, since contract size is less than 16 bits.
            data := mload(0x40)
            mstore(0x40, add(data, and(add(size, 0x3f), 0xffe0)))
            mstore(data, size)
            mstore(add(add(data, 0x20), size), 0) // Zeroize the last slot.
            extcodecopy(pointer, add(data, 0x20), DATA_OFFSET, size)
        }
    }

    /// @dev Returns the `data` from the bytecode of the storage contract at `pointer`,
    /// from the byte at `start`, to the end of the data stored.
    function read(address pointer, uint256 start) internal view returns (bytes memory data) {
        /// @solidity memory-safe-assembly
        assembly {
            let pointerCodesize := extcodesize(pointer)
            if iszero(pointerCodesize) {
                // Store the function selector of `InvalidPointer()`.
                mstore(0x00, 0x11052bb4)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            // If `!(pointer.code.size > start)`, reverts.
            // This also handles the case where `start + DATA_OFFSET` overflows.
            if iszero(gt(pointerCodesize, start)) {
                // Store the function selector of `ReadOutOfBounds()`.
                mstore(0x00, 0x84eb0dd1)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            let size := sub(pointerCodesize, add(start, DATA_OFFSET))

            // Get the pointer to the free memory and allocate
            // enough 32-byte words for the data and the length of the data,
            // then copy the code to the allocated memory.
            // Masking with 0xffe0 will suffice, since contract size is less than 16 bits.
            data := mload(0x40)
            mstore(0x40, add(data, and(add(size, 0x3f), 0xffe0)))
            mstore(data, size)
            mstore(add(add(data, 0x20), size), 0) // Zeroize the last slot.
            extcodecopy(pointer, add(data, 0x20), add(start, DATA_OFFSET), size)
        }
    }

    /// @dev Returns the `data` from the bytecode of the storage contract at `pointer`,
    /// from the byte at `start`, to the byte at `end` (exclusive) of the data stored.
    function read(address pointer, uint256 start, uint256 end)
        internal
        view
        returns (bytes memory data)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let pointerCodesize := extcodesize(pointer)
            if iszero(pointerCodesize) {
                // Store the function selector of `InvalidPointer()`.
                mstore(0x00, 0x11052bb4)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            // If `!(pointer.code.size > end) || (start > end)`, revert.
            // This also handles the cases where
            // `end + DATA_OFFSET` or `start + DATA_OFFSET` overflows.
            if iszero(
                and(
                    gt(pointerCodesize, end), // Within bounds.
                    iszero(gt(start, end)) // Valid range.
                )
            ) {
                // Store the function selector of `ReadOutOfBounds()`.
                mstore(0x00, 0x84eb0dd1)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            let size := sub(end, start)

            // Get the pointer to the free memory and allocate
            // enough 32-byte words for the data and the length of the data,
            // then copy the code to the allocated memory.
            // Masking with 0xffe0 will suffice, since contract size is less than 16 bits.
            data := mload(0x40)
            mstore(0x40, add(data, and(add(size, 0x3f), 0xffe0)))
            mstore(data, size)
            mstore(add(add(data, 0x20), size), 0) // Zeroize the last slot.
            extcodecopy(pointer, add(data, 0x20), add(start, DATA_OFFSET), size)
        }
    }
}
Settings
{
  "compilationTarget": {
    "src/net/Net.sol": "Net"
  },
  "evmVersion": "paris",
  "libraries": {},
  "metadata": {
    "appendCBOR": false,
    "bytecodeHash": "none"
  },
  "optimizer": {
    "enabled": true,
    "runs": 20
  },
  "remappings": [
    ":@erc721a/=lib/ERC721A/contracts/",
    ":@openzeppelin/=lib/openzeppelin-contracts/",
    ":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
    ":@prb/test/=lib/prb-test/src/",
    ":@solady/=lib/solady/src/",
    ":ERC721A/=lib/ERC721A/contracts/",
    ":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/",
    ":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
    ":forge-std/=lib/forge-std/src/",
    ":openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",
    ":prb-math/=lib/prb-math/src/",
    ":prb-test/=lib/prb-test/src/",
    ":solady/=lib/solady/src/",
    ":solmate/=lib/solady/lib/solmate/src/",
    ":src/=src/",
    ":utility-contracts/=lib/utility-contracts/src/"
  ]
}
ABI
[{"inputs":[],"name":"InvalidEndIndex","type":"error"},{"inputs":[],"name":"InvalidRange","type":"error"},{"inputs":[],"name":"InvalidStartIndex","type":"error"},{"inputs":[],"name":"MsgEmpty","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"string","name":"topic","type":"string"},{"indexed":false,"internalType":"uint256","name":"messageIndex","type":"uint256"}],"name":"MessageSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"app","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"string","name":"topic","type":"string"},{"indexed":false,"internalType":"uint256","name":"messageIndex","type":"uint256"}],"name":"MessageSentViaApp","type":"event"},{"inputs":[{"internalType":"bytes","name":"encodedMessage","type":"bytes"}],"name":"decodeMessage","outputs":[{"components":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"}],"internalType":"struct INet.Message","name":"","type":"tuple"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"idx","type":"uint256"}],"name":"decodeMessageAtIndex","outputs":[{"components":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"}],"internalType":"struct INet.Message","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"idx","type":"uint256"}],"name":"getMessage","outputs":[{"components":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"}],"internalType":"struct INet.Message","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"idx","type":"uint256"},{"internalType":"address","name":"app","type":"address"}],"name":"getMessageForApp","outputs":[{"components":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"}],"internalType":"struct INet.Message","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"idx","type":"uint256"},{"internalType":"address","name":"app","type":"address"},{"internalType":"string","name":"topic","type":"string"}],"name":"getMessageForAppTopic","outputs":[{"components":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"}],"internalType":"struct INet.Message","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"idx","type":"uint256"},{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"user","type":"address"}],"name":"getMessageForAppUser","outputs":[{"components":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"}],"internalType":"struct INet.Message","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"idx","type":"uint256"},{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"user","type":"address"},{"internalType":"string","name":"topic","type":"string"}],"name":"getMessageForAppUserTopic","outputs":[{"components":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"}],"internalType":"struct INet.Message","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"idx","type":"uint256"},{"internalType":"address","name":"app","type":"address"}],"name":"getMessageIdxForApp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"idx","type":"uint256"},{"internalType":"address","name":"app","type":"address"},{"internalType":"string","name":"topic","type":"string"}],"name":"getMessageIdxForAppTopic","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"idx","type":"uint256"},{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"user","type":"address"}],"name":"getMessageIdxForAppUser","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"idx","type":"uint256"},{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"user","type":"address"},{"internalType":"string","name":"topic","type":"string"}],"name":"getMessageIdxForAppUserTopic","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"startIdx","type":"uint256"},{"internalType":"uint256","name":"endIdx","type":"uint256"}],"name":"getMessagesInRange","outputs":[{"components":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"}],"internalType":"struct INet.Message[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"startIdx","type":"uint256"},{"internalType":"uint256","name":"endIdx","type":"uint256"},{"internalType":"address","name":"app","type":"address"}],"name":"getMessagesInRangeForApp","outputs":[{"components":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"}],"internalType":"struct INet.Message[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"startIdx","type":"uint256"},{"internalType":"uint256","name":"endIdx","type":"uint256"},{"internalType":"address","name":"app","type":"address"},{"internalType":"string","name":"topic","type":"string"}],"name":"getMessagesInRangeForAppTopic","outputs":[{"components":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"}],"internalType":"struct INet.Message[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"startIdx","type":"uint256"},{"internalType":"uint256","name":"endIdx","type":"uint256"},{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"user","type":"address"}],"name":"getMessagesInRangeForAppUser","outputs":[{"components":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"}],"internalType":"struct INet.Message[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"startIdx","type":"uint256"},{"internalType":"uint256","name":"endIdx","type":"uint256"},{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"user","type":"address"},{"internalType":"string","name":"topic","type":"string"}],"name":"getMessagesInRangeForAppUserTopic","outputs":[{"components":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"}],"internalType":"struct INet.Message[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"startIdx","type":"uint256"},{"internalType":"uint256","name":"endIdx","type":"uint256"},{"internalType":"bytes32","name":"hashVal","type":"bytes32"}],"name":"getMessagesInRangeForHash","outputs":[{"components":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"}],"internalType":"struct INet.Message[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalMessagesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"app","type":"address"}],"name":"getTotalMessagesForAppCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"app","type":"address"},{"internalType":"string","name":"topic","type":"string"}],"name":"getTotalMessagesForAppTopicCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"user","type":"address"}],"name":"getTotalMessagesForAppUserCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"app","type":"address"},{"internalType":"address","name":"user","type":"address"},{"internalType":"string","name":"topic","type":"string"}],"name":"getTotalMessagesForAppUserTopicCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hashVal","type":"bytes32"}],"name":"getTotalMessagesForHashCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"hashVal","type":"bytes32"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"hashToMessageIndexes","outputs":[{"internalType":"uint256","name":"messageIndexes","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"messagePointers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"sendMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"string","name":"text","type":"string"},{"internalType":"string","name":"topic","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"sendMessageViaApp","outputs":[],"stateMutability":"nonpayable","type":"function"}]