// Contract: SNEX
// Target function: function presaleMint(bytes32[] memory _proof) public payable
 internal pure returns (bool) {
		return processProof(proof, leaf) == root;
	}

	function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
		bytes32 computedHash = leaf;
		for(uint256 i = 0; i < proof.length; i++) {
			computedHash = _hashPair(computedHash, proof[i]);
		}
		return computedHash;
	}

	function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
		bytes32 computedHash = leaf;
		for(uint256 i = 0; i < proof.length; i++) {
			computedHash = _hashPair(computedHash, proof[i]);
		}
		return computedHash;
	}

	function processMultiProof(bytes32[] memory proof, bool[] memory proofFlags, bytes32[] memory leaves) internal pure returns (bytes32 merkleRoot) {
		uint256 leavesLen = leaves.length;
		uint256 totalHashes = proofFlags.length;

		require(leavesLen + proof.length - 1 == totalHashes, _strInvalidMultiproof);

		bytes32[] memory hashes = new bytes32[](totalHashes);
		uint256 leafPos = 0;
		uint256 hashPos = 0;
		uint256 proofPos = 0;

		for(uint256 i = 0; i < totalHashes; i++) {
			bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
			bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
			hashes[i] = _hashPair(a, b);
		}

		if(totalHashes > 0) {
			return hashes[totalHashes - 1];
		}
		else if(leavesLen > 0) {
			return leaves[0];
		}
		else {
			return proof[0];
		}
	}

	function processMultiProofCalldata(bytes32[] calldata proof, bool[] calldata proofFlags, bytes32[] memory leaves) internal pure returns (bytes32 merkleRoot) {
		uint256 leavesLen = leaves.length;
		uint256 totalHashes = proofFlags.length;

		require(leavesLen + proof.length - 1 == totalHashes, _strInvalidMultiproof);

		bytes32[] memory hashes = new bytes32[](totalHashes);
		uint256 leafPos = 0;
		uint256 hashPos = 0;
		uint256 proofPos = 0;

		for(uint256 i = 0; i < totalHashes; i++) {
			bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
			bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
			hashes[i] = _hashPair(a, b);
		}

		if(totalHashes > 0) {
			return hashes[totalHashes - 1];
		}
		else if(leavesLen > 0) {
			return leaves[0];
		}
		else {
			return proof[0];
		}
	}

	function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
		return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
	}

	function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
		/// @solidity memory-safe-assembly
		assembly {
			mstore(0x00, a)
			mstore(0x20, b)
			value := keccak256(0x00, 0x40)
		}
	}
}

pragma solidity ^0.8.17;
contract SNEX is Context, ERC721, IERC4906 {
	using MerkleProof for bytes32[];

	bool private _boolPaused = false;
	bool private _boolAllowList = true;

	bytes32 _allowlistMerkleRoot;

	uint256 private _presaleMintFee = 10000000000000000; //10000000000000000, 0.01 ETH
	uint256 private _publicMintFee = 50000000000000000; //50000000000000000, 0.05 ETH
	uint256 private _mintedTokens = 0;
	uint256 private _maxCap = 5000;
	uint256 private _presaleMaxSales = 1000;
	uint256 private _sellerFeePoints = 750; // 7.5%

	address private _addrContractOwner = 0x4DaE7E6c0Ca196643012cDc526bBc6b445A2ca59;

	string private _strDescription = 'Proof of rich';
	string private _strBaseURI = 'https://proofofrich.com/';
	string private _strExternalURLPrefix = string(abi.encodePacked(_strBaseURI, 'token/'));
	string private _strScript = '';
	string private _strCommonBlack = 'common-black';
	string private _strCommonWhite = 'common-white';
	string private _strCommonBlack2 = 'common-black-common-black';
	string private _strCommonWhite2 = 'common-white-common-white';
	string private _strCommonCommon = 'common-common';
	string private _strCommonCommon2 = 'common-common-common-common';

	string[27] private _strPalette = ['#00a5e3','#8dd7bf','#ff96c5','#ff5768','#ffbf65','#fc6238','#ffd872','#f2d4cc','#e77577','#6c88c4','#c05780','#ff828b','#e7c582','#00b0ba','#0065a2','#00cdac','#ff6f68','#ffdacc','#ff60a8','#cff800','#ff5c77','#4dd091','#ffec59','#ffa23a','#74737a','#ffffff','#292929'];

	string private _strSVGContract = '';
	string private _strSVGPrefix = '';
	string private _strSVGUnrevealed = '';

	string private _strContractJSON = string(abi.encodePacked(
		'{',
			_strJSONName, _strTokenName, '",',
			_strJSONDescription, _strDescription, '",',
			'"symbol": "', _strTokenTicker, '",',
			'"image": "', _strSVGContract, '",',
			'"external_link": "', _strBaseURI, '",',
			'"total_supply": "', Strings.toString(_maxCap),'",',
			'"seller_fee_basis_points": "', Strings.toString(_sellerFeePoints),'",',
			'"fee_recipient": "', Strings.toHexString(_addrContractOwner), '"'
		'}'
	));

	struct TokenProperties {
		bool revealed;
		uint256 rarity;
		uint256[] tokenColors;
	}

	mapping(uint => TokenProperties) private tokenData;

// ==================================================================
//                              MODIFIERS
// ==================================================================

	modifier insideBounds(uint256 tokenId) {
		require(tokenId > 0, _strOutOfBounds);
		require(tokenId <= _mintedTokens, _strOutOfBounds);
		_;
	}

	modifier onlyAdmin {
		require(_msgSender() == _addrContractOwner, _strNotAuthorized);
		_;
	}

	constructor() ERC721() {}
// ==================================================================
//                       AUX INTERNAL FUNCTIONS
// ==================================================================

	function _buildArrayColors(uint256 tokenId) internal view returns(string memory) {
		string memory _retValue = "[";

		for(uint i = 0; i < 4; i++) {
			_retValue = string(abi.encodePacked(_retValue, Strings.toString(tokenData[tokenId].tokenColors[i])));
			if(i < 3) {
				_retValue = string(abi.encodePacked(_retValue, ","));
			}
		}
		_retValue = string(abi.encodePacked(_retValue, "]"));

		return _retValue;
	}

	function _buildHexColor(uint256 _tokenId, uint256 _colorIndex) internal view returns(string memory) {
		return _strPalette[tokenData[_tokenId].tokenColors[_colorIndex]];
	}

	function _buildSVG(uint256 _tokenId) internal view returns(string memory) {
		return string(abi.encodePacked(
			_strSVGPrefix, _buildHexColor(_tokenId, 0), ";fill-opacity:1;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round' id='segment1' cx='65.458244' cy='10.460792' r='13.229167' transform='rotate(36.46956)' /><circle style='fill:", _buildHexColor(_tokenId, 1), ";fill-opacity:1;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round' id='segment2' cx='69.997108' cy='8.2027063' r='13.229167' transform='rotate(36.46956)' /><circle style='fill:", _buildHexColor(_tokenId, 2), ";fill-opacity:1;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round' id='segment3' cx='76.831429' cy='8.6074381' r='13.229167' transform='rotate(36.46956)' /><circle style='fill:", _buildHexColor(_tokenId, 3), ";fill-opacity:1;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round' id='segment4' cx='82.556908' cy='11.740624' r='13.229167' transform='rotate(36.46956)' /></svg>"));
	}

	function _ownerBalance(uint256 tokenId) internal view returns(uint256) {
		address owner = ownerOf(tokenId);
		return owner.balance;
	}

	function _getTokenRarity(uint256 _rarity) internal view returns(string memory) {
		string memory retValue = '';
		if(_rarity == 1) {
			retValue = _strCommonBlack;
		}
		else if(_rarity == 2) {
			retValue = _strCommonWhite;
		}
		else if(_rarity == 3) {
			retValue = _strCommonBlack2;
		}
		else if(_rarity == 4) {
			retValue = _strCommonWhite2;
		}
		else if(_rarity == 5) {
			retValue = _strCommonCommon;
		}
		else if(_rarity == 6) {
			retValue = _strCommonCommon2;
		}
		else {
			retValue = 'Unrevealed';
		}

		return retValue;
	}

// ==================================================================
//                       MAIN PUBLIC FUNCTIONS
// ==================================================================

// ------------------------------------------------------------------
//                               GETTERS
// ------------------------------------------------------------------
	/// @dev Returns the URI to the contract's JSON.
	///	 Note: can be a URL or a base64-encoded JSON.
	function contractURI() public view returns(string memory) {
		string memory _retValue = string(
			abi.encodePacked(
				'data:application/json;base64,',
				Base64.encode(abi.encodePacked(_strContractJSON))
			)
		);

		return _retValue;
	}

	/// @dev Generates the token HTML.
	/// @param tokenId Token ID.
	function generateHTML(uint256 tokenId) insideBounds(tokenId) public view returns(string memory) {
		bytes memory html = abi.encodePacked(
			"<!DOCTYPE html><html><head><meta charset='UTF-8' /><style>body{margin: 0;}canvas{background:#000;display:block;}</style></head><body><canvas id='snex'></canvas><script>const tokenId=", Strings.toString(tokenId), ";let minimum=5;let balance=", Strings.toString(_ownerBalance(tokenId)),";if(balance==0){minimum=0}let snakeLength=Math.floor(balance/1000000000000000000)+minimum;if(snakeLength>9995){snakeLength=9995}const tokenColors=", _buildArrayColors(tokenId),";</script><script>", _strScript, "</script></body></html>"
		);

		return string(
			abi.encodePacked(
				'data:text/html;base64,',
				Base64.encode(html)
			)
		);
	}

	/// @dev Returns contract's total minted tokens.
	function mintedTokens() public view returns(uint256) {
		return _mintedTokens;
	}

	/// @dev Returns the contract's name.
	function name() public view returns(string memory) {
		return _strTokenName;
	}

	function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
		return interfaceId == bytes4(0x49064906) || super.supportsInterface(interfaceId);
	}

	/// @dev Returns the contract's symbol, or ticker.
	function symbol() public view returns(string memory) {
		return _strTokenTicker;
	}

	/// @dev Returns a base64-encoded JSON that describes the given tokenID
	/// @param tokenId Token ID.
	function tokenURI(uint256 tokenId) insideBounds(tokenId) public view returns(string memory) {
		string memory _strSuffix = '';
		if(tokenData[tokenId].revealed == true) {
			_strSuffix = string(abi.encodePacked(
				'"image": "', _buildSVG(tokenId), '",',
				'"animation_url": "', generateHTML(tokenId), '"'
			));
		}
		else {
			_strSuffix = string(abi.encodePacked(
				'"image": "', _strSVGUnrevealed, '"'
			));
		}
		return string(
			abi.encodePacked(
				'data:application/json;base64,',
				Base64.encode(abi.encodePacked(
					'{',
						_strJSONName, 'SNEX #', Strings.toString(tokenId), '",',
						_strJSONDescription, _strDescription, '",',
						'"external_url": "', _strExternalURLPrefix, Strings.toString(tokenId), '",',
						'"attributes": [',
							'{',
								'"trait_type": "Rarity", ',
								'"value": "', _getTokenRarity(tokenData[tokenId].rarity), '"',
							'}',
						'],',
						_strSuffix,
					'}'
				))
			)
		);
	}

	/// @dev Returns contract's max supply.
	function totalSupply() public view returns(uint256) {
		return _maxCap;
	}

// ------------------------------------------------------------------
//                               SETTERS
// ------------------------------------------------------------------
	/// @dev Changes the contract's owner.
	///	 Note: Only current contract's owner can change this.
	/// @param _newOwner Address of the new owner.
	function changeContractOwner(address _newOwner) onlyAdmin public {
		_addrContractOwner = _newOwner;
	}

	/// @dev Changes mint fee.
	///	 Note: Only contract's owner can change this.
	/// @param _newValue New value in wei.
	/// @param _isPresale true for presale fee, false for public fee.
	function changeMintFee(uint256 _newValue, bool _isPresale) onlyAdmin public {
		if(_isPresale) {
			_presaleMintFee = _newValue;
		}
		else {
			_publicMintFee = _newValue;
		}
	}

	/// @dev Changes onchain contents.
	///	 Note: Only contract's owner can change this.
	/// @param _string New content, minified.
	/// @param _index 1 for contract-level SVG, 2 for unrevealed SVG, 3 for revealed SVG prefix, 4 for JS script, 5 for description, 6 for base URI.
	function changeOnchainData(string memory _string, uint8 _index) onlyAdmin public {
		if(_index == 1) {
			_strSVGContract = _string;
		}
		else if(_index == 2) {
			_strSVGUnrevealed = _string;
		}
		else if(_index == 3) {
			_strSVGPrefix = _string;
		}
		else if(_index == 4) {
			_strScript = _string;
		}
		else if(_index == 5) {
			_strDescription = _string;
		}
		else if(_index == 6) {
			_strBaseURI = _string;
		}
		if(_mintedTokens > 0) {
			emit BatchMetadataUpdate(1, (_mintedTokens + 1));
		}
	}

	/// @dev Mints a new token during public sale.
	function mint() public payable {
		uint256 _newTokenId = _mintedTokens + 1;
		require(!_boolPaused, _strPaused);
		require(_newTokenId > 0, _strOutOfBounds);
		require(_newTokenId <= _maxCap, _strOutOfBounds);
		require(!_boolAllowList, _strInPresale);

		if(_msgSender() != _addrContractOwner) {
			require(msg.value >= _publicMintFee, _strNotEnoughBalance);
		}

		_mintedTokens++;
		_mint(_msgSender(), _newTokenId);
		tokenData[_newTokenId].revealed = false;
		tokenData[_newTokenId].rarity = 0;
	}