Commit 24f02458 authored by Kegan's avatar Kegan
Browse files

recoded SkinZNFT.sol

parent 68dcfc1a
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "hardhat/console.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
......@@ -133,137 +132,146 @@ interface ArbSys {
uint callvalue, bytes data);
}
contract SkinZNFT is ERC721Enumerable, ReentrancyGuard, Ownable {
contract SkinZ is ERC721Enumerable, ReentrancyGuard, Ownable {
using Address for address;
struct SkinDefinition {
string image; //ipfs CID for skin artwork
string name;
}
mapping(uint256 => SkinDefinition) internal _nftData; // on NFT claim (mint) we generate random NFT data
struct SkinData {
string image;
struct UnmintedSkin {
string ipfs_cid;
string name;
uint256 count;
uint256 remainder;
}
SkinData[] internal _unmintedSkins;
struct MintedSkin {
string ipfs_cid;
string name;
}
uint256 internal max_token_id;
uint256 internal nft_cost;
// mapping of TokenID to MintedSkin data
mapping (uint256 => MintedSkin) private _skins;
// mapping of index to UnmintedSkin - index is zero based
mapping (uint256 => UnmintedSkin) private _unmintedIndex;
// number of remaining unique unminted skins
uint256 private _unmintedCount;
bool internal allow_claims;
// price of the NFT data
uint256 private _price;
// if unminted skins can be claimed
bool private _allowClaims;
// maximum mintable token id
uint256 public maxTokenId;
constructor() ERC721("Skin-Z", "SKZ") Ownable() {
max_token_id = 0;
nft_cost = 10000000000000000; // 0.01 ETH
allow_claims = false;
constructor() ERC721("Skin-Z", "SKZ") Ownable()
{
_unmintedCount = 0;
_price = 10000000000000000;
_allowClaims = false;
//NOTE: if we move away from arbitrum, this needs removed!
/*address arbsys_addr = address(0x64);
require(arbsys_addr.isContract(), "could not find arbsys contract at 0x64?! not deployed on arbitrum?");*/
//generate unminted skins
pushSkin(SkinData("QmVWU23fNY5RNp3mpAhGE6ZtZGcs2EnrKkN4Lmf5w96joC","Example NFT", 2)); // push a certain # of these skins to be mintable
pushSkin(SkinData("QmVWU23fNY5RNp3mpAhGE6ZtZGcs2EnrKkN4Lmf5w96joC","Rare NFT", 1)); // push a certain # of these skins to be mintable
pushSkin(SkinData("QmVWU23fNY5RNp3mpAhGE6ZtZGcs2EnrKkN4Lmf5w96joC","Common NFT", 1)); // push a certain # of these skins to be mintable
addSkin(UnmintedSkin("QmVWU23fNY5RNp3mpAhGE6ZtZGcs2EnrKkN4Lmf5w96joC","Example NFT", 2)); // push a certain # of these skins to be mintable
addSkin(UnmintedSkin("QmVWU23fNY5RNp3mpAhGE6ZtZGcs2EnrKkN4Lmf5w96joC","Rare NFT", 1)); // push a certain # of these skins to be mintable
addSkin(UnmintedSkin("QmVWU23fNY5RNp3mpAhGE6ZtZGcs2EnrKkN4Lmf5w96joC","Common NFT", 1)); // push a certain # of these skins to be mintable
}
// owner can push new skins for minting
function pushSkin(SkinData memory data) public onlyOwner {
_unmintedSkins.push(data);
max_token_id += data.count;
function addSkin(UnmintedSkin memory skin) public onlyOwner {
require(skin.remainder > 0, "Cannot add skin with 0 remaining mints");
_unmintedIndex[_unmintedCount] = skin;
_unmintedCount++;
maxTokenId += skin.remainder;
}
// batch push new mints (for when we deploy a new generation!)
function pushMultiple(SkinData[] memory data) public onlyOwner {
for(uint256 i = 0; i < data.length; i++)
function addMultipleSkins(UnmintedSkin[] memory skins) public onlyOwner {
require(skins.length > 0, "no skins to add");
for(uint256 i = 0; i < skins.length; i++)
{
_unmintedSkins.push(data[i]);
max_token_id += data[i].count;
addSkin(skins[i]);
}
}
function transferFunds(address payable reciever, uint256 amount) public onlyOwner nonReentrant {
function transferEth(address payable receiver, uint256 amount) public onlyOwner {
require(address(this).balance >= amount, "invalid transfer amount");
require(reciever != address(0x0), "cannot burn payments");
reciever.transfer(amount);
require(receiver != address(0x0), "cannot burn payments");
receiver.transfer(amount);
}
function enableClaims() public onlyOwner {
allow_claims = true;
_allowClaims = true;
}
function disableClaims() public onlyOwner {
allow_claims = false;
_allowClaims = false;
}
function setClaimCost(uint256 amount) public onlyOwner {
nft_cost = amount;
}
// NOTE: if moving from arbitrum to another blockchain, this source of randomness would need uodated!
// generate a random number (changes between arbitrum and ethereum mainnet!)
function random(uint256 seed) internal view returns(uint256)
{
/*ArbSys arbos = ArbSys(address(0x64));
return uint256(keccak256(abi.encodePacked("created by kegan hollern",block.number, seed,arbos.arbBlockNumber(), arbos.arbOSVersion())));*/
return uint256(keccak256(abi.encodePacked("created by kegan hollern",block.number, seed))); //code for non-arbitrum randomization
_price = amount;
}
function randomSkin(uint256 seed) internal returns (SkinDefinition memory)
{
require(_unmintedSkins.length > 0, "no skins are available to be randomly generated");
uint256 index = 0;
if(_unmintedSkins.length > 1) //if only one skin type remains, no reason to run RNG and waste gas
{
uint256 rng = random(seed);
index = rng % _unmintedSkins.length;
}
string memory skin_cid = _unmintedSkins[index].image;
string memory skin_name = _unmintedSkins[index].name;
if((_unmintedSkins[index].count - 1) == 0) { // no reason to decrement count to 0 (and spend gas) when we just need to remove this item
//remove skin
_unmintedSkins[index] = _unmintedSkins[_unmintedSkins.length-1]; //move last element into the position of our item we want to remove
_unmintedSkins.pop(); //delete last item which is now a duplicate
} else {
_unmintedSkins[index].count--; //more skins of this type remain, just decrement count
}
SkinDefinition memory output = SkinDefinition(skin_cid, skin_name);
return output;
}
function tokenURI(uint256 tokenId) override public view returns (string memory) {
require(_exists(tokenId), "token not claimed");
SkinDefinition memory _data = _nftData[tokenId];
assert(bytes(_data.image).length > 0); //should NEVER happen
MintedSkin memory _data = _skins[tokenId];
assert(bytes(_data.ipfs_cid).length > 0); // should NEVER happen
//nft metadata is stored on chain instead of on IPFS, this allows each NFT to have a unique name.
string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name":"',_data.name,' #',Strings.toString(tokenId), '", "description": "Skin-Z are NFTs for DayZ Standalone. While not unique, some skins are rarer than others. Owning one allows skins to be applied onto gear in DayZ.", "image": "ipfs://', _data.image, '"}'))));
// nft metadata is stored on chain instead of on IPFS, this allows each NFT to have a unique name.
string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name":"',_data.name,' #',Strings.toString(tokenId), '", "description": "Skin-Z are NFTs for DayZ Standalone. While not unique, some skins are rarer than others. Owning one allows skins to be applied onto gear in DayZ.", "image": "ipfs://', _data.ipfs_cid, '"}'))));
string memory output = string(abi.encodePacked('data:application/json;base64,', json));
return output;
}
function claim(uint256 tokenId) public payable nonReentrant {
require(allow_claims, "claims are disabled");
require(_allowClaims, "claims are disabled");
require(!_msgSender().isContract(),"smart contracts cannot mint");
require(_unmintedSkins.length > 0, "no more skins to claim");
require(tokenId <= max_token_id && tokenId > 0, "Invalid Token ID"); //tokenIds start from 1!
require(_unmintedCount > 0, "no more skins to claim");
require(tokenId <= maxTokenId && tokenId > 0, "Invalid Token ID"); //tokenIds start from 1!
require(!_exists(tokenId), "token already claimed");
require(msg.value >= nft_cost,"deposit too small");
require(msg.value >= _price,"deposit too small");
_nftData[tokenId] = randomSkin(tokenId);
_skins[tokenId] = randomSkin(tokenId);
assert(bytes(_nftData[tokenId].image).length > 0);
assert(bytes(_nftData[tokenId].name).length > 0);
assert(bytes(_skins[tokenId].ipfs_cid).length > 0);
assert(bytes(_skins[tokenId].name).length > 0);
_safeMint(_msgSender(), tokenId);
if(msg.value > nft_cost)
if(msg.value > _price)
{
payable(_msgSender()).transfer(msg.value - nft_cost);
payable(_msgSender()).transfer(msg.value - _price);
}
}
function random(uint256 seed) private view returns(uint256) {
/*ArbSys arbos = ArbSys(address(0x64));
return uint256(keccak256(abi.encodePacked("created by kegan hollern",block.number, seed,arbos.arbBlockNumber(), arbos.arbOSVersion())));*/
return uint256(keccak256(abi.encodePacked("created by kegan hollern",block.number, seed))); //code for non-arbitrum randomization
}
function randomSkin(uint256 seed) private returns (MintedSkin memory) {
require(_unmintedCount > 0, "no skins are available to be randomly generated");
uint256 index = 0;
if(_unmintedCount > 1) //if only one skin type remains, no reason to run RNG and waste gas
{
uint256 rng = random(seed);
index = rng % _unmintedCount;
}
string memory skin_cid = _unmintedIndex[index].ipfs_cid;
string memory skin_name = _unmintedIndex[index].name;
if((_unmintedIndex[index].remainder - 1) == 0) { // no reason to decrement count to 0 (and spend gas) when we just need to remove this item
// remove skin
if(index != _unmintedCount-1)
{
_unmintedIndex[index] = _unmintedIndex[_unmintedCount-1]; //move last element into the position of our item we want to remove
}
delete _unmintedIndex[_unmintedCount-1];//delete last element
} else {
_unmintedIndex[index].remainder--; //more skins of this type remain, just decrement count
}
MintedSkin memory output = MintedSkin(skin_cid, skin_name);
return output;
}
// ETH can only be sent to this wallet during claims (otherwise we revert)
receive() external payable {
revert();
......@@ -271,5 +279,4 @@ contract SkinZNFT is ERC721Enumerable, ReentrancyGuard, Ownable {
fallback() external payable {
revert();
}
}
\ No newline at end of file
......@@ -20,7 +20,7 @@ task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
defaultNetwork: "arbitrum_mainnet",
defaultNetwork: "hardhat",
networks: {
hardhat: {
},
......
......@@ -11,6 +11,7 @@
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-etherscan": "^2.1.6",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "^4.3.1",
"@openzeppelin/hardhat-upgrades": "^1.10.0",
......@@ -1182,6 +1183,46 @@
"hardhat": "^2.0.0"
}
},
"node_modules/@nomiclabs/hardhat-etherscan": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-2.1.6.tgz",
"integrity": "sha512-gCvT5fj8GbXS9+ACS3BzrX0pzYHHZqAHCb+NcipOkl2cy48FakUXlzrCf4P4sTH+Y7W10OgT62ezD1sJ+/NikQ==",
"dev": true,
"dependencies": {
"@ethersproject/abi": "^5.1.2",
"@ethersproject/address": "^5.0.2",
"cbor": "^5.0.2",
"debug": "^4.1.1",
"fs-extra": "^7.0.1",
"node-fetch": "^2.6.0",
"semver": "^6.3.0"
},
"peerDependencies": {
"hardhat": "^2.0.4"
}
},
"node_modules/@nomiclabs/hardhat-etherscan/node_modules/cbor": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/cbor/-/cbor-5.2.0.tgz",
"integrity": "sha512-5IMhi9e1QU76ppa5/ajP1BmMWZ2FHkhAhjeVKQ/EFCgYSEaeVaoGtL7cxJskf9oCCk+XjzaIdc3IuU/dbA/o2A==",
"dev": true,
"dependencies": {
"bignumber.js": "^9.0.1",
"nofilter": "^1.0.4"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@nomiclabs/hardhat-etherscan/node_modules/nofilter": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/nofilter/-/nofilter-1.0.4.tgz",
"integrity": "sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/@nomiclabs/hardhat-waffle": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.1.tgz",
......@@ -1924,6 +1965,15 @@
"integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==",
"dev": true
},
"node_modules/bignumber.js": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz",
"integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
......@@ -16477,6 +16527,39 @@
"dev": true,
"requires": {}
},
"@nomiclabs/hardhat-etherscan": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-2.1.6.tgz",
"integrity": "sha512-gCvT5fj8GbXS9+ACS3BzrX0pzYHHZqAHCb+NcipOkl2cy48FakUXlzrCf4P4sTH+Y7W10OgT62ezD1sJ+/NikQ==",
"dev": true,
"requires": {
"@ethersproject/abi": "^5.1.2",
"@ethersproject/address": "^5.0.2",
"cbor": "^5.0.2",
"debug": "^4.1.1",
"fs-extra": "^7.0.1",
"node-fetch": "^2.6.0",
"semver": "^6.3.0"
},
"dependencies": {
"cbor": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/cbor/-/cbor-5.2.0.tgz",
"integrity": "sha512-5IMhi9e1QU76ppa5/ajP1BmMWZ2FHkhAhjeVKQ/EFCgYSEaeVaoGtL7cxJskf9oCCk+XjzaIdc3IuU/dbA/o2A==",
"dev": true,
"requires": {
"bignumber.js": "^9.0.1",
"nofilter": "^1.0.4"
}
},
"nofilter": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/nofilter/-/nofilter-1.0.4.tgz",
"integrity": "sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA==",
"dev": true
}
}
},
"@nomiclabs/hardhat-waffle": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.1.tgz",
......@@ -17109,6 +17192,12 @@
"integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==",
"dev": true
},
"bignumber.js": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz",
"integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==",
"dev": true
},
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment