// SPDX-License-Identifier: LicenseRef-Blockwell-Smart-License
pragma solidity ^0.8.9;

import "common8/LicenseRef-Blockwell-Smart-License.sol";
import "common8/relay/RelayBase.sol";
import "common8/ERC721.sol";
import "common8/Erc20.sol";
import "common8/ERC721TokenReceiver.sol";

/**
 * @dev Relay contract for verifying crosschain swaps.
 *
 * Blockwell Exclusive (Intellectual Property that lives on-chain via Smart License)
 */
contract SwapNftRelay is RelayBase, ERC721TokenReceiver {
    uint256 public swapNonce;

    event SwapToChain(
        uint256 toChainId,
        uint256 swapNonce,
        ERC721 tokenContract,
        address indexed from,
        address indexed to,
        bytes32 indexed swapId,
        uint256[] tokenIds
    );

    event SwapFromChain(
        ERC721 tokenContract,
        address indexed from,
        address indexed to,
        bytes32 indexed swapId,
        uint256 fromChainId,
        uint256[] tokenIds
    );

    error SwapIdMismatch();

    constructor(uint256 _swappersNeeded) RelayBase(_swappersNeeded) {
        name = "NFT SwapRelay";
        bwtype = SWAP_NFT_RELAY;
        bwver = 88;
    }

    function onERC721Received(
        address,
        address,
        uint256,
        bytes memory
    ) external pure returns (bytes4) {
        return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
    }

    /**
     * @dev Initiates a swap to another chain. Transfers the tokens to this contract and emits an event
     *      indicating the request to swap.
     *
     * Blockwell Exclusive (Intellectual Property that lives on-chain via Smart License)
     */
    function swapToChain(
        ERC721 tokenContract,
        uint256 toChainId,
        address to,
        uint256[] calldata tokenIds
    ) public {
        uint256 nonce = getSwapNonce();
        uint256 chainID;
        assembly {
            chainID := chainid()
        }

        bytes32 swapId = keccak256(
            abi.encodePacked(
                address(this),
                chainID,
                nonce,
                tokenContract,
                msg.sender,
                to,
                toChainId,
                tokenIds
            )
        );

        for (uint256 i = 0; i < tokenIds.length; i++) {
            tokenContract.transferFrom(msg.sender, address(this), tokenIds[i]);
        }

        emit SwapToChain(toChainId, nonce, tokenContract, msg.sender, to, swapId, tokenIds);
    }

    function swapFromChain(
        address relayContract,
        uint256 fromChainId,
        uint256 sourceSwapNonce,
        address sourceTokenContract,
        ERC721 tokenContract,
        address from,
        address to,
        bytes32 swapId,
        uint256[] calldata tokenIds
    ) public onlySwapper {
        uint256 chainID;
        assembly {
            chainID := chainid()
        }
        bytes32 swapIdCheck = keccak256(
            abi.encodePacked(
                relayContract,
                fromChainId,
                sourceSwapNonce,
                sourceTokenContract,
                from,
                to,
                chainID,
                tokenIds
            )
        );
        if (swapId != swapIdCheck) {
            revert SwapIdMismatch();
        }

        if (shouldSwap(swapId, msg.sender)) {
            for (uint256 i = 0; i < tokenIds.length; i++) {
                tokenContract.transferFrom(address(this), to, tokenIds[i]);
            }
        }

        emit SwapFromChain(tokenContract, from, to, swapId, fromChainId, tokenIds);
    }

    function getSwapNonce() internal returns (uint256) {
        return ++swapNonce;
    }

    function withdraw() public onlyAdmin {
        payable(msg.sender).transfer(address(this).balance);
    }

    function withdrawTokens(Erc20 token) public onlyAdmin {
        token.transfer(msg.sender, token.balanceOf(address(this)));
    }
}
[{"inputs":[{"internalType":"uint256","name":"_swappersNeeded","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"SwapIdMismatch","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint8","name":"groupId","type":"uint8"},{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"AddedToGroup","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint8","name":"groupId","type":"uint8"},{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"RemovedFromGroup","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"swapId","type":"bytes32"},{"indexed":true,"internalType":"address","name":"swapper","type":"address"},{"indexed":true,"internalType":"uint256","name":"count","type":"uint256"}],"name":"Swap","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract ERC721","name":"tokenContract","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"bytes32","name":"swapId","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"fromChainId","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"SwapFromChain","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"toChainId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"swapNonce","type":"uint256"},{"indexed":false,"internalType":"contract ERC721","name":"tokenContract","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"bytes32","name":"swapId","type":"bytes32"},{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"SwapToChain","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"count","type":"uint256"}],"name":"SwappersNeededUpdate","type":"event"},{"inputs":[],"name":"ADMIN","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SWAPPERS","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"addAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"addSwapper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"bwtype","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bwver","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isSwapper","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"removeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"removeSwapper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"count","type":"uint256"}],"name":"setSwappersNeeded","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"swapDone","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"relayContract","type":"address"},{"internalType":"uint256","name":"fromChainId","type":"uint256"},{"internalType":"uint256","name":"sourceSwapNonce","type":"uint256"},{"internalType":"address","name":"sourceTokenContract","type":"address"},{"internalType":"contract ERC721","name":"tokenContract","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes32","name":"swapId","type":"bytes32"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"swapFromChain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"swapNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"swapId","type":"bytes32"}],"name":"swapRelayers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC721","name":"tokenContract","type":"address"},{"internalType":"uint256","name":"toChainId","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"swapToChain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"swappersNeeded","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"swapIds","type":"bytes32[]"}],"name":"swapsDone","outputs":[{"internalType":"bool[]","name":"","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract Erc20","name":"token","type":"address"}],"name":"withdrawTokens","outputs":[],"stateMutability":"nonpayable","type":"function"}]