library CurrencyTransferLib {
    /// @dev The address interpreted as native token of the chain.
    address public constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    /// @dev Transfers a given amount of currency.
    function transferCurrency(
        address _currency,
        address _from,
        address _to,
        uint256 _amount
    ) internal {
        if (_amount == 0) {
            return;
        }

        if (_currency == NATIVE_TOKEN) {
            safeTransferNativeToken(_to, _amount);
        } else {
            safeTransferERC20(_currency, _from, _to, _amount);
        }
    }

    /// @dev Transfers a given amount of currency. (With native token wrapping)
    function transferCurrencyWithWrapperAndBalanceCheck(
        address _currency,
        address _from,
        address _to,
        uint256 _amount,
        address _nativeTokenWrapper
    ) internal {
        if (_amount == 0) {
            return;
        }

        if (_currency == NATIVE_TOKEN) {
            if (_from == address(this)) {
                // withdraw from weth then transfer withdrawn native token to recipient
                IWETH(_nativeTokenWrapper).withdraw(_amount);
                safeTransferNativeTokenWithWrapper(_to, _amount, _nativeTokenWrapper);
            } else if (_to == address(this)) {
                // store native currency in weth
                require(_amount == msg.value, "msg.value != amount");
                IWETH(_nativeTokenWrapper).deposit{ value: _amount }();
            } else {
                safeTransferNativeTokenWithWrapper(_to, _amount, _nativeTokenWrapper);
            }
        } else {
            safeTransferERC20WithBalanceCheck(_currency, _from, _to, _amount);
        }
    }

    /// @dev Transfer `amount` of ERC20 token from `from` to `to`.
    function safeTransferERC20(
        address _currency,
        address _from,
        address _to,
        uint256 _amount
    ) internal {
        if (_from == _to) {
            return;
        }

        bool success = _from == address(this)
            ? IERC20Upgradeable(_currency).transfer(_to, _amount)
            : IERC20Upgradeable(_currency).transferFrom(_from, _to, _amount);

        require(success, "currency transfer failed.");
    }

    /// @dev Transfer `amount` of ERC20 token from `from` to `to`.
    function safeTransferERC20WithBalanceCheck(
        address _currency,
        address _from,
        address _to,
        uint256 _amount
    ) internal {
        if (_from == _to) {
            return;
        }

        uint256 balBefore = IERC20Upgradeable(_currency).balanceOf(_to);
        bool success = _from == address(this)
            ? IERC20Upgradeable(_currency).transfer(_to, _amount)
            : IERC20Upgradeable(_currency).transferFrom(_from, _to, _amount);
        uint256 balAfter = IERC20Upgradeable(_currency).balanceOf(_to);

        require(success && (balAfter == balBefore + _amount), "currency transfer failed.");
    }

    /// @dev Transfers `amount` of native token to `to`.
    function safeTransferNativeToken(address to, uint256 value) internal {
        // solhint-disable avoid-low-level-calls
        // slither-disable-next-line low-level-calls
        (bool success, ) = to.call{ value: value }("");
        require(success, "native token transfer failed");
    }

    /// @dev Transfers `amount` of native token to `to`. (With native token wrapping)
    function safeTransferNativeTokenWithWrapper(
        address to,
        uint256 value,
        address _nativeTokenWrapper
    ) internal {
        // solhint-disable avoid-low-level-calls
        // slither-disable-next-line low-level-calls
        (bool success, ) = to.call{ value: value }("");
        if (!success) {
            IWETH(_nativeTokenWrapper).deposit{ value: value }();
            require(IERC20Upgradeable(_nativeTokenWrapper).transfer(to, value), "transfer failed");
        }
    }
}
function safeTransferERC20WithBalanceCheck(
    address _currency,
    address _from,
    address _to,
    uint256 _amount
) internal {
    if (_from == _to) {
        return;
    }

    uint256 balBefore = IERC20Upgradeable(_currency).balanceOf(_to);
    bool success = _from == address(this)
        ? IERC20Upgradeable(_currency).transfer(_to, _amount)
        : IERC20Upgradeable(_currency).transferFrom(_from, _to, _amount);
    uint256 balAfter = IERC20Upgradeable(_currency).balanceOf(_to);

    require(success && (balAfter == balBefore + _amount), "currency transfer failed.");
}

/// @dev Transfer `amount` of ERC20 token from `from` to `to`.