// SPDX-License-Identifier: MITpragmasolidity 0.8.17;/**
* @notice CollectionType is used in OrderStructs.Maker's collectionType to determine the collection type being traded.
*/enumCollectionType {
ERC721,
ERC1155
}
Contract Source Code
File 2 of 14: GenericErrors.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;/**
* @notice It is emitted if the call recipient is not a contract.
*/errorNotAContract();
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;/**
* @title IOwnableTwoSteps
* @author LooksRare protocol team (👀,💎)
*/interfaceIOwnableTwoSteps{
/**
* @notice This enum keeps track of the ownership status.
* @param NoOngoingTransfer The default status when the owner is set
* @param TransferInProgress The status when a transfer to a new owner is initialized
* @param RenouncementInProgress The status when a transfer to address(0) is initialized
*/enumStatus {
NoOngoingTransfer,
TransferInProgress,
RenouncementInProgress
}
/**
* @notice This is returned when there is no transfer of ownership in progress.
*/errorNoOngoingTransferInProgress();
/**
* @notice This is returned when the caller is not the owner.
*/errorNotOwner();
/**
* @notice This is returned when there is no renouncement in progress but
* the owner tries to validate the ownership renouncement.
*/errorRenouncementNotInProgress();
/**
* @notice This is returned when the transfer is already in progress but the owner tries
* initiate a new ownership transfer.
*/errorTransferAlreadyInProgress();
/**
* @notice This is returned when there is no ownership transfer in progress but the
* ownership change tries to be approved.
*/errorTransferNotInProgress();
/**
* @notice This is returned when the ownership transfer is attempted to be validated by the
* a caller that is not the potential owner.
*/errorWrongPotentialOwner();
/**
* @notice This is emitted if the ownership transfer is cancelled.
*/eventCancelOwnershipTransfer();
/**
* @notice This is emitted if the ownership renouncement is initiated.
*/eventInitiateOwnershipRenouncement();
/**
* @notice This is emitted if the ownership transfer is initiated.
* @param previousOwner Previous/current owner
* @param potentialOwner Potential/future owner
*/eventInitiateOwnershipTransfer(address previousOwner, address potentialOwner);
/**
* @notice This is emitted when there is a new owner.
*/eventNewOwner(address newOwner);
}
Contract Source Code
File 6 of 14: ITransferManager.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.17;// Librariesimport {OrderStructs} from"../libraries/OrderStructs.sol";
// Enumsimport {CollectionType} from"../enums/CollectionType.sol";
/**
* @title ITransferManager
* @author LooksRare protocol team (👀,💎)
*/interfaceITransferManager{
/**
* @notice This struct is only used for transferBatchItemsAcrossCollections.
* @param collection Collection address
* @param collectionType 0 for ERC721, 1 for ERC1155
* @param itemIds Array of item ids to transfer
* @param amounts Array of amounts to transfer
*/structBatchTransferItem {
address collection;
CollectionType collectionType;
uint256[] itemIds;
uint256[] amounts;
}
/**
* @notice It is emitted if operators' approvals to transfer NFTs are granted by a user.
* @param user Address of the user
* @param operators Array of operator addresses
*/eventApprovalsGranted(address user, address[] operators);
/**
* @notice It is emitted if operators' approvals to transfer NFTs are revoked by a user.
* @param user Address of the user
* @param operators Array of operator addresses
*/eventApprovalsRemoved(address user, address[] operators);
/**
* @notice It is emitted if a new operator is added to the global allowlist.
* @param operator Operator address
*/eventOperatorAllowed(address operator);
/**
* @notice It is emitted if an operator is removed from the global allowlist.
* @param operator Operator address
*/eventOperatorRemoved(address operator);
/**
* @notice It is returned if the operator to approve has already been approved by the user.
*/errorOperatorAlreadyApprovedByUser();
/**
* @notice It is returned if the operator to revoke has not been previously approved by the user.
*/errorOperatorNotApprovedByUser();
/**
* @notice It is returned if the transfer caller is already allowed by the owner.
* @dev This error can only be returned for owner operations.
*/errorOperatorAlreadyAllowed();
/**
* @notice It is returned if the operator to approve is not in the global allowlist defined by the owner.
* @dev This error can be returned if the user tries to grant approval to an operator address not in the
* allowlist or if the owner tries to remove the operator from the global allowlist.
*/errorOperatorNotAllowed();
/**
* @notice It is returned if the transfer caller is invalid.
* For a transfer called to be valid, the operator must be in the global allowlist and
* approved by the 'from' user.
*/errorTransferCallerInvalid();
}
Contract Source Code
File 7 of 14: LowLevelERC1155Transfer.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;// Interfacesimport {IERC1155} from"../interfaces/generic/IERC1155.sol";
// Errorsimport {ERC1155SafeTransferFromFail, ERC1155SafeBatchTransferFromFail} from"../errors/LowLevelErrors.sol";
import {NotAContract} from"../errors/GenericErrors.sol";
/**
* @title LowLevelERC1155Transfer
* @notice This contract contains low-level calls to transfer ERC1155 tokens.
* @author LooksRare protocol team (👀,💎)
*/contractLowLevelERC1155Transfer{
/**
* @notice Execute ERC1155 safeTransferFrom
* @param collection Address of the collection
* @param from Address of the sender
* @param to Address of the recipient
* @param tokenId tokenId to transfer
* @param amount Amount to transfer
*/function_executeERC1155SafeTransferFrom(address collection,
addressfrom,
address to,
uint256 tokenId,
uint256 amount
) internal{
if (collection.code.length==0) {
revert NotAContract();
}
(bool status, ) = collection.call(abi.encodeCall(IERC1155.safeTransferFrom, (from, to, tokenId, amount, "")));
if (!status) {
revert ERC1155SafeTransferFromFail();
}
}
/**
* @notice Execute ERC1155 safeBatchTransferFrom
* @param collection Address of the collection
* @param from Address of the sender
* @param to Address of the recipient
* @param tokenIds Array of tokenIds to transfer
* @param amounts Array of amounts to transfer
*/function_executeERC1155SafeBatchTransferFrom(address collection,
addressfrom,
address to,
uint256[] calldata tokenIds,
uint256[] calldata amounts
) internal{
if (collection.code.length==0) {
revert NotAContract();
}
(bool status, ) = collection.call(
abi.encodeCall(IERC1155.safeBatchTransferFrom, (from, to, tokenIds, amounts, ""))
);
if (!status) {
revert ERC1155SafeBatchTransferFromFail();
}
}
}
Contract Source Code
File 8 of 14: LowLevelERC721Transfer.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;// Interfacesimport {IERC721} from"../interfaces/generic/IERC721.sol";
// Errorsimport {ERC721TransferFromFail} from"../errors/LowLevelErrors.sol";
import {NotAContract} from"../errors/GenericErrors.sol";
/**
* @title LowLevelERC721Transfer
* @notice This contract contains low-level calls to transfer ERC721 tokens.
* @author LooksRare protocol team (👀,💎)
*/contractLowLevelERC721Transfer{
/**
* @notice Execute ERC721 transferFrom
* @param collection Address of the collection
* @param from Address of the sender
* @param to Address of the recipient
* @param tokenId tokenId to transfer
*/function_executeERC721TransferFrom(address collection, addressfrom, address to, uint256 tokenId) internal{
if (collection.code.length==0) {
revert NotAContract();
}
(bool status, ) = collection.call(abi.encodeCall(IERC721.transferFrom, (from, to, tokenId)));
if (!status) {
revert ERC721TransferFromFail();
}
}
}
Contract Source Code
File 9 of 14: LowLevelErrors.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;/**
* @notice It is emitted if the ETH transfer fails.
*/errorETHTransferFail();
/**
* @notice It is emitted if the ERC20 approval fails.
*/errorERC20ApprovalFail();
/**
* @notice It is emitted if the ERC20 transfer fails.
*/errorERC20TransferFail();
/**
* @notice It is emitted if the ERC20 transferFrom fails.
*/errorERC20TransferFromFail();
/**
* @notice It is emitted if the ERC721 transferFrom fails.
*/errorERC721TransferFromFail();
/**
* @notice It is emitted if the ERC1155 safeTransferFrom fails.
*/errorERC1155SafeTransferFromFail();
/**
* @notice It is emitted if the ERC1155 safeBatchTransferFrom fails.
*/errorERC1155SafeBatchTransferFromFail();
Contract Source Code
File 10 of 14: OrderStructs.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.17;// Enumsimport {CollectionType} from"../enums/CollectionType.sol";
import {QuoteType} from"../enums/QuoteType.sol";
/**
* @title OrderStructs
* @notice This library contains all order struct types for the LooksRare protocol (v2).
* @author LooksRare protocol team (👀,💎)
*/libraryOrderStructs{
/**
* 1. Maker struct
*//**
* @notice Maker is the struct for a maker order.
* @param quoteType Quote type (i.e. 0 = BID, 1 = ASK)
* @param globalNonce Global user order nonce for maker orders
* @param subsetNonce Subset nonce (shared across bid/ask maker orders)
* @param orderNonce Order nonce (it can be shared across bid/ask maker orders)
* @param strategyId Strategy id
* @param collectionType Collection type (i.e. 0 = ERC721, 1 = ERC1155)
* @param collection Collection address
* @param currency Currency address (@dev address(0) = ETH)
* @param signer Signer address
* @param startTime Start timestamp
* @param endTime End timestamp
* @param price Minimum price for maker ask, maximum price for maker bid
* @param itemIds Array of itemIds
* @param amounts Array of amounts
* @param additionalParameters Extra data specific for the order
*/structMaker {
QuoteType quoteType;
uint256 globalNonce;
uint256 subsetNonce;
uint256 orderNonce;
uint256 strategyId;
CollectionType collectionType;
address collection;
address currency;
address signer;
uint256 startTime;
uint256 endTime;
uint256 price;
uint256[] itemIds;
uint256[] amounts;
bytes additionalParameters;
}
/**
* 2. Taker struct
*//**
* @notice Taker is the struct for a taker ask/bid order. It contains the parameters required for a direct purchase.
* @dev Taker struct is matched against MakerAsk/MakerBid structs at the protocol level.
* @param recipient Recipient address (to receive NFTs or non-fungible tokens)
* @param additionalParameters Extra data specific for the order
*/structTaker {
address recipient;
bytes additionalParameters;
}
/**
* 3. Merkle tree struct
*/enumMerkleTreeNodePosition { Left, Right }
/**
* @notice MerkleTreeNode is a MerkleTree's node.
* @param value It can be an order hash or a proof
* @param position The node's position in its branch.
* It can be left or right or none
* (before the tree is sorted).
*/structMerkleTreeNode {
bytes32 value;
MerkleTreeNodePosition position;
}
/**
* @notice MerkleTree is the struct for a merkle tree of order hashes.
* @dev A Merkle tree can be computed with order hashes.
* It can contain order hashes from both maker bid and maker ask structs.
* @param root Merkle root
* @param proof Array containing the merkle proof
*/structMerkleTree {
bytes32 root;
MerkleTreeNode[] proof;
}
/**
* 4. Constants
*//**
* @notice This is the type hash constant used to compute the maker order hash.
*/bytes32internalconstant _MAKER_TYPEHASH =keccak256(
"Maker(""uint8 quoteType,""uint256 globalNonce,""uint256 subsetNonce,""uint256 orderNonce,""uint256 strategyId,""uint8 collectionType,""address collection,""address currency,""address signer,""uint256 startTime,""uint256 endTime,""uint256 price,""uint256[] itemIds,""uint256[] amounts,""bytes additionalParameters"")"
);
/**
* 5. Hash functions
*//**
* @notice This function is used to compute the order hash for a maker struct.
* @param maker Maker order struct
* @return makerHash Hash of the maker struct
*/functionhash(Maker memory maker) internalpurereturns (bytes32) {
// Encoding is done into two parts to avoid stack too deep issuesreturnkeccak256(
bytes.concat(
abi.encode(
_MAKER_TYPEHASH,
maker.quoteType,
maker.globalNonce,
maker.subsetNonce,
maker.orderNonce,
maker.strategyId,
maker.collectionType,
maker.collection,
maker.currency
),
abi.encode(
maker.signer,
maker.startTime,
maker.endTime,
maker.price,
keccak256(abi.encodePacked(maker.itemIds)),
keccak256(abi.encodePacked(maker.amounts)),
keccak256(maker.additionalParameters)
)
)
);
}
}
Contract Source Code
File 11 of 14: OwnableTwoSteps.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.17;// Interfacesimport {IOwnableTwoSteps} from"./interfaces/IOwnableTwoSteps.sol";
/**
* @title OwnableTwoSteps
* @notice This contract offers transfer of ownership in two steps with potential owner
* having to confirm the transaction to become the owner.
* Renouncement of the ownership is also a two-step process since the next potential owner is the address(0).
* @author LooksRare protocol team (👀,💎)
*/abstractcontractOwnableTwoStepsisIOwnableTwoSteps{
/**
* @notice Address of the current owner.
*/addresspublic owner;
/**
* @notice Address of the potential owner.
*/addresspublic potentialOwner;
/**
* @notice Ownership status.
*/
Status public ownershipStatus;
/**
* @notice Modifier to wrap functions for contracts that inherit this contract.
*/modifieronlyOwner() {
_onlyOwner();
_;
}
/**
* @notice Constructor
* @param _owner The contract's owner
*/constructor(address _owner) {
owner = _owner;
emit NewOwner(_owner);
}
/**
* @notice This function is used to cancel the ownership transfer.
* @dev This function can be used for both cancelling a transfer to a new owner and
* cancelling the renouncement of the ownership.
*/functioncancelOwnershipTransfer() externalonlyOwner{
Status _ownershipStatus = ownershipStatus;
if (_ownershipStatus == Status.NoOngoingTransfer) {
revert NoOngoingTransferInProgress();
}
if (_ownershipStatus == Status.TransferInProgress) {
delete potentialOwner;
}
delete ownershipStatus;
emit CancelOwnershipTransfer();
}
/**
* @notice This function is used to confirm the ownership renouncement.
*/functionconfirmOwnershipRenouncement() externalonlyOwner{
if (ownershipStatus != Status.RenouncementInProgress) {
revert RenouncementNotInProgress();
}
delete owner;
delete ownershipStatus;
emit NewOwner(address(0));
}
/**
* @notice This function is used to confirm the ownership transfer.
* @dev This function can only be called by the current potential owner.
*/functionconfirmOwnershipTransfer() external{
if (ownershipStatus != Status.TransferInProgress) {
revert TransferNotInProgress();
}
if (msg.sender!= potentialOwner) {
revert WrongPotentialOwner();
}
owner =msg.sender;
delete ownershipStatus;
delete potentialOwner;
emit NewOwner(msg.sender);
}
/**
* @notice This function is used to initiate the transfer of ownership to a new owner.
* @param newPotentialOwner New potential owner address
*/functioninitiateOwnershipTransfer(address newPotentialOwner) externalonlyOwner{
if (ownershipStatus != Status.NoOngoingTransfer) {
revert TransferAlreadyInProgress();
}
ownershipStatus = Status.TransferInProgress;
potentialOwner = newPotentialOwner;
/**
* @dev This function can only be called by the owner, so msg.sender is the owner.
* We don't have to SLOAD the owner again.
*/emit InitiateOwnershipTransfer(msg.sender, newPotentialOwner);
}
/**
* @notice This function is used to initiate the ownership renouncement.
*/functioninitiateOwnershipRenouncement() externalonlyOwner{
if (ownershipStatus != Status.NoOngoingTransfer) {
revert TransferAlreadyInProgress();
}
ownershipStatus = Status.RenouncementInProgress;
emit InitiateOwnershipRenouncement();
}
function_onlyOwner() privateview{
if (msg.sender!= owner) revert NotOwner();
}
}
Contract Source Code
File 12 of 14: QuoteType.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.17;/**
* @notice QuoteType is used in OrderStructs.Maker's quoteType to determine whether the maker order is a bid or an ask.
*/enumQuoteType {
Bid,
Ask
}
Contract Source Code
File 13 of 14: SharedErrors.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.17;/**
* @notice It is returned if the amount is invalid.
* For ERC721, any number that is not 1. For ERC1155, if amount is 0.
*/errorAmountInvalid();
/**
* @notice It is returned if the ask price is too high for the bid user.
*/errorAskTooHigh();
/**
* @notice It is returned if the bid price is too low for the ask user.
*/errorBidTooLow();
/**
* @notice It is returned if the function cannot be called by the sender.
*/errorCallerInvalid();
/**
* @notice It is returned if the currency is invalid.
*/errorCurrencyInvalid();
/**
* @notice The function selector is invalid for this strategy implementation.
*/errorFunctionSelectorInvalid();
/**
* @notice It is returned if there is either a mismatch or an error in the length of the array(s).
*/errorLengthsInvalid();
/**
* @notice It is returned if the merkle proof provided is invalid.
*/errorMerkleProofInvalid();
/**
* @notice It is returned if the length of the merkle proof provided is greater than tolerated.
* @param length Proof length
*/errorMerkleProofTooLarge(uint256 length);
/**
* @notice It is returned if the order is permanently invalid.
* There may be an issue with the order formatting.
*/errorOrderInvalid();
/**
* @notice It is returned if the maker quote type is invalid.
*/errorQuoteTypeInvalid();
Contract Source Code
File 14 of 14: TransferManager.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.17;// LooksRare unopinionated librariesimport {OwnableTwoSteps} from"@looksrare/contracts-libs/contracts/OwnableTwoSteps.sol";
import {LowLevelERC721Transfer} from"@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelERC721Transfer.sol";
import {LowLevelERC1155Transfer} from"@looksrare/contracts-libs/contracts/lowLevelCallers/LowLevelERC1155Transfer.sol";
// Interfaces and errorsimport {ITransferManager} from"./interfaces/ITransferManager.sol";
import {AmountInvalid, LengthsInvalid} from"./errors/SharedErrors.sol";
// Librariesimport {OrderStructs} from"./libraries/OrderStructs.sol";
// Enumsimport {CollectionType} from"./enums/CollectionType.sol";
/**
* @title TransferManager
* @notice This contract provides the transfer functions for ERC721/ERC1155 for contracts that require them.
* Collection type "0" refers to ERC721 transfer functions.
* Collection type "1" refers to ERC1155 transfer functions.
* @dev "Safe" transfer functions for ERC721 are not implemented since they come with added gas costs
* to verify if the recipient is a contract as it requires verifying the receiver interface is valid.
* @author LooksRare protocol team (👀,💎)
*/contractTransferManagerisITransferManager, LowLevelERC721Transfer, LowLevelERC1155Transfer, OwnableTwoSteps{
/**
* @notice This returns whether the user has approved the operator address.
* The first address is the user and the second address is the operator (e.g. LooksRareProtocol).
*/mapping(address=>mapping(address=>bool)) public hasUserApprovedOperator;
/**
* @notice This returns whether the operator address is allowed by this contract's owner.
*/mapping(address=>bool) public isOperatorAllowed;
/**
* @notice Constructor
* @param _owner Owner address
*/constructor(address _owner) OwnableTwoSteps(_owner) {}
/**
* @notice This function transfers items for a single ERC721 collection.
* @param collection Collection address
* @param from Sender address
* @param to Recipient address
* @param itemIds Array of itemIds
* @param amounts Array of amounts
*/functiontransferItemsERC721(address collection,
addressfrom,
address to,
uint256[] calldata itemIds,
uint256[] calldata amounts
) external{
uint256 length = itemIds.length;
if (length ==0) {
revert LengthsInvalid();
}
_isOperatorValidForTransfer(from, msg.sender);
for (uint256 i; i < length; ) {
if (amounts[i] !=1) {
revert AmountInvalid();
}
_executeERC721TransferFrom(collection, from, to, itemIds[i]);
unchecked {
++i;
}
}
}
/**
* @notice This function transfers items for a single ERC1155 collection.
* @param collection Collection address
* @param from Sender address
* @param to Recipient address
* @param itemIds Array of itemIds
* @param amounts Array of amounts
* @dev It does not allow batch transferring if from = msg.sender since native function should be used.
*/functiontransferItemsERC1155(address collection,
addressfrom,
address to,
uint256[] calldata itemIds,
uint256[] calldata amounts
) external{
uint256 length = itemIds.length;
if (length ==0|| amounts.length!= length) {
revert LengthsInvalid();
}
_isOperatorValidForTransfer(from, msg.sender);
if (length ==1) {
if (amounts[0] ==0) {
revert AmountInvalid();
}
_executeERC1155SafeTransferFrom(collection, from, to, itemIds[0], amounts[0]);
} else {
for (uint256 i; i < length; ) {
if (amounts[i] ==0) {
revert AmountInvalid();
}
unchecked {
++i;
}
}
_executeERC1155SafeBatchTransferFrom(collection, from, to, itemIds, amounts);
}
}
/**
* @notice This function transfers items across an array of collections that can be both ERC721 and ERC1155.
* @param items Array of BatchTransferItem
* @param from Sender address
* @param to Recipient address
*/functiontransferBatchItemsAcrossCollections(
BatchTransferItem[] calldata items,
addressfrom,
address to
) external{
uint256 itemsLength = items.length;
if (itemsLength ==0) {
revert LengthsInvalid();
}
if (from!=msg.sender) {
_isOperatorValidForTransfer(from, msg.sender);
}
for (uint256 i; i < itemsLength; ) {
uint256[] calldata itemIds = items[i].itemIds;
uint256 itemIdsLengthForSingleCollection = itemIds.length;
uint256[] calldata amounts = items[i].amounts;
if (itemIdsLengthForSingleCollection ==0|| amounts.length!= itemIdsLengthForSingleCollection) {
revert LengthsInvalid();
}
CollectionType collectionType = items[i].collectionType;
if (collectionType == CollectionType.ERC721) {
for (uint256 j; j < itemIdsLengthForSingleCollection; ) {
if (amounts[j] !=1) {
revert AmountInvalid();
}
_executeERC721TransferFrom(items[i].collection, from, to, itemIds[j]);
unchecked {
++j;
}
}
} elseif (collectionType == CollectionType.ERC1155) {
for (uint256 j; j < itemIdsLengthForSingleCollection; ) {
if (amounts[j] ==0) {
revert AmountInvalid();
}
unchecked {
++j;
}
}
_executeERC1155SafeBatchTransferFrom(items[i].collection, from, to, itemIds, amounts);
}
unchecked {
++i;
}
}
}
/**
* @notice This function allows a user to grant approvals for an array of operators.
* Users cannot grant approvals if the operator is not allowed by this contract's owner.
* @param operators Array of operator addresses
* @dev Each operator address must be globally allowed to be approved.
*/functiongrantApprovals(address[] calldata operators) external{
uint256 length = operators.length;
if (length ==0) {
revert LengthsInvalid();
}
for (uint256 i; i < length; ) {
address operator = operators[i];
if (!isOperatorAllowed[operator]) {
revert OperatorNotAllowed();
}
if (hasUserApprovedOperator[msg.sender][operator]) {
revert OperatorAlreadyApprovedByUser();
}
hasUserApprovedOperator[msg.sender][operator] =true;
unchecked {
++i;
}
}
emit ApprovalsGranted(msg.sender, operators);
}
/**
* @notice This function allows a user to revoke existing approvals for an array of operators.
* @param operators Array of operator addresses
* @dev Each operator address must be approved at the user level to be revoked.
*/functionrevokeApprovals(address[] calldata operators) external{
uint256 length = operators.length;
if (length ==0) {
revert LengthsInvalid();
}
for (uint256 i; i < length; ) {
address operator = operators[i];
if (!hasUserApprovedOperator[msg.sender][operator]) {
revert OperatorNotApprovedByUser();
}
delete hasUserApprovedOperator[msg.sender][operator];
unchecked {
++i;
}
}
emit ApprovalsRemoved(msg.sender, operators);
}
/**
* @notice This function allows an operator to be added for the shared transfer system.
* Once the operator is allowed, users can grant NFT approvals to this operator.
* @param operator Operator address to allow
* @dev Only callable by owner.
*/functionallowOperator(address operator) externalonlyOwner{
if (isOperatorAllowed[operator]) {
revert OperatorAlreadyAllowed();
}
isOperatorAllowed[operator] =true;
emit OperatorAllowed(operator);
}
/**
* @notice This function allows the user to remove an operator for the shared transfer system.
* @param operator Operator address to remove
* @dev Only callable by owner.
*/functionremoveOperator(address operator) externalonlyOwner{
if (!isOperatorAllowed[operator]) {
revert OperatorNotAllowed();
}
delete isOperatorAllowed[operator];
emit OperatorRemoved(operator);
}
/**
* @notice This function is internal and verifies whether the transfer
* (by an operator on behalf of a user) is valid. If not, it reverts.
* @param user User address
* @param operator Operator address
*/function_isOperatorValidForTransfer(address user, address operator) privateview{
if (isOperatorAllowed[operator] && hasUserApprovedOperator[user][operator]) {
return;
}
revert TransferCallerInvalid();
}
}