From 109e22eb69bea0db426556c2e72d47d4b3f3de55 Mon Sep 17 00:00:00 2001
From: adaki2004 <keszeydani@gmail.com>
Date: Thu, 6 Jul 2023 16:57:51 +0200
Subject: [PATCH] Adjust current bridge api to an nft bridge version

---
 .../contracts/nft_bridge/Erc721Bridge.sol     | 284 ++++++++++++++++++
 .../contracts/nft_bridge/Erc721Vault.sol      | 111 +++++++
 .../contracts/nft_bridge/NftBridgeErrors.sol  |  22 ++
 .../nft_bridge/erc721/IErc721Bridge.sol       | 105 +++++++
 .../erc721/libs/LibErc721BridgeData.sol       |  58 ++++
 .../erc721/libs/LibErc721BridgeProcess.sol    | 102 +++++++
 .../erc721/libs/LibErc721BridgeSend.sol       | 154 ++++++++++
 .../erc721/libs/LibErc721BridgeStatus.sol     | 146 +++++++++
 8 files changed, 982 insertions(+)
 create mode 100644 packages/protocol/contracts/nft_bridge/Erc721Bridge.sol
 create mode 100644 packages/protocol/contracts/nft_bridge/Erc721Vault.sol
 create mode 100644 packages/protocol/contracts/nft_bridge/NftBridgeErrors.sol
 create mode 100644 packages/protocol/contracts/nft_bridge/erc721/IErc721Bridge.sol
 create mode 100644 packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeData.sol
 create mode 100644 packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeProcess.sol
 create mode 100644 packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeSend.sol
 create mode 100644 packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeStatus.sol

diff --git a/packages/protocol/contracts/nft_bridge/Erc721Bridge.sol b/packages/protocol/contracts/nft_bridge/Erc721Bridge.sol
new file mode 100644
index 00000000000..1a163b37a75
--- /dev/null
+++ b/packages/protocol/contracts/nft_bridge/Erc721Bridge.sol
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: MIT
+//  _____     _ _         _         _
+// |_   _|_ _(_) |_____  | |   __ _| |__ ___
+//   | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
+//   |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
+
+pragma solidity ^0.8.20;
+
+import { AddressResolver } from "../common/AddressResolver.sol";
+import { EssentialContract } from "../common/EssentialContract.sol";
+import { Proxied } from "../common/Proxied.sol";
+import { IErc721Bridge } from "./erc721/IErc721Bridge.sol";
+import { NftBridgeErrors } from "./NftBridgeErrors.sol";
+import { LibErc721BridgeData } from "./erc721/libs/LibErc721BridgeData.sol";
+// import { LibBridgeProcess } from "./libs/LibBridgeProcess.sol";
+// import { LibBridgeRelease } from "./libs/LibBridgeRelease.sol";
+// import { LibBridgeRetry } from "./libs/LibBridgeRetry.sol";
+import { LibErc721BridgeSend } from "./erc721/libs/LibErc721BridgeSend.sol";
+import { LibErc721BridgeStatus } from "./erc721/libs/LibErc721BridgeStatus.sol";
+
+/**
+ * This contract is an ERC-721 token bridge contract which is deployed on both L1 and L2.
+ * which calls the library implementations. See _IErc721Bridge_ for more details.
+ * @dev The code hash for the same address on L1 and L2 may be different.
+ * @custom:security-contact hello@taiko.xyz
+ */
+contract Erc721Bridge is EssentialContract, IErc721Bridge, NftBridgeErrors {
+    using LibErc721BridgeData for Message;
+
+    LibErc721BridgeData.State private _state; // 50 slots reserved
+
+    event MessageStatusChanged(
+        bytes32 indexed msgHash,
+        LibErc721BridgeStatus.MessageStatus status,
+        address transactor
+    );
+
+    event DestChainEnabled(uint256 indexed chainId, bool enabled);
+
+    /**
+     * Initializer to be called after being deployed behind a proxy.
+     * @dev Initializer function to setup the EssentialContract.
+     * @param _addressManager The address of the AddressManager contract.
+     */
+    function init(address _addressManager) external initializer {
+        EssentialContract._init(_addressManager);
+    }
+
+    /**
+     * Sends a message from the current chain to the destination chain specified
+     * in the message.
+     * @dev Sends a message by calling the LibErc721BridgeSend.sendMessageErc721 library
+     * function.
+     * @param message The message to send. (See IBridge)
+     * @return msgHash The hash of the message that was sent.
+     */
+    function sendMessageErc721(Message calldata message)
+        external
+        payable
+        nonReentrant
+        returns (bytes32 msgHash)
+    {
+        return LibErc721BridgeSend.sendMessageErc721({
+            state: _state,
+            resolver: AddressResolver(this),
+            message: message
+        });
+    }
+
+    // TODO: Implement them gradually
+    /**
+     * Releases the Ether locked in the bridge as part of a cross-chain
+     * transfer.
+     * @dev Releases the Ether by calling the LibBridgeRelease.releaseEther
+     * library function.
+     * @param message The message containing the details of the Ether transfer.
+     * (See IBridge)
+     * @param proof The proof of the cross-chain transfer.
+     */
+    function releaseTokenErc721(
+        IErc721Bridge.Message calldata message,
+        bytes calldata proof
+    )
+        external
+        nonReentrant
+    {
+        return;
+        // TODO: Implement
+        // return LibBridgeRelease.releaseEther({
+        //     state: _state,
+        //     resolver: AddressResolver(this),
+        //     message: message,
+        //     proof: proof
+        // });
+    }
+
+    // /**
+    //  * Processes a message received from another chain.
+    //  * @dev Processes the message by calling the LibBridgeProcess.processMessage
+    //  * library function.
+    //  * @param message The message to process.
+    //  * @param proof The proof of the cross-chain transfer.
+    //  */
+    // function processMessage(
+    //     Message calldata message,
+    //     bytes calldata proof
+    // )
+    //     external
+    //     nonReentrant
+    // {
+    //     return LibBridgeProcess.processMessage({
+    //         state: _state,
+    //         resolver: AddressResolver(this),
+    //         message: message,
+    //         proof: proof
+    //     });
+    // }
+
+    // /**
+    //  * Retries sending a message that previously failed to send.
+    //  * @dev Retries the message by calling the LibBridgeRetry.retryMessage
+    //  * library function.
+    //  * @param message The message to retry.
+    //  * @param isLastAttempt Specifies whether this is the last attempt to send
+    //  * the message.
+    //  */
+    // function retryMessage(
+    //     Message calldata message,
+    //     bool isLastAttempt
+    // )
+    //     external
+    //     nonReentrant
+    // {
+    //     return LibBridgeRetry.retryMessage({
+    //         state: _state,
+    //         resolver: AddressResolver(this),
+    //         message: message,
+    //         isLastAttempt: isLastAttempt
+    //     });
+    // }
+
+    /**
+     * Check if the message with the given hash has been sent.
+     * @param msgHash The hash of the message.
+     * @return Returns true if the message has been sent, false otherwise.
+     */
+    function isMessageSentErc721(bytes32 msgHash)
+        public
+        view
+        virtual
+        returns (bool)
+    {
+        return LibErc721BridgeSend.isMessageSentErc721(AddressResolver(this), msgHash);
+    }
+
+    /**
+     * Check if the message with the given hash has been received.
+     * @param msgHash The hash of the message.
+     * @param srcChainId The source chain ID.
+     * @param proof The proof of message receipt.
+     * @return Returns true if the message has been received, false otherwise.
+     */
+    function isMessageReceivedErc721(
+        bytes32 msgHash,
+        uint256 srcChainId,
+        bytes calldata proof
+    )
+        public
+        view
+        virtual
+        override
+        returns (bool)
+    {
+        return LibErc721BridgeSend.isMessageReceivedErc721({
+            resolver: AddressResolver(this),
+            msgHash: msgHash,
+            srcChainId: srcChainId,
+            proof: proof
+        });
+    }
+
+    /**
+     * Check if the message with the given hash has failed.
+     * @param msgHash The hash of the message.
+     * @param destChainId The destination chain ID.
+     * @param proof The proof of message failure.
+     * @return Returns true if the message has failed, false otherwise.
+     */
+    function isMessageFailedErc721(
+        bytes32 msgHash,
+        uint256 destChainId,
+        bytes calldata proof
+    )
+        public
+        view
+        virtual
+        override
+        returns (bool)
+    {
+        return LibErc721BridgeStatus.isMessageFailedErc721({
+            resolver: AddressResolver(this),
+            msgHash: msgHash,
+            destChainId: destChainId,
+            proof: proof
+        });
+    }
+
+    /**
+     * Get the status of the message with the given hash.
+     * @param msgHash The hash of the message.
+     * @return Returns the status of the message.
+     */
+    function getMessageStatusErc721(bytes32 msgHash)
+        public
+        view
+        virtual
+        returns (LibErc721BridgeStatus.MessageStatus)
+    {
+        return LibErc721BridgeStatus.getMessageStatusErc721(msgHash);
+    }
+
+    /**
+     * Get the current context
+     * @return Returns the current context.
+     */
+    function context() public view returns (Context memory) {
+        return _state.ctx;
+    }
+
+    // /**
+    //  * Check if the Ether associated with the given message hash has been
+    //  * released.
+    //  * @param msgHash The hash of the message.
+    //  * @return Returns true if the Ether has been released, false otherwise.
+    //  */
+    // function isEtherReleased(bytes32 msgHash) public view returns (bool) {
+    //     return _state.etherReleased[msgHash];
+    // }
+
+    /**
+     * Check if the destination chain with the given ID is enabled.
+     * @param _chainId The ID of the chain.
+     * @return enabled Returns true if the destination chain is enabled, false
+     * otherwise.
+     */
+
+    function isDestChainEnabled(uint256 _chainId)
+        public
+        view
+        returns (bool enabled)
+    {
+        (enabled,) =
+            LibErc721BridgeSend.isDestChainEnabled(AddressResolver(this), _chainId);
+    }
+
+    /**
+     * Compute the hash of a given message.
+     * @param message The message to compute the hash for.
+     * @return Returns the hash of the message.
+     */
+    function hashMessage(Message calldata message)
+        public
+        pure
+        override
+        returns (bytes32)
+    {
+        return LibErc721BridgeData.hashMessage(message);
+    }
+
+    /**
+     * Get the slot associated with a given message hash status.
+     * @param msgHash The hash of the message.
+     * @return Returns the slot associated with the given message hash status.
+     */
+    function getMessageStatusSlot(bytes32 msgHash)
+        public
+        pure
+        returns (bytes32)
+    {
+        return LibErc721BridgeStatus.getMessageStatusSlot(msgHash);
+    }
+}
+
+contract ProxiedErc721Bridge is Proxied, Erc721Bridge { }
diff --git a/packages/protocol/contracts/nft_bridge/Erc721Vault.sol b/packages/protocol/contracts/nft_bridge/Erc721Vault.sol
new file mode 100644
index 00000000000..2010b5bce2e
--- /dev/null
+++ b/packages/protocol/contracts/nft_bridge/Erc721Vault.sol
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: MIT
+//  _____     _ _         _         _
+// |_   _|_ _(_) |_____  | |   __ _| |__ ___
+//   | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
+//   |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
+
+pragma solidity ^0.8.20;
+
+import { IERC721ReceiverUpgradeable } from
+    "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol";
+import { IERC721Upgradeable} from
+    "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
+import { Create2Upgradeable } from
+    "@openzeppelin/contracts-upgradeable/utils/Create2Upgradeable.sol";
+import { EssentialContract } from "../common/EssentialContract.sol";
+import { Proxied } from "../common/Proxied.sol";
+import { LibAddress } from "../libs/LibAddress.sol";
+import { NftBridgeErrors } from "./NftBridgeErrors.sol";
+
+/**
+ * This contract is for vaulting (and releasing) ERC721 tokens
+ * @dev Only the contract owner can authorize or deauthorize addresses.
+ * @custom:security-contact hello@taiko.xyz
+ */
+contract Erc721Vault is EssentialContract, NftBridgeErrors, IERC721ReceiverUpgradeable {
+    using LibAddress for address;
+
+    // Maps contract to a contract on a different chain
+    struct ContractMapping {
+        address sisterContract;
+        string tokenName;
+        string tokenSymbol;
+        mapping (uint256 tokenId => bool inVault) tokenInVault;
+    }
+    mapping(address tokenContract => ContractMapping wrappedTokenContract) originalToWrapperData;
+
+    mapping(address addr => bool isAuthorized) private _authorizedAddrs;
+    uint256[49] private __gap;
+
+    event Authorized(address indexed addr, bool authorized);
+    event TokensReleased(address indexed to, address contractAddress, uint256[] tokenIds);
+
+    modifier onlyAuthorized() {
+        if (!isAuthorized(msg.sender)) {
+            revert ERC721_TV_NOT_AUTHORIZED();
+        }
+        _;
+    }
+
+    /**
+     * Initialize the contract with an address manager
+     * @param addressManager The address of the address manager
+     */
+    function init(address addressManager) external initializer {
+        EssentialContract._init(addressManager);
+    }
+
+    /**
+     * Transfer token(s) from Erc721Vault to a designated address, checking that the
+     * sender is authorized.
+     * @param recipient Address to receive Ether.
+     * @param tokenContract Token contract.
+     * @param tokenIds Array of tokenIds to be sent
+     */
+    function releaseTokens(
+        address recipient,
+        address tokenContract,
+        uint256[] memory tokenIds
+    )
+        public
+        onlyAuthorized
+        nonReentrant
+    {
+        if (recipient == address(0)) {
+            revert ERC721_TV_DO_NOT_BURN();
+        }
+
+        for (uint256 i; i < tokenIds.length; i++) {
+            IERC721Upgradeable(tokenContract).safeTransferFrom(address(this), recipient, tokenIds[i]);
+        }
+
+        emit TokensReleased(recipient, tokenContract, tokenIds);
+    }
+
+    /**
+     * Set the authorized status of an address, only the owner can call this.
+     * @param addr Address to set the authorized status of.
+     * @param authorized Authorized status to set.
+     */
+    function authorize(address addr, bool authorized) public onlyOwner {
+        if (addr == address(0) || _authorizedAddrs[addr] == authorized) {
+            revert ERC721_B_TV_PARAM();
+        }
+        _authorizedAddrs[addr] = authorized;
+        emit Authorized(addr, authorized);
+    }
+
+    /**
+     * Get the authorized status of an address.
+     * @param addr Address to get the authorized status of.
+     */
+    function isAuthorized(address addr) public view returns (bool) {
+        return _authorizedAddrs[addr];
+    }
+
+    function onERC721Received(address, address, uint256, bytes calldata ) external pure returns (bytes4) {
+        return IERC721ReceiverUpgradeable.onERC721Received.selector;
+    }
+}
+
+contract ProxiedErc721Vault is Proxied, Erc721Vault { }
diff --git a/packages/protocol/contracts/nft_bridge/NftBridgeErrors.sol b/packages/protocol/contracts/nft_bridge/NftBridgeErrors.sol
new file mode 100644
index 00000000000..3978531db85
--- /dev/null
+++ b/packages/protocol/contracts/nft_bridge/NftBridgeErrors.sol
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: MIT
+//  _____     _ _         _         _
+// |_   _|_ _(_) |_____  | |   __ _| |__ ___
+//   | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
+//   |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
+
+pragma solidity ^0.8.20;
+
+// Can be a common error contract for 1155 and 721
+abstract contract NftBridgeErrors {
+    // TokenVault
+    error ERC721_TV_NOT_AUTHORIZED();
+    error ERC721_TV_DO_NOT_BURN();
+    error ERC721_B_TV_PARAM();
+    // Bridge
+    error ERC721_B_FORBIDDEN();
+    error ERC721_B_WRONG_CHAIN_ID();
+    error ERC721_B_STATUS_MISMATCH();
+    error ERC721_B_SIGNAL_NOT_RECEIVED();
+    error ERC721_B_OWNER_IS_NULL();
+    error ERC721_B_WRONG_TO_ADDRESS();
+}
diff --git a/packages/protocol/contracts/nft_bridge/erc721/IErc721Bridge.sol b/packages/protocol/contracts/nft_bridge/erc721/IErc721Bridge.sol
new file mode 100644
index 00000000000..27ea743a690
--- /dev/null
+++ b/packages/protocol/contracts/nft_bridge/erc721/IErc721Bridge.sol
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: MIT
+//  _____     _ _         _         _
+// |_   _|_ _(_) |_____  | |   __ _| |__ ___
+//   | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
+//   |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
+
+pragma solidity ^0.8.20;
+
+/**
+ * Bridge interface for NFT contracts.
+ * @dev Ether is held by Bridges on L1 and by the EtherVault on L2,
+ * not TokenVaults.
+ */
+interface IErc721Bridge {
+    struct Message {
+        // Message ID.
+        uint256 id;
+        // Message sender address (auto filled).
+        address sender;
+        // Source chain ID (auto filled).
+        uint256 srcChainId;
+        // Destination chain ID where the `to` address lives (auto filled).
+        uint256 destChainId;
+        // Owner address of the bridged asset.
+        address owner;
+        // Destination owner address.
+        address to;
+        // Alternate address to send any refund. If blank, defaults to owner.
+        address refundAddress;
+        // Deposited token contract address (!It is always the deposited AKA original address)
+        address tokenContract;
+        // Token Ids - multiple per given contract can be birdged
+        // For an ERC1155 the only difference is that it can have
+        // multiple amounts/tokenId
+        uint256[] tokenIds;
+        // Processing fee for the relayer. Zero if owner will process themself.
+        uint256 processingFee;
+        // gasLimit to invoke on the destination chain
+        uint256 gasLimit;
+        // Token symbol
+        string tokenSymbol;
+        // Token name
+        string tokenName;
+        // URIs - so that it shows up as in parent chain.
+        string[] tokenURIs;
+    }
+
+    struct Context {
+        bytes32 msgHash;
+        address sender;
+        uint256 srcChainId;
+    }
+
+    event SignalSentErc721(address sender, bytes32 msgHash);
+    event MessageSentErc721(bytes32 indexed msgHash, Message message);
+    event TokenReleasedErc721(bytes32 indexed msgHash, address to, address token, uint256 tokenId);
+
+    /// Sends a message to the destination chain and takes custody
+    /// of the token(s) required in this contract.
+    function sendMessageErc721(Message memory message)
+        external
+        payable
+        returns (bytes32 msgHash);
+
+    // Release token(s) with a proof that the message processing on the destination
+    // chain has been failed.
+    function releaseTokenErc721(
+        IErc721Bridge.Message calldata message,
+        bytes calldata proof
+    )
+        external;
+
+    /// Checks if a msgHash has been stored on the bridge contract by the
+    /// current address.
+    function isMessageSentErc721(bytes32 msgHash) external view returns (bool);
+
+    /// Checks if a msgHash has been received on the destination chain and
+    /// sent by the src chain.
+    function isMessageReceivedErc721(
+        bytes32 msgHash,
+        uint256 srcChainId,
+        bytes calldata proof
+    )
+        external
+        view
+        returns (bool);
+
+    /// Checks if a msgHash has been failed on the destination chain.
+    function isMessageFailedErc721(
+        bytes32 msgHash,
+        uint256 destChainId,
+        bytes calldata proof
+    )
+        external
+        view
+        returns (bool);
+
+    /// Returns the bridge state context.
+    function context() external view returns (Context memory context);
+
+    function hashMessage(IErc721Bridge.Message calldata message)
+        external
+        pure
+        returns (bytes32);
+}
diff --git a/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeData.sol b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeData.sol
new file mode 100644
index 00000000000..cb0a2c121a2
--- /dev/null
+++ b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeData.sol
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: MIT
+//  _____     _ _         _         _
+// |_   _|_ _(_) |_____  | |   __ _| |__ ___
+//   | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
+//   |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
+
+pragma solidity ^0.8.20;
+
+import { AddressResolver } from "../../../common/AddressResolver.sol";
+import { BlockHeader } from "../../../libs/LibBlockHeader.sol";
+import { IErc721Bridge } from "../IErc721Bridge.sol";
+import { LibAddress } from "../../../libs/LibAddress.sol";
+
+/**
+ * Stores message metadata on the Bridge. It's used to keep track of the state
+ * of messages that are being
+ * transferred across the bridge, and it contains functions to hash messages and
+ * check their status.
+ */
+library LibErc721BridgeData {
+    /// @dev The State struct stores the state of messages in the Bridge
+    /// contract.
+    struct State {
+        uint256 nextMessageId;
+        IErc721Bridge.Context ctx; // 3 slots
+        mapping(bytes32 msgHash => bool released) tokensReleased;
+        uint256[45] __gap;
+    }
+
+    /// @dev StatusProof holds the block header and proof for a particular
+    /// status.
+    struct StatusProof {
+        BlockHeader header;
+        bytes proof;
+    }
+
+    bytes32 internal constant MESSAGE_HASH_PLACEHOLDER = bytes32(uint256(1));
+    uint256 internal constant CHAINID_PLACEHOLDER = type(uint256).max;
+    address internal constant SRC_CHAIN_SENDER_PLACEHOLDER =
+        address(uint160(uint256(1)));
+
+    // Note: These events must match the ones defined in Bridge.sol.
+    event MessageSentErc721(bytes32 indexed msgHash, IErc721Bridge.Message message);
+    event DestChainEnabledErc721(uint256 indexed chainId, bool enabled);
+
+    /**
+     * Calculate the keccak256 hash of the message
+     * @param message The message to be hashed
+     * @return msgHash The keccak256 hash of the message
+     */
+    function hashMessage(IErc721Bridge.Message memory message)
+        internal
+        pure
+        returns (bytes32)
+    {
+        return keccak256(abi.encode(message));
+    }
+}
diff --git a/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeProcess.sol b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeProcess.sol
new file mode 100644
index 00000000000..848f27b622e
--- /dev/null
+++ b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeProcess.sol
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: MIT
+//  _____     _ _         _         _
+// |_   _|_ _(_) |_____  | |   __ _| |__ ___
+//   | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
+//   |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
+
+pragma solidity ^0.8.20;
+
+import { AddressResolver } from "../../../common/AddressResolver.sol";
+import { Erc721Vault } from "../../Erc721Vault.sol";
+import { IErc721Bridge } from "../IErc721Bridge.sol";
+import { ISignalService } from "../../../signal/ISignalService.sol";
+import { LibAddress } from "../../../libs/LibAddress.sol";
+import { LibErc721BridgeData } from "./LibErc721BridgeData.sol";
+import { LibErc721BridgeStatus } from "./LibErc721BridgeStatus.sol";
+import { LibMath } from "../../../libs/LibMath.sol";
+
+/**
+ * This library provides functions for processing bridge messages on the
+ * destination chain.
+ */
+library LibErc721BridgeProcess {
+    using LibMath for uint256;
+    using LibAddress for address;
+    using LibErc721BridgeData for IErc721Bridge.Message;
+    using LibErc721BridgeData for LibErc721BridgeData.State;
+
+    error ERC721_B_FORBIDDEN();
+    error ERC721_B_SIGNAL_NOT_RECEIVED();
+    error ERC721_B_STATUS_MISMATCH();
+    error ERC721_B_WRONG_CHAIN_ID();
+
+    /**
+     * Process the bridge message on the destination chain. It can be called by
+     * any address, including `message.owner`.
+     * @dev It starts by hashing the message,
+     * and doing a lookup in the bridge state to see if the status is "NEW".
+     * It then does 2 things. Checks if Vault already has this NFT. If yes, then
+     * releases it (sends) if not it means it is a new bridging (so mints it).
+     * @param state The bridge state.
+     * @param resolver The address resolver.
+     * @param message The message to process.
+     * @param proof The msgHash proof from the source chain.
+     */
+    function processMessage(
+        LibErc721BridgeData.State storage state,
+        AddressResolver resolver,
+        IErc721Bridge.Message calldata message,
+        bytes calldata proof
+    )
+        internal
+    {
+        // If the gas limit is set to zero, only the owner can process the
+        // message.
+        if (message.gasLimit == 0 && msg.sender != message.owner) {
+            revert ERC721_B_FORBIDDEN();
+        }
+
+        if (message.destChainId != block.chainid) {
+            revert ERC721_B_WRONG_CHAIN_ID();
+        }
+
+        // The message status must be "NEW"; "RETRIABLE" is handled in
+        // LibBridgeRetry.sol.
+        bytes32 msgHash = message.hashMessage();
+        if (
+            LibErc721BridgeStatus.getMessageStatusErc721(msgHash)
+                != LibErc721BridgeStatus.MessageStatus.NEW
+        ) {
+            revert ERC721_B_STATUS_MISMATCH();
+        }
+        // Message must have been "received" on the destChain (current chain)
+        address srcErc721Bridge =
+            resolver.resolve(message.srcChainId, "erc721_bridge", false);
+
+        if (
+            !ISignalService(resolver.resolve("signal_service", false))
+                .isSignalReceived({
+                srcChainId: message.srcChainId,
+                app: srcErc721Bridge,
+                signal: msgHash,
+                proof: proof
+            })
+        ) {
+            revert ERC721_B_SIGNAL_NOT_RECEIVED();
+        }
+
+        // Release tokens needs to have the mechanism to resolve the addresses
+        // and deploy modified erc721 contracts if needed (with tokenURI)
+        address tokenVault = resolver.resolve("erc721_vault", true);
+        if (tokenVault != address(0)) {
+            Erc721Vault(tokenVault).releaseTokens(
+                message.owner,
+                message.tokenContract,
+                message.tokenIds
+            );
+        }
+
+        // Mark the status as DONE (otherwise reverts anyways)
+        LibErc721BridgeStatus.updateMessageStatusErc721(msgHash, LibErc721BridgeStatus.MessageStatus.DONE);
+    }
+}
diff --git a/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeSend.sol b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeSend.sol
new file mode 100644
index 00000000000..5180c701e0f
--- /dev/null
+++ b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeSend.sol
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: MIT
+//  _____     _ _         _         _
+// |_   _|_ _(_) |_____  | |   __ _| |__ ___
+//   | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
+//   |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
+
+pragma solidity ^0.8.20;
+
+import { IERC721Upgradeable} from
+    "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
+import { AddressResolver } from "../../../common/AddressResolver.sol";
+import { IErc721Bridge } from "../IErc721Bridge.sol";
+import { ISignalService } from "../../../signal/ISignalService.sol";
+import { LibAddress } from "../../../libs/LibAddress.sol";
+import { LibErc721BridgeData } from "./LibErc721BridgeData.sol";
+
+/**
+ * Entry point for starting a bridge transaction.
+ */
+library LibErc721BridgeSend {
+    using LibAddress for address;
+    using LibErc721BridgeData for IErc721Bridge.Message;
+
+    error B_INCORRECT_VALUE();
+    error ERC721_B_OWNER_IS_NULL();
+    error ERC721_B_WRONG_CHAIN_ID();
+    error ERC721_B_WRONG_TO_ADDRESS();
+
+    /**
+     * Send a message to the Bridge with the details of the request.
+     * @dev The Bridge takes custody of the funds, unless the source chain is
+     * Taiko,
+     * in which case the funds are sent to and managed by the EtherVault.
+     * @param state The current state of the Bridge
+     * @param resolver The address resolver
+     * @param message Specifies the `depositValue`, `callValue`, and
+     * `processingFee`.
+     * These must sum to `msg.value`. It also specifies the `destChainId`
+     * which must have a `bridge` address set on the AddressResolver and
+     * differ from the current chain ID.
+     * @return msgHash The hash of the sent message.
+     * This is picked up by an off-chain relayer which indicates
+     * a bridge message has been sent and is ready to be processed on the
+     * destination chain.
+     */
+    function sendMessageErc721(
+        LibErc721BridgeData.State storage state,
+        AddressResolver resolver,
+        IErc721Bridge.Message memory message
+    )
+        internal
+        returns (bytes32 msgHash)
+    {
+        if (message.owner == address(0)) {
+            revert ERC721_B_OWNER_IS_NULL();
+        }
+
+        (bool destChainEnabled, address destChain) =
+            isDestChainEnabled(resolver, message.destChainId);
+
+        if (!destChainEnabled || message.destChainId == block.chainid) {
+            revert ERC721_B_WRONG_CHAIN_ID();
+        }
+        if (message.to == address(0) || message.to == destChain) {
+            revert ERC721_B_WRONG_TO_ADDRESS();
+        }
+
+        // Send tokens to vault
+        address tokenVault = resolver.resolve("erc721_vault", true);
+
+        // User has to accept address(this) to transfer NFT tokens on behalf
+        for (uint256 i; i < message.tokenIds.length; i++) {
+            IERC721Upgradeable(message.tokenContract).safeTransferFrom(
+                message.owner, tokenVault, message.tokenIds[i]);
+        }
+
+        message.id = state.nextMessageId++;
+        message.sender = msg.sender;
+        message.srcChainId = block.chainid;
+
+        msgHash = message.hashMessage();
+        // Store a key which is the hash of this contract address and the
+        // msgHash, with a value of 1.
+        ISignalService(resolver.resolve("signal_service", false)).sendSignal(
+            msgHash
+        );
+        emit LibErc721BridgeData.MessageSentErc721(msgHash, message);
+    }
+
+    /**
+     * Check if the destination chain is enabled.
+     * @param resolver The address resolver
+     * @param chainId The destination chain id
+     * @return enabled True if the destination chain is enabled
+     * @return destBridge The bridge of the destination chain
+     */
+    function isDestChainEnabled(
+        AddressResolver resolver,
+        uint256 chainId
+    )
+        internal
+        view
+        returns (bool enabled, address destBridge)
+    {
+        destBridge = resolver.resolve(chainId, "erc721_bridge", true);
+        enabled = destBridge != address(0);
+    }
+
+    /**
+     * Check if the message was sent.
+     * @param resolver The address resolver
+     * @param msgHash The hash of the sent message
+     * @return True if the message was sent
+     */
+    function isMessageSentErc721(
+        AddressResolver resolver,
+        bytes32 msgHash
+    )
+        internal
+        view
+        returns (bool)
+    {
+        return ISignalService(resolver.resolve("signal_service", false))
+            .isSignalSent({ app: address(this), signal: msgHash });
+    }
+
+    /**
+     * Check if the message was received.
+     * @param resolver The address resolver
+     * @param msgHash The hash of the received message
+     * @param srcChainId The id of the source chain
+     * @param proof The proof of message receipt
+     * @return True if the message was received
+     */
+    function isMessageReceivedErc721(
+        AddressResolver resolver,
+        bytes32 msgHash,
+        uint256 srcChainId,
+        bytes calldata proof
+    )
+        internal
+        view
+        returns (bool)
+    {
+        address srcBridge = resolver.resolve(srcChainId, "bridge", false);
+        return ISignalService(resolver.resolve("signal_service", false))
+            .isSignalReceived({
+            srcChainId: srcChainId,
+            app: srcBridge,
+            signal: msgHash,
+            proof: proof
+        });
+    }
+}
diff --git a/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeStatus.sol b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeStatus.sol
new file mode 100644
index 00000000000..d2580a963e6
--- /dev/null
+++ b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeStatus.sol
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: MIT
+//  _____     _ _         _         _
+// |_   _|_ _(_) |_____  | |   __ _| |__ ___
+//   | |/ _` | | / / _ \ | |__/ _` | '_ (_-<
+//   |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/
+
+pragma solidity ^0.8.20;
+
+import { AddressResolver } from "../../../common/AddressResolver.sol";
+import { BlockHeader, LibBlockHeader } from "../../../libs/LibBlockHeader.sol";
+import { ICrossChainSync } from "../../../common/ICrossChainSync.sol";
+import { LibErc721BridgeData } from "./LibErc721BridgeData.sol";
+import { LibTrieProof } from "../../../libs/LibTrieProof.sol";
+
+/**
+ * This library provides functions to get and update the status of bridge
+ * messages.
+ */
+library LibErc721BridgeStatus {
+    using LibBlockHeader for BlockHeader;
+
+    enum MessageStatus {
+        NEW,
+        RETRIABLE,
+        DONE,
+        FAILED
+    }
+
+    event MessageStatusChangedErc721(
+        bytes32 indexed msgHash, MessageStatus status, address transactor
+    );
+
+    error B_MSG_HASH_NULL();
+    error B_WRONG_CHAIN_ID();
+
+    /**
+     * Updates the status of a bridge message.
+     * @dev If messageStatus is same as in the messageStatus mapping, does
+     * nothing.
+     * @param msgHash The hash of the message.
+     * @param status The new status of the message.
+     */
+    function updateMessageStatusErc721(
+        bytes32 msgHash,
+        MessageStatus status
+    )
+        internal
+    {
+        if (getMessageStatusErc721(msgHash) != status) {
+            _setMessageStatus(msgHash, status);
+            emit MessageStatusChangedErc721(msgHash, status, msg.sender);
+        }
+    }
+
+    /**
+     * Gets the status of a bridge message.
+     * @param msgHash The hash of the message.
+     * @return The status of the message.
+     */
+    function getMessageStatusErc721(bytes32 msgHash)
+        internal
+        view
+        returns (MessageStatus)
+    {
+        bytes32 slot = getMessageStatusSlot(msgHash);
+        uint256 value;
+        assembly {
+            value := sload(slot)
+        }
+        return MessageStatus(value);
+    }
+
+    /**
+     * Checks if a bridge message has failed.
+     * @param resolver The address resolver.
+     * @param msgHash The hash of the message.
+     * @param destChainId The ID of the destination chain.
+     * @param proof The proof of the status of the message.
+     * @return True if the message has failed, false otherwise.
+     */
+    function isMessageFailedErc721(
+        AddressResolver resolver,
+        bytes32 msgHash,
+        uint256 destChainId,
+        bytes calldata proof
+    )
+        internal
+        view
+        returns (bool)
+    {
+        if (destChainId == block.chainid) {
+            revert B_WRONG_CHAIN_ID();
+        }
+        if (msgHash == 0x0) {
+            revert B_MSG_HASH_NULL();
+        }
+
+        LibErc721BridgeData.StatusProof memory sp =
+            abi.decode(proof, (LibErc721BridgeData.StatusProof));
+
+        bytes32 syncedHeaderHash = ICrossChainSync(
+            resolver.resolve("taiko", false)
+        ).getCrossChainBlockHash(sp.header.height);
+
+        if (
+            syncedHeaderHash == 0
+                || syncedHeaderHash != sp.header.hashBlockHeader()
+        ) {
+            return false;
+        }
+
+        return LibTrieProof.verifyWithAccountProof({
+            stateRoot: sp.header.stateRoot,
+            addr: resolver.resolve(destChainId, "bridge", false),
+            slot: getMessageStatusSlot(msgHash),
+            value: bytes32(uint256(LibErc721BridgeStatus.MessageStatus.FAILED)),
+            mkproof: sp.proof
+        });
+    }
+    /**
+     * Gets the storage slot for a bridge message status.
+     * @param msgHash The hash of the message.
+     * @return The storage slot for the message status.
+     */
+
+    function getMessageStatusSlot(bytes32 msgHash)
+        internal
+        pure
+        returns (bytes32)
+    {
+        return keccak256(bytes.concat(bytes("MESSAGE_STATUS"), msgHash));
+    }
+
+    /**
+     * Sets the status of a bridge message.
+     * @param msgHash The hash of the message.
+     * @param status The new status of the message.
+     */
+    function _setMessageStatus(bytes32 msgHash, MessageStatus status) private {
+        bytes32 slot = getMessageStatusSlot(msgHash);
+        uint256 value = uint256(status);
+        assembly {
+            sstore(slot, value)
+        }
+    }
+}