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"
}
}}
Submitted on: 2025-10-15 09:47:23
Comments
Log in to comment.
No comments yet.