// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (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;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/utils/ERC721Holder.sol)
pragma solidity ^0.8.0;
import "../IERC721Receiver.sol";
/**
* @dev Implementation of the {IERC721Receiver} interface.
*
* Accepts all token transfers.
* Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}.
*/
contract ERC721Holder is IERC721Receiver {
/**
* @dev See {IERC721Receiver-onERC721Received}.
*
* Always returns `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address,
address,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
}
// 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.7.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: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
*
* 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: AGPL-3.0
pragma solidity ^0.8.0;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
contract CurveErrorCodes {
enum Error {
OK, // No error
INVALID_NUMITEMS, // The numItem value is 0
SPOT_PRICE_OVERFLOW, // The updated spot price doesn't fit into 128 bits
DELTA_OVERFLOW, // The updated delta doesn't fit into 128 bits
SPOT_PRICE_UNDERFLOW // The updated spot price goes too low
}
}
interface ILSSVMPair {
enum PoolType {
TOKEN,
NFT,
TRADE
}
function nft() external view returns (address);
function poolType() external view returns (PoolType);
function pairVariant()
external
pure
returns (ILSSVMPairFactory.PairVariant);
function token() external view returns (ERC20);
function nftId() external view returns (uint256);
function getAllIds() external view returns (uint256[] memory);
function changeAssetRecipient(address payable newRecipient) external;
function transferOwnership(
address newOwner,
bytes calldata data
) external payable;
function swapTokenForSpecificNFTs(
uint256[] calldata nftIds,
uint256 maxExpectedTokenInput,
address nftRecipient,
bool isRouter,
address routerCaller
) external payable returns (uint256 amountUsed);
function swapNFTsForToken(
uint256[] calldata nftIds,
uint256 minExpectedTokenOutput,
address payable tokenRecipient,
bool isRouter,
address routerCaller
) external returns (uint256 outputAmount);
function getBuyNFTQuote(
uint256 assetId,
uint256 numNFTs
)
external
view
returns (
CurveErrorCodes.Error error,
uint256 newSpotPrice,
uint256 newDelta,
uint256 inputAmount,
uint256 protocolFee,
uint256 royaltyAmount
);
function getSellNFTQuote(
uint256 assetId,
uint256 numNFTs
)
external
view
returns (
CurveErrorCodes.Error error,
uint256 newSpotPrice,
uint256 newDelta,
uint256 inputAmount,
uint256 protocolFee,
uint256 royaltyAmount
);
}
interface ILSSVMPairFactory {
enum PairVariant {
ERC721_ETH,
ERC721_ERC20,
ERC1155_ETH,
ERC1155_ERC20
}
struct CreateERC721ERC20PairParams {
ERC20 token;
IERC721 nft;
ICurve bondingCurve;
address payable assetRecipient;
ILSSVMPair.PoolType poolType;
uint128 delta;
uint96 fee;
uint128 spotPrice;
address propertyChecker;
uint256[] initialNFTIDs;
uint256 initialTokenBalance;
address hookAddress;
address referralAddress;
}
function createPairERC721ERC20(
ILSSVMPairFactory.CreateERC721ERC20PairParams calldata params
) external returns (ILSSVMPair pair);
}
interface ICurve {
// Only used as a parameter for pair creation
}
interface IAllowListHook {
function updateAllowListWithNewRouter(address _newRouter) external;
}
interface IVRFConsumer {
function requestRandomWords(
uint32 numWords
) external returns (uint256 requestId);
}
interface ISudoVRFRouter {
function buyNFTsCallback(
uint256 requestId,
uint256[] calldata randomWords
) external;
function allowedSenders(address) external view returns (bool);
}
interface ISudoFactoryWrapper {
function isPair(address pair) external view returns (bool);
function isRandomPair(address pair) external view returns (bool);
function getUnlockTime(address pair) external view returns (uint256);
function getPairCreator(address pair) external view returns (address);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.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) {
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1);
///////////////////////////////////////////////
// 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. It 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)`.
// We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`.
// This gives `2**k < a <= 2**(k+1)` → `2**(k/2) <= sqrt(a) < 2 ** (k/2+1)`.
// Using an algorithm similar to the msb conmputation, we are able to compute `result = 2**(k/2)` which is a
// good first aproximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1;
uint256 x = a;
if (x >> 128 > 0) {
x >>= 128;
result <<= 64;
}
if (x >> 64 > 0) {
x >>= 64;
result <<= 32;
}
if (x >> 32 > 0) {
x >>= 32;
result <<= 16;
}
if (x >> 16 > 0) {
x >>= 16;
result <<= 8;
}
if (x >> 8 > 0) {
x >>= 8;
result <<= 4;
}
if (x >> 4 > 0) {
x >>= 4;
result <<= 2;
}
if (x >> 2 > 0) {
result <<= 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) {
uint256 result = sqrt(a);
if (rounding == Rounding.Up && result * result < a) {
result += 1;
}
return result;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.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 anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing 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 (last updated v4.8.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.0;
import "./Ownable.sol";
/**
* @dev Contract module which provides 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} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() external {
address sender = _msgSender();
require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
_transferOwnership(sender);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (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() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {ILSSVMPairFactory, ILSSVMPair, ICurve} from "./Interfaces.sol";
/**
* @title SudoFactoryWrapper
* @author 0xdaedboi
* @notice A wrapper contract for managing single-asset SudoSwap v2 ERC721-ERC20 pair creation and withdrawals with additional features like locking.
* @dev This contract provides a higher-level interface for creating and managing single-asset SudoSwap ERC721-ERC20 pairs only, with added lock mechanisms and access control.
*/
contract SudoSingleFactoryWrapper is
Ownable2Step,
ReentrancyGuard,
ERC721Holder
{
using SafeTransferLib for ERC20;
// =========================================
// Constants and Immutable Variables
// =========================================
/// @notice Maximum lock duration for a pair (30 days)
uint256 public constant MAX_LOCK_DURATION = 30 days;
/// @notice Factory instance to create pairs
ILSSVMPairFactory public immutable factory;
/// @notice Linear bonding curve instance
ICurve public immutable bondingCurve;
// =========================================
// State Variables
// =========================================
/// @notice Address of the AllowListHook for managing allowed addresses
address public allowListHook;
/// @notice Minimum duration that a pair must remain locked unless overridden
uint256 public minLockDuration;
/// @notice Maximum duration that a pair can be locked
uint256 public maxLockDuration;
/// @notice Total whitelisted token count
uint256 public whitelistedTokenCount;
/// @notice Mapping to store if a token is whitelisted
mapping(address => bool) public isWhitelistedToken;
/// @notice Mapping to store if a collection is whitelisted for pair creation
/// @dev This is used to allow certain ERC721-compatible collections that don't correctly inherit the interface
mapping(address => bool) public isWhitelistedCollection;
/// @notice Mapping to store collection-specific minimum lock durations
/// @dev If a collection has a min lock duration set that is less than the new global min lock duration, it will not be used.
mapping(address => uint256) public collectionMinLockDuration;
/// @notice Mapping to store pair information
mapping(address => PairInfo) public pairInfo;
/// @notice Mapping to store pair addresses created by a user
mapping(address => address[]) public pairsByCreator;
/// @notice Mapping to store if an address is a pair created by this factory wrapper
mapping(address => bool) public isPair;
// =========================================
// Structs
// =========================================
/// @notice Struct to store pair information
struct PairInfo {
uint256 pairUnlockTime; // Timestamp when the pair will be unlocked, 0 if unlocked
address pairCreator; // Address of the pair creator
bool hasWithdrawn; // Whether the pair has been withdrawn from
}
// =========================================
// Events
// =========================================
event PairCreated(
address indexed pair,
address indexed creator,
uint256 unlockTime
);
event PairWithdrawal(address indexed pair, address indexed withdrawer);
event AllowListHookUpdated(address newAllowListHook);
event LockDurationsUpdated(
uint256 newMinimumLockDuration,
uint256 newMaximumLockDuration
);
event WhitelistedTokensUpdated(address[] newWhitelistedTokens, bool isAdd);
event WhitelistedCollectionsUpdated(
address indexed newWhitelistedCollection,
bool isAdd
);
event CollectionMinLockDurationUpdated(
address indexed collection,
uint256 newMinLockDuration
);
// =========================================
// Constructor
// =========================================
/**
* @param _factory Address of the LSSVMPairFactory.
* @param _bondingCurve Address of the bonding curve.
* @param _allowListHook Address of the AllowListHook.
* @param _minLockDuration Minimum lock duration for a pair.
* @param _maxLockDuration Maximum lock duration for a pair.
* @param _whitelistedTokens Array of all whitelisted tokens.
*/
constructor(
address _factory,
address _bondingCurve,
address _allowListHook,
uint256 _minLockDuration,
uint256 _maxLockDuration,
address[] memory _whitelistedTokens
) {
require(
_factory != address(0) &&
_bondingCurve != address(0) &&
_allowListHook != address(0),
"Invalid factory, bonding curve, or allow list hook address"
);
require(
_minLockDuration < _maxLockDuration,
"Invalid minimum lock duration"
);
require(
_maxLockDuration <= MAX_LOCK_DURATION,
"Invalid maximum lock duration"
);
for (uint256 i = 0; i < _whitelistedTokens.length; ) {
require(
_whitelistedTokens[i] != address(0),
"Invalid token address"
);
isWhitelistedToken[_whitelistedTokens[i]] = true;
unchecked {
++i;
}
}
factory = ILSSVMPairFactory(payable(_factory));
bondingCurve = ICurve(_bondingCurve);
allowListHook = _allowListHook;
minLockDuration = _minLockDuration;
maxLockDuration = _maxLockDuration;
whitelistedTokenCount = _whitelistedTokens.length;
}
receive() external payable {
revert("ETH not accepted");
}
// =========================================
// External Functions
// =========================================
/**
* @notice Creates a new locked single-asset ERC721-ERC20 pair for selling NFTs.
* @dev ETH is not supported. Supports ERC721 interface-compliant collections or whitelisted collections.
* @param _nft Address of the NFT contract.
* @param _token Address of the ERC20 token.
* @param _spotPrice Initial spot price for the pair.
* @param _lockDuration Duration for which the pair is locked.
* @param _nftID Initial NFT ID to be added for ERC721.
* @return pairAddress Address of the created pair.
*/
function createPair(
address _nft,
address _token,
uint128 _spotPrice,
uint256 _lockDuration,
uint256 _nftID
) external nonReentrant returns (address pairAddress) {
require(_nft != address(0), "Invalid NFT address");
require(isWhitelistedToken[_token], "Token not whitelisted");
require(
_lockDuration <= maxLockDuration,
"Must be less than max lock duration"
);
require(
_lockDuration >=
Math.max(collectionMinLockDuration[_nft], minLockDuration),
"Must be greater than or equal to collection min lock duration or global min lock duration"
);
// Check if NFT is ERC721-compatible or whitelisted
if (
IERC165(_nft).supportsInterface(type(IERC721).interfaceId) ||
isWhitelistedCollection[_nft]
) {
pairAddress = _createERC721ERC20Pair(
msg.sender,
_nft,
_token,
_spotPrice,
_lockDuration,
_nftID
);
}
// Invalid NFT
else {
revert("Must be ERC721");
}
return pairAddress;
}
/**
* @notice Withdraws the specified pair for a user after lock duration.
* @dev Transfers ownership of the pair to the user.
* @param _pair Address of the pair to withdraw.
*/
function withdrawPair(address _pair) external nonReentrant {
require(_pair != address(0) && isPair[_pair], "Invalid pair address");
address sender = msg.sender;
PairInfo memory info = pairInfo[_pair];
ILSSVMPair pair = ILSSVMPair(_pair);
require(sender == info.pairCreator, "Only the creator can withdraw");
require(info.hasWithdrawn == false, "Pair already withdrawn");
require(
block.timestamp >= info.pairUnlockTime || info.pairUnlockTime == 0,
"Pair is still locked"
);
pairInfo[_pair].hasWithdrawn = true;
pair.transferOwnership(sender, "");
emit PairWithdrawal(_pair, sender);
}
// =========================================
// View Functions
// =========================================
/**
* @notice Retrieves the unlock time for a specified pair.
* @param _pair Address of the pair.
* @return The unlock time for the pair.
*/
function getUnlockTime(address _pair) external view returns (uint256) {
return pairInfo[_pair].pairUnlockTime;
}
/**
* @notice Retrieves the creator of a specified pair.
* @param _pair Address of the pair.
* @return The creator of the pair.
*/
function getPairCreator(address _pair) external view returns (address) {
return pairInfo[_pair].pairCreator;
}
/**
* @notice Retrieves all pairs created by a specific creator.
* @param _creator Address of the creator.
* @param _offset The offset of the pairs to retrieve.
* @param _limit The maximum number of pairs to retrieve.
* @return pairsInfo An array of PairInfo structs containing pair details.
* @return hasMore Whether there are more pairs to retrieve.
*/
function getAllPairsInfoByCreator(
address _creator,
uint256 _offset,
uint256 _limit
) external view returns (PairInfo[] memory, bool hasMore) {
address[] memory pairs = pairsByCreator[_creator];
uint256 end = _offset + _limit > pairs.length
? pairs.length
: _offset + _limit;
PairInfo[] memory pairsInfo = new PairInfo[](end - _offset);
for (uint256 i = _offset; i < end; ) {
pairsInfo[i - _offset] = pairInfo[pairs[i]];
// gas savings
unchecked {
++i;
}
}
return (pairsInfo, end < pairs.length);
}
// =========================================
// Admin Functions
// =========================================
/**
* @notice Updates the AllowListHook address.
* @param _newAllowListHook The new AllowListHook address.
*/
function setAllowListHook(address _newAllowListHook) external onlyOwner {
require(_newAllowListHook != address(0), "Invalid address");
allowListHook = _newAllowListHook;
emit AllowListHookUpdated(_newAllowListHook);
}
/**
* @notice Updates the whitelisted tokens for pair creation.
* @param _whitelistedTokens Array of addresses of the whitelisted tokens to add/remove.
* @param _isAdd Whether to add or remove the tokens.
*/
function setWhitelistedTokens(
address[] calldata _whitelistedTokens,
bool _isAdd
) external onlyOwner {
require(_whitelistedTokens.length > 0, "No whitelisted tokens");
for (uint256 i = 0; i < _whitelistedTokens.length; ) {
require(
_whitelistedTokens[i] != address(0),
"Invalid token address"
);
if (_isAdd) {
require(
!isWhitelistedToken[_whitelistedTokens[i]],
"Token already whitelisted"
);
isWhitelistedToken[_whitelistedTokens[i]] = true;
whitelistedTokenCount++;
} else {
require(
isWhitelistedToken[_whitelistedTokens[i]],
"Token not whitelisted"
);
isWhitelistedToken[_whitelistedTokens[i]] = false;
whitelistedTokenCount--;
}
unchecked {
++i;
}
}
emit WhitelistedTokensUpdated(_whitelistedTokens, _isAdd);
}
/**
* @notice Add or remove a whitelisted collection for pair creation.
* @param _collection Address of the collection to add/remove.
* @param _isAdd Whether to add or remove the collection.
*/
function setWhitelistedCollection(
address _collection,
bool _isAdd
) external onlyOwner {
require(_collection != address(0), "Invalid collection address");
isWhitelistedCollection[_collection] = _isAdd;
emit WhitelistedCollectionsUpdated(_collection, _isAdd);
}
/**
* @notice Updates the global minimum and maximum lock duration.
* @dev If a collection has a min lock duration set that is less than the new global min lock duration, it will not be used.
* @param _newMinLockDuration The new minimum lock duration.
* @param _newMaxLockDuration The new maximum lock duration.
*/
function setLockDurations(
uint256 _newMinLockDuration,
uint256 _newMaxLockDuration
) external onlyOwner {
require(
_newMinLockDuration < _newMaxLockDuration,
"Invalid minimum lock duration"
);
require(
_newMaxLockDuration <= MAX_LOCK_DURATION,
"Invalid maximum lock duration"
);
minLockDuration = _newMinLockDuration;
maxLockDuration = _newMaxLockDuration;
emit LockDurationsUpdated(_newMinLockDuration, _newMaxLockDuration);
}
/**
* @notice Updates the minimum lock duration for a specific collection.
* @dev Should be greater than the global minimum lock duration and less than the global maximum lock duration, otherwise it will not be used.
* @param _collection Address of the collection.
* @param _minLockDuration The new minimum lock duration.
*/
function setCollectionMinLockDuration(
address _collection,
uint256 _minLockDuration
) external onlyOwner {
require(_collection != address(0), "Invalid collection address");
require(
_minLockDuration > minLockDuration &&
_minLockDuration < maxLockDuration,
"Invalid min lock duration"
);
collectionMinLockDuration[_collection] = _minLockDuration;
emit CollectionMinLockDurationUpdated(_collection, _minLockDuration);
}
// =========================================
// Internal Functions
// =========================================
/**
* @notice Creates an ERC721-ERC20 pair.
* @param _sender Address that created the pair.
* @param _nft Address of the NFT contract.
* @param _token Address of the ERC20 token.
* @param _spotPrice Initial spot price for the pair.
* @param _lockDuration Duration for which the pair is locked.
* @param _nftID Initial NFT ID to be added for ERC721.
* @return pairAddress Address of the created pair.
*/
function _createERC721ERC20Pair(
address _sender,
address _nft,
address _token,
uint128 _spotPrice,
uint256 _lockDuration,
uint256 _nftID
) internal returns (address pairAddress) {
IERC721 nft = IERC721(_nft);
ERC20 token = ERC20(_token);
uint256[] memory initialNFTIDs = new uint256[](1);
initialNFTIDs[0] = _nftID;
// Transfer initial NFT and approve factory
nft.safeTransferFrom(_sender, address(this), _nftID);
nft.approve(address(factory), _nftID);
ILSSVMPairFactory.CreateERC721ERC20PairParams
memory params = ILSSVMPairFactory.CreateERC721ERC20PairParams({
token: token,
nft: nft,
bondingCurve: bondingCurve,
assetRecipient: payable(_sender),
poolType: ILSSVMPair.PoolType.NFT,
delta: 0,
fee: 0,
spotPrice: _spotPrice,
propertyChecker: address(0),
initialNFTIDs: initialNFTIDs,
initialTokenBalance: 0,
hookAddress: allowListHook,
referralAddress: address(0)
});
ILSSVMPair pair = factory.createPairERC721ERC20(params);
// Update pair info and emit event
_finalizePairCreation(_sender, pair, _lockDuration);
return address(pair);
}
/**
* @notice Finalizes the pair creation by storing the pair info.
* @param _sender Address that created the pair.
* @param _pair The newly created LSSVMPair.
* @param _lockDuration Duration for which the pair is locked.
*/
function _finalizePairCreation(
address _sender,
ILSSVMPair _pair,
uint256 _lockDuration
) internal {
uint256 unlockTime = _lockDuration == 0
? 0 // 0 means the pair has no lock duration
: block.timestamp + _lockDuration;
// Store pair info
pairInfo[address(_pair)] = PairInfo({
pairUnlockTime: unlockTime,
pairCreator: _sender,
hasWithdrawn: false
});
// Store pair address for creator
pairsByCreator[_sender].push(address(_pair));
// Mark pair as created by this factory wrapper
isPair[address(_pair)] = true;
emit PairCreated(address(_pair), _sender, unlockTime);
}
}
{
"compilationTarget": {
"src/bmx/SudoSingleFactoryWrapper.sol": "SudoSingleFactoryWrapper"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 1000000
},
"remappings": [
":@chainlink-contracts/=lib/foundry-chainlink-toolkit/lib/chainlink-brownie-contracts/contracts/",
":@manifoldxyz/=lib/",
":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
":@prb/math/=lib/prb-math/src/",
":clones-with-immutable-args/=lib/clones-with-immutable-args/src/",
":create3-factory/=lib/create3-factory/src/",
":ds-test/=lib/clones-with-immutable-args/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":foundry-huff/=lib/foundry-huff/src/",
":huffmate/=lib/huffmate/src/",
":manifoldxyz/=lib/royalty-registry-solidity/contracts/",
":solady/=lib/solady/src/",
":solmate/=lib/solmate/src/",
":stringutils/=lib/foundry-huff/lib/solidity-stringutils/"
]
}
[{"inputs":[{"internalType":"address","name":"_factory","type":"address"},{"internalType":"address","name":"_bondingCurve","type":"address"},{"internalType":"address","name":"_allowListHook","type":"address"},{"internalType":"uint256","name":"_minLockDuration","type":"uint256"},{"internalType":"uint256","name":"_maxLockDuration","type":"uint256"},{"internalType":"address[]","name":"_whitelistedTokens","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newAllowListHook","type":"address"}],"name":"AllowListHookUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"collection","type":"address"},{"indexed":false,"internalType":"uint256","name":"newMinLockDuration","type":"uint256"}],"name":"CollectionMinLockDurationUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newMinimumLockDuration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newMaximumLockDuration","type":"uint256"}],"name":"LockDurationsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","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"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pair","type":"address"},{"indexed":true,"internalType":"address","name":"creator","type":"address"},{"indexed":false,"internalType":"uint256","name":"unlockTime","type":"uint256"}],"name":"PairCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pair","type":"address"},{"indexed":true,"internalType":"address","name":"withdrawer","type":"address"}],"name":"PairWithdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newWhitelistedCollection","type":"address"},{"indexed":false,"internalType":"bool","name":"isAdd","type":"bool"}],"name":"WhitelistedCollectionsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"newWhitelistedTokens","type":"address[]"},{"indexed":false,"internalType":"bool","name":"isAdd","type":"bool"}],"name":"WhitelistedTokensUpdated","type":"event"},{"inputs":[],"name":"MAX_LOCK_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"allowListHook","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bondingCurve","outputs":[{"internalType":"contract ICurve","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"collectionMinLockDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_nft","type":"address"},{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint128","name":"_spotPrice","type":"uint128"},{"internalType":"uint256","name":"_lockDuration","type":"uint256"},{"internalType":"uint256","name":"_nftID","type":"uint256"}],"name":"createPair","outputs":[{"internalType":"address","name":"pairAddress","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"contract ILSSVMPairFactory","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_creator","type":"address"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"getAllPairsInfoByCreator","outputs":[{"components":[{"internalType":"uint256","name":"pairUnlockTime","type":"uint256"},{"internalType":"address","name":"pairCreator","type":"address"},{"internalType":"bool","name":"hasWithdrawn","type":"bool"}],"internalType":"struct SudoSingleFactoryWrapper.PairInfo[]","name":"","type":"tuple[]"},{"internalType":"bool","name":"hasMore","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_pair","type":"address"}],"name":"getPairCreator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_pair","type":"address"}],"name":"getUnlockTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isPair","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isWhitelistedCollection","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isWhitelistedToken","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxLockDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minLockDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"pairInfo","outputs":[{"internalType":"uint256","name":"pairUnlockTime","type":"uint256"},{"internalType":"address","name":"pairCreator","type":"address"},{"internalType":"bool","name":"hasWithdrawn","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"pairsByCreator","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":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newAllowListHook","type":"address"}],"name":"setAllowListHook","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collection","type":"address"},{"internalType":"uint256","name":"_minLockDuration","type":"uint256"}],"name":"setCollectionMinLockDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newMinLockDuration","type":"uint256"},{"internalType":"uint256","name":"_newMaxLockDuration","type":"uint256"}],"name":"setLockDurations","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collection","type":"address"},{"internalType":"bool","name":"_isAdd","type":"bool"}],"name":"setWhitelistedCollection","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_whitelistedTokens","type":"address[]"},{"internalType":"bool","name":"_isAdd","type":"bool"}],"name":"setWhitelistedTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"whitelistedTokenCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_pair","type":"address"}],"name":"withdrawPair","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]