HyperlaneHookReceiver

Description:

Multi-token contract following ERC1155 standard, supporting both fungible and non-fungible tokens.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/hooks/HyperlaneHookReceiver.sol": {
      "content": "// ABOUTME: This contract receives cross-chain messages from HyperlaneHook via the Hyperlane protocol.
// ABOUTME: It transfers ERC1155 NFTs to recipients based on cross-chain key purchase notifications.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

import {IMessageRecipient} from "../interfaces/hooks/hyperlane/IMessageRecipient.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";

/// @title HyperlaneHookReceiver
/// @notice Receives cross-chain key purchase notifications and transfers ERC1155 NFTs to recipients
/// @dev Implements IMessageRecipient to receive messages via Hyperlane Mailbox
contract HyperlaneHookReceiver is IMessageRecipient {
  /// @notice Emitted when a cross-chain key purchase notification is received
  /// @param origin The domain ID of the origin chain
  /// @param sender The address of the HyperlaneHook contract on the origin chain
  /// @param lockAddress The address of the lock on the origin chain
  /// @param tokenId The ID of the purchased key
  /// @param recipient The address that received the key
  event KeyPurchaseReceived(
    uint32 indexed origin,
    bytes32 indexed sender,
    address indexed lockAddress,
    uint256 tokenId,
    address recipient
  );

  /// @notice Thrown when the message sender is not the Mailbox
  error NOT_MAILBOX();

  /// @notice Thrown when the message origin or sender is not trusted
  error UNTRUSTED_SENDER(
    uint32 origin,
    uint32 expectedOrigin,
    bytes32 sender,
    bytes32 expectedSender
  );

  /// @notice The Hyperlane Mailbox contract address
  address public immutable MAILBOX;

  /// @notice The trusted origin domain ID
  uint32 public immutable ORIGIN_HOOK_DOMAIN;

  /// @notice The trusted HyperlaneHook contract address on the origin chain
  bytes32 public immutable ORIGIN_HOOK_ADDRESS;

  /// @notice The ERC1155 NFT contract address
  address public immutable NFT_CONTRACT;

  /// @notice The token ID of the ERC1155 NFT to transfer
  uint256 public immutable NFT_TOKEN_ID;

  /// @notice Initializes the receiver with the Mailbox address, trusted sender, and NFT contract
  /// @param _mailbox The address of the Hyperlane Mailbox contract on this chain
  /// @param originHookDomain The domain ID of the trusted origin chain
  /// @param originHookAddress The HyperlaneHook contract address on the origin chain (as bytes32)
  /// @param _nftContract The address of the ERC1155 NFT contract
  /// @param _nftTokenId The token ID of the ERC1155 NFT to transfer
  constructor(
    address _mailbox,
    uint32 originHookDomain,
    bytes32 originHookAddress,
    address _nftContract,
    uint256 _nftTokenId
  ) {
    MAILBOX = _mailbox;
    ORIGIN_HOOK_DOMAIN = originHookDomain;
    ORIGIN_HOOK_ADDRESS = originHookAddress;
    NFT_CONTRACT = _nftContract;
    NFT_TOKEN_ID = _nftTokenId;
  }

  /// @notice Restricts function to only be callable by the Mailbox
  modifier onlyMailbox() {
    if (msg.sender != MAILBOX) {
      revert NOT_MAILBOX();
    }
    _;
  }

  /// @notice Handles an incoming message from the Hyperlane Mailbox
  /// @dev Only callable by the Mailbox, and only processes messages from the trusted sender
  /// @param _origin Domain ID of the origin chain
  /// @param _sender Address of the sender on the origin chain (HyperlaneHook contract)
  /// @param _message Encoded message containing (lockAddress, tokenId, recipient)
  function handle(
    uint32 _origin,
    bytes32 _sender,
    bytes calldata _message
  ) external payable override onlyMailbox {
    // Verify the sender is trusted
    if (_origin != ORIGIN_HOOK_DOMAIN || _sender != ORIGIN_HOOK_ADDRESS) {
      revert UNTRUSTED_SENDER(
        _origin,
        ORIGIN_HOOK_DOMAIN,
        _sender,
        ORIGIN_HOOK_ADDRESS
      );
    }

    // Decode the message: (lockAddress, tokenId, recipient)
    (address lockAddress, uint256 tokenId, address recipient) = abi.decode(
      _message,
      (address, uint256, address)
    );

    // Emit event for tracking
    emit KeyPurchaseReceived(_origin, _sender, lockAddress, tokenId, recipient);

    // Custom logic can be added here to handle the key purchase notification
    // For example:
    // - Mint tokens
    // - Update state
    // - Call other contracts
    _handleKeyPurchase(_origin, _sender, lockAddress, tokenId, recipient);
  }

  /// @notice Internal function to handle key purchase logic by transferring an ERC1155 NFT
  /// @dev Transfers 1 NFT of token ID 2 to the recipient. The ERC1155 contract emits TransferSingle event and reverts if insufficient balance.
  /// @param _origin Domain ID of the origin chain
  /// @param _sender Address of the HyperlaneHook contract on the origin chain
  /// @param lockAddress The address of the lock on the origin chain
  /// @param tokenId The ID of the purchased key
  /// @param recipient The address that received the key
  function _handleKeyPurchase(
    uint32 _origin,
    bytes32 _sender,
    address lockAddress,
    uint256 tokenId,
    address recipient
  ) internal virtual {
    // Transfer 1 NFT to the recipient (will revert if insufficient balance)
    IERC1155(NFT_CONTRACT).safeTransferFrom(
      address(this),
      recipient,
      NFT_TOKEN_ID,
      1,
      ""
    );
  }

  /// @notice Required by ERC1155 to receive tokens
  /// @dev Accepts all ERC1155 token transfers
  function onERC1155Received(
    address,
    address,
    uint256,
    uint256,
    bytes calldata
  ) external pure returns (bytes4) {
    return this.onERC1155Received.selector;
  }

  /// @notice Required by ERC1155 to receive batch transfers
  /// @dev Accepts all ERC1155 batch token transfers
  function onERC1155BatchReceived(
    address,
    address,
    uint256[] calldata,
    uint256[] calldata,
    bytes calldata
  ) external pure returns (bytes4) {
    return this.onERC1155BatchReceived.selector;
  }
}
"
    },
    "contracts/interfaces/hooks/hyperlane/IMessageRecipient.sol": {
      "content": "// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.9;

/// @title IMessageRecipient
/// @notice Interface for contracts that receive interchain messages from Hyperlane
/// @dev Implement this interface to receive messages via the Hyperlane Mailbox
interface IMessageRecipient {
  /// @notice Handle an interchain message
  /// @param _origin Domain ID of the origin chain
  /// @param _sender Address of the sender on the origin chain as bytes32
  /// @param _message Raw bytes content of the message body
  function handle(
    uint32 _origin,
    bytes32 _sender,
    bytes calldata _message
  ) external payable;
}
"
    },
    "@openzeppelin/contracts/token/ERC1155/IERC1155.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (token/ERC1155/IERC1155.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the value of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] calldata accounts,
        uint256[] calldata ids
    ) external view returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`.
     *
     * WARNING: This function can potentially allow a reentrancy attack when transferring tokens
     * to an untrusted contract, when invoking {onERC1155Received} on the receiver.
     * Ensure to follow the checks-effects-interactions pattern and consider employing
     * reentrancy guards when interacting with untrusted contracts.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `value` amount.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * WARNING: This function can potentially allow a reentrancy attack when transferring tokens
     * to an untrusted contract, when invoking {onERC1155BatchReceived} on the receiver.
     * Ensure to follow the checks-effects-interactions pattern and consider employing
     * reentrancy guards when interacting with untrusted contracts.
     *
     * Emits either a {TransferSingle} or a {TransferBatch} event, depending on the length of the array arguments.
     *
     * Requirements:
     *
     * - `ids` and `values` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external;
}
"
    },
    "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/IERC1155Receiver.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Interface that must be implemented by smart contracts in order to receive
 * ERC-1155 token transfers.
 */
interface IERC1155Receiver is IERC165 {
    /**
     * @dev Handles the receipt of a single ERC1155 token type. This function is
     * called at the end of a `safeTransferFrom` after the balance has been updated.
     *
     * NOTE: To accept the transfer, this must return
     * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
     * (i.e. 0xf23a6e61, or its own function selector).
     *
     * @param operator The address which initiated the transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param id The ID of the token being transferred
     * @param value The amount of tokens being transferred
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
     */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);

    /**
     * @dev Handles the receipt of a multiple ERC1155 token types. This function
     * is called at the end of a `safeBatchTransferFrom` after the balances have
     * been updated.
     *
     * NOTE: To accept the transfer(s), this must return
     * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
     * (i.e. 0xbc197c81, or its own function selector).
     *
     * @param operator The address which initiated the batch transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param ids An array containing ids of each token being transferred (order and length must match values array)
     * @param values An array containing amounts of each token being transferred (order and length must match ids array)
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
     */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4);
}
"
    },
    "@openzeppelin/contracts/utils/introspection/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 80
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "abi"
        ]
      }
    },
    "evmVersion": "shanghai"
  }
}}

Tags:
ERC1155, ERC165, NFT, Non-Fungible, Factory|addr:0x5e8b3152c34b79ac3e191c092711f332d156f408|verified:true|block:23578730|tx:0x7d73f33db17787613e11dafb9c8f95184b2a6a84cdb84c4bd04638929e20d35f|first_check:1760514442

Submitted on: 2025-10-15 09:47:23

Comments

Log in to comment.

No comments yet.