Skip to content

Commit

Permalink
SFT Visions support
Browse files Browse the repository at this point in the history
  • Loading branch information
channing-magiceden committed Mar 27, 2024
1 parent 196f485 commit ffde07e
Show file tree
Hide file tree
Showing 2 changed files with 219 additions and 1 deletion.
212 changes: 212 additions & 0 deletions contracts/Visions.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
//SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/common/ERC2981.sol";
import {ERC721A, ERC721CM, IERC721A, ERC721ACQueryable} from "./ERC721CM.sol";
import {UpdatableRoyalties} from "./royalties/UpdatableRoyalties.sol";

/**
* @title SFT Visions smart contract
* @dev Vanilla ERC721CM with a few tweaks
* - ✅ no global wallet limit (through init when deploying script)
* - ✅ operator airdrop methods (for GP airdrop, treasury, etc.)
* - ✅ trades are locked for 3 days for first 2000 tokens
* - ✅ all trades are locked until collection is minted out
* - ✅ ERC2981 royalties
*/
contract Visions is ERC721CM, UpdatableRoyalties {
/*
All tokens whose id is less than this value (or equal) are transfer locked for 3 days (transferLockTimestamp).
All tokens whose id is more than this value are not locked from immediate transfers.
*/
uint16 private _transferLockMaxId = 2000;
uint64 private _transferLockTimestamp = 0;
// when set to true, it will bypass "transfer locked until minted out"
bool private _transferLockOverride = false;
address private _sftOperator;
error NotEnoughGenesisPasses();
error TransferLockedForNow();
error TransferLockedUntilMintedOut();

constructor(
string memory collectionName,
string memory collectionSymbol,
string memory tokenURISuffix,
uint256 globalWalletLimit,
address cosigner,
uint64 timestampExpirySeconds,
address mintCurrency,
address royaltyReceiver,
uint96 royaltyFeeNumerator,
uint64 transferLockTimestamp,
address sftOperator
)
ERC721CM(
collectionName,
collectionSymbol,
tokenURISuffix,
8000,
globalWalletLimit,
cosigner,
timestampExpirySeconds,
mintCurrency
)
UpdatableRoyalties(royaltyReceiver, royaltyFeeNumerator)
{
_transferLockTimestamp = transferLockTimestamp;
_sftOperator = sftOperator;
}

/**
* @dev Returns the token id to start from (1).
*/
function _startTokenId() internal view virtual override returns (uint256) {
return 1;
}

/**
* @dev Returns the total number of tokens minted.
*/
function totalMinted() public view returns (uint256) {
return _totalMinted();
}

/**
* @dev Transfers are locked for a pre-determined window for earlier stages.
* @param from - the address to transfer from
* @param to - the address to transfer to
* @param tokenId - the id of the token to transfer
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) public payable virtual override(ERC721A, IERC721A) {
// transfer locked until minted out or manual override
if (
!_transferLockOverride &&
totalMinted() < this.getMaxMintableSupply()
) {
revert TransferLockedUntilMintedOut();
}

// tokens minted in earlier phases are locked for a specific time
if (
tokenId <= _transferLockMaxId &&
block.timestamp < _transferLockTimestamp
) {
revert TransferLockedForNow();
}

super.transferFrom(from, to, tokenId);
}

// ERC2981 Royalty START
function supportsInterface(
bytes4 interfaceId
)
public
view
virtual
override(ERC2981, ERC721ACQueryable, IERC721A)
returns (bool)
{
return
ERC721ACQueryable.supportsInterface(interfaceId) ||
ERC2981.supportsInterface(interfaceId);
}
// ERC2981 Royalty END

// SFT operator START
modifier onlySftOperator() {
require(
msg.sender == _sftOperator,
"Visions: caller is not the SFT operator"
);
_;
}
modifier onlyOwnerOrSftOperator() {
require(
msg.sender == owner() || msg.sender == _sftOperator,
"Visions: caller is not the owner or SFT operator"
);
_;
}

/**
* @dev Updates the transfer lock variables (max id and timestamp).
* @param newTransferLockMaxId The maximum token id to lock transfers for.
* @param timestamp The timestamp to lock transfers until.
*/
function updateTransferLock(
uint16 newTransferLockMaxId,
uint64 timestamp
) external onlySftOperator {
_transferLockMaxId = newTransferLockMaxId;
_transferLockTimestamp = timestamp;
}

/**
* @dev Enables or disables "transfer lock until minted out".
* @param transferLockOverride The new value for the transfer lock override.
*/
function setTransferLockOverride(
bool transferLockOverride
) external onlySftOperator {
_transferLockOverride = transferLockOverride;
}

/**
* @dev Mints token(s) by owner.
* This is the first phase, where we airdrop an NFT for each owner of a GP.
* The owners parameter values will come from a snapshot.
* @param owners - the addresses to mint for
*/
function airdropForGenesisPassHolders(
address[] calldata owners
) external onlySftOperator {
// for each id we mint a PFP for the owner
// we cannot batch mint to respect the id mapping (GP id 1 => PFP id 1, etc.)
for (uint16 i = 0; i < owners.length; i++) {
// minting the token
_safeMint(owners[i], 1);
}
}

/**
* @dev Mints token(s) by owner.
*
* NOTE: This function bypasses validations thus only available for owner.
* This is typically used for owner to pre-mint or mint the remaining of the supply.
*/
function operatorMint(
uint32 qty,
address to
) external onlySftOperator hasSupply(qty) {
_safeMint(to, qty);
}

/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);

/**
* @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 override onlyOwnerOrSftOperator {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}

function disableSftOperator() external onlySftOperator {
_sftOperator = address(0);
}
// SFT operator END
}
8 changes: 7 additions & 1 deletion scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,14 @@ export const deploy = async (
overrides.gasLimit = hre.ethers.BigNumber.from(args.gaslimit);
}

contractName = 'Visions';
const contractFactory = await hre.ethers.getContractFactory(contractName);

const params = [
args.name,
args.symbol,
args.tokenurisuffix,
maxsupply,
//maxsupply,
hre.ethers.BigNumber.from(args.globalwalletlimit),
args.cosigner ?? hre.ethers.constants.AddressZero,
args.timestampexpiryseconds ?? 300,
Expand All @@ -106,6 +107,11 @@ export const deploy = async (
);
}

params.push(
1711606451,
'0x69C3Cc3a652e72Da3748387005BFC48DF6EaBF78',
)

console.log(
`Going to deploy ${contractName} with params`,
JSON.stringify(args, null, 2),
Expand Down

0 comments on commit ffde07e

Please sign in to comment.