Source Code
Latest 5 from a total of 5 transactions
| Transaction Hash |
Method
|
Block
|
From
|
|
To
|
||||
|---|---|---|---|---|---|---|---|---|---|
| Accept ETH Listi... | 20774683 | 433 days ago | IN | 0.199 ETH | 0.00270413 | ||||
| Accept ETH Listi... | 20774675 | 433 days ago | IN | 0.199 ETH | 0.00280709 | ||||
| Accept ETH Listi... | 20442937 | 480 days ago | IN | 0.09 ETH | 0.00065599 | ||||
| Accept ETH Listi... | 20237073 | 508 days ago | IN | 0.0149 ETH | 0.00228366 | ||||
| Accept ETH Listi... | 19922030 | 552 days ago | IN | 0.1 ETH | 0.00166172 |
Latest 25 internal transactions (View All)
Advanced mode:
| Parent Transaction Hash | Method | Block |
From
|
|
To
|
||
|---|---|---|---|---|---|---|---|
| Fulfill Advanced... | 23879351 | 14 mins ago | 0.02646 ETH | ||||
| Fulfill Advanced... | 23879351 | 14 mins ago | 0.02646 ETH | ||||
| Fulfill Advanced... | 23879351 | 14 mins ago | 0.02646 ETH | ||||
| Accept ETH Listi... | 23879351 | 14 mins ago | 0.07938 ETH | ||||
| Fulfill Advanced... | 23879301 | 24 mins ago | 0.02646 ETH | ||||
| Fulfill Advanced... | 23879301 | 24 mins ago | 0.02646 ETH | ||||
| Fulfill Advanced... | 23879301 | 24 mins ago | 0.02646 ETH | ||||
| Accept ETH Listi... | 23879301 | 24 mins ago | 0.07938 ETH | ||||
| Fulfill Advanced... | 23879254 | 34 mins ago | 0.02648 ETH | ||||
| Fulfill Advanced... | 23879254 | 34 mins ago | 0.02648 ETH | ||||
| Fulfill Advanced... | 23879254 | 34 mins ago | 0.02648 ETH | ||||
| Accept ETH Listi... | 23879254 | 34 mins ago | 0.07944 ETH | ||||
| Fulfill Advanced... | 23879230 | 38 mins ago | 0.02647 ETH | ||||
| Fulfill Advanced... | 23879230 | 38 mins ago | 0.02647 ETH | ||||
| Fulfill Advanced... | 23879230 | 38 mins ago | 0.02647 ETH | ||||
| Accept ETH Listi... | 23879230 | 38 mins ago | 0.07941 ETH | ||||
| Fulfill Advanced... | 23879150 | 55 mins ago | 0.02647 ETH | ||||
| Fulfill Advanced... | 23879150 | 55 mins ago | 0.02647 ETH | ||||
| Accept ETH Listi... | 23879150 | 55 mins ago | 0.05294 ETH | ||||
| Fulfill Advanced... | 23879091 | 1 hr ago | 0.1318 ETH | ||||
| Fulfill Advanced... | 23879091 | 1 hr ago | 0.1317 ETH | ||||
| Accept ETH Listi... | 23879091 | 1 hr ago | 0.2635 ETH | ||||
| Fulfill Advanced... | 23879079 | 1 hr ago | 0.1318 ETH | ||||
| Fulfill Advanced... | 23879079 | 1 hr ago | 0.1318 ETH | ||||
| Accept ETH Listi... | 23879079 | 1 hr ago | 0.2636 ETH |
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Name:
SeaportV16Module
Compiler Version
v0.8.17+commit.8df45f5f
Optimization Enabled:
Yes with 200 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {ISeaport} from "../../../interfaces/ISeaport.sol";
// Notes on the Seaport module:
// - supports filling listings (both ERC721/ERC1155)
// - supports filling offers (both ERC721/ERC1155)
contract SeaportV16Module is BaseExchangeModule {
// --- Structs ---
struct SeaportETHListingWithPrice {
ISeaport.AdvancedOrder order;
uint256 price;
}
struct SeaportPrivateListingWithPrice {
ISeaport.AdvancedOrder[] orders;
ISeaport.Fulfillment[] fulfillments;
uint256 price;
}
// --- Fields ---
ISeaport public immutable EXCHANGE;
// --- Constructor ---
constructor(
address owner,
address router,
address exchange
) BaseModule(owner) BaseExchangeModule(router) {
EXCHANGE = ISeaport(exchange);
}
// --- Fallback ---
receive() external payable {}
// --- Single ETH listing ---
function acceptETHListing(
ISeaport.AdvancedOrder calldata order,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(
order,
new ISeaport.CriteriaResolver[](0),
params.fillTo,
params.amount
)
: _fillSingleOrder(order, new ISeaport.CriteriaResolver[](0), params.fillTo, params.amount);
}
// --- Single ERC20 listing ---
function acceptERC20Listing(
ISeaport.AdvancedOrder calldata order,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(
order,
new ISeaport.CriteriaResolver[](0),
params.fillTo,
0
)
: _fillSingleOrder(order, new ISeaport.CriteriaResolver[](0), params.fillTo, 0);
}
// --- Multiple ETH listings ---
function acceptETHListings(
SeaportETHListingWithPrice[] calldata orders,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256 length = orders.length;
ISeaport.CriteriaResolver[] memory criteriaResolvers = new ISeaport.CriteriaResolver[](0);
// Execute the fills
if (params.revertIfIncomplete) {
for (uint256 i; i < length; ) {
_fillSingleOrderWithRevertIfIncomplete(
orders[i].order,
criteriaResolvers,
params.fillTo,
orders[i].price
);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < length; ) {
_fillSingleOrder(orders[i].order, criteriaResolvers, params.fillTo, orders[i].price);
unchecked {
++i;
}
}
}
}
// --- Multiple ERC20 listings ---
function acceptERC20Listings(
ISeaport.AdvancedOrder[] calldata orders,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
uint256 length = orders.length;
ISeaport.CriteriaResolver[] memory criteriaResolvers = new ISeaport.CriteriaResolver[](0);
// Execute the fills
if (params.revertIfIncomplete) {
for (uint256 i; i < length; ) {
_fillSingleOrderWithRevertIfIncomplete(orders[i], criteriaResolvers, params.fillTo, 0);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < length; ) {
_fillSingleOrder(orders[i], criteriaResolvers, params.fillTo, 0);
unchecked {
++i;
}
}
}
}
// --- Multiple ETH private listings ---
function acceptETHPrivateListings(
SeaportPrivateListingWithPrice[] calldata orders,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256 length = orders.length;
ISeaport.CriteriaResolver[] memory criteriaResolvers = new ISeaport.CriteriaResolver[](0);
// Execute the fills
if (params.revertIfIncomplete) {
for (uint256 i; i < length; ) {
_fillPrivateOrderWithRevertIfIncomplete(
orders[i].orders,
criteriaResolvers,
orders[i].fulfillments,
params.fillTo,
orders[i].price
);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < length; ) {
_fillPrivateOrder(
orders[i].orders,
criteriaResolvers,
orders[i].fulfillments,
params.fillTo,
orders[i].price
);
unchecked {
++i;
}
}
}
}
// --- Multiple Private ERC20 Private listings ---
function acceptERC20PrivateListings(
SeaportPrivateListingWithPrice[] calldata orders,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
uint256 length = orders.length;
ISeaport.CriteriaResolver[] memory criteriaResolvers = new ISeaport.CriteriaResolver[](0);
// Execute the fills
if (params.revertIfIncomplete) {
for (uint256 i; i < length; ) {
_fillPrivateOrderWithRevertIfIncomplete(
orders[i].orders,
criteriaResolvers,
orders[i].fulfillments,
params.fillTo,
0
);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < length; ) {
_fillPrivateOrder(
orders[i].orders,
criteriaResolvers,
orders[i].fulfillments,
params.fillTo,
0
);
unchecked {
++i;
}
}
}
}
// --- Single ERC721 offer ---
function acceptERC721Offer(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
// Extract the ERC721 token from the consideration items
ISeaport.ConsiderationItem calldata nftItem = order.parameters.consideration[0];
if (
nftItem.itemType != ISeaport.ItemType.ERC721 &&
nftItem.itemType != ISeaport.ItemType.ERC721_WITH_CRITERIA
) {
revert WrongParams();
}
IERC721 nftToken = IERC721(nftItem.token);
// Extract the payment token from the offer items
ISeaport.OfferItem calldata paymentItem = order.parameters.offer[0];
IERC20 paymentToken = IERC20(paymentItem.token);
// Approve the exchange if needed
_approveERC721IfNeeded(nftToken, address(EXCHANGE));
_approveERC20IfNeeded(paymentToken, address(EXCHANGE), type(uint256).max);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(order, criteriaResolvers, address(this), 0)
: _fillSingleOrder(order, criteriaResolvers, address(this), 0);
uint256 identifier = nftItem.itemType == ISeaport.ItemType.ERC721
? nftItem.identifierOrCriteria
: criteriaResolvers[0].identifier;
// Pay fees
if (nftToken.ownerOf(identifier) != address(this)) {
// Only pay fees if the fill was successful
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, paymentToken);
unchecked {
++i;
}
}
}
// Refund any ERC721 leftover
_sendAllERC721(params.refundTo, nftToken, identifier);
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, paymentToken);
}
// --- Single ERC1155 offer ---
function acceptERC1155Offer(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
// Extract the ERC1155 token from the consideration items
ISeaport.ConsiderationItem calldata nftItem = order.parameters.consideration[0];
if (
nftItem.itemType != ISeaport.ItemType.ERC1155 &&
nftItem.itemType != ISeaport.ItemType.ERC1155_WITH_CRITERIA
) {
revert WrongParams();
}
IERC1155 nftToken = IERC1155(nftItem.token);
// Extract the payment token from the offer items
ISeaport.OfferItem calldata paymentItem = order.parameters.offer[0];
IERC20 paymentToken = IERC20(paymentItem.token);
// Approve the exchange if needed
_approveERC1155IfNeeded(nftToken, address(EXCHANGE));
_approveERC20IfNeeded(paymentToken, address(EXCHANGE), type(uint256).max);
uint256 identifier = nftItem.itemType == ISeaport.ItemType.ERC1155
? nftItem.identifierOrCriteria
: criteriaResolvers[0].identifier;
uint256 balanceBefore = nftToken.balanceOf(address(this), identifier);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(order, criteriaResolvers, address(this), 0)
: _fillSingleOrder(order, criteriaResolvers, address(this), 0);
uint256 balanceAfter = nftToken.balanceOf(address(this), identifier);
// Pay fees
uint256 amountFilled = balanceBefore - balanceAfter;
if (amountFilled > 0) {
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(
fee.recipient,
// Only pay fees for the amount that was actually filled
(fee.amount * amountFilled) / order.numerator,
paymentToken
);
unchecked {
++i;
}
}
}
// Refund any ERC1155 leftover
_sendAllERC1155(params.refundTo, nftToken, identifier);
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, paymentToken);
}
// --- Generic handler (used for Seaport-based approvals) ---
function matchOrders(
ISeaport.Order[] calldata orders,
ISeaport.Fulfillment[] calldata fulfillments
) external nonReentrant {
// We don't perform any kind of input or return value validation,
// so this function should be used with precaution - the official
// way to use it is only for Seaport-based approvals
EXCHANGE.matchOrders(orders, fulfillments);
}
// --- ERC1271 ---
function isValidSignature(bytes32, bytes memory) external pure returns (bytes4) {
// Needed for filling private listings
return this.isValidSignature.selector;
}
// --- ERC721 / ERC1155 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
// --- Internal ---
// NOTE: In lots of cases, Seaport will not revert if fills were not
// fully executed. An example of that is partial filling, which will
// successfully fill any amount that is still available (including a
// zero amount). One way to ensure that we revert in case of partial
// executions is to check the order's filled amount before and after
// we trigger the fill (we can use Seaport's `getOrderStatus` method
// to check). Since this can be expensive in terms of gas, we have a
// separate method variant to be called when reverts are enabled.
function _fillSingleOrder(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
address receiver,
uint256 value
) internal {
// Execute the fill
try
EXCHANGE.fulfillAdvancedOrder{value: value}(order, criteriaResolvers, bytes32(0), receiver)
{} catch {}
}
function _fillSingleOrderWithRevertIfIncomplete(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
address receiver,
uint256 value
) internal {
// Cache the order's hash
bytes32 orderHash = _getOrderHash(order.parameters);
// Before filling, get the order's filled amount
uint256 beforeFilledAmount = _getFilledAmount(orderHash, order.denominator);
// Execute the fill
bool success;
try
EXCHANGE.fulfillAdvancedOrder{value: value}(order, criteriaResolvers, bytes32(0), receiver)
returns (bool fulfilled) {
success = fulfilled;
} catch {
revert UnsuccessfulFill();
}
if (!success) {
revert UnsuccessfulFill();
} else {
// After successfully filling, get the order's filled amount
uint256 afterFilledAmount = _getFilledAmount(orderHash, order.denominator);
// Make sure the amount filled as part of this call is correct
if (afterFilledAmount - beforeFilledAmount != order.numerator) {
revert UnsuccessfulFill();
}
}
}
function _fillPrivateOrder(
ISeaport.AdvancedOrder[] calldata advancedOrders,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
ISeaport.Fulfillment[] memory fulfillments,
address receiver,
uint256 value
) internal {
// Execute the fill
try
EXCHANGE.matchAdvancedOrders{value: value}(
advancedOrders,
criteriaResolvers,
fulfillments,
receiver
)
{} catch {}
}
function _fillPrivateOrderWithRevertIfIncomplete(
ISeaport.AdvancedOrder[] calldata advancedOrders,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
ISeaport.Fulfillment[] memory fulfillments,
address receiver,
uint256 value
) internal {
// Execute the fill
try
EXCHANGE.matchAdvancedOrders{value: value}(
advancedOrders,
criteriaResolvers,
fulfillments,
receiver
)
{} catch {
revert UnsuccessfulFill();
}
}
function _getOrderHash(
// Must use `memory` instead of `calldata` for the below cast
ISeaport.OrderParameters memory orderParameters
) internal view returns (bytes32 orderHash) {
// `OrderParameters` and `OrderComponents` share the exact same
// fields, apart from the last one, so here we simply treat the
// `orderParameters` argument as `OrderComponents` and then set
// the last field to the correct data
ISeaport.OrderComponents memory orderComponents;
assembly {
orderComponents := orderParameters
}
orderComponents.counter = EXCHANGE.getCounter(orderParameters.offerer);
orderHash = EXCHANGE.getOrderHash(orderComponents);
}
function _getFilledAmount(
bytes32 orderHash,
uint256 adjustedTotalSize
) internal view returns (uint256 adjustedTotalFilled) {
(, , uint256 totalFilled, uint256 totalSize) = EXCHANGE.getOrderStatus(orderHash);
adjustedTotalFilled = totalSize > 0 ? (totalFilled * adjustedTotalSize) / totalSize : 0;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./OwnablePermissions.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
abstract contract OwnableBasic is OwnablePermissions, Ownable {
function _requireCallerIsContractOwner() internal view virtual override {
_checkOwner();
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/utils/Context.sol";
abstract contract OwnablePermissions is Context {
function _requireCallerIsContractOwner() internal view virtual;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "../utils/CreatorTokenBase.sol";
import "../token/erc721/ERC721OpenZeppelin.sol";
/**
* @title ERC721C
* @author Limit Break, Inc.
* @notice Extends OpenZeppelin's ERC721 implementation with Creator Token functionality, which
* allows the contract owner to update the transfer validation logic by managing a security policy in
* an external transfer validation security policy registry. See {CreatorTokenTransferValidator}.
*/
abstract contract ERC721C is ERC721OpenZeppelin, CreatorTokenBase {
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(ICreatorToken).interfaceId || super.supportsInterface(interfaceId);
}
/// @dev Ties the open-zeppelin _beforeTokenTransfer hook to more granular transfer validation logic
function _beforeTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize) internal virtual override {
for (uint256 i = 0; i < batchSize;) {
_validateBeforeTransfer(from, to, firstTokenId + i);
unchecked {
++i;
}
}
}
/// @dev Ties the open-zeppelin _afterTokenTransfer hook to more granular transfer validation logic
function _afterTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize) internal virtual override {
for (uint256 i = 0; i < batchSize;) {
_validateAfterTransfer(from, to, firstTokenId + i);
unchecked {
++i;
}
}
}
}
/**
* @title ERC721CInitializable
* @author Limit Break, Inc.
* @notice Initializable implementation of ERC721C to allow for EIP-1167 proxy clones.
*/
abstract contract ERC721CInitializable is ERC721OpenZeppelinInitializable, CreatorTokenBase {
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(ICreatorToken).interfaceId || super.supportsInterface(interfaceId);
}
/// @dev Ties the open-zeppelin _beforeTokenTransfer hook to more granular transfer validation logic
function _beforeTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize) internal virtual override {
for (uint256 i = 0; i < batchSize;) {
_validateBeforeTransfer(from, to, firstTokenId + i);
unchecked {
++i;
}
}
}
/// @dev Ties the open-zeppelin _afterTokenTransfer hook to more granular transfer validation logic
function _afterTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize) internal virtual override {
for (uint256 i = 0; i < batchSize;) {
_validateAfterTransfer(from, to, firstTokenId + i);
unchecked {
++i;
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "../interfaces/ICreatorTokenTransferValidator.sol";
interface ICreatorToken {
event TransferValidatorUpdated(address oldValidator, address newValidator);
function getTransferValidator() external view returns (ICreatorTokenTransferValidator);
function getSecurityPolicy() external view returns (CollectionSecurityPolicy memory);
function getWhitelistedOperators() external view returns (address[] memory);
function getPermittedContractReceivers() external view returns (address[] memory);
function isOperatorWhitelisted(address operator) external view returns (bool);
function isContractReceiverPermitted(address receiver) external view returns (bool);
function isTransferAllowed(address caller, address from, address to) external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./IEOARegistry.sol";
import "./ITransferSecurityRegistry.sol";
import "./ITransferValidator.sol";
interface ICreatorTokenTransferValidator is ITransferSecurityRegistry, ITransferValidator, IEOARegistry {}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
interface IEOARegistry is IERC165 {
function isVerifiedEOA(address account) external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "../utils/TransferPolicy.sol";
interface ITransferSecurityRegistry {
event AddedToAllowlist(AllowlistTypes indexed kind, uint256 indexed id, address indexed account);
event CreatedAllowlist(AllowlistTypes indexed kind, uint256 indexed id, string indexed name);
event ReassignedAllowlistOwnership(AllowlistTypes indexed kind, uint256 indexed id, address indexed newOwner);
event RemovedFromAllowlist(AllowlistTypes indexed kind, uint256 indexed id, address indexed account);
event SetAllowlist(AllowlistTypes indexed kind, address indexed collection, uint120 indexed id);
event SetTransferSecurityLevel(address indexed collection, TransferSecurityLevels level);
function createOperatorWhitelist(string calldata name) external returns (uint120);
function createPermittedContractReceiverAllowlist(string calldata name) external returns (uint120);
function reassignOwnershipOfOperatorWhitelist(uint120 id, address newOwner) external;
function reassignOwnershipOfPermittedContractReceiverAllowlist(uint120 id, address newOwner) external;
function renounceOwnershipOfOperatorWhitelist(uint120 id) external;
function renounceOwnershipOfPermittedContractReceiverAllowlist(uint120 id) external;
function setTransferSecurityLevelOfCollection(address collection, TransferSecurityLevels level) external;
function setOperatorWhitelistOfCollection(address collection, uint120 id) external;
function setPermittedContractReceiverAllowlistOfCollection(address collection, uint120 id) external;
function addOperatorToWhitelist(uint120 id, address operator) external;
function addPermittedContractReceiverToAllowlist(uint120 id, address receiver) external;
function removeOperatorFromWhitelist(uint120 id, address operator) external;
function removePermittedContractReceiverFromAllowlist(uint120 id, address receiver) external;
function getCollectionSecurityPolicy(address collection) external view returns (CollectionSecurityPolicy memory);
function getWhitelistedOperators(uint120 id) external view returns (address[] memory);
function getPermittedContractReceivers(uint120 id) external view returns (address[] memory);
function isOperatorWhitelisted(uint120 id, address operator) external view returns (bool);
function isContractReceiverPermitted(uint120 id, address receiver) external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "../utils/TransferPolicy.sol";
interface ITransferValidator {
function applyCollectionTransferPolicy(address caller, address from, address to) external view;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "../../access/OwnablePermissions.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
abstract contract ERC721OpenZeppelinBase is ERC721 {
// Token name
string internal _contractName;
// Token symbol
string internal _contractSymbol;
function name() public view virtual override returns (string memory) {
return _contractName;
}
function symbol() public view virtual override returns (string memory) {
return _contractSymbol;
}
function _setNameAndSymbol(string memory name_, string memory symbol_) internal {
_contractName = name_;
_contractSymbol = symbol_;
}
}
abstract contract ERC721OpenZeppelin is ERC721OpenZeppelinBase {
constructor(string memory name_, string memory symbol_) ERC721("", "") {
_setNameAndSymbol(name_, symbol_);
}
}
abstract contract ERC721OpenZeppelinInitializable is OwnablePermissions, ERC721OpenZeppelinBase {
error ERC721OpenZeppelinInitializable__AlreadyInitializedERC721();
/// @notice Specifies whether or not the contract is initialized
bool private _erc721Initialized;
/// @dev Initializes parameters of ERC721 tokens.
/// These cannot be set in the constructor because this contract is optionally compatible with EIP-1167.
function initializeERC721(string memory name_, string memory symbol_) public {
_requireCallerIsContractOwner();
if(_erc721Initialized) {
revert ERC721OpenZeppelinInitializable__AlreadyInitializedERC721();
}
_erc721Initialized = true;
_setNameAndSymbol(name_, symbol_);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "../access/OwnablePermissions.sol";
import "../interfaces/ICreatorToken.sol";
import "../interfaces/ICreatorTokenTransferValidator.sol";
import "../utils/TransferValidation.sol";
import "@openzeppelin/contracts/interfaces/IERC165.sol";
/**
* @title CreatorTokenBase
* @author Limit Break, Inc.
* @notice CreatorTokenBase is an abstract contract that provides basic functionality for managing token
* transfer policies through an implementation of ICreatorTokenTransferValidator. This contract is intended to be used
* as a base for creator-specific token contracts, enabling customizable transfer restrictions and security policies.
*
* <h4>Features:</h4>
* <ul>Ownable: This contract can have an owner who can set and update the transfer validator.</ul>
* <ul>TransferValidation: Implements the basic token transfer validation interface.</ul>
* <ul>ICreatorToken: Implements the interface for creator tokens, providing view functions for token security policies.</ul>
*
* <h4>Benefits:</h4>
* <ul>Provides a flexible and modular way to implement custom token transfer restrictions and security policies.</ul>
* <ul>Allows creators to enforce policies such as whitelisted operators and permitted contract receivers.</ul>
* <ul>Can be easily integrated into other token contracts as a base contract.</ul>
*
* <h4>Intended Usage:</h4>
* <ul>Use as a base contract for creator token implementations that require advanced transfer restrictions and
* security policies.</ul>
* <ul>Set and update the ICreatorTokenTransferValidator implementation contract to enforce desired policies for the
* creator token.</ul>
*/
abstract contract CreatorTokenBase is OwnablePermissions, TransferValidation, ICreatorToken {
error CreatorTokenBase__InvalidTransferValidatorContract();
error CreatorTokenBase__SetTransferValidatorFirst();
address public constant DEFAULT_TRANSFER_VALIDATOR = address(0x0000721C310194CcfC01E523fc93C9cCcFa2A0Ac);
TransferSecurityLevels public constant DEFAULT_TRANSFER_SECURITY_LEVEL = TransferSecurityLevels.One;
uint120 public constant DEFAULT_OPERATOR_WHITELIST_ID = uint120(1);
ICreatorTokenTransferValidator private transferValidator;
/**
* @notice Allows the contract owner to set the transfer validator to the official validator contract
* and set the security policy to the recommended default settings.
* @dev May be overridden to change the default behavior of an individual collection.
*/
function setToDefaultSecurityPolicy() public virtual {
_requireCallerIsContractOwner();
setTransferValidator(DEFAULT_TRANSFER_VALIDATOR);
ICreatorTokenTransferValidator(DEFAULT_TRANSFER_VALIDATOR).setTransferSecurityLevelOfCollection(address(this), DEFAULT_TRANSFER_SECURITY_LEVEL);
ICreatorTokenTransferValidator(DEFAULT_TRANSFER_VALIDATOR).setOperatorWhitelistOfCollection(address(this), DEFAULT_OPERATOR_WHITELIST_ID);
}
/**
* @notice Allows the contract owner to set the transfer validator to a custom validator contract
* and set the security policy to their own custom settings.
*/
function setToCustomValidatorAndSecurityPolicy(
address validator,
TransferSecurityLevels level,
uint120 operatorWhitelistId,
uint120 permittedContractReceiversAllowlistId) public {
_requireCallerIsContractOwner();
setTransferValidator(validator);
ICreatorTokenTransferValidator(validator).
setTransferSecurityLevelOfCollection(address(this), level);
ICreatorTokenTransferValidator(validator).
setOperatorWhitelistOfCollection(address(this), operatorWhitelistId);
ICreatorTokenTransferValidator(validator).
setPermittedContractReceiverAllowlistOfCollection(address(this), permittedContractReceiversAllowlistId);
}
/**
* @notice Allows the contract owner to set the security policy to their own custom settings.
* @dev Reverts if the transfer validator has not been set.
*/
function setToCustomSecurityPolicy(
TransferSecurityLevels level,
uint120 operatorWhitelistId,
uint120 permittedContractReceiversAllowlistId) public {
_requireCallerIsContractOwner();
ICreatorTokenTransferValidator validator = getTransferValidator();
if (address(validator) == address(0)) {
revert CreatorTokenBase__SetTransferValidatorFirst();
}
validator.setTransferSecurityLevelOfCollection(address(this), level);
validator.setOperatorWhitelistOfCollection(address(this), operatorWhitelistId);
validator.setPermittedContractReceiverAllowlistOfCollection(address(this), permittedContractReceiversAllowlistId);
}
/**
* @notice Sets the transfer validator for the token contract.
*
* @dev Throws when provided validator contract is not the zero address and doesn't support
* the ICreatorTokenTransferValidator interface.
* @dev Throws when the caller is not the contract owner.
*
* @dev <h4>Postconditions:</h4>
* 1. The transferValidator address is updated.
* 2. The `TransferValidatorUpdated` event is emitted.
*
* @param transferValidator_ The address of the transfer validator contract.
*/
function setTransferValidator(address transferValidator_) public {
_requireCallerIsContractOwner();
bool isValidTransferValidator = false;
if(transferValidator_.code.length > 0) {
try IERC165(transferValidator_).supportsInterface(type(ICreatorTokenTransferValidator).interfaceId)
returns (bool supportsInterface) {
isValidTransferValidator = supportsInterface;
} catch {}
}
if(transferValidator_ != address(0) && !isValidTransferValidator) {
revert CreatorTokenBase__InvalidTransferValidatorContract();
}
emit TransferValidatorUpdated(address(transferValidator), transferValidator_);
transferValidator = ICreatorTokenTransferValidator(transferValidator_);
}
/**
* @notice Returns the transfer validator contract address for this token contract.
*/
function getTransferValidator() public view override returns (ICreatorTokenTransferValidator) {
return transferValidator;
}
/**
* @notice Returns the security policy for this token contract, which includes:
* Transfer security level, operator whitelist id, permitted contract receiver allowlist id.
*/
function getSecurityPolicy() public view override returns (CollectionSecurityPolicy memory) {
if (address(transferValidator) != address(0)) {
return transferValidator.getCollectionSecurityPolicy(address(this));
}
return CollectionSecurityPolicy({
transferSecurityLevel: TransferSecurityLevels.Zero,
operatorWhitelistId: 0,
permittedContractReceiversId: 0
});
}
/**
* @notice Returns the list of all whitelisted operators for this token contract.
* @dev This can be an expensive call and should only be used in view-only functions.
*/
function getWhitelistedOperators() public view override returns (address[] memory) {
if (address(transferValidator) != address(0)) {
return transferValidator.getWhitelistedOperators(
transferValidator.getCollectionSecurityPolicy(address(this)).operatorWhitelistId);
}
return new address[](0);
}
/**
* @notice Returns the list of permitted contract receivers for this token contract.
* @dev This can be an expensive call and should only be used in view-only functions.
*/
function getPermittedContractReceivers() public view override returns (address[] memory) {
if (address(transferValidator) != address(0)) {
return transferValidator.getPermittedContractReceivers(
transferValidator.getCollectionSecurityPolicy(address(this)).permittedContractReceiversId);
}
return new address[](0);
}
/**
* @notice Checks if an operator is whitelisted for this token contract.
* @param operator The address of the operator to check.
*/
function isOperatorWhitelisted(address operator) public view override returns (bool) {
if (address(transferValidator) != address(0)) {
return transferValidator.isOperatorWhitelisted(
transferValidator.getCollectionSecurityPolicy(address(this)).operatorWhitelistId, operator);
}
return false;
}
/**
* @notice Checks if a contract receiver is permitted for this token contract.
* @param receiver The address of the receiver to check.
*/
function isContractReceiverPermitted(address receiver) public view override returns (bool) {
if (address(transferValidator) != address(0)) {
return transferValidator.isContractReceiverPermitted(
transferValidator.getCollectionSecurityPolicy(address(this)).permittedContractReceiversId, receiver);
}
return false;
}
/**
* @notice Determines if a transfer is allowed based on the token contract's security policy. Use this function
* to simulate whether or not a transfer made by the specified `caller` from the `from` address to the `to`
* address would be allowed by this token's security policy.
*
* @notice This function only checks the security policy restrictions and does not check whether token ownership
* or approvals are in place.
*
* @param caller The address of the simulated caller.
* @param from The address of the sender.
* @param to The address of the receiver.
* @return True if the transfer is allowed, false otherwise.
*/
function isTransferAllowed(address caller, address from, address to) public view override returns (bool) {
if (address(transferValidator) != address(0)) {
try transferValidator.applyCollectionTransferPolicy(caller, from, to) {
return true;
} catch {
return false;
}
}
return true;
}
/**
* @dev Pre-validates a token transfer, reverting if the transfer is not allowed by this token's security policy.
* Inheriting contracts are responsible for overriding the _beforeTokenTransfer function, or its equivalent
* and calling _validateBeforeTransfer so that checks can be properly applied during token transfers.
*
* @dev Throws when the transfer doesn't comply with the collection's transfer policy, if the transferValidator is
* set to a non-zero address.
*
* @param caller The address of the caller.
* @param from The address of the sender.
* @param to The address of the receiver.
*/
function _preValidateTransfer(
address caller,
address from,
address to,
uint256 /*tokenId*/,
uint256 /*value*/) internal virtual override {
if (address(transferValidator) != address(0)) {
transferValidator.applyCollectionTransferPolicy(caller, from, to);
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
enum AllowlistTypes {
Operators,
PermittedContractReceivers
}
enum ReceiverConstraints {
None,
NoCode,
EOA
}
enum CallerConstraints {
None,
OperatorWhitelistEnableOTC,
OperatorWhitelistDisableOTC
}
enum StakerConstraints {
None,
CallerIsTxOrigin,
EOA
}
enum TransferSecurityLevels {
Zero,
One,
Two,
Three,
Four,
Five,
Six
}
struct TransferSecurityPolicy {
CallerConstraints callerConstraints;
ReceiverConstraints receiverConstraints;
}
struct CollectionSecurityPolicy {
TransferSecurityLevels transferSecurityLevel;
uint120 operatorWhitelistId;
uint120 permittedContractReceiversId;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/utils/Context.sol";
/**
* @title TransferValidation
* @author Limit Break, Inc.
* @notice A mix-in that can be combined with ERC-721 contracts to provide more granular hooks.
* Openzeppelin's ERC721 contract only provides hooks for before and after transfer. This allows
* developers to validate or customize transfers within the context of a mint, a burn, or a transfer.
*/
abstract contract TransferValidation is Context {
error ShouldNotMintToBurnAddress();
/// @dev Inheriting contracts should call this function in the _beforeTokenTransfer function to get more granular hooks.
function _validateBeforeTransfer(address from, address to, uint256 tokenId) internal virtual {
bool fromZeroAddress = from == address(0);
bool toZeroAddress = to == address(0);
if(fromZeroAddress && toZeroAddress) {
revert ShouldNotMintToBurnAddress();
} else if(fromZeroAddress) {
_preValidateMint(_msgSender(), to, tokenId, msg.value);
} else if(toZeroAddress) {
_preValidateBurn(_msgSender(), from, tokenId, msg.value);
} else {
_preValidateTransfer(_msgSender(), from, to, tokenId, msg.value);
}
}
/// @dev Inheriting contracts should call this function in the _afterTokenTransfer function to get more granular hooks.
function _validateAfterTransfer(address from, address to, uint256 tokenId) internal virtual {
bool fromZeroAddress = from == address(0);
bool toZeroAddress = to == address(0);
if(fromZeroAddress && toZeroAddress) {
revert ShouldNotMintToBurnAddress();
} else if(fromZeroAddress) {
_postValidateMint(_msgSender(), to, tokenId, msg.value);
} else if(toZeroAddress) {
_postValidateBurn(_msgSender(), from, tokenId, msg.value);
} else {
_postValidateTransfer(_msgSender(), from, to, tokenId, msg.value);
}
}
/// @dev Optional validation hook that fires before a mint
function _preValidateMint(address caller, address to, uint256 tokenId, uint256 value) internal virtual {}
/// @dev Optional validation hook that fires after a mint
function _postValidateMint(address caller, address to, uint256 tokenId, uint256 value) internal virtual {}
/// @dev Optional validation hook that fires before a burn
function _preValidateBurn(address caller, address from, uint256 tokenId, uint256 value) internal virtual {}
/// @dev Optional validation hook that fires after a burn
function _postValidateBurn(address caller, address from, uint256 tokenId, uint256 value) internal virtual {}
/// @dev Optional validation hook that fires before a transfer
function _preValidateTransfer(address caller, address from, address to, uint256 tokenId, uint256 value) internal virtual {}
/// @dev Optional validation hook that fires after a transfer
function _postValidateTransfer(address caller, address from, address to, uint256 tokenId, uint256 value) internal virtual {}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC165.sol) pragma solidity ^0.8.0; import "../utils/introspection/IERC165.sol";
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/Clones.sol)
pragma solidity ^0.8.0;
/**
* @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*
* _Available since v3.4._
*/
library Clones {
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create opcode, which should never revert.
*/
function clone(address implementation) internal returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create(0, 0x09, 0x37)
}
require(instance != address(0), "ERC1167: create failed");
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple time will revert, since
* the clones cannot be deployed twice at the same address.
*/
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create2(0, 0x09, 0x37, salt)
}
require(instance != address(0), "ERC1167: create2 failed");
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(add(ptr, 0x38), deployer)
mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
mstore(add(ptr, 0x14), implementation)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
mstore(add(ptr, 0x58), salt)
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
predicted := keccak256(add(ptr, 0x43), 0x55)
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddress(implementation, salt, address(this));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/ERC1155.sol)
pragma solidity ^0.8.0;
import "./IERC1155.sol";
import "./IERC1155Receiver.sol";
import "./extensions/IERC1155MetadataURI.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/introspection/ERC165.sol";
/**
* @dev Implementation of the basic standard multi-token.
* See https://eips.ethereum.org/EIPS/eip-1155
* Originally based on code by Enjin: https://github.com/enjin/erc-1155
*
* _Available since v3.1._
*/
contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
using Address for address;
// Mapping from token ID to account balances
mapping(uint256 => mapping(address => uint256)) private _balances;
// Mapping from account to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
// Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
string private _uri;
/**
* @dev See {_setURI}.
*/
constructor(string memory uri_) {
_setURI(uri_);
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC1155).interfaceId ||
interfaceId == type(IERC1155MetadataURI).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC1155MetadataURI-uri}.
*
* This implementation returns the same URI for *all* token types. It relies
* on the token type ID substitution mechanism
* https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
*
* Clients calling this function must replace the `\{id\}` substring with the
* actual token type ID.
*/
function uri(uint256) public view virtual override returns (string memory) {
return _uri;
}
/**
* @dev See {IERC1155-balanceOf}.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
require(account != address(0), "ERC1155: address zero is not a valid owner");
return _balances[id][account];
}
/**
* @dev See {IERC1155-balanceOfBatch}.
*
* Requirements:
*
* - `accounts` and `ids` must have the same length.
*/
function balanceOfBatch(
address[] memory accounts,
uint256[] memory ids
) public view virtual override returns (uint256[] memory) {
require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");
uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; ++i) {
batchBalances[i] = balanceOf(accounts[i], ids[i]);
}
return batchBalances;
}
/**
* @dev See {IERC1155-setApprovalForAll}.
*/
function setApprovalForAll(address operator, bool approved) public virtual override {
_setApprovalForAll(_msgSender(), operator, approved);
}
/**
* @dev See {IERC1155-isApprovedForAll}.
*/
function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
return _operatorApprovals[account][operator];
}
/**
* @dev See {IERC1155-safeTransferFrom}.
*/
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) public virtual override {
require(
from == _msgSender() || isApprovedForAll(from, _msgSender()),
"ERC1155: caller is not token owner or approved"
);
_safeTransferFrom(from, to, id, amount, data);
}
/**
* @dev See {IERC1155-safeBatchTransferFrom}.
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) public virtual override {
require(
from == _msgSender() || isApprovedForAll(from, _msgSender()),
"ERC1155: caller is not token owner or approved"
);
_safeBatchTransferFrom(from, to, ids, amounts, data);
}
/**
* @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `from` must have a balance of tokens of type `id` of at least `amount`.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*/
function _safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual {
require(to != address(0), "ERC1155: transfer to the zero address");
address operator = _msgSender();
uint256[] memory ids = _asSingletonArray(id);
uint256[] memory amounts = _asSingletonArray(amount);
_beforeTokenTransfer(operator, from, to, ids, amounts, data);
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
unchecked {
_balances[id][from] = fromBalance - amount;
}
_balances[id][to] += amount;
emit TransferSingle(operator, from, to, id, amount);
_afterTokenTransfer(operator, from, to, ids, amounts, data);
_doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
*/
function _safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
require(to != address(0), "ERC1155: transfer to the zero address");
address operator = _msgSender();
_beforeTokenTransfer(operator, from, to, ids, amounts, data);
for (uint256 i = 0; i < ids.length; ++i) {
uint256 id = ids[i];
uint256 amount = amounts[i];
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
unchecked {
_balances[id][from] = fromBalance - amount;
}
_balances[id][to] += amount;
}
emit TransferBatch(operator, from, to, ids, amounts);
_afterTokenTransfer(operator, from, to, ids, amounts, data);
_doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
}
/**
* @dev Sets a new URI for all token types, by relying on the token type ID
* substitution mechanism
* https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
*
* By this mechanism, any occurrence of the `\{id\}` substring in either the
* URI or any of the amounts in the JSON file at said URI will be replaced by
* clients with the token type ID.
*
* For example, the `https://token-cdn-domain/\{id\}.json` URI would be
* interpreted by clients as
* `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
* for token type ID 0x4cce0.
*
* See {uri}.
*
* Because these URIs cannot be meaningfully represented by the {URI} event,
* this function emits no events.
*/
function _setURI(string memory newuri) internal virtual {
_uri = newuri;
}
/**
* @dev Creates `amount` tokens of token type `id`, and assigns them to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*/
function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual {
require(to != address(0), "ERC1155: mint to the zero address");
address operator = _msgSender();
uint256[] memory ids = _asSingletonArray(id);
uint256[] memory amounts = _asSingletonArray(amount);
_beforeTokenTransfer(operator, address(0), to, ids, amounts, data);
_balances[id][to] += amount;
emit TransferSingle(operator, address(0), to, id, amount);
_afterTokenTransfer(operator, address(0), to, ids, amounts, data);
_doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - `ids` and `amounts` must have the same length.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
*/
function _mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {
require(to != address(0), "ERC1155: mint to the zero address");
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
address operator = _msgSender();
_beforeTokenTransfer(operator, address(0), to, ids, amounts, data);
for (uint256 i = 0; i < ids.length; i++) {
_balances[ids[i]][to] += amounts[i];
}
emit TransferBatch(operator, address(0), to, ids, amounts);
_afterTokenTransfer(operator, address(0), to, ids, amounts, data);
_doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
}
/**
* @dev Destroys `amount` tokens of token type `id` from `from`
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `from` must have at least `amount` tokens of token type `id`.
*/
function _burn(address from, uint256 id, uint256 amount) internal virtual {
require(from != address(0), "ERC1155: burn from the zero address");
address operator = _msgSender();
uint256[] memory ids = _asSingletonArray(id);
uint256[] memory amounts = _asSingletonArray(amount);
_beforeTokenTransfer(operator, from, address(0), ids, amounts, "");
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
unchecked {
_balances[id][from] = fromBalance - amount;
}
emit TransferSingle(operator, from, address(0), id, amount);
_afterTokenTransfer(operator, from, address(0), ids, amounts, "");
}
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - `ids` and `amounts` must have the same length.
*/
function _burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) internal virtual {
require(from != address(0), "ERC1155: burn from the zero address");
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
address operator = _msgSender();
_beforeTokenTransfer(operator, from, address(0), ids, amounts, "");
for (uint256 i = 0; i < ids.length; i++) {
uint256 id = ids[i];
uint256 amount = amounts[i];
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
unchecked {
_balances[id][from] = fromBalance - amount;
}
}
emit TransferBatch(operator, from, address(0), ids, amounts);
_afterTokenTransfer(operator, from, address(0), ids, amounts, "");
}
/**
* @dev Approve `operator` to operate on all of `owner` tokens
*
* Emits an {ApprovalForAll} event.
*/
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
require(owner != operator, "ERC1155: setting approval status for self");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/**
* @dev Hook that is called before any token transfer. This includes minting
* and burning, as well as batched variants.
*
* The same hook is called on both single and batched variants. For single
* transfers, the length of the `ids` and `amounts` arrays will be 1.
*
* Calling conditions (for each `id` and `amount` pair):
*
* - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* of token type `id` will be transferred to `to`.
* - When `from` is zero, `amount` tokens of token type `id` will be minted
* for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
* will be burned.
* - `from` and `to` are never both zero.
* - `ids` and `amounts` have the same, non-zero length.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {}
/**
* @dev Hook that is called after any token transfer. This includes minting
* and burning, as well as batched variants.
*
* The same hook is called on both single and batched variants. For single
* transfers, the length of the `id` and `amount` arrays will be 1.
*
* Calling conditions (for each `id` and `amount` pair):
*
* - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* of token type `id` will be transferred to `to`.
* - When `from` is zero, `amount` tokens of token type `id` will be minted
* for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
* will be burned.
* - `from` and `to` are never both zero.
* - `ids` and `amounts` have the same, non-zero length.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {}
function _doSafeTransferAcceptanceCheck(
address operator,
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) private {
if (to.isContract()) {
try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
if (response != IERC1155Receiver.onERC1155Received.selector) {
revert("ERC1155: ERC1155Receiver rejected tokens");
}
} catch Error(string memory reason) {
revert(reason);
} catch {
revert("ERC1155: transfer to non-ERC1155Receiver implementer");
}
}
}
function _doSafeBatchTransferAcceptanceCheck(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) private {
if (to.isContract()) {
try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
bytes4 response
) {
if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
revert("ERC1155: ERC1155Receiver rejected tokens");
}
} catch Error(string memory reason) {
revert(reason);
} catch {
revert("ERC1155: transfer to non-ERC1155Receiver implementer");
}
}
}
function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) {
uint256[] memory array = new uint256[](1);
array[0] = element;
return array;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol)
pragma solidity ^0.8.0;
import "../IERC1155.sol";
/**
* @dev Interface of the optional ERC1155MetadataExtension interface, as defined
* in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP].
*
* _Available since v3.1._
*/
interface IERC1155MetadataURI is IERC1155 {
/**
* @dev Returns the URI for token type `id`.
*
* If the `\{id\}` substring is present in the URI, it must be replaced by
* clients with the actual token type ID.
*/
function uri(uint256 id) external view returns (string memory);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/IERC1155.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC1155 compliant contract, as defined in the
* https://eips.ethereum.org/EIPS/eip-1155[EIP].
*
* _Available since v3.1._
*/
interface IERC1155 is IERC165 {
/**
* @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
*/
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
/**
* @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
* transfers.
*/
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values
);
/**
* @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
* `approved`.
*/
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
/**
* @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
*
* If an {URI} event was emitted for `id`, the standard
* https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
* returned by {IERC1155MetadataURI-uri}.
*/
event URI(string value, uint256 indexed id);
/**
* @dev Returns the amount of tokens of token type `id` owned by `account`.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function balanceOf(address account, uint256 id) external view returns (uint256);
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
*
* Requirements:
*
* - `accounts` and `ids` must have the same length.
*/
function balanceOfBatch(
address[] calldata accounts,
uint256[] calldata ids
) external view returns (uint256[] memory);
/**
* @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
*
* Emits an {ApprovalForAll} event.
*
* Requirements:
*
* - `operator` cannot be the caller.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
*
* See {setApprovalForAll}.
*/
function isApprovedForAll(address account, address operator) external view returns (bool);
/**
* @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
*
* Emits a {TransferSingle} event.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
* - `from` must have a balance of tokens of type `id` of at least `amount`.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
* acceptance magic value.
*/
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
/**
* @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
*
* Emits a {TransferBatch} event.
*
* Requirements:
*
* - `ids` and `amounts` must have the same length.
* - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
* acceptance magic value.
*/
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev _Available since v3.1._
*/
interface IERC1155Receiver is IERC165 {
/**
* @dev Handles the receipt of a single ERC1155 token type. This function is
* called at the end of a `safeTransferFrom` after the balance has been updated.
*
* NOTE: To accept the transfer, this must return
* `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
* (i.e. 0xf23a6e61, or its own function selector).
*
* @param operator The address which initiated the transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param id The ID of the token being transferred
* @param value The amount of tokens being transferred
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
*/
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
) external returns (bytes4);
/**
* @dev Handles the receipt of a multiple ERC1155 token types. This function
* is called at the end of a `safeBatchTransferFrom` after the balances have
* been updated.
*
* NOTE: To accept the transfer(s), this must return
* `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
* (i.e. 0xbc197c81, or its own function selector).
*
* @param operator The address which initiated the batch transfer (i.e. msg.sender)
* @param from The address which previously owned the token
* @param ids An array containing ids of each token being transferred (order and length must match values array)
* @param values An array containing amounts of each token being transferred (order and length must match ids array)
* @param data Additional data with no specified format
* @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
*/
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external returns (bytes4);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20 is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/
function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/
function _transfer(address from, address to, uint256 amount) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
// Overflow not possible: amount <= accountBalance <= totalSupply.
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/ERC721.sol)
pragma solidity ^0.8.0;
import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./extensions/IERC721Metadata.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/Strings.sol";
import "../../utils/introspection/ERC165.sol";
/**
* @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
* the Metadata extension, but not including the Enumerable extension, which is available separately as
* {ERC721Enumerable}.
*/
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
using Address for address;
using Strings for uint256;
// Token name
string private _name;
// Token symbol
string private _symbol;
// Mapping from token ID to owner address
mapping(uint256 => address) private _owners;
// Mapping owner address to token count
mapping(address => uint256) private _balances;
// Mapping from token ID to approved address
mapping(uint256 => address) private _tokenApprovals;
// Mapping from owner to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
/**
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC721-balanceOf}.
*/
function balanceOf(address owner) public view virtual override returns (uint256) {
require(owner != address(0), "ERC721: address zero is not a valid owner");
return _balances[owner];
}
/**
* @dev See {IERC721-ownerOf}.
*/
function ownerOf(uint256 tokenId) public view virtual override returns (address) {
address owner = _ownerOf(tokenId);
require(owner != address(0), "ERC721: invalid token ID");
return owner;
}
/**
* @dev See {IERC721Metadata-name}.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev See {IERC721Metadata-symbol}.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev See {IERC721Metadata-tokenURI}.
*/
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
_requireMinted(tokenId);
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}
/**
* @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
* token will be the concatenation of the `baseURI` and the `tokenId`. Empty
* by default, can be overridden in child contracts.
*/
function _baseURI() internal view virtual returns (string memory) {
return "";
}
/**
* @dev See {IERC721-approve}.
*/
function approve(address to, uint256 tokenId) public virtual override {
address owner = ERC721.ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner");
require(
_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
"ERC721: approve caller is not token owner or approved for all"
);
_approve(to, tokenId);
}
/**
* @dev See {IERC721-getApproved}.
*/
function getApproved(uint256 tokenId) public view virtual override returns (address) {
_requireMinted(tokenId);
return _tokenApprovals[tokenId];
}
/**
* @dev See {IERC721-setApprovalForAll}.
*/
function setApprovalForAll(address operator, bool approved) public virtual override {
_setApprovalForAll(_msgSender(), operator, approved);
}
/**
* @dev See {IERC721-isApprovedForAll}.
*/
function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator];
}
/**
* @dev See {IERC721-transferFrom}.
*/
function transferFrom(address from, address to, uint256 tokenId) public virtual override {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_transfer(from, to, tokenId);
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
safeTransferFrom(from, to, tokenId, "");
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_safeTransfer(from, to, tokenId, data);
}
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* `data` is additional data, it has no specified format and it is sent in call to `to`.
*
* This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
* implement alternative mechanisms to perform token transfer, such as signature-based.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
_transfer(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
}
/**
* @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
*/
function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
return _owners[tokenId];
}
/**
* @dev Returns whether `tokenId` exists.
*
* Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
*
* Tokens start existing when they are minted (`_mint`),
* and stop existing when they are burned (`_burn`).
*/
function _exists(uint256 tokenId) internal view virtual returns (bool) {
return _ownerOf(tokenId) != address(0);
}
/**
* @dev Returns whether `spender` is allowed to manage `tokenId`.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
address owner = ERC721.ownerOf(tokenId);
return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
}
/**
* @dev Safely mints `tokenId` and transfers it to `to`.
*
* Requirements:
*
* - `tokenId` must not exist.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeMint(address to, uint256 tokenId) internal virtual {
_safeMint(to, tokenId, "");
}
/**
* @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
* forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
*/
function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
_mint(to, tokenId);
require(
_checkOnERC721Received(address(0), to, tokenId, data),
"ERC721: transfer to non ERC721Receiver implementer"
);
}
/**
* @dev Mints `tokenId` and transfers it to `to`.
*
* WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
*
* Requirements:
*
* - `tokenId` must not exist.
* - `to` cannot be the zero address.
*
* Emits a {Transfer} event.
*/
function _mint(address to, uint256 tokenId) internal virtual {
require(to != address(0), "ERC721: mint to the zero address");
require(!_exists(tokenId), "ERC721: token already minted");
_beforeTokenTransfer(address(0), to, tokenId, 1);
// Check that tokenId was not minted by `_beforeTokenTransfer` hook
require(!_exists(tokenId), "ERC721: token already minted");
unchecked {
// Will not overflow unless all 2**256 token ids are minted to the same owner.
// Given that tokens are minted one by one, it is impossible in practice that
// this ever happens. Might change if we allow batch minting.
// The ERC fails to describe this case.
_balances[to] += 1;
}
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
_afterTokenTransfer(address(0), to, tokenId, 1);
}
/**
* @dev Destroys `tokenId`.
* The approval is cleared when the token is burned.
* This is an internal function that does not check if the sender is authorized to operate on the token.
*
* Requirements:
*
* - `tokenId` must exist.
*
* Emits a {Transfer} event.
*/
function _burn(uint256 tokenId) internal virtual {
address owner = ERC721.ownerOf(tokenId);
_beforeTokenTransfer(owner, address(0), tokenId, 1);
// Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
owner = ERC721.ownerOf(tokenId);
// Clear approvals
delete _tokenApprovals[tokenId];
unchecked {
// Cannot overflow, as that would require more tokens to be burned/transferred
// out than the owner initially received through minting and transferring in.
_balances[owner] -= 1;
}
delete _owners[tokenId];
emit Transfer(owner, address(0), tokenId);
_afterTokenTransfer(owner, address(0), tokenId, 1);
}
/**
* @dev Transfers `tokenId` from `from` to `to`.
* As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
*
* Emits a {Transfer} event.
*/
function _transfer(address from, address to, uint256 tokenId) internal virtual {
require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
require(to != address(0), "ERC721: transfer to the zero address");
_beforeTokenTransfer(from, to, tokenId, 1);
// Check that tokenId was not transferred by `_beforeTokenTransfer` hook
require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
// Clear approvals from the previous owner
delete _tokenApprovals[tokenId];
unchecked {
// `_balances[from]` cannot overflow for the same reason as described in `_burn`:
// `from`'s balance is the number of token held, which is at least one before the current
// transfer.
// `_balances[to]` could overflow in the conditions described in `_mint`. That would require
// all 2**256 token ids to be minted, which in practice is impossible.
_balances[from] -= 1;
_balances[to] += 1;
}
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
_afterTokenTransfer(from, to, tokenId, 1);
}
/**
* @dev Approve `to` to operate on `tokenId`
*
* Emits an {Approval} event.
*/
function _approve(address to, uint256 tokenId) internal virtual {
_tokenApprovals[tokenId] = to;
emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
}
/**
* @dev Approve `operator` to operate on all of `owner` tokens
*
* Emits an {ApprovalForAll} event.
*/
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
require(owner != operator, "ERC721: approve to caller");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/**
* @dev Reverts if the `tokenId` has not been minted yet.
*/
function _requireMinted(uint256 tokenId) internal view virtual {
require(_exists(tokenId), "ERC721: invalid token ID");
}
/**
* @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
* The call is not executed if the target address is not a contract.
*
* @param from address representing the previous owner of the given token ID
* @param to target address that will receive the tokens
* @param tokenId uint256 ID of the token to be transferred
* @param data bytes optional data to send along with the call
* @return bool whether the call correctly returned the expected magic value
*/
function _checkOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory data
) private returns (bool) {
if (to.isContract()) {
try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
return retval == IERC721Receiver.onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert("ERC721: transfer to non ERC721Receiver implementer");
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
} else {
return true;
}
}
/**
* @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
* - When `from` is zero, the tokens will be minted for `to`.
* - When `to` is zero, ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
* - `batchSize` is non-zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
/**
* @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
* - When `from` is zero, the tokens were minted for `to`.
* - When `to` is zero, ``from``'s tokens were burned.
* - `from` and `to` are never both zero.
* - `batchSize` is non-zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}
/**
* @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
*
* WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant
* being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such
* that `ownerOf(tokenId)` is `a`.
*/
// solhint-disable-next-line func-name-mixedcase
function __unsafe_increaseBalance(address account, uint256 amount) internal {
_balances[account] += amount;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC721.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Metadata is IERC721 {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.0;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1, "Math: mulDiv overflow");
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
import "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toString(int256 value) internal pure returns (string memory) {
return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface IAllowanceTransfer {
struct PermitDetails {
address token;
uint160 amount;
uint48 expiration;
uint48 nonce;
}
struct PermitBatch {
PermitDetails[] details;
address spender;
uint256 sigDeadline;
}
struct AllowanceTransferDetails {
address from;
address to;
uint160 amount;
address token;
}
function permit(
address owner,
PermitBatch memory permitBatch,
bytes calldata signature
) external;
function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
interface IBlur {
enum Side {
Buy,
Sell
}
enum AssetType {
ERC721,
ERC1155
}
enum SignatureVersion {
Single,
Bulk
}
struct Fee {
uint16 rate;
address payable recipient;
}
struct Order {
address trader;
Side side;
address matchingPolicy;
IERC165 collection;
uint256 tokenId;
uint256 amount;
address paymentToken;
uint256 price;
uint256 listingTime;
uint256 expirationTime;
Fee[] fees;
uint256 salt;
bytes extraParams;
}
struct Input {
Order order;
uint8 v;
bytes32 r;
bytes32 s;
bytes extraSignature;
SignatureVersion signatureVersion;
uint256 blockNumber;
}
function execute(Input calldata sell, Input calldata buy) external payable;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface ICaviarPoolV1 {
struct Message {
bytes32 id;
bytes payload;
uint256 timestamp;
bytes signature;
}
function nft() external returns (address);
function baseToken() external returns (address);
function buyQuote(uint256 outputAmount) external view returns (uint256);
function nftBuy(
uint256[] calldata tokenIds,
uint256 maxInputAmount,
uint256 deadline
) external payable returns (uint256 inputAmount);
function nftSell(
uint256[] calldata tokenIds,
uint256 minOutputAmount,
uint256 deadline,
bytes32[][] calldata proofs,
Message[] calldata messages
) external returns (uint256 outputAmount);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
interface ICollectionPool {
enum PoolType {
TOKEN,
NFT,
TRADE
}
enum PoolVariant {
ENUMERABLE_ETH,
MISSING_ENUMERABLE_ETH,
ENUMERABLE_ERC20,
MISSING_ENUMERABLE_ERC20
}
struct CurveParams {
uint128 spotPrice;
uint128 delta;
bytes props;
bytes state;
}
struct CurveFees {
uint256 trade;
uint256 protocol;
uint256[] royalties;
}
function nft() external returns (IERC721);
function token() external returns (IERC20);
function poolType() external view returns (PoolType);
function poolVariant() external pure returns (PoolVariant);
function externalFilter() external view returns (address);
function getBuyNFTQuote(
uint256 numNFTs
)
external
view
returns (
CurveParams memory newParams,
uint256 totalAmount,
uint256 inputAmount,
CurveFees memory fees
);
}
interface ICollectionRouter {
struct PoolSwapSpecific {
ICollectionPool pool;
uint256[] nftIds;
bytes32[] proof;
bool[] proofFlags;
/// @dev only used for selling into pools
bytes externalFilterContext;
}
function swapETHForSpecificNFTs(
PoolSwapSpecific[] calldata swapList,
address ethRecipient,
address nftRecipient,
uint256 deadline
) external payable returns (uint256 remainingValue);
function swapERC20ForSpecificNFTs(
PoolSwapSpecific[] calldata swapList,
uint256 inputAmount,
address nftRecipient,
uint256 deadline
) external returns (uint256 remainingValue);
function swapNFTsForToken(
PoolSwapSpecific[] calldata swapList,
uint256 minOutput,
address tokenRecipient,
uint256 deadline
) external returns (uint256 outputAmount);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface IConduitController {
function getConduitCodeHashes()
external
view
returns (bytes32 creationCodeHash, bytes32 runtimeCodeHash);
}
interface IConduit {
enum ConduitItemType {
NATIVE, // Unused
ERC20,
ERC721,
ERC1155
}
struct ConduitTransfer {
ConduitItemType itemType;
address token;
address from;
address to;
uint256 identifier;
uint256 amount;
}
function execute(ConduitTransfer[] calldata transfers) external returns (bytes4 magicValue);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface ICryptoPunksMarket {
struct BuyOrder {
address buyer;
uint256 price;
uint256 punkIndex;
}
function punkIndexToAddress(uint256 punkIndex) external view returns (address owner);
function balanceOf(address owner) external view returns (uint256 balance);
function transferPunk(address to, uint256 punkIndex) external;
function buyPunk(uint256 punkIndex) external payable;
function offerPunkForSaleToAddress(
uint256 punkIndex,
uint256 minSalePriceInWei,
address to
) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IDittoPool {
struct SwapTokensForNftsArgs {
uint256[] nftIds;
uint256 maxExpectedTokenInput;
address tokenSender;
address nftRecipient;
bytes swapData;
}
struct SwapNftsForTokensArgs {
uint256[] nftIds;
uint256[] lpIds;
uint256 minExpectedTokenOutput;
address nftSender;
address tokenRecipient;
bytes permitterData;
bytes swapData;
}
function token() external returns (IERC20);
function swapNftsForTokens(
SwapNftsForTokensArgs calldata args_
) external returns (uint256 outputAmount);
function swapTokensForNfts(
SwapTokensForNftsArgs calldata args_
) external returns (uint256 inputAmount);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface IEIP2612 {
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IElement {
struct Signature {
uint8 signatureType;
uint8 v;
bytes32 r;
bytes32 s;
}
struct Property {
address propertyValidator;
bytes propertyData;
}
struct Fee {
address recipient;
uint256 amount;
bytes feeData;
}
struct NFTSellOrder {
address maker;
address taker;
uint256 expiry;
uint256 nonce;
IERC20 erc20Token;
uint256 erc20TokenAmount;
Fee[] fees;
address nft;
uint256 nftId;
}
struct NFTBuyOrder {
address maker;
address taker;
uint256 expiry;
uint256 nonce;
IERC20 erc20Token;
uint256 erc20TokenAmount;
Fee[] fees;
address nft;
uint256 nftId;
Property[] nftProperties;
}
struct ERC1155SellOrder {
address maker;
address taker;
uint256 expiry;
uint256 nonce;
IERC20 erc20Token;
uint256 erc20TokenAmount;
Fee[] fees;
address erc1155Token;
uint256 erc1155TokenId;
// End of fields shared with NFTOrder
uint128 erc1155TokenAmount;
}
struct ERC1155BuyOrder {
address maker;
address taker;
uint256 expiry;
uint256 nonce;
IERC20 erc20Token;
uint256 erc20TokenAmount;
Fee[] fees;
address erc1155Token;
uint256 erc1155TokenId;
Property[] erc1155TokenProperties;
// End of fields shared with NFTOrder
uint128 erc1155TokenAmount;
}
struct BatchSignedOrder {
address maker;
uint256 listingTime;
uint256 expirationTime;
uint256 startNonce;
address erc20Token;
address platformFeeRecipient;
uint8 v;
bytes32 r;
bytes32 s;
bytes collectionsBytes;
}
/// @param data1 [56 bits(startNonce) + 8 bits(v) + 32 bits(listingTime) + 160 bits(maker)]
/// @param data2 [64 bits(taker part1) + 32 bits(expiryTime) + 160 bits(erc20Token)]
/// @param data3 [96 bits(taker part2) + 160 bits(platformFeeRecipient)]
struct Parameter {
uint256 data1;
uint256 data2;
uint256 data3;
bytes32 r;
bytes32 s;
}
/// @param data1 [56 bits(startNonce) + 8 bits(v) + 32 bits(listingTime) + 160 bits(maker)]
/// @param data2 [64 bits(taker part1) + 32 bits(expiryTime) + 160 bits(erc20Token)]
/// @param data3 [96 bits(taker part2) + 160 bits(platformFeeRecipient)]
struct Parameters {
uint256 data1;
uint256 data2;
uint256 data3;
bytes32 r;
bytes32 s;
bytes collections;
}
function buyERC721Ex(
NFTSellOrder calldata sellOrder,
Signature calldata signature,
address taker,
bytes calldata takerData
) external payable;
function batchBuyERC721sEx(
NFTSellOrder[] calldata sellOrders,
Signature[] calldata signatures,
address[] calldata takers,
bytes[] calldata takerDatas,
bool revertIfIncomplete
) external payable returns (bool[] memory successes);
function buyERC1155Ex(
ERC1155SellOrder calldata sellOrder,
Signature calldata signature,
address taker,
uint128 erc1155BuyAmount,
bytes calldata takerData
) external payable;
function batchBuyERC1155sEx(
ERC1155SellOrder[] calldata sellOrders,
Signature[] calldata signatures,
address[] calldata takers,
uint128[] calldata erc1155TokenAmounts,
bytes[] calldata takerDatas,
bool revertIfIncomplete
) external payable returns (bool[] memory successes);
function sellERC721(
NFTBuyOrder calldata buyOrder,
Signature calldata signature,
uint256 erc721TokenId,
bool unwrapNativeToken,
bytes calldata takerData
) external;
function sellERC1155(
ERC1155BuyOrder calldata buyOrder,
Signature calldata signature,
uint256 erc1155TokenId,
uint128 erc1155SellAmount,
bool unwrapNativeToken,
bytes calldata takerData
) external;
function fillBatchSignedERC721Order(Parameter calldata parameter, bytes calldata collections)
external
payable;
/// @param additional1 [96 bits(withdrawETHAmount) + 160 bits(erc20Token)]
/// @param additional2 [8 bits(revertIfIncomplete) + 88 bits(unused) + 160 bits(royaltyFeeRecipient)]
function fillBatchSignedERC721Orders(
Parameters[] calldata parameters,
uint256 additional1,
uint256 additional2
) external payable;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
enum ExchangeKind {
WYVERN_V23,
LOOKS_RARE,
ZEROEX_V4,
FOUNDATION,
X2Y2,
SEAPORT
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
interface IFoundation {
function buyV2(
IERC721 nftContract,
uint256 tokenId,
uint256 maxPrice,
address referrer
) external payable;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
interface ILooksRare {
struct MakerOrder {
bool isOrderAsk;
address signer;
IERC165 collection;
uint256 price;
uint256 tokenId;
uint256 amount;
address strategy;
IERC20 currency;
uint256 nonce;
uint256 startTime;
uint256 endTime;
uint256 minPercentageToAsk;
bytes params;
uint8 v;
bytes32 r;
bytes32 s;
}
struct TakerOrder {
bool isOrderAsk;
address taker;
uint256 price;
uint256 tokenId;
uint256 minPercentageToAsk;
bytes params;
}
function transferSelectorNFT() external view returns (ILooksRareTransferSelectorNFT);
function matchAskWithTakerBidUsingETHAndWETH(
TakerOrder calldata takerBid,
MakerOrder calldata makerAsk
) external payable;
function matchAskWithTakerBid(
TakerOrder calldata takerBid,
MakerOrder calldata makerAsk
) external payable;
function matchBidWithTakerAsk(
TakerOrder calldata takerAsk,
MakerOrder calldata makerBid
) external;
}
interface ILooksRareTransferSelectorNFT {
function TRANSFER_MANAGER_ERC721() external view returns (address);
function TRANSFER_MANAGER_ERC1155() external view returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface ILooksRareV2 {
enum QuoteType {
Bid,
Ask
}
enum CollectionType {
ERC721,
ERC1155
}
enum MerkleTreeNodePosition {
Left,
Right
}
struct MakerOrder {
QuoteType quoteType;
uint256 globalNonce;
uint256 subsetNonce;
uint256 orderNonce;
uint256 strategyId;
CollectionType collectionType;
address collection;
IERC20 currency;
address signer;
uint256 startTime;
uint256 endTime;
uint256 price;
uint256[] itemIds;
uint256[] amounts;
bytes additionalParameters;
}
struct TakerOrder {
address recipient;
bytes additionalParameters;
}
struct MerkleTreeNode {
bytes32 value;
MerkleTreeNodePosition position;
}
struct MerkleTree {
bytes32 root;
MerkleTreeNode[] proof;
}
function transferManager() external view returns (ITransferManager);
function executeTakerAsk(
TakerOrder calldata takerAsk,
MakerOrder calldata makerBid,
bytes calldata makerSignature,
MerkleTree calldata merkleTree,
address affiliate
) external;
function executeTakerBid(
TakerOrder calldata takerBid,
MakerOrder calldata makerAsk,
bytes calldata makerSignature,
MerkleTree calldata merkleTree,
address affiliate
) external payable;
}
interface ITransferManager {
function grantApprovals(address[] calldata operators) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
interface IMidasRouter {
function getMinAmountIn(
address pair,
uint256[] calldata tokenIds
) external view returns (uint128 totalAmount);
}
interface IMidasPair {
function sellNFT(uint256 nftId, address to) external returns (uint128 amountOut);
function buyNFT(uint256 nftId, address to) external;
}
interface IMidasFactory {
function getPairERC721(address tokenA, address tokenB) external view returns (address pair);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {INFTXVaultFactory} from "./INFTXVaultFactory.sol";
interface INFTXMarketplaceZap {
function nftxFactory() external view returns (INFTXVaultFactory);
struct BuyOrder {
uint256 vaultId;
address collection;
uint256[] specificIds;
uint256 amount;
address[] path;
uint256 price;
}
struct SellOrder {
uint256 vaultId;
address collection;
IERC20 currency;
uint256[] specificIds;
// ERC1155
uint256[] amounts;
uint256 price;
address[] path;
}
function mintAndSell721(
uint256 vaultId,
uint256[] calldata ids,
uint256 minEthOut,
address[] calldata path,
address to
) external;
function mintAndSell721WETH(
uint256 vaultId,
uint256[] calldata ids,
uint256 minEthOut,
address[] calldata path,
address to
) external;
function mintAndSell1155(
uint256 vaultId,
uint256[] calldata ids,
uint256[] calldata amounts,
uint256 minWethOut,
address[] calldata path,
address to
) external;
function mintAndSell1155WETH(
uint256 vaultId,
uint256[] calldata ids,
uint256[] calldata amounts,
uint256 minWethOut,
address[] calldata path,
address to
) external;
function buyAndRedeem(
uint256 vaultId,
uint256 amount,
uint256[] calldata specificIds,
address[] calldata path,
address to
) external payable;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {INFTXVaultFactory} from "./INFTXVaultFactory.sol";
interface INFTXMarketplace0xZap {
function nftxFactory() external view returns (INFTXVaultFactory);
struct BuyOrder {
uint256 vaultId;
address collection;
uint256[] specificIds;
uint256 amount;
uint256 price;
bytes swapCallData;
}
struct SellOrder {
uint256 vaultId;
address collection;
IERC20 currency;
uint256[] specificIds;
// For ERC1155 only
uint256[] amounts;
uint256 price;
bytes swapCallData;
}
function mintAndSell721(
uint256 vaultId,
uint256[] calldata ids,
bytes calldata swapCallData,
address payable to
) external;
function mintAndSell1155(
uint256 vaultId,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata swapCallData,
address payable to
) external;
function buyAndRedeem(
uint256 vaultId,
uint256 amount,
uint256[] calldata specificIds,
bytes calldata swapCallData,
address payable to
) external payable;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {INFTXVaultFactory} from "./INFTXVaultFactory.sol";
interface INFTXV3MarketplaceZap {
function nftxVaultFactory() external view returns (INFTXVaultFactory);
struct SellOrder {
uint256 vaultId;
address collection;
IERC20 currency;
uint256[] idsIn;
// For ERC1155 only
uint256[] amounts;
uint256 price;
bytes executeCallData;
bool deductRoyalty;
}
struct BuyOrder {
uint256 vaultId;
address collection;
uint256[] idsOut;
uint256 price;
bytes executeCallData;
uint256 vTokenPremiumLimit;
bool deductRoyalty;
}
function sell721(
uint256 vaultId,
uint256[] calldata idsIn,
bytes calldata executeCallData,
address payable to,
bool deductRoyalty
) external;
function sell1155(
uint256 vaultId,
uint256[] calldata idsIn,
uint256[] calldata amounts,
bytes calldata executeCallData,
address payable to,
bool deductRoyalty
) external;
function buyNFTsWithETH(
uint256 vaultId,
uint256[] calldata idsOut,
bytes calldata executeCallData,
address payable to,
uint256 vTokenPremiumLimit,
bool deductRoyalty
) external payable;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface INFTXVault {
function is1155() external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface INFTXVaultFactory {
function vault(uint256 vaultId) external view returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
interface IPaymentProcessor {
enum TokenProtocols {
ERC721,
ERC1155
}
struct SignatureECDSA {
uint8 v;
bytes32 r;
bytes32 s;
}
struct MatchedOrder {
bool sellerAcceptedOffer;
bool collectionLevelOffer;
TokenProtocols protocol;
address paymentCoin;
address tokenAddress;
address seller;
address privateBuyer;
address buyer;
address delegatedPurchaser;
address marketplace;
uint256 marketplaceFeeNumerator;
uint256 maxRoyaltyFeeNumerator;
uint256 listingNonce;
uint256 offerNonce;
uint256 listingMinPrice;
uint256 offerPrice;
uint256 listingExpiration;
uint256 offerExpiration;
uint256 tokenId;
uint256 amount;
}
function buySingleListing(
MatchedOrder memory saleDetails,
SignatureECDSA memory signedListing,
SignatureECDSA memory signedOffer
) external payable;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
interface IRarible {
struct AssetType {
bytes4 assetClass;
bytes data;
}
struct Asset {
AssetType assetType;
uint256 value;
}
struct Order {
address maker;
Asset makeAsset;
address taker;
Asset takeAsset;
uint256 salt;
uint256 start;
uint256 end;
bytes4 dataType;
bytes data;
}
struct NftData {
uint256 tokenId;
IERC165 collection;
}
function matchOrders(
Order calldata orderLeft,
bytes calldata signatureLeft,
Order calldata orderRight,
bytes calldata signatureRight
) external payable;
}
interface IRaribleTransferManager {
function TRANSFER_MANAGER() external view returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface IReservoirV6_0_1 {
struct ExecutionInfo {
address module;
bytes data;
uint256 value;
}
function execute(ExecutionInfo[] calldata executionInfos) external payable;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface ISeaport {
enum OrderType {
FULL_OPEN,
PARTIAL_OPEN,
FULL_RESTRICTED,
PARTIAL_RESTRICTED
}
enum ItemType {
NATIVE,
ERC20,
ERC721,
ERC1155,
ERC721_WITH_CRITERIA,
ERC1155_WITH_CRITERIA
}
enum Side {
OFFER,
CONSIDERATION
}
struct OfferItem {
ItemType itemType;
address token;
uint256 identifierOrCriteria;
uint256 startAmount;
uint256 endAmount;
}
struct ConsiderationItem {
ItemType itemType;
address token;
uint256 identifierOrCriteria;
uint256 startAmount;
uint256 endAmount;
address recipient;
}
struct SpentItem {
ItemType itemType;
address token;
uint256 identifier;
uint256 amount;
}
struct ReceivedItem {
ItemType itemType;
address token;
uint256 identifier;
uint256 amount;
address recipient;
}
struct OrderComponents {
address offerer;
address zone;
OfferItem[] offer;
ConsiderationItem[] consideration;
OrderType orderType;
uint256 startTime;
uint256 endTime;
bytes32 zoneHash;
uint256 salt;
bytes32 conduitKey;
uint256 counter;
}
struct OrderParameters {
address offerer;
address zone;
OfferItem[] offer;
ConsiderationItem[] consideration;
OrderType orderType;
uint256 startTime;
uint256 endTime;
bytes32 zoneHash;
uint256 salt;
bytes32 conduitKey;
uint256 totalOriginalConsiderationItems;
}
struct Order {
OrderParameters parameters;
bytes signature;
}
struct AdvancedOrder {
OrderParameters parameters;
uint120 numerator;
uint120 denominator;
bytes signature;
bytes extraData;
}
struct CriteriaResolver {
uint256 orderIndex;
Side side;
uint256 index;
uint256 identifier;
bytes32[] criteriaProof;
}
struct FulfillmentComponent {
uint256 orderIndex;
uint256 itemIndex;
}
struct Fulfillment {
FulfillmentComponent[] offerComponents;
FulfillmentComponent[] considerationComponents;
}
struct Execution {
ReceivedItem item;
address offerer;
bytes32 conduitKey;
}
struct ZoneParameters {
bytes32 orderHash;
address fulfiller;
address offerer;
SpentItem[] offer;
ReceivedItem[] consideration;
bytes extraData;
bytes32[] orderHashes;
uint256 startTime;
uint256 endTime;
bytes32 zoneHash;
}
struct Schema {
uint256 id;
bytes metadata;
}
function getOrderHash(OrderComponents calldata order) external view returns (bytes32 orderHash);
function getOrderStatus(bytes32 orderHash)
external
view
returns (
bool isValidated,
bool isCancelled,
uint256 totalFilled,
uint256 totalSize
);
function getCounter(address offerer) external view returns (uint256 counter);
function fulfillAdvancedOrder(
AdvancedOrder calldata advancedOrder,
CriteriaResolver[] calldata criteriaResolvers,
bytes32 fulfillerConduitKey,
address recipient
) external payable returns (bool fulfilled);
function fulfillAvailableAdvancedOrders(
AdvancedOrder[] memory advancedOrders,
CriteriaResolver[] calldata criteriaResolvers,
FulfillmentComponent[][] calldata offerFulfillments,
FulfillmentComponent[][] calldata considerationFulfillments,
bytes32 fulfillerConduitKey,
address recipient,
uint256 maximumFulfilled
) external payable returns (bool[] memory availableOrders, Execution[] memory executions);
function matchOrders(Order[] calldata orders, Fulfillment[] calldata fulfillments)
external
payable
returns (Execution[] memory executions);
function matchAdvancedOrders(
AdvancedOrder[] calldata advancedOrders,
CriteriaResolver[] calldata criteriaResolvers,
Fulfillment[] calldata fulfillments,
address recipient
) external payable returns (Execution[] memory executions);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
interface ISudoswapPair {
enum PairVariant {
ENUMERABLE_ETH,
MISSING_ENUMERABLE_ETH,
ENUMERABLE_ERC20,
MISSING_ENUMERABLE_ERC20
}
function nft() external returns (IERC721);
function token() external returns (IERC20);
function pairVariant() external pure returns (PairVariant);
function getBuyNFTQuote(uint256 numNFTs)
external
view
returns (
uint8 error,
uint256 newSpotPrice,
uint256 newDelta,
uint256 inputAmount,
uint256 protocolFee
);
}
interface ISudoswapRouter {
struct PairSwapSpecific {
ISudoswapPair pair;
uint256[] nftIds;
}
function swapETHForSpecificNFTs(
PairSwapSpecific[] calldata swapList,
address ethRecipient,
address nftRecipient,
uint256 deadline
) external payable returns (uint256 remainingValue);
function swapERC20ForSpecificNFTs(
PairSwapSpecific[] calldata swapList,
uint256 inputAmount,
address nftRecipient,
uint256 deadline
) external returns (uint256 remainingValue);
function swapNFTsForToken(
PairSwapSpecific[] calldata swapList,
uint256 minOutput,
address tokenRecipient,
uint256 deadline
) external returns (uint256 outputAmount);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
interface ISudoswapPairV2 {
enum PairVariant {
ERC721_ETH,
ERC721_ERC20,
ERC1155_ETH,
ERC1155_ERC20
}
function nft() external returns (address);
function nftId() external returns (uint256 id);
function token() external returns (IERC20);
function pairVariant() external pure returns (PairVariant);
function getBuyNFTQuote(
uint256 assetId,
uint256 numNFTs
)
external
view
returns (
uint8 error,
uint256 newSpotPrice,
uint256 newDelta,
uint256 inputAmount,
uint256 protocolFee,
uint256 royaltyAmount
);
function swapTokenForSpecificNFTs(
uint256[] calldata nftIds,
uint256 maxExpectedTokenInput,
address nftRecipient,
bool isRouter,
address routerCaller
) external payable returns (uint256);
function swapNFTsForToken(
uint256[] calldata nftIds,
uint256 minExpectedTokenOutput,
address payable tokenRecipient,
bool isRouter,
address routerCaller
) external returns (uint256 outputAmount);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
interface ISuperRare {
function buy(
IERC721 nftContract,
uint256 tokenId,
address currency,
uint256 price
) external payable;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IUniswapV3Router {
struct ExactOutputSingleParams {
IERC20 tokenIn;
IERC20 tokenOut;
uint24 fee;
address recipient;
uint256 amountOut;
uint256 amountInMaximum;
uint160 sqrtPriceLimitX96;
}
function exactOutputSingle(ExactOutputSingleParams calldata params)
external
payable
returns (uint256 amountIn);
struct ExactInputSingleParams {
IERC20 tokenIn;
IERC20 tokenOut;
uint24 fee;
address recipient;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
function exactInputSingle(ExactInputSingleParams calldata params)
external
payable
returns (uint256 amountOut);
function refundETH() external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IWETH is IERC20 {
function deposit() external payable;
function withdraw(uint256 amount) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface IWyvernV23 {
function registry() external view returns (address);
function tokenTransferProxy() external view returns (address);
function atomicMatch_(
address[14] calldata addrs,
uint256[18] calldata uints,
uint8[8] calldata feeMethodsSidesKindsHowToCalls,
bytes calldata calldataBuy,
bytes calldata calldataSell,
bytes calldata replacementPatternBuy,
bytes calldata replacementPatternSell,
bytes calldata staticExtradataBuy,
bytes calldata staticExtradataSell,
uint8[2] calldata vs,
bytes32[5] calldata rssMetadata
) external payable;
}
interface IWyvernV23ProxyRegistry {
function registerProxy() external;
function proxies(address user) external view returns (address);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
interface IX2Y2 {
struct OrderItem {
uint256 price;
bytes data;
}
struct ERC721Pair {
IERC721 token;
uint256 tokenId;
}
struct ERC1155Pair {
IERC1155 token;
uint256 tokenId;
uint256 amount;
}
struct Order {
uint256 salt;
address user;
uint256 network;
uint256 intent;
uint256 delegateType;
uint256 deadline;
IERC20 currency;
bytes dataMask;
OrderItem[] items;
bytes32 r;
bytes32 s;
uint8 v;
uint8 signVersion;
}
struct SettleShared {
uint256 salt;
uint256 deadline;
uint256 amountToEth;
uint256 amountToWeth;
address user;
bool canFail;
}
struct Fee {
uint256 percentage;
address to;
}
enum Op {
INVALID,
COMPLETE_SELL_OFFER,
COMPLETE_BUY_OFFER,
CANCEL_OFFER,
BID,
COMPLETE_AUCTION,
REFUND_AUCTION,
REFUND_AUCTION_STUCK_ITEM
}
struct SettleDetail {
Op op;
uint256 orderIdx;
uint256 itemIdx;
uint256 price;
bytes32 itemHash;
address executionDelegate;
bytes dataReplacement;
uint256 bidIncentivePct;
uint256 aucMinIncrementPct;
uint256 aucIncDurationSecs;
Fee[] fees;
}
struct RunInput {
Order[] orders;
SettleDetail[] details;
SettleShared shared;
bytes32 r;
bytes32 s;
uint8 v;
}
function run(RunInput calldata input) external payable;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
interface IZeroExV4 {
struct Property {
address propertyValidator;
bytes propertyData;
}
struct Fee {
address recipient;
uint256 amount;
bytes feeData;
}
struct ERC721Order {
uint8 direction;
address maker;
address taker;
uint256 expiry;
uint256 nonce;
IERC20 erc20Token;
uint256 erc20TokenAmount;
Fee[] fees;
IERC721 erc721Token;
uint256 erc721TokenId;
Property[] erc721TokenProperties;
}
struct ERC1155Order {
uint8 direction;
address maker;
address taker;
uint256 expiry;
uint256 nonce;
IERC20 erc20Token;
uint256 erc20TokenAmount;
Fee[] fees;
IERC1155 erc1155Token;
uint256 erc1155TokenId;
Property[] erc1155TokenProperties;
uint128 erc1155TokenAmount;
}
struct Signature {
uint8 signatureType;
uint8 v;
bytes32 r;
bytes32 s;
}
function buyERC721(
ERC721Order calldata sellOrder,
Signature calldata signature,
bytes memory callbackData
) external payable;
function batchBuyERC721s(
ERC721Order[] calldata sellOrders,
Signature[] calldata signatures,
bytes[] calldata callbackData,
bool revertIfIncomplete
) external payable returns (bool[] memory);
function sellERC721(
ERC721Order calldata buyOrder,
Signature calldata signature,
uint256 erc721TokenId,
bool unwrapNativeToken,
bytes memory callbackData
) external;
function buyERC1155(
ERC1155Order calldata sellOrder,
Signature calldata signature,
uint128 erc1155BuyAmount,
bytes calldata callbackData
) external payable;
function batchBuyERC1155s(
ERC1155Order[] calldata sellOrders,
Signature[] calldata signatures,
uint128[] calldata erc1155FillAmounts,
bytes[] calldata callbackData,
bool revertIfIncomplete
) external payable returns (bool[] memory successes);
function sellERC1155(
ERC1155Order calldata buyOrder,
Signature calldata signature,
uint256 erc1155TokenId,
uint128 erc1155SellAmount,
bool unwrapNativeToken,
bytes calldata callbackData
) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface IZora {
function fillAsk(
address tokenContract,
uint256 tokenId,
address fillCurrency,
uint256 fillAmount,
address finder
) external payable;
}// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.13;
import {CREATE3} from "solmate/src/utils/CREATE3.sol";
contract Create3Factory {
function deploy(
bytes32 salt,
bytes memory creationCode
) external payable returns (address deployed) {
salt = keccak256(abi.encodePacked(msg.sender, salt));
return CREATE3.deploy(salt, creationCode, msg.value);
}
function getDeployed(address deployer, bytes32 salt) external view returns (address deployed) {
salt = keccak256(abi.encodePacked(deployer, salt));
return CREATE3.getDeployed(salt);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
// Based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/metatx/ERC2771Context.sol
abstract contract ERC2771Context {
address private immutable _trustedForwarder;
constructor(address trustedForwarder) {
_trustedForwarder = trustedForwarder;
}
function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
return forwarder == _trustedForwarder;
}
function _msgSender() internal view virtual returns (address sender) {
if (isTrustedForwarder(msg.sender)) {
// The assembly code is more direct than the Solidity version using `abi.decode`
assembly {
sender := shr(96, calldataload(sub(calldatasize(), 20)))
}
} else {
return msg.sender;
}
}
function _msgData() internal view virtual returns (bytes calldata) {
if (isTrustedForwarder(msg.sender)) {
return msg.data[:msg.data.length - 20];
} else {
return msg.data;
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface IEIP2981 {
function royaltyInfo(
uint256,
uint256 price
) external view returns (address receiver, uint256 amount);
}
// Basic RoyaltyEngine-compliant wrapper around EIP2981
contract LiteRoyaltyEngine {
function getRoyaltyView(
address token,
uint256 tokenId,
uint256 price
) public view returns (address[] memory recipients, uint256[] memory amounts) {
recipients = new address[](1);
amounts = new uint256[](1);
(address recipient, uint256 amount) = IEIP2981(token).royaltyInfo(tokenId, price);
recipients[0] = recipient;
amounts[0] = amount;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ERC2771Context} from "./ERC2771Context.sol";
import {IEIP2612} from "../interfaces/IEIP2612.sol";
import {IReservoirV6_0_1} from "../interfaces/IReservoirV6_0_1.sol";
// Notes:
// - transfer ERC20 tokens via gasless EIP2612 permits
// - ERC2771-compliant for meta-transaction support
contract PermitProxy is ERC2771Context, ReentrancyGuard {
using SafeERC20 for IERC20;
// --- Structs ---
struct Transfer {
address recipient;
uint256 amount;
}
// https://eips.ethereum.org/EIPS/eip-2612
struct EIP2612Permit {
IERC20 token;
address owner;
address spender;
uint256 amount;
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}
struct EIP2612PermitWithTransfers {
EIP2612Permit permit;
Transfer[] transfers;
}
// --- Errors ---
error Unauthorized();
// --- Fields ---
IReservoirV6_0_1 internal immutable ROUTER;
// --- Constructor ---
constructor(address router, address trustedForwarder) ERC2771Context(trustedForwarder) {
ROUTER = IReservoirV6_0_1(router);
}
// --- Public methods ---
function eip2612PermitWithTransfersAndExecute(
EIP2612PermitWithTransfers[] calldata permitsWithTransfers,
IReservoirV6_0_1.ExecutionInfo[] calldata executionInfos
) external nonReentrant {
uint256 permitsWithTransfersLength = permitsWithTransfers.length;
for (uint256 i = 0; i < permitsWithTransfersLength; ) {
EIP2612PermitWithTransfers memory permitWithTransfers = permitsWithTransfers[i];
EIP2612Permit memory permit = permitWithTransfers.permit;
Transfer[] memory transfers = permitWithTransfers.transfers;
if (permit.owner != _msgSender()) {
revert Unauthorized();
}
IEIP2612(address(permit.token)).permit(
permit.owner,
permit.spender,
permit.amount,
permit.deadline,
permit.v,
permit.r,
permit.s
);
uint256 transfersLength = transfers.length;
for (uint256 j = 0; j < transfersLength; ) {
permit.token.safeTransferFrom(permit.owner, transfers[j].recipient, transfers[j].amount);
unchecked {
++j;
}
}
unchecked {
++i;
}
}
if (executionInfos.length > 0) {
ROUTER.execute(executionInfos);
}
}
function eip2612Permit(EIP2612Permit[] calldata permits) external nonReentrant {
uint256 permitsLength = permits.length;
for (uint256 i = 0; i < permitsLength; ) {
EIP2612Permit memory permit = permits[i];
IEIP2612(address(permit.token)).permit(
permit.owner,
permit.spender,
permit.amount,
permit.deadline,
permit.v,
permit.r,
permit.s
);
unchecked {
++i;
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {ICryptoPunksMarket} from "../interfaces/ICryptoPunksMarket.sol";
// Since the CryptoPunks are not ERC721 standard-compliant, you cannot create
// orders for them on modern exchange protocols like Seaport. The workarounds
// include using the wrapped version of CryptoPunks (cumbersome and costly to
// use) or using the native CryptoPunks exchange (it lacks features available
// available when using newer exchange protocols - off-chain orders, bids for
// the whole collection or for a set of attributes). To overcome all of these
// we created a new contract called `PunksProxy` which acts in a similiar way
// to the wrapped version of the CryptoPunks but in a zero-abstraction manner
// with everything abstracted out (eg. no need to wrap or unwrap). It acts as
// a standard ERC721 with the caveat that for any transfer operation there is
// a corresponding CryptoPunks-native approval (basically a private offer for
// a price of zero to the proxy contract).
contract PunksProxy {
using Address for address;
// --- Fields ---
ICryptoPunksMarket public constant EXCHANGE =
ICryptoPunksMarket(0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB);
mapping(uint256 => address) private tokenApprovals;
mapping(address => mapping(address => bool)) private operatorApprovals;
// --- Errors ---
error Unauthorized();
error UnsuccessfulSafeTransfer();
// --- ERC721 standard events ---
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
// --- ERC721 standard methods ---
function balanceOf(address owner) external view returns (uint256 balance) {
balance = EXCHANGE.balanceOf(owner);
}
function ownerOf(uint256 tokenId) public view returns (address owner) {
owner = EXCHANGE.punkIndexToAddress(tokenId);
}
function getApproved(uint256 tokenId) public view returns (address approved) {
approved = tokenApprovals[tokenId];
}
function isApprovedForAll(address owner, address operator) public view returns (bool approved) {
approved = operatorApprovals[owner][operator];
}
function approve(address to, uint256 tokenId) external {
address owner = ownerOf(tokenId);
if (msg.sender != owner && !isApprovedForAll(owner, msg.sender)) {
revert Unauthorized();
}
tokenApprovals[tokenId] = to;
emit Approval(owner, to, tokenId);
}
function setApprovalForAll(address operator, bool approved) external {
operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external {
transfer(from, to, tokenId);
checkOnERC721Received(from, to, tokenId, data);
}
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external {
transfer(from, to, tokenId);
checkOnERC721Received(from, to, tokenId, "");
}
function transferFrom(
address from,
address to,
uint256 tokenId
) external {
transfer(from, to, tokenId);
}
function transfer(
address from,
address to,
uint256 tokenId
) internal {
address owner = ownerOf(tokenId);
if (from != owner) {
revert Unauthorized();
}
if (
msg.sender != owner &&
getApproved(tokenId) != msg.sender &&
!isApprovedForAll(owner, msg.sender)
) {
revert Unauthorized();
}
EXCHANGE.buyPunk(tokenId);
EXCHANGE.transferPunk(to, tokenId);
emit Transfer(from, to, tokenId);
}
function checkOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory data
) private {
if (to.isContract()) {
try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (
bytes4 result
) {
if (result != IERC721Receiver.onERC721Received.selector) {
revert UnsuccessfulSafeTransfer();
}
} catch {
revert UnsuccessfulSafeTransfer();
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IConduit, IConduitController} from "../interfaces/IConduit.sol";
import {IReservoirV6_0_1} from "../interfaces/IReservoirV6_0_1.sol";
// Forked from:
// https://github.com/ProjectOpenSea/seaport/blob/b13939729001cb12f715d7b73422aafeca0bcd0d/contracts/helpers/TransferHelper.sol
contract ReservoirApprovalProxy is ReentrancyGuard {
// --- Structs ---
struct TransferHelperItem {
IConduit.ConduitItemType itemType;
address token;
uint256 identifier;
uint256 amount;
}
struct TransferHelperItemsWithRecipient {
TransferHelperItem[] items;
address recipient;
}
// --- Errors ---
error ConduitExecutionFailed();
error InvalidRecipient();
// --- Fields ---
IConduitController internal immutable _CONDUIT_CONTROLLER;
bytes32 internal immutable _CONDUIT_CREATION_CODE_HASH;
bytes32 internal immutable _CONDUIT_RUNTIME_CODE_HASH;
IReservoirV6_0_1 internal immutable _ROUTER;
// --- Constructor ---
constructor(address conduitController, address router) {
IConduitController controller = IConduitController(conduitController);
(_CONDUIT_CREATION_CODE_HASH, _CONDUIT_RUNTIME_CODE_HASH) = controller.getConduitCodeHashes();
_CONDUIT_CONTROLLER = controller;
_ROUTER = IReservoirV6_0_1(router);
}
// --- Public methods ---
function bulkTransferWithExecute(
TransferHelperItemsWithRecipient[] calldata transfers,
IReservoirV6_0_1.ExecutionInfo[] calldata executionInfos,
bytes32 conduitKey
) external nonReentrant {
uint256 numTransfers = transfers.length;
address conduit = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
bytes1(0xff),
address(_CONDUIT_CONTROLLER),
conduitKey,
_CONDUIT_CREATION_CODE_HASH
)
)
)
)
);
uint256 sumOfItemsAcrossAllTransfers;
unchecked {
for (uint256 i = 0; i < numTransfers; ++i) {
TransferHelperItemsWithRecipient calldata transfer = transfers[i];
sumOfItemsAcrossAllTransfers += transfer.items.length;
}
}
IConduit.ConduitTransfer[] memory conduitTransfers = new IConduit.ConduitTransfer[](
sumOfItemsAcrossAllTransfers
);
uint256 itemIndex;
unchecked {
for (uint256 i = 0; i < numTransfers; ++i) {
TransferHelperItemsWithRecipient calldata transfer = transfers[i];
TransferHelperItem[] calldata transferItems = transfer.items;
_checkRecipientIsNotZeroAddress(transfer.recipient);
uint256 numItemsInTransfer = transferItems.length;
for (uint256 j = 0; j < numItemsInTransfer; ++j) {
TransferHelperItem calldata item = transferItems[j];
conduitTransfers[itemIndex] = IConduit.ConduitTransfer(
item.itemType,
item.token,
msg.sender,
transfer.recipient,
item.identifier,
item.amount
);
++itemIndex;
}
}
}
bytes4 conduitMagicValue = IConduit(conduit).execute(conduitTransfers);
if (conduitMagicValue != IConduit.execute.selector) {
revert ConduitExecutionFailed();
}
_ROUTER.execute(executionInfos);
}
function _checkRecipientIsNotZeroAddress(address recipient) internal pure {
if (recipient == address(0x0)) {
revert InvalidRecipient();
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
// Adapted from:
// https://github.com/boringcrypto/BoringSolidity/blob/e74c5b22a61bfbadd645e51a64aa1d33734d577a/contracts/BoringOwnable.sol
contract TwoStepOwnable {
// --- Fields ---
address public owner;
address public pendingOwner;
// --- Events ---
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// --- Errors ---
error InvalidParams();
error Unauthorized();
// --- Modifiers ---
modifier onlyOwner() {
if (msg.sender != owner) {
revert Unauthorized();
}
_;
}
// --- Constructor ---
constructor(address initialOwner) {
owner = initialOwner;
emit OwnershipTransferred(address(0), initialOwner);
}
// --- Methods ---
function transferOwnership(address newOwner) public onlyOwner {
pendingOwner = newOwner;
}
function claimOwnership() public {
address _pendingOwner = pendingOwner;
if (msg.sender != _pendingOwner) {
revert Unauthorized();
}
owner = _pendingOwner;
pendingOwner = address(0);
emit OwnershipTransferred(owner, _pendingOwner);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract WETH9 {
string public name = "Wrapped Ether";
string public symbol = "WETH";
uint8 public decimals = 18;
event Approval(address indexed src, address indexed guy, uint wad);
event Transfer(address indexed src, address indexed dst, uint wad);
event Deposit(address indexed dst, uint wad);
event Withdrawal(address indexed src, uint wad);
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => uint)) public allowance;
receive() external payable {
deposit();
}
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint wad) public {
require(balanceOf[msg.sender] >= wad);
balanceOf[msg.sender] -= wad;
payable(msg.sender).transfer(wad);
emit Withdrawal(msg.sender, wad);
}
function totalSupply() public view returns (uint) {
return address(this).balance;
}
function approve(address guy, uint wad) public returns (bool) {
allowance[msg.sender][guy] = wad;
emit Approval(msg.sender, guy, wad);
return true;
}
function transfer(address dst, uint wad) public returns (bool) {
return transferFrom(msg.sender, dst, wad);
}
function transferFrom(address src, address dst, uint wad) public returns (bool) {
require(balanceOf[src] >= wad);
if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
require(allowance[src][msg.sender] >= wad);
allowance[src][msg.sender] -= wad;
}
balanceOf[src] -= wad;
balanceOf[dst] += wad;
emit Transfer(src, dst, wad);
return true;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
contract MockERC1155 is ERC1155 {
uint256 public nextTokenId;
constructor() ERC1155("https://mock.com") {}
function mint(uint256 tokenId) external {
_mint(msg.sender, tokenId, 1, "");
}
function mintMany(uint256 tokenId, uint256 amount) external {
_mint(msg.sender, tokenId, amount, "");
}
function mintWithPrice(uint256 amount, uint256 price) external payable {
require(msg.value == price * amount, "Insufficient value");
_mint(msg.sender, nextTokenId++, amount, "");
}
function fail() external pure {
revert();
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockERC20 is ERC20 {
constructor() ERC20("Mock", "MOCK") {}
function mint(uint256 tokenId) external {
_mint(msg.sender, tokenId);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract MockERC721 is ERC721 {
uint256 public nextTokenId;
constructor() ERC721("Mock", "MOCK") {}
function mint(uint256 tokenId) external {
_safeMint(msg.sender, tokenId);
}
function mintWithPrice(uint256 price) external payable {
require(msg.value == price, "Insufficient value");
_safeMint(msg.sender, nextTokenId++);
}
function fail() external pure {
revert();
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@limitbreak/creator-token-contracts/contracts/access/OwnableBasic.sol";
import "@limitbreak/creator-token-contracts/contracts/erc721c/ERC721C.sol";
contract MockERC721C is OwnableBasic, ERC721C {
uint256 public nextTokenId;
constructor() ERC721OpenZeppelin("Mock", "MOCK") {}
function mint(uint256 tokenId) external {
_safeMint(msg.sender, tokenId);
}
function mintWithPrice(uint256 price) external payable {
require(msg.value == price, "Insufficient value");
_safeMint(msg.sender, nextTokenId++);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {BaseModule} from "./BaseModule.sol";
// When sniping NFTs, a lot of gas is lost when someone else's fill transaction
// gets included right before. To optimize the amount of gas that is lost, this
// module performs a balance/owner check so that we revert as early as possible
// and spend as few gas as possible.
contract BalanceAssertModule {
// --- Errors ---
error AssertFailed();
// --- [ERC721] Single assert ---
function assertERC721Owner(
IERC721 token,
uint256 tokenId,
address owner
) external view {
address actualOwner = token.ownerOf(tokenId);
if (owner != actualOwner) {
revert AssertFailed();
}
}
// --- [ERC1155] Single assert ---
function assertERC1155Balance(
IERC1155 token,
uint256 tokenId,
address owner,
uint256 balance
) external view {
uint256 actualBalance = token.balanceOf(owner, tokenId);
if (balance < actualBalance) {
revert AssertFailed();
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {TwoStepOwnable} from "../../misc/TwoStepOwnable.sol";
// Notes:
// - includes common helpers useful for all modules
abstract contract BaseModule is TwoStepOwnable, ReentrancyGuard {
using SafeERC20 for IERC20;
// --- Events ---
event CallExecuted(address target, bytes data, uint256 value);
// --- Errors ---
error UnsuccessfulCall();
error UnsuccessfulPayment();
error WrongParams();
// --- Constructor ---
constructor(address owner) TwoStepOwnable(owner) {}
// --- Owner ---
// To be able to recover anything that gets stucked by mistake in the module,
// we allow the owner to perform any arbitrary call. Since the goal is to be
// stateless, this should only happen in case of mistakes. In addition, this
// method is also useful for withdrawing any earned trading rewards.
function makeCalls(
address[] calldata targets,
bytes[] calldata data,
uint256[] calldata values
) external payable onlyOwner nonReentrant {
uint256 length = targets.length;
for (uint256 i = 0; i < length; ) {
_makeCall(targets[i], data[i], values[i]);
emit CallExecuted(targets[i], data[i], values[i]);
unchecked {
++i;
}
}
}
// --- Helpers ---
function _sendETH(address to, uint256 amount) internal {
if (amount > 0) {
(bool success, ) = payable(to).call{value: amount}("");
if (!success) {
revert UnsuccessfulPayment();
}
}
}
function _sendERC20(
address to,
uint256 amount,
IERC20 token
) internal {
if (amount > 0) {
token.safeTransfer(to, amount);
}
}
function _makeCall(
address target,
bytes memory data,
uint256 value
) internal {
(bool success, ) = payable(target).call{value: value}(data);
if (!success) {
revert UnsuccessfulCall();
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {ISeaport} from "../../../interfaces/ISeaport.sol";
// Notes on the Seaport module:
// - supports filling listings (both ERC721/ERC1155)
// - supports filling offers (both ERC721/ERC1155)
contract AlienswapModule is BaseExchangeModule {
// --- Structs ---
struct SeaportETHListingWithPrice {
ISeaport.AdvancedOrder order;
uint256 price;
}
// --- Fields ---
ISeaport public immutable EXCHANGE;
// --- Constructor ---
constructor(
address owner,
address router,
address exchange
) BaseModule(owner) BaseExchangeModule(router) {
EXCHANGE = ISeaport(exchange);
}
// --- Fallback ---
receive() external payable {}
// --- Single ETH listing ---
function acceptETHListing(
ISeaport.AdvancedOrder calldata order,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(
order,
new ISeaport.CriteriaResolver[](0),
params.fillTo,
params.amount
)
: _fillSingleOrder(order, new ISeaport.CriteriaResolver[](0), params.fillTo, params.amount);
}
// --- Single ERC20 listing ---
function acceptERC20Listing(
ISeaport.AdvancedOrder calldata order,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(
order,
new ISeaport.CriteriaResolver[](0),
params.fillTo,
0
)
: _fillSingleOrder(order, new ISeaport.CriteriaResolver[](0), params.fillTo, 0);
}
// --- Multiple ETH listings ---
function acceptETHListings(
SeaportETHListingWithPrice[] calldata orders,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256 length = orders.length;
ISeaport.CriteriaResolver[] memory criteriaResolvers = new ISeaport.CriteriaResolver[](0);
// Execute the fills
if (params.revertIfIncomplete) {
for (uint256 i; i < length; ) {
_fillSingleOrderWithRevertIfIncomplete(
orders[i].order,
criteriaResolvers,
params.fillTo,
orders[i].price
);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < length; ) {
_fillSingleOrder(orders[i].order, criteriaResolvers, params.fillTo, orders[i].price);
unchecked {
++i;
}
}
}
}
// --- Multiple ERC20 listings ---
function acceptERC20Listings(
ISeaport.AdvancedOrder[] calldata orders,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
uint256 length = orders.length;
ISeaport.CriteriaResolver[] memory criteriaResolvers = new ISeaport.CriteriaResolver[](0);
// Execute the fills
if (params.revertIfIncomplete) {
for (uint256 i; i < length; ) {
_fillSingleOrderWithRevertIfIncomplete(orders[i], criteriaResolvers, params.fillTo, 0);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < length; ) {
_fillSingleOrder(orders[i], criteriaResolvers, params.fillTo, 0);
unchecked {
++i;
}
}
}
}
// --- Single ERC721 offer ---
function acceptERC721Offer(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
// Extract the ERC721 token from the consideration items
ISeaport.ConsiderationItem calldata nftItem = order.parameters.consideration[0];
if (
nftItem.itemType != ISeaport.ItemType.ERC721 &&
nftItem.itemType != ISeaport.ItemType.ERC721_WITH_CRITERIA
) {
revert WrongParams();
}
IERC721 nftToken = IERC721(nftItem.token);
// Extract the payment token from the offer items
ISeaport.OfferItem calldata paymentItem = order.parameters.offer[0];
IERC20 paymentToken = IERC20(paymentItem.token);
// Approve the exchange if needed
_approveERC721IfNeeded(nftToken, address(EXCHANGE));
_approveERC20IfNeeded(paymentToken, address(EXCHANGE), type(uint256).max);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(order, criteriaResolvers, address(this), 0)
: _fillSingleOrder(order, criteriaResolvers, address(this), 0);
uint256 identifier = nftItem.itemType == ISeaport.ItemType.ERC721
? nftItem.identifierOrCriteria
: criteriaResolvers[0].identifier;
// Pay fees
if (nftToken.ownerOf(identifier) != address(this)) {
// Only pay fees if the fill was successful
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, paymentToken);
unchecked {
++i;
}
}
}
// Refund any ERC721 leftover
_sendAllERC721(params.refundTo, nftToken, identifier);
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, paymentToken);
}
// --- Single ERC1155 offer ---
function acceptERC1155Offer(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
// Extract the ERC1155 token from the consideration items
ISeaport.ConsiderationItem calldata nftItem = order.parameters.consideration[0];
if (
nftItem.itemType != ISeaport.ItemType.ERC1155 &&
nftItem.itemType != ISeaport.ItemType.ERC1155_WITH_CRITERIA
) {
revert WrongParams();
}
IERC1155 nftToken = IERC1155(nftItem.token);
// Extract the payment token from the offer items
ISeaport.OfferItem calldata paymentItem = order.parameters.offer[0];
IERC20 paymentToken = IERC20(paymentItem.token);
// Approve the exchange if needed
_approveERC1155IfNeeded(nftToken, address(EXCHANGE));
_approveERC20IfNeeded(paymentToken, address(EXCHANGE), type(uint256).max);
uint256 identifier = nftItem.itemType == ISeaport.ItemType.ERC1155
? nftItem.identifierOrCriteria
: criteriaResolvers[0].identifier;
uint256 balanceBefore = nftToken.balanceOf(address(this), identifier);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(order, criteriaResolvers, address(this), 0)
: _fillSingleOrder(order, criteriaResolvers, address(this), 0);
uint256 balanceAfter = nftToken.balanceOf(address(this), identifier);
// Pay fees
uint256 amountFilled = balanceBefore - balanceAfter;
if (amountFilled > 0) {
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(
fee.recipient,
// Only pay fees for the amount that was actually filled
(fee.amount * amountFilled) / order.numerator,
paymentToken
);
unchecked {
++i;
}
}
}
// Refund any ERC1155 leftover
_sendAllERC1155(params.refundTo, nftToken, identifier);
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, paymentToken);
}
// --- Generic handler (used for Seaport-based approvals) ---
function matchOrders(
ISeaport.Order[] calldata orders,
ISeaport.Fulfillment[] calldata fulfillments
) external nonReentrant {
// We don't perform any kind of input or return value validation,
// so this function should be used with precaution - the official
// way to use it is only for Seaport-based approvals
EXCHANGE.matchOrders(orders, fulfillments);
}
// --- ERC721 / ERC1155 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
// --- Internal ---
// NOTE: In lots of cases, Seaport will not revert if fills were not
// fully executed. An example of that is partial filling, which will
// successfully fill any amount that is still available (including a
// zero amount). One way to ensure that we revert in case of partial
// executions is to check the order's filled amount before and after
// we trigger the fill (we can use Seaport's `getOrderStatus` method
// to check). Since this can be expensive in terms of gas, we have a
// separate method variant to be called when reverts are enabled.
function _fillSingleOrder(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
address receiver,
uint256 value
) internal {
// Execute the fill
try
EXCHANGE.fulfillAdvancedOrder{value: value}(order, criteriaResolvers, bytes32(0), receiver)
{} catch {}
}
function _fillSingleOrderWithRevertIfIncomplete(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
address receiver,
uint256 value
) internal {
// Cache the order's hash
bytes32 orderHash = _getOrderHash(order.parameters);
// Before filling, get the order's filled amount
uint256 beforeFilledAmount = _getFilledAmount(orderHash);
// Execute the fill
bool success;
try
EXCHANGE.fulfillAdvancedOrder{value: value}(order, criteriaResolvers, bytes32(0), receiver)
returns (bool fulfilled) {
success = fulfilled;
} catch {
revert UnsuccessfulFill();
}
if (!success) {
revert UnsuccessfulFill();
} else {
// After successfully filling, get the order's filled amount
uint256 afterFilledAmount = _getFilledAmount(orderHash);
// Make sure the amount filled as part of this call is correct
if (afterFilledAmount - beforeFilledAmount != order.numerator) {
revert UnsuccessfulFill();
}
}
}
function _getOrderHash(
// Must use `memory` instead of `calldata` for the below cast
ISeaport.OrderParameters memory orderParameters
) internal view returns (bytes32 orderHash) {
// `OrderParameters` and `OrderComponents` share the exact same
// fields, apart from the last one, so here we simply treat the
// `orderParameters` argument as `OrderComponents` and then set
// the last field to the correct data
ISeaport.OrderComponents memory orderComponents;
assembly {
orderComponents := orderParameters
}
orderComponents.counter = EXCHANGE.getCounter(orderParameters.offerer);
orderHash = EXCHANGE.getOrderHash(orderComponents);
}
function _getFilledAmount(bytes32 orderHash) internal view returns (uint256 totalFilled) {
(, , totalFilled, ) = EXCHANGE.getOrderStatus(orderHash);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {BaseModule} from "../BaseModule.sol";
// Notes:
// - includes common helpers useful for all marketplace/exchange modules
abstract contract BaseExchangeModule is BaseModule {
using SafeERC20 for IERC20;
// --- Structs ---
// Every fill execution has the following parameters:
// - `fillTo`: the recipient of the received items
// - `refundTo`: the recipient of any refunds
// - `revertIfIncomplete`: whether to revert or skip unsuccessful fills
// The below `ETHListingParams` and `ERC20ListingParams` rely on the
// off-chain execution encoder to ensure that the orders filled with
// the passed in listing parameters exactly match (eg. order amounts
// and payment tokens match).
struct ETHListingParams {
address fillTo;
address refundTo;
bool revertIfIncomplete;
// The total amount of ETH to be provided when filling
uint256 amount;
}
struct ERC20ListingParams {
address fillTo;
address refundTo;
bool revertIfIncomplete;
// The ERC20 payment token for the listings
IERC20 token;
// The total amount of `token` to be provided when filling
uint256 amount;
}
struct OfferParams {
address fillTo;
address refundTo;
bool revertIfIncomplete;
}
struct Fee {
address recipient;
uint256 amount;
}
// --- Fields ---
address public immutable router;
// --- Errors ---
error UnsuccessfulFill();
// --- Constructor ---
constructor(address routerAddress) {
router = routerAddress;
}
// --- Modifiers ---
modifier refundETHLeftover(address refundTo) {
_;
uint256 leftover = address(this).balance;
if (leftover > 0) {
_sendETH(refundTo, leftover);
}
}
modifier refundERC20Leftover(address refundTo, IERC20 token) {
_;
uint256 leftover = token.balanceOf(address(this));
if (leftover > 0) {
token.safeTransfer(refundTo, leftover);
}
}
modifier chargeETHFees(Fee[] calldata fees, uint256 amount) {
if (fees.length == 0) {
_;
} else {
uint256 balanceBefore = address(this).balance;
_;
uint256 length = fees.length;
if (length > 0) {
uint256 balanceAfter = address(this).balance;
uint256 actualPaid = balanceBefore - balanceAfter;
uint256 actualFee;
for (uint256 i = 0; i < length; ) {
// Adjust the fee to what was actually paid
actualFee = (fees[i].amount * actualPaid) / amount;
if (actualFee > 0) {
_sendETH(fees[i].recipient, actualFee);
}
unchecked {
++i;
}
}
}
}
}
modifier chargeERC20Fees(
Fee[] calldata fees,
IERC20 token,
uint256 amount
) {
if (fees.length == 0) {
_;
} else {
uint256 balanceBefore = token.balanceOf(address(this));
_;
uint256 length = fees.length;
if (length > 0) {
uint256 balanceAfter = token.balanceOf(address(this));
uint256 actualPaid = balanceBefore - balanceAfter;
uint256 actualFee;
for (uint256 i = 0; i < length; ) {
// Adjust the fee to what was actually paid
actualFee = (fees[i].amount * actualPaid) / amount;
if (actualFee > 0) {
token.safeTransfer(fees[i].recipient, actualFee);
}
unchecked {
++i;
}
}
}
}
}
// --- Helpers ---
function _sendAllETH(address to) internal {
_sendETH(to, address(this).balance);
}
function _sendAllERC20(address to, IERC20 token) internal {
uint256 balance = token.balanceOf(address(this));
if (balance > 0) {
token.safeTransfer(to, balance);
}
}
function _sendAllERC721(address to, IERC721 token, uint256 tokenId) internal {
if (token.ownerOf(tokenId) == address(this)) {
token.safeTransferFrom(address(this), to, tokenId);
}
}
function _sendAllERC1155(address to, IERC1155 token, uint256 tokenId) internal {
uint256 balance = token.balanceOf(address(this), tokenId);
if (balance > 0) {
token.safeTransferFrom(address(this), to, tokenId, balance, "");
}
}
function _approveERC20IfNeeded(IERC20 token, address spender, uint256 amount) internal {
uint256 allowance = token.allowance(address(this), spender);
if (allowance < amount) {
// Some contracts revert if trying to approve starting from a non-zero amount
if (allowance > 0) {
_approveERC20(address(token), spender, 0);
}
_approveERC20(address(token), spender, amount);
}
}
function _approveERC721IfNeeded(IERC721 token, address operator) internal {
bool isApproved = token.isApprovedForAll(address(this), operator);
if (!isApproved) {
token.setApprovalForAll(operator, true);
}
}
function _approveERC1155IfNeeded(IERC1155 token, address operator) internal {
bool isApproved = token.isApprovedForAll(address(this), operator);
if (!isApproved) {
token.setApprovalForAll(operator, true);
}
}
function _approveERC20(address token, address spender, uint256 amount) internal {
// To avoid extra calldata padding (which some contracts reject)
(bool success, ) = token.call(
abi.encodeWithSignature("approve(address,uint256)", spender, amount)
);
if (!success) {
revert UnsuccessfulFill();
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {ICaviarPoolV1} from "../../../interfaces/ICaviarV1.sol";
contract CaviarV1Module is BaseExchangeModule {
// --- Constructor ---
constructor(address owner, address router) BaseModule(owner) BaseExchangeModule(router) {}
// --- Fallback ---
receive() external payable {}
// --- Multiple ETH listings ---
function buyWithETH(
ICaviarPoolV1[] calldata pairs,
uint256[] calldata nftIds,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256[] memory tokenIds = new uint256[](1);
uint256 pairsLength = pairs.length;
for (uint256 i; i < pairsLength; ) {
// Fetch the current price
uint256 inputAmount = pairs[i].buyQuote(1e18);
uint256 tokenId = nftIds[i];
tokenIds[0] = tokenId;
// Execute fill
try pairs[i].nftBuy{value: inputAmount}(tokenIds, inputAmount, 0) {
_sendAllERC721(params.fillTo, IERC721(pairs[i].nft()), tokenId);
} catch {
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
unchecked {
++i;
}
}
}
// --- Single offer ---
function sell(
ICaviarPoolV1 pair,
uint256 nftId,
uint256 minOutput,
ICaviarPoolV1.Message calldata stolenProof,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
address nft = pair.nft();
// Approve the pair if needed
_approveERC721IfNeeded(IERC721(nft), address(pair));
// Build router data
uint256[] memory tokenIds = new uint256[](1);
bytes32[][] memory proofs = new bytes32[][](0);
ICaviarPoolV1.Message[] memory stolenProofs = new ICaviarPoolV1.Message[](1);
tokenIds[0] = nftId;
stolenProofs[0] = stolenProof;
// Execute fill
try pair.nftSell(tokenIds, minOutput, 0, proofs, stolenProofs) {
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendETH(fee.recipient, fee.amount);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllETH(params.fillTo);
} catch {
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any leftovers
_sendAllERC721(params.refundTo, IERC721(nft), nftId);
}
// --- ERC721/1155 hooks ---
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {ICryptoPunksMarket} from "../../../interfaces/ICryptoPunksMarket.sol";
contract CryptoPunksModule is BaseExchangeModule {
// --- Fields ---
ICryptoPunksMarket public immutable MARKETPLACE;
// --- Constructor ---
constructor(
address owner,
address router,
address marketplace
) BaseModule(owner) BaseExchangeModule(router) {
MARKETPLACE = ICryptoPunksMarket(marketplace);
}
// --- Fallback ---
receive() external payable {}
// --- Multiple buy Punks ---
function batchBuyPunksWithETH(
ICryptoPunksMarket.BuyOrder[] calldata buyOrders,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256 length = buyOrders.length;
for (uint256 i = 0; i < length; ) {
// Execute fill
_buy(buyOrders[i], params.revertIfIncomplete);
unchecked {
++i;
}
}
}
function _buy(ICryptoPunksMarket.BuyOrder calldata buyOrder, bool revertIfIncomplete) internal {
try MARKETPLACE.buyPunk{value: buyOrder.price}(buyOrder.punkIndex) {
// Transfer the punk to the receiver
MARKETPLACE.transferPunk(buyOrder.buyer, buyOrder.punkIndex);
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {IDittoPool} from "../../../interfaces/IDittoPool.sol";
struct DittoOrderParams {
address tokenSender;
uint256[] nftIds;
bytes swapData;
}
contract DittoModule is BaseExchangeModule {
using SafeERC20 for IERC20;
// --- Constructor ---
constructor(address owner, address router) BaseModule(owner) BaseExchangeModule(router) {}
// --- Helper methods ---
function poolTransferNftFrom(IERC721 token, address from, address to, uint256 tokenId) external {
token.transferFrom(from, to, tokenId);
}
function poolTransferErc20From(
IERC20 token,
address from,
address to,
uint256 amount
) external virtual {
if (from == address(this)) {
token.safeTransfer(to, amount);
} else {
token.safeTransferFrom(from, to, amount);
}
}
// --- Multiple ERC20 listing ---
function buyWithERC20(
IDittoPool[] calldata pairs,
DittoOrderParams[] calldata orderParams,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
uint256 pairsLength = pairs.length;
for (uint256 i; i < pairsLength; ) {
// Execute fill
IDittoPool.SwapTokensForNftsArgs memory args = IDittoPool.SwapTokensForNftsArgs({
nftIds: orderParams[i].nftIds,
maxExpectedTokenInput: params.amount,
tokenSender: orderParams[i].tokenSender,
nftRecipient: params.fillTo,
swapData: orderParams[i].swapData
});
pairs[i].swapTokensForNfts(args);
unchecked {
++i;
}
}
}
// --- Single ERC721 offer ---
function sell(
IDittoPool pool,
DittoOrderParams calldata orderParams,
uint256[] calldata lpIds,
bytes calldata permitterData,
uint256 minOutput,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
IERC20 token = pool.token();
IDittoPool.SwapNftsForTokensArgs memory args = IDittoPool.SwapNftsForTokensArgs({
nftIds: orderParams.nftIds,
lpIds: lpIds,
minExpectedTokenOutput: minOutput,
nftSender: orderParams.tokenSender,
tokenRecipient: params.fillTo,
permitterData: permitterData,
swapData: orderParams.swapData
});
pool.swapNftsForTokens(args);
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, token);
unchecked {
++i;
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {IElement} from "../../../interfaces/IElement.sol";
// Notes:
// - supports filling listings (both ERC721/ERC1155)
// - supports filling offers (both ERC721/ERC1155)
contract ElementModule is BaseExchangeModule {
using SafeERC20 for IERC20;
// --- Fields ---
IElement public immutable EXCHANGE;
// --- Constructor ---
constructor(
address owner,
address router,
address exchange
) BaseModule(owner) BaseExchangeModule(router) {
EXCHANGE = IElement(exchange);
}
// --- Fallback ---
receive() external payable {}
// --- [ERC721] Single ETH listing ---
function acceptETHListingERC721(
IElement.NFTSellOrder calldata order,
IElement.Signature calldata signature,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buyERC721Ex(order, signature, params.fillTo, params.revertIfIncomplete, params.amount);
}
// --- [ERC721] Single ERC20 listing ---
function acceptERC20ListingERC721(
IElement.NFTSellOrder calldata order,
IElement.Signature calldata signature,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute fill
_buyERC721Ex(order, signature, params.fillTo, params.revertIfIncomplete, 0);
}
// --- [ERC721] Multiple ETH listings ---
function acceptETHListingsERC721(
IElement.NFTSellOrder[] calldata orders,
IElement.Signature[] calldata signatures,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buyERC721sEx(orders, signatures, params.fillTo, params.revertIfIncomplete, params.amount);
}
// --- [ERC721] Multiple ERC20 listings ---
function acceptERC20ListingsERC721(
IElement.NFTSellOrder[] calldata orders,
IElement.Signature[] calldata signatures,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute fill
_buyERC721sEx(orders, signatures, params.fillTo, params.revertIfIncomplete, 0);
}
// --- [ERC721] Single ETH listing V2 ---
function acceptETHListingERC721V2(
IElement.BatchSignedOrder calldata order,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_fillBatchSignedOrder(order, params.fillTo, params.revertIfIncomplete, params.amount);
}
// --- [ERC721] Single ERC20 listing V2 ---
function acceptERC20ListingERC721V2(
IElement.BatchSignedOrder calldata order,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute fill
_fillBatchSignedOrder(order, params.fillTo, params.revertIfIncomplete, 0);
}
// --- [ERC721] Multiple ETH listings V2 ---
function acceptETHListingsERC721V2(
IElement.BatchSignedOrder[] calldata orders,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_fillBatchSignedOrders(orders, params.fillTo, params.revertIfIncomplete, params.amount);
}
// --- [ERC721] Multiple ERC20 listings V2 ---
function acceptERC20ListingsERC721V2(
IElement.BatchSignedOrder[] calldata orders,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute fill
_fillBatchSignedOrders(orders, params.fillTo, params.revertIfIncomplete, 0);
}
// --- [ERC1155] Single ETH listing ---
function acceptETHListingERC1155(
IElement.ERC1155SellOrder calldata order,
IElement.Signature calldata signature,
uint128 amount,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buyERC1155Ex(
order,
signature,
amount,
params.fillTo,
params.revertIfIncomplete,
params.amount
);
}
// --- [ERC1155] Single ERC20 listing ---
function acceptERC20ListingERC1155(
IElement.ERC1155SellOrder calldata order,
IElement.Signature calldata signature,
uint128 amount,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute fill
_buyERC1155Ex(order, signature, amount, params.fillTo, params.revertIfIncomplete, 0);
}
// --- [ERC1155] Multiple ETH listings ---
function acceptETHListingsERC1155(
IElement.ERC1155SellOrder[] calldata orders,
IElement.Signature[] calldata signatures,
uint128[] calldata amounts,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buyERC1155sEx(
orders,
signatures,
amounts,
params.fillTo,
params.revertIfIncomplete,
params.amount
);
}
// --- [ERC1155] Multiple ERC20 listings ---
function acceptERC20ListingsERC1155(
IElement.ERC1155SellOrder[] calldata orders,
IElement.Signature[] calldata signatures,
uint128[] calldata amounts,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute fill
_buyERC1155sEx(orders, signatures, amounts, params.fillTo, params.revertIfIncomplete, 0);
}
// --- [ERC721] Single offer ---
function acceptERC721Offer(
IElement.NFTBuyOrder calldata order,
IElement.Signature calldata signature,
OfferParams calldata params,
uint256 tokenId,
Fee[] calldata fees
) external nonReentrant {
// Approve the exchange if needed
_approveERC721IfNeeded(IERC721(order.nft), address(EXCHANGE));
// Execute fill
try EXCHANGE.sellERC721(order, signature, tokenId, false, "") {
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, order.erc20Token);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, order.erc20Token);
} catch {
// Revert if specified
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any ERC721 leftover
_sendAllERC721(params.refundTo, IERC721(order.nft), tokenId);
}
// --- [ERC1155] Single offer ---
function acceptERC1155Offer(
IElement.ERC1155BuyOrder calldata order,
IElement.Signature calldata signature,
uint128 amount,
OfferParams calldata params,
uint256 tokenId,
Fee[] calldata fees
) external nonReentrant {
// Approve the exchange if needed
_approveERC1155IfNeeded(IERC1155(order.erc1155Token), address(EXCHANGE));
// Execute fill
try EXCHANGE.sellERC1155(order, signature, tokenId, amount, false, "") {
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, order.erc20Token);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, order.erc20Token);
} catch {
// Revert if specified
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any ERC1155 leftover
_sendAllERC1155(params.refundTo, IERC1155(order.erc1155Token), tokenId);
}
// --- ERC721 / ERC1155 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
// --- Internal ---
function _buyERC721Ex(
IElement.NFTSellOrder calldata order,
IElement.Signature calldata signature,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
// Execute fill
try EXCHANGE.buyERC721Ex{value: value}(order, signature, receiver, "") {} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
function _buyERC721sEx(
IElement.NFTSellOrder[] calldata orders,
IElement.Signature[] calldata signatures,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
uint256 length = orders.length;
address[] memory takers = new address[](length);
for (uint256 i; i < length; ) {
takers[i] = receiver;
unchecked {
++i;
}
}
// Execute fill
try
EXCHANGE.batchBuyERC721sEx{value: value}(
orders,
signatures,
takers,
new bytes[](length),
revertIfIncomplete
)
{} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
function _fillBatchSignedOrder(
IElement.BatchSignedOrder calldata order,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
IElement.Parameter memory parameter;
parameter.r = order.r;
parameter.s = order.s;
// data1 [56 bits(startNonce) + 8 bits(v) + 32 bits(listingTime) + 160 bits(maker)]
parameter.data1 =
(order.startNonce << 200) |
(uint256(order.v) << 192) |
(order.listingTime << 160) |
uint256(uint160(order.maker));
// data2 [64 bits(taker part1) + 32 bits(expiryTime) + 160 bits(erc20Token)]
uint256 taker = uint256(uint160(receiver));
parameter.data2 =
((taker >> 96) << 192) |
(order.expirationTime << 160) |
uint256(uint160(order.erc20Token));
// data3 [96 bits(taker part2) + 160 bits(platformFeeRecipient)]
parameter.data3 = (taker << 160) | uint256(uint160(order.platformFeeRecipient));
// Execute fill
try
EXCHANGE.fillBatchSignedERC721Order{value: value}(parameter, order.collectionsBytes)
{} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
function _fillBatchSignedOrders(
IElement.BatchSignedOrder[] calldata orders,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
uint256 length = orders.length;
uint256 taker = uint256(uint160(receiver));
IElement.Parameters[] memory parameters = new IElement.Parameters[](length);
for (uint256 i; i < length; ) {
IElement.BatchSignedOrder calldata order = orders[i];
IElement.Parameters memory parameter;
parameter.r = order.r;
parameter.s = order.s;
parameter.collections = order.collectionsBytes;
// data1 [56 bits(startNonce) + 8 bits(v) + 32 bits(listingTime) + 160 bits(maker)]
parameter.data1 =
(order.startNonce << 200) |
(uint256(order.v) << 192) |
(order.listingTime << 160) |
uint256(uint160(order.maker));
// data2 [64 bits(taker part1) + 32 bits(expiryTime) + 160 bits(erc20Token)]
parameter.data2 =
((taker >> 96) << 192) |
(order.expirationTime << 160) |
uint256(uint160(order.erc20Token));
// data3 [96 bits(taker part2) + 160 bits(platformFeeRecipient)]
parameter.data3 = (taker << 160) | uint256(uint160(order.platformFeeRecipient));
parameters[i] = parameter;
unchecked {
++i;
}
}
// Execute fill
uint256 additional2 = revertIfIncomplete ? (1 << 248) : 0;
try EXCHANGE.fillBatchSignedERC721Orders{value: value}(parameters, 0, additional2) {} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
function _buyERC1155Ex(
IElement.ERC1155SellOrder calldata order,
IElement.Signature calldata signature,
uint128 amount,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
try EXCHANGE.buyERC1155Ex{value: value}(order, signature, receiver, amount, "") {} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
function _buyERC1155sEx(
IElement.ERC1155SellOrder[] calldata orders,
IElement.Signature[] calldata signatures,
uint128[] calldata amounts,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
uint256 length = orders.length;
address[] memory takers = new address[](length);
for (uint256 i; i < length; ) {
takers[i] = receiver;
unchecked {
++i;
}
}
// Execute fill
try
EXCHANGE.batchBuyERC1155sEx{value: value}(
orders,
signatures,
takers,
amounts,
new bytes[](length),
revertIfIncomplete
)
{} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {IFoundation} from "../../../interfaces/IFoundation.sol";
// Notes:
// - only supports filling "buy now" listings (ERC721 and ETH-denominated)
contract FoundationModule is BaseExchangeModule {
// --- Structs ---
struct Listing {
IERC721 token;
uint256 tokenId;
uint256 price;
}
// --- Fields ---
IFoundation public immutable EXCHANGE;
// --- Constructor ---
constructor(
address owner,
address router,
address exchange
) BaseModule(owner) BaseExchangeModule(router) {
EXCHANGE = IFoundation(exchange);
}
// --- Fallback ---
receive() external payable {}
// --- Single ETH listing ---
function acceptETHListing(
Listing calldata listing,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buy(listing.token, listing.tokenId, params.fillTo, params.revertIfIncomplete, listing.price);
}
// --- Multiple ETH listings ---
function acceptETHListings(
Listing[] calldata listings,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Foundation does not support batch filling so we fill orders one by one
for (uint256 i = 0; i < listings.length; ) {
_buy(
listings[i].token,
listings[i].tokenId,
params.fillTo,
params.revertIfIncomplete,
listings[i].price
);
unchecked {
++i;
}
}
}
// --- ERC721 hooks ---
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata // data
) external pure returns (bytes4) {
return this.onERC721Received.selector;
}
// --- Internal ---
function _buy(
IERC721 token,
uint256 tokenId,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
// Execute fill
try EXCHANGE.buyV2{value: value}(token, tokenId, value, receiver) {
token.safeTransferFrom(address(this), receiver, tokenId);
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {ILooksRare, ILooksRareTransferSelectorNFT} from "../../../interfaces/ILooksRare.sol";
// Notes:
// - supports filling listings (both ERC721/ERC1155 but only ETH-denominated)
// - supports filling offers (both ERC721/ERC1155)
contract LooksRareModule is BaseExchangeModule {
using SafeERC20 for IERC20;
// --- Fields ---
ILooksRare public immutable EXCHANGE;
address public immutable ERC721_TRANSFER_MANAGER;
address public immutable ERC1155_TRANSFER_MANAGER;
bytes4 public constant ERC721_INTERFACE = 0x80ac58cd;
bytes4 public constant ERC1155_INTERFACE = 0xd9b67a26;
// --- Constructor ---
constructor(
address owner,
address router,
address exchange
) BaseModule(owner) BaseExchangeModule(router) {
EXCHANGE = ILooksRare(exchange);
ILooksRareTransferSelectorNFT transferSelector = EXCHANGE.transferSelectorNFT();
ERC721_TRANSFER_MANAGER = transferSelector.TRANSFER_MANAGER_ERC721();
ERC1155_TRANSFER_MANAGER = transferSelector.TRANSFER_MANAGER_ERC1155();
}
// --- Fallback ---
receive() external payable {}
// --- Single ETH listing ---
function acceptETHListing(
ILooksRare.TakerOrder calldata takerBid,
ILooksRare.MakerOrder calldata makerAsk,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buy(takerBid, makerAsk, params.fillTo, params.revertIfIncomplete, params.amount);
}
// --- Multiple ETH listings ---
function acceptETHListings(
ILooksRare.TakerOrder[] calldata takerBids,
ILooksRare.MakerOrder[] calldata makerAsks,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// LooksRare does not support batch filling so we fill orders one by one
for (uint256 i = 0; i < takerBids.length; ) {
// Use `memory` to avoid `Stack too deep` errors
ILooksRare.TakerOrder memory takerBid = takerBids[i];
// Execute fill
_buy(takerBids[i], makerAsks[i], params.fillTo, params.revertIfIncomplete, takerBid.price);
unchecked {
++i;
}
}
}
// --- [ERC721] Single offer ---
function acceptERC721Offer(
ILooksRare.TakerOrder calldata takerAsk,
ILooksRare.MakerOrder calldata makerBid,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
IERC721 collection = IERC721(address(makerBid.collection));
// Approve the transfer manager if needed
_approveERC721IfNeeded(collection, ERC721_TRANSFER_MANAGER);
// Execute the fill
_sell(takerAsk, makerBid, params.fillTo, params.revertIfIncomplete, fees);
// Refund any ERC721 leftover
_sendAllERC721(params.refundTo, collection, takerAsk.tokenId);
}
// --- [ERC1155] Single offer ---
function acceptERC1155Offer(
ILooksRare.TakerOrder calldata takerAsk,
ILooksRare.MakerOrder calldata makerBid,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
IERC1155 collection = IERC1155(address(makerBid.collection));
// Approve the transfer manager if needed
_approveERC1155IfNeeded(collection, ERC1155_TRANSFER_MANAGER);
// Execute the fill
_sell(takerAsk, makerBid, params.fillTo, params.revertIfIncomplete, fees);
// Refund any ERC1155 leftover
_sendAllERC1155(params.refundTo, collection, takerAsk.tokenId);
}
// --- ERC721 / ERC1155 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
// --- Internal ---
function _buy(
ILooksRare.TakerOrder calldata takerBid,
ILooksRare.MakerOrder calldata makerAsk,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
// Execute the fill
try EXCHANGE.matchAskWithTakerBidUsingETHAndWETH{value: value}(takerBid, makerAsk) {
IERC165 collection = makerAsk.collection;
// Forward any token to the specified receiver
bool isERC721 = collection.supportsInterface(ERC721_INTERFACE);
if (isERC721) {
IERC721(address(collection)).safeTransferFrom(address(this), receiver, takerBid.tokenId);
} else {
bool isERC1155 = collection.supportsInterface(ERC1155_INTERFACE);
if (isERC1155) {
IERC1155(address(collection)).safeTransferFrom(
address(this),
receiver,
takerBid.tokenId,
makerAsk.amount,
""
);
}
}
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
function _sell(
ILooksRare.TakerOrder calldata takerAsk,
ILooksRare.MakerOrder calldata makerBid,
address receiver,
bool revertIfIncomplete,
Fee[] calldata fees
) internal {
// Execute the fill
try EXCHANGE.matchBidWithTakerAsk(takerAsk, makerBid) {
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, makerBid.currency);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllERC20(receiver, makerBid.currency);
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {ILooksRareV2, ITransferManager} from "../../../interfaces/ILooksRareV2.sol";
// Notes:
// - supports filling listings (both ERC721/ERC1155 but only ETH-denominated)
// - supports filling offers (both ERC721/ERC1155)
contract LooksRareV2Module is BaseExchangeModule {
using SafeERC20 for IERC20;
// --- Fields ---
ILooksRareV2 public immutable EXCHANGE;
ITransferManager public immutable TRANSFER_MANAGER;
// --- Constructor ---
constructor(
address owner,
address router,
address exchange
) BaseModule(owner) BaseExchangeModule(router) {
EXCHANGE = ILooksRareV2(exchange);
TRANSFER_MANAGER = EXCHANGE.transferManager();
// Grant approval to the transfer manager
address[] memory operators = new address[](1);
operators[0] = address(EXCHANGE);
TRANSFER_MANAGER.grantApprovals(operators);
}
// --- Fallback ---
receive() external payable {}
// --- Single ETH listing ---
function acceptETHListing(
ILooksRareV2.MakerOrder calldata makerAsk,
bytes calldata makerSignature,
ILooksRareV2.MerkleTree calldata merkleTree,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buy(
makerAsk,
makerSignature,
merkleTree,
params.fillTo,
params.revertIfIncomplete,
params.amount
);
}
// --- Multiple ETH listings ---
function acceptETHListings(
ILooksRareV2.MakerOrder[] calldata makerAsks,
bytes[] calldata makerSignatures,
ILooksRareV2.MerkleTree[] calldata merkleTrees,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// LooksRare does not support batch filling so we fill orders one by one
for (uint256 i = 0; i < makerAsks.length; ) {
// Execute fill
_buy(
makerAsks[i],
makerSignatures[i],
merkleTrees[i],
params.fillTo,
params.revertIfIncomplete,
makerAsks[i].price
);
unchecked {
++i;
}
}
}
// --- [ERC721] Single offer ---
function acceptERC721Offer(
ILooksRareV2.MakerOrder calldata makerBid,
bytes calldata takerAdditionalParameters,
bytes calldata makerSignature,
ILooksRareV2.MerkleTree calldata merkleTree,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
IERC721 collection = IERC721(address(makerBid.collection));
// Approve the transfer manager if needed
_approveERC721IfNeeded(collection, address(TRANSFER_MANAGER));
// Execute the fill
uint256 tokenId = _sell(
makerBid,
takerAdditionalParameters,
makerSignature,
merkleTree,
params.fillTo,
params.revertIfIncomplete,
fees
);
// Refund any ERC721 leftover
_sendAllERC721(params.refundTo, collection, tokenId);
}
// --- [ERC1155] Single offer ---
function acceptERC1155Offer(
ILooksRareV2.MakerOrder calldata makerBid,
bytes calldata takerAdditionalParameters,
bytes calldata makerSignature,
ILooksRareV2.MerkleTree calldata merkleTree,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
IERC1155 collection = IERC1155(address(makerBid.collection));
// Approve the transfer manager if needed
_approveERC1155IfNeeded(collection, address(TRANSFER_MANAGER));
// Execute the fill
uint256 tokenId = _sell(
makerBid,
takerAdditionalParameters,
makerSignature,
merkleTree,
params.fillTo,
params.revertIfIncomplete,
fees
);
// Refund any ERC1155 leftover
_sendAllERC1155(params.refundTo, collection, tokenId);
}
// --- ERC721 / ERC1155 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
// --- Internal ---
function _buy(
ILooksRareV2.MakerOrder calldata makerAsk,
bytes calldata makerSignature,
ILooksRareV2.MerkleTree calldata merkleTree,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
ILooksRareV2.TakerOrder memory takerBid;
takerBid.recipient = receiver;
// Execute the fill
try
EXCHANGE.executeTakerBid{value: value}(
takerBid,
makerAsk,
makerSignature,
merkleTree,
address(0)
)
{} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
function _sell(
ILooksRareV2.MakerOrder calldata makerBid,
bytes calldata takerAdditionalParameters,
bytes calldata makerSignature,
ILooksRareV2.MerkleTree calldata merkleTree,
address receiver,
bool revertIfIncomplete,
Fee[] calldata fees
) internal returns (uint256 tokenId) {
ILooksRareV2.TakerOrder memory takerAsk;
takerAsk.recipient = address(this);
takerAsk.additionalParameters = takerAdditionalParameters;
// Execute the fill
try EXCHANGE.executeTakerAsk(takerAsk, makerBid, makerSignature, merkleTree, address(0)) {
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, makerBid.currency);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllERC20(receiver, makerBid.currency);
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
tokenId = abi.decode(takerAdditionalParameters[0:32], (uint256));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {INFTXMarketplaceZap} from "../../../interfaces/INFTX.sol";
import {INFTXVault} from "../../../interfaces/INFTXVault.sol";
import {INFTXVaultFactory} from "../../../interfaces/INFTXVaultFactory.sol";
contract NFTXModule is BaseExchangeModule {
// --- Fields ---
INFTXMarketplaceZap public immutable NFTX_MARKETPLACE;
// --- Constructor ---
constructor(
address owner,
address router,
address nftxMarketplace
) BaseModule(owner) BaseExchangeModule(router) {
NFTX_MARKETPLACE = INFTXMarketplaceZap(nftxMarketplace);
}
// --- Fallback ---
receive() external payable {}
// --- Multiple ETH listings ---
function buyWithETH(
INFTXMarketplaceZap.BuyOrder[] calldata orders,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256 length = orders.length;
for (uint256 i = 0; i < length; ) {
INFTXMarketplaceZap.BuyOrder memory order = orders[i];
// Execute fill
_buy(orders[i], params.fillTo, params.revertIfIncomplete, order.price);
unchecked {
++i;
}
}
}
// --- Internal ---
function _buy(
INFTXMarketplaceZap.BuyOrder calldata buyOrder,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
// Execute the fill
try
NFTX_MARKETPLACE.buyAndRedeem{value: value}(
buyOrder.vaultId,
buyOrder.amount,
buyOrder.specificIds,
buyOrder.path,
receiver
)
{} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
// --- Single ERC721 offer ---
function sell(
INFTXMarketplaceZap.SellOrder[] calldata orders,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
uint256 length = orders.length;
for (uint256 i = 0; i < length; ) {
// Execute fill
_sell(orders[i], params.fillTo, params.revertIfIncomplete, fees);
unchecked {
++i;
}
}
}
function _sell(
INFTXMarketplaceZap.SellOrder calldata sellOrder,
address receiver,
bool revertIfIncomplete,
Fee[] calldata fees
) internal {
address collection = sellOrder.collection;
INFTXVault vault = INFTXVault(NFTX_MARKETPLACE.nftxFactory().vault(sellOrder.vaultId));
// Execute the sell
if (!vault.is1155()) {
_approveERC721IfNeeded(IERC721(collection), address(NFTX_MARKETPLACE));
try
NFTX_MARKETPLACE.mintAndSell721WETH(
sellOrder.vaultId,
sellOrder.specificIds,
sellOrder.price,
sellOrder.path,
address(this)
)
{
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, sellOrder.currency);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllERC20(receiver, sellOrder.currency);
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any ERC721 leftover
uint256 length = sellOrder.specificIds.length;
for (uint256 i = 0; i < length; ) {
_sendAllERC721(receiver, IERC721(collection), sellOrder.specificIds[i]);
unchecked {
++i;
}
}
} else {
_approveERC1155IfNeeded(IERC1155(collection), address(NFTX_MARKETPLACE));
try
NFTX_MARKETPLACE.mintAndSell1155WETH(
sellOrder.vaultId,
sellOrder.specificIds,
sellOrder.amounts,
sellOrder.price,
sellOrder.path,
address(this)
)
{
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, sellOrder.currency);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllERC20(receiver, sellOrder.currency);
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any ERC1155 leftover
uint256 length = sellOrder.specificIds.length;
for (uint256 i = 0; i < length; ) {
_sendAllERC1155(receiver, IERC1155(collection), sellOrder.specificIds[i]);
unchecked {
++i;
}
}
}
}
// --- ERC721 / ERC1155 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {INFTXV3MarketplaceZap} from "../../../interfaces/INFTXV3MarketplaceZap.sol";
import {INFTXVault} from "../../../interfaces/INFTXVault.sol";
import {INFTXVaultFactory} from "../../../interfaces/INFTXVaultFactory.sol";
contract NFTXV3Module is BaseExchangeModule {
// --- Fields ---
INFTXV3MarketplaceZap public immutable NFTX_V3_MARKETPLACE;
bytes4 public constant ERC721_INTERFACE = 0x80ac58cd;
bytes4 public constant ERC1155_INTERFACE = 0xd9b67a26;
// --- Constructor ---
constructor(
address owner,
address router,
address nftxMarketplace
) BaseModule(owner) BaseExchangeModule(router) {
NFTX_V3_MARKETPLACE = INFTXV3MarketplaceZap(nftxMarketplace);
}
// --- Fallback ---
receive() external payable {}
// --- Multiple ETH listings ---
function buyWithETH(
INFTXV3MarketplaceZap.BuyOrder[] calldata orders,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256 length = orders.length;
for (uint256 i = 0; i < length; ) {
INFTXV3MarketplaceZap.BuyOrder memory order = orders[i];
// Execute fill
_buy(orders[i], params.fillTo, params.revertIfIncomplete, order.price);
unchecked {
++i;
}
}
}
// --- Internal ---
function _buy(
INFTXV3MarketplaceZap.BuyOrder calldata buyOrder,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
// Execute the fill
try
NFTX_V3_MARKETPLACE.buyNFTsWithETH{value: value}(
buyOrder.vaultId,
buyOrder.idsOut,
buyOrder.executeCallData,
payable(receiver),
buyOrder.vTokenPremiumLimit,
buyOrder.deductRoyalty
)
{} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
// --- Single ERC721 offer ---
function sell(
INFTXV3MarketplaceZap.SellOrder[] calldata orders,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
uint256 length = orders.length;
for (uint256 i = 0; i < length; ) {
// Execute fill
_sell(orders[i], params.fillTo, params.revertIfIncomplete, fees);
unchecked {
++i;
}
}
}
function _sell(
INFTXV3MarketplaceZap.SellOrder calldata sellOrder,
address receiver,
bool revertIfIncomplete,
Fee[] calldata fees
) internal {
address collection = sellOrder.collection;
INFTXVault vault = INFTXVault(NFTX_V3_MARKETPLACE.nftxVaultFactory().vault(sellOrder.vaultId));
// Execute the sell
if (!vault.is1155()) {
_approveERC721IfNeeded(IERC721(collection), address(NFTX_V3_MARKETPLACE));
// Return ETH
try
NFTX_V3_MARKETPLACE.sell721(
sellOrder.vaultId,
sellOrder.idsIn,
sellOrder.executeCallData,
payable(address(this)),
sellOrder.deductRoyalty
)
{
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendETH(fee.recipient, fee.amount);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllETH(receiver);
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any ERC721 leftover
uint256 length = sellOrder.idsIn.length;
for (uint256 i = 0; i < length; ) {
_sendAllERC721(receiver, IERC721(collection), sellOrder.idsIn[i]);
unchecked {
++i;
}
}
} else {
_approveERC1155IfNeeded(IERC1155(collection), address(NFTX_V3_MARKETPLACE));
try
NFTX_V3_MARKETPLACE.sell1155(
sellOrder.vaultId,
sellOrder.idsIn,
sellOrder.amounts,
sellOrder.executeCallData,
payable(address(this)),
sellOrder.deductRoyalty
)
{
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendETH(fee.recipient, fee.amount);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllETH(receiver);
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any ERC1155 leftover
uint256 length = sellOrder.idsIn.length;
for (uint256 i = 0; i < length; ) {
_sendAllERC1155(receiver, IERC1155(collection), sellOrder.idsIn[i]);
unchecked {
++i;
}
}
}
}
// --- ERC721 / ERC1155 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {INFTXMarketplace0xZap} from "../../../interfaces/INFTXMarketplace0xZap.sol";
import {INFTXVault} from "../../../interfaces/INFTXVault.sol";
import {INFTXVaultFactory} from "../../../interfaces/INFTXVaultFactory.sol";
contract NFTXZeroExModule is BaseExchangeModule {
// --- Fields ---
INFTXMarketplace0xZap public immutable NFTX_ZEROEX_MARKETPLACE;
bytes4 public constant ERC721_INTERFACE = 0x80ac58cd;
bytes4 public constant ERC1155_INTERFACE = 0xd9b67a26;
// --- Constructor ---
constructor(
address owner,
address router,
address nftxMarketplace
) BaseModule(owner) BaseExchangeModule(router) {
NFTX_ZEROEX_MARKETPLACE = INFTXMarketplace0xZap(nftxMarketplace);
}
// --- Fallback ---
receive() external payable {}
// --- Multiple ETH listings ---
function buyWithETH(
INFTXMarketplace0xZap.BuyOrder[] calldata orders,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256 length = orders.length;
for (uint256 i = 0; i < length; ) {
INFTXMarketplace0xZap.BuyOrder memory order = orders[i];
// Execute fill
_buy(orders[i], params.fillTo, params.revertIfIncomplete, order.price);
unchecked {
++i;
}
}
}
// --- Internal ---
function _buy(
INFTXMarketplace0xZap.BuyOrder calldata buyOrder,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
// Execute the fill
try
NFTX_ZEROEX_MARKETPLACE.buyAndRedeem{value: value}(
buyOrder.vaultId,
buyOrder.amount,
buyOrder.specificIds,
buyOrder.swapCallData,
payable(receiver)
)
{} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
// --- Single ERC721 offer ---
function sell(
INFTXMarketplace0xZap.SellOrder[] calldata orders,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
uint256 length = orders.length;
for (uint256 i = 0; i < length; ) {
// Execute fill
_sell(orders[i], params.fillTo, params.revertIfIncomplete, fees);
unchecked {
++i;
}
}
}
function _sell(
INFTXMarketplace0xZap.SellOrder calldata sellOrder,
address receiver,
bool revertIfIncomplete,
Fee[] calldata fees
) internal {
address collection = sellOrder.collection;
INFTXVault vault = INFTXVault(NFTX_ZEROEX_MARKETPLACE.nftxFactory().vault(sellOrder.vaultId));
// Execute the sell
if (!vault.is1155()) {
_approveERC721IfNeeded(IERC721(collection), address(NFTX_ZEROEX_MARKETPLACE));
// Return ETH
try
NFTX_ZEROEX_MARKETPLACE.mintAndSell721(
sellOrder.vaultId,
sellOrder.specificIds,
sellOrder.swapCallData,
payable(address(this))
)
{
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendETH(fee.recipient, fee.amount);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllETH(receiver);
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any ERC721 leftover
uint256 length = sellOrder.specificIds.length;
for (uint256 i = 0; i < length; ) {
_sendAllERC721(receiver, IERC721(collection), sellOrder.specificIds[i]);
unchecked {
++i;
}
}
} else {
_approveERC1155IfNeeded(IERC1155(collection), address(NFTX_ZEROEX_MARKETPLACE));
try
NFTX_ZEROEX_MARKETPLACE.mintAndSell1155(
sellOrder.vaultId,
sellOrder.specificIds,
sellOrder.amounts,
sellOrder.swapCallData,
payable(address(this))
)
{
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendETH(fee.recipient, fee.amount);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllETH(receiver);
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any ERC1155 leftover
uint256 length = sellOrder.specificIds.length;
for (uint256 i = 0; i < length; ) {
_sendAllERC1155(receiver, IERC1155(collection), sellOrder.specificIds[i]);
unchecked {
++i;
}
}
}
}
// --- ERC721 / ERC1155 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {IPaymentProcessor} from "../../../interfaces/IPaymentProcessor.sol";
// Notes:
// - supports filling listings (both ETH and ERC20)
// - supports filling offers
contract PaymentProcessorModule is BaseExchangeModule {
// --- Fields ---
IPaymentProcessor public immutable EXCHANGE;
// --- Constructor ---
constructor(
address owner,
address router,
address exchange
) BaseModule(owner) BaseExchangeModule(router) {
EXCHANGE = IPaymentProcessor(exchange);
}
// --- Fallback ---
receive() external payable {}
// --- Single ETH listing ---
function acceptETHListings(
IPaymentProcessor.MatchedOrder[] memory saleDetails,
IPaymentProcessor.SignatureECDSA[] memory signedListings,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256 length = saleDetails.length;
for (uint256 i; i < length; ) {
// Execute the fill
try
EXCHANGE.buySingleListing{value: saleDetails[i].offerPrice}(
saleDetails[i],
signedListings[i],
IPaymentProcessor.SignatureECDSA({v: 0, r: bytes32(0), s: bytes32(0)})
)
{
// Forward any token to the specified receiver
if (saleDetails[i].protocol == IPaymentProcessor.TokenProtocols.ERC721) {
IERC721(saleDetails[i].tokenAddress).safeTransferFrom(
address(this),
params.fillTo,
saleDetails[i].tokenId
);
} else {
IERC1155(saleDetails[i].tokenAddress).safeTransferFrom(
address(this),
params.fillTo,
saleDetails[i].tokenId,
saleDetails[i].amount,
""
);
}
} catch {
// Revert if specified
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
unchecked {
++i;
}
}
}
function acceptERC20Listings(
IPaymentProcessor.MatchedOrder[] memory saleDetails,
IPaymentProcessor.SignatureECDSA[] memory signedListings,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
uint256 length = saleDetails.length;
for (uint256 i; i < length; ) {
// Execute the fill
try
EXCHANGE.buySingleListing(
saleDetails[i],
signedListings[i],
IPaymentProcessor.SignatureECDSA({v: 0, r: bytes32(0), s: bytes32(0)})
)
{
// Forward any token to the specified receiver
if (saleDetails[i].protocol == IPaymentProcessor.TokenProtocols.ERC721) {
IERC721(saleDetails[i].tokenAddress).safeTransferFrom(
address(this),
params.fillTo,
saleDetails[i].tokenId
);
} else {
IERC1155(saleDetails[i].tokenAddress).safeTransferFrom(
address(this),
params.fillTo,
saleDetails[i].tokenId,
saleDetails[i].amount,
""
);
}
} catch {
// Revert if specified
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
unchecked {
++i;
}
}
}
function acceptOffers(
IPaymentProcessor.MatchedOrder[] memory saleDetails,
IPaymentProcessor.SignatureECDSA[] memory signedOffers,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
uint256 length = saleDetails.length;
for (uint256 i; i < length; ) {
// Approve the exchange if needed
if (saleDetails[i].protocol == IPaymentProcessor.TokenProtocols.ERC721) {
_approveERC721IfNeeded(IERC721(saleDetails[i].tokenAddress), address(EXCHANGE));
} else {
_approveERC1155IfNeeded(IERC1155(saleDetails[i].tokenAddress), address(EXCHANGE));
}
// Execute the fill
try
EXCHANGE.buySingleListing(
saleDetails[i],
IPaymentProcessor.SignatureECDSA({v: 0, r: bytes32(0), s: bytes32(0)}),
signedOffers[i]
)
{
// Pay fees
uint256 feesLength = fees.length;
for (uint256 j; j < feesLength; ) {
Fee memory fee = fees[j];
_sendERC20(fee.recipient, fee.amount, IERC20(saleDetails[i].paymentCoin));
unchecked {
++j;
}
}
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, IERC20(saleDetails[i].paymentCoin));
} catch {
// Revert if specified
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
unchecked {
++i;
}
}
}
// --- ERC1271 ---
function isValidSignature(bytes32, bytes memory) external pure returns (bytes4) {
return this.isValidSignature.selector;
}
// --- ERC721 / ERC1155 hooks ---
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {IRarible} from "../../../interfaces/IRarible.sol";
// Notes:
// - supports filling listings (both ERC721/ERC1155 but only ETH-denominated)
// - supports filling offers (both ERC721/ERC1155)
contract RaribleModule is BaseExchangeModule {
using SafeERC20 for IERC20;
// --- Fields ---
IRarible public immutable EXCHANGE;
address public immutable TRANSFER_MANAGER;
bytes4 public constant ERC721_INTERFACE = 0x80ac58cd;
bytes4 public constant ERC1155_INTERFACE = 0xd9b67a26;
// --- Constructor ---
constructor(
address owner,
address router,
address exchange,
address transferManager
) BaseModule(owner) BaseExchangeModule(router) {
EXCHANGE = IRarible(exchange);
TRANSFER_MANAGER = transferManager;
}
// --- Fallback ---
receive() external payable {}
// --- Single ETH listing ---
function acceptETHListing(
IRarible.Order calldata orderLeft,
bytes calldata signatureLeft,
IRarible.Order calldata orderRight,
bytes calldata signatureRight,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buy(
orderLeft,
signatureLeft,
orderRight,
signatureRight,
params.fillTo,
params.revertIfIncomplete,
params.amount
);
}
// --- Multiple ETH listings ---
function acceptETHListings(
IRarible.Order[] calldata ordersLeft,
bytes[] calldata signaturesLeft,
IRarible.Order[] calldata ordersRight,
bytes calldata signatureRight,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
for (uint256 i = 0; i < ordersLeft.length; ) {
IRarible.Order calldata orderLeft = ordersLeft[i];
IRarible.Order calldata orderRight = ordersRight[i];
// Execute fill
_buy(
orderLeft,
signaturesLeft[i],
orderRight,
signatureRight,
params.fillTo,
params.revertIfIncomplete,
orderLeft.takeAsset.value
);
unchecked {
++i;
}
}
}
// --- [ERC721] Single offer ---
function acceptERC721Offer(
IRarible.Order calldata orderLeft,
bytes calldata signatureLeft,
IRarible.Order calldata orderRight,
bytes calldata signatureRight,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
(address token, uint256 tokenId) = abi.decode(
orderRight.makeAsset.assetType.data,
(address, uint256)
);
IERC721 collection = IERC721(address(token));
// Approve the transfer manager if needed
_approveERC721IfNeeded(collection, TRANSFER_MANAGER);
// Execute the fill
_sell(
orderLeft,
signatureLeft,
orderRight,
signatureRight,
params.fillTo,
params.revertIfIncomplete,
fees
);
// Refund any ERC721 leftover
_sendAllERC721(params.refundTo, collection, tokenId);
}
// --- [ERC1155] Single offer ---
function acceptERC1155Offer(
IRarible.Order calldata orderLeft,
bytes calldata signatureLeft,
IRarible.Order calldata orderRight,
bytes calldata signatureRight,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
(address token, uint256 tokenId) = abi.decode(
orderRight.makeAsset.assetType.data,
(address, uint256)
);
IERC1155 collection = IERC1155(address(token));
// Approve the transfer manager if needed
_approveERC1155IfNeeded(collection, TRANSFER_MANAGER);
// Execute the fill
_sell(
orderLeft,
signatureLeft,
orderRight,
signatureRight,
params.fillTo,
params.revertIfIncomplete,
fees
);
// Refund any ERC1155 leftover
_sendAllERC1155(params.refundTo, collection, tokenId);
}
// --- ERC721 / ERC1155 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
// --- Internal ---
function _buy(
IRarible.Order calldata orderLeft,
bytes calldata signatureLeft,
IRarible.Order calldata orderRight,
bytes calldata signatureRight,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
// Execute the fill
try EXCHANGE.matchOrders{value: value}(orderLeft, signatureLeft, orderRight, signatureRight) {
(address token, uint256 tokenId) = abi.decode(
orderLeft.makeAsset.assetType.data,
(address, uint256)
);
IERC165 collection = IERC165(token);
// Forward any token to the specified receiver
bool isERC721 = collection.supportsInterface(ERC721_INTERFACE);
if (isERC721) {
IERC721(address(collection)).safeTransferFrom(address(this), receiver, tokenId);
} else {
IERC1155(address(collection)).safeTransferFrom(
address(this),
receiver,
tokenId,
orderRight.takeAsset.value,
""
);
}
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
function _sell(
IRarible.Order calldata orderLeft,
bytes calldata signatureLeft,
IRarible.Order calldata orderRight,
bytes calldata signatureRight,
address receiver,
bool revertIfIncomplete,
Fee[] calldata fees
) internal {
// Execute the fill
try EXCHANGE.matchOrders(orderLeft, signatureLeft, orderRight, signatureRight) {
// Pay fees
address token = abi.decode(orderLeft.makeAsset.assetType.data, (address));
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, IERC20(token));
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllERC20(receiver, IERC20(token));
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {ISeaport} from "../../../interfaces/ISeaport.sol";
// Notes on the Seaport module:
// - supports filling listings (both ERC721/ERC1155)
// - supports filling offers (both ERC721/ERC1155)
contract SeaportModule is BaseExchangeModule {
// --- Structs ---
struct SeaportETHListingWithPrice {
ISeaport.AdvancedOrder order;
uint256 price;
}
// --- Fields ---
ISeaport public immutable EXCHANGE;
// --- Constructor ---
constructor(
address owner,
address router,
address exchange
) BaseModule(owner) BaseExchangeModule(router) {
EXCHANGE = ISeaport(exchange);
}
// --- Fallback ---
receive() external payable {}
// --- Single ETH listing ---
function acceptETHListing(
ISeaport.AdvancedOrder calldata order,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(
order,
new ISeaport.CriteriaResolver[](0),
params.fillTo,
params.amount
)
: _fillSingleOrder(order, new ISeaport.CriteriaResolver[](0), params.fillTo, params.amount);
}
// --- Single ERC20 listing ---
function acceptERC20Listing(
ISeaport.AdvancedOrder calldata order,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(
order,
new ISeaport.CriteriaResolver[](0),
params.fillTo,
0
)
: _fillSingleOrder(order, new ISeaport.CriteriaResolver[](0), params.fillTo, 0);
}
// --- Multiple ETH listings ---
function acceptETHListings(
SeaportETHListingWithPrice[] calldata orders,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256 length = orders.length;
ISeaport.CriteriaResolver[] memory criteriaResolvers = new ISeaport.CriteriaResolver[](0);
// Execute the fills
if (params.revertIfIncomplete) {
for (uint256 i; i < length; ) {
_fillSingleOrderWithRevertIfIncomplete(
orders[i].order,
criteriaResolvers,
params.fillTo,
orders[i].price
);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < length; ) {
_fillSingleOrder(orders[i].order, criteriaResolvers, params.fillTo, orders[i].price);
unchecked {
++i;
}
}
}
}
// --- Multiple ERC20 listings ---
function acceptERC20Listings(
ISeaport.AdvancedOrder[] calldata orders,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
uint256 length = orders.length;
ISeaport.CriteriaResolver[] memory criteriaResolvers = new ISeaport.CriteriaResolver[](0);
// Execute the fills
if (params.revertIfIncomplete) {
for (uint256 i; i < length; ) {
_fillSingleOrderWithRevertIfIncomplete(orders[i], criteriaResolvers, params.fillTo, 0);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < length; ) {
_fillSingleOrder(orders[i], criteriaResolvers, params.fillTo, 0);
unchecked {
++i;
}
}
}
}
// --- Single ERC721 offer ---
function acceptERC721Offer(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
// Extract the ERC721 token from the consideration items
ISeaport.ConsiderationItem calldata nftItem = order.parameters.consideration[0];
if (
nftItem.itemType != ISeaport.ItemType.ERC721 &&
nftItem.itemType != ISeaport.ItemType.ERC721_WITH_CRITERIA
) {
revert WrongParams();
}
IERC721 nftToken = IERC721(nftItem.token);
// Extract the payment token from the offer items
ISeaport.OfferItem calldata paymentItem = order.parameters.offer[0];
IERC20 paymentToken = IERC20(paymentItem.token);
// Approve the exchange if needed
_approveERC721IfNeeded(nftToken, address(EXCHANGE));
_approveERC20IfNeeded(paymentToken, address(EXCHANGE), type(uint256).max);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(order, criteriaResolvers, address(this), 0)
: _fillSingleOrder(order, criteriaResolvers, address(this), 0);
uint256 identifier = nftItem.itemType == ISeaport.ItemType.ERC721
? nftItem.identifierOrCriteria
: criteriaResolvers[0].identifier;
// Pay fees
if (nftToken.ownerOf(identifier) != address(this)) {
// Only pay fees if the fill was successful
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, paymentToken);
unchecked {
++i;
}
}
}
// Refund any ERC721 leftover
_sendAllERC721(params.refundTo, nftToken, identifier);
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, paymentToken);
}
// --- Single ERC1155 offer ---
function acceptERC1155Offer(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
// Extract the ERC1155 token from the consideration items
ISeaport.ConsiderationItem calldata nftItem = order.parameters.consideration[0];
if (
nftItem.itemType != ISeaport.ItemType.ERC1155 &&
nftItem.itemType != ISeaport.ItemType.ERC1155_WITH_CRITERIA
) {
revert WrongParams();
}
IERC1155 nftToken = IERC1155(nftItem.token);
// Extract the payment token from the offer items
ISeaport.OfferItem calldata paymentItem = order.parameters.offer[0];
IERC20 paymentToken = IERC20(paymentItem.token);
// Approve the exchange if needed
_approveERC1155IfNeeded(nftToken, address(EXCHANGE));
_approveERC20IfNeeded(paymentToken, address(EXCHANGE), type(uint256).max);
uint256 identifier = nftItem.itemType == ISeaport.ItemType.ERC1155
? nftItem.identifierOrCriteria
: criteriaResolvers[0].identifier;
uint256 balanceBefore = nftToken.balanceOf(address(this), identifier);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(order, criteriaResolvers, address(this), 0)
: _fillSingleOrder(order, criteriaResolvers, address(this), 0);
uint256 balanceAfter = nftToken.balanceOf(address(this), identifier);
// Pay fees
uint256 amountFilled = balanceBefore - balanceAfter;
if (amountFilled > 0) {
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(
fee.recipient,
// Only pay fees for the amount that was actually filled
(fee.amount * amountFilled) / order.numerator,
paymentToken
);
unchecked {
++i;
}
}
}
// Refund any ERC1155 leftover
_sendAllERC1155(params.refundTo, nftToken, identifier);
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, paymentToken);
}
// --- Generic handler (used for Seaport-based approvals) ---
function matchOrders(
ISeaport.Order[] calldata orders,
ISeaport.Fulfillment[] calldata fulfillments
) external nonReentrant {
// We don't perform any kind of input or return value validation,
// so this function should be used with precaution - the official
// way to use it is only for Seaport-based approvals
EXCHANGE.matchOrders(orders, fulfillments);
}
// --- ERC721 / ERC1155 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
// --- Internal ---
// NOTE: In lots of cases, Seaport will not revert if fills were not
// fully executed. An example of that is partial filling, which will
// successfully fill any amount that is still available (including a
// zero amount). One way to ensure that we revert in case of partial
// executions is to check the order's filled amount before and after
// we trigger the fill (we can use Seaport's `getOrderStatus` method
// to check). Since this can be expensive in terms of gas, we have a
// separate method variant to be called when reverts are enabled.
function _fillSingleOrder(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
address receiver,
uint256 value
) internal {
// Execute the fill
try
EXCHANGE.fulfillAdvancedOrder{value: value}(order, criteriaResolvers, bytes32(0), receiver)
{} catch {}
}
function _fillSingleOrderWithRevertIfIncomplete(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
address receiver,
uint256 value
) internal {
// Cache the order's hash
bytes32 orderHash = _getOrderHash(order.parameters);
// Before filling, get the order's filled amount
uint256 beforeFilledAmount = _getFilledAmount(orderHash);
// Execute the fill
bool success;
try
EXCHANGE.fulfillAdvancedOrder{value: value}(order, criteriaResolvers, bytes32(0), receiver)
returns (bool fulfilled) {
success = fulfilled;
} catch {
revert UnsuccessfulFill();
}
if (!success) {
revert UnsuccessfulFill();
} else {
// After successfully filling, get the order's filled amount
uint256 afterFilledAmount = _getFilledAmount(orderHash);
// Make sure the amount filled as part of this call is correct
if (afterFilledAmount - beforeFilledAmount != order.numerator) {
revert UnsuccessfulFill();
}
}
}
function _getOrderHash(
// Must use `memory` instead of `calldata` for the below cast
ISeaport.OrderParameters memory orderParameters
) internal view returns (bytes32 orderHash) {
// `OrderParameters` and `OrderComponents` share the exact same
// fields, apart from the last one, so here we simply treat the
// `orderParameters` argument as `OrderComponents` and then set
// the last field to the correct data
ISeaport.OrderComponents memory orderComponents;
assembly {
orderComponents := orderParameters
}
orderComponents.counter = EXCHANGE.getCounter(orderParameters.offerer);
orderHash = EXCHANGE.getOrderHash(orderComponents);
}
function _getFilledAmount(bytes32 orderHash) internal view returns (uint256 totalFilled) {
(, , totalFilled, ) = EXCHANGE.getOrderStatus(orderHash);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {ISeaport} from "../../../interfaces/ISeaport.sol";
// Notes on the Seaport module:
// - supports filling listings (both ERC721/ERC1155)
// - supports filling offers (both ERC721/ERC1155)
contract SeaportV14Module is BaseExchangeModule {
// --- Structs ---
struct SeaportETHListingWithPrice {
ISeaport.AdvancedOrder order;
uint256 price;
}
// --- Fields ---
ISeaport public immutable EXCHANGE;
// --- Constructor ---
constructor(
address owner,
address router,
address exchange
) BaseModule(owner) BaseExchangeModule(router) {
EXCHANGE = ISeaport(exchange);
}
// --- Fallback ---
receive() external payable {}
// --- Single ETH listing ---
function acceptETHListing(
ISeaport.AdvancedOrder calldata order,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(
order,
new ISeaport.CriteriaResolver[](0),
params.fillTo,
params.amount
)
: _fillSingleOrder(order, new ISeaport.CriteriaResolver[](0), params.fillTo, params.amount);
}
// --- Single ERC20 listing ---
function acceptERC20Listing(
ISeaport.AdvancedOrder calldata order,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(
order,
new ISeaport.CriteriaResolver[](0),
params.fillTo,
0
)
: _fillSingleOrder(order, new ISeaport.CriteriaResolver[](0), params.fillTo, 0);
}
// --- Multiple ETH listings ---
function acceptETHListings(
SeaportETHListingWithPrice[] calldata orders,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256 length = orders.length;
ISeaport.CriteriaResolver[] memory criteriaResolvers = new ISeaport.CriteriaResolver[](0);
// Execute the fills
if (params.revertIfIncomplete) {
for (uint256 i; i < length; ) {
_fillSingleOrderWithRevertIfIncomplete(
orders[i].order,
criteriaResolvers,
params.fillTo,
orders[i].price
);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < length; ) {
_fillSingleOrder(orders[i].order, criteriaResolvers, params.fillTo, orders[i].price);
unchecked {
++i;
}
}
}
}
// --- Multiple ERC20 listings ---
function acceptERC20Listings(
ISeaport.AdvancedOrder[] calldata orders,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
uint256 length = orders.length;
ISeaport.CriteriaResolver[] memory criteriaResolvers = new ISeaport.CriteriaResolver[](0);
// Execute the fills
if (params.revertIfIncomplete) {
for (uint256 i; i < length; ) {
_fillSingleOrderWithRevertIfIncomplete(orders[i], criteriaResolvers, params.fillTo, 0);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < length; ) {
_fillSingleOrder(orders[i], criteriaResolvers, params.fillTo, 0);
unchecked {
++i;
}
}
}
}
// --- Single ERC721 offer ---
function acceptERC721Offer(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
// Extract the ERC721 token from the consideration items
ISeaport.ConsiderationItem calldata nftItem = order.parameters.consideration[0];
if (
nftItem.itemType != ISeaport.ItemType.ERC721 &&
nftItem.itemType != ISeaport.ItemType.ERC721_WITH_CRITERIA
) {
revert WrongParams();
}
IERC721 nftToken = IERC721(nftItem.token);
// Extract the payment token from the offer items
ISeaport.OfferItem calldata paymentItem = order.parameters.offer[0];
IERC20 paymentToken = IERC20(paymentItem.token);
// Approve the exchange if needed
_approveERC721IfNeeded(nftToken, address(EXCHANGE));
_approveERC20IfNeeded(paymentToken, address(EXCHANGE), type(uint256).max);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(order, criteriaResolvers, address(this), 0)
: _fillSingleOrder(order, criteriaResolvers, address(this), 0);
uint256 identifier = nftItem.itemType == ISeaport.ItemType.ERC721
? nftItem.identifierOrCriteria
: criteriaResolvers[0].identifier;
// Pay fees
if (nftToken.ownerOf(identifier) != address(this)) {
// Only pay fees if the fill was successful
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, paymentToken);
unchecked {
++i;
}
}
}
// Refund any ERC721 leftover
_sendAllERC721(params.refundTo, nftToken, identifier);
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, paymentToken);
}
// --- Single ERC1155 offer ---
function acceptERC1155Offer(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
// Extract the ERC1155 token from the consideration items
ISeaport.ConsiderationItem calldata nftItem = order.parameters.consideration[0];
if (
nftItem.itemType != ISeaport.ItemType.ERC1155 &&
nftItem.itemType != ISeaport.ItemType.ERC1155_WITH_CRITERIA
) {
revert WrongParams();
}
IERC1155 nftToken = IERC1155(nftItem.token);
// Extract the payment token from the offer items
ISeaport.OfferItem calldata paymentItem = order.parameters.offer[0];
IERC20 paymentToken = IERC20(paymentItem.token);
// Approve the exchange if needed
_approveERC1155IfNeeded(nftToken, address(EXCHANGE));
_approveERC20IfNeeded(paymentToken, address(EXCHANGE), type(uint256).max);
uint256 identifier = nftItem.itemType == ISeaport.ItemType.ERC1155
? nftItem.identifierOrCriteria
: criteriaResolvers[0].identifier;
uint256 balanceBefore = nftToken.balanceOf(address(this), identifier);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(order, criteriaResolvers, address(this), 0)
: _fillSingleOrder(order, criteriaResolvers, address(this), 0);
uint256 balanceAfter = nftToken.balanceOf(address(this), identifier);
// Pay fees
uint256 amountFilled = balanceBefore - balanceAfter;
if (amountFilled > 0) {
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(
fee.recipient,
// Only pay fees for the amount that was actually filled
(fee.amount * amountFilled) / order.numerator,
paymentToken
);
unchecked {
++i;
}
}
}
// Refund any ERC1155 leftover
_sendAllERC1155(params.refundTo, nftToken, identifier);
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, paymentToken);
}
// --- Generic handler (used for Seaport-based approvals) ---
function matchOrders(
ISeaport.Order[] calldata orders,
ISeaport.Fulfillment[] calldata fulfillments
) external nonReentrant {
// We don't perform any kind of input or return value validation,
// so this function should be used with precaution - the official
// way to use it is only for Seaport-based approvals
EXCHANGE.matchOrders(orders, fulfillments);
}
// --- ERC721 / ERC1155 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
// --- Internal ---
// NOTE: In lots of cases, Seaport will not revert if fills were not
// fully executed. An example of that is partial filling, which will
// successfully fill any amount that is still available (including a
// zero amount). One way to ensure that we revert in case of partial
// executions is to check the order's filled amount before and after
// we trigger the fill (we can use Seaport's `getOrderStatus` method
// to check). Since this can be expensive in terms of gas, we have a
// separate method variant to be called when reverts are enabled.
function _fillSingleOrder(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
address receiver,
uint256 value
) internal {
// Execute the fill
try
EXCHANGE.fulfillAdvancedOrder{value: value}(order, criteriaResolvers, bytes32(0), receiver)
{} catch {}
}
function _fillSingleOrderWithRevertIfIncomplete(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
address receiver,
uint256 value
) internal {
// Cache the order's hash
bytes32 orderHash = _getOrderHash(order.parameters);
// Before filling, get the order's filled amount
uint256 beforeFilledAmount = _getFilledAmount(orderHash);
// Execute the fill
bool success;
try
EXCHANGE.fulfillAdvancedOrder{value: value}(order, criteriaResolvers, bytes32(0), receiver)
returns (bool fulfilled) {
success = fulfilled;
} catch {
revert UnsuccessfulFill();
}
if (!success) {
revert UnsuccessfulFill();
} else {
// After successfully filling, get the order's filled amount
uint256 afterFilledAmount = _getFilledAmount(orderHash);
// Make sure the amount filled as part of this call is correct
if (afterFilledAmount - beforeFilledAmount != order.numerator) {
revert UnsuccessfulFill();
}
}
}
function _getOrderHash(
// Must use `memory` instead of `calldata` for the below cast
ISeaport.OrderParameters memory orderParameters
) internal view returns (bytes32 orderHash) {
// `OrderParameters` and `OrderComponents` share the exact same
// fields, apart from the last one, so here we simply treat the
// `orderParameters` argument as `OrderComponents` and then set
// the last field to the correct data
ISeaport.OrderComponents memory orderComponents;
assembly {
orderComponents := orderParameters
}
orderComponents.counter = EXCHANGE.getCounter(orderParameters.offerer);
orderHash = EXCHANGE.getOrderHash(orderComponents);
}
function _getFilledAmount(bytes32 orderHash) internal view returns (uint256 totalFilled) {
(, , totalFilled, ) = EXCHANGE.getOrderStatus(orderHash);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {ISeaport} from "../../../interfaces/ISeaport.sol";
// Notes on the Seaport module:
// - supports filling listings (both ERC721/ERC1155)
// - supports filling offers (both ERC721/ERC1155)
contract SeaportV15Module is BaseExchangeModule {
// --- Structs ---
struct SeaportETHListingWithPrice {
ISeaport.AdvancedOrder order;
uint256 price;
}
struct SeaportPrivateListingWithPrice {
ISeaport.AdvancedOrder[] orders;
ISeaport.Fulfillment[] fulfillments;
uint256 price;
}
// --- Fields ---
ISeaport public immutable EXCHANGE;
// --- Constructor ---
constructor(
address owner,
address router,
address exchange
) BaseModule(owner) BaseExchangeModule(router) {
EXCHANGE = ISeaport(exchange);
}
// --- Fallback ---
receive() external payable {}
// --- Single ETH listing ---
function acceptETHListing(
ISeaport.AdvancedOrder calldata order,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(
order,
new ISeaport.CriteriaResolver[](0),
params.fillTo,
params.amount
)
: _fillSingleOrder(order, new ISeaport.CriteriaResolver[](0), params.fillTo, params.amount);
}
// --- Single ERC20 listing ---
function acceptERC20Listing(
ISeaport.AdvancedOrder calldata order,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(
order,
new ISeaport.CriteriaResolver[](0),
params.fillTo,
0
)
: _fillSingleOrder(order, new ISeaport.CriteriaResolver[](0), params.fillTo, 0);
}
// --- Multiple ETH listings ---
function acceptETHListings(
SeaportETHListingWithPrice[] calldata orders,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256 length = orders.length;
ISeaport.CriteriaResolver[] memory criteriaResolvers = new ISeaport.CriteriaResolver[](0);
// Execute the fills
if (params.revertIfIncomplete) {
for (uint256 i; i < length; ) {
_fillSingleOrderWithRevertIfIncomplete(
orders[i].order,
criteriaResolvers,
params.fillTo,
orders[i].price
);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < length; ) {
_fillSingleOrder(orders[i].order, criteriaResolvers, params.fillTo, orders[i].price);
unchecked {
++i;
}
}
}
}
// --- Multiple ERC20 listings ---
function acceptERC20Listings(
ISeaport.AdvancedOrder[] calldata orders,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
uint256 length = orders.length;
ISeaport.CriteriaResolver[] memory criteriaResolvers = new ISeaport.CriteriaResolver[](0);
// Execute the fills
if (params.revertIfIncomplete) {
for (uint256 i; i < length; ) {
_fillSingleOrderWithRevertIfIncomplete(orders[i], criteriaResolvers, params.fillTo, 0);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < length; ) {
_fillSingleOrder(orders[i], criteriaResolvers, params.fillTo, 0);
unchecked {
++i;
}
}
}
}
// --- Multiple ETH private listings ---
function acceptETHPrivateListings(
SeaportPrivateListingWithPrice[] calldata orders,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256 length = orders.length;
ISeaport.CriteriaResolver[] memory criteriaResolvers = new ISeaport.CriteriaResolver[](0);
// Execute the fills
if (params.revertIfIncomplete) {
for (uint256 i; i < length; ) {
_fillPrivateOrderWithRevertIfIncomplete(
orders[i].orders,
criteriaResolvers,
orders[i].fulfillments,
params.fillTo,
orders[i].price
);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < length; ) {
_fillPrivateOrder(
orders[i].orders,
criteriaResolvers,
orders[i].fulfillments,
params.fillTo,
orders[i].price
);
unchecked {
++i;
}
}
}
}
// --- Multiple Private ERC20 Private listings ---
function acceptERC20PrivateListings(
SeaportPrivateListingWithPrice[] calldata orders,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
uint256 length = orders.length;
ISeaport.CriteriaResolver[] memory criteriaResolvers = new ISeaport.CriteriaResolver[](0);
// Execute the fills
if (params.revertIfIncomplete) {
for (uint256 i; i < length; ) {
_fillPrivateOrderWithRevertIfIncomplete(
orders[i].orders,
criteriaResolvers,
orders[i].fulfillments,
params.fillTo,
0
);
unchecked {
++i;
}
}
} else {
for (uint256 i; i < length; ) {
_fillPrivateOrder(
orders[i].orders,
criteriaResolvers,
orders[i].fulfillments,
params.fillTo,
0
);
unchecked {
++i;
}
}
}
}
// --- Single ERC721 offer ---
function acceptERC721Offer(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
// Extract the ERC721 token from the consideration items
ISeaport.ConsiderationItem calldata nftItem = order.parameters.consideration[0];
if (
nftItem.itemType != ISeaport.ItemType.ERC721 &&
nftItem.itemType != ISeaport.ItemType.ERC721_WITH_CRITERIA
) {
revert WrongParams();
}
IERC721 nftToken = IERC721(nftItem.token);
// Extract the payment token from the offer items
ISeaport.OfferItem calldata paymentItem = order.parameters.offer[0];
IERC20 paymentToken = IERC20(paymentItem.token);
// Approve the exchange if needed
_approveERC721IfNeeded(nftToken, address(EXCHANGE));
_approveERC20IfNeeded(paymentToken, address(EXCHANGE), type(uint256).max);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(order, criteriaResolvers, address(this), 0)
: _fillSingleOrder(order, criteriaResolvers, address(this), 0);
uint256 identifier = nftItem.itemType == ISeaport.ItemType.ERC721
? nftItem.identifierOrCriteria
: criteriaResolvers[0].identifier;
// Pay fees
if (nftToken.ownerOf(identifier) != address(this)) {
// Only pay fees if the fill was successful
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, paymentToken);
unchecked {
++i;
}
}
}
// Refund any ERC721 leftover
_sendAllERC721(params.refundTo, nftToken, identifier);
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, paymentToken);
}
// --- Single ERC1155 offer ---
function acceptERC1155Offer(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
// Extract the ERC1155 token from the consideration items
ISeaport.ConsiderationItem calldata nftItem = order.parameters.consideration[0];
if (
nftItem.itemType != ISeaport.ItemType.ERC1155 &&
nftItem.itemType != ISeaport.ItemType.ERC1155_WITH_CRITERIA
) {
revert WrongParams();
}
IERC1155 nftToken = IERC1155(nftItem.token);
// Extract the payment token from the offer items
ISeaport.OfferItem calldata paymentItem = order.parameters.offer[0];
IERC20 paymentToken = IERC20(paymentItem.token);
// Approve the exchange if needed
_approveERC1155IfNeeded(nftToken, address(EXCHANGE));
_approveERC20IfNeeded(paymentToken, address(EXCHANGE), type(uint256).max);
uint256 identifier = nftItem.itemType == ISeaport.ItemType.ERC1155
? nftItem.identifierOrCriteria
: criteriaResolvers[0].identifier;
uint256 balanceBefore = nftToken.balanceOf(address(this), identifier);
// Execute the fill
params.revertIfIncomplete
? _fillSingleOrderWithRevertIfIncomplete(order, criteriaResolvers, address(this), 0)
: _fillSingleOrder(order, criteriaResolvers, address(this), 0);
uint256 balanceAfter = nftToken.balanceOf(address(this), identifier);
// Pay fees
uint256 amountFilled = balanceBefore - balanceAfter;
if (amountFilled > 0) {
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(
fee.recipient,
// Only pay fees for the amount that was actually filled
(fee.amount * amountFilled) / order.numerator,
paymentToken
);
unchecked {
++i;
}
}
}
// Refund any ERC1155 leftover
_sendAllERC1155(params.refundTo, nftToken, identifier);
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, paymentToken);
}
// --- Generic handler (used for Seaport-based approvals) ---
function matchOrders(
ISeaport.Order[] calldata orders,
ISeaport.Fulfillment[] calldata fulfillments
) external nonReentrant {
// We don't perform any kind of input or return value validation,
// so this function should be used with precaution - the official
// way to use it is only for Seaport-based approvals
EXCHANGE.matchOrders(orders, fulfillments);
}
// --- ERC1271 ---
function isValidSignature(bytes32, bytes memory) external pure returns (bytes4) {
// Needed for filling private listings
return this.isValidSignature.selector;
}
// --- ERC721 / ERC1155 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
// --- Internal ---
// NOTE: In lots of cases, Seaport will not revert if fills were not
// fully executed. An example of that is partial filling, which will
// successfully fill any amount that is still available (including a
// zero amount). One way to ensure that we revert in case of partial
// executions is to check the order's filled amount before and after
// we trigger the fill (we can use Seaport's `getOrderStatus` method
// to check). Since this can be expensive in terms of gas, we have a
// separate method variant to be called when reverts are enabled.
function _fillSingleOrder(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
address receiver,
uint256 value
) internal {
// Execute the fill
try
EXCHANGE.fulfillAdvancedOrder{value: value}(order, criteriaResolvers, bytes32(0), receiver)
{} catch {}
}
function _fillSingleOrderWithRevertIfIncomplete(
ISeaport.AdvancedOrder calldata order,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
address receiver,
uint256 value
) internal {
// Cache the order's hash
bytes32 orderHash = _getOrderHash(order.parameters);
// Before filling, get the order's filled amount
uint256 beforeFilledAmount = _getFilledAmount(orderHash, order.denominator);
// Execute the fill
bool success;
try
EXCHANGE.fulfillAdvancedOrder{value: value}(order, criteriaResolvers, bytes32(0), receiver)
returns (bool fulfilled) {
success = fulfilled;
} catch {
revert UnsuccessfulFill();
}
if (!success) {
revert UnsuccessfulFill();
} else {
// After successfully filling, get the order's filled amount
uint256 afterFilledAmount = _getFilledAmount(orderHash, order.denominator);
// Make sure the amount filled as part of this call is correct
if (afterFilledAmount - beforeFilledAmount != order.numerator) {
revert UnsuccessfulFill();
}
}
}
function _fillPrivateOrder(
ISeaport.AdvancedOrder[] calldata advancedOrders,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
ISeaport.Fulfillment[] memory fulfillments,
address receiver,
uint256 value
) internal {
// Execute the fill
try
EXCHANGE.matchAdvancedOrders{value: value}(
advancedOrders,
criteriaResolvers,
fulfillments,
receiver
)
{} catch {}
}
function _fillPrivateOrderWithRevertIfIncomplete(
ISeaport.AdvancedOrder[] calldata advancedOrders,
// Use `memory` instead of `calldata` to avoid `Stack too deep` errors
ISeaport.CriteriaResolver[] memory criteriaResolvers,
ISeaport.Fulfillment[] memory fulfillments,
address receiver,
uint256 value
) internal {
// Execute the fill
try
EXCHANGE.matchAdvancedOrders{value: value}(
advancedOrders,
criteriaResolvers,
fulfillments,
receiver
)
{} catch {
revert UnsuccessfulFill();
}
}
function _getOrderHash(
// Must use `memory` instead of `calldata` for the below cast
ISeaport.OrderParameters memory orderParameters
) internal view returns (bytes32 orderHash) {
// `OrderParameters` and `OrderComponents` share the exact same
// fields, apart from the last one, so here we simply treat the
// `orderParameters` argument as `OrderComponents` and then set
// the last field to the correct data
ISeaport.OrderComponents memory orderComponents;
assembly {
orderComponents := orderParameters
}
orderComponents.counter = EXCHANGE.getCounter(orderParameters.offerer);
orderHash = EXCHANGE.getOrderHash(orderComponents);
}
function _getFilledAmount(
bytes32 orderHash,
uint256 adjustedTotalSize
) internal view returns (uint256 adjustedTotalFilled) {
(, , uint256 totalFilled, uint256 totalSize) = EXCHANGE.getOrderStatus(orderHash);
adjustedTotalFilled = totalSize > 0 ? (totalFilled * adjustedTotalSize) / totalSize : 0;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {ISudoswapPair, ISudoswapRouter} from "../../../interfaces/ISudoswap.sol";
contract SudoswapModule is BaseExchangeModule {
// --- Fields ---
ISudoswapRouter public immutable SUDOSWAP_ROUTER;
// --- Constructor ---
constructor(
address owner,
address router,
address sudoswapRouter
) BaseModule(owner) BaseExchangeModule(router) {
SUDOSWAP_ROUTER = ISudoswapRouter(sudoswapRouter);
}
// --- Fallback ---
receive() external payable {}
// --- Multiple ETH listings ---
function buyWithETH(
ISudoswapPair[] calldata pairs,
uint256[] calldata nftIds,
uint256 deadline,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256 pairsLength = pairs.length;
for (uint256 i; i < pairsLength; ) {
// Build router data
ISudoswapRouter.PairSwapSpecific[] memory swapList = new ISudoswapRouter.PairSwapSpecific[](
1
);
swapList[0] = ISudoswapRouter.PairSwapSpecific({pair: pairs[i], nftIds: new uint256[](1)});
swapList[0].nftIds[0] = nftIds[i];
// Fetch the current price quote
(, , , uint256 price, ) = pairs[i].getBuyNFTQuote(1);
// Execute fill
try
SUDOSWAP_ROUTER.swapETHForSpecificNFTs{value: price}(
swapList,
address(this),
params.fillTo,
deadline
)
{} catch {
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
unchecked {
++i;
}
}
}
// --- Multiple ERC20 listings ---
function buyWithERC20(
ISudoswapPair[] calldata pairs,
uint256[] calldata nftIds,
uint256 deadline,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the router if needed
_approveERC20IfNeeded(params.token, address(SUDOSWAP_ROUTER), params.amount);
uint256 pairsLength = pairs.length;
for (uint256 i; i < pairsLength; ) {
// Build router data
ISudoswapRouter.PairSwapSpecific[] memory swapList = new ISudoswapRouter.PairSwapSpecific[](
1
);
swapList[0] = ISudoswapRouter.PairSwapSpecific({pair: pairs[i], nftIds: new uint256[](1)});
swapList[0].nftIds[0] = nftIds[i];
// Fetch the current price quote
(, , , uint256 price, ) = pairs[i].getBuyNFTQuote(1);
// Execute fill
try
SUDOSWAP_ROUTER.swapERC20ForSpecificNFTs(swapList, price, params.fillTo, deadline)
{} catch {
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
unchecked {
++i;
}
}
}
// --- Single ERC721 offer ---
function sell(
ISudoswapPair pair,
uint256 nftId,
uint256 minOutput,
uint256 deadline,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
IERC721 collection = pair.nft();
// Approve the router if needed
_approveERC721IfNeeded(collection, address(SUDOSWAP_ROUTER));
// Build router data
ISudoswapRouter.PairSwapSpecific[] memory swapList = new ISudoswapRouter.PairSwapSpecific[](1);
swapList[0] = ISudoswapRouter.PairSwapSpecific({pair: pair, nftIds: new uint256[](1)});
swapList[0].nftIds[0] = nftId;
// Execute fill
try SUDOSWAP_ROUTER.swapNFTsForToken(swapList, minOutput, address(this), deadline) {
ISudoswapPair.PairVariant variant = pair.pairVariant();
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
uint8(variant) < 2
? _sendETH(fee.recipient, fee.amount)
: _sendERC20(fee.recipient, fee.amount, pair.token());
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
uint8(variant) < 2 ? _sendAllETH(params.fillTo) : _sendAllERC20(params.fillTo, pair.token());
} catch {
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any ERC721 leftover
_sendAllERC721(params.refundTo, collection, nftId);
}
// --- ERC721 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {ISudoswapPairV2} from "../../../interfaces/ISudoswapV2.sol";
contract SudoswapV2Module is BaseExchangeModule {
// --- Constructor ---
constructor(address owner, address router) BaseModule(owner) BaseExchangeModule(router) {}
// --- Fallback ---
receive() external payable {}
// --- Multiple ETH listings ---
function buyWithETH(
ISudoswapPairV2[] calldata pairs,
// Token ids for ERC721 pairs, amounts for ERC1155 pairs
uint256[] calldata nftIds,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256[] memory tokenIds = new uint256[](1);
uint256 pairsLength = pairs.length;
for (uint256 i; i < pairsLength; ) {
ISudoswapPairV2 pair = pairs[i];
ISudoswapPairV2.PairVariant variant = pair.pairVariant();
bool isERC1155 = isERC1155Pair(variant);
// Fetch the current price
(, , , uint256 price, , ) = pair.getBuyNFTQuote(
isERC1155 ? pair.nftId() : nftIds[i],
isERC1155 ? nftIds[i] : 1
);
tokenIds[0] = nftIds[i];
// Execute fill
try
pair.swapTokenForSpecificNFTs{value: price}(
tokenIds,
price,
params.fillTo,
false,
address(0)
)
{} catch {
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
unchecked {
++i;
}
}
}
// --- Multiple ERC20 listings ---
function buyWithERC20(
ISudoswapPairV2[] calldata pairs,
// Token ids for ERC721 pairs, amounts for ERC1155 pairs
uint256[] calldata nftIds,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
uint256[] memory tokenIds = new uint256[](1);
uint256 pairsLength = pairs.length;
for (uint256 i; i < pairsLength; ) {
ISudoswapPairV2 pair = pairs[i];
ISudoswapPairV2.PairVariant variant = pair.pairVariant();
bool isERC1155 = isERC1155Pair(variant);
// Fetch the current price
(, , , uint256 price, , ) = pair.getBuyNFTQuote(
isERC1155 ? pair.nftId() : nftIds[i],
isERC1155 ? nftIds[i] : 1
);
tokenIds[0] = nftIds[i];
// Approve the pair if needed
_approveERC20IfNeeded(params.token, address(pair), params.amount);
// Execute fill
try
pair.swapTokenForSpecificNFTs(tokenIds, price, params.fillTo, false, address(0))
{} catch {
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
unchecked {
++i;
}
}
}
// --- Single offer ---
function sell(
ISudoswapPairV2 pair,
// Token id for ERC721 pairs, amount for ERC1155 pairs
uint256 nftId,
uint256 minOutput,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
ISudoswapPairV2.PairVariant variant = pair.pairVariant();
bool isETH = isETHPair(variant);
address nft = pair.nft();
IERC20 token = isETH ? IERC20(address(0)) : pair.token();
// Approve the pair if needed
if (!isERC1155Pair(variant)) {
_approveERC721IfNeeded(IERC721(nft), address(pair));
} else {
_approveERC1155IfNeeded(IERC1155(nft), address(pair));
}
// Build router data
uint256[] memory tokenIds = new uint256[](1);
tokenIds[0] = nftId;
// Execute fill
try pair.swapNFTsForToken(tokenIds, minOutput, payable(address(this)), false, address(0)) {
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
isETH ? _sendETH(fee.recipient, fee.amount) : _sendERC20(fee.recipient, fee.amount, token);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
isETH ? _sendAllETH(params.fillTo) : _sendAllERC20(params.fillTo, token);
} catch {
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any leftovers
if (!isERC1155Pair(variant)) {
_sendAllERC721(params.refundTo, IERC721(nft), nftId);
} else {
_sendAllERC1155(params.refundTo, IERC1155(nft), pair.nftId());
}
}
// --- ERC721/1155 hooks ---
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
// --- Internal methods ---
function isERC1155Pair(ISudoswapPairV2.PairVariant vaiant) internal pure returns (bool) {
return
ISudoswapPairV2.PairVariant.ERC1155_ERC20 == vaiant ||
ISudoswapPairV2.PairVariant.ERC1155_ETH == vaiant;
}
function isETHPair(ISudoswapPairV2.PairVariant vaiant) internal pure returns (bool) {
return
ISudoswapPairV2.PairVariant.ERC721_ETH == vaiant ||
ISudoswapPairV2.PairVariant.ERC1155_ETH == vaiant;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {ISuperRare} from "../../../interfaces/ISuperRare.sol";
// Notes:
// - only supports filling "buy now" listings (ERC721 and ETH-denominated)
contract SuperRareModule is BaseExchangeModule {
// --- Structs ---
struct Listing {
IERC721 token;
uint256 tokenId;
address currency;
uint256 price;
uint256 priceWithFees;
}
// --- Fields ---
ISuperRare public immutable BAZAAR;
// --- Constructor ---
constructor(
address owner,
address router,
address bazaar
) BaseModule(owner) BaseExchangeModule(router) {
BAZAAR = ISuperRare(bazaar);
}
// --- Fallback ---
receive() external payable {}
// --- Single ETH listing ---
function acceptETHListing(
Listing calldata listing,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buy(
listing.token,
listing.tokenId,
listing.currency,
listing.price,
params.fillTo,
params.revertIfIncomplete,
listing.priceWithFees
);
}
// --- Multiple ETH listings ---
function acceptETHListings(
Listing[] calldata listings,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
uint256 length = listings.length;
for (uint256 i = 0; i < length; ) {
_buy(
listings[i].token,
listings[i].tokenId,
listings[i].currency,
listings[i].price,
params.fillTo,
params.revertIfIncomplete,
listings[i].priceWithFees
);
unchecked {
++i;
}
}
}
// --- ERC721 hooks ---
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata // data
) external pure returns (bytes4) {
return this.onERC721Received.selector;
}
// --- Internal ---
function _buy(
IERC721 token,
uint256 tokenId,
address currency,
uint256 price,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
// Execute fill
try BAZAAR.buy{value: value}(token, tokenId, currency, price) {
token.safeTransferFrom(address(this), receiver, tokenId);
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {IUniswapV3Router} from "../../../interfaces/IUniswapV3Router.sol";
import {IWETH} from "../../../interfaces/IWETH.sol";
// Notes:
// - supports swapping ETH and ERC20 to any token via a direct path
contract SwapModule is BaseExchangeModule {
struct TransferDetail {
address recipient;
uint256 amount;
bool toETH;
}
struct BuySwap {
IUniswapV3Router.ExactOutputSingleParams params;
TransferDetail[] transfers;
}
struct SellSwap {
IUniswapV3Router.ExactInputSingleParams params;
TransferDetail[] transfers;
}
// --- Fields ---
IWETH public immutable WETH;
IUniswapV3Router public immutable SWAP_ROUTER;
// --- Constructor ---
constructor(
address owner,
address router,
address weth,
address swapRouter
) BaseModule(owner) BaseExchangeModule(router) {
WETH = IWETH(weth);
SWAP_ROUTER = IUniswapV3Router(swapRouter);
}
// --- Fallback ---
receive() external payable {}
// --- Wrap ---
function wrap(TransferDetail[] calldata targets) external payable nonReentrant {
WETH.deposit{value: msg.value}();
uint256 length = targets.length;
for (uint256 i = 0; i < length; ) {
// Zero represents "everything"
uint256 amount = targets[i].amount == 0 ? WETH.balanceOf(address(this)) : targets[i].amount;
_sendERC20(targets[i].recipient, amount, WETH);
unchecked {
++i;
}
}
}
// --- Unwrap ---
function unwrap(TransferDetail[] calldata targets) external nonReentrant {
uint256 balance = WETH.balanceOf(address(this));
WETH.withdraw(balance);
uint256 length = targets.length;
for (uint256 i = 0; i < length; ) {
// Zero represents "everything"
uint256 amount = targets[i].amount == 0 ? address(this).balance : targets[i].amount;
_sendETH(targets[i].recipient, amount);
unchecked {
++i;
}
}
}
// --- Swaps ---
function ethToExactOutput(
// Assumes all swaps have the same token in
BuySwap[] calldata swaps,
address refundTo,
bool revertIfIncomplete
) external payable nonReentrant refundETHLeftover(refundTo) {
uint256 swapsLength = swaps.length;
for (uint256 i; i < swapsLength; ) {
BuySwap calldata swap = swaps[i];
// Execute the swap
try SWAP_ROUTER.exactOutputSingle{value: swap.params.amountInMaximum}(swap.params) {
uint256 length = swap.transfers.length;
for (uint256 j = 0; j < length; ) {
TransferDetail calldata transferDetail = swap.transfers[j];
if (transferDetail.toETH) {
// Zero represents "everything"
uint256 amount = transferDetail.amount == 0
? WETH.balanceOf(address(this))
: transferDetail.amount;
WETH.withdraw(amount);
_sendETH(transferDetail.recipient, amount);
} else {
// Zero represents "everything"
uint256 amount = transferDetail.amount == 0
? IERC20(swap.params.tokenOut).balanceOf(address(this))
: transferDetail.amount;
_sendERC20(transferDetail.recipient, amount, IERC20(swap.params.tokenOut));
}
unchecked {
++j;
}
}
} catch {
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
unchecked {
++i;
}
}
// Refund any ETH stucked in the router
SWAP_ROUTER.refundETH();
}
function erc20ToExactOutput(
// Assumes all swaps have the same token in
BuySwap[] calldata swaps,
address refundTo,
bool revertIfIncomplete
) external nonReentrant refundERC20Leftover(refundTo, swaps[0].params.tokenIn) {
uint256 swapsLength = swaps.length;
for (uint256 i; i < swapsLength; ) {
BuySwap calldata swap = swaps[i];
// Approve the router if needed
_approveERC20IfNeeded(swap.params.tokenIn, address(SWAP_ROUTER), swap.params.amountInMaximum);
// Execute the swap
try SWAP_ROUTER.exactOutputSingle(swap.params) {
uint256 transfersLength = swap.transfers.length;
for (uint256 j = 0; j < transfersLength; ) {
TransferDetail calldata transferDetail = swap.transfers[j];
if (transferDetail.toETH) {
// Zero represents "everything"
uint256 amount = transferDetail.amount == 0
? WETH.balanceOf(address(this))
: transferDetail.amount;
WETH.withdraw(amount);
_sendETH(transferDetail.recipient, amount);
} else {
// Zero represents "everything"
uint256 amount = transferDetail.amount == 0
? IERC20(swap.params.tokenOut).balanceOf(address(this))
: transferDetail.amount;
_sendERC20(transferDetail.recipient, amount, IERC20(swap.params.tokenOut));
}
unchecked {
++j;
}
}
} catch {
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
unchecked {
++i;
}
}
}
function erc20ToExactInput(
// Assumes all swaps have the same token in
SellSwap[] calldata swaps,
address refundTo,
bool revertIfIncomplete
) external nonReentrant refundERC20Leftover(refundTo, swaps[0].params.tokenIn) {
uint256 swapsLength = swaps.length;
for (uint256 i; i < swapsLength; ) {
SellSwap calldata swap = swaps[i];
// Approve the router if needed
_approveERC20IfNeeded(swap.params.tokenIn, address(SWAP_ROUTER), swap.params.amountIn);
// Execute the swap
try SWAP_ROUTER.exactInputSingle(swap.params) {
uint256 transfersLength = swap.transfers.length;
for (uint256 j = 0; j < transfersLength; ) {
TransferDetail calldata transferDetail = swap.transfers[j];
if (transferDetail.toETH) {
// Zero represents "everything"
uint256 amount = transferDetail.amount == 0
? WETH.balanceOf(address(this))
: transferDetail.amount;
WETH.withdraw(amount);
_sendETH(transferDetail.recipient, amount);
} else {
// Zero represents "everything"
uint256 amount = transferDetail.amount == 0
? IERC20(swap.params.tokenOut).balanceOf(address(this))
: transferDetail.amount;
_sendERC20(transferDetail.recipient, amount, IERC20(swap.params.tokenOut));
}
unchecked {
++j;
}
}
} catch {
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
unchecked {
++i;
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {IX2Y2} from "../../../interfaces/IX2Y2.sol";
// Notes on the X2Y2 module:
// - supports filling listings (both ERC721/ERC1155 but only ETH-denominated)
// - supports filling offers (both ERC721/ERC1155)
contract X2Y2Module is BaseExchangeModule {
using SafeERC20 for IERC20;
// --- Fields ---
IX2Y2 public immutable EXCHANGE;
address public immutable ERC721_DELEGATE;
address public immutable ERC1155_DELEGATE;
// --- Constructor ---
constructor(
address owner,
address router,
address exchange,
address erc721Delegate,
address erc1155Delegate
) BaseModule(owner) BaseExchangeModule(router) {
EXCHANGE = IX2Y2(exchange);
ERC721_DELEGATE = erc721Delegate;
ERC1155_DELEGATE = erc1155Delegate;
}
// --- Fallback ---
receive() external payable {}
// --- Single ETH listing ---
function acceptETHListing(
IX2Y2.RunInput calldata input,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buy(input, params.fillTo, params.revertIfIncomplete, params.amount);
}
// --- Multiple ETH listings ---
function acceptETHListings(
IX2Y2.RunInput[] calldata inputs,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// X2Y2 does not support batch filling so we fill orders one by one
uint256 length = inputs.length;
for (uint256 i = 0; i < length; ) {
// Execute fill
_buy(inputs[i], params.fillTo, params.revertIfIncomplete, inputs[i].details[0].price);
unchecked {
++i;
}
}
}
// --- [ERC721] Single offer ---
function acceptERC721Offer(
IX2Y2.RunInput calldata input,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
if (input.details.length != 1) {
revert WrongParams();
}
// Extract the order's corresponding token
IX2Y2.SettleDetail calldata detail = input.details[0];
IX2Y2.Order calldata order = input.orders[detail.orderIdx];
IX2Y2.OrderItem calldata orderItem = order.items[detail.itemIdx];
if (detail.op != IX2Y2.Op.COMPLETE_BUY_OFFER) {
revert WrongParams();
}
// Apply any mask (if required)
bytes memory data = orderItem.data;
{
if (order.dataMask.length > 0 && detail.dataReplacement.length > 0) {
_arrayReplace(data, detail.dataReplacement, order.dataMask);
}
}
IX2Y2.ERC721Pair[] memory pairs = abi.decode(orderItem.data, (IX2Y2.ERC721Pair[]));
if (pairs.length != 1) {
revert WrongParams();
}
IERC721 collection = pairs[0].token;
uint256 tokenId = pairs[0].tokenId;
// Approve the delegate if needed
_approveERC721IfNeeded(collection, ERC721_DELEGATE);
// Execute fill
try EXCHANGE.run(input) {
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, order.currency);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, order.currency);
} catch {
// Revert if specified
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any ERC721 leftover
_sendAllERC721(params.refundTo, collection, tokenId);
}
// --- [ERC1155] Single offer ---
function acceptERC1155Offer(
IX2Y2.RunInput calldata input,
OfferParams calldata params,
Fee[] calldata fees
) external nonReentrant {
if (input.details.length != 1) {
revert WrongParams();
}
// Extract the order's corresponding token
IX2Y2.SettleDetail calldata detail = input.details[0];
IX2Y2.Order calldata order = input.orders[detail.orderIdx];
IX2Y2.OrderItem calldata orderItem = order.items[detail.itemIdx];
if (detail.op != IX2Y2.Op.COMPLETE_BUY_OFFER) {
revert WrongParams();
}
// Apply any mask (if required)
bytes memory data = orderItem.data;
{
if (order.dataMask.length > 0 && detail.dataReplacement.length > 0) {
_arrayReplace(data, detail.dataReplacement, order.dataMask);
}
}
IX2Y2.ERC1155Pair[] memory pairs = abi.decode(orderItem.data, (IX2Y2.ERC1155Pair[]));
if (pairs.length != 1) {
revert WrongParams();
}
IERC1155 collection = pairs[0].token;
uint256 tokenId = pairs[0].tokenId;
// Approve the delegate if needed
_approveERC1155IfNeeded(collection, ERC1155_DELEGATE);
// Execute fill
try EXCHANGE.run(input) {
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, order.currency);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, order.currency);
} catch {
// Revert if specified
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any ERC1155 leftover
_sendAllERC1155(params.refundTo, collection, tokenId);
}
// --- ERC721 / ERC1155 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
// --- Internal ---
function _arrayReplace(
bytes memory source,
bytes memory replacement,
bytes memory mask
) internal view virtual {
uint256 sourceLength = source.length;
for (uint256 i; i < sourceLength; ) {
if (mask[i] != 0) {
source[i] = replacement[i];
}
unchecked {
++i;
}
}
}
function _buy(
IX2Y2.RunInput calldata input,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
if (input.details.length != 1) {
revert WrongParams();
}
// Extract the order's corresponding token
IX2Y2.SettleDetail calldata detail = input.details[0];
IX2Y2.Order calldata order = input.orders[detail.orderIdx];
IX2Y2.OrderItem calldata orderItem = order.items[detail.itemIdx];
if (detail.op != IX2Y2.Op.COMPLETE_SELL_OFFER) {
revert WrongParams();
}
// Execute fill
try EXCHANGE.run{value: value}(input) {
if (order.delegateType == 1) {
IX2Y2.ERC721Pair[] memory pairs = abi.decode(orderItem.data, (IX2Y2.ERC721Pair[]));
if (pairs.length != 1) {
revert WrongParams();
}
pairs[0].token.safeTransferFrom(address(this), receiver, pairs[0].tokenId);
} else {
IX2Y2.ERC1155Pair[] memory pairs = abi.decode(orderItem.data, (IX2Y2.ERC1155Pair[]));
if (pairs.length != 1) {
revert WrongParams();
}
pairs[0].token.safeTransferFrom(
address(this),
receiver,
pairs[0].tokenId,
pairs[0].amount,
""
);
}
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {IZeroExV4} from "../../../interfaces/IZeroExV4.sol";
// Notes:
// - supports filling listings (both ERC721/ERC1155)
// - supports filling offers (both ERC721/ERC1155)
contract ZeroExV4Module is BaseExchangeModule {
using SafeERC20 for IERC20;
// --- Fields ---
IZeroExV4 public immutable EXCHANGE;
// --- Constructor ---
constructor(
address owner,
address router,
address exchange
) BaseModule(owner) BaseExchangeModule(router) {
EXCHANGE = IZeroExV4(exchange);
}
// --- Fallback ---
receive() external payable {}
// --- [ERC721] Single ETH listing ---
function acceptETHListingERC721(
IZeroExV4.ERC721Order calldata order,
IZeroExV4.Signature calldata signature,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buyERC721(order, signature, params.fillTo, params.revertIfIncomplete, params.amount);
}
// --- [ERC721] Single ERC20 listing ---
function acceptERC20ListingERC721(
IZeroExV4.ERC721Order calldata order,
IZeroExV4.Signature calldata signature,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute fill
_buyERC721(order, signature, params.fillTo, params.revertIfIncomplete, 0);
}
// --- [ERC721] Multiple ETH listings ---
function acceptETHListingsERC721(
IZeroExV4.ERC721Order[] calldata orders,
IZeroExV4.Signature[] calldata signatures,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buyERC721s(orders, signatures, params.fillTo, params.revertIfIncomplete, params.amount);
}
// --- [ERC721] Multiple ERC20 listings ---
function acceptERC20ListingsERC721(
IZeroExV4.ERC721Order[] calldata orders,
IZeroExV4.Signature[] calldata signatures,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute fill
_buyERC721s(orders, signatures, params.fillTo, params.revertIfIncomplete, 0);
}
// --- [ERC1155] Single ETH listing ---
function acceptETHListingERC1155(
IZeroExV4.ERC1155Order calldata order,
IZeroExV4.Signature calldata signature,
uint128 amount,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buyERC1155(order, signature, amount, params.fillTo, params.revertIfIncomplete, params.amount);
}
// --- [ERC1155] Single ERC20 listing ---
function acceptERC20ListingERC1155(
IZeroExV4.ERC1155Order calldata order,
IZeroExV4.Signature calldata signature,
uint128 amount,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute fill
_buyERC1155(order, signature, amount, params.fillTo, params.revertIfIncomplete, 0);
}
// --- [ERC1155] Multiple ETH listings ---
function acceptETHListingsERC1155(
IZeroExV4.ERC1155Order[] calldata orders,
IZeroExV4.Signature[] calldata signatures,
uint128[] memory amounts,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buyERC1155s(
orders,
signatures,
amounts,
params.fillTo,
params.revertIfIncomplete,
params.amount
);
}
// --- [ERC1155] Multiple ERC20 listings ---
function acceptERC20ListingsERC1155(
IZeroExV4.ERC1155Order[] calldata orders,
IZeroExV4.Signature[] calldata signatures,
uint128[] memory amounts,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Approve the exchange if needed
_approveERC20IfNeeded(params.token, address(EXCHANGE), params.amount);
// Execute fill
_buyERC1155s(orders, signatures, amounts, params.fillTo, params.revertIfIncomplete, 0);
}
// --- [ERC721] Single offer ---
function acceptERC721Offer(
IZeroExV4.ERC721Order calldata order,
IZeroExV4.Signature calldata signature,
OfferParams calldata params,
uint256 tokenId,
Fee[] calldata fees
) external nonReentrant {
// Approve the exchange if needed
_approveERC721IfNeeded(order.erc721Token, address(EXCHANGE));
// Execute fill
try EXCHANGE.sellERC721(order, signature, tokenId, false, "") {
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, order.erc20Token);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, order.erc20Token);
} catch {
// Revert if specified
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any ERC721 leftover
_sendAllERC721(params.refundTo, order.erc721Token, tokenId);
}
// --- [ERC1155] Single offer ---
function acceptERC1155Offer(
IZeroExV4.ERC1155Order calldata order,
IZeroExV4.Signature calldata signature,
uint128 amount,
OfferParams calldata params,
uint256 tokenId,
Fee[] calldata fees
) external nonReentrant {
// Approve the exchange if needed
_approveERC1155IfNeeded(order.erc1155Token, address(EXCHANGE));
// Execute fill
try EXCHANGE.sellERC1155(order, signature, tokenId, amount, false, "") {
// Pay fees
uint256 feesLength = fees.length;
for (uint256 i; i < feesLength; ) {
Fee memory fee = fees[i];
_sendERC20(fee.recipient, fee.amount, order.erc20Token);
unchecked {
++i;
}
}
// Forward any left payment to the specified receiver
_sendAllERC20(params.fillTo, order.erc20Token);
} catch {
// Revert if specified
if (params.revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
// Refund any ERC1155 leftover
_sendAllERC1155(params.refundTo, order.erc1155Token, tokenId);
}
// --- ERC721 / ERC1155 hooks ---
// Single token offer acceptance can be done approval-less by using the
// standard `safeTransferFrom` method together with specifying data for
// further contract calls. An example:
// `safeTransferFrom(
// 0xWALLET,
// 0xMODULE,
// TOKEN_ID,
// 0xABI_ENCODED_ROUTER_EXECUTION_CALLDATA_FOR_OFFER_ACCEPTANCE
// )`
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // tokenId
uint256, // amount
bytes calldata data
) external returns (bytes4) {
if (data.length > 0) {
_makeCall(router, data, 0);
}
return this.onERC1155Received.selector;
}
// --- Internal ---
function _buyERC721(
IZeroExV4.ERC721Order calldata order,
IZeroExV4.Signature calldata signature,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
// Execute fill
try EXCHANGE.buyERC721{value: value}(order, signature, "") {
order.erc721Token.safeTransferFrom(address(this), receiver, order.erc721TokenId);
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
function _buyERC1155(
IZeroExV4.ERC1155Order calldata order,
IZeroExV4.Signature calldata signature,
uint128 amount,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
try EXCHANGE.buyERC1155{value: value}(order, signature, amount, "") {
order.erc1155Token.safeTransferFrom(
address(this),
receiver,
order.erc1155TokenId,
amount,
""
);
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
function _buyERC721s(
IZeroExV4.ERC721Order[] calldata orders,
IZeroExV4.Signature[] calldata signatures,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
uint256 length = orders.length;
// Execute fill
try
EXCHANGE.batchBuyERC721s{value: value}(
orders,
signatures,
new bytes[](length),
revertIfIncomplete
)
returns (bool[] memory fulfilled) {
for (uint256 i = 0; i < length; ) {
if (fulfilled[i]) {
orders[i].erc721Token.safeTransferFrom(address(this), receiver, orders[i].erc721TokenId);
}
unchecked {
++i;
}
}
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
function _buyERC1155s(
IZeroExV4.ERC1155Order[] calldata orders,
IZeroExV4.Signature[] calldata signatures,
uint128[] memory amounts,
address receiver,
bool revertIfIncomplete,
uint256 value
) internal {
uint256 length = orders.length;
uint128[] memory fillAmounts = new uint128[](length);
for (uint256 i = 0; i < length; ) {
fillAmounts[i] = amounts[i];
unchecked {
++i;
}
}
// Execute fill
try
EXCHANGE.batchBuyERC1155s{value: value}(
orders,
signatures,
fillAmounts,
new bytes[](length),
revertIfIncomplete
)
returns (bool[] memory fulfilled) {
for (uint256 i = 0; i < length; ) {
if (fulfilled[i]) {
orders[i].erc1155Token.safeTransferFrom(
address(this),
receiver,
orders[i].erc1155TokenId,
fillAmounts[i],
""
);
}
unchecked {
++i;
}
}
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {BaseExchangeModule} from "./BaseExchangeModule.sol";
import {BaseModule} from "../BaseModule.sol";
import {IZora} from "../../../interfaces/IZora.sol";
// Notes:
// - supports filling "asks"
contract ZoraModule is BaseExchangeModule {
// --- Structs ---
struct Ask {
IERC721 collection;
uint256 tokenId;
address currency;
uint256 amount;
address finder;
}
// --- Fields ---
IZora public immutable EXCHANGE;
// --- Constructor ---
constructor(
address owner,
address router,
address exchange
) BaseModule(owner) BaseExchangeModule(router) {
EXCHANGE = IZora(exchange);
}
// --- Fallback ---
receive() external payable {}
// --- Single ETH listing ---
function acceptETHListing(
Ask calldata ask,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Execute fill
_buy(ask, params.fillTo, params.revertIfIncomplete);
}
// --- Multiple ETH listings ---
function acceptETHListings(
Ask[] calldata asks,
ETHListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundETHLeftover(params.refundTo)
chargeETHFees(fees, params.amount)
{
// Foundation does not support batch filling so we fill orders one by one
for (uint256 i = 0; i < asks.length; ) {
_buy(asks[i], params.fillTo, params.revertIfIncomplete);
unchecked {
++i;
}
}
}
// --- Single ERC20 listing ---
function acceptERC20Listing(
Ask calldata ask,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Execute fill
_buy(ask, params.fillTo, params.revertIfIncomplete);
}
// --- Multiple ERC20 listings ---
function acceptERC20Listings(
Ask[] calldata asks,
ERC20ListingParams calldata params,
Fee[] calldata fees
)
external
payable
nonReentrant
refundERC20Leftover(params.refundTo, params.token)
chargeERC20Fees(fees, params.token, params.amount)
{
// Foundation does not support batch filling so we fill orders one by one
for (uint256 i = 0; i < asks.length; ) {
_buy(asks[i], params.fillTo, params.revertIfIncomplete);
unchecked {
++i;
}
}
}
// --- ERC721 hooks ---
function onERC721Received(
address, // operator,
address, // from
uint256, // tokenId,
bytes calldata // data
) external pure returns (bytes4) {
return this.onERC721Received.selector;
}
// --- Internal ---
function _buy(Ask calldata ask, address receiver, bool revertIfIncomplete) internal {
// Execute fill
try
EXCHANGE.fillAsk{value: ask.currency == address(0) ? ask.amount : 0}(
address(ask.collection),
ask.tokenId,
ask.currency,
ask.amount,
ask.finder
)
{
ask.collection.safeTransferFrom(address(this), receiver, ask.tokenId);
} catch {
// Revert if specified
if (revertIfIncomplete) {
revert UnsuccessfulFill();
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {MintProxy} from "./MintProxy.sol";
contract MintModule {
// --- Errors ---
error UnsuccessfulCall();
// --- Fields ---
address public implementation;
mapping(address => address) public proxies;
// --- Constructor ---
constructor() {
implementation = address(new MintProxy());
}
// --- Methods ---
function mint(address minter, bytes calldata data) external payable {
address proxy = proxies[minter];
if (proxy == address(0)) {
proxy = Clones.cloneDeterministic(implementation, bytes32(uint256(uint160(minter))));
MintProxy(proxy).initialize(minter);
proxies[minter] = proxy;
}
(bool result, ) = proxy.call{value: msg.value}(data);
if (!result) {
revert UnsuccessfulCall();
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
contract MintProxy is ReentrancyGuard {
// --- Structs ---
struct Fee {
address recipient;
uint256 amount;
}
struct MintDetail {
address to;
bytes data;
uint256 value;
Fee[] fees;
address token;
uint256 quantity;
string comment;
}
struct MintParams {
address refundTo;
bool revertIfIncomplete;
}
// --- Events ---
event MintComment(address indexed token, uint256 quantity, string comment);
// --- Errors ---
error AlreadyInitialized();
error Unauthorized();
error UnsuccessfulCall();
error UnsuccessfulMint();
error UnsuccessfulPayment();
// --- Fields ---
address public owner;
// --- Initializer ---
function initialize(address _owner) external {
if (owner != address(0)) {
revert AlreadyInitialized();
}
owner = _owner;
}
// --- Methods ---
function mintMultiple(
MintDetail[] calldata mintDetails,
MintParams calldata params
) external payable nonReentrant {
uint256 length = mintDetails.length;
for (uint256 i; i < length; ) {
MintDetail calldata mintDetail = mintDetails[i];
(bool result, ) = mintDetail.to.call{value: mintDetail.value}(mintDetail.data);
if (!result && params.revertIfIncomplete) {
revert UnsuccessfulMint();
} else if (result) {
Fee[] calldata fees = mintDetail.fees;
uint256 feesLength = fees.length;
for (uint256 j; j < feesLength; ) {
_sendETH(fees[j].recipient, fees[j].amount);
unchecked {
++j;
}
}
if (bytes(mintDetail.comment).length > 0) {
emit MintComment(mintDetail.token, mintDetail.quantity, mintDetail.comment);
}
}
unchecked {
++i;
}
}
uint256 leftover = address(this).balance;
if (leftover > 0) {
_sendETH(params.refundTo, leftover);
}
}
function makeCalls(
address[] calldata targets,
bytes[] calldata data,
uint256[] calldata values
) external payable nonReentrant {
if (msg.sender != owner) {
revert Unauthorized();
}
uint256 length = targets.length;
for (uint256 i = 0; i < length; ) {
(bool success, ) = payable(targets[i]).call{value: values[i]}(data[i]);
if (!success) {
revert UnsuccessfulCall();
}
unchecked {
++i;
}
}
}
// --- ERC721 / ERC1155 hooks ---
function onERC721Received(
address, // operator
address, // from
uint256 tokenId,
bytes calldata // data
) external returns (bytes4) {
IERC721(msg.sender).safeTransferFrom(address(this), owner, tokenId);
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256 tokenId,
uint256 amount,
bytes calldata // data
) external returns (bytes4) {
IERC1155(msg.sender).safeTransferFrom(address(this), owner, tokenId, amount, "");
return this.onERC1155Received.selector;
}
// --- Internal ---
function _sendETH(address to, uint256 amount) internal {
if (amount > 0) {
(bool success, ) = payable(to).call{value: amount}("");
if (!success) {
revert UnsuccessfulPayment();
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
contract ReservoirV6_0_1 is ReentrancyGuard {
using Address for address;
// --- Structs ---
struct ExecutionInfo {
address module;
bytes data;
uint256 value;
}
struct AmountCheckInfo {
address target;
bytes data;
uint256 threshold;
}
// --- Errors ---
error UnsuccessfulExecution();
error UnsuccessfulPayment();
// --- Modifiers ---
modifier refundETH() {
_;
uint256 leftover = address(this).balance;
if (leftover > 0) {
(bool success, ) = payable(msg.sender).call{value: leftover}("");
if (!success) {
revert UnsuccessfulPayment();
}
}
}
// --- Fallback ---
receive() external payable {}
// --- Public ---
// Trigger a set of executions atomically
function execute(
ExecutionInfo[] calldata executionInfos
) external payable nonReentrant refundETH {
uint256 length = executionInfos.length;
for (uint256 i = 0; i < length; ) {
_executeInternal(executionInfos[i]);
unchecked {
++i;
}
}
}
// Trigger a set of executions with amount checking. As opposed to the regular
// `execute` method, `executeWithAmountCheck` supports stopping the executions
// once the provided amount check reaches a certain value. This is useful when
// trying to fill orders with slippage (eg. provide multiple orders and try to
// fill until a certain balance is reached). In order to be flexible, checking
// the amount is done generically by calling the `target` contract with `data`.
// For example, this could be used to check the ERC721 total owned balance (by
// using `balanceOf(owner)`), the ERC1155 total owned balance per token id (by
// using `balanceOf(owner, tokenId)`), but also for checking the ERC1155 total
// owned balance per multiple token ids (by using a custom contract that wraps
// `balanceOfBatch(owners, tokenIds)`).
function executeWithAmountCheck(
ExecutionInfo[] calldata executionInfos,
AmountCheckInfo calldata amountCheckInfo
) external payable nonReentrant refundETH {
// Cache some data for efficiency
address target = amountCheckInfo.target;
bytes calldata data = amountCheckInfo.data;
uint256 threshold = amountCheckInfo.threshold;
uint256 length = executionInfos.length;
for (uint256 i = 0; i < length; ) {
// Check the amount and break if it exceeds the threshold
uint256 amount = _getAmount(target, data);
if (amount >= threshold) {
break;
}
_executeInternal(executionInfos[i]);
unchecked {
++i;
}
}
}
// --- Internal ---
function _executeInternal(ExecutionInfo calldata executionInfo) internal {
address module = executionInfo.module;
// Ensure the target is a contract
if (!module.isContract()) {
revert UnsuccessfulExecution();
}
(bool success, ) = module.call{value: executionInfo.value}(executionInfo.data);
if (!success) {
revert UnsuccessfulExecution();
}
}
function _getAmount(address target, bytes calldata data) internal view returns (uint256 amount) {
// Ensure the target is a contract
if (!target.isContract()) {
revert UnsuccessfulExecution();
}
(bool success, bytes memory result) = target.staticcall(data);
if (!success) {
revert UnsuccessfulExecution();
}
amount = abi.decode(result, (uint256));
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import { ZoneParameters, Schema } from "../lib/ConsiderationStructs.sol";
interface ZoneInterface {
function validateOrder(
ZoneParameters calldata zoneParameters
) external returns (bytes4 validOrderMagicValue);
function getSeaportMetadata()
external
view
returns (
string memory name,
Schema[] memory schemas // map to Seaport Improvement Proposal IDs
);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {ZoneParameters, Schema} from "../lib/ConsiderationStructs.sol";
interface ZoneInterfaceV16 {
function authorizeOrder(
ZoneParameters calldata zoneParameters
) external returns (bytes4 authorizeOrderMagicValue);
function validateOrder(
ZoneParameters calldata zoneParameters
) external returns (bytes4 validOrderMagicValue);
function getSeaportMetadata()
external
view
returns (
string memory name,
Schema[] memory schemas // map to Seaport Improvement Proposal IDs
);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// prettier-ignore
enum OrderType {
// 0: no partial fills, anyone can execute
FULL_OPEN,
// 1: partial fills supported, anyone can execute
PARTIAL_OPEN,
// 2: no partial fills, only offerer or zone can execute
FULL_RESTRICTED,
// 3: partial fills supported, only offerer or zone can execute
PARTIAL_RESTRICTED,
// 4: contract order type
CONTRACT
}
// prettier-ignore
enum BasicOrderType {
// 0: no partial fills, anyone can execute
ETH_TO_ERC721_FULL_OPEN,
// 1: partial fills supported, anyone can execute
ETH_TO_ERC721_PARTIAL_OPEN,
// 2: no partial fills, only offerer or zone can execute
ETH_TO_ERC721_FULL_RESTRICTED,
// 3: partial fills supported, only offerer or zone can execute
ETH_TO_ERC721_PARTIAL_RESTRICTED,
// 4: no partial fills, anyone can execute
ETH_TO_ERC1155_FULL_OPEN,
// 5: partial fills supported, anyone can execute
ETH_TO_ERC1155_PARTIAL_OPEN,
// 6: no partial fills, only offerer or zone can execute
ETH_TO_ERC1155_FULL_RESTRICTED,
// 7: partial fills supported, only offerer or zone can execute
ETH_TO_ERC1155_PARTIAL_RESTRICTED,
// 8: no partial fills, anyone can execute
ERC20_TO_ERC721_FULL_OPEN,
// 9: partial fills supported, anyone can execute
ERC20_TO_ERC721_PARTIAL_OPEN,
// 10: no partial fills, only offerer or zone can execute
ERC20_TO_ERC721_FULL_RESTRICTED,
// 11: partial fills supported, only offerer or zone can execute
ERC20_TO_ERC721_PARTIAL_RESTRICTED,
// 12: no partial fills, anyone can execute
ERC20_TO_ERC1155_FULL_OPEN,
// 13: partial fills supported, anyone can execute
ERC20_TO_ERC1155_PARTIAL_OPEN,
// 14: no partial fills, only offerer or zone can execute
ERC20_TO_ERC1155_FULL_RESTRICTED,
// 15: partial fills supported, only offerer or zone can execute
ERC20_TO_ERC1155_PARTIAL_RESTRICTED,
// 16: no partial fills, anyone can execute
ERC721_TO_ERC20_FULL_OPEN,
// 17: partial fills supported, anyone can execute
ERC721_TO_ERC20_PARTIAL_OPEN,
// 18: no partial fills, only offerer or zone can execute
ERC721_TO_ERC20_FULL_RESTRICTED,
// 19: partial fills supported, only offerer or zone can execute
ERC721_TO_ERC20_PARTIAL_RESTRICTED,
// 20: no partial fills, anyone can execute
ERC1155_TO_ERC20_FULL_OPEN,
// 21: partial fills supported, anyone can execute
ERC1155_TO_ERC20_PARTIAL_OPEN,
// 22: no partial fills, only offerer or zone can execute
ERC1155_TO_ERC20_FULL_RESTRICTED,
// 23: partial fills supported, only offerer or zone can execute
ERC1155_TO_ERC20_PARTIAL_RESTRICTED
}
// prettier-ignore
enum BasicOrderRouteType {
// 0: provide Ether (or other native token) to receive offered ERC721 item.
ETH_TO_ERC721,
// 1: provide Ether (or other native token) to receive offered ERC1155 item.
ETH_TO_ERC1155,
// 2: provide ERC20 item to receive offered ERC721 item.
ERC20_TO_ERC721,
// 3: provide ERC20 item to receive offered ERC1155 item.
ERC20_TO_ERC1155,
// 4: provide ERC721 item to receive offered ERC20 item.
ERC721_TO_ERC20,
// 5: provide ERC1155 item to receive offered ERC20 item.
ERC1155_TO_ERC20
}
// prettier-ignore
enum ItemType {
// 0: ETH on mainnet, MATIC on polygon, etc.
NATIVE,
// 1: ERC20 items (ERC777 and ERC20 analogues could also technically work)
ERC20,
// 2: ERC721 items
ERC721,
// 3: ERC1155 items
ERC1155,
// 4: ERC721 items where a number of tokenIds are supported
ERC721_WITH_CRITERIA,
// 5: ERC1155 items where a number of ids are supported
ERC1155_WITH_CRITERIA
}
// prettier-ignore
enum Side {
// 0: Items that can be spent
OFFER,
// 1: Items that must be received
CONSIDERATION
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import { OrderType, BasicOrderType, ItemType, Side } from "./ConsiderationEnums.sol";
/**
* @dev An order contains eleven components: an offerer, a zone (or account that
* can cancel the order or restrict who can fulfill the order depending on
* the type), the order type (specifying partial fill support as well as
* restricted order status), the start and end time, a hash that will be
* provided to the zone when validating restricted orders, a salt, a key
* corresponding to a given conduit, a counter, and an arbitrary number of
* offer items that can be spent along with consideration items that must
* be received by their respective recipient.
*/
struct OrderComponents {
address offerer;
address zone;
OfferItem[] offer;
ConsiderationItem[] consideration;
OrderType orderType;
uint256 startTime;
uint256 endTime;
bytes32 zoneHash;
uint256 salt;
bytes32 conduitKey;
uint256 counter;
}
/**
* @dev An offer item has five components: an item type (ETH or other native
* tokens, ERC20, ERC721, and ERC1155, as well as criteria-based ERC721 and
* ERC1155), a token address, a dual-purpose "identifierOrCriteria"
* component that will either represent a tokenId or a merkle root
* depending on the item type, and a start and end amount that support
* increasing or decreasing amounts over the duration of the respective
* order.
*/
struct OfferItem {
ItemType itemType;
address token;
uint256 identifierOrCriteria;
uint256 startAmount;
uint256 endAmount;
}
/**
* @dev A consideration item has the same five components as an offer item and
* an additional sixth component designating the required recipient of the
* item.
*/
struct ConsiderationItem {
ItemType itemType;
address token;
uint256 identifierOrCriteria;
uint256 startAmount;
uint256 endAmount;
address payable recipient;
}
/**
* @dev A spent item is translated from a utilized offer item and has four
* components: an item type (ETH or other native tokens, ERC20, ERC721, and
* ERC1155), a token address, a tokenId, and an amount.
*/
struct SpentItem {
ItemType itemType;
address token;
uint256 identifier;
uint256 amount;
}
/**
* @dev A received item is translated from a utilized consideration item and has
* the same four components as a spent item, as well as an additional fifth
* component designating the required recipient of the item.
*/
struct ReceivedItem {
ItemType itemType;
address token;
uint256 identifier;
uint256 amount;
address payable recipient;
}
/**
* @dev For basic orders involving ETH / native / ERC20 <=> ERC721 / ERC1155
* matching, a group of six functions may be called that only requires a
* subset of the usual order arguments. Note the use of a "basicOrderType"
* enum; this represents both the usual order type as well as the "route"
* of the basic order (a simple derivation function for the basic order
* type is `basicOrderType = orderType + (4 * basicOrderRoute)`.)
*/
struct BasicOrderParameters {
// calldata offset
address considerationToken; // 0x24
uint256 considerationIdentifier; // 0x44
uint256 considerationAmount; // 0x64
address payable offerer; // 0x84
address zone; // 0xa4
address offerToken; // 0xc4
uint256 offerIdentifier; // 0xe4
uint256 offerAmount; // 0x104
BasicOrderType basicOrderType; // 0x124
uint256 startTime; // 0x144
uint256 endTime; // 0x164
bytes32 zoneHash; // 0x184
uint256 salt; // 0x1a4
bytes32 offererConduitKey; // 0x1c4
bytes32 fulfillerConduitKey; // 0x1e4
uint256 totalOriginalAdditionalRecipients; // 0x204
AdditionalRecipient[] additionalRecipients; // 0x224
bytes signature; // 0x244
// Total length, excluding dynamic array data: 0x264 (580)
}
/**
* @dev Basic orders can supply any number of additional recipients, with the
* implied assumption that they are supplied from the offered ETH (or other
* native token) or ERC20 token for the order.
*/
struct AdditionalRecipient {
uint256 amount;
address payable recipient;
}
/**
* @dev The full set of order components, with the exception of the counter,
* must be supplied when fulfilling more sophisticated orders or groups of
* orders. The total number of original consideration items must also be
* supplied, as the caller may specify additional consideration items.
*/
struct OrderParameters {
address offerer; // 0x00
address zone; // 0x20
OfferItem[] offer; // 0x40
ConsiderationItem[] consideration; // 0x60
OrderType orderType; // 0x80
uint256 startTime; // 0xa0
uint256 endTime; // 0xc0
bytes32 zoneHash; // 0xe0
uint256 salt; // 0x100
bytes32 conduitKey; // 0x120
uint256 totalOriginalConsiderationItems; // 0x140
// offer.length // 0x160
}
/**
* @dev Orders require a signature in addition to the other order parameters.
*/
struct Order {
OrderParameters parameters;
bytes signature;
}
/**
* @dev Advanced orders include a numerator (i.e. a fraction to attempt to fill)
* and a denominator (the total size of the order) in addition to the
* signature and other order parameters. It also supports an optional field
* for supplying extra data; this data will be provided to the zone if the
* order type is restricted and the zone is not the caller, or will be
* provided to the offerer as context for contract order types.
*/
struct AdvancedOrder {
OrderParameters parameters;
uint120 numerator;
uint120 denominator;
bytes signature;
bytes extraData;
}
/**
* @dev Orders can be validated (either explicitly via `validate`, or as a
* consequence of a full or partial fill), specifically cancelled (they can
* also be cancelled in bulk via incrementing a per-zone counter), and
* partially or fully filled (with the fraction filled represented by a
* numerator and denominator).
*/
struct OrderStatus {
bool isValidated;
bool isCancelled;
uint120 numerator;
uint120 denominator;
}
/**
* @dev A criteria resolver specifies an order, side (offer vs. consideration),
* and item index. It then provides a chosen identifier (i.e. tokenId)
* alongside a merkle proof demonstrating the identifier meets the required
* criteria.
*/
struct CriteriaResolver {
uint256 orderIndex;
Side side;
uint256 index;
uint256 identifier;
bytes32[] criteriaProof;
}
/**
* @dev A fulfillment is applied to a group of orders. It decrements a series of
* offer and consideration items, then generates a single execution
* element. A given fulfillment can be applied to as many offer and
* consideration items as desired, but must contain at least one offer and
* at least one consideration that match. The fulfillment must also remain
* consistent on all key parameters across all offer items (same offerer,
* token, type, tokenId, and conduit preference) as well as across all
* consideration items (token, type, tokenId, and recipient).
*/
struct Fulfillment {
FulfillmentComponent[] offerComponents;
FulfillmentComponent[] considerationComponents;
}
/**
* @dev Each fulfillment component contains one index referencing a specific
* order and another referencing a specific offer or consideration item.
*/
struct FulfillmentComponent {
uint256 orderIndex;
uint256 itemIndex;
}
/**
* @dev An execution is triggered once all consideration items have been zeroed
* out. It sends the item in question from the offerer to the item's
* recipient, optionally sourcing approvals from either this contract
* directly or from the offerer's chosen conduit if one is specified. An
* execution is not provided as an argument, but rather is derived via
* orders, criteria resolvers, and fulfillments (where the total number of
* executions will be less than or equal to the total number of indicated
* fulfillments) and returned as part of `matchOrders`.
*/
struct Execution {
ReceivedItem item;
address offerer;
bytes32 conduitKey;
}
/**
* @dev Restricted orders are validated post-execution by calling validateOrder
* on the zone. This struct provides context about the order fulfillment
* and any supplied extraData, as well as all order hashes fulfilled in a
* call to a match or fulfillAvailable method.
*/
struct ZoneParameters {
bytes32 orderHash;
address fulfiller;
address offerer;
SpentItem[] offer;
ReceivedItem[] consideration;
bytes extraData;
bytes32[] orderHashes;
uint256 startTime;
uint256 endTime;
bytes32 zoneHash;
}
/**
* @dev Zones and contract offerers can communicate which schemas they implement
* along with any associated metadata related to each schema.
*/
struct Schema {
uint256 id;
bytes metadata;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
/**
* @notice SignedZoneControllerEventsAndErrors contains errors and events
* related to deploying and managing new signed zones.
*/
interface SignedZoneControllerEventsAndErrors {
/**
* @dev Emit an event whenever a new zone is created.
*
* @param zoneAddress The address of the zone.
* @param zoneName The name for the zone returned in
* getSeaportMetadata().
* @param apiEndpoint The API endpoint where orders for this zone can be
* signed.
* @param documentationURI The URI to the documentation describing the
* behavior of the contract.
* Request and response payloads are defined in SIP-7.
* @param salt The salt used to deploy the zone.
*/
event ZoneCreated(
address zoneAddress,
string zoneName,
string apiEndpoint,
string documentationURI,
bytes32 salt
);
/**
* @dev Emit an event whenever zone ownership is transferred.
*
* @param zone The zone for which ownership has been
* transferred.
* @param previousOwner The previous owner of the zone.
* @param newOwner The new owner of the zone.
*/
event OwnershipTransferred(
address indexed zone,
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev Emit an event whenever a zone owner registers a new potential
* owner for that zone.
*
* @param newPotentialOwner The new potential owner of the zone.
*/
event PotentialOwnerUpdated(address indexed newPotentialOwner);
/**
* @dev Emit an event when a signer has been updated.
*/
event SignerUpdated(address signedZone, address signer, bool active);
/**
* @dev Revert with an error when attempting to update channels or transfer
* ownership of a zone when the caller is not the owner of the
* zone in question.
*/
error CallerIsNotOwner(address zone);
/**
* @dev Revert with an error when the caller is not the owner or an active
* signer of the signed zone in question.
*/
error CallerIsNotOwnerOrSigner(address zone);
/**
* @dev Revert with an error when attempting to claim ownership of a zone
* with a caller that is not the current potential owner for the
* zone in question.
*/
error CallerIsNotNewPotentialOwner(address zone);
/**
* @dev Revert with an error when attempting to create a new signed zone
* using a salt where the first twenty bytes do not match the address
* of the caller or are not set to zero.
*/
error InvalidCreator();
/**
* @dev Revert with an error when attempting to create a new zone when no
* initial owner address is supplied.
*/
error InvalidInitialOwner();
/**
* @dev Revert with an error when attempting to set a new potential owner
* that is already set.
*/
error NewPotentialOwnerAlreadySet(address zone, address newPotentialOwner);
/**
* @dev Revert with an error when attempting to cancel ownership transfer
* when no new potential owner is currently set.
*/
error NoPotentialOwnerCurrentlySet(address zone);
/**
* @dev Revert with an error when attempting to register a new potential
* owner and supplying the null address.
*/
error NewPotentialOwnerIsZeroAddress(address zone);
/**
* @dev Revert with an error when attempting to interact with a zone that
* does not yet exist.
*/
error NoZone();
/**
* @dev Revert with an error if trying to add a signer that is
* already active.
*/
error SignerAlreadyAdded(address signer);
/**
* @dev Revert with an error if a new signer is the zero address.
*/
error SignerCannotBeZeroAddress();
/**
* @dev Revert with an error if a removed signer is trying to be
* reauthorized.
*/
error SignerCannotBeReauthorized(address signer);
/**
* @dev Revert with an error if trying to remove a signer that is
* not present.
*/
error SignerNotPresent(address signer);
/**
* @dev Revert with an error when attempting to deploy a zone that is
* currently deployed.
*/
error ZoneAlreadyExists(address zone);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
/**
* @title SignedZoneControllerInterface
* @author BCLeFevre
* @notice SignedZoneControllerInterface enables the deploying of SignedZones.
* SignedZones are an implementation of SIP-7 that requires orders
* to be signed by an approved signer.
* https://github.com/ProjectOpenSea/SIPs/blob/main/SIPS/sip-7.md
*
*/
interface SignedZoneControllerInterface {
/**
* @notice Deploy a SignedZone to a precomputed address.
*
* @param zoneName The name for the zone returned in
* getSeaportMetadata().
* @param apiEndpoint The API endpoint where orders for this zone can be
* signed.
* @param documentationURI The URI to the documentation describing the
* behavior of the contract.
* Request and response payloads are defined in SIP-7.
* @param salt The salt to be used to derive the zone address
* @param initialOwner The initial owner to set for the new zone.
*
* @return derivedAddress The derived address for the zone.
*/
function createZone(
string memory zoneName,
string memory apiEndpoint,
string memory documentationURI,
address initialOwner,
bytes32 salt
) external returns (address derivedAddress);
/**
* @notice Returns the active signers for the zone.
*
* @param signedZone The signed zone to get the active signers for.
*
* @return signers The active signers.
*/
function getActiveSigners(address signedZone)
external
view
returns (address[] memory signers);
/**
* @notice Returns additional information about the zone.
*
* @param zone The zone to get the additional information for.
*
* @return domainSeparator The domain separator used for signing.
* @return zoneName The name of the zone.
* @return apiEndpoint The API endpoint for the zone.
* @return substandards The substandards supported by the zone.
* @return documentationURI The documentation URI for the zone.
*/
function getAdditionalZoneInformation(address zone)
external
view
returns (
bytes32 domainSeparator,
string memory zoneName,
string memory apiEndpoint,
uint256[] memory substandards,
string memory documentationURI
);
/**
* @notice Update the API endpoint returned by the supplied zone.
* Only the owner or an active signer can call this function.
*
* @param signedZone The signed zone to update the API endpoint for.
* @param newApiEndpoint The new API endpoint.
*/
function updateAPIEndpoint(
address signedZone,
string calldata newApiEndpoint
) external;
/**
* @notice Update the signer for a given signed zone.
*
* @param signedZone The signed zone to update the signer for.
* @param signer The signer to update.
* @param active If the signer should be active or not.
*/
function updateSigner(
address signedZone,
address signer,
bool active
) external;
/**
* @notice Initiate zone ownership transfer by assigning a new potential
* owner for the given zone. Once set, the new potential owner
* may call `acceptOwnership` to claim ownership of the zone.
* Only the owner of the zone in question may call this function.
*
* @param zone The zone for which to initiate ownership transfer.
* @param newPotentialOwner The new potential owner of the zone.
*/
function transferOwnership(address zone, address newPotentialOwner)
external;
/**
* @notice Clear the currently set potential owner, if any, from a zone.
* Only the owner of the zone in question may call this function.
*
* @param zone The zone for which to cancel ownership transfer.
*/
function cancelOwnershipTransfer(address zone) external;
/**
* @notice Accept ownership of a supplied zone. Only accounts that the
* current owner has set as the new potential owner may call this
* function.
*
* @param zone The zone for which to accept ownership.
*/
function acceptOwnership(address zone) external;
/**
* @notice Retrieve the current owner of a deployed zone.
*
* @param zone The zone for which to retrieve the associated owner.
*
* @return owner The owner of the supplied zone.
*/
function ownerOf(address zone) external view returns (address owner);
/**
* @notice Retrieve the potential owner, if any, for a given zone. The
* current owner may set a new potential owner via
* `transferOwnership` and that owner may then accept ownership of
* the zone in question via `acceptOwnership`.
*
* @param zone The zone for which to retrieve the potential owner.
*
* @return potentialOwner The potential owner, if any, for the zone.
*/
function getPotentialOwner(address zone)
external
view
returns (address potentialOwner);
/**
* @notice Derive the zone address associated with a salt.
*
* @param salt The salt to be used to derive the zone address
*
* @return derivedAddress The derived address of the signed zone.
*/
function getZone(bytes32 salt)
external
view
returns (address derivedAddress);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
/**
* @notice SignedZoneEventsAndErrors contains errors and events
* related to zone interaction.
*/
interface SignedZoneEventsAndErrors {
/**
* @dev Emit an event when a new signer is added.
*/
event SignerAdded(address signer);
/**
* @dev Emit an event when a signer is removed.
*/
event SignerRemoved(address signer);
/**
* @dev Revert with an error if msg.sender is not the owner
* or an active signer.
*/
error OnlyOwnerOrActiveSigner();
/**
* @dev Revert with an error when the signature has expired.
*/
error SignatureExpired(uint256 expiration, bytes32 orderHash);
/**
* @dev Revert with an error when attempting to update the signers of a
* the zone from a caller that is not the zone's controller.
*/
error InvalidController();
/**
* @dev Revert with an error if supplied order extraData is an invalid
* length.
*/
error InvalidExtraDataLength(bytes32 orderHash);
/**
* @dev Revert with an error if the supplied order extraData does not
* support the zone's SIP6 version.
*/
error InvalidSIP6Version(bytes32 orderHash);
/**
* @dev Revert with an error if the supplied order extraData does not
* support the zone's substandard requirements.
*/
error InvalidSubstandardSupport(
string reason,
uint256 substandardVersion,
bytes32 orderHash
);
/**
* @dev Revert with an error if the supplied order extraData does not
* support the zone's substandard version.
*/
error InvalidSubstandardVersion(bytes32 orderHash);
/**
* @dev Revert with an error if the fulfiller does not match.
*/
error InvalidFulfiller(
address expectedFulfiller,
address actualFulfiller,
bytes32 orderHash
);
/**
* @dev Revert with an error if the consideration does not match.
*/
error InvalidConsideration(
uint256 expectedConsiderationHash,
uint256 actualConsiderationHash,
bytes32 orderHash
);
/**
* @dev Revert with an error if the zone parameter encoding is invalid.
*/
error InvalidZoneParameterEncoding();
/**
* @dev Revert with an error when an order is signed with a signer
* that is not active.
*/
error SignerNotActive(address signer, bytes32 orderHash);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
/**
* @title SignedZone
* @author ryanio, BCLeFevre
* @notice SignedZone is an implementation of SIP-7 that requires orders
* to be signed by an approved signer.
* https://github.com/ProjectOpenSea/SIPs/blob/main/SIPS/sip-7.md
*
*/
interface SignedZoneInterface {
/**
* @notice Update the active status of a signer.
*
* @param signer The signer address to update.
* @param active The new active status of the signer.
*/
function updateSigner(address signer, bool active) external;
/**
* @notice Returns the active signers for the zone.
*
* @return signers The active signers.
*/
function getActiveSigners()
external
view
returns (address[] memory signers);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import { Schema } from "../../lib/ConsiderationStructs.sol";
/**
* @dev SIP-5: Contract Metadata Interface for Seaport Contracts
* https://github.com/ProjectOpenSea/SIPs/blob/main/SIPS/sip-5.md
*/
interface SIP5Interface {
/**
* @dev An event that is emitted when a SIP-5 compatible contract is deployed.
*/
event SeaportCompatibleContractDeployed();
/**
* @dev Returns Seaport metadata for this contract, returning the
* contract name and supported schemas.
*
* @return name The contract name
* @return schemas The supported SIPs
*/
function getSeaportMetadata()
external
view
returns (string memory name, Schema[] memory schemas);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
/// @dev ECDSA signature offsets.
uint256 constant ECDSA_MaxLength = 65;
uint256 constant ECDSA_signature_s_offset = 0x40;
uint256 constant ECDSA_signature_v_offset = 0x60;
/// @dev Helpers for memory offsets.
uint256 constant OneWord = 0x20;
uint256 constant TwoWords = 0x40;
uint256 constant ThreeWords = 0x60;
uint256 constant FourWords = 0x80;
uint256 constant FiveWords = 0xa0;
uint256 constant Signature_lower_v = 27;
uint256 constant MaxUint8 = 0xff;
bytes32 constant EIP2098_allButHighestBitMask = (
0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
);
uint256 constant Ecrecover_precompile = 1;
uint256 constant Ecrecover_args_size = 0x80;
uint256 constant FreeMemoryPointerSlot = 0x40;
uint256 constant ZeroSlot = 0x60;
uint256 constant Slot0x80 = 0x80;
/// @dev The EIP-712 digest offsets.
uint256 constant EIP712_DomainSeparator_offset = 0x02;
uint256 constant EIP712_SignedOrderHash_offset = 0x22;
uint256 constant EIP712_DigestPayload_size = 0x42;
uint256 constant EIP_712_PREFIX = (
0x1901000000000000000000000000000000000000000000000000000000000000
);
/*
* error InvalidController()
* - Defined in SignedZoneEventsAndErrors.sol
* Memory layout:
* - 0x00: Left-padded selector (data begins at 0x1c)
* Revert buffer is memory[0x1c:0x20]
*/
uint256 constant InvalidController_error_selector = 0x6d5769be;
uint256 constant InvalidController_error_length = 0x04;
/*
* error InvalidFulfiller(address expectedFulfiller, address actualFulfiller, bytes32 orderHash)
* - Defined in SignedZoneEventsAndErrors.sol
* Memory layout:
* - 0x00: Left-padded selector (data begins at 0x1c)
* - 0x20: expectedFulfiller
* - 0x40: actualFullfiller
* - 0x60: orderHash
* Revert buffer is memory[0x1c:0x80]
*/
uint256 constant InvalidFulfiller_error_selector = 0x1bcf9bb7;
uint256 constant InvalidFulfiller_error_expectedFulfiller_ptr = 0x20;
uint256 constant InvalidFulfiller_error_actualFulfiller_ptr = 0x40;
uint256 constant InvalidFulfiller_error_orderHash_ptr = 0x60;
uint256 constant InvalidFulfiller_error_length = 0x64;
/*
* error InvalidConsideration(uint256 expectedConsideration, uint256 actualConsideration, bytes32 orderHash)
* - Defined in SignedZoneEventsAndErrors.sol
* Memory layout:
* - 0x00: Left-padded selector (data begins at 0x1c)
* - 0x20: expectedConsideration
* - 0x40: actualConsideration
* - 0x60: orderHash
* Revert buffer is memory[0x1c:0x80]
*/
uint256 constant InvalidConsideration_error_selector = 0x59cb96d1;
uint256 constant InvalidConsideration_error_expectedConsideration_ptr = 0x20;
uint256 constant InvalidConsideration_error_actualConsideration_ptr = 0x40;
uint256 constant InvalidConsideration_error_orderHash_ptr = 0x60;
uint256 constant InvalidConsideration_error_length = 0x64;
/*
* error InvalidZoneParameterEncoding()
* - Defined in SignedZoneEventsAndErrors.sol
* Memory layout:
* - 0x00: Left-padded selector (data begins at 0x1c)
* Revert buffer is memory[0x1c:0x20]
*/
uint256 constant InvalidZoneParameterEncoding_error_selector = 0x46d5d895;
uint256 constant InvalidZoneParameterEncoding_error_length = 0x04;
/*
* error InvalidExtraDataLength()
* - Defined in SignedZoneEventsAndErrors.sol
* Memory layout:
* - 0x00: Left-padded selector (data begins at 0x1c)
* - 0x20: orderHash
* Revert buffer is memory[0x1c:0x40]
*/
uint256 constant InvalidExtraDataLength_error_selector = 0xd232fd2c;
uint256 constant InvalidExtraDataLength_error_orderHash_ptr = 0x20;
uint256 constant InvalidExtraDataLength_error_length = 0x24;
uint256 constant InvalidExtraDataLength_epected_length = 0x7e;
uint256 constant ExtraData_expiration_offset = 0x35;
uint256 constant ExtraData_substandard_version_byte_offset = 0x7d;
/*
* error InvalidSIP6Version()
* - Defined in SignedZoneEventsAndErrors.sol
* Memory layout:
* - 0x00: Left-padded selector (data begins at 0x1c)
* - 0x20: orderHash
* Revert buffer is memory[0x1c:0x40]
*/
uint256 constant InvalidSIP6Version_error_selector = 0x64115774;
uint256 constant InvalidSIP6Version_error_orderHash_ptr = 0x20;
uint256 constant InvalidSIP6Version_error_length = 0x24;
/*
* error InvalidSubstandardVersion()
* - Defined in SignedZoneEventsAndErrors.sol
* Memory layout:
* - 0x00: Left-padded selector (data begins at 0x1c)
* - 0x20: orderHash
* Revert buffer is memory[0x1c:0x40]
*/
uint256 constant InvalidSubstandardVersion_error_selector = 0x26787999;
uint256 constant InvalidSubstandardVersion_error_orderHash_ptr = 0x20;
uint256 constant InvalidSubstandardVersion_error_length = 0x24;
/*
* error InvalidSubstandardSupport()
* - Defined in SignedZoneEventsAndErrors.sol
* Memory layout:
* - 0x00: Left-padded selector (data begins at 0x1c)
* - 0x20: reason
* - 0x40: substandardVersion
* - 0x60: orderHash
* Revert buffer is memory[0x1c:0xe0]
*/
uint256 constant InvalidSubstandardSupport_error_selector = 0x2be76224;
uint256 constant InvalidSubstandardSupport_error_reason_offset_ptr = 0x20;
uint256 constant InvalidSubstandardSupport_error_substandard_version_ptr = 0x40;
uint256 constant InvalidSubstandardSupport_error_orderHash_ptr = 0x60;
uint256 constant InvalidSubstandardSupport_error_reason_length_ptr = 0x80;
uint256 constant InvalidSubstandardSupport_error_reason_ptr = 0xa0;
uint256 constant InvalidSubstandardSupport_error_reason_2_ptr = 0xc0;
uint256 constant InvalidSubstandardSupport_error_length = 0xc4;
/*
* error SignatureExpired()
* - Defined in SignedZoneEventsAndErrors.sol
* Memory layout:
* - 0x00: Left-padded selector (data begins at 0x1c)
* - 0x20: expiration
* - 0x40: orderHash
* Revert buffer is memory[0x1c:0x60]
*/
uint256 constant SignatureExpired_error_selector = 0x16546071;
uint256 constant SignatureExpired_error_expiration_ptr = 0x20;
uint256 constant SignatureExpired_error_orderHash_ptr = 0x40;
uint256 constant SignatureExpired_error_length = 0x44;
// Zone parameter calldata pointers
uint256 constant Zone_parameters_cdPtr = 0x04;
uint256 constant Zone_parameters_fulfiller_cdPtr = 0x44;
uint256 constant Zone_consideration_head_cdPtr = 0xa4;
uint256 constant Zone_extraData_cdPtr = 0xc4;
// Zone parameter memory pointers
uint256 constant Zone_parameters_ptr = 0x20;
// Zone parameter offsets
uint256 constant Zone_parameters_offset = 0x24;
uint256 constant expectedFulfiller_offset = 0x45;
uint256 constant actualConsideration_offset = 0x84;
uint256 constant expectedConsideration_offset = 0xa2;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {
ZoneParameters,
Schema,
ReceivedItem
} from "../lib/ConsiderationStructs.sol";
import { ZoneInterface } from "../interfaces/ZoneInterface.sol";
import {
SignedZoneEventsAndErrors
} from "./interfaces/SignedZoneEventsAndErrors.sol";
import { SIP5Interface } from "./interfaces/SIP5Interface.sol";
import {
SignedZoneControllerInterface
} from "./interfaces/SignedZoneControllerInterface.sol";
import "./lib/SignedZoneConstants.sol";
/**
* @title SignedZone
* @author ryanio, BCLeFevre
* @custom:modifiedby Tony Snark
* @notice SignedZone is an implementation of SIP-7 that requires orders
* to be signed by an approved signer.
* https://github.com/ProjectOpenSea/SIPs/blob/main/SIPS/sip-7.md
*
* Modification:
* Removes support for SIP7 sub-standard 1.
* Adds support for SIP7 sub-standard 3.
*/
contract SignedZone is SignedZoneEventsAndErrors, ZoneInterface, SIP5Interface {
/// @dev The zone's controller that is set during deployment.
address private immutable _controller;
/// @dev The authorized signers, and if they are active
mapping(address => bool) private _signers;
/// @dev The EIP-712 digest parameters.
bytes32 internal immutable _NAME_HASH = keccak256(bytes("SignedZone"));
bytes32 internal immutable _VERSION_HASH = keccak256(bytes("1.0.0"));
// prettier-ignore
bytes32 internal immutable _EIP_712_DOMAIN_TYPEHASH = keccak256(
abi.encodePacked(
"EIP712Domain(",
"string name,",
"string version,",
"uint256 chainId,",
"address verifyingContract",
")"
)
);
// prettier-ignore
bytes32 internal immutable _SIGNED_ORDER_TYPEHASH = keccak256(
abi.encodePacked(
"SignedOrder(",
"address fulfiller,",
"uint64 expiration,",
"bytes32 orderHash,",
"bytes context",
")"
)
);
bytes public constant CONSIDERATION_BYTES =
// prettier-ignore
abi.encodePacked(
"Consideration(",
"ReceivedItem[] consideration",
")"
);
bytes public constant RECEIVED_ITEM_BYTES =
// prettier-ignore
abi.encodePacked(
"ReceivedItem(",
"uint8 itemType,",
"address token,",
"uint256 identifier,",
"uint256 amount,",
"address recipient",
")"
);
bytes32 public constant RECEIVED_ITEM_HASHTYPE =
keccak256(RECEIVED_ITEM_BYTES);
bytes32 public constant CONSIDERATION_HASHTYPE =
keccak256(abi.encodePacked(CONSIDERATION_BYTES, RECEIVED_ITEM_BYTES));
uint256 internal immutable _CHAIN_ID = block.chainid;
bytes32 internal immutable _DOMAIN_SEPARATOR;
/**
* @notice Constructor to deploy the contract.
*/
constructor() {
// Set the deployer as the controller.
_controller = msg.sender;
// Derive and set the domain separator.
_DOMAIN_SEPARATOR = _deriveDomainSeparator();
// Emit an event to signal a SIP-5 contract has been deployed.
emit SeaportCompatibleContractDeployed();
}
/**
* @notice Check if a given order including extraData is currently valid.
*
* @dev This function is called by Seaport whenever any extraData is
* provided by the caller.
*
* @return validOrderMagicValue A magic value indicating if the order is
* currently valid.
*/
function validateOrder(ZoneParameters calldata zoneParameters)
public
view
virtual
override
returns (bytes4 validOrderMagicValue)
{
// Check Zone parameters validity.
_assertValidZoneParameters();
// Put the extraData and orderHash on the stack for cheaper access.
bytes calldata extraData = zoneParameters.extraData;
bytes32 orderHash = zoneParameters.orderHash;
uint256 considerationLength;
// Declare a variable to hold the expiration.
uint64 expiration;
// Validate the extraData.
assembly {
// Get the length of the extraData.
let extraDataPtr := add(0x24, calldataload(Zone_extraData_cdPtr))
let extraDataLength := calldataload(extraDataPtr)
if iszero(
eq(extraDataLength, InvalidExtraDataLength_epected_length)
) {
// Store left-padded selector with push4, mem[28:32] = selector
mstore(0, InvalidExtraDataLength_error_selector)
mstore(InvalidExtraDataLength_error_orderHash_ptr, orderHash)
// revert(abi.encodeWithSignature(
// "InvalidExtraDataLength(bytes32)", orderHash)
// )
revert(0x1c, InvalidExtraDataLength_error_length)
}
// extraData bytes 0-1: SIP-6 version byte (MUST be 0x00)
let versionByte := shr(248, calldataload(add(extraDataPtr, 0x20)))
if iszero(eq(versionByte, 0x00)) {
// Store left-padded selector with push4, mem[28:32] = selector
mstore(0, InvalidSIP6Version_error_selector)
mstore(InvalidSIP6Version_error_orderHash_ptr, orderHash)
// revert(abi.encodeWithSignature(
// "InvalidSIP6Version(bytes32)", orderHash)
// )
revert(0x1c, InvalidSIP6Version_error_length)
}
// extraData bytes 93-94: Substandard #1 (MUST be 0x00)
let subStandardVersionByte := shr(
248,
calldataload(
add(extraDataPtr, ExtraData_substandard_version_byte_offset)
)
)
if iszero(eq(subStandardVersionByte, 0x00)) {
// Store left-padded selector with push4, mem[28:32] = selector
mstore(0, InvalidSubstandardVersion_error_selector)
mstore(InvalidSubstandardVersion_error_orderHash_ptr, orderHash)
// revert(abi.encodeWithSignature(
// "InvalidSubstandardVersion(bytes32)", orderHash)
// )
revert(0x1c, InvalidSubstandardVersion_error_length)
}
// extraData bytes 21-29: expiration timestamp (uint64)
expiration := shr(
192,
calldataload(add(extraDataPtr, ExtraData_expiration_offset))
)
// Revert if expired.
if lt(expiration, timestamp()) {
// Store left-padded selector with push4, mem[28:32] = selector
mstore(0, SignatureExpired_error_selector)
mstore(SignatureExpired_error_expiration_ptr, expiration)
mstore(SignatureExpired_error_orderHash_ptr, orderHash)
// revert(abi.encodeWithSignature(
// "SignatureExpired(uint256, bytes32)", expiration orderHash)
// )
revert(0x1c, SignatureExpired_error_length)
}
// // Get the length of the consideration array.
considerationLength := calldataload(
add(0x24, calldataload(Zone_consideration_head_cdPtr))
)
}
// extraData bytes 29-93: signature
// (strictly requires 64 byte compact sig, EIP-2098)
bytes calldata signature = extraData[29:93];
// extraData bytes 93-end: context (optional, variable length)
bytes calldata context = extraData[93:];
// Check the validity of the Substandard #1 extraData and get the
// expected fulfiller address.
address expectedFulfiller = _getExpectedFulfiller(orderHash);
// Check the validity of the Substandard #1 extraData and get the
// expected fulfiller address.
if (considerationLength > 0) {
_assertValidSubstandard(
_deriveConsiderationHash(zoneParameters.consideration),
orderHash
);
}
// Derive the signedOrder hash.
bytes32 signedOrderHash = _deriveSignedOrderHash(
expectedFulfiller,
expiration,
orderHash,
context
);
// Derive the EIP-712 digest using the domain separator and signedOrder
// hash.
bytes32 digest = _deriveEIP712Digest(
_domainSeparator(),
signedOrderHash
);
// Recover the signer address from the digest and signature.
address recoveredSigner = _recoverSigner(digest, signature);
// Revert if the signer is not active.
if (!_signers[recoveredSigner]) {
revert SignerNotActive(recoveredSigner, orderHash);
}
// Return the selector of validateOrder as the magic value.
validOrderMagicValue = ZoneInterface.validateOrder.selector;
}
/**
* @dev Returns Seaport metadata for this contract, returning the
* contract name and supported schemas.
*
* @return name The contract name
* @return schemas The supported SIPs
*/
function getSeaportMetadata()
external
view
override(SIP5Interface, ZoneInterface)
returns (string memory name, Schema[] memory schemas)
{
// Return the supported SIPs.
schemas = new Schema[](1);
schemas[0].id = 7;
// Get the SIP-7 information.
(
bytes32 domainSeparator,
string memory zoneName,
string memory apiEndpoint,
uint256[] memory substandards,
string memory documentationURI
) = _sip7Information();
// Return the zone name.
name = zoneName;
// Encode the SIP-7 information.
schemas[0].metadata = abi.encode(
domainSeparator,
apiEndpoint,
substandards,
documentationURI
);
}
/**
* @notice The fallback function is used as a dispatcher for the
* `updateSigner`, `getActiveSigners` and `supportsInterface`
* functions.
*/
// prettier-ignore
fallback(bytes calldata) external payable returns (bytes memory output) {
// Get the function selector.
bytes4 selector = msg.sig;
if (selector == 0xf460590b) {
// updateSigner(address,bool)
// Get the signer, and active status.
address signer = abi.decode(msg.data[4:], (address));
bool active = abi.decode(msg.data[36:], (bool));
// Call to update the signer.
_updateSigner(signer, active);
} else if (selector == 0xa784b80c) {
// getActiveSigners()
// Call the internal function to get the active signers.
return abi.encode(_getActiveSigners());
} else if (selector == 0x01ffc9a7) {
// supportsInterface(bytes4)
// Get the interface ID.
bytes4 interfaceId = abi.decode(msg.data[4:], (bytes4));
// Call the internal function to determine if the interface is
// supported.
return abi.encode(_supportsInterface(interfaceId));
}
}
/**
* @notice Add or remove a signer to the zone.
* Only the controller can call this function.
*
* @param signer The signer address to add or remove.
*/
function _updateSigner(address signer, bool active) internal {
// Only the controller can call this function.
_assertCallerIsController();
// Add or remove the signer.
active ? _addSigner(signer) : _removeSigner(signer);
}
/**
* @notice Add a new signer to the zone.
* Only the controller or an active signer can call this function.
*
* @param signer The new signer address to add.
*/
function _addSigner(address signer) internal {
// Set the signer info.
_signers[signer] = true;
// Emit an event that the signer was added.
emit SignerAdded(signer);
}
/**
* @notice Remove an active signer from the zone.
* Only the controller or an active signer can call this function.
*
* @param signer The signer address to remove.
*/
function _removeSigner(address signer) internal {
// Set the signer's active status to false.
_signers[signer] = false;
// Emit an event that the signer was removed.
emit SignerRemoved(signer);
}
/**
* @notice Returns the active signers for the zone.
*
* @return signers The active signers.
*/
function _getActiveSigners()
internal
view
returns (address[] memory signers)
{
// Return the active signers for the zone by calling the controller.
signers = SignedZoneControllerInterface(_controller).getActiveSigners(
address(this)
);
}
/**
* @notice Returns whether the interface is supported.
*
* @param interfaceId The interface id to check against.
*/
function _supportsInterface(bytes4 interfaceId)
internal
pure
returns (bool supportsInterface)
{
// Determine if the interface is supported.
supportsInterface =
interfaceId == type(SIP5Interface).interfaceId || // SIP-5
interfaceId == type(ZoneInterface).interfaceId || // ZoneInterface
interfaceId == 0x01ffc9a7; // ERC-165
}
/**
* @notice Internal call to return the signing information, substandards,
* and documentation about the zone.
*
* @return domainSeparator The domain separator used for signing.
* @return zoneName The zone name.
* @return apiEndpoint The API endpoint for the zone.
* @return substandards The substandards supported by the zone.
* @return documentationURI The documentation URI for the zone.
*/
function _sip7Information()
internal
view
returns (
bytes32 domainSeparator,
string memory zoneName,
string memory apiEndpoint,
uint256[] memory substandards,
string memory documentationURI
)
{
// Return the SIP-7 information.
domainSeparator = _domainSeparator();
// Get the SIP-7 information from the controller.
(
,
zoneName,
apiEndpoint,
substandards,
documentationURI
) = SignedZoneControllerInterface(_controller)
.getAdditionalZoneInformation(address(this));
}
/**
* @dev Derive the signedOrder hash from the orderHash and expiration.
*
* @param fulfiller The expected fulfiller address.
* @param expiration The signature expiration timestamp.
* @param orderHash The order hash.
* @param context The optional variable-length context.
*
* @return signedOrderHash The signedOrder hash.
*
*/
function _deriveSignedOrderHash(
address fulfiller,
uint64 expiration,
bytes32 orderHash,
bytes calldata context
) internal view returns (bytes32 signedOrderHash) {
// Derive the signed order hash.
signedOrderHash = keccak256(
abi.encode(
_SIGNED_ORDER_TYPEHASH,
fulfiller,
expiration,
orderHash,
keccak256(context)
)
);
}
/**
* @dev Internal view function to return the signer of a signature.
*
* @param digest The digest to verify the signature against.
* @param signature A signature from the signer indicating that the order
* has been approved.
*
* @return recoveredSigner The recovered signer.
*/
function _recoverSigner(bytes32 digest, bytes memory signature)
internal
view
returns (address recoveredSigner)
{
// Utilize assembly to perform optimized signature verification check.
assembly {
// Ensure that first word of scratch space is empty.
mstore(0, 0)
// Declare value for v signature parameter.
let v
// Get the length of the signature.
let signatureLength := mload(signature)
// Get the pointer to the value preceding the signature length.
// This will be used for temporary memory overrides - either the
// signature head for isValidSignature or the digest for ecrecover.
let wordBeforeSignaturePtr := sub(signature, OneWord)
// Cache the current value behind the signature to restore it later.
let cachedWordBeforeSignature := mload(wordBeforeSignaturePtr)
// Declare lenDiff + recoveredSigner scope to manage stack pressure.
{
// Take the difference between the max ECDSA signature length
// and the actual signature length. Overflow desired for any
// values > 65. If the diff is not 0 or 1, it is not a valid
// ECDSA signature - move on to EIP1271 check.
let lenDiff := sub(ECDSA_MaxLength, signatureLength)
// If diff is 0 or 1, it may be an ECDSA signature.
// Try to recover signer.
if iszero(gt(lenDiff, 1)) {
// Read the signature `s` value.
let originalSignatureS := mload(
add(signature, ECDSA_signature_s_offset)
)
// Read the first byte of the word after `s`. If the
// signature is 65 bytes, this will be the real `v` value.
// If not, it will need to be modified - doing it this way
// saves an extra condition.
v := byte(
0,
mload(add(signature, ECDSA_signature_v_offset))
)
// If lenDiff is 1, parse 64-byte signature as ECDSA.
if lenDiff {
// Extract yParity from highest bit of vs and add 27 to
// get v.
v := add(
shr(MaxUint8, originalSignatureS),
Signature_lower_v
)
// Extract canonical s from vs, all but the highest bit.
// Temporarily overwrite the original `s` value in the
// signature.
mstore(
add(signature, ECDSA_signature_s_offset),
and(
originalSignatureS,
EIP2098_allButHighestBitMask
)
)
}
// Temporarily overwrite the signature length with `v` to
// conform to the expected input for ecrecover.
mstore(signature, v)
// Temporarily overwrite the word before the length with
// `digest` to conform to the expected input for ecrecover.
mstore(wordBeforeSignaturePtr, digest)
// Attempt to recover the signer for the given signature. Do
// not check the call status as ecrecover will return a null
// address if the signature is invalid.
pop(
staticcall(
gas(),
Ecrecover_precompile, // Call ecrecover precompile.
wordBeforeSignaturePtr, // Use data memory location.
Ecrecover_args_size, // Size of digest, v, r, and s.
0, // Write result to scratch space.
OneWord // Provide size of returned result.
)
)
// Restore cached word before signature.
mstore(wordBeforeSignaturePtr, cachedWordBeforeSignature)
// Restore cached signature length.
mstore(signature, signatureLength)
// Restore cached signature `s` value.
mstore(
add(signature, ECDSA_signature_s_offset),
originalSignatureS
)
// Read the recovered signer from the buffer given as return
// space for ecrecover.
recoveredSigner := mload(0)
}
}
// Restore the cached values overwritten by selector, digest and
// signature head.
mstore(wordBeforeSignaturePtr, cachedWordBeforeSignature)
}
}
/**
* @dev Internal view function to get the EIP-712 domain separator. If the
* chainId matches the chainId set on deployment, the cached domain
* separator will be returned; otherwise, it will be derived from
* scratch.
*
* @return The domain separator.
*/
function _domainSeparator() internal view returns (bytes32) {
// prettier-ignore
return block.chainid == _CHAIN_ID
? _DOMAIN_SEPARATOR
: _deriveDomainSeparator();
}
/**
* @dev Internal view function to derive the EIP-712 domain separator.
*
* @return domainSeparator The derived domain separator.
*/
function _deriveDomainSeparator()
internal
view
returns (bytes32 domainSeparator)
{
bytes32 typehash = _EIP_712_DOMAIN_TYPEHASH;
bytes32 nameHash = _NAME_HASH;
bytes32 versionHash = _VERSION_HASH;
// Leverage scratch space and other memory to perform an efficient hash.
assembly {
// Retrieve the free memory pointer; it will be replaced afterwards.
let freeMemoryPointer := mload(FreeMemoryPointerSlot)
// Retrieve value at 0x80; it will also be replaced afterwards.
let slot0x80 := mload(Slot0x80)
// Place typehash, name hash, and version hash at start of memory.
mstore(0, typehash)
mstore(OneWord, nameHash)
mstore(TwoWords, versionHash)
// Place chainId in the next memory location.
mstore(ThreeWords, chainid())
// Place the address of this contract in the next memory location.
mstore(FourWords, address())
// Hash relevant region of memory to derive the domain separator.
domainSeparator := keccak256(0, FiveWords)
// Restore the free memory pointer.
mstore(FreeMemoryPointerSlot, freeMemoryPointer)
// Restore the zero slot to zero.
mstore(ZeroSlot, 0)
// Restore the value at 0x80.
mstore(Slot0x80, slot0x80)
}
}
/**
* @dev Internal pure function to efficiently derive an digest to sign for
* an order in accordance with EIP-712.
*
* @param domainSeparator The domain separator.
* @param signedOrderHash The signedOrder hash.
*
* @return digest The digest hash.
*/
function _deriveEIP712Digest(
bytes32 domainSeparator,
bytes32 signedOrderHash
) internal pure returns (bytes32 digest) {
// Leverage scratch space to perform an efficient hash.
assembly {
// Place the EIP-712 prefix at the start of scratch space.
mstore(0, EIP_712_PREFIX)
// Place the domain separator in the next region of scratch space.
mstore(EIP712_DomainSeparator_offset, domainSeparator)
// Place the signed order hash in scratch space, spilling into the
// first two bytes of the free memory pointer — this should never be
// set as memory cannot be expanded to that size, and will be
// zeroed out after the hash is performed.
mstore(EIP712_SignedOrderHash_offset, signedOrderHash)
// Hash the relevant region
digest := keccak256(0, EIP712_DigestPayload_size)
// Clear out the dirtied bits in the memory pointer.
mstore(EIP712_SignedOrderHash_offset, 0)
}
}
/**
* @dev Private view function to revert if the caller is not the
* controller.
*/
function _assertCallerIsController() internal view {
// Get the controller address to use in the assembly block.
address controller = _controller;
assembly {
// Revert if the caller is not the controller.
if iszero(eq(caller(), controller)) {
// Store left-padded selector with push4, mem[28:32] = selector
mstore(0, InvalidController_error_selector)
// revert(abi.encodeWithSignature(
// "InvalidController()")
// )
revert(0x1c, InvalidController_error_length)
}
}
}
/**
* @dev Internal pure function to validate calldata offsets for the
* dyanamic type in ZoneParameters. This ensures that functions using
* the calldata object normally will be using the same data as the
* assembly functions and that values that are bound to a given range
* are within that range.
*/
function _assertValidZoneParameters() internal pure {
// Utilize assembly in order to read offset data directly from calldata.
assembly {
/*
* Checks:
* 1. Zone parameters struct offset == 0x20
*/
// Zone parameters at calldata 0x04 must have offset of 0x20.
if iszero(
eq(calldataload(Zone_parameters_cdPtr), Zone_parameters_ptr)
) {
// Store left-padded selector with push4 (reduces bytecode), mem[28:32] = selector
mstore(0, InvalidZoneParameterEncoding_error_selector)
// revert(abi.encodeWithSignature("InvalidZoneParameterEncoding()"))
revert(0x1c, InvalidZoneParameterEncoding_error_length)
}
}
}
/**
* @dev Internal pure function to ensure that the context argument for the
* supplied extra data follows the substandard #1 format. Returns the
* expected fulfiller of the order for deriving the signed order hash.
*
* @param orderHash The order hash.
*
* @return expectedFulfiller The expected fulfiller of the order.
*/
function _getExpectedFulfiller(bytes32 orderHash)
internal
pure
returns (address expectedFulfiller)
{
// Revert if the expected fulfiller is not the zero address and does
// not match the actual fulfiller
assembly {
// Get the actual fulfiller.
let actualFulfiller := calldataload(Zone_parameters_fulfiller_cdPtr)
let extraDataPtr := calldataload(Zone_extraData_cdPtr)
// Get the expected fulfiller.
expectedFulfiller := shr(
96,
calldataload(add(expectedFulfiller_offset, extraDataPtr))
)
// Revert if expected fulfiller is not the zero address and does
// not match the actual fulfiller.
if and(
iszero(iszero(expectedFulfiller)),
iszero(eq(expectedFulfiller, actualFulfiller))
) {
// Store left-padded selector with push4, mem[28:32] = selector
mstore(0, InvalidFulfiller_error_selector)
mstore(
InvalidFulfiller_error_expectedFulfiller_ptr,
expectedFulfiller
)
mstore(
InvalidFulfiller_error_actualFulfiller_ptr,
actualFulfiller
)
mstore(InvalidFulfiller_error_orderHash_ptr, orderHash)
// revert(abi.encodeWithSignature(
// "InvalidFulfiller(address,address,bytes32)", expectedFulfiller, actualFulfiller, orderHash)
// )
revert(0x1c, InvalidFulfiller_error_length)
}
}
}
/**
* @dev Internal pure function to ensure that the context argument for the
* supplied extra data follows the substandard #1 format. Returns the
* expected fulfiller of the order for deriving the signed order hash.
*
*/
function _assertValidSubstandard(
bytes32 considerationHash,
bytes32 orderHash
) internal pure {
// identifier does not match the actual consideration.
assembly {
let extraDataPtr := calldataload(Zone_extraData_cdPtr)
let considerationPtr := calldataload(Zone_consideration_head_cdPtr)
// Get the actual consideration.
let actualConsideration := calldataload(
add(actualConsideration_offset, considerationPtr)
)
// Get the expected consideration.
let expectedConsiderationHash := calldataload(
add(expectedConsideration_offset, extraDataPtr) //TODO rename
)
// Revert if expected consideration item does not match the actual
// consideration item.
if iszero(eq(considerationHash, expectedConsiderationHash)) {
// Store left-padded selector with push4, mem[28:32] = selector
mstore(0, InvalidConsideration_error_selector)
mstore(
InvalidConsideration_error_expectedConsideration_ptr,
expectedConsiderationHash
)
mstore(
InvalidConsideration_error_actualConsideration_ptr,
actualConsideration
)
mstore(InvalidConsideration_error_orderHash_ptr, orderHash)
// revert(abi.encodeWithSignature(
// "InvalidConsideration(uint256,uint256,bytes32)", expectedConsideration, actualConsideration, orderHash)
// )
revert(0x1c, InvalidConsideration_error_length)
}
}
}
/// @dev Calculates consideration hash
function _deriveConsiderationHash(ReceivedItem[] calldata consideration)
internal
pure
returns (bytes32)
{
uint256 numberOfItems = consideration.length;
bytes32[] memory considerationHashes = new bytes32[](numberOfItems);
for (uint256 i; i < numberOfItems; ) {
considerationHashes[i] = _deriveReceivedItemHash(consideration[i]);
unchecked {
++i;
}
}
return
keccak256(
abi.encode(
CONSIDERATION_HASHTYPE,
keccak256(abi.encodePacked(considerationHashes))
)
);
}
/// @dev Calculates consideration item hash
function _deriveReceivedItemHash(ReceivedItem calldata receivedItem)
internal
pure
returns (bytes32)
{
return
keccak256(
abi.encode(
RECEIVED_ITEM_HASHTYPE,
receivedItem.itemType,
receivedItem.token,
receivedItem.identifier,
receivedItem.amount,
receivedItem.recipient
)
);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import { SignedZone } from "./SignedZone.sol";
import { SignedZoneInterface } from "./interfaces/SignedZoneInterface.sol";
import {
SignedZoneControllerInterface
} from "./interfaces/SignedZoneControllerInterface.sol";
import {
SignedZoneControllerEventsAndErrors
} from "./interfaces/SignedZoneControllerEventsAndErrors.sol";
import "./lib/SignedZoneConstants.sol";
/**
* @title SignedZoneController
* @author BCLeFevre
* @notice SignedZoneController enables the deploying of SignedZones.
* SignedZones are an implementation of SIP-7 that requires orders to
* be signed by an approved signer.
* https://github.com/ProjectOpenSea/SIPs/blob/main/SIPS/sip-7.md
*/
contract SignedZoneController is
SignedZoneControllerInterface,
SignedZoneControllerEventsAndErrors
{
/**
* @dev The struct for storing signer info.
*/
struct SignerInfo {
/// @dev If the signer is currently active.
bool active;
/// @dev If the signer has been active before.
bool previouslyActive;
}
// Properties used by the signed zone, stored on the controller.
struct SignedZoneProperties {
/// @dev Owner of the signed zone (used for permissioned functions)
address owner;
/// @dev Potential owner of the signed zone
address potentialOwner;
/// @dev The name for this zone returned in getSeaportMetadata().
string zoneName;
/// @dev The API endpoint where orders for this zone can be signed.
/// Request and response payloads are defined in SIP-7.
string apiEndpoint;
/// @dev The URI to the documentation describing the behavior of the
/// contract.
string documentationURI;
/// @dev The substandards supported by this zone.
/// Substandards are defined in SIP-7.
uint256[] substandards;
/// @dev Mapping of signer information keyed by signer Address
mapping(address => SignerInfo) signers;
/// @dev List of active signers
address[] activeSignerList;
}
/// @dev Mapping of signed zone properties keyed by the Signed Zone
/// address.
mapping(address => SignedZoneProperties) internal _signedZones;
/// @dev The EIP-712 digest parameters for the SignedZone.
bytes32 internal immutable _NAME_HASH = keccak256(bytes("SignedZone"));
bytes32 internal immutable _VERSION_HASH = keccak256(bytes("1.0"));
// prettier-ignore
bytes32 internal immutable _EIP_712_DOMAIN_TYPEHASH = keccak256(
abi.encodePacked(
"EIP712Domain(",
"string name,",
"string version,",
"uint256 chainId,",
"address verifyingContract",
")"
)
);
uint256 internal immutable _CHAIN_ID = block.chainid;
// Set the signed zone creation code as an immutable argument.
bytes32 internal immutable _SIGNED_ZONE_CREATION_CODE_HASH;
/**
* @dev Initialize contract
*/
constructor() {
// Derive the signed zone creation code hash and set it as an
// immutable.
_SIGNED_ZONE_CREATION_CODE_HASH = keccak256(
type(SignedZone).creationCode
);
}
/**
* @notice Deploy a SignedZone to a precomputed address.
*
* @param zoneName The name for the zone returned in
* getSeaportMetadata().
* @param apiEndpoint The API endpoint where orders for this zone can be
* signed.
* @param documentationURI The URI to the documentation describing the
* behavior of the contract.
* Request and response payloads are defined in SIP-7.
* @param salt The salt to be used to derive the zone address
* @param initialOwner The initial owner to set for the new zone.
*
* @return derivedAddress The derived address for the zone.
*/
function createZone(
string memory zoneName,
string memory apiEndpoint,
string memory documentationURI,
address initialOwner,
bytes32 salt
) external override returns (address derivedAddress) {
// Ensure that an initial owner has been supplied.
if (initialOwner == address(0)) {
revert InvalidInitialOwner();
}
// Ensure the first 20 bytes of the salt are the same as the msg.sender.
if ((address(uint160(bytes20(salt))) != msg.sender)) {
// Revert with an error indicating that the creator is invalid.
revert InvalidCreator();
}
// Derive the SignedZone address from the deployer, salt and creation
// code hash.
derivedAddress = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
salt,
_SIGNED_ZONE_CREATION_CODE_HASH
)
)
)
)
);
// TODO : Check runtime code hash to ensure that the zone is not already
// deployed.
// Revert if a zone is currently deployed to the derived address.
if (derivedAddress.code.length != 0) {
revert ZoneAlreadyExists(derivedAddress);
}
// Deploy the zone using the supplied salt.
new SignedZone{ salt: salt }();
// Initialize storage variable referencing signed zone properties.
SignedZoneProperties storage signedZoneProperties = _signedZones[
derivedAddress
];
// Set the supplied intial owner as the owner of the zone.
signedZoneProperties.owner = initialOwner;
// Set the zone name.
signedZoneProperties.zoneName = zoneName;
// Set the API endpoint.
signedZoneProperties.apiEndpoint = apiEndpoint;
// Set the documentation URI.
signedZoneProperties.documentationURI = documentationURI;
// Set the substandard.
signedZoneProperties.substandards = [3];
// Emit an event signifying that the zone was created.
emit ZoneCreated(
derivedAddress,
zoneName,
apiEndpoint,
documentationURI,
salt
);
// Emit an event indicating that zone ownership has been assigned.
emit OwnershipTransferred(derivedAddress, address(0), initialOwner);
}
/**
* @notice Initiate zone ownership transfer by assigning a new potential
* owner for the given zone. Once set, the new potential owner
* may call `acceptOwnership` to claim ownership of the zone.
* Only the owner of the zone in question may call this function.
*
* @param zone The zone for which to initiate ownership transfer.
* @param newPotentialOwner The new potential owner of the zone.
*/
function transferOwnership(address zone, address newPotentialOwner)
external
override
{
// Ensure the caller is the current owner of the zone in question.
_assertCallerIsZoneOwner(zone);
// Ensure the new potential owner is not an invalid address.
if (newPotentialOwner == address(0)) {
revert NewPotentialOwnerIsZeroAddress(zone);
}
// Ensure the new potential owner is not already set.
if (newPotentialOwner == _signedZones[zone].potentialOwner) {
revert NewPotentialOwnerAlreadySet(zone, newPotentialOwner);
}
// Emit an event indicating that the potential owner has been updated.
emit PotentialOwnerUpdated(newPotentialOwner);
// Set the new potential owner as the potential owner of the zone.
_signedZones[zone].potentialOwner = newPotentialOwner;
}
/**
* @notice Clear the currently set potential owner, if any, from a zone.
* Only the owner of the zone in question may call this function.
*
* @param zone The zone for which to cancel ownership transfer.
*/
function cancelOwnershipTransfer(address zone) external override {
// Ensure the caller is the current owner of the zone in question.
_assertCallerIsZoneOwner(zone);
// Ensure that ownership transfer is currently possible.
if (_signedZones[zone].potentialOwner == address(0)) {
revert NoPotentialOwnerCurrentlySet(zone);
}
// Emit an event indicating that the potential owner has been cleared.
emit PotentialOwnerUpdated(address(0));
// Clear the current new potential owner from the zone.
_signedZones[zone].potentialOwner = address(0);
}
/**
* @notice Accept ownership of a supplied zone. Only accounts that the
* current owner has set as the new potential owner may call this
* function.
*
* @param zone The zone for which to accept ownership.
*/
function acceptOwnership(address zone) external override {
// Ensure that the zone in question exists.
_assertZoneExists(zone);
// If caller does not match current potential owner of the zone...
if (msg.sender != _signedZones[zone].potentialOwner) {
// Revert, indicating that caller is not current potential owner.
revert CallerIsNotNewPotentialOwner(zone);
}
// Emit an event indicating that the potential owner has been cleared.
emit PotentialOwnerUpdated(address(0));
// Clear the current new potential owner from the zone.
_signedZones[zone].potentialOwner = address(0);
// Emit an event indicating zone ownership has been transferred.
emit OwnershipTransferred(zone, _signedZones[zone].owner, msg.sender);
// Set the caller as the owner of the zone.
_signedZones[zone].owner = msg.sender;
}
/**
* @notice Retrieve the current owner of a deployed zone.
*
* @param zone The zone for which to retrieve the associated owner.
*
* @return owner The owner of the supplied zone.
*/
function ownerOf(address zone)
external
view
override
returns (address owner)
{
// Ensure that the zone in question exists.
_assertZoneExists(zone);
// Retrieve the current owner of the zone in question.
owner = _signedZones[zone].owner;
}
/**
* @notice Retrieve the potential owner, if any, for a given zone. The
* current owner may set a new potential owner via
* `transferOwnership` and that owner may then accept ownership of
* the zone in question via `acceptOwnership`.
*
* @param zone The zone for which to retrieve the potential owner.
*
* @return potentialOwner The potential owner, if any, for the zone.
*/
function getPotentialOwner(address zone)
external
view
override
returns (address potentialOwner)
{
// Ensure that the zone in question exists.
_assertZoneExists(zone);
// Retrieve the current potential owner of the zone in question.
potentialOwner = _signedZones[zone].potentialOwner;
}
/**
* @notice Returns the active signers for the zone.
*
* @param zone The zone to return the active signers for.
*
* @return signers The active signers.
*/
function getActiveSigners(address zone)
external
view
override
returns (address[] memory signers)
{
// Ensure that the zone in question exists.
_assertZoneExists(zone);
// Retrieve storage region where the singers for the signedZone are
// stored.
SignedZoneProperties storage signedZoneProperties = _signedZones[zone];
// Return the active signers for the zone.
signers = signedZoneProperties.activeSignerList;
}
/**
* @notice Update the API endpoint returned by a zone.
* Only the owner or an active signer of the supplied zone can call
* this function.
*
* @param zone The signed zone to update the API endpoint for.
* @param newApiEndpoint The new API endpoint.
*/
function updateAPIEndpoint(address zone, string calldata newApiEndpoint)
external
override
{
// Ensure the caller is the owner or an active signer of the signed zone.
_assertCallerIsZoneOwnerOrSigner(zone);
// Retrieve storage region where the singers for the signedZone are
// stored.
SignedZoneProperties storage signedZoneProperties = _signedZones[zone];
// Update the API endpoint on the signed zone.
signedZoneProperties.apiEndpoint = newApiEndpoint;
}
/**
* @notice Add or remove a signer from the supplied zone.
* Only the owner or an active signer of the supplied zone can call
* this function.
*
* @param zone The signed zone to update the signer permissions for.
* @param signer The signer to update the permissions for.
* @param active Whether the signer should be active or not.
*/
function updateSigner(
address zone,
address signer,
bool active
) external override {
// Ensure the caller is the owner or an active signer of the signed zone.
_assertCallerIsZoneOwnerOrSigner(zone);
// Retrieve storage region where the singers for the signedZone are
// stored.
SignedZoneProperties storage signedZoneProperties = _signedZones[zone];
// Validate signer permissions.
_assertSignerPermissions(signedZoneProperties, signer, active);
// Update the signer on the signed zone.
SignedZoneInterface(zone).updateSigner(signer, active);
// Update the signer information.
signedZoneProperties.signers[signer].active = active;
signedZoneProperties.signers[signer].previouslyActive = true;
// Add the signer to the list of signers if they are active.
if (active) {
signedZoneProperties.activeSignerList.push(signer);
} else {
// Remove the signer from the list of signers.
for (
uint256 i = 0;
i < signedZoneProperties.activeSignerList.length;
) {
if (signedZoneProperties.activeSignerList[i] == signer) {
signedZoneProperties.activeSignerList[
i
] = signedZoneProperties.activeSignerList[
signedZoneProperties.activeSignerList.length - 1
];
signedZoneProperties.activeSignerList.pop();
break;
}
unchecked {
++i;
}
}
}
// Emit an event signifying that the signer was updated.
emit SignerUpdated(zone, signer, active);
}
/**
* @notice Derive the zone address associated with a salt.
*
* @param salt The salt to be used to derive the zone address.
*
* @return derivedAddress The derived address of the signed zone.
*/
function getZone(bytes32 salt)
external
view
override
returns (address derivedAddress)
{
// Derive the SignedZone address from deployer, salt and creation code
// hash.
derivedAddress = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
salt,
_SIGNED_ZONE_CREATION_CODE_HASH
)
)
)
)
);
}
/**
* @notice External call to return the signing information, substandards,
* and documentation about the zone.
*
* @return domainSeparator The domain separator used for signing.
* @return zoneName The name of the zone.
* @return apiEndpoint The API endpoint for the zone.
* @return substandards The substandards supported by the zone.
* @return documentationURI The documentation URI for the zone.
*/
function getAdditionalZoneInformation(address zone)
external
view
override
returns (
bytes32 domainSeparator,
string memory zoneName,
string memory apiEndpoint,
uint256[] memory substandards,
string memory documentationURI
)
{
// Ensure the zone exists.
_assertZoneExists(zone);
// Return the zone's additional information.
return _additionalZoneInformation(zone);
}
/**
* @notice Internal call to return the signing information, substandards,
* and documentation about the zone.
*
* @return domainSeparator The domain separator used for signing.
* @return zoneName The name of the zone.
* @return apiEndpoint The API endpoint for the zone.
* @return substandards The substandards supported by the zone.
* @return documentationURI The documentation URI for the zone.
*/
function _additionalZoneInformation(address zone)
internal
view
returns (
bytes32 domainSeparator,
string memory zoneName,
string memory apiEndpoint,
uint256[] memory substandards,
string memory documentationURI
)
{
// Get the zone properties.
SignedZoneProperties storage signedZoneProperties = _signedZones[zone];
// Return the SIP-7 information.
domainSeparator = _domainSeparator(zone);
zoneName = signedZoneProperties.zoneName;
apiEndpoint = signedZoneProperties.apiEndpoint;
substandards = signedZoneProperties.substandards;
documentationURI = signedZoneProperties.documentationURI;
}
/**
* @dev Internal view function to get the EIP-712 domain separator. If the
* chainId matches the chainId set on deployment, the cached domain
* separator will be returned; otherwise, it will be derived from
* scratch.
*
* @return The domain separator.
*/
function _domainSeparator(address zone) internal view returns (bytes32) {
// prettier-ignore
return _deriveDomainSeparator(zone);
}
/**
* @dev Internal view function to derive the EIP-712 domain separator.
*
* @return domainSeparator The derived domain separator.
*/
function _deriveDomainSeparator(address zone)
internal
view
returns (bytes32 domainSeparator)
{
bytes32 typehash = _EIP_712_DOMAIN_TYPEHASH;
bytes32 nameHash = _NAME_HASH;
bytes32 versionHash = _VERSION_HASH;
// Leverage scratch space and other memory to perform an efficient hash.
assembly {
// Retrieve the free memory pointer; it will be replaced afterwards.
let freeMemoryPointer := mload(FreeMemoryPointerSlot)
// Retrieve value at 0x80; it will also be replaced afterwards.
let slot0x80 := mload(Slot0x80)
// Place typehash, name hash, and version hash at start of memory.
mstore(0, typehash)
mstore(OneWord, nameHash)
mstore(TwoWords, versionHash)
// Place chainId in the next memory location.
mstore(ThreeWords, chainid())
// Place the address of the signed zone contract in the next memory location.
mstore(FourWords, zone)
// Hash relevant region of memory to derive the domain separator.
domainSeparator := keccak256(0, FiveWords)
// Restore the free memory pointer.
mstore(FreeMemoryPointerSlot, freeMemoryPointer)
// Restore the zero slot to zero.
mstore(ZeroSlot, 0)
// Restore the value at 0x80.
mstore(Slot0x80, slot0x80)
}
}
/**
* @dev Private view function to revert if the caller is not the owner of a
* given zone.
*
* @param zone The zone for which to assert ownership.
*/
function _assertCallerIsZoneOwner(address zone) private view {
// Ensure that the zone in question exists.
_assertZoneExists(zone);
// If the caller does not match the current owner of the zone...
if (msg.sender != _signedZones[zone].owner) {
// Revert, indicating that the caller is not the owner.
revert CallerIsNotOwner(zone);
}
}
/**
* @dev Private view function to revert if the caller is not the owner or
* an active signer of a given zone.
*
* @param zone The zone for which to assert ownership.
*/
function _assertCallerIsZoneOwnerOrSigner(address zone) private view {
// Ensure that the zone in question exists.
_assertZoneExists(zone);
// Initialize storage variable referencing signed zone properties.
SignedZoneProperties storage signedZoneProperties = _signedZones[zone];
// Ensure the caller is the owner or an active signer of the signed zone.
if (
msg.sender != _signedZones[zone].owner &&
!signedZoneProperties.signers[msg.sender].active
) {
// Revert, indicating that the caller is not the owner.
revert CallerIsNotOwnerOrSigner(zone);
}
}
/**
* @dev Private view function to revert if a given zone does not exist.
*
* @param zone The zone for which to assert existence.
*/
function _assertZoneExists(address zone) private view {
// Attempt to retrieve a the owner for the zone in question.
if (_signedZones[zone].owner == address(0)) {
// Revert if no ownerwas located.
revert NoZone();
}
}
/**
* @dev Private view function to revert if a signer being added to a zone
* is the zero address or the signer already exists, or the signer was
* previously authorized. If the signer is being removed, the
* function will revert if the signer is not active.
*
* @param signedZoneProperties The signed zone properties for the zone.
* @param signer The signer to add or remove.
* @param active Whether the signer is being added or removed.
*/
function _assertSignerPermissions(
SignedZoneProperties storage signedZoneProperties,
address signer,
bool active
) private view {
// If the signer is being added...
if (active) {
// Do not allow the zero address to be added as a signer.
if (signer == address(0)) {
revert SignerCannotBeZeroAddress();
}
// Revert if the signer is already added.
if (signedZoneProperties.signers[signer].active) {
revert SignerAlreadyAdded(signer);
}
// Revert if the signer was previously authorized.
if (signedZoneProperties.signers[signer].previouslyActive) {
revert SignerCannotBeReauthorized(signer);
}
} else {
// Revert if the signer is not active.
if (!signedZoneProperties.signers[signer].active) {
revert SignerNotPresent(signer);
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {SignedZoneV16} from "./SignedZoneV16.sol";
import {SignedZoneInterface} from "./interfaces/SignedZoneInterface.sol";
import {SignedZoneControllerInterface} from "./interfaces/SignedZoneControllerInterface.sol";
import {SignedZoneControllerEventsAndErrors} from "./interfaces/SignedZoneControllerEventsAndErrors.sol";
import "./lib/SignedZoneConstants.sol";
/**
* @title SignedZoneControllerV16
* @author BCLeFevre
* @notice SignedZoneControllerV16 enables the deploying of SignedZones.
* SignedZones are an implementation of SIP-7 that requires orders to
* be signed by an approved signer.
* https://github.com/ProjectOpenSea/SIPs/blob/main/SIPS/sip-7.md
*/
contract SignedZoneControllerV16 is
SignedZoneControllerInterface,
SignedZoneControllerEventsAndErrors
{
/**
* @dev The struct for storing signer info.
*/
struct SignerInfo {
/// @dev If the signer is currently active.
bool active;
/// @dev If the signer has been active before.
bool previouslyActive;
}
// Properties used by the signed zone, stored on the controller.
struct SignedZoneProperties {
/// @dev Owner of the signed zone (used for permissioned functions)
address owner;
/// @dev Potential owner of the signed zone
address potentialOwner;
/// @dev The name for this zone returned in getSeaportMetadata().
string zoneName;
/// @dev The API endpoint where orders for this zone can be signed.
/// Request and response payloads are defined in SIP-7.
string apiEndpoint;
/// @dev The URI to the documentation describing the behavior of the
/// contract.
string documentationURI;
/// @dev The substandards supported by this zone.
/// Substandards are defined in SIP-7.
uint256[] substandards;
/// @dev Mapping of signer information keyed by signer Address
mapping(address => SignerInfo) signers;
/// @dev List of active signers
address[] activeSignerList;
}
/// @dev Mapping of signed zone properties keyed by the Signed Zone
/// address.
mapping(address => SignedZoneProperties) internal _signedZones;
/// @dev The EIP-712 digest parameters for the SignedZone.
bytes32 internal immutable _NAME_HASH = keccak256(bytes("SignedZone"));
bytes32 internal immutable _VERSION_HASH = keccak256(bytes("1.0"));
// prettier-ignore
bytes32 internal immutable _EIP_712_DOMAIN_TYPEHASH = keccak256(
abi.encodePacked(
"EIP712Domain(",
"string name,",
"string version,",
"uint256 chainId,",
"address verifyingContract",
")"
)
);
uint256 internal immutable _CHAIN_ID = block.chainid;
// Set the signed zone creation code as an immutable argument.
bytes32 internal immutable _SIGNED_ZONE_CREATION_CODE_HASH;
/**
* @dev Initialize contract
*/
constructor() {
// Derive the signed zone creation code hash and set it as an
// immutable.
_SIGNED_ZONE_CREATION_CODE_HASH = keccak256(type(SignedZoneV16).creationCode);
}
/**
* @notice Deploy a SignedZoneV16 to a precomputed address.
*
* @param zoneName The name for the zone returned in
* getSeaportMetadata().
* @param apiEndpoint The API endpoint where orders for this zone can be
* signed.
* @param documentationURI The URI to the documentation describing the
* behavior of the contract.
* Request and response payloads are defined in SIP-7.
* @param salt The salt to be used to derive the zone address
* @param initialOwner The initial owner to set for the new zone.
*
* @return derivedAddress The derived address for the zone.
*/
function createZone(
string memory zoneName,
string memory apiEndpoint,
string memory documentationURI,
address initialOwner,
bytes32 salt
) external override returns (address derivedAddress) {
// Ensure that an initial owner has been supplied.
if (initialOwner == address(0)) {
revert InvalidInitialOwner();
}
// Ensure the first 20 bytes of the salt are the same as the msg.sender.
if ((address(uint160(bytes20(salt))) != msg.sender)) {
// Revert with an error indicating that the creator is invalid.
revert InvalidCreator();
}
// Derive the SignedZoneV16 address from the deployer, salt and creation
// code hash.
derivedAddress = address(
uint160(
uint256(
keccak256(
abi.encodePacked(bytes1(0xff), address(this), salt, _SIGNED_ZONE_CREATION_CODE_HASH)
)
)
)
);
// TODO : Check runtime code hash to ensure that the zone is not already
// deployed.
// Revert if a zone is currently deployed to the derived address.
if (derivedAddress.code.length != 0) {
revert ZoneAlreadyExists(derivedAddress);
}
// Deploy the zone using the supplied salt.
new SignedZoneV16{salt: salt}();
// Initialize storage variable referencing signed zone properties.
SignedZoneProperties storage signedZoneProperties = _signedZones[derivedAddress];
// Set the supplied intial owner as the owner of the zone.
signedZoneProperties.owner = initialOwner;
// Set the zone name.
signedZoneProperties.zoneName = zoneName;
// Set the API endpoint.
signedZoneProperties.apiEndpoint = apiEndpoint;
// Set the documentation URI.
signedZoneProperties.documentationURI = documentationURI;
// Set the substandard.
signedZoneProperties.substandards = [3];
// Emit an event signifying that the zone was created.
emit ZoneCreated(derivedAddress, zoneName, apiEndpoint, documentationURI, salt);
// Emit an event indicating that zone ownership has been assigned.
emit OwnershipTransferred(derivedAddress, address(0), initialOwner);
}
/**
* @notice Initiate zone ownership transfer by assigning a new potential
* owner for the given zone. Once set, the new potential owner
* may call `acceptOwnership` to claim ownership of the zone.
* Only the owner of the zone in question may call this function.
*
* @param zone The zone for which to initiate ownership transfer.
* @param newPotentialOwner The new potential owner of the zone.
*/
function transferOwnership(address zone, address newPotentialOwner) external override {
// Ensure the caller is the current owner of the zone in question.
_assertCallerIsZoneOwner(zone);
// Ensure the new potential owner is not an invalid address.
if (newPotentialOwner == address(0)) {
revert NewPotentialOwnerIsZeroAddress(zone);
}
// Ensure the new potential owner is not already set.
if (newPotentialOwner == _signedZones[zone].potentialOwner) {
revert NewPotentialOwnerAlreadySet(zone, newPotentialOwner);
}
// Emit an event indicating that the potential owner has been updated.
emit PotentialOwnerUpdated(newPotentialOwner);
// Set the new potential owner as the potential owner of the zone.
_signedZones[zone].potentialOwner = newPotentialOwner;
}
/**
* @notice Clear the currently set potential owner, if any, from a zone.
* Only the owner of the zone in question may call this function.
*
* @param zone The zone for which to cancel ownership transfer.
*/
function cancelOwnershipTransfer(address zone) external override {
// Ensure the caller is the current owner of the zone in question.
_assertCallerIsZoneOwner(zone);
// Ensure that ownership transfer is currently possible.
if (_signedZones[zone].potentialOwner == address(0)) {
revert NoPotentialOwnerCurrentlySet(zone);
}
// Emit an event indicating that the potential owner has been cleared.
emit PotentialOwnerUpdated(address(0));
// Clear the current new potential owner from the zone.
_signedZones[zone].potentialOwner = address(0);
}
/**
* @notice Accept ownership of a supplied zone. Only accounts that the
* current owner has set as the new potential owner may call this
* function.
*
* @param zone The zone for which to accept ownership.
*/
function acceptOwnership(address zone) external override {
// Ensure that the zone in question exists.
_assertZoneExists(zone);
// If caller does not match current potential owner of the zone...
if (msg.sender != _signedZones[zone].potentialOwner) {
// Revert, indicating that caller is not current potential owner.
revert CallerIsNotNewPotentialOwner(zone);
}
// Emit an event indicating that the potential owner has been cleared.
emit PotentialOwnerUpdated(address(0));
// Clear the current new potential owner from the zone.
_signedZones[zone].potentialOwner = address(0);
// Emit an event indicating zone ownership has been transferred.
emit OwnershipTransferred(zone, _signedZones[zone].owner, msg.sender);
// Set the caller as the owner of the zone.
_signedZones[zone].owner = msg.sender;
}
/**
* @notice Retrieve the current owner of a deployed zone.
*
* @param zone The zone for which to retrieve the associated owner.
*
* @return owner The owner of the supplied zone.
*/
function ownerOf(address zone) external view override returns (address owner) {
// Ensure that the zone in question exists.
_assertZoneExists(zone);
// Retrieve the current owner of the zone in question.
owner = _signedZones[zone].owner;
}
/**
* @notice Retrieve the potential owner, if any, for a given zone. The
* current owner may set a new potential owner via
* `transferOwnership` and that owner may then accept ownership of
* the zone in question via `acceptOwnership`.
*
* @param zone The zone for which to retrieve the potential owner.
*
* @return potentialOwner The potential owner, if any, for the zone.
*/
function getPotentialOwner(address zone) external view override returns (address potentialOwner) {
// Ensure that the zone in question exists.
_assertZoneExists(zone);
// Retrieve the current potential owner of the zone in question.
potentialOwner = _signedZones[zone].potentialOwner;
}
/**
* @notice Returns the active signers for the zone.
*
* @param zone The zone to return the active signers for.
*
* @return signers The active signers.
*/
function getActiveSigners(
address zone
) external view override returns (address[] memory signers) {
// Ensure that the zone in question exists.
_assertZoneExists(zone);
// Retrieve storage region where the singers for the signedZone are
// stored.
SignedZoneProperties storage signedZoneProperties = _signedZones[zone];
// Return the active signers for the zone.
signers = signedZoneProperties.activeSignerList;
}
/**
* @notice Update the API endpoint returned by a zone.
* Only the owner or an active signer of the supplied zone can call
* this function.
*
* @param zone The signed zone to update the API endpoint for.
* @param newApiEndpoint The new API endpoint.
*/
function updateAPIEndpoint(address zone, string calldata newApiEndpoint) external override {
// Ensure the caller is the owner or an active signer of the signed zone.
_assertCallerIsZoneOwnerOrSigner(zone);
// Retrieve storage region where the singers for the signedZone are
// stored.
SignedZoneProperties storage signedZoneProperties = _signedZones[zone];
// Update the API endpoint on the signed zone.
signedZoneProperties.apiEndpoint = newApiEndpoint;
}
/**
* @notice Add or remove a signer from the supplied zone.
* Only the owner or an active signer of the supplied zone can call
* this function.
*
* @param zone The signed zone to update the signer permissions for.
* @param signer The signer to update the permissions for.
* @param active Whether the signer should be active or not.
*/
function updateSigner(address zone, address signer, bool active) external override {
// Ensure the caller is the owner or an active signer of the signed zone.
_assertCallerIsZoneOwnerOrSigner(zone);
// Retrieve storage region where the singers for the signedZone are
// stored.
SignedZoneProperties storage signedZoneProperties = _signedZones[zone];
// Validate signer permissions.
_assertSignerPermissions(signedZoneProperties, signer, active);
// Update the signer on the signed zone.
SignedZoneInterface(zone).updateSigner(signer, active);
// Update the signer information.
signedZoneProperties.signers[signer].active = active;
signedZoneProperties.signers[signer].previouslyActive = true;
// Add the signer to the list of signers if they are active.
if (active) {
signedZoneProperties.activeSignerList.push(signer);
} else {
// Remove the signer from the list of signers.
for (uint256 i = 0; i < signedZoneProperties.activeSignerList.length; ) {
if (signedZoneProperties.activeSignerList[i] == signer) {
signedZoneProperties.activeSignerList[i] = signedZoneProperties.activeSignerList[
signedZoneProperties.activeSignerList.length - 1
];
signedZoneProperties.activeSignerList.pop();
break;
}
unchecked {
++i;
}
}
}
// Emit an event signifying that the signer was updated.
emit SignerUpdated(zone, signer, active);
}
/**
* @notice Derive the zone address associated with a salt.
*
* @param salt The salt to be used to derive the zone address.
*
* @return derivedAddress The derived address of the signed zone.
*/
function getZone(bytes32 salt) external view override returns (address derivedAddress) {
// Derive the SignedZoneV16 address from deployer, salt and creation code
// hash.
derivedAddress = address(
uint160(
uint256(
keccak256(
abi.encodePacked(bytes1(0xff), address(this), salt, _SIGNED_ZONE_CREATION_CODE_HASH)
)
)
)
);
}
/**
* @notice External call to return the signing information, substandards,
* and documentation about the zone.
*
* @return domainSeparator The domain separator used for signing.
* @return zoneName The name of the zone.
* @return apiEndpoint The API endpoint for the zone.
* @return substandards The substandards supported by the zone.
* @return documentationURI The documentation URI for the zone.
*/
function getAdditionalZoneInformation(
address zone
)
external
view
override
returns (
bytes32 domainSeparator,
string memory zoneName,
string memory apiEndpoint,
uint256[] memory substandards,
string memory documentationURI
)
{
// Ensure the zone exists.
_assertZoneExists(zone);
// Return the zone's additional information.
return _additionalZoneInformation(zone);
}
/**
* @notice Internal call to return the signing information, substandards,
* and documentation about the zone.
*
* @return domainSeparator The domain separator used for signing.
* @return zoneName The name of the zone.
* @return apiEndpoint The API endpoint for the zone.
* @return substandards The substandards supported by the zone.
* @return documentationURI The documentation URI for the zone.
*/
function _additionalZoneInformation(
address zone
)
internal
view
returns (
bytes32 domainSeparator,
string memory zoneName,
string memory apiEndpoint,
uint256[] memory substandards,
string memory documentationURI
)
{
// Get the zone properties.
SignedZoneProperties storage signedZoneProperties = _signedZones[zone];
// Return the SIP-7 information.
domainSeparator = _domainSeparator(zone);
zoneName = signedZoneProperties.zoneName;
apiEndpoint = signedZoneProperties.apiEndpoint;
substandards = signedZoneProperties.substandards;
documentationURI = signedZoneProperties.documentationURI;
}
/**
* @dev Internal view function to get the EIP-712 domain separator. If the
* chainId matches the chainId set on deployment, the cached domain
* separator will be returned; otherwise, it will be derived from
* scratch.
*
* @return The domain separator.
*/
function _domainSeparator(address zone) internal view returns (bytes32) {
// prettier-ignore
return _deriveDomainSeparator(zone);
}
/**
* @dev Internal view function to derive the EIP-712 domain separator.
*
* @return domainSeparator The derived domain separator.
*/
function _deriveDomainSeparator(address zone) internal view returns (bytes32 domainSeparator) {
bytes32 typehash = _EIP_712_DOMAIN_TYPEHASH;
bytes32 nameHash = _NAME_HASH;
bytes32 versionHash = _VERSION_HASH;
// Leverage scratch space and other memory to perform an efficient hash.
assembly {
// Retrieve the free memory pointer; it will be replaced afterwards.
let freeMemoryPointer := mload(FreeMemoryPointerSlot)
// Retrieve value at 0x80; it will also be replaced afterwards.
let slot0x80 := mload(Slot0x80)
// Place typehash, name hash, and version hash at start of memory.
mstore(0, typehash)
mstore(OneWord, nameHash)
mstore(TwoWords, versionHash)
// Place chainId in the next memory location.
mstore(ThreeWords, chainid())
// Place the address of the signed zone contract in the next memory location.
mstore(FourWords, zone)
// Hash relevant region of memory to derive the domain separator.
domainSeparator := keccak256(0, FiveWords)
// Restore the free memory pointer.
mstore(FreeMemoryPointerSlot, freeMemoryPointer)
// Restore the zero slot to zero.
mstore(ZeroSlot, 0)
// Restore the value at 0x80.
mstore(Slot0x80, slot0x80)
}
}
/**
* @dev Private view function to revert if the caller is not the owner of a
* given zone.
*
* @param zone The zone for which to assert ownership.
*/
function _assertCallerIsZoneOwner(address zone) private view {
// Ensure that the zone in question exists.
_assertZoneExists(zone);
// If the caller does not match the current owner of the zone...
if (msg.sender != _signedZones[zone].owner) {
// Revert, indicating that the caller is not the owner.
revert CallerIsNotOwner(zone);
}
}
/**
* @dev Private view function to revert if the caller is not the owner or
* an active signer of a given zone.
*
* @param zone The zone for which to assert ownership.
*/
function _assertCallerIsZoneOwnerOrSigner(address zone) private view {
// Ensure that the zone in question exists.
_assertZoneExists(zone);
// Initialize storage variable referencing signed zone properties.
SignedZoneProperties storage signedZoneProperties = _signedZones[zone];
// Ensure the caller is the owner or an active signer of the signed zone.
if (
msg.sender != _signedZones[zone].owner && !signedZoneProperties.signers[msg.sender].active
) {
// Revert, indicating that the caller is not the owner.
revert CallerIsNotOwnerOrSigner(zone);
}
}
/**
* @dev Private view function to revert if a given zone does not exist.
*
* @param zone The zone for which to assert existence.
*/
function _assertZoneExists(address zone) private view {
// Attempt to retrieve a the owner for the zone in question.
if (_signedZones[zone].owner == address(0)) {
// Revert if no ownerwas located.
revert NoZone();
}
}
/**
* @dev Private view function to revert if a signer being added to a zone
* is the zero address or the signer already exists, or the signer was
* previously authorized. If the signer is being removed, the
* function will revert if the signer is not active.
*
* @param signedZoneProperties The signed zone properties for the zone.
* @param signer The signer to add or remove.
* @param active Whether the signer is being added or removed.
*/
function _assertSignerPermissions(
SignedZoneProperties storage signedZoneProperties,
address signer,
bool active
) private view {
// If the signer is being added...
if (active) {
// Do not allow the zero address to be added as a signer.
if (signer == address(0)) {
revert SignerCannotBeZeroAddress();
}
// Revert if the signer is already added.
if (signedZoneProperties.signers[signer].active) {
revert SignerAlreadyAdded(signer);
}
// Revert if the signer was previously authorized.
if (signedZoneProperties.signers[signer].previouslyActive) {
revert SignerCannotBeReauthorized(signer);
}
} else {
// Revert if the signer is not active.
if (!signedZoneProperties.signers[signer].active) {
revert SignerNotPresent(signer);
}
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {ZoneParameters, Schema, ReceivedItem} from "../lib/ConsiderationStructs.sol";
import {ZoneInterfaceV16} from "../interfaces/ZoneInterfaceV16.sol";
import {SignedZoneEventsAndErrors} from "./interfaces/SignedZoneEventsAndErrors.sol";
import {SIP5Interface} from "./interfaces/SIP5Interface.sol";
import {SignedZoneControllerInterface} from "./interfaces/SignedZoneControllerInterface.sol";
import "./lib/SignedZoneConstants.sol";
/**
* @title SignedZoneV16
* @author ryanio, BCLeFevre
* @custom:modifiedby Tony Snark
* @notice SignedZoneV16 is an implementation of SIP-7 that requires orders
* to be signed by an approved signer.
* https://github.com/ProjectOpenSea/SIPs/blob/main/SIPS/sip-7.md
*
* Modification:
* Removes support for SIP7 sub-standard 1.
* Adds support for SIP7 sub-standard 3.
*/
contract SignedZoneV16 is SignedZoneEventsAndErrors, ZoneInterfaceV16, SIP5Interface {
/// @dev The zone's controller that is set during deployment.
address private immutable _controller;
/// @dev The authorized signers, and if they are active
mapping(address => bool) private _signers;
/// @dev The EIP-712 digest parameters.
bytes32 internal immutable _NAME_HASH = keccak256(bytes("SignedZone"));
bytes32 internal immutable _VERSION_HASH = keccak256(bytes("1.0.0"));
// prettier-ignore
bytes32 internal immutable _EIP_712_DOMAIN_TYPEHASH = keccak256(
abi.encodePacked(
"EIP712Domain(",
"string name,",
"string version,",
"uint256 chainId,",
"address verifyingContract",
")"
)
);
// prettier-ignore
bytes32 internal immutable _SIGNED_ORDER_TYPEHASH = keccak256(
abi.encodePacked(
"SignedOrder(",
"address fulfiller,",
"uint64 expiration,",
"bytes32 orderHash,",
"bytes context",
")"
)
);
bytes public constant CONSIDERATION_BYTES =
// prettier-ignore
abi.encodePacked(
"Consideration(",
"ReceivedItem[] consideration",
")"
);
bytes public constant RECEIVED_ITEM_BYTES =
// prettier-ignore
abi.encodePacked(
"ReceivedItem(",
"uint8 itemType,",
"address token,",
"uint256 identifier,",
"uint256 amount,",
"address recipient",
")"
);
bytes32 public constant RECEIVED_ITEM_HASHTYPE = keccak256(RECEIVED_ITEM_BYTES);
bytes32 public constant CONSIDERATION_HASHTYPE =
keccak256(abi.encodePacked(CONSIDERATION_BYTES, RECEIVED_ITEM_BYTES));
uint256 internal immutable _CHAIN_ID = block.chainid;
bytes32 internal immutable _DOMAIN_SEPARATOR;
/**
* @notice Constructor to deploy the contract.
*/
constructor() {
// Set the deployer as the controller.
_controller = msg.sender;
// Derive and set the domain separator.
_DOMAIN_SEPARATOR = _deriveDomainSeparator();
// Emit an event to signal a SIP-5 contract has been deployed.
emit SeaportCompatibleContractDeployed();
}
/**
* @dev Authorizes an order before any token fulfillments from any order have been executed by Seaport.
*
* @return authorizedOrderMagicValue The magic value that indicates a valid
* order.
*/
function authorizeOrder(
ZoneParameters calldata
) public view virtual override returns (bytes4 authorizedOrderMagicValue) {
return this.authorizeOrder.selector;
}
/**
* @notice Check if a given order including extraData is currently valid.
*
* @dev This function is called by Seaport whenever any extraData is
* provided by the caller.
*
* @return validOrderMagicValue A magic value indicating if the order is
* currently valid.
*/
function validateOrder(
ZoneParameters calldata zoneParameters
) public view virtual override returns (bytes4 validOrderMagicValue) {
// Check Zone parameters validity.
_assertValidZoneParameters();
// Put the extraData and orderHash on the stack for cheaper access.
bytes calldata extraData = zoneParameters.extraData;
bytes32 orderHash = zoneParameters.orderHash;
uint256 considerationLength;
// Declare a variable to hold the expiration.
uint64 expiration;
// Validate the extraData.
assembly {
// Get the length of the extraData.
let extraDataPtr := add(0x24, calldataload(Zone_extraData_cdPtr))
let extraDataLength := calldataload(extraDataPtr)
if iszero(eq(extraDataLength, InvalidExtraDataLength_epected_length)) {
// Store left-padded selector with push4, mem[28:32] = selector
mstore(0, InvalidExtraDataLength_error_selector)
mstore(InvalidExtraDataLength_error_orderHash_ptr, orderHash)
// revert(abi.encodeWithSignature(
// "InvalidExtraDataLength(bytes32)", orderHash)
// )
revert(0x1c, InvalidExtraDataLength_error_length)
}
// extraData bytes 0-1: SIP-6 version byte (MUST be 0x00)
let versionByte := shr(248, calldataload(add(extraDataPtr, 0x20)))
if iszero(eq(versionByte, 0x00)) {
// Store left-padded selector with push4, mem[28:32] = selector
mstore(0, InvalidSIP6Version_error_selector)
mstore(InvalidSIP6Version_error_orderHash_ptr, orderHash)
// revert(abi.encodeWithSignature(
// "InvalidSIP6Version(bytes32)", orderHash)
// )
revert(0x1c, InvalidSIP6Version_error_length)
}
// extraData bytes 93-94: Substandard #1 (MUST be 0x00)
let subStandardVersionByte := shr(
248,
calldataload(add(extraDataPtr, ExtraData_substandard_version_byte_offset))
)
if iszero(eq(subStandardVersionByte, 0x00)) {
// Store left-padded selector with push4, mem[28:32] = selector
mstore(0, InvalidSubstandardVersion_error_selector)
mstore(InvalidSubstandardVersion_error_orderHash_ptr, orderHash)
// revert(abi.encodeWithSignature(
// "InvalidSubstandardVersion(bytes32)", orderHash)
// )
revert(0x1c, InvalidSubstandardVersion_error_length)
}
// extraData bytes 21-29: expiration timestamp (uint64)
expiration := shr(192, calldataload(add(extraDataPtr, ExtraData_expiration_offset)))
// Revert if expired.
if lt(expiration, timestamp()) {
// Store left-padded selector with push4, mem[28:32] = selector
mstore(0, SignatureExpired_error_selector)
mstore(SignatureExpired_error_expiration_ptr, expiration)
mstore(SignatureExpired_error_orderHash_ptr, orderHash)
// revert(abi.encodeWithSignature(
// "SignatureExpired(uint256, bytes32)", expiration orderHash)
// )
revert(0x1c, SignatureExpired_error_length)
}
// // Get the length of the consideration array.
considerationLength := calldataload(add(0x24, calldataload(Zone_consideration_head_cdPtr)))
}
// extraData bytes 29-93: signature
// (strictly requires 64 byte compact sig, EIP-2098)
bytes calldata signature = extraData[29:93];
// extraData bytes 93-end: context (optional, variable length)
bytes calldata context = extraData[93:];
// Check the validity of the Substandard #1 extraData and get the
// expected fulfiller address.
address expectedFulfiller = _getExpectedFulfiller(orderHash);
// Check the validity of the Substandard #1 extraData and get the
// expected fulfiller address.
if (considerationLength > 0) {
_assertValidSubstandard(_deriveConsiderationHash(zoneParameters.consideration), orderHash);
}
// Derive the signedOrder hash.
bytes32 signedOrderHash = _deriveSignedOrderHash(
expectedFulfiller,
expiration,
orderHash,
context
);
// Derive the EIP-712 digest using the domain separator and signedOrder
// hash.
bytes32 digest = _deriveEIP712Digest(_domainSeparator(), signedOrderHash);
// Recover the signer address from the digest and signature.
address recoveredSigner = _recoverSigner(digest, signature);
// Revert if the signer is not active.
if (!_signers[recoveredSigner]) {
revert SignerNotActive(recoveredSigner, orderHash);
}
// Return the selector of validateOrder as the magic value.
validOrderMagicValue = ZoneInterfaceV16.validateOrder.selector;
}
/**
* @dev Returns Seaport metadata for this contract, returning the
* contract name and supported schemas.
*
* @return name The contract name
* @return schemas The supported SIPs
*/
function getSeaportMetadata()
external
view
override(SIP5Interface, ZoneInterfaceV16)
returns (string memory name, Schema[] memory schemas)
{
// Return the supported SIPs.
schemas = new Schema[](1);
schemas[0].id = 7;
// Get the SIP-7 information.
(
bytes32 domainSeparator,
string memory zoneName,
string memory apiEndpoint,
uint256[] memory substandards,
string memory documentationURI
) = _sip7Information();
// Return the zone name.
name = zoneName;
// Encode the SIP-7 information.
schemas[0].metadata = abi.encode(domainSeparator, apiEndpoint, substandards, documentationURI);
}
/**
* @notice The fallback function is used as a dispatcher for the
* `updateSigner`, `getActiveSigners` and `supportsInterface`
* functions.
*/
// prettier-ignore
fallback(bytes calldata) external payable returns (bytes memory output) {
// Get the function selector.
bytes4 selector = msg.sig;
if (selector == 0xf460590b) {
// updateSigner(address,bool)
// Get the signer, and active status.
address signer = abi.decode(msg.data[4:], (address));
bool active = abi.decode(msg.data[36:], (bool));
// Call to update the signer.
_updateSigner(signer, active);
} else if (selector == 0xa784b80c) {
// getActiveSigners()
// Call the internal function to get the active signers.
return abi.encode(_getActiveSigners());
} else if (selector == 0x01ffc9a7) {
// supportsInterface(bytes4)
// Get the interface ID.
bytes4 interfaceId = abi.decode(msg.data[4:], (bytes4));
// Call the internal function to determine if the interface is
// supported.
return abi.encode(_supportsInterface(interfaceId));
}
}
/**
* @notice Add or remove a signer to the zone.
* Only the controller can call this function.
*
* @param signer The signer address to add or remove.
*/
function _updateSigner(address signer, bool active) internal {
// Only the controller can call this function.
_assertCallerIsController();
// Add or remove the signer.
active ? _addSigner(signer) : _removeSigner(signer);
}
/**
* @notice Add a new signer to the zone.
* Only the controller or an active signer can call this function.
*
* @param signer The new signer address to add.
*/
function _addSigner(address signer) internal {
// Set the signer info.
_signers[signer] = true;
// Emit an event that the signer was added.
emit SignerAdded(signer);
}
/**
* @notice Remove an active signer from the zone.
* Only the controller or an active signer can call this function.
*
* @param signer The signer address to remove.
*/
function _removeSigner(address signer) internal {
// Set the signer's active status to false.
_signers[signer] = false;
// Emit an event that the signer was removed.
emit SignerRemoved(signer);
}
/**
* @notice Returns the active signers for the zone.
*
* @return signers The active signers.
*/
function _getActiveSigners() internal view returns (address[] memory signers) {
// Return the active signers for the zone by calling the controller.
signers = SignedZoneControllerInterface(_controller).getActiveSigners(address(this));
}
/**
* @notice Returns whether the interface is supported.
*
* @param interfaceId The interface id to check against.
*/
function _supportsInterface(bytes4 interfaceId) internal pure returns (bool supportsInterface) {
// Determine if the interface is supported.
supportsInterface =
interfaceId == type(SIP5Interface).interfaceId || // SIP-5
interfaceId == type(ZoneInterfaceV16).interfaceId || // ZoneInterface
interfaceId == 0x01ffc9a7; // ERC-165
}
/**
* @notice Internal call to return the signing information, substandards,
* and documentation about the zone.
*
* @return domainSeparator The domain separator used for signing.
* @return zoneName The zone name.
* @return apiEndpoint The API endpoint for the zone.
* @return substandards The substandards supported by the zone.
* @return documentationURI The documentation URI for the zone.
*/
function _sip7Information()
internal
view
returns (
bytes32 domainSeparator,
string memory zoneName,
string memory apiEndpoint,
uint256[] memory substandards,
string memory documentationURI
)
{
// Return the SIP-7 information.
domainSeparator = _domainSeparator();
// Get the SIP-7 information from the controller.
(, zoneName, apiEndpoint, substandards, documentationURI) = SignedZoneControllerInterface(
_controller
).getAdditionalZoneInformation(address(this));
}
/**
* @dev Derive the signedOrder hash from the orderHash and expiration.
*
* @param fulfiller The expected fulfiller address.
* @param expiration The signature expiration timestamp.
* @param orderHash The order hash.
* @param context The optional variable-length context.
*
* @return signedOrderHash The signedOrder hash.
*
*/
function _deriveSignedOrderHash(
address fulfiller,
uint64 expiration,
bytes32 orderHash,
bytes calldata context
) internal view returns (bytes32 signedOrderHash) {
// Derive the signed order hash.
signedOrderHash = keccak256(
abi.encode(_SIGNED_ORDER_TYPEHASH, fulfiller, expiration, orderHash, keccak256(context))
);
}
/**
* @dev Internal view function to return the signer of a signature.
*
* @param digest The digest to verify the signature against.
* @param signature A signature from the signer indicating that the order
* has been approved.
*
* @return recoveredSigner The recovered signer.
*/
function _recoverSigner(
bytes32 digest,
bytes memory signature
) internal view returns (address recoveredSigner) {
// Utilize assembly to perform optimized signature verification check.
assembly {
// Ensure that first word of scratch space is empty.
mstore(0, 0)
// Declare value for v signature parameter.
let v
// Get the length of the signature.
let signatureLength := mload(signature)
// Get the pointer to the value preceding the signature length.
// This will be used for temporary memory overrides - either the
// signature head for isValidSignature or the digest for ecrecover.
let wordBeforeSignaturePtr := sub(signature, OneWord)
// Cache the current value behind the signature to restore it later.
let cachedWordBeforeSignature := mload(wordBeforeSignaturePtr)
// Declare lenDiff + recoveredSigner scope to manage stack pressure.
{
// Take the difference between the max ECDSA signature length
// and the actual signature length. Overflow desired for any
// values > 65. If the diff is not 0 or 1, it is not a valid
// ECDSA signature - move on to EIP1271 check.
let lenDiff := sub(ECDSA_MaxLength, signatureLength)
// If diff is 0 or 1, it may be an ECDSA signature.
// Try to recover signer.
if iszero(gt(lenDiff, 1)) {
// Read the signature `s` value.
let originalSignatureS := mload(add(signature, ECDSA_signature_s_offset))
// Read the first byte of the word after `s`. If the
// signature is 65 bytes, this will be the real `v` value.
// If not, it will need to be modified - doing it this way
// saves an extra condition.
v := byte(0, mload(add(signature, ECDSA_signature_v_offset)))
// If lenDiff is 1, parse 64-byte signature as ECDSA.
if lenDiff {
// Extract yParity from highest bit of vs and add 27 to
// get v.
v := add(shr(MaxUint8, originalSignatureS), Signature_lower_v)
// Extract canonical s from vs, all but the highest bit.
// Temporarily overwrite the original `s` value in the
// signature.
mstore(
add(signature, ECDSA_signature_s_offset),
and(originalSignatureS, EIP2098_allButHighestBitMask)
)
}
// Temporarily overwrite the signature length with `v` to
// conform to the expected input for ecrecover.
mstore(signature, v)
// Temporarily overwrite the word before the length with
// `digest` to conform to the expected input for ecrecover.
mstore(wordBeforeSignaturePtr, digest)
// Attempt to recover the signer for the given signature. Do
// not check the call status as ecrecover will return a null
// address if the signature is invalid.
pop(
staticcall(
gas(),
Ecrecover_precompile, // Call ecrecover precompile.
wordBeforeSignaturePtr, // Use data memory location.
Ecrecover_args_size, // Size of digest, v, r, and s.
0, // Write result to scratch space.
OneWord // Provide size of returned result.
)
)
// Restore cached word before signature.
mstore(wordBeforeSignaturePtr, cachedWordBeforeSignature)
// Restore cached signature length.
mstore(signature, signatureLength)
// Restore cached signature `s` value.
mstore(add(signature, ECDSA_signature_s_offset), originalSignatureS)
// Read the recovered signer from the buffer given as return
// space for ecrecover.
recoveredSigner := mload(0)
}
}
// Restore the cached values overwritten by selector, digest and
// signature head.
mstore(wordBeforeSignaturePtr, cachedWordBeforeSignature)
}
}
/**
* @dev Internal view function to get the EIP-712 domain separator. If the
* chainId matches the chainId set on deployment, the cached domain
* separator will be returned; otherwise, it will be derived from
* scratch.
*
* @return The domain separator.
*/
function _domainSeparator() internal view returns (bytes32) {
// prettier-ignore
return block.chainid == _CHAIN_ID
? _DOMAIN_SEPARATOR
: _deriveDomainSeparator();
}
/**
* @dev Internal view function to derive the EIP-712 domain separator.
*
* @return domainSeparator The derived domain separator.
*/
function _deriveDomainSeparator() internal view returns (bytes32 domainSeparator) {
bytes32 typehash = _EIP_712_DOMAIN_TYPEHASH;
bytes32 nameHash = _NAME_HASH;
bytes32 versionHash = _VERSION_HASH;
// Leverage scratch space and other memory to perform an efficient hash.
assembly {
// Retrieve the free memory pointer; it will be replaced afterwards.
let freeMemoryPointer := mload(FreeMemoryPointerSlot)
// Retrieve value at 0x80; it will also be replaced afterwards.
let slot0x80 := mload(Slot0x80)
// Place typehash, name hash, and version hash at start of memory.
mstore(0, typehash)
mstore(OneWord, nameHash)
mstore(TwoWords, versionHash)
// Place chainId in the next memory location.
mstore(ThreeWords, chainid())
// Place the address of this contract in the next memory location.
mstore(FourWords, address())
// Hash relevant region of memory to derive the domain separator.
domainSeparator := keccak256(0, FiveWords)
// Restore the free memory pointer.
mstore(FreeMemoryPointerSlot, freeMemoryPointer)
// Restore the zero slot to zero.
mstore(ZeroSlot, 0)
// Restore the value at 0x80.
mstore(Slot0x80, slot0x80)
}
}
/**
* @dev Internal pure function to efficiently derive an digest to sign for
* an order in accordance with EIP-712.
*
* @param domainSeparator The domain separator.
* @param signedOrderHash The signedOrder hash.
*
* @return digest The digest hash.
*/
function _deriveEIP712Digest(
bytes32 domainSeparator,
bytes32 signedOrderHash
) internal pure returns (bytes32 digest) {
// Leverage scratch space to perform an efficient hash.
assembly {
// Place the EIP-712 prefix at the start of scratch space.
mstore(0, EIP_712_PREFIX)
// Place the domain separator in the next region of scratch space.
mstore(EIP712_DomainSeparator_offset, domainSeparator)
// Place the signed order hash in scratch space, spilling into the
// first two bytes of the free memory pointer — this should never be
// set as memory cannot be expanded to that size, and will be
// zeroed out after the hash is performed.
mstore(EIP712_SignedOrderHash_offset, signedOrderHash)
// Hash the relevant region
digest := keccak256(0, EIP712_DigestPayload_size)
// Clear out the dirtied bits in the memory pointer.
mstore(EIP712_SignedOrderHash_offset, 0)
}
}
/**
* @dev Private view function to revert if the caller is not the
* controller.
*/
function _assertCallerIsController() internal view {
// Get the controller address to use in the assembly block.
address controller = _controller;
assembly {
// Revert if the caller is not the controller.
if iszero(eq(caller(), controller)) {
// Store left-padded selector with push4, mem[28:32] = selector
mstore(0, InvalidController_error_selector)
// revert(abi.encodeWithSignature(
// "InvalidController()")
// )
revert(0x1c, InvalidController_error_length)
}
}
}
/**
* @dev Internal pure function to validate calldata offsets for the
* dyanamic type in ZoneParameters. This ensures that functions using
* the calldata object normally will be using the same data as the
* assembly functions and that values that are bound to a given range
* are within that range.
*/
function _assertValidZoneParameters() internal pure {
// Utilize assembly in order to read offset data directly from calldata.
assembly {
/*
* Checks:
* 1. Zone parameters struct offset == 0x20
*/
// Zone parameters at calldata 0x04 must have offset of 0x20.
if iszero(eq(calldataload(Zone_parameters_cdPtr), Zone_parameters_ptr)) {
// Store left-padded selector with push4 (reduces bytecode), mem[28:32] = selector
mstore(0, InvalidZoneParameterEncoding_error_selector)
// revert(abi.encodeWithSignature("InvalidZoneParameterEncoding()"))
revert(0x1c, InvalidZoneParameterEncoding_error_length)
}
}
}
/**
* @dev Internal pure function to ensure that the context argument for the
* supplied extra data follows the substandard #1 format. Returns the
* expected fulfiller of the order for deriving the signed order hash.
*
* @param orderHash The order hash.
*
* @return expectedFulfiller The expected fulfiller of the order.
*/
function _getExpectedFulfiller(
bytes32 orderHash
) internal pure returns (address expectedFulfiller) {
// Revert if the expected fulfiller is not the zero address and does
// not match the actual fulfiller
assembly {
// Get the actual fulfiller.
let actualFulfiller := calldataload(Zone_parameters_fulfiller_cdPtr)
let extraDataPtr := calldataload(Zone_extraData_cdPtr)
// Get the expected fulfiller.
expectedFulfiller := shr(96, calldataload(add(expectedFulfiller_offset, extraDataPtr)))
// Revert if expected fulfiller is not the zero address and does
// not match the actual fulfiller.
if and(iszero(iszero(expectedFulfiller)), iszero(eq(expectedFulfiller, actualFulfiller))) {
// Store left-padded selector with push4, mem[28:32] = selector
mstore(0, InvalidFulfiller_error_selector)
mstore(InvalidFulfiller_error_expectedFulfiller_ptr, expectedFulfiller)
mstore(InvalidFulfiller_error_actualFulfiller_ptr, actualFulfiller)
mstore(InvalidFulfiller_error_orderHash_ptr, orderHash)
// revert(abi.encodeWithSignature(
// "InvalidFulfiller(address,address,bytes32)", expectedFulfiller, actualFulfiller, orderHash)
// )
revert(0x1c, InvalidFulfiller_error_length)
}
}
}
/**
* @dev Internal pure function to ensure that the context argument for the
* supplied extra data follows the substandard #1 format. Returns the
* expected fulfiller of the order for deriving the signed order hash.
*
*/
function _assertValidSubstandard(bytes32 considerationHash, bytes32 orderHash) internal pure {
// identifier does not match the actual consideration.
assembly {
let extraDataPtr := calldataload(Zone_extraData_cdPtr)
let considerationPtr := calldataload(Zone_consideration_head_cdPtr)
// Get the actual consideration.
let actualConsideration := calldataload(add(actualConsideration_offset, considerationPtr))
// Get the expected consideration.
let expectedConsiderationHash := calldataload(
add(expectedConsideration_offset, extraDataPtr) //TODO rename
)
// Revert if expected consideration item does not match the actual
// consideration item.
if iszero(eq(considerationHash, expectedConsiderationHash)) {
// Store left-padded selector with push4, mem[28:32] = selector
mstore(0, InvalidConsideration_error_selector)
mstore(InvalidConsideration_error_expectedConsideration_ptr, expectedConsiderationHash)
mstore(InvalidConsideration_error_actualConsideration_ptr, actualConsideration)
mstore(InvalidConsideration_error_orderHash_ptr, orderHash)
// revert(abi.encodeWithSignature(
// "InvalidConsideration(uint256,uint256,bytes32)", expectedConsideration, actualConsideration, orderHash)
// )
revert(0x1c, InvalidConsideration_error_length)
}
}
}
/// @dev Calculates consideration hash
function _deriveConsiderationHash(
ReceivedItem[] calldata consideration
) internal pure returns (bytes32) {
uint256 numberOfItems = consideration.length;
bytes32[] memory considerationHashes = new bytes32[](numberOfItems);
for (uint256 i; i < numberOfItems; ) {
considerationHashes[i] = _deriveReceivedItemHash(consideration[i]);
unchecked {
++i;
}
}
return
keccak256(
abi.encode(CONSIDERATION_HASHTYPE, keccak256(abi.encodePacked(considerationHashes)))
);
}
/// @dev Calculates consideration item hash
function _deriveReceivedItemHash(
ReceivedItem calldata receivedItem
) internal pure returns (bytes32) {
return
keccak256(
abi.encode(
RECEIVED_ITEM_HASHTYPE,
receivedItem.itemType,
receivedItem.token,
receivedItem.identifier,
receivedItem.amount,
receivedItem.recipient
)
);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
contract ReservoirErc1155 is ERC1155, Ownable {
using Strings for uint256;
// Fields
address private royaltyRecipient;
uint256 private royaltyBps;
string public contractURI;
// Constructor
constructor(
address _owner,
string memory _uri,
string memory _contractURI,
address _royaltyRecipient,
uint256 _royaltyBps
) ERC1155(_uri) {
contractURI = _contractURI;
royaltyRecipient = _royaltyRecipient;
royaltyBps = _royaltyBps;
_transferOwnership(_owner);
}
// Public methods
function mint(uint256 tokenId, uint256 amount) external {
_mint(msg.sender, tokenId, amount, "");
}
function uri(uint256 tokenId) public view virtual override returns (string memory) {
return string(abi.encodePacked(super.uri(tokenId), tokenId.toString()));
}
// Owner methods
function updateURI(string memory _uri) external onlyOwner {
_setURI(_uri);
}
function updateContractURI(string memory _contractURI) external onlyOwner {
contractURI = _contractURI;
}
function updateRoyalty(address _royaltyRecipient, uint256 _royaltyBps) external onlyOwner {
royaltyRecipient = _royaltyRecipient;
royaltyBps = _royaltyBps;
}
// EIP2981
function royaltyInfo(
uint256,
uint256 price
) external view returns (address receiver, uint256 amount) {
receiver = royaltyRecipient;
amount = (price * royaltyBps) / 10000;
}
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == 0x2a55205a || super.supportsInterface(interfaceId);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract ReservoirErc721 is ERC721, Ownable {
// Fields
uint256 private nextTokenId;
string private baseTokenURI;
address private royaltyRecipient;
uint256 private royaltyBps;
string public contractURI;
// Constructor
constructor(
address _owner,
string memory _baseTokenURI,
string memory _contractURI,
address _royaltyRecipient,
uint256 _royaltyBps
) ERC721("Reservoir", "RSV") {
baseTokenURI = _baseTokenURI;
contractURI = _contractURI;
royaltyRecipient = _royaltyRecipient;
royaltyBps = _royaltyBps;
_transferOwnership(_owner);
}
// Public methods
function mint() external {
_mint(msg.sender, nextTokenId++);
}
// Owner methods
function updateBaseTokenURI(string memory _baseTokenURI) external onlyOwner {
baseTokenURI = _baseTokenURI;
}
function updateContractURI(string memory _contractURI) external onlyOwner {
contractURI = _contractURI;
}
function updateRoyalty(address _royaltyRecipient, uint256 _royaltyBps) external onlyOwner {
royaltyRecipient = _royaltyRecipient;
royaltyBps = _royaltyBps;
}
// EIP2981
function royaltyInfo(
uint256,
uint256 price
) external view returns (address receiver, uint256 amount) {
receiver = royaltyRecipient;
amount = (price * royaltyBps) / 10000;
}
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == 0x2a55205a || super.supportsInterface(interfaceId);
}
// Internal methods
function _baseURI() internal view override returns (string memory) {
return baseTokenURI;
}
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import {IPropertyValidator} from "./interfaces/IPropertyValidator.sol";
// Credits: https://github.com/mzhu25/0x-property-validators
contract BitVectorValidator is IPropertyValidator {
function validateProperty(
address, // tokenAddress
uint256 tokenId,
bytes calldata propertyData
) external pure {
// tokenId < propertyData.length * 8
require(tokenId < propertyData.length << 3, "Bit vector length exceeded");
// Bit corresponding to tokenId must be set
require(
uint8(propertyData[tokenId >> 3]) & (0x80 >> (tokenId & 7)) != 0,
"Token id not in bit vector"
);
}
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
interface IPropertyValidator {
function validateProperty(
address tokenAddress,
uint256 tokenId,
bytes calldata propertyData
) external view;
}// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import {IPropertyValidator} from "./interfaces/IPropertyValidator.sol";
// Credits: https://github.com/mzhu25/0x-property-validators
contract PackedListValidator is IPropertyValidator {
function validateProperty(
address, // tokenAddress
uint256 tokenId,
bytes calldata propertyData
)
external
pure
{
(uint256 bytesPerTokenId, bytes memory packedList) = abi.decode(
propertyData,
(uint256, bytes)
);
require(
bytesPerTokenId != 0 && bytesPerTokenId <= 32,
"Invalid number of bytes per token id"
);
// Masks the lower `bytesPerTokenId` bytes of a word
// So if `bytesPerTokenId` == 1, then bitmask = 0xff
// if `bytesPerTokenId` == 2, then bitmask = 0xffff, etc.
uint256 bitMask = ~(type(uint256).max << (bytesPerTokenId << 3));
assembly {
// Binary search for given token id
let left := 1
// right = number of tokenIds in the list
let right := div(mload(packedList), bytesPerTokenId)
// while(left < right)
for {} lt(left, right) {} {
// mid = (left + right) / 2
let mid := shr(1, add(left, right))
// more or less equivalent to:
// value = list[index]
let offset := add(packedList, mul(mid, bytesPerTokenId))
let value := and(mload(offset), bitMask)
// if (value < tokenId) {
// left = mid + 1;
// continue;
// }
if lt(value, tokenId) {
left := add(mid, 1)
continue
}
// if (value > tokenId) {
// right = mid;
// continue;
// }
if gt(value, tokenId) {
right := mid
continue
}
// if (value == tokenId) { return; }
stop()
}
// At this point left == right; check if list[left] == tokenId
let offset := add(packedList, mul(left, bytesPerTokenId))
let value := and(mload(offset), bitMask)
if eq(value, tokenId) {
stop()
}
}
revert("Token id not in packed list");
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IPropertyValidator} from "./interfaces/IPropertyValidator.sol";
contract TokenRangeValidator is IPropertyValidator {
function validateProperty(
address, // tokenAddress
uint256 tokenId,
bytes calldata propertyData
) external pure {
(uint256 startTokenId, uint256 endTokenId) = abi.decode(propertyData, (uint256, uint256));
require(startTokenId <= tokenId && tokenId <= endTokenId, "Token id out of range");
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Library for converting between addresses and bytes32 values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/Bytes32AddressLib.sol)
library Bytes32AddressLib {
function fromLast20Bytes(bytes32 bytesValue) internal pure returns (address) {
return address(uint160(uint256(bytesValue)));
}
function fillLast12Bytes(address addressValue) internal pure returns (bytes32) {
return bytes32(bytes20(addressValue));
}
}// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {Bytes32AddressLib} from "./Bytes32AddressLib.sol";
/// @notice Deploy to deterministic addresses without an initcode factor.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/CREATE3.sol)
/// @author Modified from 0xSequence (https://github.com/0xSequence/create3/blob/master/contracts/Create3.sol)
library CREATE3 {
using Bytes32AddressLib for bytes32;
//--------------------------------------------------------------------------------//
// Opcode | Opcode + Arguments | Description | Stack View //
//--------------------------------------------------------------------------------//
// 0x36 | 0x36 | CALLDATASIZE | size //
// 0x3d | 0x3d | RETURNDATASIZE | 0 size //
// 0x3d | 0x3d | RETURNDATASIZE | 0 0 size //
// 0x37 | 0x37 | CALLDATACOPY | //
// 0x36 | 0x36 | CALLDATASIZE | size //
// 0x3d | 0x3d | RETURNDATASIZE | 0 size //
// 0x34 | 0x34 | CALLVALUE | value 0 size //
// 0xf0 | 0xf0 | CREATE | newContract //
//--------------------------------------------------------------------------------//
// Opcode | Opcode + Arguments | Description | Stack View //
//--------------------------------------------------------------------------------//
// 0x67 | 0x67XXXXXXXXXXXXXXXX | PUSH8 bytecode | bytecode //
// 0x3d | 0x3d | RETURNDATASIZE | 0 bytecode //
// 0x52 | 0x52 | MSTORE | //
// 0x60 | 0x6008 | PUSH1 08 | 8 //
// 0x60 | 0x6018 | PUSH1 18 | 24 8 //
// 0xf3 | 0xf3 | RETURN | //
//--------------------------------------------------------------------------------//
bytes internal constant PROXY_BYTECODE = hex"67_36_3d_3d_37_36_3d_34_f0_3d_52_60_08_60_18_f3";
bytes32 internal constant PROXY_BYTECODE_HASH = keccak256(PROXY_BYTECODE);
function deploy(
bytes32 salt,
bytes memory creationCode,
uint256 value
) internal returns (address deployed) {
bytes memory proxyChildBytecode = PROXY_BYTECODE;
address proxy;
/// @solidity memory-safe-assembly
assembly {
// Deploy a new contract with our pre-made bytecode via CREATE2.
// We start 32 bytes into the code to avoid copying the byte length.
proxy := create2(0, add(proxyChildBytecode, 32), mload(proxyChildBytecode), salt)
}
require(proxy != address(0), "DEPLOYMENT_FAILED");
deployed = getDeployed(salt);
(bool success, ) = proxy.call{value: value}(creationCode);
require(success && deployed.code.length != 0, "INITIALIZATION_FAILED");
}
function getDeployed(bytes32 salt) internal view returns (address) {
address proxy = keccak256(
abi.encodePacked(
// Prefix:
bytes1(0xFF),
// Creator:
address(this),
// Salt:
salt,
// Bytecode hash:
PROXY_BYTECODE_HASH
)
).fromLast20Bytes();
return
keccak256(
abi.encodePacked(
// 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01)
// 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex)
hex"d6_94",
proxy,
hex"01" // Nonce of the proxy contract (1)
)
).fromLast20Bytes();
}
}{
"viaIR": true,
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"libraries": {}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"router","type":"address"},{"internalType":"address","name":"exchange","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InvalidParams","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"UnsuccessfulCall","type":"error"},{"inputs":[],"name":"UnsuccessfulFill","type":"error"},{"inputs":[],"name":"UnsuccessfulPayment","type":"error"},{"inputs":[],"name":"WrongParams","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"CallExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"EXCHANGE","outputs":[{"internalType":"contract ISeaport","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"offerer","type":"address"},{"internalType":"address","name":"zone","type":"address"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"}],"internalType":"struct ISeaport.OfferItem[]","name":"offer","type":"tuple[]"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct ISeaport.ConsiderationItem[]","name":"consideration","type":"tuple[]"},{"internalType":"enum ISeaport.OrderType","name":"orderType","type":"uint8"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"bytes32","name":"zoneHash","type":"bytes32"},{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"bytes32","name":"conduitKey","type":"bytes32"},{"internalType":"uint256","name":"totalOriginalConsiderationItems","type":"uint256"}],"internalType":"struct ISeaport.OrderParameters","name":"parameters","type":"tuple"},{"internalType":"uint120","name":"numerator","type":"uint120"},{"internalType":"uint120","name":"denominator","type":"uint120"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"internalType":"struct ISeaport.AdvancedOrder","name":"order","type":"tuple"},{"components":[{"internalType":"uint256","name":"orderIndex","type":"uint256"},{"internalType":"enum ISeaport.Side","name":"side","type":"uint8"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"uint256","name":"identifier","type":"uint256"},{"internalType":"bytes32[]","name":"criteriaProof","type":"bytes32[]"}],"internalType":"struct ISeaport.CriteriaResolver[]","name":"criteriaResolvers","type":"tuple[]"},{"components":[{"internalType":"address","name":"fillTo","type":"address"},{"internalType":"address","name":"refundTo","type":"address"},{"internalType":"bool","name":"revertIfIncomplete","type":"bool"}],"internalType":"struct BaseExchangeModule.OfferParams","name":"params","type":"tuple"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct BaseExchangeModule.Fee[]","name":"fees","type":"tuple[]"}],"name":"acceptERC1155Offer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"offerer","type":"address"},{"internalType":"address","name":"zone","type":"address"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"}],"internalType":"struct ISeaport.OfferItem[]","name":"offer","type":"tuple[]"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct ISeaport.ConsiderationItem[]","name":"consideration","type":"tuple[]"},{"internalType":"enum ISeaport.OrderType","name":"orderType","type":"uint8"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"bytes32","name":"zoneHash","type":"bytes32"},{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"bytes32","name":"conduitKey","type":"bytes32"},{"internalType":"uint256","name":"totalOriginalConsiderationItems","type":"uint256"}],"internalType":"struct ISeaport.OrderParameters","name":"parameters","type":"tuple"},{"internalType":"uint120","name":"numerator","type":"uint120"},{"internalType":"uint120","name":"denominator","type":"uint120"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"internalType":"struct ISeaport.AdvancedOrder","name":"order","type":"tuple"},{"components":[{"internalType":"address","name":"fillTo","type":"address"},{"internalType":"address","name":"refundTo","type":"address"},{"internalType":"bool","name":"revertIfIncomplete","type":"bool"},{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct BaseExchangeModule.ERC20ListingParams","name":"params","type":"tuple"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct BaseExchangeModule.Fee[]","name":"fees","type":"tuple[]"}],"name":"acceptERC20Listing","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"offerer","type":"address"},{"internalType":"address","name":"zone","type":"address"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"}],"internalType":"struct ISeaport.OfferItem[]","name":"offer","type":"tuple[]"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct ISeaport.ConsiderationItem[]","name":"consideration","type":"tuple[]"},{"internalType":"enum ISeaport.OrderType","name":"orderType","type":"uint8"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"bytes32","name":"zoneHash","type":"bytes32"},{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"bytes32","name":"conduitKey","type":"bytes32"},{"internalType":"uint256","name":"totalOriginalConsiderationItems","type":"uint256"}],"internalType":"struct ISeaport.OrderParameters","name":"parameters","type":"tuple"},{"internalType":"uint120","name":"numerator","type":"uint120"},{"internalType":"uint120","name":"denominator","type":"uint120"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"internalType":"struct ISeaport.AdvancedOrder[]","name":"orders","type":"tuple[]"},{"components":[{"internalType":"address","name":"fillTo","type":"address"},{"internalType":"address","name":"refundTo","type":"address"},{"internalType":"bool","name":"revertIfIncomplete","type":"bool"},{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct BaseExchangeModule.ERC20ListingParams","name":"params","type":"tuple"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct BaseExchangeModule.Fee[]","name":"fees","type":"tuple[]"}],"name":"acceptERC20Listings","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"components":[{"internalType":"address","name":"offerer","type":"address"},{"internalType":"address","name":"zone","type":"address"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"}],"internalType":"struct ISeaport.OfferItem[]","name":"offer","type":"tuple[]"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct ISeaport.ConsiderationItem[]","name":"consideration","type":"tuple[]"},{"internalType":"enum ISeaport.OrderType","name":"orderType","type":"uint8"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"bytes32","name":"zoneHash","type":"bytes32"},{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"bytes32","name":"conduitKey","type":"bytes32"},{"internalType":"uint256","name":"totalOriginalConsiderationItems","type":"uint256"}],"internalType":"struct ISeaport.OrderParameters","name":"parameters","type":"tuple"},{"internalType":"uint120","name":"numerator","type":"uint120"},{"internalType":"uint120","name":"denominator","type":"uint120"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"internalType":"struct ISeaport.AdvancedOrder[]","name":"orders","type":"tuple[]"},{"components":[{"components":[{"internalType":"uint256","name":"orderIndex","type":"uint256"},{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"internalType":"struct ISeaport.FulfillmentComponent[]","name":"offerComponents","type":"tuple[]"},{"components":[{"internalType":"uint256","name":"orderIndex","type":"uint256"},{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"internalType":"struct ISeaport.FulfillmentComponent[]","name":"considerationComponents","type":"tuple[]"}],"internalType":"struct ISeaport.Fulfillment[]","name":"fulfillments","type":"tuple[]"},{"internalType":"uint256","name":"price","type":"uint256"}],"internalType":"struct SeaportV16Module.SeaportPrivateListingWithPrice[]","name":"orders","type":"tuple[]"},{"components":[{"internalType":"address","name":"fillTo","type":"address"},{"internalType":"address","name":"refundTo","type":"address"},{"internalType":"bool","name":"revertIfIncomplete","type":"bool"},{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct BaseExchangeModule.ERC20ListingParams","name":"params","type":"tuple"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct BaseExchangeModule.Fee[]","name":"fees","type":"tuple[]"}],"name":"acceptERC20PrivateListings","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"offerer","type":"address"},{"internalType":"address","name":"zone","type":"address"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"}],"internalType":"struct ISeaport.OfferItem[]","name":"offer","type":"tuple[]"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct ISeaport.ConsiderationItem[]","name":"consideration","type":"tuple[]"},{"internalType":"enum ISeaport.OrderType","name":"orderType","type":"uint8"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"bytes32","name":"zoneHash","type":"bytes32"},{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"bytes32","name":"conduitKey","type":"bytes32"},{"internalType":"uint256","name":"totalOriginalConsiderationItems","type":"uint256"}],"internalType":"struct ISeaport.OrderParameters","name":"parameters","type":"tuple"},{"internalType":"uint120","name":"numerator","type":"uint120"},{"internalType":"uint120","name":"denominator","type":"uint120"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"internalType":"struct ISeaport.AdvancedOrder","name":"order","type":"tuple"},{"components":[{"internalType":"uint256","name":"orderIndex","type":"uint256"},{"internalType":"enum ISeaport.Side","name":"side","type":"uint8"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"uint256","name":"identifier","type":"uint256"},{"internalType":"bytes32[]","name":"criteriaProof","type":"bytes32[]"}],"internalType":"struct ISeaport.CriteriaResolver[]","name":"criteriaResolvers","type":"tuple[]"},{"components":[{"internalType":"address","name":"fillTo","type":"address"},{"internalType":"address","name":"refundTo","type":"address"},{"internalType":"bool","name":"revertIfIncomplete","type":"bool"}],"internalType":"struct BaseExchangeModule.OfferParams","name":"params","type":"tuple"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct BaseExchangeModule.Fee[]","name":"fees","type":"tuple[]"}],"name":"acceptERC721Offer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"offerer","type":"address"},{"internalType":"address","name":"zone","type":"address"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"}],"internalType":"struct ISeaport.OfferItem[]","name":"offer","type":"tuple[]"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct ISeaport.ConsiderationItem[]","name":"consideration","type":"tuple[]"},{"internalType":"enum ISeaport.OrderType","name":"orderType","type":"uint8"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"bytes32","name":"zoneHash","type":"bytes32"},{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"bytes32","name":"conduitKey","type":"bytes32"},{"internalType":"uint256","name":"totalOriginalConsiderationItems","type":"uint256"}],"internalType":"struct ISeaport.OrderParameters","name":"parameters","type":"tuple"},{"internalType":"uint120","name":"numerator","type":"uint120"},{"internalType":"uint120","name":"denominator","type":"uint120"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"internalType":"struct ISeaport.AdvancedOrder","name":"order","type":"tuple"},{"components":[{"internalType":"address","name":"fillTo","type":"address"},{"internalType":"address","name":"refundTo","type":"address"},{"internalType":"bool","name":"revertIfIncomplete","type":"bool"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct BaseExchangeModule.ETHListingParams","name":"params","type":"tuple"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct BaseExchangeModule.Fee[]","name":"fees","type":"tuple[]"}],"name":"acceptETHListing","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"components":[{"components":[{"internalType":"address","name":"offerer","type":"address"},{"internalType":"address","name":"zone","type":"address"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"}],"internalType":"struct ISeaport.OfferItem[]","name":"offer","type":"tuple[]"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct ISeaport.ConsiderationItem[]","name":"consideration","type":"tuple[]"},{"internalType":"enum ISeaport.OrderType","name":"orderType","type":"uint8"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"bytes32","name":"zoneHash","type":"bytes32"},{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"bytes32","name":"conduitKey","type":"bytes32"},{"internalType":"uint256","name":"totalOriginalConsiderationItems","type":"uint256"}],"internalType":"struct ISeaport.OrderParameters","name":"parameters","type":"tuple"},{"internalType":"uint120","name":"numerator","type":"uint120"},{"internalType":"uint120","name":"denominator","type":"uint120"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"internalType":"struct ISeaport.AdvancedOrder","name":"order","type":"tuple"},{"internalType":"uint256","name":"price","type":"uint256"}],"internalType":"struct SeaportV16Module.SeaportETHListingWithPrice[]","name":"orders","type":"tuple[]"},{"components":[{"internalType":"address","name":"fillTo","type":"address"},{"internalType":"address","name":"refundTo","type":"address"},{"internalType":"bool","name":"revertIfIncomplete","type":"bool"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct BaseExchangeModule.ETHListingParams","name":"params","type":"tuple"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct BaseExchangeModule.Fee[]","name":"fees","type":"tuple[]"}],"name":"acceptETHListings","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"components":[{"components":[{"internalType":"address","name":"offerer","type":"address"},{"internalType":"address","name":"zone","type":"address"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"}],"internalType":"struct ISeaport.OfferItem[]","name":"offer","type":"tuple[]"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct ISeaport.ConsiderationItem[]","name":"consideration","type":"tuple[]"},{"internalType":"enum ISeaport.OrderType","name":"orderType","type":"uint8"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"bytes32","name":"zoneHash","type":"bytes32"},{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"bytes32","name":"conduitKey","type":"bytes32"},{"internalType":"uint256","name":"totalOriginalConsiderationItems","type":"uint256"}],"internalType":"struct ISeaport.OrderParameters","name":"parameters","type":"tuple"},{"internalType":"uint120","name":"numerator","type":"uint120"},{"internalType":"uint120","name":"denominator","type":"uint120"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"internalType":"struct ISeaport.AdvancedOrder[]","name":"orders","type":"tuple[]"},{"components":[{"components":[{"internalType":"uint256","name":"orderIndex","type":"uint256"},{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"internalType":"struct ISeaport.FulfillmentComponent[]","name":"offerComponents","type":"tuple[]"},{"components":[{"internalType":"uint256","name":"orderIndex","type":"uint256"},{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"internalType":"struct ISeaport.FulfillmentComponent[]","name":"considerationComponents","type":"tuple[]"}],"internalType":"struct ISeaport.Fulfillment[]","name":"fulfillments","type":"tuple[]"},{"internalType":"uint256","name":"price","type":"uint256"}],"internalType":"struct SeaportV16Module.SeaportPrivateListingWithPrice[]","name":"orders","type":"tuple[]"},{"components":[{"internalType":"address","name":"fillTo","type":"address"},{"internalType":"address","name":"refundTo","type":"address"},{"internalType":"bool","name":"revertIfIncomplete","type":"bool"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct BaseExchangeModule.ETHListingParams","name":"params","type":"tuple"},{"components":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct BaseExchangeModule.Fee[]","name":"fees","type":"tuple[]"}],"name":"acceptETHPrivateListings","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"claimOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"isValidSignature","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address[]","name":"targets","type":"address[]"},{"internalType":"bytes[]","name":"data","type":"bytes[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"}],"name":"makeCalls","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"address","name":"offerer","type":"address"},{"internalType":"address","name":"zone","type":"address"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"}],"internalType":"struct ISeaport.OfferItem[]","name":"offer","type":"tuple[]"},{"components":[{"internalType":"enum ISeaport.ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifierOrCriteria","type":"uint256"},{"internalType":"uint256","name":"startAmount","type":"uint256"},{"internalType":"uint256","name":"endAmount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct ISeaport.ConsiderationItem[]","name":"consideration","type":"tuple[]"},{"internalType":"enum ISeaport.OrderType","name":"orderType","type":"uint8"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"bytes32","name":"zoneHash","type":"bytes32"},{"internalType":"uint256","name":"salt","type":"uint256"},{"internalType":"bytes32","name":"conduitKey","type":"bytes32"},{"internalType":"uint256","name":"totalOriginalConsiderationItems","type":"uint256"}],"internalType":"struct ISeaport.OrderParameters","name":"parameters","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"internalType":"struct ISeaport.Order[]","name":"orders","type":"tuple[]"},{"components":[{"components":[{"internalType":"uint256","name":"orderIndex","type":"uint256"},{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"internalType":"struct ISeaport.FulfillmentComponent[]","name":"offerComponents","type":"tuple[]"},{"components":[{"internalType":"uint256","name":"orderIndex","type":"uint256"},{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"internalType":"struct ISeaport.FulfillmentComponent[]","name":"considerationComponents","type":"tuple[]"}],"internalType":"struct ISeaport.Fulfillment[]","name":"fulfillments","type":"tuple[]"}],"name":"matchOrders","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"router","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]Contract Creation Code
60c0346200014b57601f62003fe238819003918201601f19168301916001600160401b0383118484101762000150578084926060946040528339810103126200014b576200004d8162000166565b620000696040620000616020850162000166565b930162000166565b600080546001600160a01b0319166001600160a01b03938416908117825560405194917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a360016002556080521660a052613e6690816200017c82396080518181816103fb015261171f015260a0518181816108a901528181610d1601528181611363015281816115e901528181611b7a015281816126780152818161329101528181613303015281816135d4015281816136e0015281816138b6015281816139260152818161397e01528181613a0c01528181613c970152613d760152f35b600080fd5b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036200014b5756fe6080604052600436101561001b575b361561001957600080fd5b005b60003560e01c806312f3a43f14610197578063150b7a021461018e5780631626ba7e146101855780632b8a88ec1461017c5780634e71e0c814610173578063590823091461016a5780636baab5f71461016157806376af66291461015857806380b102ff1461014f57806386f20e8c146101465780638da5cb5b1461013d578063a0810f3614610134578063a81744041461012b578063a87d645c14610122578063b50e44b814610119578063e30c397814610110578063f23a6e6114610107578063f2fde38b146100fe5763f887ea400361000e576100f9611708565b61000e565b506100f96116b7565b506100f9611642565b506100f9611618565b506100f96115d2565b506100f9611442565b506100f96112b3565b506100f9611271565b506100f9611247565b506100f96111c3565b506100f9611069565b506100f9610eaa565b506100f9610c90565b506100f9610bca565b506100f9610b0b565b506100f96107f1565b506100f9610586565b506100f961038b565b506100f96101d5565b9181601f840112156101d0578235916001600160401b0383116101d0576020808501948460051b0101116101d057565b600080fd5b506060806003193601126101d0576001600160401b03906004358281116101d0576102049036906004016101a0565b6024939193358281116101d05761021f9036906004016101a0565b926044359081116101d0576102389036906004016101a0565b60005491956001600160a01b039490928516330361032f576102586117fa565b60005b81811061026c576100196001600255565b807fa3f06cf374cf66be06f5fe85cdd3b13d9d9fdef6482f640d2de1d44c3ed7332c8787868c6103228f878f81816102f3828f60019f976102ee8c8e6102e88e6102df886103069f806102fe9f6102c76102d7938d8d611765565b35976102d289610340565b61178a565b969093611765565b3593369161054f565b906118ba565b611765565b35986102d28a610340565b959094611765565b359160409384519687961686528c60208701528c8601916117d9565b918301520390a10161025b565b6040516282b42960e81b8152600490fd5b6001600160a01b038116036101d057565b359061035c82610340565b565b9181601f840112156101d0578235916001600160401b0383116101d057602083818601950101116101d057565b50346101d05760803660031901126101d0576103a8600435610340565b6103b3602435610340565b6064356001600160401b0381116101d0576103d290369060040161035e565b806103ea575b604051630a85bd0160e11b8152602090f35b61041f916103f991369161054f565b7f000000000000000000000000000000000000000000000000000000000000000061187e565b38806103d8565b50634e487b7160e01b600052604160045260246000fd5b60a081019081106001600160401b0382111761045857604052565b610460610426565b604052565b604081019081106001600160401b0382111761045857604052565b6001600160401b03811161045857604052565b606081019081106001600160401b0382111761045857604052565b60c081019081106001600160401b0382111761045857604052565b608081019081106001600160401b0382111761045857604052565b90601f801991011681019081106001600160401b0382111761045857604052565b6040519061016082018281106001600160401b0382111761045857604052565b6020906001600160401b038111610542575b601f01601f19160190565b61054a610426565b610537565b92919261055b82610525565b9161056960405193846104e4565b8294818452818301116101d0578281602093846000960137010152565b50346101d05760403660031901126101d0576024356001600160401b0381116101d057366023820112156101d0576105c890369060248160040135910161054f565b50604051630b135d3f60e11b8152602090f35b60a09060231901126101d057602490565b908160a09103126101d05790565b6020906001600160401b038111610613575b60051b0190565b61061b610426565b61060c565b81601f820112156101d057803591610637836105fa565b9261064560405194856104e4565b808452602092838086019260051b8201019283116101d0578301905b82821061066f575050505090565b81358152908301908301610661565b60609060431901126101d057604490565b9181601f840112156101d0578235916001600160401b0383116101d0576020808501948460061b0101116101d057565b60c06003198201126101d0576001600160401b03906004358281116101d057816106eb916004016105ec565b9260248035908482116101d057836023830112156101d057816004013591610712836105fa565b926040610721815195866104e4565b818552602093808587019360051b850101938885116101d057818101935b8585106107725750505050505050926107578361067e565b9260a4359182116101d05761076e9160040161068f565b9091565b84358b81116101d05782019060a0828c0360231901126101d0578451906107988261043d565b848301358252604483013560028110156101d057898301526064830135868301526084830135606083015260a4830135918d83116101d0576107e18d878c969587960101610620565b608082015281520194019361073f565b50346101d057610800366106bf565b61080c949192946117fa565b61082c61082661081c848061274e565b6060810190612764565b90612799565b926003610838856127dd565b610841816127cb565b141580610ae4575b610ad25760209161086a61085e84870161177d565b6001600160a01b031690565b9361098f61097561089861085e87610892610826610888888061274e565b60408101906127e7565b0161177d565b986001600160a01b039887906108da7f00000000000000000000000000000000000000000000000000000000000000008c166108d4818d612a0a565b8d611e09565b60036108e5826127dd565b6108ee816127cb565b03610abc5760400135809a5b604051627eeac760e11b808252306004830152602482018490529094918c16918f8587604481875afa968715610aaf575b600097610a89575b5060406109409101611963565b15610a78576109519030908a613574565b60405190815230600482015260248101929092529093849190829081906044820190565b03915afa918215610a6b575b600092610a3c575b506118f3565b91826109c7575b6109bd886109b88b6109b38b8b6109ae8c850161177d565b612ae7565b61177d565b6128b7565b6100196001600255565b60005b8181106109d75750610996565b80610a368a6109f16109ec600195878b611908565b612856565b8051610a3090610a10908a908d906001600160a01b0316940151611926565b610a2a610a1e8d8b01612add565b6001600160781b031690565b90611939565b906128a4565b016109ca565b610a5d919250873d8911610a64575b610a5581836104e4565b810190611a24565b9038610989565b503d610a4b565b610a73611a33565b610981565b610a849030908a613269565b610951565b610940919750610aa7604091883d8a11610a6457610a5581836104e4565b979150610933565b610ab7611a33565b61092b565b506060610ac88361281c565b510151809a6108fa565b604051635863f78960e01b8152600490fd5b506005610af0856127dd565b610af9816127cb565b1415610849565b60009103126101d057565b50346101d057600080600319360112610b72576001546001600160a01b038116903382900361032f5782546001600160a01b03199081168317845516600155807f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b80fd5b60e06003198201126101d0576001600160401b03906004358281116101d05781610ba1916004016101a0565b9093909260a06023198401126101d05760249260c4359182116101d05761076e9160040161068f565b50346101d057610c0c610bdc36610b75565b91610be9959394956117fa565b602081013594610bf886610340565b606082013596610c0788610340565b612153565b6040516370a0823160e01b8152306004820152906020826024816001600160a01b0387165afa918215610c83575b600092610c63575b5081610c52576100196001600255565b610c5b92611bf5565b3880806109bd565b610c7c91925060203d8111610a6457610a5581836104e4565b9038610c42565b610c8b611a33565b610c3a565b50346101d057610c9f366106bf565b610cad9492949391936117fa565b610cbd61082661081c858061274e565b936002610cc9866127dd565b610cd2816127cb565b141580610e7d575b610ad257602091610cef61085e84880161177d565b93610d0761085e856108926108266108888b8061274e565b966001600160a01b0396610d477f00000000000000000000000000000000000000000000000000000000000000008916610d41818a612a0a565b8a611e09565b610d5360408b01611963565b15610e6c57610d6490833091613574565b6002610d6f826127dd565b610d78816127cb565b03610e5757604091500135945b6040516331a9108f60e11b8152600481018790529084826024818985165afa918215610e4a575b600092610e1b575b5030911603610ddb575b6109bd866109b8896109b38989610dd68a850161177d565b612925565b60005b818110610deb5750610dbe565b80610e1588610e006109ec6001958789611908565b805190880151906001600160a01b03166128a4565b01610dde565b610e3c919250853d8711610e43575b610e3481836104e4565b810190612841565b9038610db4565b503d610e2a565b610e52611a33565b610dac565b50610e6360609161281c565b51015194610d85565b610e7890833091613269565b610d64565b506004610e89866127dd565b610e92816127cb565b1415610cda565b60809060231901126101d057602490565b5060c03660031901126101d0576001600160401b036004358181116101d057610ed79036906004016105ec565b610ee036610e99565b9160a4359081116101d057610ef990369060040161068f565b929091610f046117fa565b60208083013593610f1485610340565b60608401359580610f4d57505050610f2d92935061199e565b4780610f3d576100196001600255565b610f46916119e5565b38806109bd565b919392610f8f91938747926040830135610f6681611959565b15610ff257610f8892610f7761196d565b903591610f8383610340565b613688565b47906118f3565b60005b828110610fa55750505050509050610f2d565b80610fc888610fc38589610fbc6001978a8c611908565b0135611926565b611939565b80610fd5575b5001610f92565b610fec90610fe76109b384888a611908565b6119e5565b38610fce565b61100f92610ffe61196d565b90359161100a83610340565b6132d9565b610f88565b60c06003198201126101d0576001600160401b03906004358281116101d05781611040916004016101a0565b9093909260806023198401126101d05760249260a4359182116101d05761076e9160040161068f565b5061107336611014565b9361107f9391936117fa565b6020808401359461108f86610340565b606085013596806110a857505050610f2d9394506120b0565b92919493909347926110b861196d565b6110c460408401611963565b156111735760005b8481106111355750505050506110e39047906118f3565b60005b8281106110f95750505050509050610f2d565b8061111088610fc38589610fbc6001978a8c611908565b8061111d575b50016110e6565b61112f90610fe76109b384888a611908565b38611116565b8061116d61114f611149600194898861206b565b8061209b565b6111588761177d565b858d611165868c8b61206b565b013592613688565b016110cc565b60005b84811061118b5750505050506110e390610f88565b806111bd61119f611149600194898861206b565b6111a88761177d565b858d6111b5868c8b61206b565b0135926132d9565b01611176565b50346101d05760e03660031901126101d0576001600160401b036004358181116101d0576111f59036906004016105ec565b906111ff366105db565b9060c4359081116101d05761121b610c0c91369060040161068f565b906112246117fa565b60208401359361123385610340565b60608101359561124287610340565b611a40565b50346101d05760003660031901126101d0576000546040516001600160a01b039091168152602090f35b50346101d057610c0c61128336610b75565b91611290959394956117fa565b60208101359461129f86610340565b6060820135966112ae88610340565b61255c565b50346101d057604060031981813601126101d0576001600160401b03906004358281116101d0576112e89036906004016101a0565b90926024359081116101d05791611304859336906004016101a0565b9161130d6117fa565b8451958694632a05d10160e21b8652806044870188600489015252606486019160648260051b8801019781936000925b8484106113d257896000818061135e8f8e8e8e858403016024860152613039565b0381837f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af180156113c5575b6113a2576100196001600255565b6113be903d806000833e6113b681836104e4565b810190612be6565b50806109bd565b6113cd611a33565b611394565b919395969798509193986114216001916063198d820301855261142e6113f88d86612fb2565b916114126114068480612cec565b89835289830190612ead565b90602094848680960190612f81565b91858185039101526117d9565b9b019301940191938a98979695939161133d565b5061144c36611014565b936114589391936117fa565b6020808401359461146886610340565b6060850135968061148157505050610f2d9394506124af565b929490939194479261149161196d565b906040936114a0858201611963565b1561157d5760005b8983821061151457505050505050506114c29047906118f3565b60005b8281106114d85750505050509050610f2d565b806114ef88610fc38589610fbc6001978a8c611908565b806114fc575b50016114c5565b61150e90610fe76109b384888a611908565b386114f5565b9061157761156187808888611571898e6115678a8561155960019e61155084611548611542828f8890612318565b80612348565b9b909d612318565b90810190612348565b9b909561177d565b99612318565b01359636916123ff565b926139e1565b016114a8565b60005b8983821061159857505050505050506114c290610f88565b906115cc611561878088886115c6898e6115678a8561155960019e61155084611548611542828f8890612318565b926138fb565b01611580565b50346101d05760003660031901126101d0576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b50346101d05760003660031901126101d0576001546040516001600160a01b039091168152602090f35b50346101d05760a03660031901126101d05761165f600435610340565b61166a602435610340565b6084356001600160401b0381116101d05761168990369060040161035e565b806116a1575b60405163f23a6e6160e01b8152602090f35b6116b0916103f991369161054f565b388061168f565b50346101d05760203660031901126101d0576004356116d581610340565b6000546001600160a01b0391908216330361032f57166bffffffffffffffffffffffff60a01b6001541617600155600080f35b50346101d05760003660031901126101d0576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b50634e487b7160e01b600052603260045260246000fd5b91908110156117755760051b0190565b61061b61174e565b3561178781610340565b90565b91908110156117cc575b60051b81013590601e19813603018212156101d05701908135916001600160401b0383116101d05760200182360381136101d0579190565b6117d461174e565b611794565b908060209392818452848401376000828201840152601f01601f1916010190565b60028054146118095760028055565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b3d15611879573d9061185f82610525565b9161186d60405193846104e4565b82523d6000602084013e565b606090565b8151600092839260209091019083906001600160a01b03165af16118a061184e565b50156118a857565b6040516322092f2f60e11b8152600490fd5b8151600093849391926020909201916001600160a01b03165af16118a061184e565b50634e487b7160e01b600052601160045260246000fd5b9190820391821161190057565b61035c6118dc565b9190811015611919575b60061b0190565b61192161174e565b611912565b8181029291811591840414171561190057565b8115611943570490565b634e487b7160e01b600052601260045260246000fd5b801515036101d057565b3561178781611959565b604051602081018181106001600160401b03821117611991575b6040526000815290565b611999610426565b611987565b60408201356119ac81611959565b156119cb5761035c916119bd61196d565b606082359261116584610340565b61035c916119d761196d565b60608235926111b584610340565b816119ee575050565b6000918291829182916001600160a01b03165af1611a0a61184e565b5015611a1257565b60405163d2dcf4f360e01b8152600490fd5b908160209103126101d0575190565b506040513d6000823e3d90fd5b9091939293611a516060840161177d565b60808401359580611a6a5750505061035c929350611b61565b6040516370a0823160e01b808252306004830152602096949593949293611ad9936001600160a01b038716939289929091908385602481895afa948515611b54575b600095611b2f575b5090611abf91611b61565b604051908152306004820152928390818060248101610975565b60005b828110611aed575050505050509050565b80611b0489610fc3858a610fbc6001978a8d611908565b80611b11575b5001611adc565b611b2990611b236109b384888b611908565b87611bf5565b38611b0a565b611abf92919550611b4c90853d8711610a6457610a5581836104e4565b949091611ab4565b611b5c611a33565b611aac565b611ba86060830135611b7281610340565b6080840135907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690611f00565b6040820135611bb681611959565b15611bd85761035c91611bc761196d565b903591611bd383610340565b613574565b61035c91611be461196d565b903591611bf083610340565b613269565b60405163a9059cbb60e01b60208083019182526001600160a01b039490941660248301526044808301959095529381529192611c8e92916000908190611c3c6064866104e4565b60018060a01b03169260405194611c5286610465565b8786527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c656488870152519082855af1611c8861184e565b91611d77565b805190828215928315611cfe575b50505015611ca75750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152fd5b611d0e9350820181019101611d16565b388281611c9c565b908160209103126101d0575161178781611959565b15611d3257565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b91929015611d975750815115611d8b575090565b611787903b1515611d2b565b825190915015611daa5750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b828510611df0575050604492506000838284010152601f80199101168101030190fd5b8481018201518686016044015293810193859350611dcd565b604051636eb1769f60e11b81523060048201526001600160a01b038381166024830152929091831690602083604481855afa928315611ef3575b600093611ed3575b5060001992838110611e5f575b5050505050565b60009485948592611ec4575b60405191602083019463095ea7b360e01b8652166024830152604482015260448152611e96816104c9565b51925af1611ea261184e565b5015611eb2573880808080611e58565b604051631298f31b60e11b8152600490fd5b611ece8486611ffc565b611e6b565b611eec91935060203d8111610a6457610a5581836104e4565b9138611e4b565b611efb611a33565b611e43565b604051636eb1769f60e11b81523060048201526001600160a01b038381166024830152909392911690602084604481855afa938415611fef575b600094611fcf575b50828410611f51575b50505050565b6000611f97611fa58296958396611fc0575b60405163095ea7b360e01b602082019081526001600160a01b03909616602482015260448101919091529182906064820190565b03601f1981018352826104e4565b51925af1611fb161184e565b5015611eb25738808080611f4b565b611fca8587611ffc565b611f63565b611fe891945060203d8111610a6457610a5581836104e4565b9238611f42565b611ff7611a33565b611f3a565b60405163095ea7b360e01b602082019081526001600160a01b039093166024820152600060448083018290528252928392918390608081016001600160401b0381118282101761205e575b60405251925af161205661184e565b5015611eb257565b612066610426565b612047565b919081101561208e575b60051b81013590603e19813603018212156101d0570190565b61209661174e565b612075565b903590609e19813603018212156101d0570190565b9092916120bb61196d565b916120c860408301611963565b156121105760005b8581106120df57505050509050565b8061210a6120f36111496001948a8761206b565b6120fc8661177d565b876020611165868d8a61206b565b016120d0565b60005b85811061212257505050509050565b8061214d6121366111496001948a8761206b565b61213f8661177d565b8760206111b5868d8a61206b565b01612113565b9091929493946121656060850161177d565b6080850135968061217e5750505061035c93945061227d565b6040516370a0823160e01b8082523060048301526020979496939592946121d3946001600160a01b038816948a9392919084866024818a5afa95861561224a575b600096612223575b5090611abf929161227d565b60005b8281106121e7575050505050509050565b806121fe89610fc3858a610fbc6001978a8d611908565b8061220b575b50016121d6565b61221d90611b236109b384888b611908565b38612204565b611abf939291965061224190863d8811610a6457610a5581836104e4565b959091926121c7565b612252611a33565b6121bf565b909161178792811015612270575b60051b81019061209b565b61227861174e565b612265565b919061228e611b726060840161177d565b61229661196d565b906122a360408401611963565b156122e05760005b8181106122b9575050505050565b806122da6122ca6001938589612257565b856122d48861177d565b91613574565b016122ab565b60005b8181106122f1575050505050565b806123126123026001938589612257565b8561230c8861177d565b91613269565b016122e3565b919081101561233b575b60051b81013590605e19813603018212156101d0570190565b61234361174e565b612322565b903590601e19813603018212156101d057018035906001600160401b0382116101d057602001918160051b360383136101d057565b81601f820112156101d057803590612394826105fa565b926040926123a4845195866104e4565b808552602091828087019260061b850101938185116101d0578301915b8483106123d15750505050505090565b85838303126101d05783869182516123e881610465565b8535815282860135838201528152019201916123c1565b9092919261240c816105fa565b9160409161241c835194856104e4565b839581855260208095019160051b8301938185116101d05783925b8584106124475750505050505050565b6001600160401b039084358281116101d05786019083828603126101d057835161247081610465565b82358481116101d0578661248591850161237d565b8152898301359384116101d0576124a0868b9586950161237d565b83820152815201930192612437565b906124b861196d565b6040936124c6858201611963565b156125215760005b8381106124dd57505050505050565b8061251b61156187878785611571898e61156761250061154260019d898b612318565b95909761155961251183838d612318565b6020810190612348565b016124ce565b60005b83811061253357505050505050565b80612556611561878787856115c6898e61156761250061154260019d898b612318565b01612524565b90919294939461256e6060850161177d565b608085013596806125875750505061035c939450612660565b6040516370a0823160e01b8082523060048301526020979496939592946125dc946001600160a01b038816948a9392919084866024818a5afa958615612653575b60009661262c575b5090611abf9291612660565b60005b8281106125f0575050505050509050565b8061260789610fc3858a610fbc6001978a8d611908565b80612614575b50016125df565b61262690611b236109b384888b611908565b3861260d565b611abf939291965061264a90863d8811610a6457610a5581836104e4565b959091926125d0565b61265b611a33565b6125c8565b906126a66126706060850161177d565b6080850135907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690611f00565b6126ae61196d565b906126bb60408501611963565b156127195760005b8181106126d1575050505050565b8061271361270d856126e9611542600196888b612318565b6126fa612511878a8d969596612318565b94906127058d61177d565b9536916123ff565b92613957565b016126c3565b60005b81811061272a575050505050565b80612748612742856126e9611542600196888b612318565b9261388f565b0161271c565b90359061015e19813603018212156101d0570190565b903590601e19813603018212156101d057018035906001600160401b0382116101d0576020019160c08202360383136101d057565b90156127a25790565b61178761174e565b600611156101d057565b50634e487b7160e01b600052602160045260246000fd5b600611156127d557565b61035c6127b4565b35611787816127aa565b903590601e19813603018212156101d057018035906001600160401b0382116101d0576020019160a08202360383136101d057565b60209080511561282a570190565b61283261174e565b0190565b519061035c82610340565b908160209103126101d0575161178781610340565b6040813603126101d057602060405191604083018381106001600160401b03821117612897575b604052803561288b81610340565b83520135602082015290565b61289f610426565b61287d565b816128ae57505050565b61035c92611bf5565b6040516370a0823160e01b8152306004820152906020826024816001600160a01b0387165afa918215612918575b6000926128f8575b50816128ae57505050565b61291191925060203d8111610a6457610a5581836104e4565b90386128ed565b612920611a33565b6128e5565b6040516331a9108f60e11b8152600481018490526001600160a01b03928316939192602082602481885afa9182156129fd575b6000926129dd575b5016301461296d57505050565b823b156101d057604051632142170760e11b81523060048201526001600160a01b0390921660248301526044820152906000908290818381606481015b03925af180156129d0575b6129bd575b50565b806129ca61035c92610480565b80610b00565b6129d8611a33565b6129b5565b6129f691925060203d8111610e4357610e3481836104e4565b9038612960565b612a05611a33565b612958565b60405163e985e9c560e01b81523060048201526001600160a01b0383811660248301529190911690602081604481855afa908115612abf575b600091612a91575b5015612a55575050565b803b156101d05760405163a22cb46560e01b81526001600160a01b039092166004830152600160248301526000908290818381604481016129aa565b612ab2915060203d8111612ab8575b612aaa81836104e4565b810190611d16565b38612a4b565b503d612aa0565b612ac7611a33565b612a43565b6001600160781b038116036101d057565b3561178781612acc565b604051627eeac760e11b81523060048201526024810184905290916001600160a01b0316602082604481845afa918215612bd9575b600092612bb9575b5081612b305750505050565b803b156101d057604051637921219560e11b81523060048201526001600160a01b039390931660248401526044830193909352606482015260a06084820152600060a482018190529091829060c490829084905af18015612bac575b612b99575b808080611f4b565b806129ca612ba692610480565b38612b91565b612bb4611a33565b612b8c565b612bd291925060203d8111610a6457610a5581836104e4565b9038612b24565b612be1611a33565b612b1c565b60209081818403126101d0578051906001600160401b0382116101d0570182601f820112156101d057805191612c1b836105fa565b936040612c2a815196876104e4565b848652828601918360e0809702860101948186116101d0578401925b858410612c57575050505050505090565b8382038781126101d057835191612c6d83610493565b60a08092126101d0578892612cd488938751612c888161043d565b8951612c93816127aa565b8152858a0151612ca281610340565b86820152888a0151898201526060808b0151908201526080808b015190612cc882610340565b82015283528801612836565b8382015260c087015186820152815201930192612c46565b903561015e19823603018112156101d0570190565b9035601e19823603018112156101d05701602081359101916001600160401b0382116101d05760a08202360383136101d057565b906006821015612d425752565b612d4a6127b4565b52565b9190808252602080920192916000905b828210612d6b575050505090565b9091929380612d866001928735612d81816127aa565b612d35565b82860135612d9381610340565b828060a01b03168382015260408087013590820152606080870135908201526080808701359082015260a08091019501920190929192612d5d565b9035601e19823603018112156101d05701602081359101916001600160401b0382116101d05760c08202360383136101d057565b9190808252602080920192916000905b828210612e20575050505090565b9091929380612e366001928735612d81816127aa565b82860135612e4381610340565b828060a01b038091168483015260408088013590830152606080880135908301526080808801359083015260a09081880135612e7e81610340565b169082015260c0908101950193920190612e12565b359060048210156101d057565b906004821015612d425752565b90612ec881612ebb84610351565b6001600160a01b03169052565b612ee7612ed760208401610351565b6001600160a01b03166020830152565b612f26612f0b612efa6040850185612d01565b610160806040870152850191612d4d565b612f186060850185612dce565b908483036060860152612e02565b91612f40612f3660808301612e93565b6080840190612ea0565b60a081013560a083015260c081013560c083015260e081013560e0830152610100808201359083015261012080820135908301526101408091013591015290565b9035601e19823603018112156101d05701602081359101916001600160401b0382116101d05781360383136101d057565b9035603e19823603018112156101d0570190565b9035601e19823603018112156101d05701602081359101916001600160401b0382116101d0578160061b360383136101d057565b9190808252602080920192916000905b828210613018575050505090565b8335855283810135858201526040948501949093019260019091019061300a565b9082818152602080910193818360051b82010194846000925b858410613063575050505050505090565b9091929394959685806130ba600193601f198682030188526130858c88612fb2565b906130ad6130a36130968480612fc6565b6040808652850191612ffa565b9285810190612fc6565b9185818503910152612ffa565b990194019401929594939190613052565b6117879161314861313d6130f06130e28580612cec565b60a0855260a0850190612ead565b60208501356130fe81612acc565b6001600160781b038091166020860152604086013561311c81612acc565b16604085015261312f6060860186612f81565b9085830360608701526117d9565b926080810190612f81565b9160808185039101526117d9565b90815180825260208092019182818360051b8201950193600080925b858410613183575050505050505090565b9091929394959681810384528751908660c060a092838101938551825283860151600281101561321b575b8483015260408087015190830152606080870151908301526080958601519582015284519384905291939101919083019085905b808210613202575050509080600192990194019401929594939190613172565b91938060019294865181520194019201889392916131e2565b6132236127b4565b6131ae565b91613252906132446060939695966080865260808601906130cb565b908482036020860152613156565b600060408401526001600160a01b03909416910152565b9060209161328b60405194859384936339eb2ac960e21b855260048501613228565b038160007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af16132c25750565b6129ba9060203d8111612ab857612aaa81836104e4565b92602092916132ff94604051958694859384936339eb2ac960e21b855260048501613228565b03917f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af16132c25750565b81601f820112156101d05780359061334b826105fa565b9260409061335b825195866104e4565b838552602091828601918360a0809702860101948186116101d0578401925b85841061338b575050505050505090565b86848303126101d05784879184516133a28161043d565b86356133ad816127aa565b8152828701356133bc81610340565b838201528587013586820152606080880135908201526080808801359082015281520193019261337a565b81601f820112156101d0578035906133fe826105fa565b9260409061340e825195866104e4565b838552602091828601918360c0809702860101948186116101d0578401925b85841061343e575050505050505090565b86848303126101d0578487918451613455816104ae565b8635613460816127aa565b81528287013561346f81610340565b838201528587013586820152606080880135908201526080808801359082015260a0808801359061349f82610340565b82015281520193019261342d565b610160813603126101d0576134c0610505565b906134ca81610351565b82526134d860208201610351565b60208301526001600160401b0360408201358181116101d0576134fe9036908401613334565b604084015260608201359081116101d05761351c90369083016133e7565b606083015261352d60808201612e93565b608083015260a081013560a083015260c081013560c083015260e081013560e083015261010080820135908301526101208082013590830152610140809101359082015290565b9161358f61358a613585858061274e565b6134ad565b613c64565b916135cf604085019160206135af6135a9610a1e86612add565b87613d59565b9460009260405194859283926339eb2ac960e21b84528b60048501613228565b0381847f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1909181613668575b5061361d57604051631298f31b60e11b8152600490fd5b61363357604051631298f31b60e11b8152600490fd5b61365a610a1e926136556136619561364f610a1e602096612add565b90613d59565b6118f3565b9301612add565b03611eb257565b61368191925060203d8111612ab857612aaa81836104e4565b9038613606565b9290916136dc61369e61358a613585878061274e565b93602060408701936136bb6136b5610a1e87612add565b88613d59565b956000936040518096819482936339eb2ac960e21b84528d60048501613228565b03917f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1909181613668575061361d57604051631298f31b60e11b8152600490fd5b90815180825260208080930193019160005b828110613748575050505090565b835180518652820151858301526040909401939281019260010161373a565b90815180825260208092019182818360051b82019501936000915b8483106137925750505050505090565b90919293949584806137cb83856001950387528a5190836137bb83516040808552840190613728565b9201519084818403910152613728565b9801930193019194939290613782565b949290959391958660808701608088525260a086019660a08160051b8801019782600090815b84831061384c5750505050505060609161382b87613839938861035c98999a0360208b0152613156565b908782036040890152613767565b6001600160a01b03909216940193909352565b90919293949a609f198b82030185528b35609e198436030181121561388b576001918461387992016130cb565b9b602090810196950193019190613801565b8280fd5b604051637968958960e11b815294600094869485946138b194600487016137db565b0381837f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af16138e75750565b6129ba903d806000833e6113b681836104e4565b6139226000959693949660405197889687958695637968958960e11b8752600487016137db565b03917f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af16138e75750565b604051637968958960e11b8152946000948694859461397994600487016137db565b0381837f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af190816139c6575b5061035c57604051631298f31b60e11b8152600490fd5b6139da903d806000833e6113b681836104e4565b50386139af565b613a086000959693949660405197889687958695637968958960e11b8752600487016137db565b03917f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af190816139c6575061035c57604051631298f31b60e11b8152600490fd5b6040519061016082018281106001600160401b03821117613ab9575b60405281610140600091828152826020820152606060408201526060808201528260808201528260a08201528260c08201528260e082015282610100820152826101208201520152565b613ac1610426565b613a6f565b90815180825260208080930193019160005b828110613ae6575050505090565b909192938260a06001928751613afd828251612d35565b8084015185841b869003168285015260408082015190830152606080820151908301526080908101519082015201950193929101613ad8565b90815180825260208080930193019160005b828110613b56575050505090565b909192938260c06001928751613b6d828251612d35565b848060a01b038085830151168584015260408083015190840152606080830151908401526080808301519084015260a0809201511690820152019501910192919092613b48565b602080825282516001600160a01b03169082015260208201516001600160a01b031660408201526040820151613c11613bfb61016092836060860152610180850190613ac6565b6060850151848203601f19016080860152613b36565b92613c24608082015160a0850190612ea0565b60a081015160c084015260c081015160e084015260e081015161010090818501528101516101209081850152810151906101409182850152015191015290565b613c6c613a53565b50805160405163f07ec37360e01b81526001600160a01b039182166004820152602092613cf49284927f0000000000000000000000000000000000000000000000000000000000000000909116908381602481855afa908115613d4c575b600091613d2f575b5061014083015260405180809581946379df72bd60e01b835260048301613bb4565b03915afa918215613d22575b600092613d0c57505090565b6117879250803d10610a6457610a5581836104e4565b613d2a611a33565b613d00565b613d469150843d8611610a6457610a5581836104e4565b38613cd2565b613d54611a33565b613cca565b6040516346423aa760e01b815260048101919091526080816024817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa908115613e23575b6000908192613dd0575b5060008215613dc9575061178792610fc391611926565b9250505090565b91506080823d8211613e1b575b81613dea608093836104e4565b81010312610b725750613dfd8151611959565b613e0a6020820151611959565b606060408201519101519038613db2565b3d9150613ddd565b613e2b611a33565b613da856fea264697066735822122043d852a6d86a0e81b5c004298144c147b2a3f64b9b815eace91da6dde1147ef164736f6c63430008110033000000000000000000000000f3d63166f0ca56c3c1a3508fce03ff0cf3fb691e000000000000000000000000c2c862322e9c97d6244a3506655da95f05246fd80000000000000000000000000000000000000068f116a894984e2db1123eb395
Deployed Bytecode
0x6080604052600436101561001b575b361561001957600080fd5b005b60003560e01c806312f3a43f14610197578063150b7a021461018e5780631626ba7e146101855780632b8a88ec1461017c5780634e71e0c814610173578063590823091461016a5780636baab5f71461016157806376af66291461015857806380b102ff1461014f57806386f20e8c146101465780638da5cb5b1461013d578063a0810f3614610134578063a81744041461012b578063a87d645c14610122578063b50e44b814610119578063e30c397814610110578063f23a6e6114610107578063f2fde38b146100fe5763f887ea400361000e576100f9611708565b61000e565b506100f96116b7565b506100f9611642565b506100f9611618565b506100f96115d2565b506100f9611442565b506100f96112b3565b506100f9611271565b506100f9611247565b506100f96111c3565b506100f9611069565b506100f9610eaa565b506100f9610c90565b506100f9610bca565b506100f9610b0b565b506100f96107f1565b506100f9610586565b506100f961038b565b506100f96101d5565b9181601f840112156101d0578235916001600160401b0383116101d0576020808501948460051b0101116101d057565b600080fd5b506060806003193601126101d0576001600160401b03906004358281116101d0576102049036906004016101a0565b6024939193358281116101d05761021f9036906004016101a0565b926044359081116101d0576102389036906004016101a0565b60005491956001600160a01b039490928516330361032f576102586117fa565b60005b81811061026c576100196001600255565b807fa3f06cf374cf66be06f5fe85cdd3b13d9d9fdef6482f640d2de1d44c3ed7332c8787868c6103228f878f81816102f3828f60019f976102ee8c8e6102e88e6102df886103069f806102fe9f6102c76102d7938d8d611765565b35976102d289610340565b61178a565b969093611765565b3593369161054f565b906118ba565b611765565b35986102d28a610340565b959094611765565b359160409384519687961686528c60208701528c8601916117d9565b918301520390a10161025b565b6040516282b42960e81b8152600490fd5b6001600160a01b038116036101d057565b359061035c82610340565b565b9181601f840112156101d0578235916001600160401b0383116101d057602083818601950101116101d057565b50346101d05760803660031901126101d0576103a8600435610340565b6103b3602435610340565b6064356001600160401b0381116101d0576103d290369060040161035e565b806103ea575b604051630a85bd0160e11b8152602090f35b61041f916103f991369161054f565b7f000000000000000000000000c2c862322e9c97d6244a3506655da95f05246fd861187e565b38806103d8565b50634e487b7160e01b600052604160045260246000fd5b60a081019081106001600160401b0382111761045857604052565b610460610426565b604052565b604081019081106001600160401b0382111761045857604052565b6001600160401b03811161045857604052565b606081019081106001600160401b0382111761045857604052565b60c081019081106001600160401b0382111761045857604052565b608081019081106001600160401b0382111761045857604052565b90601f801991011681019081106001600160401b0382111761045857604052565b6040519061016082018281106001600160401b0382111761045857604052565b6020906001600160401b038111610542575b601f01601f19160190565b61054a610426565b610537565b92919261055b82610525565b9161056960405193846104e4565b8294818452818301116101d0578281602093846000960137010152565b50346101d05760403660031901126101d0576024356001600160401b0381116101d057366023820112156101d0576105c890369060248160040135910161054f565b50604051630b135d3f60e11b8152602090f35b60a09060231901126101d057602490565b908160a09103126101d05790565b6020906001600160401b038111610613575b60051b0190565b61061b610426565b61060c565b81601f820112156101d057803591610637836105fa565b9261064560405194856104e4565b808452602092838086019260051b8201019283116101d0578301905b82821061066f575050505090565b81358152908301908301610661565b60609060431901126101d057604490565b9181601f840112156101d0578235916001600160401b0383116101d0576020808501948460061b0101116101d057565b60c06003198201126101d0576001600160401b03906004358281116101d057816106eb916004016105ec565b9260248035908482116101d057836023830112156101d057816004013591610712836105fa565b926040610721815195866104e4565b818552602093808587019360051b850101938885116101d057818101935b8585106107725750505050505050926107578361067e565b9260a4359182116101d05761076e9160040161068f565b9091565b84358b81116101d05782019060a0828c0360231901126101d0578451906107988261043d565b848301358252604483013560028110156101d057898301526064830135868301526084830135606083015260a4830135918d83116101d0576107e18d878c969587960101610620565b608082015281520194019361073f565b50346101d057610800366106bf565b61080c949192946117fa565b61082c61082661081c848061274e565b6060810190612764565b90612799565b926003610838856127dd565b610841816127cb565b141580610ae4575b610ad25760209161086a61085e84870161177d565b6001600160a01b031690565b9361098f61097561089861085e87610892610826610888888061274e565b60408101906127e7565b0161177d565b986001600160a01b039887906108da7f0000000000000000000000000000000000000068f116a894984e2db1123eb3958c166108d4818d612a0a565b8d611e09565b60036108e5826127dd565b6108ee816127cb565b03610abc5760400135809a5b604051627eeac760e11b808252306004830152602482018490529094918c16918f8587604481875afa968715610aaf575b600097610a89575b5060406109409101611963565b15610a78576109519030908a613574565b60405190815230600482015260248101929092529093849190829081906044820190565b03915afa918215610a6b575b600092610a3c575b506118f3565b91826109c7575b6109bd886109b88b6109b38b8b6109ae8c850161177d565b612ae7565b61177d565b6128b7565b6100196001600255565b60005b8181106109d75750610996565b80610a368a6109f16109ec600195878b611908565b612856565b8051610a3090610a10908a908d906001600160a01b0316940151611926565b610a2a610a1e8d8b01612add565b6001600160781b031690565b90611939565b906128a4565b016109ca565b610a5d919250873d8911610a64575b610a5581836104e4565b810190611a24565b9038610989565b503d610a4b565b610a73611a33565b610981565b610a849030908a613269565b610951565b610940919750610aa7604091883d8a11610a6457610a5581836104e4565b979150610933565b610ab7611a33565b61092b565b506060610ac88361281c565b510151809a6108fa565b604051635863f78960e01b8152600490fd5b506005610af0856127dd565b610af9816127cb565b1415610849565b60009103126101d057565b50346101d057600080600319360112610b72576001546001600160a01b038116903382900361032f5782546001600160a01b03199081168317845516600155807f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b80fd5b60e06003198201126101d0576001600160401b03906004358281116101d05781610ba1916004016101a0565b9093909260a06023198401126101d05760249260c4359182116101d05761076e9160040161068f565b50346101d057610c0c610bdc36610b75565b91610be9959394956117fa565b602081013594610bf886610340565b606082013596610c0788610340565b612153565b6040516370a0823160e01b8152306004820152906020826024816001600160a01b0387165afa918215610c83575b600092610c63575b5081610c52576100196001600255565b610c5b92611bf5565b3880806109bd565b610c7c91925060203d8111610a6457610a5581836104e4565b9038610c42565b610c8b611a33565b610c3a565b50346101d057610c9f366106bf565b610cad9492949391936117fa565b610cbd61082661081c858061274e565b936002610cc9866127dd565b610cd2816127cb565b141580610e7d575b610ad257602091610cef61085e84880161177d565b93610d0761085e856108926108266108888b8061274e565b966001600160a01b0396610d477f0000000000000000000000000000000000000068f116a894984e2db1123eb3958916610d41818a612a0a565b8a611e09565b610d5360408b01611963565b15610e6c57610d6490833091613574565b6002610d6f826127dd565b610d78816127cb565b03610e5757604091500135945b6040516331a9108f60e11b8152600481018790529084826024818985165afa918215610e4a575b600092610e1b575b5030911603610ddb575b6109bd866109b8896109b38989610dd68a850161177d565b612925565b60005b818110610deb5750610dbe565b80610e1588610e006109ec6001958789611908565b805190880151906001600160a01b03166128a4565b01610dde565b610e3c919250853d8711610e43575b610e3481836104e4565b810190612841565b9038610db4565b503d610e2a565b610e52611a33565b610dac565b50610e6360609161281c565b51015194610d85565b610e7890833091613269565b610d64565b506004610e89866127dd565b610e92816127cb565b1415610cda565b60809060231901126101d057602490565b5060c03660031901126101d0576001600160401b036004358181116101d057610ed79036906004016105ec565b610ee036610e99565b9160a4359081116101d057610ef990369060040161068f565b929091610f046117fa565b60208083013593610f1485610340565b60608401359580610f4d57505050610f2d92935061199e565b4780610f3d576100196001600255565b610f46916119e5565b38806109bd565b919392610f8f91938747926040830135610f6681611959565b15610ff257610f8892610f7761196d565b903591610f8383610340565b613688565b47906118f3565b60005b828110610fa55750505050509050610f2d565b80610fc888610fc38589610fbc6001978a8c611908565b0135611926565b611939565b80610fd5575b5001610f92565b610fec90610fe76109b384888a611908565b6119e5565b38610fce565b61100f92610ffe61196d565b90359161100a83610340565b6132d9565b610f88565b60c06003198201126101d0576001600160401b03906004358281116101d05781611040916004016101a0565b9093909260806023198401126101d05760249260a4359182116101d05761076e9160040161068f565b5061107336611014565b9361107f9391936117fa565b6020808401359461108f86610340565b606085013596806110a857505050610f2d9394506120b0565b92919493909347926110b861196d565b6110c460408401611963565b156111735760005b8481106111355750505050506110e39047906118f3565b60005b8281106110f95750505050509050610f2d565b8061111088610fc38589610fbc6001978a8c611908565b8061111d575b50016110e6565b61112f90610fe76109b384888a611908565b38611116565b8061116d61114f611149600194898861206b565b8061209b565b6111588761177d565b858d611165868c8b61206b565b013592613688565b016110cc565b60005b84811061118b5750505050506110e390610f88565b806111bd61119f611149600194898861206b565b6111a88761177d565b858d6111b5868c8b61206b565b0135926132d9565b01611176565b50346101d05760e03660031901126101d0576001600160401b036004358181116101d0576111f59036906004016105ec565b906111ff366105db565b9060c4359081116101d05761121b610c0c91369060040161068f565b906112246117fa565b60208401359361123385610340565b60608101359561124287610340565b611a40565b50346101d05760003660031901126101d0576000546040516001600160a01b039091168152602090f35b50346101d057610c0c61128336610b75565b91611290959394956117fa565b60208101359461129f86610340565b6060820135966112ae88610340565b61255c565b50346101d057604060031981813601126101d0576001600160401b03906004358281116101d0576112e89036906004016101a0565b90926024359081116101d05791611304859336906004016101a0565b9161130d6117fa565b8451958694632a05d10160e21b8652806044870188600489015252606486019160648260051b8801019781936000925b8484106113d257896000818061135e8f8e8e8e858403016024860152613039565b0381837f0000000000000000000000000000000000000068f116a894984e2db1123eb3956001600160a01b03165af180156113c5575b6113a2576100196001600255565b6113be903d806000833e6113b681836104e4565b810190612be6565b50806109bd565b6113cd611a33565b611394565b919395969798509193986114216001916063198d820301855261142e6113f88d86612fb2565b916114126114068480612cec565b89835289830190612ead565b90602094848680960190612f81565b91858185039101526117d9565b9b019301940191938a98979695939161133d565b5061144c36611014565b936114589391936117fa565b6020808401359461146886610340565b6060850135968061148157505050610f2d9394506124af565b929490939194479261149161196d565b906040936114a0858201611963565b1561157d5760005b8983821061151457505050505050506114c29047906118f3565b60005b8281106114d85750505050509050610f2d565b806114ef88610fc38589610fbc6001978a8c611908565b806114fc575b50016114c5565b61150e90610fe76109b384888a611908565b386114f5565b9061157761156187808888611571898e6115678a8561155960019e61155084611548611542828f8890612318565b80612348565b9b909d612318565b90810190612348565b9b909561177d565b99612318565b01359636916123ff565b926139e1565b016114a8565b60005b8983821061159857505050505050506114c290610f88565b906115cc611561878088886115c6898e6115678a8561155960019e61155084611548611542828f8890612318565b926138fb565b01611580565b50346101d05760003660031901126101d0576040517f0000000000000000000000000000000000000068f116a894984e2db1123eb3956001600160a01b03168152602090f35b50346101d05760003660031901126101d0576001546040516001600160a01b039091168152602090f35b50346101d05760a03660031901126101d05761165f600435610340565b61166a602435610340565b6084356001600160401b0381116101d05761168990369060040161035e565b806116a1575b60405163f23a6e6160e01b8152602090f35b6116b0916103f991369161054f565b388061168f565b50346101d05760203660031901126101d0576004356116d581610340565b6000546001600160a01b0391908216330361032f57166bffffffffffffffffffffffff60a01b6001541617600155600080f35b50346101d05760003660031901126101d0576040517f000000000000000000000000c2c862322e9c97d6244a3506655da95f05246fd86001600160a01b03168152602090f35b50634e487b7160e01b600052603260045260246000fd5b91908110156117755760051b0190565b61061b61174e565b3561178781610340565b90565b91908110156117cc575b60051b81013590601e19813603018212156101d05701908135916001600160401b0383116101d05760200182360381136101d0579190565b6117d461174e565b611794565b908060209392818452848401376000828201840152601f01601f1916010190565b60028054146118095760028055565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b3d15611879573d9061185f82610525565b9161186d60405193846104e4565b82523d6000602084013e565b606090565b8151600092839260209091019083906001600160a01b03165af16118a061184e565b50156118a857565b6040516322092f2f60e11b8152600490fd5b8151600093849391926020909201916001600160a01b03165af16118a061184e565b50634e487b7160e01b600052601160045260246000fd5b9190820391821161190057565b61035c6118dc565b9190811015611919575b60061b0190565b61192161174e565b611912565b8181029291811591840414171561190057565b8115611943570490565b634e487b7160e01b600052601260045260246000fd5b801515036101d057565b3561178781611959565b604051602081018181106001600160401b03821117611991575b6040526000815290565b611999610426565b611987565b60408201356119ac81611959565b156119cb5761035c916119bd61196d565b606082359261116584610340565b61035c916119d761196d565b60608235926111b584610340565b816119ee575050565b6000918291829182916001600160a01b03165af1611a0a61184e565b5015611a1257565b60405163d2dcf4f360e01b8152600490fd5b908160209103126101d0575190565b506040513d6000823e3d90fd5b9091939293611a516060840161177d565b60808401359580611a6a5750505061035c929350611b61565b6040516370a0823160e01b808252306004830152602096949593949293611ad9936001600160a01b038716939289929091908385602481895afa948515611b54575b600095611b2f575b5090611abf91611b61565b604051908152306004820152928390818060248101610975565b60005b828110611aed575050505050509050565b80611b0489610fc3858a610fbc6001978a8d611908565b80611b11575b5001611adc565b611b2990611b236109b384888b611908565b87611bf5565b38611b0a565b611abf92919550611b4c90853d8711610a6457610a5581836104e4565b949091611ab4565b611b5c611a33565b611aac565b611ba86060830135611b7281610340565b6080840135907f0000000000000000000000000000000000000068f116a894984e2db1123eb3956001600160a01b031690611f00565b6040820135611bb681611959565b15611bd85761035c91611bc761196d565b903591611bd383610340565b613574565b61035c91611be461196d565b903591611bf083610340565b613269565b60405163a9059cbb60e01b60208083019182526001600160a01b039490941660248301526044808301959095529381529192611c8e92916000908190611c3c6064866104e4565b60018060a01b03169260405194611c5286610465565b8786527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c656488870152519082855af1611c8861184e565b91611d77565b805190828215928315611cfe575b50505015611ca75750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152fd5b611d0e9350820181019101611d16565b388281611c9c565b908160209103126101d0575161178781611959565b15611d3257565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b91929015611d975750815115611d8b575090565b611787903b1515611d2b565b825190915015611daa5750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b828510611df0575050604492506000838284010152601f80199101168101030190fd5b8481018201518686016044015293810193859350611dcd565b604051636eb1769f60e11b81523060048201526001600160a01b038381166024830152929091831690602083604481855afa928315611ef3575b600093611ed3575b5060001992838110611e5f575b5050505050565b60009485948592611ec4575b60405191602083019463095ea7b360e01b8652166024830152604482015260448152611e96816104c9565b51925af1611ea261184e565b5015611eb2573880808080611e58565b604051631298f31b60e11b8152600490fd5b611ece8486611ffc565b611e6b565b611eec91935060203d8111610a6457610a5581836104e4565b9138611e4b565b611efb611a33565b611e43565b604051636eb1769f60e11b81523060048201526001600160a01b038381166024830152909392911690602084604481855afa938415611fef575b600094611fcf575b50828410611f51575b50505050565b6000611f97611fa58296958396611fc0575b60405163095ea7b360e01b602082019081526001600160a01b03909616602482015260448101919091529182906064820190565b03601f1981018352826104e4565b51925af1611fb161184e565b5015611eb25738808080611f4b565b611fca8587611ffc565b611f63565b611fe891945060203d8111610a6457610a5581836104e4565b9238611f42565b611ff7611a33565b611f3a565b60405163095ea7b360e01b602082019081526001600160a01b039093166024820152600060448083018290528252928392918390608081016001600160401b0381118282101761205e575b60405251925af161205661184e565b5015611eb257565b612066610426565b612047565b919081101561208e575b60051b81013590603e19813603018212156101d0570190565b61209661174e565b612075565b903590609e19813603018212156101d0570190565b9092916120bb61196d565b916120c860408301611963565b156121105760005b8581106120df57505050509050565b8061210a6120f36111496001948a8761206b565b6120fc8661177d565b876020611165868d8a61206b565b016120d0565b60005b85811061212257505050509050565b8061214d6121366111496001948a8761206b565b61213f8661177d565b8760206111b5868d8a61206b565b01612113565b9091929493946121656060850161177d565b6080850135968061217e5750505061035c93945061227d565b6040516370a0823160e01b8082523060048301526020979496939592946121d3946001600160a01b038816948a9392919084866024818a5afa95861561224a575b600096612223575b5090611abf929161227d565b60005b8281106121e7575050505050509050565b806121fe89610fc3858a610fbc6001978a8d611908565b8061220b575b50016121d6565b61221d90611b236109b384888b611908565b38612204565b611abf939291965061224190863d8811610a6457610a5581836104e4565b959091926121c7565b612252611a33565b6121bf565b909161178792811015612270575b60051b81019061209b565b61227861174e565b612265565b919061228e611b726060840161177d565b61229661196d565b906122a360408401611963565b156122e05760005b8181106122b9575050505050565b806122da6122ca6001938589612257565b856122d48861177d565b91613574565b016122ab565b60005b8181106122f1575050505050565b806123126123026001938589612257565b8561230c8861177d565b91613269565b016122e3565b919081101561233b575b60051b81013590605e19813603018212156101d0570190565b61234361174e565b612322565b903590601e19813603018212156101d057018035906001600160401b0382116101d057602001918160051b360383136101d057565b81601f820112156101d057803590612394826105fa565b926040926123a4845195866104e4565b808552602091828087019260061b850101938185116101d0578301915b8483106123d15750505050505090565b85838303126101d05783869182516123e881610465565b8535815282860135838201528152019201916123c1565b9092919261240c816105fa565b9160409161241c835194856104e4565b839581855260208095019160051b8301938185116101d05783925b8584106124475750505050505050565b6001600160401b039084358281116101d05786019083828603126101d057835161247081610465565b82358481116101d0578661248591850161237d565b8152898301359384116101d0576124a0868b9586950161237d565b83820152815201930192612437565b906124b861196d565b6040936124c6858201611963565b156125215760005b8381106124dd57505050505050565b8061251b61156187878785611571898e61156761250061154260019d898b612318565b95909761155961251183838d612318565b6020810190612348565b016124ce565b60005b83811061253357505050505050565b80612556611561878787856115c6898e61156761250061154260019d898b612318565b01612524565b90919294939461256e6060850161177d565b608085013596806125875750505061035c939450612660565b6040516370a0823160e01b8082523060048301526020979496939592946125dc946001600160a01b038816948a9392919084866024818a5afa958615612653575b60009661262c575b5090611abf9291612660565b60005b8281106125f0575050505050509050565b8061260789610fc3858a610fbc6001978a8d611908565b80612614575b50016125df565b61262690611b236109b384888b611908565b3861260d565b611abf939291965061264a90863d8811610a6457610a5581836104e4565b959091926125d0565b61265b611a33565b6125c8565b906126a66126706060850161177d565b6080850135907f0000000000000000000000000000000000000068f116a894984e2db1123eb3956001600160a01b031690611f00565b6126ae61196d565b906126bb60408501611963565b156127195760005b8181106126d1575050505050565b8061271361270d856126e9611542600196888b612318565b6126fa612511878a8d969596612318565b94906127058d61177d565b9536916123ff565b92613957565b016126c3565b60005b81811061272a575050505050565b80612748612742856126e9611542600196888b612318565b9261388f565b0161271c565b90359061015e19813603018212156101d0570190565b903590601e19813603018212156101d057018035906001600160401b0382116101d0576020019160c08202360383136101d057565b90156127a25790565b61178761174e565b600611156101d057565b50634e487b7160e01b600052602160045260246000fd5b600611156127d557565b61035c6127b4565b35611787816127aa565b903590601e19813603018212156101d057018035906001600160401b0382116101d0576020019160a08202360383136101d057565b60209080511561282a570190565b61283261174e565b0190565b519061035c82610340565b908160209103126101d0575161178781610340565b6040813603126101d057602060405191604083018381106001600160401b03821117612897575b604052803561288b81610340565b83520135602082015290565b61289f610426565b61287d565b816128ae57505050565b61035c92611bf5565b6040516370a0823160e01b8152306004820152906020826024816001600160a01b0387165afa918215612918575b6000926128f8575b50816128ae57505050565b61291191925060203d8111610a6457610a5581836104e4565b90386128ed565b612920611a33565b6128e5565b6040516331a9108f60e11b8152600481018490526001600160a01b03928316939192602082602481885afa9182156129fd575b6000926129dd575b5016301461296d57505050565b823b156101d057604051632142170760e11b81523060048201526001600160a01b0390921660248301526044820152906000908290818381606481015b03925af180156129d0575b6129bd575b50565b806129ca61035c92610480565b80610b00565b6129d8611a33565b6129b5565b6129f691925060203d8111610e4357610e3481836104e4565b9038612960565b612a05611a33565b612958565b60405163e985e9c560e01b81523060048201526001600160a01b0383811660248301529190911690602081604481855afa908115612abf575b600091612a91575b5015612a55575050565b803b156101d05760405163a22cb46560e01b81526001600160a01b039092166004830152600160248301526000908290818381604481016129aa565b612ab2915060203d8111612ab8575b612aaa81836104e4565b810190611d16565b38612a4b565b503d612aa0565b612ac7611a33565b612a43565b6001600160781b038116036101d057565b3561178781612acc565b604051627eeac760e11b81523060048201526024810184905290916001600160a01b0316602082604481845afa918215612bd9575b600092612bb9575b5081612b305750505050565b803b156101d057604051637921219560e11b81523060048201526001600160a01b039390931660248401526044830193909352606482015260a06084820152600060a482018190529091829060c490829084905af18015612bac575b612b99575b808080611f4b565b806129ca612ba692610480565b38612b91565b612bb4611a33565b612b8c565b612bd291925060203d8111610a6457610a5581836104e4565b9038612b24565b612be1611a33565b612b1c565b60209081818403126101d0578051906001600160401b0382116101d0570182601f820112156101d057805191612c1b836105fa565b936040612c2a815196876104e4565b848652828601918360e0809702860101948186116101d0578401925b858410612c57575050505050505090565b8382038781126101d057835191612c6d83610493565b60a08092126101d0578892612cd488938751612c888161043d565b8951612c93816127aa565b8152858a0151612ca281610340565b86820152888a0151898201526060808b0151908201526080808b015190612cc882610340565b82015283528801612836565b8382015260c087015186820152815201930192612c46565b903561015e19823603018112156101d0570190565b9035601e19823603018112156101d05701602081359101916001600160401b0382116101d05760a08202360383136101d057565b906006821015612d425752565b612d4a6127b4565b52565b9190808252602080920192916000905b828210612d6b575050505090565b9091929380612d866001928735612d81816127aa565b612d35565b82860135612d9381610340565b828060a01b03168382015260408087013590820152606080870135908201526080808701359082015260a08091019501920190929192612d5d565b9035601e19823603018112156101d05701602081359101916001600160401b0382116101d05760c08202360383136101d057565b9190808252602080920192916000905b828210612e20575050505090565b9091929380612e366001928735612d81816127aa565b82860135612e4381610340565b828060a01b038091168483015260408088013590830152606080880135908301526080808801359083015260a09081880135612e7e81610340565b169082015260c0908101950193920190612e12565b359060048210156101d057565b906004821015612d425752565b90612ec881612ebb84610351565b6001600160a01b03169052565b612ee7612ed760208401610351565b6001600160a01b03166020830152565b612f26612f0b612efa6040850185612d01565b610160806040870152850191612d4d565b612f186060850185612dce565b908483036060860152612e02565b91612f40612f3660808301612e93565b6080840190612ea0565b60a081013560a083015260c081013560c083015260e081013560e0830152610100808201359083015261012080820135908301526101408091013591015290565b9035601e19823603018112156101d05701602081359101916001600160401b0382116101d05781360383136101d057565b9035603e19823603018112156101d0570190565b9035601e19823603018112156101d05701602081359101916001600160401b0382116101d0578160061b360383136101d057565b9190808252602080920192916000905b828210613018575050505090565b8335855283810135858201526040948501949093019260019091019061300a565b9082818152602080910193818360051b82010194846000925b858410613063575050505050505090565b9091929394959685806130ba600193601f198682030188526130858c88612fb2565b906130ad6130a36130968480612fc6565b6040808652850191612ffa565b9285810190612fc6565b9185818503910152612ffa565b990194019401929594939190613052565b6117879161314861313d6130f06130e28580612cec565b60a0855260a0850190612ead565b60208501356130fe81612acc565b6001600160781b038091166020860152604086013561311c81612acc565b16604085015261312f6060860186612f81565b9085830360608701526117d9565b926080810190612f81565b9160808185039101526117d9565b90815180825260208092019182818360051b8201950193600080925b858410613183575050505050505090565b9091929394959681810384528751908660c060a092838101938551825283860151600281101561321b575b8483015260408087015190830152606080870151908301526080958601519582015284519384905291939101919083019085905b808210613202575050509080600192990194019401929594939190613172565b91938060019294865181520194019201889392916131e2565b6132236127b4565b6131ae565b91613252906132446060939695966080865260808601906130cb565b908482036020860152613156565b600060408401526001600160a01b03909416910152565b9060209161328b60405194859384936339eb2ac960e21b855260048501613228565b038160007f0000000000000000000000000000000000000068f116a894984e2db1123eb3956001600160a01b03165af16132c25750565b6129ba9060203d8111612ab857612aaa81836104e4565b92602092916132ff94604051958694859384936339eb2ac960e21b855260048501613228565b03917f0000000000000000000000000000000000000068f116a894984e2db1123eb3956001600160a01b03165af16132c25750565b81601f820112156101d05780359061334b826105fa565b9260409061335b825195866104e4565b838552602091828601918360a0809702860101948186116101d0578401925b85841061338b575050505050505090565b86848303126101d05784879184516133a28161043d565b86356133ad816127aa565b8152828701356133bc81610340565b838201528587013586820152606080880135908201526080808801359082015281520193019261337a565b81601f820112156101d0578035906133fe826105fa565b9260409061340e825195866104e4565b838552602091828601918360c0809702860101948186116101d0578401925b85841061343e575050505050505090565b86848303126101d0578487918451613455816104ae565b8635613460816127aa565b81528287013561346f81610340565b838201528587013586820152606080880135908201526080808801359082015260a0808801359061349f82610340565b82015281520193019261342d565b610160813603126101d0576134c0610505565b906134ca81610351565b82526134d860208201610351565b60208301526001600160401b0360408201358181116101d0576134fe9036908401613334565b604084015260608201359081116101d05761351c90369083016133e7565b606083015261352d60808201612e93565b608083015260a081013560a083015260c081013560c083015260e081013560e083015261010080820135908301526101208082013590830152610140809101359082015290565b9161358f61358a613585858061274e565b6134ad565b613c64565b916135cf604085019160206135af6135a9610a1e86612add565b87613d59565b9460009260405194859283926339eb2ac960e21b84528b60048501613228565b0381847f0000000000000000000000000000000000000068f116a894984e2db1123eb3956001600160a01b03165af1909181613668575b5061361d57604051631298f31b60e11b8152600490fd5b61363357604051631298f31b60e11b8152600490fd5b61365a610a1e926136556136619561364f610a1e602096612add565b90613d59565b6118f3565b9301612add565b03611eb257565b61368191925060203d8111612ab857612aaa81836104e4565b9038613606565b9290916136dc61369e61358a613585878061274e565b93602060408701936136bb6136b5610a1e87612add565b88613d59565b956000936040518096819482936339eb2ac960e21b84528d60048501613228565b03917f0000000000000000000000000000000000000068f116a894984e2db1123eb3956001600160a01b03165af1909181613668575061361d57604051631298f31b60e11b8152600490fd5b90815180825260208080930193019160005b828110613748575050505090565b835180518652820151858301526040909401939281019260010161373a565b90815180825260208092019182818360051b82019501936000915b8483106137925750505050505090565b90919293949584806137cb83856001950387528a5190836137bb83516040808552840190613728565b9201519084818403910152613728565b9801930193019194939290613782565b949290959391958660808701608088525260a086019660a08160051b8801019782600090815b84831061384c5750505050505060609161382b87613839938861035c98999a0360208b0152613156565b908782036040890152613767565b6001600160a01b03909216940193909352565b90919293949a609f198b82030185528b35609e198436030181121561388b576001918461387992016130cb565b9b602090810196950193019190613801565b8280fd5b604051637968958960e11b815294600094869485946138b194600487016137db565b0381837f0000000000000000000000000000000000000068f116a894984e2db1123eb3956001600160a01b03165af16138e75750565b6129ba903d806000833e6113b681836104e4565b6139226000959693949660405197889687958695637968958960e11b8752600487016137db565b03917f0000000000000000000000000000000000000068f116a894984e2db1123eb3956001600160a01b03165af16138e75750565b604051637968958960e11b8152946000948694859461397994600487016137db565b0381837f0000000000000000000000000000000000000068f116a894984e2db1123eb3956001600160a01b03165af190816139c6575b5061035c57604051631298f31b60e11b8152600490fd5b6139da903d806000833e6113b681836104e4565b50386139af565b613a086000959693949660405197889687958695637968958960e11b8752600487016137db565b03917f0000000000000000000000000000000000000068f116a894984e2db1123eb3956001600160a01b03165af190816139c6575061035c57604051631298f31b60e11b8152600490fd5b6040519061016082018281106001600160401b03821117613ab9575b60405281610140600091828152826020820152606060408201526060808201528260808201528260a08201528260c08201528260e082015282610100820152826101208201520152565b613ac1610426565b613a6f565b90815180825260208080930193019160005b828110613ae6575050505090565b909192938260a06001928751613afd828251612d35565b8084015185841b869003168285015260408082015190830152606080820151908301526080908101519082015201950193929101613ad8565b90815180825260208080930193019160005b828110613b56575050505090565b909192938260c06001928751613b6d828251612d35565b848060a01b038085830151168584015260408083015190840152606080830151908401526080808301519084015260a0809201511690820152019501910192919092613b48565b602080825282516001600160a01b03169082015260208201516001600160a01b031660408201526040820151613c11613bfb61016092836060860152610180850190613ac6565b6060850151848203601f19016080860152613b36565b92613c24608082015160a0850190612ea0565b60a081015160c084015260c081015160e084015260e081015161010090818501528101516101209081850152810151906101409182850152015191015290565b613c6c613a53565b50805160405163f07ec37360e01b81526001600160a01b039182166004820152602092613cf49284927f0000000000000000000000000000000000000068f116a894984e2db1123eb395909116908381602481855afa908115613d4c575b600091613d2f575b5061014083015260405180809581946379df72bd60e01b835260048301613bb4565b03915afa918215613d22575b600092613d0c57505090565b6117879250803d10610a6457610a5581836104e4565b613d2a611a33565b613d00565b613d469150843d8611610a6457610a5581836104e4565b38613cd2565b613d54611a33565b613cca565b6040516346423aa760e01b815260048101919091526080816024817f0000000000000000000000000000000000000068f116a894984e2db1123eb3956001600160a01b03165afa908115613e23575b6000908192613dd0575b5060008215613dc9575061178792610fc391611926565b9250505090565b91506080823d8211613e1b575b81613dea608093836104e4565b81010312610b725750613dfd8151611959565b613e0a6020820151611959565b606060408201519101519038613db2565b3d9150613ddd565b613e2b611a33565b613da856fea264697066735822122043d852a6d86a0e81b5c004298144c147b2a3f64b9b815eace91da6dde1147ef164736f6c63430008110033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000f3d63166f0ca56c3c1a3508fce03ff0cf3fb691e000000000000000000000000c2c862322e9c97d6244a3506655da95f05246fd80000000000000000000000000000000000000068f116a894984e2db1123eb395
-----Decoded View---------------
Arg [0] : owner (address): 0xf3d63166F0Ca56C3c1A3508FcE03Ff0Cf3Fb691e
Arg [1] : router (address): 0xC2c862322E9c97D6244a3506655DA95F05246Fd8
Arg [2] : exchange (address): 0x0000000000000068F116a894984e2DB1123eB395
-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 000000000000000000000000f3d63166f0ca56c3c1a3508fce03ff0cf3fb691e
Arg [1] : 000000000000000000000000c2c862322e9c97d6244a3506655da95f05246fd8
Arg [2] : 0000000000000000000000000000000000000068f116a894984e2db1123eb395
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.