mail@pastecode.io avatar
19 days ago
12 kB
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;

import { OApp, Origin, MessagingFee } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol";
import { OAppOptionsType3 } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OAppOptionsType3.sol";
import { IOAppMsgInspector } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppMsgInspector.sol";
import { OAppPreCrimeSimulator } from "@layerzerolabs/lz-evm-oapp-v2/contracts/precrime/OAppPreCrimeSimulator.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { ERC721URIStorage } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import { OFTComposeMsgCodec } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/libs/OFTComposeMsgCodec.sol";
import { MessagingReceipt, MessagingFee } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/interfaces/IOFT.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

 * @dev Struct representing OFT limit information.
 * @dev These amounts can change dynamically and are up the the specific oft implementation.
struct OFTLimit {
    uint256 tokenID;

 * @dev Struct representing OFT receipt information.
struct OFTReceipt {
    uint256 tokenID;

 * @dev Struct representing OFT fee details.
 * @dev Future proof mechanism to provide a standardized way to communicate fees to things like a UI.
struct OFTFeeDetail {
    int256 feeAmountLD; // Amount of the fee in local decimals.
    string description; // Description of the fee.

struct MessagingParams {
    uint32 dstEid;
    bytes32 receiver;
    bytes message;
    bytes options;
    bool payInLzToken;

 * @dev Struct representing token parameters for the OFT send() operation.
struct SendParam {
    uint32 dstEid; // Destination endpoint ID.
    bytes32 to; // Recipient address.
    uint32 tokenID; // Token ID to transfer..
    string tokenURI; // Token URI.
    bytes extraOptions;

contract OFT721 is OApp, ERC721, ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdTracker;

    string private _baseTokenURI;
    uint256 private mintRate;

    event TokenMinted(address recipient, uint256 tokenId);

    event testAction(string action);

    // Events
    event OFTSent(
        bytes32 indexed guid, // GUID of the OFT message.
        uint32 dstEid, // Destination Endpoint ID.
        address indexed fromAddress, // Address of the sender on the src chain.
        uint256 tokenID
    event OFTReceived(
    bytes32 indexed guid, // GUID of the OFT message.
    uint32 srcEid, // Source Endpoint ID.
    address indexed toAddress, // Address of the recipient on the dst chain.
    uint256 tokenID

        address _endpoint, 
        address _owner, 
        string memory _name, 
        string memory _symbol, 
        uint256 _initialMintRate, 
        string memory baseTokenURI,
        uint256 _tokenIDStart
    ) OApp(_endpoint, _owner) ERC721(_name, _symbol) Ownable(_owner) {
        _baseTokenURI = baseTokenURI;
        mintRate = _initialMintRate;
        _tokenIdTracker._value = _tokenIDStart;

     * @dev Called when data is received from the protocol. It overrides the equivalent function in the parent contract.
     * Protocol messages are defined as packets, comprised of the following parameters.
     * @param _origin A struct containing information about where the packet came from.
     * @param _guid A global unique identifier for tracking the packet.
     * @param payload Encoded message.
    // Your function implementation
    function _lzReceive(
        Origin calldata _origin,
        bytes32 _guid,
        bytes calldata payload,
        address,  // Executor address as specified by the OApp.
        bytes calldata  // Any extra data or options to trigger on receipt.
    ) internal override {
        // Declare inFlightToken and decode the payload
        SendParam memory inFlightToken = abi.decode(payload, (SendParam));
        // Use inFlightToken for the _credit function
        if (!_credit(bytes32ToAddress(inFlightToken.to), inFlightToken.tokenID, inFlightToken.tokenURI)) {
            revert("Failed to mint token on destination chain");

     * @dev Check if the peer is considered 'trusted' by the OApp.
     * @param _eid The endpoint ID to check.
     * @param _peer The peer to check.
     * @return Whether the peer passed is considered 'trusted' by the OApp.
     * @dev Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source.
    function isPeer(uint32 _eid, bytes32 _peer) public view virtual returns (bool) {
        return peers[_eid] == _peer;

    function _debit(
        address _from,
        uint256 _tokenID,
        uint32 _dstEid
    ) internal virtual returns (bool success) {
        // @dev Burn the token on the source chain.
        if (ownerOf(_tokenID) != _from) {
            revert("Transfer of token that is not own");
        return true;

    function _credit(
        address _to,
        uint256 _tokenID,
        string memory _tokenURI
    ) internal virtual returns (bool success) {
        // @dev Mint the token on the destination chain.
        _mint(_to, _tokenID);
        _setTokenURI(_tokenID, _tokenURI);
        return true;

     * @dev Executes the send operation.
     * @param _sendParam The parameters for the send operation.
     * @param _fee The calculated fee for the send() operation.
     *      - nativeFee: The native fee.
     *      - lzTokenFee: The lzToken fee.
     * @param _refundAddress The address to receive any excess funds.
     * @return msgReceipt The receipt for the send operation.
     * @return oftReceipt The OFT receipt information.
     * @dev MessagingReceipt: LayerZero msg receipt
     *  - guid: The unique identifier for the sent message.
     *  - nonce: The nonce of the sent message.
     *  - fee: The LayerZero fee incurred for the message.
    function send(
        SendParam calldata _sendParam,
        MessagingFee calldata _fee,
        address _refundAddress
    ) external payable virtual returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) {

        if (!_debit(msg.sender, _sendParam.tokenID, _sendParam.dstEid)) {
            revert("Transfer of token that is not own or failed to burn token.");

        (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam);
        require(message.length > 0, "Failed to build message.");

        msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress);
        require(msgReceipt.guid != bytes32(0), "Failed to send message to LayerZero endpoint.");

        oftReceipt = OFTReceipt(_sendParam.tokenID);

        emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, _sendParam.tokenID);

    /* @dev Quotes the gas needed to pay for the full omnichain transaction.
    * @return nativeFee Estimated gas fee in native gas.
    * @return lzTokenFee Estimated gas fee in ZRO token.
    function quote(
        uint32 _dstEid, // Destination chain's endpoint ID.
        SendParam memory _message, // The message to send.
        bytes calldata _options, // Message execution options
        bool _payInLzToken // boolean for which token to return fee in
    ) public view returns (uint256 nativeFee, uint256 lzTokenFee) {
        bytes memory _payload = abi.encode(_message);
        MessagingFee memory fee = _quote(_dstEid, _payload, _options, _payInLzToken);
        return (fee.nativeFee, fee.lzTokenFee);

     * @dev Encodes an OFT LayerZero message.
     * @param _sendTo The recipient address.
     * @param _tokenID The token ID.
     * @param _tokenURI The token URI..
     * @return _msg The encoded message.
    function encode(
        bytes32 _sendTo,
        uint64 _tokenID,
        string memory _tokenURI
    ) internal view returns (bytes memory _msg) {
        _msg = abi.encodePacked(_sendTo, _tokenID, _tokenURI);

     * @dev Converts an address to bytes32.
     * @param _addr The address to convert.
     * @return The bytes32 representation of the address.
    function addressToBytes32(address _addr) internal pure returns (bytes32) {
        return bytes32(uint256(uint160(_addr)));

     * @dev Converts bytes32 to an address.
     * @param _b The bytes32 value to convert.
     * @return The address representation of bytes32.
    function bytes32ToAddress(bytes32 _b) internal pure returns (address) {
        return address(uint160(uint256(_b)));

     * @dev Internal function to build the message and options.
     * @param _sendParam The parameters for the send() operation.
     * @return message The encoded message.
     * @return options The encoded options.
    function _buildMsgAndOptions(
        SendParam calldata _sendParam
    ) internal view virtual returns (bytes memory message, bytes memory options) {
        bool hasCompose;
        // @dev This generated message has the msg.sender encoded into the payload so the remote knows who the caller is.
        message = encode(
        // @dev Change the msg type depending if its composed or not.
        // uint16 msgType = hasCompose ? SEND_AND_CALL : SEND;
        // @dev Combine the callers _extraOptions with the enforced options via the OAppOptionsType3.
        options = _sendParam.extraOptions; // combineOptions(_sendParam.dstEid, msgType, _sendParam.extraOptions);

        // @dev Optionally inspect the message and options depending if the OApp owner has set a msg inspector.
        // @dev If it fails inspection, needs to revert in the implementation. ie. does not rely on return boolean
        // if (msgInspector != address(0)) IOAppMsgInspector(msgInspector).inspect(message, options);

    function tokenURI(uint256 tokenId) public view virtual override(ERC721, ERC721URIStorage) returns (string memory) {
        return super.tokenURI(tokenId);
     * @dev Returns the base URI set for the tokens.
    function _baseURI() internal view virtual override returns (string memory) {
        return _baseTokenURI;

     * @dev Public mint function to mint new tokens with metadata.
     * @param recipient The address to mint the token to.
     * @param metadataURI The metadata URI to associate with the token.
    function mint(address recipient, string memory metadataURI) public virtual payable {
        require(msg.value >= mintRate, "Insufficient mint fee");
        uint256 tokenId = _tokenIdTracker.current();
        _mint(recipient, tokenId);
        _setTokenURI(tokenId, metadataURI);
        emit TokenMinted(recipient, tokenId);

     * @dev Sets the mint rate for new tokens.
     * @param _mintRate The new mint rate.
    function setRate(uint256 _mintRate) external onlyOwner {
        mintRate = _mintRate;

     * @dev Withdraws the balance of the contract to the owner's address.
    function withdraw() external onlyOwner {
        uint256 amount = address(this).balance;
        require(amount > 0, "No funds to withdraw");

     * @dev See {ERC721-supportsInterface}.
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC721URIStorage) returns (bool) {
        return super.supportsInterface(interfaceId);

Leave a Comment