├── .env.example ├── .gitignore ├── tsconfig.json ├── scripts ├── planetsMetadata.ts ├── DrGreenMarketplace.ts └── DrGreenNFT.ts ├── contracts ├── PlanetsMetadataInterface.sol ├── DrGreenNFTInterface.sol ├── DrGreenTimelock.sol ├── IERC7496.sol ├── DrGreenMarketplace.sol ├── DynamicTraits.sol ├── DrGreenNFT.sol └── PlanetsMetadata.sol ├── hardhat.config.ts ├── package.json ├── LICENSE.md ├── README.md └── test ├── DrGreenMarketplace.test.js └── DrGreenNFT.test.js /.env.example: -------------------------------------------------------------------------------- 1 | ALCHEMY_API_URL="" 2 | METAMASK_PRIVATE_KEY="" 3 | ETHERSCAN_API_KEY="" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules 3 | typechain-types 4 | artifacts 5 | package-lock.json -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /scripts/planetsMetadata.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | 3 | async function PlanetsMetadata() { 4 | 5 | const nftContract = await ethers.getContractFactory("PlanetsMetadata"); 6 | 7 | const contract = await nftContract.deploy(); 8 | 9 | console.log("Contract deployed at:", contract.target) 10 | } 11 | 12 | // We recommend this pattern to be able to use async/await everywhere 13 | // and properly handle errors. 14 | PlanetsMetadata().catch((error) => { 15 | console.error(error); 16 | process.exitCode = 1; 17 | }); 18 | -------------------------------------------------------------------------------- /scripts/DrGreenMarketplace.ts: -------------------------------------------------------------------------------- 1 | import { ethers, upgrades } from "hardhat"; 2 | 3 | async function DrGreenMarketplace() { 4 | const DrGreenMarketplace = await ethers.getContractFactory( 5 | "DrGreenMarketplace" 6 | ); 7 | const marketplace = await DrGreenMarketplace.deploy( 8 | "0x4758FFb3EF10bf657d4264AEd72db6Db564a9e11" 9 | ); 10 | await marketplace.waitForDeployment(); 11 | console.log("Marketplace deployed to:", await marketplace.getAddress()); 12 | } 13 | 14 | DrGreenMarketplace().catch((error) => { 15 | console.error(error); 16 | process.exitCode = 1; 17 | }); 18 | -------------------------------------------------------------------------------- /contracts/PlanetsMetadataInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.22; 3 | 4 | interface PlanetsMetadataInterface { 5 | function setRandomTokenMetadata(uint8 planetNo, uint256 tokenId) 6 | external 7 | returns (uint16); 8 | 9 | function setTokenMetadataId(uint256 tokenId, uint16 metadataId) external; 10 | 11 | function getMetadataIdByToken(uint256 tokenId) 12 | external 13 | view 14 | returns (uint256); 15 | 16 | function getAvailableNFTsbyPlanet(uint8 planetNo) 17 | external 18 | view 19 | returns (uint256); 20 | } -------------------------------------------------------------------------------- /contracts/DrGreenNFTInterface.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.8.22; 4 | 5 | interface DrGreenNFTInterface { 6 | function addClient(uint16 tokenId, uint256 clientsToAdd) external; 7 | 8 | function addTransaction( 9 | uint16 tokenId, 10 | uint256 txsToAdd, 11 | uint256 txsAmtToAdd, 12 | bool isRefunded 13 | ) external; 14 | 15 | function ownerOf(uint256 tokenId) external view returns (address); 16 | 17 | function royaltyInfo( 18 | uint256 tokenId, 19 | uint256 salePrice 20 | ) external view returns (address, uint256); 21 | 22 | function safeTransferFrom(address from, address to, uint256 tokenId) external; 23 | } 24 | -------------------------------------------------------------------------------- /contracts/DrGreenTimelock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/governance/TimelockController.sol"; 5 | 6 | contract DrGreenTimelock is TimelockController { 7 | /** 8 | * @dev Constructor for the TimelockController 9 | * @param minDelay Minimum delay for executing proposals 10 | * @param proposers List of addresses with proposer role 11 | * @param executors List of addresses with executor role 12 | */ 13 | constructor( 14 | uint256 minDelay, 15 | address[] memory proposers, 16 | address[] memory executors, 17 | address admin 18 | ) TimelockController(minDelay, proposers, executors, admin) { 19 | // Timelock initialized with given parameters 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /scripts/DrGreenNFT.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | 3 | async function DrGreenNFT() { 4 | const nftContract = await ethers.getContractFactory("DrGreenNFT"); 5 | 6 | const contract = await nftContract.deploy( 7 | "0xb3182Ae1069253DC69910F6475578878d3679DAc", 8 | "ipfs://QmNhYFSkkiWKAvL56iZkEfpLmvwubihNUroHqKSMsUQkWi/112_md/", 9 | "ipfs://Qma9B7dUc1FSnTjidRYNHx9R2KRGvXBWQsfmXnW8jpRNF8", 10 | "0x6397277046830b948B8029e2612d3CB23fB7c2b7", 11 | "0x32E71eD285659C4038c938c487C915fbc684ecf4" 12 | ); 13 | console.log("Contract deployed at:", contract.target); 14 | } 15 | 16 | // We recommend this pattern to be able to use async/await everywhere 17 | // and properly handle errors. 18 | DrGreenNFT().catch((error) => { 19 | console.error(error); 20 | process.exitCode = 1; 21 | }); 22 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/config"; 2 | import "@nomiclabs/hardhat-ethers"; 3 | import '@openzeppelin/hardhat-upgrades'; 4 | import "@nomicfoundation/hardhat-toolbox"; 5 | require("dotenv").config(); 6 | 7 | const { ALCHEMY_API_URL, METAMASK_PRIVATE_KEY, ETHERSCAN_API_KEY } = process.env; 8 | 9 | const config: HardhatUserConfig = { 10 | defaultNetwork: "sepolia", 11 | networks: { 12 | sepolia: { 13 | url: `${ALCHEMY_API_URL}`, 14 | accounts: [`${METAMASK_PRIVATE_KEY}`], 15 | }, 16 | }, 17 | solidity: { 18 | version: "0.8.22", 19 | settings: { 20 | optimizer: { 21 | enabled: true, 22 | runs: 200 23 | }, 24 | }, 25 | }, 26 | etherscan: { 27 | apiKey: ETHERSCAN_API_KEY 28 | } 29 | }; 30 | 31 | export default config; 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dr-green-nft", 3 | "devDependencies": { 4 | "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", 5 | "@nomicfoundation/hardhat-ethers": "^3.0.5", 6 | "@nomicfoundation/hardhat-network-helpers": "^1.0.10", 7 | "@nomicfoundation/hardhat-toolbox": "^4.0.0", 8 | "@nomicfoundation/hardhat-verify": "^2.0.4", 9 | "@openzeppelin/hardhat-upgrades": "^3.0.5", 10 | "@typechain/ethers-v6": "^0.5.1", 11 | "@typechain/hardhat": "^9.1.0", 12 | "@types/chai": "^4.3.12", 13 | "@types/mocha": "^10.0.6", 14 | "chai": "^4.4.1", 15 | "ethers": "^6.11.1", 16 | "hardhat": "^2.21.0", 17 | "hardhat-gas-reporter": "^1.0.10", 18 | "solidity-coverage": "^0.8.11", 19 | "ts-node": "^10.9.2", 20 | "typechain": "^8.3.2", 21 | "typescript": "^5.4.2" 22 | }, 23 | "dependencies": { 24 | "@nomiclabs/hardhat-ethers": "^2.2.3", 25 | "@openzeppelin/contracts": "^5.0.2", 26 | "@openzeppelin/contracts-upgradeable": "^5.0.2", 27 | "dotenv": "^16.4.5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 © Dr. Green NFT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /contracts/IERC7496.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | pragma solidity 0.8.22; 3 | 4 | interface IERC7496 { 5 | /* Events */ 6 | event TraitUpdated( 7 | bytes32 indexed traitKey, 8 | uint256 tokenId, 9 | bytes32 traitValue 10 | ); 11 | event TraitUpdatedRange( 12 | bytes32 indexed traitKey, 13 | uint256 fromTokenId, 14 | uint256 toTokenId 15 | ); 16 | event TraitUpdatedRangeUniformValue( 17 | bytes32 indexed traitKey, 18 | uint256 fromTokenId, 19 | uint256 toTokenId, 20 | bytes32 traitValue 21 | ); 22 | event TraitUpdatedList(bytes32 indexed traitKey, uint256[] tokenIds); 23 | event TraitUpdatedListUniformValue( 24 | bytes32 indexed traitKey, 25 | uint256[] tokenIds, 26 | bytes32 traitValue 27 | ); 28 | event TraitMetadataURIUpdated(); 29 | 30 | /* Getters */ 31 | function getTraitValue(uint256 tokenId, bytes32 traitKey) 32 | external 33 | view 34 | returns (bytes32 traitValue); 35 | 36 | function getTraitValues(uint256 tokenId, bytes32[] calldata traitKeys) 37 | external 38 | view 39 | returns (bytes32[] memory traitValues); 40 | 41 | function getTraitMetadataURI() external view returns (string memory uri); 42 | 43 | /* Setters */ 44 | // function setTrait(uint256 tokenId, bytes32 traitKey, bytes32 traitValue) external; 45 | 46 | /* Errors */ 47 | error TraitValueUnchanged(); 48 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Cosmostation logo 3 |

4 |

Dr. Green NFT

5 |

Join the world’s first legal cannabis on-demand delivery business with Dr. Green!

6 | 7 | ## About 8 | 9 | Dr. Green simplifies the way cannabis is bought and sold worldwide. Using Ethereum blockchain and NFT technology as an authentication key ensures that every transaction is transparent and secure. 10 | 11 | With Dr. Green, anyone can join the cannabis industry safely. Our digital key, stored on the blockchain, includes our medical cannabis license and through smart contracts, holders of the digital key can drop ship cannabis legally wherever it's accepted - this includes recreational and medical cannabis. 12 | 13 | ## Dr Green Community 14 | 15 | [Website](https://drgreennft.com/) 16 | 17 | [Marketplace](https://marketplace.drgreennft.com/) 18 | 19 | [Telegram](https://t.me/DrGreenNFTentry) 20 | 21 | [Discord](https://discord.com/invite/drgreen) 22 | 23 | [Twitter](https://x.com/DrGreen_nft) 24 | 25 | [Instagram](https://www.instagram.com/drgreen/) 26 | 27 | [Whitepaper](https://drgreennft.com/assets/drgreen_whitepaper_2024.pdf) 28 | 29 | ## Overview 30 | 31 | This project is built using Hardhat, an Ethereum smart contract development environment. It includes a different smart contract required for NFT Mining and Trading, a deployment script, and a testing framework. 32 | 33 | ## Project Structure 34 | 35 | ``` 36 | my-hardhat-project/ 37 | ├── contracts/ # Solidity smart contracts 38 | │ └── MyContract.sol # Your custom contract 39 | ├── scripts/ # Deployment scripts 40 | │ └── deploy.js # Sample deployment script 41 | ├── test/ # Test scripts for smart contracts 42 | │ └── MyContract.js # Sample test for MyContract 43 | ├── artifacts/ # Compiled contract files (auto-generated) 44 | ├── cache/ # Cache files (auto-generated) 45 | ├── hardhat.config.js # Hardhat configuration file 46 | └── package.json # Project dependencies and scripts 47 | ``` 48 | 49 | ## Prerequisites 50 | 51 | Ensure the following are installed: 52 | 53 | 1. Node.js (v16 or later) 54 | 2. npm (Node Package Manager) 55 | 56 | ## Setup Instructions 57 | 58 | 1. Clone the Repository 59 | ``` 60 | git clone 61 | cd my-hardhat-project 62 | ``` 63 | 64 | 2. Install Dependencies 65 | ``` 66 | npm install 67 | ``` 68 | 69 | 3. Compile the Contracts 70 | ``` 71 | npx hardhat compile 72 | ``` 73 | 74 | 4. Run Tests 75 | Run the test scripts in the test/ directory: 76 | ``` 77 | npx hardhat test 78 | ``` 79 | 80 | 5. Deploy the Contracts 81 | To deploy the contracts on a local network: 82 | ``` 83 | npx hardhat run scripts/deploy.js 84 | ``` 85 | To deploy on a specific network (e.g., Ethereum Testnets): 86 | 87 | Configure your hardhat.config.js with the desired network. 88 | Deploy using: 89 | ``` 90 | npx hardhat run scripts/deploy.js --network 91 | ``` 92 | 93 | 6. Run a Local Node 94 | To simulate a blockchain locally: 95 | ``` 96 | npx hardhat node 97 | ``` 98 | 99 | ## Configuration 100 | Customize your hardhat.config.js for additional settings: 101 | 102 | - Adding networks (e.g., Rinkeby, Goerli). 103 | - Adjusting Solidity versions. 104 | - Configuring plugins like Etherscan or Gas Reporter. 105 | 106 | ## Dependencies 107 | This project uses: 108 | 109 | - Hardhat: Ethereum development environment. 110 | - Ethers.js: Ethereum interaction library. 111 | - Mocha and Chai: Testing framework. 112 | Install additional plugins if required: 113 | ``` 114 | npm install --save-dev @nomiclabs/hardhat-ethers ethers 115 | npm install --save-dev @nomiclabs/hardhat-waffle 116 | npm install --save-dev @nomiclabs/hardhat-etherscan 117 | ``` 118 | 119 | ## License 120 | Copyright © Dr. Green NFT. All rights reserved. 121 | 122 | Licensed under the [MIT](LICENSE.md). 123 | -------------------------------------------------------------------------------- /test/DrGreenMarketplace.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { ethers } = require("hardhat"); 3 | 4 | describe("DrGreenMarketplace", function () { 5 | let DrGreenMarketplace, drGreenMarketplace, DrGreenNFT, drGreenNFT, owner, addr1, addr2; 6 | 7 | beforeEach(async function () { 8 | [owner, addr1, addr2] = await ethers.getSigners(); 9 | 10 | // Deploy DrGreenNFT contract 11 | const DrGreenNFTFactory = await ethers.getContractFactory("DrGreenNFT"); 12 | drGreenNFT = await DrGreenNFTFactory.deploy(); 13 | await drGreenNFT.deployed(); 14 | 15 | // Deploy DrGreenMarketplace contract 16 | const DrGreenMarketplaceFactory = await ethers.getContractFactory("DrGreenMarketplace"); 17 | drGreenMarketplace = await DrGreenMarketplaceFactory.deploy(drGreenNFT.address); 18 | await drGreenMarketplace.deployed(); 19 | 20 | // Mint an NFT to addr1 21 | await drGreenNFT.connect(addr1).mint(1); 22 | }); 23 | 24 | it("Should allow buying an NFT with a valid signature", async function () { 25 | const tokenId = 1; 26 | const price = ethers.utils.parseEther("1"); 27 | const salt = ethers.utils.randomBytes(32); 28 | 29 | // Generate signature 30 | const message = ethers.utils.solidityKeccak256( 31 | ["address", "uint256", "uint256"], 32 | [addr1.address, tokenId, price] 33 | ); 34 | const signature = await addr1.signMessage(ethers.utils.arrayify(message)); 35 | 36 | // Approve marketplace to transfer NFT 37 | await drGreenNFT.connect(addr1).setApprovalForAll(drGreenMarketplace.address, true); 38 | 39 | // Buy NFT 40 | await expect(drGreenMarketplace.connect(addr2).buyNFT(tokenId, signature, salt, { value: price })) 41 | .to.emit(drGreenMarketplace, "Bought") 42 | .withArgs(tokenId, drGreenNFT.address, price, addr1.address, addr2.address); 43 | 44 | // Check NFT ownership 45 | expect(await drGreenNFT.ownerOf(tokenId)).to.equal(addr2.address); 46 | }); 47 | 48 | it("Should allow canceling a listing with a valid signature", async function () { 49 | const tokenId = 1; 50 | const price = ethers.utils.parseEther("1"); 51 | const salt = ethers.utils.randomBytes(32); 52 | 53 | // Generate signature 54 | const message = ethers.utils.solidityKeccak256( 55 | ["address", "uint256", "uint256"], 56 | [addr1.address, tokenId, price] 57 | ); 58 | const signature = await addr1.signMessage(ethers.utils.arrayify(message)); 59 | 60 | // Cancel listing 61 | await expect(drGreenMarketplace.connect(addr1).cancelListing(tokenId, price, signature, salt)) 62 | .to.emit(drGreenMarketplace, "ListingCancelled") 63 | .withArgs(addr1.address, tokenId, signature, await ethers.provider.getBlockNumber()); 64 | }); 65 | 66 | it("Should revert if signature is already used", async function () { 67 | const tokenId = 1; 68 | const price = ethers.utils.parseEther("1"); 69 | const salt = ethers.utils.randomBytes(32); 70 | 71 | // Generate signature 72 | const message = ethers.utils.solidityKeccak256( 73 | ["address", "uint256", "uint256"], 74 | [addr1.address, tokenId, price] 75 | ); 76 | const signature = await addr1.signMessage(ethers.utils.arrayify(message)); 77 | 78 | // Approve marketplace to transfer NFT 79 | await drGreenNFT.connect(addr1).setApprovalForAll(drGreenMarketplace.address, true); 80 | 81 | // Buy NFT 82 | await drGreenMarketplace.connect(addr2).buyNFT(tokenId, signature, salt, { value: price }); 83 | 84 | // Try to buy NFT again with the same signature 85 | await expect(drGreenMarketplace.connect(addr2).buyNFT(tokenId, signature, salt, { value: price })) 86 | .to.be.revertedWith("signature already used"); 87 | }); 88 | 89 | it("Should revert if signature is invalid", async function () { 90 | const tokenId = 1; 91 | const price = ethers.utils.parseEther("1"); 92 | const salt = ethers.utils.randomBytes(32); 93 | 94 | // Generate signature with wrong owner 95 | const message = ethers.utils.solidityKeccak256( 96 | ["address", "uint256", "uint256"], 97 | [addr2.address, tokenId, price] 98 | ); 99 | const signature = await addr2.signMessage(ethers.utils.arrayify(message)); 100 | 101 | // Approve marketplace to transfer NFT 102 | await drGreenNFT.connect(addr1).setApprovalForAll(drGreenMarketplace.address, true); 103 | 104 | // Try to buy NFT with invalid signature 105 | await expect(drGreenMarketplace.connect(addr2).buyNFT(tokenId, signature, salt, { value: price })) 106 | .to.be.revertedWith("signature validation failed"); 107 | }); 108 | }); -------------------------------------------------------------------------------- /contracts/DrGreenMarketplace.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier:MIT 2 | pragma solidity 0.8.22; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 5 | import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; 6 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 7 | import "./DrGreenNFTInterface.sol"; 8 | 9 | contract DrGreenMarketplace is ReentrancyGuard { 10 | DrGreenNFTInterface _NftInterface; 11 | 12 | event Bought( 13 | uint256 indexed tokenId, 14 | address indexed nft, 15 | uint256 price, 16 | address indexed seller, 17 | address buyer 18 | ); 19 | event ListingCancelled( 20 | address indexed owner, 21 | uint256 indexed tokenId, 22 | bytes signature, 23 | uint256 timestamp 24 | ); 25 | 26 | using ECDSA for bytes32; 27 | 28 | string public constant CONTRACT_NAME = "DrGreenMarketplace"; 29 | string public constant VERSION = "1"; 30 | 31 | mapping(bytes => bool) private _usedSignatures; 32 | 33 | constructor(address _NftContractAddress) { 34 | require( 35 | _NftContractAddress != address(0), 36 | "_NftContractAddress can not be empty" 37 | ); 38 | _NftInterface = DrGreenNFTInterface(_NftContractAddress); 39 | } 40 | 41 | function buyNFT( 42 | uint256 tokenId, 43 | bytes memory sig, 44 | bytes32 salt 45 | ) external payable nonReentrant { 46 | require(sig.length > 0, "signature can not be empty."); 47 | require(salt.length > 0, "salt can not be empty."); 48 | require(!_usedSignatures[sig], "signature already used"); 49 | address tokenOwner = _NftInterface.ownerOf(tokenId); 50 | 51 | verifySignature(sig, salt, tokenOwner, tokenId, msg.value); 52 | 53 | // Get royalty information and calculate seller amount 54 | (address receiver, uint256 royaltyAmount) = _NftInterface.royaltyInfo( 55 | tokenId, 56 | msg.value 57 | ); 58 | 59 | uint256 sellerAmount = msg.value - royaltyAmount; 60 | 61 | // Perform payment with checks 62 | (bool sentToSeller, ) = tokenOwner.call{value: sellerAmount}(""); 63 | require(sentToSeller, "Payment to seller failed"); 64 | 65 | (bool sentToReceiver, ) = receiver.call{value: royaltyAmount}(""); 66 | require(sentToReceiver, "Royalty payment failed"); 67 | 68 | _usedSignatures[sig] = true; 69 | 70 | // Transfer NFT ownership to buyer 71 | _NftInterface.safeTransferFrom(tokenOwner, msg.sender, tokenId); 72 | 73 | emit Bought( 74 | tokenId, 75 | address(_NftInterface), 76 | msg.value, 77 | tokenOwner, 78 | msg.sender 79 | ); 80 | } 81 | 82 | function cancelListing( 83 | uint256 tokenId, 84 | uint256 amount, 85 | bytes memory sig, 86 | bytes32 salt 87 | ) external { 88 | require(sig.length > 0, "signature can not be empty."); 89 | require(salt.length > 0, "salt can not be empty."); 90 | require(!_usedSignatures[sig], "signature already used"); 91 | address tokenOwner = _NftInterface.ownerOf(tokenId); 92 | require(tokenOwner == msg.sender, "Owner only."); 93 | verifySignature(sig, salt, msg.sender, tokenId, amount); 94 | _usedSignatures[sig] = true; 95 | emit ListingCancelled(msg.sender, tokenId, sig, block.timestamp); 96 | } 97 | 98 | function verifySignature( 99 | bytes memory sig, 100 | bytes32 salt, 101 | address owner, 102 | uint256 tokenId, 103 | uint256 amount 104 | ) internal view returns (bool) { 105 | bytes32 digest = keccak256( 106 | abi.encodePacked( 107 | "\x19\x01", 108 | _getDomainSeparator(salt), 109 | keccak256( 110 | abi.encode( 111 | keccak256( 112 | "Sale(address owner,uint256 tokenId,uint256 amount)" 113 | ), 114 | owner, 115 | tokenId, 116 | amount 117 | ) 118 | ) 119 | ) 120 | ); 121 | 122 | // Validate signature directly 123 | require(digest.recover(sig) == owner, "signature validation failed"); 124 | return true; 125 | } 126 | 127 | function _getDomainSeparator(bytes32 salt) internal view returns (bytes32) { 128 | return 129 | keccak256( 130 | abi.encode( 131 | keccak256( 132 | "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)" 133 | ), 134 | keccak256(bytes(CONTRACT_NAME)), 135 | keccak256(bytes(VERSION)), 136 | block.chainid, 137 | address(this), 138 | salt 139 | ) 140 | ); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /contracts/DynamicTraits.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | pragma solidity 0.8.22; 3 | 4 | import {IERC7496} from "./IERC7496.sol"; 5 | 6 | library DynamicTraitsStorage { 7 | struct Layout { 8 | /// @dev A mapping of token ID to a mapping of trait key to trait value. 9 | mapping(uint256 => mapping(bytes32 => bytes32)) _traits; 10 | /// @dev An offchain string URI that points to a JSON file containing trait metadata. 11 | string _traitMetadataURI; 12 | } 13 | 14 | bytes32 internal constant STORAGE_SLOT = 15 | keccak256("contracts.storage.erc7496-dynamictraits"); 16 | 17 | function layout() internal pure returns (Layout storage l) { 18 | bytes32 slot = STORAGE_SLOT; 19 | assembly { 20 | l.slot := slot 21 | } 22 | } 23 | } 24 | 25 | /** 26 | * @title DynamicTraits 27 | * 28 | * @dev Implementation of [ERC-7496](https://eips.ethereum.org/EIPS/eip-7496) Dynamic Traits. 29 | * Uses a storage layout pattern for upgradeable contracts. 30 | * 31 | * Requirements: 32 | * - Overwrite `setTrait` with access role restriction. 33 | * - Expose a function for `setTraitMetadataURI` with access role restriction if desired. 34 | */ 35 | contract DynamicTraits is IERC7496 { 36 | using DynamicTraitsStorage for DynamicTraitsStorage.Layout; 37 | 38 | /** 39 | * @notice Get the value of a trait for a given token ID. 40 | * @param tokenId The token ID to get the trait value for 41 | * @param traitKey The trait key to get the value of 42 | */ 43 | function getTraitValue(uint256 tokenId, bytes32 traitKey) 44 | public 45 | view 46 | virtual 47 | returns (bytes32 traitValue) 48 | { 49 | // Return the trait value. 50 | return DynamicTraitsStorage.layout()._traits[tokenId][traitKey]; 51 | } 52 | 53 | /** 54 | * @notice Get the values of traits for a given token ID. 55 | * @param tokenId The token ID to get the trait values for 56 | * @param traitKeys The trait keys to get the values of 57 | */ 58 | function getTraitValues(uint256 tokenId, bytes32[] calldata traitKeys) 59 | public 60 | view 61 | virtual 62 | returns (bytes32[] memory traitValues) 63 | { 64 | // Set the length of the traitValues return array. 65 | uint256 length = traitKeys.length; 66 | traitValues = new bytes32[](length); 67 | 68 | // Assign each trait value to the corresopnding key. 69 | for (uint256 i = 0; i < length; ) { 70 | bytes32 traitKey = traitKeys[i]; 71 | traitValues[i] = getTraitValue(tokenId, traitKey); 72 | unchecked { 73 | ++i; 74 | } 75 | } 76 | } 77 | 78 | /** 79 | * @notice Get the URI for the trait metadata 80 | */ 81 | function getTraitMetadataURI() 82 | external 83 | view 84 | virtual 85 | returns (string memory labelsURI) 86 | { 87 | // Return the trait metadata URI. 88 | return DynamicTraitsStorage.layout()._traitMetadataURI; 89 | } 90 | 91 | /** 92 | * @notice Set the value of a trait for a given token ID. 93 | * Reverts if the trait value is unchanged. 94 | * @dev IMPORTANT: Override this method with access role restriction. 95 | * @param tokenId The token ID to set the trait value for 96 | * @param traitKey The trait key to set the value of 97 | * @param newValue The new trait value to set 98 | */ 99 | function setTrait( 100 | uint256 tokenId, 101 | bytes32 traitKey, 102 | bytes32 newValue 103 | ) internal { 104 | // Revert if the new value is the same as the existing value. 105 | bytes32 existingValue = DynamicTraitsStorage.layout()._traits[tokenId][ 106 | traitKey 107 | ]; 108 | if (existingValue == newValue) { 109 | revert TraitValueUnchanged(); 110 | } 111 | 112 | // Set the new trait value. 113 | _setTrait(tokenId, traitKey, newValue); 114 | 115 | // Emit the event noting the update. 116 | emit TraitUpdated(traitKey, tokenId, newValue); 117 | } 118 | 119 | /** 120 | * @notice Set the trait value (without emitting an event). 121 | * @param tokenId The token ID to set the trait value for 122 | * @param traitKey The trait key to set the value of 123 | * @param newValue The new trait value to set 124 | */ 125 | function _setTrait( 126 | uint256 tokenId, 127 | bytes32 traitKey, 128 | bytes32 newValue 129 | ) internal virtual { 130 | // Set the new trait value. 131 | DynamicTraitsStorage.layout()._traits[tokenId][traitKey] = newValue; 132 | } 133 | 134 | /** 135 | * @notice Set the URI for the trait metadata. 136 | * @param uri The new URI to set. 137 | */ 138 | function _setTraitMetadataURI(string memory uri) internal virtual { 139 | // Set the new trait metadata URI. 140 | DynamicTraitsStorage.layout()._traitMetadataURI = uri; 141 | 142 | // Emit the event noting the update. 143 | emit TraitMetadataURIUpdated(); 144 | } 145 | 146 | /** 147 | * @dev See {IERC165-supportsInterface}. 148 | */ 149 | function supportsInterface(bytes4 interfaceId) 150 | public 151 | view 152 | virtual 153 | returns (bool) 154 | { 155 | return interfaceId == type(IERC7496).interfaceId; 156 | } 157 | } -------------------------------------------------------------------------------- /test/DrGreenNFT.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { ethers } = require("hardhat"); 3 | 4 | describe("DrGreenNFT", function () { 5 | let DrGreenNFT, drGreenNFT, PlanetsMetadata, planetsMetadata, owner, addr1, addr2; 6 | 7 | beforeEach(async function () { 8 | [owner, addr1, addr2] = await ethers.getSigners(); 9 | 10 | // Deploy PlanetsMetadata contract 11 | const PlanetsMetadataFactory = await ethers.getContractFactory("PlanetsMetadata"); 12 | planetsMetadata = await PlanetsMetadataFactory.deploy(); 13 | await planetsMetadata.deployed(); 14 | 15 | // Deploy DrGreenNFT contract 16 | const DrGreenNFTFactory = await ethers.getContractFactory("DrGreenNFT"); 17 | drGreenNFT = await DrGreenNFTFactory.deploy( 18 | planetsMetadata.address, 19 | "baseTokenURI", 20 | "traitMetadataURI", 21 | owner.address, 22 | owner.address 23 | ); 24 | await drGreenNFT.deployed(); 25 | }); 26 | 27 | it("Should allow minting standard NFTs during presale", async function () { 28 | const planetNo = 1; 29 | const mintLimit = 2; 30 | const sig = ethers.utils.randomBytes(65); // Mock signature 31 | 32 | // Set presale active 33 | await drGreenNFT.setPresaleActive(true); 34 | 35 | // Mint standard NFT 36 | await expect(drGreenNFT.connect(addr1).standardPreMint(planetNo, mintLimit, sig)) 37 | .to.emit(drGreenNFT, "NFTMinted") 38 | .withArgs("StandardWhitelisted", addr1.address, 106, 1, 0, 0); 39 | 40 | // Check NFT ownership 41 | expect(await drGreenNFT.ownerOf(106)).to.equal(addr1.address); 42 | }); 43 | 44 | it("Should allow minting standard NFTs during greenlist round", async function () { 45 | const planetNo = 1; 46 | const price = ethers.utils.parseEther("1"); 47 | const merkleRoot = ethers.utils.randomBytes(32); // Mock merkle root 48 | const merkleProof = []; // Mock merkle proof 49 | 50 | // Create greenlist round 51 | await drGreenNFT.createRound( 52 | Math.floor(Date.now() / 1000), 53 | Math.floor(Date.now() / 1000) + 3600, 54 | price, 55 | [planetNo], 56 | 100, 57 | 1, // Greenlist round type 58 | merkleRoot 59 | ); 60 | 61 | // Mint standard NFT 62 | await expect(drGreenNFT.connect(addr1).standardMint(planetNo, merkleProof, { value: price })) 63 | .to.emit(drGreenNFT, "NFTMinted") 64 | .withArgs("Standard", addr1.address, 107, 2, price, 1); 65 | 66 | // Check NFT ownership 67 | expect(await drGreenNFT.ownerOf(107)).to.equal(addr1.address); 68 | }); 69 | 70 | it("Should allow minting gold NFTs by admin", async function () { 71 | const addresses = [addr1.address]; 72 | const metadataIds = [1]; 73 | 74 | // Mint gold NFT 75 | await expect(drGreenNFT.connect(owner).goldMint(addresses, metadataIds)) 76 | .to.emit(drGreenNFT, "NFTMinted") 77 | .withArgs("Gold", addr1.address, 1, 1, 0, 0); 78 | 79 | // Check NFT ownership 80 | expect(await drGreenNFT.ownerOf(1)).to.equal(addr1.address); 81 | }); 82 | 83 | it("Should allow minting platinum NFTs by admin", async function () { 84 | const addresses = [addr1.address]; 85 | const metadataIds = [56]; 86 | 87 | // Mint platinum NFT 88 | await expect(drGreenNFT.connect(owner).platinumMint(addresses, metadataIds)) 89 | .to.emit(drGreenNFT, "NFTMinted") 90 | .withArgs("Platinum", addr1.address, 56, 56, 0, 0); 91 | 92 | // Check NFT ownership 93 | expect(await drGreenNFT.ownerOf(56)).to.equal(addr1.address); 94 | }); 95 | 96 | it("Should allow setting royalty", async function () { 97 | const receiver = addr1.address; 98 | const feePercent = 900; // 9% 99 | 100 | // Set royalty 101 | await expect(drGreenNFT.connect(owner).setRoyalty(receiver, feePercent)) 102 | .to.emit(drGreenNFT, "RoyaltyInfoUpdated") 103 | .withArgs(receiver, feePercent); 104 | 105 | // Check royalty info 106 | const royaltyInfo = await drGreenNFT.royaltyInfo(1, ethers.utils.parseEther("1")); 107 | expect(royaltyInfo[0]).to.equal(receiver); 108 | expect(royaltyInfo[1]).to.equal(ethers.utils.parseEther("0.09")); 109 | }); 110 | 111 | it("Should allow setting token royalty", async function () { 112 | const tokenId = 1; 113 | const receiver = addr1.address; 114 | const feePercent = 900; // 9% 115 | 116 | // Set token royalty 117 | await expect(drGreenNFT.connect(owner).setTokenRoyalty(tokenId, receiver, feePercent)) 118 | .to.emit(drGreenNFT, "TokenRotaltyUpdated") 119 | .withArgs(tokenId, receiver, feePercent); 120 | 121 | // Check token royalty info 122 | const royaltyInfo = await drGreenNFT.royaltyInfo(tokenId, ethers.utils.parseEther("1")); 123 | expect(royaltyInfo[0]).to.equal(receiver); 124 | expect(royaltyInfo[1]).to.equal(ethers.utils.parseEther("0.09")); 125 | }); 126 | 127 | it("Should allow withdrawing funds", async function () { 128 | const price = ethers.utils.parseEther("1"); 129 | const merkleRoot = ethers.utils.randomBytes(32); // Mock merkle root 130 | const merkleProof = []; // Mock merkle proof 131 | 132 | // Create greenlist round 133 | await drGreenNFT.createRound( 134 | Math.floor(Date.now() / 1000), 135 | Math.floor(Date.now() / 1000) + 3600, 136 | price, 137 | [1], 138 | 100, 139 | 1, // Greenlist round type 140 | merkleRoot 141 | ); 142 | 143 | // Mint standard NFT 144 | await drGreenNFT.connect(addr1).standardMint(1, merkleProof, { value: price }); 145 | 146 | // Withdraw funds 147 | await expect(drGreenNFT.connect(owner).withdrawFunds(owner.address)) 148 | .to.emit(drGreenNFT, "FundsTransferred") 149 | .withArgs(owner.address, price); 150 | 151 | // Check contract balance 152 | expect(await ethers.provider.getBalance(drGreenNFT.address)).to.equal(0); 153 | }); 154 | 155 | it("Should allow adding clients and transactions", async function () { 156 | const tokenId = 106; 157 | const clientsToAdd = 10; 158 | const txsToAdd = 5; 159 | const txsAmtToAdd = ethers.utils.parseEther("0.5"); 160 | 161 | // Mint standard NFT 162 | const planetNo = 1; 163 | const mintLimit = 2; 164 | const sig = ethers.utils.randomBytes(65); // Mock signature 165 | await drGreenNFT.setPresaleActive(true); 166 | await drGreenNFT.connect(addr1).standardPreMint(planetNo, mintLimit, sig); 167 | 168 | // Add clients 169 | await drGreenNFT.connect(owner).addClient(tokenId, clientsToAdd); 170 | 171 | // Add transactions 172 | await drGreenNFT.connect(owner).addTransaction(tokenId, txsToAdd, txsAmtToAdd, false); 173 | 174 | // Check dynamic traits 175 | const clientCount = await drGreenNFT.getTraitValue(tokenId, "clientCount"); 176 | const txCount = await drGreenNFT.getTraitValue(tokenId, "txCount"); 177 | const txVolume = await drGreenNFT.getTraitValue(tokenId, "txVolume"); 178 | 179 | expect(clientCount).to.equal(clientsToAdd.toString()); 180 | expect(txCount).to.equal(txsToAdd.toString()); 181 | expect(txVolume).to.equal(txsAmtToAdd.toString()); 182 | }); 183 | 184 | it("Should allow updating token URI", async function () { 185 | const tokenId = 106; 186 | const newMetadataUri = "newMetadataUri"; 187 | const sig = ethers.utils.randomBytes(65); // Mock signature 188 | 189 | // Mint standard NFT 190 | const planetNo = 1; 191 | const mintLimit = 2; 192 | const mintSig = ethers.utils.randomBytes(65); // Mock signature 193 | await drGreenNFT.setPresaleActive(true); 194 | await drGreenNFT.connect(addr1).standardPreMint(planetNo, mintLimit, mintSig); 195 | 196 | // Update token URI 197 | await expect(drGreenNFT.connect(addr1).updateTokenURI(tokenId, newMetadataUri, sig)) 198 | .to.emit(drGreenNFT, "UpdateTokenURI") 199 | .withArgs(tokenId, newMetadataUri, Math.floor(Date.now() / 1000)); 200 | 201 | // Check token URI 202 | expect(await drGreenNFT.tokenURI(tokenId)).to.equal(newMetadataUri); 203 | }); 204 | }); -------------------------------------------------------------------------------- /contracts/DrGreenNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.22; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol"; 6 | import "@openzeppelin/contracts/access/AccessControl.sol"; 7 | import "@openzeppelin/contracts/utils/Strings.sol"; 8 | import "@openzeppelin/contracts/token/common/ERC2981.sol"; 9 | import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; 10 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 11 | import "./PlanetsMetadataInterface.sol"; 12 | import {DynamicTraits} from "./DynamicTraits.sol"; 13 | import {IERC7496} from "./IERC7496.sol"; 14 | 15 | contract DrGreenNFT is 16 | ERC721, 17 | ERC2981, 18 | AccessControl, 19 | ERC721Pausable, 20 | DynamicTraits 21 | { 22 | PlanetsMetadataInterface _planetsMetadata; 23 | 24 | event NFTMinted( 25 | string mintType, 26 | address indexed to, 27 | uint256 tokenId, 28 | uint16 metadataId, 29 | uint256 price, 30 | uint8 roundId 31 | ); 32 | event NFTTransferred( 33 | address indexed from, 34 | address indexed to, 35 | uint256 tokenId 36 | ); 37 | event FundsTransferred(address indexed to, uint256 amount); 38 | event UpdateTokenURI(uint16 indexed tokenId, string newUri, uint256 time); 39 | event RoundCreated(uint8 indexed roundId, Round round); 40 | event RoundUpdated(uint8 indexed roundId, Round round); 41 | event InventoryContractAddressUpdated( 42 | address indexed inventoryContractAddr 43 | ); 44 | event RoyaltyInfoUpdated(address indexed receiver, uint96 feePercent); 45 | event TokenRotaltyUpdated( 46 | uint256 indexed tokenId, 47 | address indexed receiver, 48 | uint96 feePercent 49 | ); 50 | event BaseTokenURIUpdated(string baseTokenUri); 51 | 52 | bytes32 public constant WHITELIST_SIGNER_ROLE = 53 | keccak256("WHITELIST_SIGNER_ROLE"); 54 | bytes32 public constant NFT_UPDATE_SIGNER_ROLE = 55 | keccak256("NFT_UPDATE_SIGNER_ROLE"); 56 | 57 | using Strings for uint256; 58 | using ECDSA for bytes32; 59 | 60 | struct NftMinted { 61 | uint8 goldMinted; 62 | uint8 platinumMinted; 63 | uint8 standardMinted; 64 | } 65 | 66 | enum RoundType { 67 | Presale, 68 | Greenlist, 69 | Public 70 | } 71 | 72 | struct Round { 73 | uint256 startTime; 74 | uint256 endTime; 75 | uint256 price; 76 | uint8[] planets; 77 | uint16 supply; 78 | RoundType roundType; 79 | bytes32 merkleRoot; 80 | bool isPaused; 81 | } 82 | 83 | uint8 private _goldCurrIndex = 1; 84 | uint8 private _platinumCurrIndex = 56; 85 | uint16 private _standardCurrIndex = 106; 86 | uint8 private _currentRoundId = 0; 87 | string public _baseTokenURI; 88 | bool public _isPresaleActive = false; 89 | address public _inventoryContractAddr; 90 | 91 | //mappings 92 | mapping(address => NftMinted) private _numberMinted; 93 | mapping(uint256 => string) private _tokenURIs; 94 | mapping(uint256 => Round) private _rounds; 95 | mapping(uint8 => bool) private _planetEnabled; 96 | mapping(bytes => bool) private _usedSignatures; 97 | mapping(uint16 => bool) private _usedMetadataIds; 98 | 99 | // constants 100 | uint8 private constant _platinumStartIndex = 56; 101 | uint16 private constant _standardStartIndex = 106; 102 | uint16 private constant MAX_SUPPLY = 5145; 103 | uint8 private constant GOLD_MAX_SUPPLY = 55; 104 | uint8 private constant PLATINUM_MAX_SUPPLY = 50; 105 | uint16 private constant NFTS_PER_PLANET = 252; 106 | bytes32 private constant CLIENT_COUNT_KEY = 107 | 0x636c69656e74436f756e74000000000000000000000000000000000000000000; 108 | bytes32 private constant TX_COUNT_KEY = 109 | 0x7478436f756e7400000000000000000000000000000000000000000000000000; 110 | bytes32 private constant TX_VOLUME_KEY = 111 | 0x7478566f6c756d65000000000000000000000000000000000000000000000000; 112 | 113 | constructor( 114 | address planetsMetadataAddr, 115 | string memory baseTokenURI, 116 | string memory traitMetadataURI, 117 | address ownerAddress, 118 | address royaltyReceiver 119 | ) ERC721("Dr Green Digital Key", "DRGDK") { 120 | require( 121 | planetsMetadataAddr != address(0), 122 | "metadata addr cant be empty" 123 | ); 124 | require(bytes(baseTokenURI).length > 0, "base URI cant be empty"); 125 | require(bytes(traitMetadataURI).length > 0, "trait URI cant be empty"); 126 | require(ownerAddress != address(0), "ownerAddress cant be empty"); 127 | require(royaltyReceiver != address(0), "royaltyReceiver cant be empty"); 128 | _planetsMetadata = PlanetsMetadataInterface(planetsMetadataAddr); 129 | _baseTokenURI = baseTokenURI; 130 | _setTraitMetadataURI(traitMetadataURI); 131 | _setDefaultRoyalty(royaltyReceiver, 900); 132 | _grantRole(DEFAULT_ADMIN_ROLE, ownerAddress); 133 | _grantRole(WHITELIST_SIGNER_ROLE, ownerAddress); 134 | _grantRole(NFT_UPDATE_SIGNER_ROLE, ownerAddress); 135 | } 136 | 137 | // This is the modifier for the whitelisted user 138 | modifier isWhitelistedUser( 139 | string memory mintType, 140 | uint8 limit, 141 | bytes memory sig 142 | ) { 143 | require(_standardCurrIndex <= MAX_SUPPLY, "Max supply reached"); 144 | require( 145 | _isWhitelisted(mintType, limit, sig), 146 | "Signature failed or user not whitelisted" 147 | ); 148 | _; 149 | } 150 | 151 | modifier onlyOwner() { 152 | require( 153 | hasRole(DEFAULT_ADMIN_ROLE, msg.sender), 154 | "Caller is not a owner" 155 | ); 156 | _; 157 | } 158 | 159 | // set modifier to call the function by order management contract only 160 | modifier onlyInventoryContract() { 161 | require( 162 | msg.sender == _inventoryContractAddr, 163 | "only inventory contract can call this function." 164 | ); 165 | _; 166 | } 167 | 168 | // function to create round by admin 169 | function createRound( 170 | uint256 startTime, 171 | uint256 endTime, 172 | uint256 price, 173 | uint8[] calldata planets, 174 | uint16 supply, 175 | RoundType roundType, 176 | bytes32 merkleRoot 177 | ) external onlyOwner { 178 | require( 179 | block.timestamp < startTime, 180 | "Start date must be later than the current time" 181 | ); 182 | // if there is any round already created we need to make sure we can only create next round once current is finished 183 | if (_currentRoundId > 0) { 184 | Round storage round = _rounds[_currentRoundId]; 185 | require( 186 | block.timestamp > round.endTime, 187 | "Current round is not yet ended." 188 | ); 189 | } 190 | _currentRoundId++; 191 | _manageRound( 192 | _currentRoundId, 193 | startTime, 194 | endTime, 195 | price, 196 | planets, 197 | supply, 198 | roundType, 199 | merkleRoot, 200 | false 201 | ); 202 | emit RoundCreated(_currentRoundId, _rounds[_currentRoundId]); 203 | } 204 | 205 | // Function to update round by admin 206 | function updateRound( 207 | uint8 roundId, 208 | uint256 startTime, 209 | uint256 endTime, 210 | uint256 price, 211 | uint8[] calldata planets, 212 | uint16 supply, 213 | RoundType roundType, 214 | bytes32 merkleRoot, 215 | bool isPaused 216 | ) external onlyOwner { 217 | require(roundId > 0 && roundId == _currentRoundId, "Invalid round ID"); 218 | _manageRound( 219 | roundId, 220 | startTime, 221 | endTime, 222 | price, 223 | planets, 224 | supply, 225 | roundType, 226 | merkleRoot, 227 | isPaused 228 | ); 229 | emit RoundUpdated(roundId, _rounds[roundId]); 230 | } 231 | 232 | // function to set inventory smart contract address by admin 233 | function setInventoryContractAddress( 234 | address inventoryContractAddress 235 | ) external onlyOwner { 236 | require( 237 | inventoryContractAddress != address(0), 238 | "cannot set zero address" 239 | ); 240 | _inventoryContractAddr = inventoryContractAddress; 241 | emit InventoryContractAddressUpdated(inventoryContractAddress); 242 | } 243 | 244 | // This function is used to mint standard NFTs when whitelisted round is open and address is whitelisted 245 | /* 246 | * @dev 247 | * Requirements: 248 | * `planetNo` should be in uin8. Example: 2. 249 | * `mintLimit` should be in uin8. Example: 2. 250 | * `sig` should be in bytes. Example: 0x4418f..........d43e6b174571b. 251 | */ 252 | function standardPreMint( 253 | uint8 planetNo, 254 | uint8 mintLimit, 255 | bytes memory sig 256 | ) external isWhitelistedUser("Standard", mintLimit, sig) { 257 | require(_isPresaleActive, "Presale is not active"); 258 | require(_planetEnabled[planetNo], "Planet not enabled for this round"); 259 | require( 260 | _numberMinted[msg.sender].standardMinted < mintLimit, 261 | "mint limit reached" 262 | ); 263 | _numberMinted[msg.sender].standardMinted += 1; 264 | uint256 tokenId = _standardCurrIndex++; 265 | uint16 metadataId = _planetsMetadata.setRandomTokenMetadata( 266 | planetNo, 267 | tokenId 268 | ); 269 | _safeMint(msg.sender, tokenId); 270 | emit NFTMinted( 271 | "StandardWhitelisted", 272 | msg.sender, 273 | tokenId, 274 | metadataId, 275 | 0, 276 | 0 277 | ); 278 | } 279 | 280 | // @dev This function is used to mint standard NFTs 281 | /* 282 | * @dev 283 | * Requirements: 284 | * `roundId` should be in uin8. 285 | * `planetNo` should be in uin8. 286 | * `merkleProof` merkleProof. 287 | */ 288 | function standardMint( 289 | uint8 planetNo, 290 | bytes32[] calldata merkleProof 291 | ) external payable { 292 | require(_standardCurrIndex <= MAX_SUPPLY, "Max supply reached"); 293 | Round storage round = _rounds[_currentRoundId]; 294 | require( 295 | round.roundType == RoundType.Greenlist || 296 | round.roundType == RoundType.Public, 297 | "Only for greenlist and public mint" 298 | ); 299 | require(round.startTime != 0, "No active round"); 300 | require( 301 | block.timestamp >= round.startTime && 302 | block.timestamp <= round.endTime && 303 | !round.isPaused, 304 | "Minting is not active or paused" 305 | ); 306 | require(_planetEnabled[planetNo], "Planet not enabled for this round"); 307 | require(msg.value == round.price, "Incorrect payment amount"); 308 | if (round.roundType == RoundType.Greenlist) { 309 | require( 310 | merkleProof.length > 0, 311 | "Merkle proof is required for Greenlist" 312 | ); 313 | require( 314 | MerkleProof.verify( 315 | merkleProof, 316 | round.merkleRoot, 317 | keccak256( 318 | abi.encodePacked( 319 | msg.sender, 320 | address(this), 321 | block.chainid 322 | ) 323 | ) 324 | ), 325 | "You are not Greenlisted for this round" 326 | ); 327 | } 328 | uint256 tokenId = _standardCurrIndex++; 329 | uint16 metadataId = _planetsMetadata.setRandomTokenMetadata( 330 | planetNo, 331 | tokenId 332 | ); 333 | _safeMint(msg.sender, tokenId); 334 | emit NFTMinted( 335 | "Standard", 336 | msg.sender, 337 | tokenId, 338 | metadataId, 339 | msg.value, 340 | _currentRoundId 341 | ); 342 | } 343 | 344 | /* 345 | * @dev 346 | * Requirements: 347 | * `addresses` Array of addresses to mint gold Nfts. 348 | * `metedataIds` Array of metedataIds which needs to be associated with NFT. 349 | */ 350 | function goldMint( 351 | address[] calldata addresses, 352 | uint16[] calldata metedataIds 353 | ) external onlyOwner { 354 | uint8 totalGoldMinted = _goldCurrIndex - 1; 355 | require(totalGoldMinted < GOLD_MAX_SUPPLY, "all gold NFTs are minted."); 356 | require( 357 | addresses.length == metedataIds.length && addresses.length > 0, 358 | "Addresses and metedataIds must be the same length and non-empty" 359 | ); 360 | uint256 totalToMint = addresses.length; 361 | require( 362 | totalGoldMinted + totalToMint <= GOLD_MAX_SUPPLY, 363 | "Total amount exceeds the gold NFT supply limit." 364 | ); 365 | for (uint8 index = 0; index < totalToMint; index++) { 366 | require( 367 | addresses[index] != address(0), 368 | "address can not be empty." 369 | ); 370 | uint16 metadataId = metedataIds[index]; 371 | require( 372 | !_usedMetadataIds[metadataId], 373 | "MetadataId is already associated with NFT." 374 | ); 375 | // Allowed metadataIds are between range 1 to 55 376 | require( 377 | metadataId > 0 && metadataId <= GOLD_MAX_SUPPLY, 378 | "metadataId must be between 1 to 55." 379 | ); 380 | } 381 | for (uint8 i = 0; i < totalToMint; i++) { 382 | address addr = addresses[i]; 383 | uint16 metadataId = metedataIds[i]; 384 | _numberMinted[addr].goldMinted += 1; 385 | uint256 tokenId = _goldCurrIndex++; 386 | _planetsMetadata.setTokenMetadataId(tokenId, metadataId); 387 | _safeMint(addr, tokenId); 388 | _usedMetadataIds[metadataId] = true; 389 | emit NFTMinted("Gold", addr, tokenId, metadataId, 0, 0); 390 | } 391 | } 392 | 393 | // This function is used to mint platinum NFTs by admin 394 | /* 395 | * @dev 396 | * Requirements: 397 | * `addresses` Array of addresses to mint platinum Nfts. 398 | * `metedataIds` Array of metedataIds which needs to be associated with NFT. 399 | */ 400 | function platinumMint( 401 | address[] calldata addresses, 402 | uint16[] calldata metedataIds 403 | ) external onlyOwner { 404 | uint8 totalPlatinumMinted = _platinumCurrIndex - _platinumStartIndex; 405 | require( 406 | totalPlatinumMinted < PLATINUM_MAX_SUPPLY, 407 | "all platinum NFTs are minted." 408 | ); 409 | require( 410 | addresses.length == metedataIds.length && addresses.length > 0, 411 | "Addresses and metedataIds must be the same length and non-empty" 412 | ); 413 | uint256 totalToMint = addresses.length; 414 | require( 415 | totalPlatinumMinted + totalToMint <= PLATINUM_MAX_SUPPLY, 416 | "Total amount exceeds the platinum NFT supply limit." 417 | ); 418 | for (uint8 index = 0; index < totalToMint; index++) { 419 | require( 420 | addresses[index] != address(0), 421 | "address can not be empty." 422 | ); 423 | uint16 metadataId = metedataIds[index]; 424 | require( 425 | !_usedMetadataIds[metadataId], 426 | "MetadataId is already associated with NFT." 427 | ); 428 | // Allowed metadataIds are between range 56 to 105 429 | require( 430 | metadataId >= _platinumStartIndex && 431 | metadataId < _platinumStartIndex + PLATINUM_MAX_SUPPLY, 432 | "metadataId must be between 56 to 105." 433 | ); 434 | } 435 | for (uint8 i = 0; i < totalToMint; i++) { 436 | address addr = addresses[i]; 437 | uint16 metadataId = metedataIds[i]; 438 | _numberMinted[addr].platinumMinted += 1; 439 | uint256 tokenId = _platinumCurrIndex++; 440 | _planetsMetadata.setTokenMetadataId(tokenId, metadataId); 441 | _safeMint(addr, tokenId); 442 | _usedMetadataIds[metadataId] = true; 443 | emit NFTMinted("Platinum", addr, tokenId, metadataId, 0, 0); 444 | } 445 | } 446 | 447 | // Function to set royalty which is paid to the given address 448 | /* 449 | * @dev 450 | * Requirements: 451 | * - `receiver` cannot be the zero address. 452 | * - the caller must have a balance of at least `amount`. 453 | * - `feePercent` should be given as multiplied by 100. Example: 9.5% should be as 950. 454 | */ 455 | function setRoyalty( 456 | address receiver, 457 | uint96 feePercent 458 | ) external onlyOwner { 459 | if (receiver == address(0)) { 460 | receiver = msg.sender; 461 | } 462 | _setDefaultRoyalty(receiver, feePercent); 463 | emit RoyaltyInfoUpdated(receiver, feePercent); 464 | } 465 | 466 | // Function to set royalty for the specific tokenID 467 | /* 468 | * @dev 469 | * Requirements: 470 | * - `receiver` cannot be the zero address. 471 | * - the caller must have a balance of at least `amount`. 472 | * - `feePercent` should be given as multiplied by 100. Example: 9.5% should be as 950. 473 | */ 474 | function setTokenRoyalty( 475 | uint256 tokenId, 476 | address receiver, 477 | uint96 feePercent 478 | ) external onlyOwner { 479 | if (receiver == address(0)) { 480 | receiver = msg.sender; 481 | } 482 | _setTokenRoyalty(tokenId, receiver, feePercent); 483 | emit TokenRotaltyUpdated(tokenId, receiver, feePercent); 484 | } 485 | 486 | // function to withdraw ethers to admin account or other acocunt 487 | function withdrawFunds(address to) external onlyOwner { 488 | require(to != address(0), "address can not be empty."); 489 | uint256 balance = address(this).balance; 490 | require(balance > 0, "No funds available for withdrawal."); 491 | emit FundsTransferred(to, balance); // Emit event before transfer for accurate logging 492 | (bool success, ) = to.call{value: balance}(""); 493 | require(success, "Withdrawal failed"); 494 | } 495 | 496 | // Function to add client with NFT tokenID 497 | /* 498 | * @dev 499 | * Requirements: 500 | * - `tokenId` NFT tokenId for which clients to add. 501 | * - `clientsToAdd` number of clients to add 502 | */ 503 | function addClient( 504 | uint16 tokenId, 505 | uint256 clientsToAdd 506 | ) external onlyInventoryContract { 507 | _requireOwned(tokenId); 508 | require(clientsToAdd > 0, "clientsToAdd must be greater than 0"); 509 | 510 | // Retrieve and parse current client count 511 | uint256 clientCount = _stringToUint256( 512 | string( 513 | abi.encodePacked( 514 | DynamicTraits.getTraitValue(tokenId, CLIENT_COUNT_KEY) 515 | ) 516 | ) 517 | ); 518 | 519 | // Add new clients and convert directly to bytes32 520 | clientCount += clientsToAdd; 521 | 522 | bytes32 clientCountBytes = bytes32(bytes(clientCount.toString())); 523 | 524 | // Call the internal function to set the trait. 525 | DynamicTraits.setTrait(tokenId, CLIENT_COUNT_KEY, clientCountBytes); 526 | } 527 | 528 | // Function to add transaction count with NFT tokenID 529 | /* 530 | * @dev 531 | * Requirements: 532 | * - `tokenId` NFT tokenId for which clients to add. 533 | * - `txsToAdd` number of txs to add 534 | * - `txsAmtToAdd` total amount for txs 535 | * - `isRefunded` is this refund? 536 | */ 537 | function addTransaction( 538 | uint16 tokenId, 539 | uint256 txsToAdd, 540 | uint256 txsAmtToAdd, 541 | bool isRefunded 542 | ) external onlyInventoryContract { 543 | _requireOwned(tokenId); 544 | require( 545 | txsToAdd > 0 && txsAmtToAdd > 0, 546 | "txsToAdd & txsAmtToAdd must be greater than 0" 547 | ); 548 | // Get current transaction count and volume 549 | uint256 txCount = _stringToUint256( 550 | string( 551 | abi.encodePacked( 552 | DynamicTraits.getTraitValue(tokenId, TX_COUNT_KEY) 553 | ) 554 | ) 555 | ); 556 | uint256 txVolume = _stringToUint256( 557 | string( 558 | abi.encodePacked( 559 | DynamicTraits.getTraitValue(tokenId, TX_VOLUME_KEY) 560 | ) 561 | ) 562 | ); 563 | 564 | // Calculate new values based on isRefunded 565 | txCount = isRefunded ? txCount - txsToAdd : txCount + txsToAdd; 566 | txVolume = isRefunded ? txVolume - txsAmtToAdd : txVolume + txsAmtToAdd; 567 | 568 | bytes32 txCountBytes = bytes32(bytes(txCount.toString())); 569 | bytes32 txVolumeBytes = bytes32(bytes(txVolume.toString())); 570 | 571 | // Set the updated values 572 | DynamicTraits.setTrait(tokenId, TX_COUNT_KEY, txCountBytes); 573 | DynamicTraits.setTrait(tokenId, TX_VOLUME_KEY, txVolumeBytes); 574 | } 575 | 576 | function setTraitMetadataURI(string calldata uri) external onlyOwner { 577 | // Set the new metadata URI. 578 | _setTraitMetadataURI(uri); 579 | } 580 | 581 | function pauseMinting() public onlyOwner { 582 | _pause(); 583 | } 584 | 585 | function unpauseMinting() public onlyOwner { 586 | _unpause(); 587 | } 588 | 589 | function setBaseTokenURI(string memory baseTokenURI) external onlyOwner { 590 | require( 591 | bytes(baseTokenURI).length > 0, 592 | "Base token URI cannot be empty" 593 | ); 594 | _baseTokenURI = baseTokenURI; 595 | emit BaseTokenURIUpdated(baseTokenURI); 596 | } 597 | 598 | // Override the grantRole function 599 | function grantRole( 600 | bytes32 role, 601 | address account 602 | ) public virtual override(AccessControl) { 603 | // Check if the role is one of the predefined roles 604 | require( 605 | role == WHITELIST_SIGNER_ROLE || role == NFT_UPDATE_SIGNER_ROLE, 606 | "Invalid role" 607 | ); 608 | 609 | // Check if the account is not the zero address 610 | require(account != address(0), "Account cant be the zero address"); 611 | require( 612 | hasRole(DEFAULT_ADMIN_ROLE, msg.sender), 613 | "you are not authorized." 614 | ); 615 | _grantRole(role, account); 616 | } 617 | 618 | function updateTokenURI( 619 | uint16 tokenId, 620 | string calldata newMetadataUri, 621 | bytes calldata sig 622 | ) external { 623 | require(!_usedSignatures[sig], "Signature already used"); 624 | require( 625 | _requireOwned(tokenId) == msg.sender, 626 | "you are not owner of the token." 627 | ); 628 | require(bytes(newMetadataUri).length > 0, "Token URI cannot be empty"); 629 | bytes32 digest = keccak256( 630 | abi.encodePacked( 631 | tokenId, 632 | newMetadataUri, 633 | address(this), 634 | block.chainid 635 | ) 636 | ); 637 | require( 638 | hasRole(NFT_UPDATE_SIGNER_ROLE, digest.recover(sig)), 639 | "signature validation failed" 640 | ); 641 | _tokenURIs[tokenId] = newMetadataUri; 642 | _usedSignatures[sig] = true; 643 | emit UpdateTokenURI(tokenId, newMetadataUri, block.timestamp); 644 | } 645 | 646 | function tokenURI( 647 | uint256 tokenId 648 | ) public view override(ERC721) returns (string memory) { 649 | _requireOwned(tokenId); 650 | if (bytes(_tokenURIs[tokenId]).length > 0) { 651 | return _tokenURIs[tokenId]; 652 | } 653 | return 654 | string( 655 | abi.encodePacked( 656 | _baseURI(), 657 | _planetsMetadata.getMetadataIdByToken(tokenId).toString(), 658 | ".json" 659 | ) 660 | ); 661 | } 662 | 663 | function supportsInterface( 664 | bytes4 interfaceId 665 | ) 666 | public 667 | view 668 | override(ERC721, DynamicTraits, ERC2981, AccessControl) 669 | returns (bool) 670 | { 671 | return 672 | interfaceId == type(IERC721).interfaceId || // ERC-721 interface 673 | interfaceId == type(IERC721Metadata).interfaceId || // ERC-721 Metadata interface 674 | interfaceId == type(IERC7496).interfaceId || // Dynamic Trait 675 | interfaceId == type(IERC2981).interfaceId || // ERC-2981 Royalty interface 676 | interfaceId == type(IAccessControl).interfaceId || // AccessControl interface 677 | super.supportsInterface(interfaceId); 678 | } 679 | 680 | function maxSupply() 681 | external 682 | view 683 | virtual 684 | returns (uint8 gold, uint8 platinum, uint16 total) 685 | { 686 | return (GOLD_MAX_SUPPLY, PLATINUM_MAX_SUPPLY, MAX_SUPPLY); 687 | } 688 | 689 | // total current supply of gold+platinum+standard 690 | function totalSupply() public view returns (uint256) { 691 | return 692 | (_goldCurrIndex - 1) + 693 | (_platinumCurrIndex - _platinumStartIndex) + 694 | (_standardCurrIndex - _standardStartIndex); 695 | } 696 | 697 | function numberMinted( 698 | address walletAddress 699 | ) external view virtual returns (NftMinted memory) { 700 | return _numberMinted[walletAddress]; 701 | } 702 | 703 | function totalMinted() 704 | external 705 | view 706 | returns ( 707 | uint8 gold, 708 | uint8 platinum, 709 | uint16 standard, 710 | uint256[20] memory planetMinted 711 | ) 712 | { 713 | uint256[20] memory planetMintedArray; 714 | for (uint8 i = 1; i <= 20; i++) { 715 | planetMintedArray[i - 1] = 716 | NFTS_PER_PLANET - 717 | _planetsMetadata.getAvailableNFTsbyPlanet(i); 718 | } 719 | return ( 720 | _goldCurrIndex - 1, 721 | _platinumCurrIndex - _platinumStartIndex, 722 | _standardCurrIndex - _standardStartIndex, 723 | planetMintedArray 724 | ); 725 | } 726 | 727 | // function to get current round 728 | function getCurrentRound() external view returns (Round memory) { 729 | return _rounds[_currentRoundId]; 730 | } 731 | 732 | // function to get the minted count by planet 733 | function getMintedbyPlanet( 734 | uint8 planetNo 735 | ) external view virtual returns (uint256) { 736 | return 737 | NFTS_PER_PLANET - 738 | _planetsMetadata.getAvailableNFTsbyPlanet(planetNo); 739 | } 740 | 741 | //Internal Functions 742 | 743 | // The following functions are overrides required by Solidity. 744 | function _update( 745 | address to, 746 | uint256 tokenId, 747 | address auth 748 | ) internal override(ERC721, ERC721Pausable) returns (address) { 749 | return super._update(to, tokenId, auth); 750 | } 751 | 752 | function _baseURI() internal view virtual override returns (string memory) { 753 | return _baseTokenURI; 754 | } 755 | 756 | function _stringToUint256( 757 | string memory s 758 | ) internal pure returns (uint256 result) { 759 | bytes memory b = bytes(s); 760 | result = 0; 761 | for (uint256 i = 0; i < b.length; i++) { 762 | uint256 c = uint256(uint8(b[i])); 763 | if (c >= 48 && c <= 57) { 764 | result = result * 10 + (c - 48); 765 | } 766 | } 767 | return result; 768 | } 769 | 770 | function _isWhitelisted( 771 | string memory mintType, 772 | uint8 limit, 773 | bytes memory sig 774 | ) internal view returns (bool) { 775 | require(msg.sender != address(0), "Caller can't be null address"); 776 | require(limit > 0, "Limit must be greater than 0"); 777 | require(sig.length == 65, "Invalid signature length"); 778 | bytes32 digest = keccak256( 779 | abi.encodePacked( 780 | "\x19Ethereum Signed Message:\n32", 781 | keccak256( 782 | abi.encodePacked( 783 | mintType, 784 | msg.sender, 785 | limit, 786 | address(this), 787 | block.chainid 788 | ) 789 | ) 790 | ) 791 | ); 792 | return hasRole(WHITELIST_SIGNER_ROLE, digest.recover(sig)); 793 | } 794 | 795 | // Internal function for managing round creation and updates 796 | function _manageRound( 797 | uint8 roundId, 798 | uint256 startTime, 799 | uint256 endTime, 800 | uint256 price, 801 | uint8[] memory planets, 802 | uint16 supply, 803 | RoundType roundType, 804 | bytes32 merkleRoot, 805 | bool isPaused 806 | ) internal { 807 | require(startTime < endTime, "Start date must be before end date"); 808 | require(price > 0, "Price must be greater than zero"); 809 | require(planets.length > 0, "At least one planet must be specified"); 810 | require(supply > 0, "Supply must be greater than zero"); 811 | 812 | // Validate merkle root for Greenlist rounds. 813 | if (roundType == RoundType.Greenlist) { 814 | require( 815 | merkleRoot != bytes32(0), 816 | "Merkle root is required for Greenlist round" 817 | ); 818 | } 819 | 820 | // Validate planet numbers and update their status. 821 | for (uint8 i = 0; i < planets.length; i++) { 822 | uint8 planetNo = planets[i]; 823 | require( 824 | planetNo > 0 && planetNo <= 20, 825 | "Planet must be between 1 to 20." 826 | ); 827 | _planetEnabled[planetNo] = true; 828 | } 829 | 830 | // Create or update the round. 831 | _rounds[roundId] = Round( 832 | startTime, 833 | endTime, 834 | price, 835 | planets, 836 | supply, 837 | roundType, 838 | merkleRoot, 839 | isPaused 840 | ); 841 | 842 | // Set presale status for Presale rounds. 843 | if (roundType == RoundType.Presale) { 844 | _isPresaleActive = true; 845 | } 846 | } 847 | } 848 | -------------------------------------------------------------------------------- /contracts/PlanetsMetadata.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.22; 3 | 4 | import "@openzeppelin/contracts/access/Ownable.sol"; 5 | 6 | contract PlanetsMetadata is Ownable { 7 | event NftAddressUpdated(address indexed nftContractAddress); 8 | event RandomMetadataAssigned( 9 | uint256 indexed tokenId, 10 | uint8 indexed planetNo, 11 | uint16 indexed metadataId 12 | ); 13 | event MetadataIdSet(uint256 indexed tokenId, uint16 indexed metadataId); 14 | 15 | mapping(uint8 => uint16[]) private _planets; 16 | mapping(uint256 => uint16) private _metadataIds; 17 | 18 | address _nftAddr; 19 | 20 | constructor() Ownable(msg.sender) { 21 | _planets[1] = [ 22 | 106, 23 | 107, 24 | 108, 25 | 109, 26 | 110, 27 | 111, 28 | 112, 29 | 113, 30 | 114, 31 | 115, 32 | 116, 33 | 117, 34 | 118, 35 | 119, 36 | 120, 37 | 121, 38 | 122, 39 | 123, 40 | 124, 41 | 125, 42 | 126, 43 | 127, 44 | 128, 45 | 129, 46 | 130, 47 | 131, 48 | 132, 49 | 133, 50 | 134, 51 | 135, 52 | 136, 53 | 137, 54 | 138, 55 | 139, 56 | 140, 57 | 141, 58 | 142, 59 | 143, 60 | 144, 61 | 145, 62 | 146, 63 | 147, 64 | 148, 65 | 149, 66 | 150, 67 | 151, 68 | 152, 69 | 153, 70 | 154, 71 | 155, 72 | 156, 73 | 157, 74 | 158, 75 | 159, 76 | 160, 77 | 161, 78 | 162, 79 | 163, 80 | 164, 81 | 165, 82 | 166, 83 | 167, 84 | 168, 85 | 169, 86 | 170, 87 | 171, 88 | 172, 89 | 173, 90 | 174, 91 | 175, 92 | 176, 93 | 177, 94 | 178, 95 | 179, 96 | 180, 97 | 181, 98 | 182, 99 | 183, 100 | 184, 101 | 185, 102 | 186, 103 | 187, 104 | 188, 105 | 189, 106 | 190, 107 | 191, 108 | 192, 109 | 193, 110 | 194, 111 | 195, 112 | 196, 113 | 197, 114 | 198, 115 | 199, 116 | 200, 117 | 201, 118 | 202, 119 | 203, 120 | 204, 121 | 205, 122 | 206, 123 | 207, 124 | 208, 125 | 209, 126 | 210, 127 | 211, 128 | 212, 129 | 213, 130 | 214, 131 | 215, 132 | 216, 133 | 217, 134 | 218, 135 | 219, 136 | 220, 137 | 221, 138 | 222, 139 | 223, 140 | 224, 141 | 225, 142 | 226, 143 | 227, 144 | 228, 145 | 229, 146 | 230, 147 | 231, 148 | 232, 149 | 233, 150 | 234, 151 | 235, 152 | 236, 153 | 237, 154 | 238, 155 | 239, 156 | 240, 157 | 241, 158 | 242, 159 | 243, 160 | 244, 161 | 245, 162 | 246, 163 | 247, 164 | 248, 165 | 249, 166 | 250, 167 | 251, 168 | 252, 169 | 253, 170 | 254, 171 | 255, 172 | 256, 173 | 257, 174 | 258, 175 | 259, 176 | 260, 177 | 261, 178 | 262, 179 | 263, 180 | 264, 181 | 265, 182 | 266, 183 | 267, 184 | 268, 185 | 269, 186 | 270, 187 | 271, 188 | 272, 189 | 273, 190 | 274, 191 | 275, 192 | 276, 193 | 277, 194 | 278, 195 | 279, 196 | 280, 197 | 281, 198 | 282, 199 | 283, 200 | 284, 201 | 285, 202 | 286, 203 | 287, 204 | 288, 205 | 289, 206 | 290, 207 | 291, 208 | 292, 209 | 293, 210 | 294, 211 | 295, 212 | 296, 213 | 297, 214 | 298, 215 | 299, 216 | 300, 217 | 301, 218 | 302, 219 | 303, 220 | 304, 221 | 305, 222 | 306, 223 | 307, 224 | 308, 225 | 309, 226 | 310, 227 | 311, 228 | 312, 229 | 313, 230 | 314, 231 | 315, 232 | 316, 233 | 317, 234 | 318, 235 | 319, 236 | 320, 237 | 321, 238 | 322, 239 | 323, 240 | 324, 241 | 325, 242 | 326, 243 | 327, 244 | 328, 245 | 329, 246 | 330, 247 | 331, 248 | 332, 249 | 333, 250 | 334, 251 | 335, 252 | 336, 253 | 337, 254 | 338, 255 | 339, 256 | 340, 257 | 341, 258 | 342, 259 | 343, 260 | 344, 261 | 345, 262 | 346, 263 | 347, 264 | 348, 265 | 349, 266 | 350, 267 | 351, 268 | 352, 269 | 353, 270 | 354, 271 | 355, 272 | 356, 273 | 357 274 | ]; 275 | _planets[2] = [ 276 | 358, 277 | 359, 278 | 360, 279 | 361, 280 | 362, 281 | 363, 282 | 364, 283 | 365, 284 | 366, 285 | 367, 286 | 368, 287 | 369, 288 | 370, 289 | 371, 290 | 372, 291 | 373, 292 | 374, 293 | 375, 294 | 376, 295 | 377, 296 | 378, 297 | 379, 298 | 380, 299 | 381, 300 | 382, 301 | 383, 302 | 384, 303 | 385, 304 | 386, 305 | 387, 306 | 388, 307 | 389, 308 | 390, 309 | 391, 310 | 392, 311 | 393, 312 | 394, 313 | 395, 314 | 396, 315 | 397, 316 | 398, 317 | 399, 318 | 400, 319 | 401, 320 | 402, 321 | 403, 322 | 404, 323 | 405, 324 | 406, 325 | 407, 326 | 408, 327 | 409, 328 | 410, 329 | 411, 330 | 412, 331 | 413, 332 | 414, 333 | 415, 334 | 416, 335 | 417, 336 | 418, 337 | 419, 338 | 420, 339 | 421, 340 | 422, 341 | 423, 342 | 424, 343 | 425, 344 | 426, 345 | 427, 346 | 428, 347 | 429, 348 | 430, 349 | 431, 350 | 432, 351 | 433, 352 | 434, 353 | 435, 354 | 436, 355 | 437, 356 | 438, 357 | 439, 358 | 440, 359 | 441, 360 | 442, 361 | 443, 362 | 444, 363 | 445, 364 | 446, 365 | 447, 366 | 448, 367 | 449, 368 | 450, 369 | 451, 370 | 452, 371 | 453, 372 | 454, 373 | 455, 374 | 456, 375 | 457, 376 | 458, 377 | 459, 378 | 460, 379 | 461, 380 | 462, 381 | 463, 382 | 464, 383 | 465, 384 | 466, 385 | 467, 386 | 468, 387 | 469, 388 | 470, 389 | 471, 390 | 472, 391 | 473, 392 | 474, 393 | 475, 394 | 476, 395 | 477, 396 | 478, 397 | 479, 398 | 480, 399 | 481, 400 | 482, 401 | 483, 402 | 484, 403 | 485, 404 | 486, 405 | 487, 406 | 488, 407 | 489, 408 | 490, 409 | 491, 410 | 492, 411 | 493, 412 | 494, 413 | 495, 414 | 496, 415 | 497, 416 | 498, 417 | 499, 418 | 500, 419 | 501, 420 | 502, 421 | 503, 422 | 504, 423 | 505, 424 | 506, 425 | 507, 426 | 508, 427 | 509, 428 | 510, 429 | 511, 430 | 512, 431 | 513, 432 | 514, 433 | 515, 434 | 516, 435 | 517, 436 | 518, 437 | 519, 438 | 520, 439 | 521, 440 | 522, 441 | 523, 442 | 524, 443 | 525, 444 | 526, 445 | 527, 446 | 528, 447 | 529, 448 | 530, 449 | 531, 450 | 532, 451 | 533, 452 | 534, 453 | 535, 454 | 536, 455 | 537, 456 | 538, 457 | 539, 458 | 540, 459 | 541, 460 | 542, 461 | 543, 462 | 544, 463 | 545, 464 | 546, 465 | 547, 466 | 548, 467 | 549, 468 | 550, 469 | 551, 470 | 552, 471 | 553, 472 | 554, 473 | 555, 474 | 556, 475 | 557, 476 | 558, 477 | 559, 478 | 560, 479 | 561, 480 | 562, 481 | 563, 482 | 564, 483 | 565, 484 | 566, 485 | 567, 486 | 568, 487 | 569, 488 | 570, 489 | 571, 490 | 572, 491 | 573, 492 | 574, 493 | 575, 494 | 576, 495 | 577, 496 | 578, 497 | 579, 498 | 580, 499 | 581, 500 | 582, 501 | 583, 502 | 584, 503 | 585, 504 | 586, 505 | 587, 506 | 588, 507 | 589, 508 | 590, 509 | 591, 510 | 592, 511 | 593, 512 | 594, 513 | 595, 514 | 596, 515 | 597, 516 | 598, 517 | 599, 518 | 600, 519 | 601, 520 | 602, 521 | 603, 522 | 604, 523 | 605, 524 | 606, 525 | 607, 526 | 608, 527 | 609 528 | ]; 529 | _planets[3] = [ 530 | 610, 531 | 611, 532 | 612, 533 | 613, 534 | 614, 535 | 615, 536 | 616, 537 | 617, 538 | 618, 539 | 619, 540 | 620, 541 | 621, 542 | 622, 543 | 623, 544 | 624, 545 | 625, 546 | 626, 547 | 627, 548 | 628, 549 | 629, 550 | 630, 551 | 631, 552 | 632, 553 | 633, 554 | 634, 555 | 635, 556 | 636, 557 | 637, 558 | 638, 559 | 639, 560 | 640, 561 | 641, 562 | 642, 563 | 643, 564 | 644, 565 | 645, 566 | 646, 567 | 647, 568 | 648, 569 | 649, 570 | 650, 571 | 651, 572 | 652, 573 | 653, 574 | 654, 575 | 655, 576 | 656, 577 | 657, 578 | 658, 579 | 659, 580 | 660, 581 | 661, 582 | 662, 583 | 663, 584 | 664, 585 | 665, 586 | 666, 587 | 667, 588 | 668, 589 | 669, 590 | 670, 591 | 671, 592 | 672, 593 | 673, 594 | 674, 595 | 675, 596 | 676, 597 | 677, 598 | 678, 599 | 679, 600 | 680, 601 | 681, 602 | 682, 603 | 683, 604 | 684, 605 | 685, 606 | 686, 607 | 687, 608 | 688, 609 | 689, 610 | 690, 611 | 691, 612 | 692, 613 | 693, 614 | 694, 615 | 695, 616 | 696, 617 | 697, 618 | 698, 619 | 699, 620 | 700, 621 | 701, 622 | 702, 623 | 703, 624 | 704, 625 | 705, 626 | 706, 627 | 707, 628 | 708, 629 | 709, 630 | 710, 631 | 711, 632 | 712, 633 | 713, 634 | 714, 635 | 715, 636 | 716, 637 | 717, 638 | 718, 639 | 719, 640 | 720, 641 | 721, 642 | 722, 643 | 723, 644 | 724, 645 | 725, 646 | 726, 647 | 727, 648 | 728, 649 | 729, 650 | 730, 651 | 731, 652 | 732, 653 | 733, 654 | 734, 655 | 735, 656 | 736, 657 | 737, 658 | 738, 659 | 739, 660 | 740, 661 | 741, 662 | 742, 663 | 743, 664 | 744, 665 | 745, 666 | 746, 667 | 747, 668 | 748, 669 | 749, 670 | 750, 671 | 751, 672 | 752, 673 | 753, 674 | 754, 675 | 755, 676 | 756, 677 | 757, 678 | 758, 679 | 759, 680 | 760, 681 | 761, 682 | 762, 683 | 763, 684 | 764, 685 | 765, 686 | 766, 687 | 767, 688 | 768, 689 | 769, 690 | 770, 691 | 771, 692 | 772, 693 | 773, 694 | 774, 695 | 775, 696 | 776, 697 | 777, 698 | 778, 699 | 779, 700 | 780, 701 | 781, 702 | 782, 703 | 783, 704 | 784, 705 | 785, 706 | 786, 707 | 787, 708 | 788, 709 | 789, 710 | 790, 711 | 791, 712 | 792, 713 | 793, 714 | 794, 715 | 795, 716 | 796, 717 | 797, 718 | 798, 719 | 799, 720 | 800, 721 | 801, 722 | 802, 723 | 803, 724 | 804, 725 | 805, 726 | 806, 727 | 807, 728 | 808, 729 | 809, 730 | 810, 731 | 811, 732 | 812, 733 | 813, 734 | 814, 735 | 815, 736 | 816, 737 | 817, 738 | 818, 739 | 819, 740 | 820, 741 | 821, 742 | 822, 743 | 823, 744 | 824, 745 | 825, 746 | 826, 747 | 827, 748 | 828, 749 | 829, 750 | 830, 751 | 831, 752 | 832, 753 | 833, 754 | 834, 755 | 835, 756 | 836, 757 | 837, 758 | 838, 759 | 839, 760 | 840, 761 | 841, 762 | 842, 763 | 843, 764 | 844, 765 | 845, 766 | 846, 767 | 847, 768 | 848, 769 | 849, 770 | 850, 771 | 851, 772 | 852, 773 | 853, 774 | 854, 775 | 855, 776 | 856, 777 | 857, 778 | 858, 779 | 859, 780 | 860, 781 | 861 782 | ]; 783 | _planets[4] = [ 784 | 862, 785 | 863, 786 | 864, 787 | 865, 788 | 866, 789 | 867, 790 | 868, 791 | 869, 792 | 870, 793 | 871, 794 | 872, 795 | 873, 796 | 874, 797 | 875, 798 | 876, 799 | 877, 800 | 878, 801 | 879, 802 | 880, 803 | 881, 804 | 882, 805 | 883, 806 | 884, 807 | 885, 808 | 886, 809 | 887, 810 | 888, 811 | 889, 812 | 890, 813 | 891, 814 | 892, 815 | 893, 816 | 894, 817 | 895, 818 | 896, 819 | 897, 820 | 898, 821 | 899, 822 | 900, 823 | 901, 824 | 902, 825 | 903, 826 | 904, 827 | 905, 828 | 906, 829 | 907, 830 | 908, 831 | 909, 832 | 910, 833 | 911, 834 | 912, 835 | 913, 836 | 914, 837 | 915, 838 | 916, 839 | 917, 840 | 918, 841 | 919, 842 | 920, 843 | 921, 844 | 922, 845 | 923, 846 | 924, 847 | 925, 848 | 926, 849 | 927, 850 | 928, 851 | 929, 852 | 930, 853 | 931, 854 | 932, 855 | 933, 856 | 934, 857 | 935, 858 | 936, 859 | 937, 860 | 938, 861 | 939, 862 | 940, 863 | 941, 864 | 942, 865 | 943, 866 | 944, 867 | 945, 868 | 946, 869 | 947, 870 | 948, 871 | 949, 872 | 950, 873 | 951, 874 | 952, 875 | 953, 876 | 954, 877 | 955, 878 | 956, 879 | 957, 880 | 958, 881 | 959, 882 | 960, 883 | 961, 884 | 962, 885 | 963, 886 | 964, 887 | 965, 888 | 966, 889 | 967, 890 | 968, 891 | 969, 892 | 970, 893 | 971, 894 | 972, 895 | 973, 896 | 974, 897 | 975, 898 | 976, 899 | 977, 900 | 978, 901 | 979, 902 | 980, 903 | 981, 904 | 982, 905 | 983, 906 | 984, 907 | 985, 908 | 986, 909 | 987, 910 | 988, 911 | 989, 912 | 990, 913 | 991, 914 | 992, 915 | 993, 916 | 994, 917 | 995, 918 | 996, 919 | 997, 920 | 998, 921 | 999, 922 | 1000, 923 | 1001, 924 | 1002, 925 | 1003, 926 | 1004, 927 | 1005, 928 | 1006, 929 | 1007, 930 | 1008, 931 | 1009, 932 | 1010, 933 | 1011, 934 | 1012, 935 | 1013, 936 | 1014, 937 | 1015, 938 | 1016, 939 | 1017, 940 | 1018, 941 | 1019, 942 | 1020, 943 | 1021, 944 | 1022, 945 | 1023, 946 | 1024, 947 | 1025, 948 | 1026, 949 | 1027, 950 | 1028, 951 | 1029, 952 | 1030, 953 | 1031, 954 | 1032, 955 | 1033, 956 | 1034, 957 | 1035, 958 | 1036, 959 | 1037, 960 | 1038, 961 | 1039, 962 | 1040, 963 | 1041, 964 | 1042, 965 | 1043, 966 | 1044, 967 | 1045, 968 | 1046, 969 | 1047, 970 | 1048, 971 | 1049, 972 | 1050, 973 | 1051, 974 | 1052, 975 | 1053, 976 | 1054, 977 | 1055, 978 | 1056, 979 | 1057, 980 | 1058, 981 | 1059, 982 | 1060, 983 | 1061, 984 | 1062, 985 | 1063, 986 | 1064, 987 | 1065, 988 | 1066, 989 | 1067, 990 | 1068, 991 | 1069, 992 | 1070, 993 | 1071, 994 | 1072, 995 | 1073, 996 | 1074, 997 | 1075, 998 | 1076, 999 | 1077, 1000 | 1078, 1001 | 1079, 1002 | 1080, 1003 | 1081, 1004 | 1082, 1005 | 1083, 1006 | 1084, 1007 | 1085, 1008 | 1086, 1009 | 1087, 1010 | 1088, 1011 | 1089, 1012 | 1090, 1013 | 1091, 1014 | 1092, 1015 | 1093, 1016 | 1094, 1017 | 1095, 1018 | 1096, 1019 | 1097, 1020 | 1098, 1021 | 1099, 1022 | 1100, 1023 | 1101, 1024 | 1102, 1025 | 1103, 1026 | 1104, 1027 | 1105, 1028 | 1106, 1029 | 1107, 1030 | 1108, 1031 | 1109, 1032 | 1110, 1033 | 1111, 1034 | 1112, 1035 | 1113 1036 | ]; 1037 | _planets[5] = [ 1038 | 1114, 1039 | 1115, 1040 | 1116, 1041 | 1117, 1042 | 1118, 1043 | 1119, 1044 | 1120, 1045 | 1121, 1046 | 1122, 1047 | 1123, 1048 | 1124, 1049 | 1125, 1050 | 1126, 1051 | 1127, 1052 | 1128, 1053 | 1129, 1054 | 1130, 1055 | 1131, 1056 | 1132, 1057 | 1133, 1058 | 1134, 1059 | 1135, 1060 | 1136, 1061 | 1137, 1062 | 1138, 1063 | 1139, 1064 | 1140, 1065 | 1141, 1066 | 1142, 1067 | 1143, 1068 | 1144, 1069 | 1145, 1070 | 1146, 1071 | 1147, 1072 | 1148, 1073 | 1149, 1074 | 1150, 1075 | 1151, 1076 | 1152, 1077 | 1153, 1078 | 1154, 1079 | 1155, 1080 | 1156, 1081 | 1157, 1082 | 1158, 1083 | 1159, 1084 | 1160, 1085 | 1161, 1086 | 1162, 1087 | 1163, 1088 | 1164, 1089 | 1165, 1090 | 1166, 1091 | 1167, 1092 | 1168, 1093 | 1169, 1094 | 1170, 1095 | 1171, 1096 | 1172, 1097 | 1173, 1098 | 1174, 1099 | 1175, 1100 | 1176, 1101 | 1177, 1102 | 1178, 1103 | 1179, 1104 | 1180, 1105 | 1181, 1106 | 1182, 1107 | 1183, 1108 | 1184, 1109 | 1185, 1110 | 1186, 1111 | 1187, 1112 | 1188, 1113 | 1189, 1114 | 1190, 1115 | 1191, 1116 | 1192, 1117 | 1193, 1118 | 1194, 1119 | 1195, 1120 | 1196, 1121 | 1197, 1122 | 1198, 1123 | 1199, 1124 | 1200, 1125 | 1201, 1126 | 1202, 1127 | 1203, 1128 | 1204, 1129 | 1205, 1130 | 1206, 1131 | 1207, 1132 | 1208, 1133 | 1209, 1134 | 1210, 1135 | 1211, 1136 | 1212, 1137 | 1213, 1138 | 1214, 1139 | 1215, 1140 | 1216, 1141 | 1217, 1142 | 1218, 1143 | 1219, 1144 | 1220, 1145 | 1221, 1146 | 1222, 1147 | 1223, 1148 | 1224, 1149 | 1225, 1150 | 1226, 1151 | 1227, 1152 | 1228, 1153 | 1229, 1154 | 1230, 1155 | 1231, 1156 | 1232, 1157 | 1233, 1158 | 1234, 1159 | 1235, 1160 | 1236, 1161 | 1237, 1162 | 1238, 1163 | 1239, 1164 | 1240, 1165 | 1241, 1166 | 1242, 1167 | 1243, 1168 | 1244, 1169 | 1245, 1170 | 1246, 1171 | 1247, 1172 | 1248, 1173 | 1249, 1174 | 1250, 1175 | 1251, 1176 | 1252, 1177 | 1253, 1178 | 1254, 1179 | 1255, 1180 | 1256, 1181 | 1257, 1182 | 1258, 1183 | 1259, 1184 | 1260, 1185 | 1261, 1186 | 1262, 1187 | 1263, 1188 | 1264, 1189 | 1265, 1190 | 1266, 1191 | 1267, 1192 | 1268, 1193 | 1269, 1194 | 1270, 1195 | 1271, 1196 | 1272, 1197 | 1273, 1198 | 1274, 1199 | 1275, 1200 | 1276, 1201 | 1277, 1202 | 1278, 1203 | 1279, 1204 | 1280, 1205 | 1281, 1206 | 1282, 1207 | 1283, 1208 | 1284, 1209 | 1285, 1210 | 1286, 1211 | 1287, 1212 | 1288, 1213 | 1289, 1214 | 1290, 1215 | 1291, 1216 | 1292, 1217 | 1293, 1218 | 1294, 1219 | 1295, 1220 | 1296, 1221 | 1297, 1222 | 1298, 1223 | 1299, 1224 | 1300, 1225 | 1301, 1226 | 1302, 1227 | 1303, 1228 | 1304, 1229 | 1305, 1230 | 1306, 1231 | 1307, 1232 | 1308, 1233 | 1309, 1234 | 1310, 1235 | 1311, 1236 | 1312, 1237 | 1313, 1238 | 1314, 1239 | 1315, 1240 | 1316, 1241 | 1317, 1242 | 1318, 1243 | 1319, 1244 | 1320, 1245 | 1321, 1246 | 1322, 1247 | 1323, 1248 | 1324, 1249 | 1325, 1250 | 1326, 1251 | 1327, 1252 | 1328, 1253 | 1329, 1254 | 1330, 1255 | 1331, 1256 | 1332, 1257 | 1333, 1258 | 1334, 1259 | 1335, 1260 | 1336, 1261 | 1337, 1262 | 1338, 1263 | 1339, 1264 | 1340, 1265 | 1341, 1266 | 1342, 1267 | 1343, 1268 | 1344, 1269 | 1345, 1270 | 1346, 1271 | 1347, 1272 | 1348, 1273 | 1349, 1274 | 1350, 1275 | 1351, 1276 | 1352, 1277 | 1353, 1278 | 1354, 1279 | 1355, 1280 | 1356, 1281 | 1357, 1282 | 1358, 1283 | 1359, 1284 | 1360, 1285 | 1361, 1286 | 1362, 1287 | 1363, 1288 | 1364, 1289 | 1365 1290 | ]; 1291 | _planets[6] = [ 1292 | 1366, 1293 | 1367, 1294 | 1368, 1295 | 1369, 1296 | 1370, 1297 | 1371, 1298 | 1372, 1299 | 1373, 1300 | 1374, 1301 | 1375, 1302 | 1376, 1303 | 1377, 1304 | 1378, 1305 | 1379, 1306 | 1380, 1307 | 1381, 1308 | 1382, 1309 | 1383, 1310 | 1384, 1311 | 1385, 1312 | 1386, 1313 | 1387, 1314 | 1388, 1315 | 1389, 1316 | 1390, 1317 | 1391, 1318 | 1392, 1319 | 1393, 1320 | 1394, 1321 | 1395, 1322 | 1396, 1323 | 1397, 1324 | 1398, 1325 | 1399, 1326 | 1400, 1327 | 1401, 1328 | 1402, 1329 | 1403, 1330 | 1404, 1331 | 1405, 1332 | 1406, 1333 | 1407, 1334 | 1408, 1335 | 1409, 1336 | 1410, 1337 | 1411, 1338 | 1412, 1339 | 1413, 1340 | 1414, 1341 | 1415, 1342 | 1416, 1343 | 1417, 1344 | 1418, 1345 | 1419, 1346 | 1420, 1347 | 1421, 1348 | 1422, 1349 | 1423, 1350 | 1424, 1351 | 1425, 1352 | 1426, 1353 | 1427, 1354 | 1428, 1355 | 1429, 1356 | 1430, 1357 | 1431, 1358 | 1432, 1359 | 1433, 1360 | 1434, 1361 | 1435, 1362 | 1436, 1363 | 1437, 1364 | 1438, 1365 | 1439, 1366 | 1440, 1367 | 1441, 1368 | 1442, 1369 | 1443, 1370 | 1444, 1371 | 1445, 1372 | 1446, 1373 | 1447, 1374 | 1448, 1375 | 1449, 1376 | 1450, 1377 | 1451, 1378 | 1452, 1379 | 1453, 1380 | 1454, 1381 | 1455, 1382 | 1456, 1383 | 1457, 1384 | 1458, 1385 | 1459, 1386 | 1460, 1387 | 1461, 1388 | 1462, 1389 | 1463, 1390 | 1464, 1391 | 1465, 1392 | 1466, 1393 | 1467, 1394 | 1468, 1395 | 1469, 1396 | 1470, 1397 | 1471, 1398 | 1472, 1399 | 1473, 1400 | 1474, 1401 | 1475, 1402 | 1476, 1403 | 1477, 1404 | 1478, 1405 | 1479, 1406 | 1480, 1407 | 1481, 1408 | 1482, 1409 | 1483, 1410 | 1484, 1411 | 1485, 1412 | 1486, 1413 | 1487, 1414 | 1488, 1415 | 1489, 1416 | 1490, 1417 | 1491, 1418 | 1492, 1419 | 1493, 1420 | 1494, 1421 | 1495, 1422 | 1496, 1423 | 1497, 1424 | 1498, 1425 | 1499, 1426 | 1500, 1427 | 1501, 1428 | 1502, 1429 | 1503, 1430 | 1504, 1431 | 1505, 1432 | 1506, 1433 | 1507, 1434 | 1508, 1435 | 1509, 1436 | 1510, 1437 | 1511, 1438 | 1512, 1439 | 1513, 1440 | 1514, 1441 | 1515, 1442 | 1516, 1443 | 1517, 1444 | 1518, 1445 | 1519, 1446 | 1520, 1447 | 1521, 1448 | 1522, 1449 | 1523, 1450 | 1524, 1451 | 1525, 1452 | 1526, 1453 | 1527, 1454 | 1528, 1455 | 1529, 1456 | 1530, 1457 | 1531, 1458 | 1532, 1459 | 1533, 1460 | 1534, 1461 | 1535, 1462 | 1536, 1463 | 1537, 1464 | 1538, 1465 | 1539, 1466 | 1540, 1467 | 1541, 1468 | 1542, 1469 | 1543, 1470 | 1544, 1471 | 1545, 1472 | 1546, 1473 | 1547, 1474 | 1548, 1475 | 1549, 1476 | 1550, 1477 | 1551, 1478 | 1552, 1479 | 1553, 1480 | 1554, 1481 | 1555, 1482 | 1556, 1483 | 1557, 1484 | 1558, 1485 | 1559, 1486 | 1560, 1487 | 1561, 1488 | 1562, 1489 | 1563, 1490 | 1564, 1491 | 1565, 1492 | 1566, 1493 | 1567, 1494 | 1568, 1495 | 1569, 1496 | 1570, 1497 | 1571, 1498 | 1572, 1499 | 1573, 1500 | 1574, 1501 | 1575, 1502 | 1576, 1503 | 1577, 1504 | 1578, 1505 | 1579, 1506 | 1580, 1507 | 1581, 1508 | 1582, 1509 | 1583, 1510 | 1584, 1511 | 1585, 1512 | 1586, 1513 | 1587, 1514 | 1588, 1515 | 1589, 1516 | 1590, 1517 | 1591, 1518 | 1592, 1519 | 1593, 1520 | 1594, 1521 | 1595, 1522 | 1596, 1523 | 1597, 1524 | 1598, 1525 | 1599, 1526 | 1600, 1527 | 1601, 1528 | 1602, 1529 | 1603, 1530 | 1604, 1531 | 1605, 1532 | 1606, 1533 | 1607, 1534 | 1608, 1535 | 1609, 1536 | 1610, 1537 | 1611, 1538 | 1612, 1539 | 1613, 1540 | 1614, 1541 | 1615, 1542 | 1616, 1543 | 1617 1544 | ]; 1545 | _planets[7] = [ 1546 | 1618, 1547 | 1619, 1548 | 1620, 1549 | 1621, 1550 | 1622, 1551 | 1623, 1552 | 1624, 1553 | 1625, 1554 | 1626, 1555 | 1627, 1556 | 1628, 1557 | 1629, 1558 | 1630, 1559 | 1631, 1560 | 1632, 1561 | 1633, 1562 | 1634, 1563 | 1635, 1564 | 1636, 1565 | 1637, 1566 | 1638, 1567 | 1639, 1568 | 1640, 1569 | 1641, 1570 | 1642, 1571 | 1643, 1572 | 1644, 1573 | 1645, 1574 | 1646, 1575 | 1647, 1576 | 1648, 1577 | 1649, 1578 | 1650, 1579 | 1651, 1580 | 1652, 1581 | 1653, 1582 | 1654, 1583 | 1655, 1584 | 1656, 1585 | 1657, 1586 | 1658, 1587 | 1659, 1588 | 1660, 1589 | 1661, 1590 | 1662, 1591 | 1663, 1592 | 1664, 1593 | 1665, 1594 | 1666, 1595 | 1667, 1596 | 1668, 1597 | 1669, 1598 | 1670, 1599 | 1671, 1600 | 1672, 1601 | 1673, 1602 | 1674, 1603 | 1675, 1604 | 1676, 1605 | 1677, 1606 | 1678, 1607 | 1679, 1608 | 1680, 1609 | 1681, 1610 | 1682, 1611 | 1683, 1612 | 1684, 1613 | 1685, 1614 | 1686, 1615 | 1687, 1616 | 1688, 1617 | 1689, 1618 | 1690, 1619 | 1691, 1620 | 1692, 1621 | 1693, 1622 | 1694, 1623 | 1695, 1624 | 1696, 1625 | 1697, 1626 | 1698, 1627 | 1699, 1628 | 1700, 1629 | 1701, 1630 | 1702, 1631 | 1703, 1632 | 1704, 1633 | 1705, 1634 | 1706, 1635 | 1707, 1636 | 1708, 1637 | 1709, 1638 | 1710, 1639 | 1711, 1640 | 1712, 1641 | 1713, 1642 | 1714, 1643 | 1715, 1644 | 1716, 1645 | 1717, 1646 | 1718, 1647 | 1719, 1648 | 1720, 1649 | 1721, 1650 | 1722, 1651 | 1723, 1652 | 1724, 1653 | 1725, 1654 | 1726, 1655 | 1727, 1656 | 1728, 1657 | 1729, 1658 | 1730, 1659 | 1731, 1660 | 1732, 1661 | 1733, 1662 | 1734, 1663 | 1735, 1664 | 1736, 1665 | 1737, 1666 | 1738, 1667 | 1739, 1668 | 1740, 1669 | 1741, 1670 | 1742, 1671 | 1743, 1672 | 1744, 1673 | 1745, 1674 | 1746, 1675 | 1747, 1676 | 1748, 1677 | 1749, 1678 | 1750, 1679 | 1751, 1680 | 1752, 1681 | 1753, 1682 | 1754, 1683 | 1755, 1684 | 1756, 1685 | 1757, 1686 | 1758, 1687 | 1759, 1688 | 1760, 1689 | 1761, 1690 | 1762, 1691 | 1763, 1692 | 1764, 1693 | 1765, 1694 | 1766, 1695 | 1767, 1696 | 1768, 1697 | 1769, 1698 | 1770, 1699 | 1771, 1700 | 1772, 1701 | 1773, 1702 | 1774, 1703 | 1775, 1704 | 1776, 1705 | 1777, 1706 | 1778, 1707 | 1779, 1708 | 1780, 1709 | 1781, 1710 | 1782, 1711 | 1783, 1712 | 1784, 1713 | 1785, 1714 | 1786, 1715 | 1787, 1716 | 1788, 1717 | 1789, 1718 | 1790, 1719 | 1791, 1720 | 1792, 1721 | 1793, 1722 | 1794, 1723 | 1795, 1724 | 1796, 1725 | 1797, 1726 | 1798, 1727 | 1799, 1728 | 1800, 1729 | 1801, 1730 | 1802, 1731 | 1803, 1732 | 1804, 1733 | 1805, 1734 | 1806, 1735 | 1807, 1736 | 1808, 1737 | 1809, 1738 | 1810, 1739 | 1811, 1740 | 1812, 1741 | 1813, 1742 | 1814, 1743 | 1815, 1744 | 1816, 1745 | 1817, 1746 | 1818, 1747 | 1819, 1748 | 1820, 1749 | 1821, 1750 | 1822, 1751 | 1823, 1752 | 1824, 1753 | 1825, 1754 | 1826, 1755 | 1827, 1756 | 1828, 1757 | 1829, 1758 | 1830, 1759 | 1831, 1760 | 1832, 1761 | 1833, 1762 | 1834, 1763 | 1835, 1764 | 1836, 1765 | 1837, 1766 | 1838, 1767 | 1839, 1768 | 1840, 1769 | 1841, 1770 | 1842, 1771 | 1843, 1772 | 1844, 1773 | 1845, 1774 | 1846, 1775 | 1847, 1776 | 1848, 1777 | 1849, 1778 | 1850, 1779 | 1851, 1780 | 1852, 1781 | 1853, 1782 | 1854, 1783 | 1855, 1784 | 1856, 1785 | 1857, 1786 | 1858, 1787 | 1859, 1788 | 1860, 1789 | 1861, 1790 | 1862, 1791 | 1863, 1792 | 1864, 1793 | 1865, 1794 | 1866, 1795 | 1867, 1796 | 1868, 1797 | 1869 1798 | ]; 1799 | _planets[8] = [ 1800 | 1870, 1801 | 1871, 1802 | 1872, 1803 | 1873, 1804 | 1874, 1805 | 1875, 1806 | 1876, 1807 | 1877, 1808 | 1878, 1809 | 1879, 1810 | 1880, 1811 | 1881, 1812 | 1882, 1813 | 1883, 1814 | 1884, 1815 | 1885, 1816 | 1886, 1817 | 1887, 1818 | 1888, 1819 | 1889, 1820 | 1890, 1821 | 1891, 1822 | 1892, 1823 | 1893, 1824 | 1894, 1825 | 1895, 1826 | 1896, 1827 | 1897, 1828 | 1898, 1829 | 1899, 1830 | 1900, 1831 | 1901, 1832 | 1902, 1833 | 1903, 1834 | 1904, 1835 | 1905, 1836 | 1906, 1837 | 1907, 1838 | 1908, 1839 | 1909, 1840 | 1910, 1841 | 1911, 1842 | 1912, 1843 | 1913, 1844 | 1914, 1845 | 1915, 1846 | 1916, 1847 | 1917, 1848 | 1918, 1849 | 1919, 1850 | 1920, 1851 | 1921, 1852 | 1922, 1853 | 1923, 1854 | 1924, 1855 | 1925, 1856 | 1926, 1857 | 1927, 1858 | 1928, 1859 | 1929, 1860 | 1930, 1861 | 1931, 1862 | 1932, 1863 | 1933, 1864 | 1934, 1865 | 1935, 1866 | 1936, 1867 | 1937, 1868 | 1938, 1869 | 1939, 1870 | 1940, 1871 | 1941, 1872 | 1942, 1873 | 1943, 1874 | 1944, 1875 | 1945, 1876 | 1946, 1877 | 1947, 1878 | 1948, 1879 | 1949, 1880 | 1950, 1881 | 1951, 1882 | 1952, 1883 | 1953, 1884 | 1954, 1885 | 1955, 1886 | 1956, 1887 | 1957, 1888 | 1958, 1889 | 1959, 1890 | 1960, 1891 | 1961, 1892 | 1962, 1893 | 1963, 1894 | 1964, 1895 | 1965, 1896 | 1966, 1897 | 1967, 1898 | 1968, 1899 | 1969, 1900 | 1970, 1901 | 1971, 1902 | 1972, 1903 | 1973, 1904 | 1974, 1905 | 1975, 1906 | 1976, 1907 | 1977, 1908 | 1978, 1909 | 1979, 1910 | 1980, 1911 | 1981, 1912 | 1982, 1913 | 1983, 1914 | 1984, 1915 | 1985, 1916 | 1986, 1917 | 1987, 1918 | 1988, 1919 | 1989, 1920 | 1990, 1921 | 1991, 1922 | 1992, 1923 | 1993, 1924 | 1994, 1925 | 1995, 1926 | 1996, 1927 | 1997, 1928 | 1998, 1929 | 1999, 1930 | 2000, 1931 | 2001, 1932 | 2002, 1933 | 2003, 1934 | 2004, 1935 | 2005, 1936 | 2006, 1937 | 2007, 1938 | 2008, 1939 | 2009, 1940 | 2010, 1941 | 2011, 1942 | 2012, 1943 | 2013, 1944 | 2014, 1945 | 2015, 1946 | 2016, 1947 | 2017, 1948 | 2018, 1949 | 2019, 1950 | 2020, 1951 | 2021, 1952 | 2022, 1953 | 2023, 1954 | 2024, 1955 | 2025, 1956 | 2026, 1957 | 2027, 1958 | 2028, 1959 | 2029, 1960 | 2030, 1961 | 2031, 1962 | 2032, 1963 | 2033, 1964 | 2034, 1965 | 2035, 1966 | 2036, 1967 | 2037, 1968 | 2038, 1969 | 2039, 1970 | 2040, 1971 | 2041, 1972 | 2042, 1973 | 2043, 1974 | 2044, 1975 | 2045, 1976 | 2046, 1977 | 2047, 1978 | 2048, 1979 | 2049, 1980 | 2050, 1981 | 2051, 1982 | 2052, 1983 | 2053, 1984 | 2054, 1985 | 2055, 1986 | 2056, 1987 | 2057, 1988 | 2058, 1989 | 2059, 1990 | 2060, 1991 | 2061, 1992 | 2062, 1993 | 2063, 1994 | 2064, 1995 | 2065, 1996 | 2066, 1997 | 2067, 1998 | 2068, 1999 | 2069, 2000 | 2070, 2001 | 2071, 2002 | 2072, 2003 | 2073, 2004 | 2074, 2005 | 2075, 2006 | 2076, 2007 | 2077, 2008 | 2078, 2009 | 2079, 2010 | 2080, 2011 | 2081, 2012 | 2082, 2013 | 2083, 2014 | 2084, 2015 | 2085, 2016 | 2086, 2017 | 2087, 2018 | 2088, 2019 | 2089, 2020 | 2090, 2021 | 2091, 2022 | 2092, 2023 | 2093, 2024 | 2094, 2025 | 2095, 2026 | 2096, 2027 | 2097, 2028 | 2098, 2029 | 2099, 2030 | 2100, 2031 | 2101, 2032 | 2102, 2033 | 2103, 2034 | 2104, 2035 | 2105, 2036 | 2106, 2037 | 2107, 2038 | 2108, 2039 | 2109, 2040 | 2110, 2041 | 2111, 2042 | 2112, 2043 | 2113, 2044 | 2114, 2045 | 2115, 2046 | 2116, 2047 | 2117, 2048 | 2118, 2049 | 2119, 2050 | 2120, 2051 | 2121 2052 | ]; 2053 | _planets[9] = [ 2054 | 2122, 2055 | 2123, 2056 | 2124, 2057 | 2125, 2058 | 2126, 2059 | 2127, 2060 | 2128, 2061 | 2129, 2062 | 2130, 2063 | 2131, 2064 | 2132, 2065 | 2133, 2066 | 2134, 2067 | 2135, 2068 | 2136, 2069 | 2137, 2070 | 2138, 2071 | 2139, 2072 | 2140, 2073 | 2141, 2074 | 2142, 2075 | 2143, 2076 | 2144, 2077 | 2145, 2078 | 2146, 2079 | 2147, 2080 | 2148, 2081 | 2149, 2082 | 2150, 2083 | 2151, 2084 | 2152, 2085 | 2153, 2086 | 2154, 2087 | 2155, 2088 | 2156, 2089 | 2157, 2090 | 2158, 2091 | 2159, 2092 | 2160, 2093 | 2161, 2094 | 2162, 2095 | 2163, 2096 | 2164, 2097 | 2165, 2098 | 2166, 2099 | 2167, 2100 | 2168, 2101 | 2169, 2102 | 2170, 2103 | 2171, 2104 | 2172, 2105 | 2173, 2106 | 2174, 2107 | 2175, 2108 | 2176, 2109 | 2177, 2110 | 2178, 2111 | 2179, 2112 | 2180, 2113 | 2181, 2114 | 2182, 2115 | 2183, 2116 | 2184, 2117 | 2185, 2118 | 2186, 2119 | 2187, 2120 | 2188, 2121 | 2189, 2122 | 2190, 2123 | 2191, 2124 | 2192, 2125 | 2193, 2126 | 2194, 2127 | 2195, 2128 | 2196, 2129 | 2197, 2130 | 2198, 2131 | 2199, 2132 | 2200, 2133 | 2201, 2134 | 2202, 2135 | 2203, 2136 | 2204, 2137 | 2205, 2138 | 2206, 2139 | 2207, 2140 | 2208, 2141 | 2209, 2142 | 2210, 2143 | 2211, 2144 | 2212, 2145 | 2213, 2146 | 2214, 2147 | 2215, 2148 | 2216, 2149 | 2217, 2150 | 2218, 2151 | 2219, 2152 | 2220, 2153 | 2221, 2154 | 2222, 2155 | 2223, 2156 | 2224, 2157 | 2225, 2158 | 2226, 2159 | 2227, 2160 | 2228, 2161 | 2229, 2162 | 2230, 2163 | 2231, 2164 | 2232, 2165 | 2233, 2166 | 2234, 2167 | 2235, 2168 | 2236, 2169 | 2237, 2170 | 2238, 2171 | 2239, 2172 | 2240, 2173 | 2241, 2174 | 2242, 2175 | 2243, 2176 | 2244, 2177 | 2245, 2178 | 2246, 2179 | 2247, 2180 | 2248, 2181 | 2249, 2182 | 2250, 2183 | 2251, 2184 | 2252, 2185 | 2253, 2186 | 2254, 2187 | 2255, 2188 | 2256, 2189 | 2257, 2190 | 2258, 2191 | 2259, 2192 | 2260, 2193 | 2261, 2194 | 2262, 2195 | 2263, 2196 | 2264, 2197 | 2265, 2198 | 2266, 2199 | 2267, 2200 | 2268, 2201 | 2269, 2202 | 2270, 2203 | 2271, 2204 | 2272, 2205 | 2273, 2206 | 2274, 2207 | 2275, 2208 | 2276, 2209 | 2277, 2210 | 2278, 2211 | 2279, 2212 | 2280, 2213 | 2281, 2214 | 2282, 2215 | 2283, 2216 | 2284, 2217 | 2285, 2218 | 2286, 2219 | 2287, 2220 | 2288, 2221 | 2289, 2222 | 2290, 2223 | 2291, 2224 | 2292, 2225 | 2293, 2226 | 2294, 2227 | 2295, 2228 | 2296, 2229 | 2297, 2230 | 2298, 2231 | 2299, 2232 | 2300, 2233 | 2301, 2234 | 2302, 2235 | 2303, 2236 | 2304, 2237 | 2305, 2238 | 2306, 2239 | 2307, 2240 | 2308, 2241 | 2309, 2242 | 2310, 2243 | 2311, 2244 | 2312, 2245 | 2313, 2246 | 2314, 2247 | 2315, 2248 | 2316, 2249 | 2317, 2250 | 2318, 2251 | 2319, 2252 | 2320, 2253 | 2321, 2254 | 2322, 2255 | 2323, 2256 | 2324, 2257 | 2325, 2258 | 2326, 2259 | 2327, 2260 | 2328, 2261 | 2329, 2262 | 2330, 2263 | 2331, 2264 | 2332, 2265 | 2333, 2266 | 2334, 2267 | 2335, 2268 | 2336, 2269 | 2337, 2270 | 2338, 2271 | 2339, 2272 | 2340, 2273 | 2341, 2274 | 2342, 2275 | 2343, 2276 | 2344, 2277 | 2345, 2278 | 2346, 2279 | 2347, 2280 | 2348, 2281 | 2349, 2282 | 2350, 2283 | 2351, 2284 | 2352, 2285 | 2353, 2286 | 2354, 2287 | 2355, 2288 | 2356, 2289 | 2357, 2290 | 2358, 2291 | 2359, 2292 | 2360, 2293 | 2361, 2294 | 2362, 2295 | 2363, 2296 | 2364, 2297 | 2365, 2298 | 2366, 2299 | 2367, 2300 | 2368, 2301 | 2369, 2302 | 2370, 2303 | 2371, 2304 | 2372, 2305 | 2373 2306 | ]; 2307 | _planets[10] = [ 2308 | 2374, 2309 | 2375, 2310 | 2376, 2311 | 2377, 2312 | 2378, 2313 | 2379, 2314 | 2380, 2315 | 2381, 2316 | 2382, 2317 | 2383, 2318 | 2384, 2319 | 2385, 2320 | 2386, 2321 | 2387, 2322 | 2388, 2323 | 2389, 2324 | 2390, 2325 | 2391, 2326 | 2392, 2327 | 2393, 2328 | 2394, 2329 | 2395, 2330 | 2396, 2331 | 2397, 2332 | 2398, 2333 | 2399, 2334 | 2400, 2335 | 2401, 2336 | 2402, 2337 | 2403, 2338 | 2404, 2339 | 2405, 2340 | 2406, 2341 | 2407, 2342 | 2408, 2343 | 2409, 2344 | 2410, 2345 | 2411, 2346 | 2412, 2347 | 2413, 2348 | 2414, 2349 | 2415, 2350 | 2416, 2351 | 2417, 2352 | 2418, 2353 | 2419, 2354 | 2420, 2355 | 2421, 2356 | 2422, 2357 | 2423, 2358 | 2424, 2359 | 2425, 2360 | 2426, 2361 | 2427, 2362 | 2428, 2363 | 2429, 2364 | 2430, 2365 | 2431, 2366 | 2432, 2367 | 2433, 2368 | 2434, 2369 | 2435, 2370 | 2436, 2371 | 2437, 2372 | 2438, 2373 | 2439, 2374 | 2440, 2375 | 2441, 2376 | 2442, 2377 | 2443, 2378 | 2444, 2379 | 2445, 2380 | 2446, 2381 | 2447, 2382 | 2448, 2383 | 2449, 2384 | 2450, 2385 | 2451, 2386 | 2452, 2387 | 2453, 2388 | 2454, 2389 | 2455, 2390 | 2456, 2391 | 2457, 2392 | 2458, 2393 | 2459, 2394 | 2460, 2395 | 2461, 2396 | 2462, 2397 | 2463, 2398 | 2464, 2399 | 2465, 2400 | 2466, 2401 | 2467, 2402 | 2468, 2403 | 2469, 2404 | 2470, 2405 | 2471, 2406 | 2472, 2407 | 2473, 2408 | 2474, 2409 | 2475, 2410 | 2476, 2411 | 2477, 2412 | 2478, 2413 | 2479, 2414 | 2480, 2415 | 2481, 2416 | 2482, 2417 | 2483, 2418 | 2484, 2419 | 2485, 2420 | 2486, 2421 | 2487, 2422 | 2488, 2423 | 2489, 2424 | 2490, 2425 | 2491, 2426 | 2492, 2427 | 2493, 2428 | 2494, 2429 | 2495, 2430 | 2496, 2431 | 2497, 2432 | 2498, 2433 | 2499, 2434 | 2500, 2435 | 2501, 2436 | 2502, 2437 | 2503, 2438 | 2504, 2439 | 2505, 2440 | 2506, 2441 | 2507, 2442 | 2508, 2443 | 2509, 2444 | 2510, 2445 | 2511, 2446 | 2512, 2447 | 2513, 2448 | 2514, 2449 | 2515, 2450 | 2516, 2451 | 2517, 2452 | 2518, 2453 | 2519, 2454 | 2520, 2455 | 2521, 2456 | 2522, 2457 | 2523, 2458 | 2524, 2459 | 2525, 2460 | 2526, 2461 | 2527, 2462 | 2528, 2463 | 2529, 2464 | 2530, 2465 | 2531, 2466 | 2532, 2467 | 2533, 2468 | 2534, 2469 | 2535, 2470 | 2536, 2471 | 2537, 2472 | 2538, 2473 | 2539, 2474 | 2540, 2475 | 2541, 2476 | 2542, 2477 | 2543, 2478 | 2544, 2479 | 2545, 2480 | 2546, 2481 | 2547, 2482 | 2548, 2483 | 2549, 2484 | 2550, 2485 | 2551, 2486 | 2552, 2487 | 2553, 2488 | 2554, 2489 | 2555, 2490 | 2556, 2491 | 2557, 2492 | 2558, 2493 | 2559, 2494 | 2560, 2495 | 2561, 2496 | 2562, 2497 | 2563, 2498 | 2564, 2499 | 2565, 2500 | 2566, 2501 | 2567, 2502 | 2568, 2503 | 2569, 2504 | 2570, 2505 | 2571, 2506 | 2572, 2507 | 2573, 2508 | 2574, 2509 | 2575, 2510 | 2576, 2511 | 2577, 2512 | 2578, 2513 | 2579, 2514 | 2580, 2515 | 2581, 2516 | 2582, 2517 | 2583, 2518 | 2584, 2519 | 2585, 2520 | 2586, 2521 | 2587, 2522 | 2588, 2523 | 2589, 2524 | 2590, 2525 | 2591, 2526 | 2592, 2527 | 2593, 2528 | 2594, 2529 | 2595, 2530 | 2596, 2531 | 2597, 2532 | 2598, 2533 | 2599, 2534 | 2600, 2535 | 2601, 2536 | 2602, 2537 | 2603, 2538 | 2604, 2539 | 2605, 2540 | 2606, 2541 | 2607, 2542 | 2608, 2543 | 2609, 2544 | 2610, 2545 | 2611, 2546 | 2612, 2547 | 2613, 2548 | 2614, 2549 | 2615, 2550 | 2616, 2551 | 2617, 2552 | 2618, 2553 | 2619, 2554 | 2620, 2555 | 2621, 2556 | 2622, 2557 | 2623, 2558 | 2624, 2559 | 2625 2560 | ]; 2561 | _planets[11] = [ 2562 | 2626, 2563 | 2627, 2564 | 2628, 2565 | 2629, 2566 | 2630, 2567 | 2631, 2568 | 2632, 2569 | 2633, 2570 | 2634, 2571 | 2635, 2572 | 2636, 2573 | 2637, 2574 | 2638, 2575 | 2639, 2576 | 2640, 2577 | 2641, 2578 | 2642, 2579 | 2643, 2580 | 2644, 2581 | 2645, 2582 | 2646, 2583 | 2647, 2584 | 2648, 2585 | 2649, 2586 | 2650, 2587 | 2651, 2588 | 2652, 2589 | 2653, 2590 | 2654, 2591 | 2655, 2592 | 2656, 2593 | 2657, 2594 | 2658, 2595 | 2659, 2596 | 2660, 2597 | 2661, 2598 | 2662, 2599 | 2663, 2600 | 2664, 2601 | 2665, 2602 | 2666, 2603 | 2667, 2604 | 2668, 2605 | 2669, 2606 | 2670, 2607 | 2671, 2608 | 2672, 2609 | 2673, 2610 | 2674, 2611 | 2675, 2612 | 2676, 2613 | 2677, 2614 | 2678, 2615 | 2679, 2616 | 2680, 2617 | 2681, 2618 | 2682, 2619 | 2683, 2620 | 2684, 2621 | 2685, 2622 | 2686, 2623 | 2687, 2624 | 2688, 2625 | 2689, 2626 | 2690, 2627 | 2691, 2628 | 2692, 2629 | 2693, 2630 | 2694, 2631 | 2695, 2632 | 2696, 2633 | 2697, 2634 | 2698, 2635 | 2699, 2636 | 2700, 2637 | 2701, 2638 | 2702, 2639 | 2703, 2640 | 2704, 2641 | 2705, 2642 | 2706, 2643 | 2707, 2644 | 2708, 2645 | 2709, 2646 | 2710, 2647 | 2711, 2648 | 2712, 2649 | 2713, 2650 | 2714, 2651 | 2715, 2652 | 2716, 2653 | 2717, 2654 | 2718, 2655 | 2719, 2656 | 2720, 2657 | 2721, 2658 | 2722, 2659 | 2723, 2660 | 2724, 2661 | 2725, 2662 | 2726, 2663 | 2727, 2664 | 2728, 2665 | 2729, 2666 | 2730, 2667 | 2731, 2668 | 2732, 2669 | 2733, 2670 | 2734, 2671 | 2735, 2672 | 2736, 2673 | 2737, 2674 | 2738, 2675 | 2739, 2676 | 2740, 2677 | 2741, 2678 | 2742, 2679 | 2743, 2680 | 2744, 2681 | 2745, 2682 | 2746, 2683 | 2747, 2684 | 2748, 2685 | 2749, 2686 | 2750, 2687 | 2751, 2688 | 2752, 2689 | 2753, 2690 | 2754, 2691 | 2755, 2692 | 2756, 2693 | 2757, 2694 | 2758, 2695 | 2759, 2696 | 2760, 2697 | 2761, 2698 | 2762, 2699 | 2763, 2700 | 2764, 2701 | 2765, 2702 | 2766, 2703 | 2767, 2704 | 2768, 2705 | 2769, 2706 | 2770, 2707 | 2771, 2708 | 2772, 2709 | 2773, 2710 | 2774, 2711 | 2775, 2712 | 2776, 2713 | 2777, 2714 | 2778, 2715 | 2779, 2716 | 2780, 2717 | 2781, 2718 | 2782, 2719 | 2783, 2720 | 2784, 2721 | 2785, 2722 | 2786, 2723 | 2787, 2724 | 2788, 2725 | 2789, 2726 | 2790, 2727 | 2791, 2728 | 2792, 2729 | 2793, 2730 | 2794, 2731 | 2795, 2732 | 2796, 2733 | 2797, 2734 | 2798, 2735 | 2799, 2736 | 2800, 2737 | 2801, 2738 | 2802, 2739 | 2803, 2740 | 2804, 2741 | 2805, 2742 | 2806, 2743 | 2807, 2744 | 2808, 2745 | 2809, 2746 | 2810, 2747 | 2811, 2748 | 2812, 2749 | 2813, 2750 | 2814, 2751 | 2815, 2752 | 2816, 2753 | 2817, 2754 | 2818, 2755 | 2819, 2756 | 2820, 2757 | 2821, 2758 | 2822, 2759 | 2823, 2760 | 2824, 2761 | 2825, 2762 | 2826, 2763 | 2827, 2764 | 2828, 2765 | 2829, 2766 | 2830, 2767 | 2831, 2768 | 2832, 2769 | 2833, 2770 | 2834, 2771 | 2835, 2772 | 2836, 2773 | 2837, 2774 | 2838, 2775 | 2839, 2776 | 2840, 2777 | 2841, 2778 | 2842, 2779 | 2843, 2780 | 2844, 2781 | 2845, 2782 | 2846, 2783 | 2847, 2784 | 2848, 2785 | 2849, 2786 | 2850, 2787 | 2851, 2788 | 2852, 2789 | 2853, 2790 | 2854, 2791 | 2855, 2792 | 2856, 2793 | 2857, 2794 | 2858, 2795 | 2859, 2796 | 2860, 2797 | 2861, 2798 | 2862, 2799 | 2863, 2800 | 2864, 2801 | 2865, 2802 | 2866, 2803 | 2867, 2804 | 2868, 2805 | 2869, 2806 | 2870, 2807 | 2871, 2808 | 2872, 2809 | 2873, 2810 | 2874, 2811 | 2875, 2812 | 2876, 2813 | 2877 2814 | ]; 2815 | _planets[12] = [ 2816 | 2878, 2817 | 2879, 2818 | 2880, 2819 | 2881, 2820 | 2882, 2821 | 2883, 2822 | 2884, 2823 | 2885, 2824 | 2886, 2825 | 2887, 2826 | 2888, 2827 | 2889, 2828 | 2890, 2829 | 2891, 2830 | 2892, 2831 | 2893, 2832 | 2894, 2833 | 2895, 2834 | 2896, 2835 | 2897, 2836 | 2898, 2837 | 2899, 2838 | 2900, 2839 | 2901, 2840 | 2902, 2841 | 2903, 2842 | 2904, 2843 | 2905, 2844 | 2906, 2845 | 2907, 2846 | 2908, 2847 | 2909, 2848 | 2910, 2849 | 2911, 2850 | 2912, 2851 | 2913, 2852 | 2914, 2853 | 2915, 2854 | 2916, 2855 | 2917, 2856 | 2918, 2857 | 2919, 2858 | 2920, 2859 | 2921, 2860 | 2922, 2861 | 2923, 2862 | 2924, 2863 | 2925, 2864 | 2926, 2865 | 2927, 2866 | 2928, 2867 | 2929, 2868 | 2930, 2869 | 2931, 2870 | 2932, 2871 | 2933, 2872 | 2934, 2873 | 2935, 2874 | 2936, 2875 | 2937, 2876 | 2938, 2877 | 2939, 2878 | 2940, 2879 | 2941, 2880 | 2942, 2881 | 2943, 2882 | 2944, 2883 | 2945, 2884 | 2946, 2885 | 2947, 2886 | 2948, 2887 | 2949, 2888 | 2950, 2889 | 2951, 2890 | 2952, 2891 | 2953, 2892 | 2954, 2893 | 2955, 2894 | 2956, 2895 | 2957, 2896 | 2958, 2897 | 2959, 2898 | 2960, 2899 | 2961, 2900 | 2962, 2901 | 2963, 2902 | 2964, 2903 | 2965, 2904 | 2966, 2905 | 2967, 2906 | 2968, 2907 | 2969, 2908 | 2970, 2909 | 2971, 2910 | 2972, 2911 | 2973, 2912 | 2974, 2913 | 2975, 2914 | 2976, 2915 | 2977, 2916 | 2978, 2917 | 2979, 2918 | 2980, 2919 | 2981, 2920 | 2982, 2921 | 2983, 2922 | 2984, 2923 | 2985, 2924 | 2986, 2925 | 2987, 2926 | 2988, 2927 | 2989, 2928 | 2990, 2929 | 2991, 2930 | 2992, 2931 | 2993, 2932 | 2994, 2933 | 2995, 2934 | 2996, 2935 | 2997, 2936 | 2998, 2937 | 2999, 2938 | 3000, 2939 | 3001, 2940 | 3002, 2941 | 3003, 2942 | 3004, 2943 | 3005, 2944 | 3006, 2945 | 3007, 2946 | 3008, 2947 | 3009, 2948 | 3010, 2949 | 3011, 2950 | 3012, 2951 | 3013, 2952 | 3014, 2953 | 3015, 2954 | 3016, 2955 | 3017, 2956 | 3018, 2957 | 3019, 2958 | 3020, 2959 | 3021, 2960 | 3022, 2961 | 3023, 2962 | 3024, 2963 | 3025, 2964 | 3026, 2965 | 3027, 2966 | 3028, 2967 | 3029, 2968 | 3030, 2969 | 3031, 2970 | 3032, 2971 | 3033, 2972 | 3034, 2973 | 3035, 2974 | 3036, 2975 | 3037, 2976 | 3038, 2977 | 3039, 2978 | 3040, 2979 | 3041, 2980 | 3042, 2981 | 3043, 2982 | 3044, 2983 | 3045, 2984 | 3046, 2985 | 3047, 2986 | 3048, 2987 | 3049, 2988 | 3050, 2989 | 3051, 2990 | 3052, 2991 | 3053, 2992 | 3054, 2993 | 3055, 2994 | 3056, 2995 | 3057, 2996 | 3058, 2997 | 3059, 2998 | 3060, 2999 | 3061, 3000 | 3062, 3001 | 3063, 3002 | 3064, 3003 | 3065, 3004 | 3066, 3005 | 3067, 3006 | 3068, 3007 | 3069, 3008 | 3070, 3009 | 3071, 3010 | 3072, 3011 | 3073, 3012 | 3074, 3013 | 3075, 3014 | 3076, 3015 | 3077, 3016 | 3078, 3017 | 3079, 3018 | 3080, 3019 | 3081, 3020 | 3082, 3021 | 3083, 3022 | 3084, 3023 | 3085, 3024 | 3086, 3025 | 3087, 3026 | 3088, 3027 | 3089, 3028 | 3090, 3029 | 3091, 3030 | 3092, 3031 | 3093, 3032 | 3094, 3033 | 3095, 3034 | 3096, 3035 | 3097, 3036 | 3098, 3037 | 3099, 3038 | 3100, 3039 | 3101, 3040 | 3102, 3041 | 3103, 3042 | 3104, 3043 | 3105, 3044 | 3106, 3045 | 3107, 3046 | 3108, 3047 | 3109, 3048 | 3110, 3049 | 3111, 3050 | 3112, 3051 | 3113, 3052 | 3114, 3053 | 3115, 3054 | 3116, 3055 | 3117, 3056 | 3118, 3057 | 3119, 3058 | 3120, 3059 | 3121, 3060 | 3122, 3061 | 3123, 3062 | 3124, 3063 | 3125, 3064 | 3126, 3065 | 3127, 3066 | 3128, 3067 | 3129 3068 | ]; 3069 | _planets[13] = [ 3070 | 3130, 3071 | 3131, 3072 | 3132, 3073 | 3133, 3074 | 3134, 3075 | 3135, 3076 | 3136, 3077 | 3137, 3078 | 3138, 3079 | 3139, 3080 | 3140, 3081 | 3141, 3082 | 3142, 3083 | 3143, 3084 | 3144, 3085 | 3145, 3086 | 3146, 3087 | 3147, 3088 | 3148, 3089 | 3149, 3090 | 3150, 3091 | 3151, 3092 | 3152, 3093 | 3153, 3094 | 3154, 3095 | 3155, 3096 | 3156, 3097 | 3157, 3098 | 3158, 3099 | 3159, 3100 | 3160, 3101 | 3161, 3102 | 3162, 3103 | 3163, 3104 | 3164, 3105 | 3165, 3106 | 3166, 3107 | 3167, 3108 | 3168, 3109 | 3169, 3110 | 3170, 3111 | 3171, 3112 | 3172, 3113 | 3173, 3114 | 3174, 3115 | 3175, 3116 | 3176, 3117 | 3177, 3118 | 3178, 3119 | 3179, 3120 | 3180, 3121 | 3181, 3122 | 3182, 3123 | 3183, 3124 | 3184, 3125 | 3185, 3126 | 3186, 3127 | 3187, 3128 | 3188, 3129 | 3189, 3130 | 3190, 3131 | 3191, 3132 | 3192, 3133 | 3193, 3134 | 3194, 3135 | 3195, 3136 | 3196, 3137 | 3197, 3138 | 3198, 3139 | 3199, 3140 | 3200, 3141 | 3201, 3142 | 3202, 3143 | 3203, 3144 | 3204, 3145 | 3205, 3146 | 3206, 3147 | 3207, 3148 | 3208, 3149 | 3209, 3150 | 3210, 3151 | 3211, 3152 | 3212, 3153 | 3213, 3154 | 3214, 3155 | 3215, 3156 | 3216, 3157 | 3217, 3158 | 3218, 3159 | 3219, 3160 | 3220, 3161 | 3221, 3162 | 3222, 3163 | 3223, 3164 | 3224, 3165 | 3225, 3166 | 3226, 3167 | 3227, 3168 | 3228, 3169 | 3229, 3170 | 3230, 3171 | 3231, 3172 | 3232, 3173 | 3233, 3174 | 3234, 3175 | 3235, 3176 | 3236, 3177 | 3237, 3178 | 3238, 3179 | 3239, 3180 | 3240, 3181 | 3241, 3182 | 3242, 3183 | 3243, 3184 | 3244, 3185 | 3245, 3186 | 3246, 3187 | 3247, 3188 | 3248, 3189 | 3249, 3190 | 3250, 3191 | 3251, 3192 | 3252, 3193 | 3253, 3194 | 3254, 3195 | 3255, 3196 | 3256, 3197 | 3257, 3198 | 3258, 3199 | 3259, 3200 | 3260, 3201 | 3261, 3202 | 3262, 3203 | 3263, 3204 | 3264, 3205 | 3265, 3206 | 3266, 3207 | 3267, 3208 | 3268, 3209 | 3269, 3210 | 3270, 3211 | 3271, 3212 | 3272, 3213 | 3273, 3214 | 3274, 3215 | 3275, 3216 | 3276, 3217 | 3277, 3218 | 3278, 3219 | 3279, 3220 | 3280, 3221 | 3281, 3222 | 3282, 3223 | 3283, 3224 | 3284, 3225 | 3285, 3226 | 3286, 3227 | 3287, 3228 | 3288, 3229 | 3289, 3230 | 3290, 3231 | 3291, 3232 | 3292, 3233 | 3293, 3234 | 3294, 3235 | 3295, 3236 | 3296, 3237 | 3297, 3238 | 3298, 3239 | 3299, 3240 | 3300, 3241 | 3301, 3242 | 3302, 3243 | 3303, 3244 | 3304, 3245 | 3305, 3246 | 3306, 3247 | 3307, 3248 | 3308, 3249 | 3309, 3250 | 3310, 3251 | 3311, 3252 | 3312, 3253 | 3313, 3254 | 3314, 3255 | 3315, 3256 | 3316, 3257 | 3317, 3258 | 3318, 3259 | 3319, 3260 | 3320, 3261 | 3321, 3262 | 3322, 3263 | 3323, 3264 | 3324, 3265 | 3325, 3266 | 3326, 3267 | 3327, 3268 | 3328, 3269 | 3329, 3270 | 3330, 3271 | 3331, 3272 | 3332, 3273 | 3333, 3274 | 3334, 3275 | 3335, 3276 | 3336, 3277 | 3337, 3278 | 3338, 3279 | 3339, 3280 | 3340, 3281 | 3341, 3282 | 3342, 3283 | 3343, 3284 | 3344, 3285 | 3345, 3286 | 3346, 3287 | 3347, 3288 | 3348, 3289 | 3349, 3290 | 3350, 3291 | 3351, 3292 | 3352, 3293 | 3353, 3294 | 3354, 3295 | 3355, 3296 | 3356, 3297 | 3357, 3298 | 3358, 3299 | 3359, 3300 | 3360, 3301 | 3361, 3302 | 3362, 3303 | 3363, 3304 | 3364, 3305 | 3365, 3306 | 3366, 3307 | 3367, 3308 | 3368, 3309 | 3369, 3310 | 3370, 3311 | 3371, 3312 | 3372, 3313 | 3373, 3314 | 3374, 3315 | 3375, 3316 | 3376, 3317 | 3377, 3318 | 3378, 3319 | 3379, 3320 | 3380, 3321 | 3381 3322 | ]; 3323 | _planets[14] = [ 3324 | 3382, 3325 | 3383, 3326 | 3384, 3327 | 3385, 3328 | 3386, 3329 | 3387, 3330 | 3388, 3331 | 3389, 3332 | 3390, 3333 | 3391, 3334 | 3392, 3335 | 3393, 3336 | 3394, 3337 | 3395, 3338 | 3396, 3339 | 3397, 3340 | 3398, 3341 | 3399, 3342 | 3400, 3343 | 3401, 3344 | 3402, 3345 | 3403, 3346 | 3404, 3347 | 3405, 3348 | 3406, 3349 | 3407, 3350 | 3408, 3351 | 3409, 3352 | 3410, 3353 | 3411, 3354 | 3412, 3355 | 3413, 3356 | 3414, 3357 | 3415, 3358 | 3416, 3359 | 3417, 3360 | 3418, 3361 | 3419, 3362 | 3420, 3363 | 3421, 3364 | 3422, 3365 | 3423, 3366 | 3424, 3367 | 3425, 3368 | 3426, 3369 | 3427, 3370 | 3428, 3371 | 3429, 3372 | 3430, 3373 | 3431, 3374 | 3432, 3375 | 3433, 3376 | 3434, 3377 | 3435, 3378 | 3436, 3379 | 3437, 3380 | 3438, 3381 | 3439, 3382 | 3440, 3383 | 3441, 3384 | 3442, 3385 | 3443, 3386 | 3444, 3387 | 3445, 3388 | 3446, 3389 | 3447, 3390 | 3448, 3391 | 3449, 3392 | 3450, 3393 | 3451, 3394 | 3452, 3395 | 3453, 3396 | 3454, 3397 | 3455, 3398 | 3456, 3399 | 3457, 3400 | 3458, 3401 | 3459, 3402 | 3460, 3403 | 3461, 3404 | 3462, 3405 | 3463, 3406 | 3464, 3407 | 3465, 3408 | 3466, 3409 | 3467, 3410 | 3468, 3411 | 3469, 3412 | 3470, 3413 | 3471, 3414 | 3472, 3415 | 3473, 3416 | 3474, 3417 | 3475, 3418 | 3476, 3419 | 3477, 3420 | 3478, 3421 | 3479, 3422 | 3480, 3423 | 3481, 3424 | 3482, 3425 | 3483, 3426 | 3484, 3427 | 3485, 3428 | 3486, 3429 | 3487, 3430 | 3488, 3431 | 3489, 3432 | 3490, 3433 | 3491, 3434 | 3492, 3435 | 3493, 3436 | 3494, 3437 | 3495, 3438 | 3496, 3439 | 3497, 3440 | 3498, 3441 | 3499, 3442 | 3500, 3443 | 3501, 3444 | 3502, 3445 | 3503, 3446 | 3504, 3447 | 3505, 3448 | 3506, 3449 | 3507, 3450 | 3508, 3451 | 3509, 3452 | 3510, 3453 | 3511, 3454 | 3512, 3455 | 3513, 3456 | 3514, 3457 | 3515, 3458 | 3516, 3459 | 3517, 3460 | 3518, 3461 | 3519, 3462 | 3520, 3463 | 3521, 3464 | 3522, 3465 | 3523, 3466 | 3524, 3467 | 3525, 3468 | 3526, 3469 | 3527, 3470 | 3528, 3471 | 3529, 3472 | 3530, 3473 | 3531, 3474 | 3532, 3475 | 3533, 3476 | 3534, 3477 | 3535, 3478 | 3536, 3479 | 3537, 3480 | 3538, 3481 | 3539, 3482 | 3540, 3483 | 3541, 3484 | 3542, 3485 | 3543, 3486 | 3544, 3487 | 3545, 3488 | 3546, 3489 | 3547, 3490 | 3548, 3491 | 3549, 3492 | 3550, 3493 | 3551, 3494 | 3552, 3495 | 3553, 3496 | 3554, 3497 | 3555, 3498 | 3556, 3499 | 3557, 3500 | 3558, 3501 | 3559, 3502 | 3560, 3503 | 3561, 3504 | 3562, 3505 | 3563, 3506 | 3564, 3507 | 3565, 3508 | 3566, 3509 | 3567, 3510 | 3568, 3511 | 3569, 3512 | 3570, 3513 | 3571, 3514 | 3572, 3515 | 3573, 3516 | 3574, 3517 | 3575, 3518 | 3576, 3519 | 3577, 3520 | 3578, 3521 | 3579, 3522 | 3580, 3523 | 3581, 3524 | 3582, 3525 | 3583, 3526 | 3584, 3527 | 3585, 3528 | 3586, 3529 | 3587, 3530 | 3588, 3531 | 3589, 3532 | 3590, 3533 | 3591, 3534 | 3592, 3535 | 3593, 3536 | 3594, 3537 | 3595, 3538 | 3596, 3539 | 3597, 3540 | 3598, 3541 | 3599, 3542 | 3600, 3543 | 3601, 3544 | 3602, 3545 | 3603, 3546 | 3604, 3547 | 3605, 3548 | 3606, 3549 | 3607, 3550 | 3608, 3551 | 3609, 3552 | 3610, 3553 | 3611, 3554 | 3612, 3555 | 3613, 3556 | 3614, 3557 | 3615, 3558 | 3616, 3559 | 3617, 3560 | 3618, 3561 | 3619, 3562 | 3620, 3563 | 3621, 3564 | 3622, 3565 | 3623, 3566 | 3624, 3567 | 3625, 3568 | 3626, 3569 | 3627, 3570 | 3628, 3571 | 3629, 3572 | 3630, 3573 | 3631, 3574 | 3632, 3575 | 3633 3576 | ]; 3577 | _planets[15] = [ 3578 | 3634, 3579 | 3635, 3580 | 3636, 3581 | 3637, 3582 | 3638, 3583 | 3639, 3584 | 3640, 3585 | 3641, 3586 | 3642, 3587 | 3643, 3588 | 3644, 3589 | 3645, 3590 | 3646, 3591 | 3647, 3592 | 3648, 3593 | 3649, 3594 | 3650, 3595 | 3651, 3596 | 3652, 3597 | 3653, 3598 | 3654, 3599 | 3655, 3600 | 3656, 3601 | 3657, 3602 | 3658, 3603 | 3659, 3604 | 3660, 3605 | 3661, 3606 | 3662, 3607 | 3663, 3608 | 3664, 3609 | 3665, 3610 | 3666, 3611 | 3667, 3612 | 3668, 3613 | 3669, 3614 | 3670, 3615 | 3671, 3616 | 3672, 3617 | 3673, 3618 | 3674, 3619 | 3675, 3620 | 3676, 3621 | 3677, 3622 | 3678, 3623 | 3679, 3624 | 3680, 3625 | 3681, 3626 | 3682, 3627 | 3683, 3628 | 3684, 3629 | 3685, 3630 | 3686, 3631 | 3687, 3632 | 3688, 3633 | 3689, 3634 | 3690, 3635 | 3691, 3636 | 3692, 3637 | 3693, 3638 | 3694, 3639 | 3695, 3640 | 3696, 3641 | 3697, 3642 | 3698, 3643 | 3699, 3644 | 3700, 3645 | 3701, 3646 | 3702, 3647 | 3703, 3648 | 3704, 3649 | 3705, 3650 | 3706, 3651 | 3707, 3652 | 3708, 3653 | 3709, 3654 | 3710, 3655 | 3711, 3656 | 3712, 3657 | 3713, 3658 | 3714, 3659 | 3715, 3660 | 3716, 3661 | 3717, 3662 | 3718, 3663 | 3719, 3664 | 3720, 3665 | 3721, 3666 | 3722, 3667 | 3723, 3668 | 3724, 3669 | 3725, 3670 | 3726, 3671 | 3727, 3672 | 3728, 3673 | 3729, 3674 | 3730, 3675 | 3731, 3676 | 3732, 3677 | 3733, 3678 | 3734, 3679 | 3735, 3680 | 3736, 3681 | 3737, 3682 | 3738, 3683 | 3739, 3684 | 3740, 3685 | 3741, 3686 | 3742, 3687 | 3743, 3688 | 3744, 3689 | 3745, 3690 | 3746, 3691 | 3747, 3692 | 3748, 3693 | 3749, 3694 | 3750, 3695 | 3751, 3696 | 3752, 3697 | 3753, 3698 | 3754, 3699 | 3755, 3700 | 3756, 3701 | 3757, 3702 | 3758, 3703 | 3759, 3704 | 3760, 3705 | 3761, 3706 | 3762, 3707 | 3763, 3708 | 3764, 3709 | 3765, 3710 | 3766, 3711 | 3767, 3712 | 3768, 3713 | 3769, 3714 | 3770, 3715 | 3771, 3716 | 3772, 3717 | 3773, 3718 | 3774, 3719 | 3775, 3720 | 3776, 3721 | 3777, 3722 | 3778, 3723 | 3779, 3724 | 3780, 3725 | 3781, 3726 | 3782, 3727 | 3783, 3728 | 3784, 3729 | 3785, 3730 | 3786, 3731 | 3787, 3732 | 3788, 3733 | 3789, 3734 | 3790, 3735 | 3791, 3736 | 3792, 3737 | 3793, 3738 | 3794, 3739 | 3795, 3740 | 3796, 3741 | 3797, 3742 | 3798, 3743 | 3799, 3744 | 3800, 3745 | 3801, 3746 | 3802, 3747 | 3803, 3748 | 3804, 3749 | 3805, 3750 | 3806, 3751 | 3807, 3752 | 3808, 3753 | 3809, 3754 | 3810, 3755 | 3811, 3756 | 3812, 3757 | 3813, 3758 | 3814, 3759 | 3815, 3760 | 3816, 3761 | 3817, 3762 | 3818, 3763 | 3819, 3764 | 3820, 3765 | 3821, 3766 | 3822, 3767 | 3823, 3768 | 3824, 3769 | 3825, 3770 | 3826, 3771 | 3827, 3772 | 3828, 3773 | 3829, 3774 | 3830, 3775 | 3831, 3776 | 3832, 3777 | 3833, 3778 | 3834, 3779 | 3835, 3780 | 3836, 3781 | 3837, 3782 | 3838, 3783 | 3839, 3784 | 3840, 3785 | 3841, 3786 | 3842, 3787 | 3843, 3788 | 3844, 3789 | 3845, 3790 | 3846, 3791 | 3847, 3792 | 3848, 3793 | 3849, 3794 | 3850, 3795 | 3851, 3796 | 3852, 3797 | 3853, 3798 | 3854, 3799 | 3855, 3800 | 3856, 3801 | 3857, 3802 | 3858, 3803 | 3859, 3804 | 3860, 3805 | 3861, 3806 | 3862, 3807 | 3863, 3808 | 3864, 3809 | 3865, 3810 | 3866, 3811 | 3867, 3812 | 3868, 3813 | 3869, 3814 | 3870, 3815 | 3871, 3816 | 3872, 3817 | 3873, 3818 | 3874, 3819 | 3875, 3820 | 3876, 3821 | 3877, 3822 | 3878, 3823 | 3879, 3824 | 3880, 3825 | 3881, 3826 | 3882, 3827 | 3883, 3828 | 3884, 3829 | 3885 3830 | ]; 3831 | _planets[16] = [ 3832 | 3886, 3833 | 3887, 3834 | 3888, 3835 | 3889, 3836 | 3890, 3837 | 3891, 3838 | 3892, 3839 | 3893, 3840 | 3894, 3841 | 3895, 3842 | 3896, 3843 | 3897, 3844 | 3898, 3845 | 3899, 3846 | 3900, 3847 | 3901, 3848 | 3902, 3849 | 3903, 3850 | 3904, 3851 | 3905, 3852 | 3906, 3853 | 3907, 3854 | 3908, 3855 | 3909, 3856 | 3910, 3857 | 3911, 3858 | 3912, 3859 | 3913, 3860 | 3914, 3861 | 3915, 3862 | 3916, 3863 | 3917, 3864 | 3918, 3865 | 3919, 3866 | 3920, 3867 | 3921, 3868 | 3922, 3869 | 3923, 3870 | 3924, 3871 | 3925, 3872 | 3926, 3873 | 3927, 3874 | 3928, 3875 | 3929, 3876 | 3930, 3877 | 3931, 3878 | 3932, 3879 | 3933, 3880 | 3934, 3881 | 3935, 3882 | 3936, 3883 | 3937, 3884 | 3938, 3885 | 3939, 3886 | 3940, 3887 | 3941, 3888 | 3942, 3889 | 3943, 3890 | 3944, 3891 | 3945, 3892 | 3946, 3893 | 3947, 3894 | 3948, 3895 | 3949, 3896 | 3950, 3897 | 3951, 3898 | 3952, 3899 | 3953, 3900 | 3954, 3901 | 3955, 3902 | 3956, 3903 | 3957, 3904 | 3958, 3905 | 3959, 3906 | 3960, 3907 | 3961, 3908 | 3962, 3909 | 3963, 3910 | 3964, 3911 | 3965, 3912 | 3966, 3913 | 3967, 3914 | 3968, 3915 | 3969, 3916 | 3970, 3917 | 3971, 3918 | 3972, 3919 | 3973, 3920 | 3974, 3921 | 3975, 3922 | 3976, 3923 | 3977, 3924 | 3978, 3925 | 3979, 3926 | 3980, 3927 | 3981, 3928 | 3982, 3929 | 3983, 3930 | 3984, 3931 | 3985, 3932 | 3986, 3933 | 3987, 3934 | 3988, 3935 | 3989, 3936 | 3990, 3937 | 3991, 3938 | 3992, 3939 | 3993, 3940 | 3994, 3941 | 3995, 3942 | 3996, 3943 | 3997, 3944 | 3998, 3945 | 3999, 3946 | 4000, 3947 | 4001, 3948 | 4002, 3949 | 4003, 3950 | 4004, 3951 | 4005, 3952 | 4006, 3953 | 4007, 3954 | 4008, 3955 | 4009, 3956 | 4010, 3957 | 4011, 3958 | 4012, 3959 | 4013, 3960 | 4014, 3961 | 4015, 3962 | 4016, 3963 | 4017, 3964 | 4018, 3965 | 4019, 3966 | 4020, 3967 | 4021, 3968 | 4022, 3969 | 4023, 3970 | 4024, 3971 | 4025, 3972 | 4026, 3973 | 4027, 3974 | 4028, 3975 | 4029, 3976 | 4030, 3977 | 4031, 3978 | 4032, 3979 | 4033, 3980 | 4034, 3981 | 4035, 3982 | 4036, 3983 | 4037, 3984 | 4038, 3985 | 4039, 3986 | 4040, 3987 | 4041, 3988 | 4042, 3989 | 4043, 3990 | 4044, 3991 | 4045, 3992 | 4046, 3993 | 4047, 3994 | 4048, 3995 | 4049, 3996 | 4050, 3997 | 4051, 3998 | 4052, 3999 | 4053, 4000 | 4054, 4001 | 4055, 4002 | 4056, 4003 | 4057, 4004 | 4058, 4005 | 4059, 4006 | 4060, 4007 | 4061, 4008 | 4062, 4009 | 4063, 4010 | 4064, 4011 | 4065, 4012 | 4066, 4013 | 4067, 4014 | 4068, 4015 | 4069, 4016 | 4070, 4017 | 4071, 4018 | 4072, 4019 | 4073, 4020 | 4074, 4021 | 4075, 4022 | 4076, 4023 | 4077, 4024 | 4078, 4025 | 4079, 4026 | 4080, 4027 | 4081, 4028 | 4082, 4029 | 4083, 4030 | 4084, 4031 | 4085, 4032 | 4086, 4033 | 4087, 4034 | 4088, 4035 | 4089, 4036 | 4090, 4037 | 4091, 4038 | 4092, 4039 | 4093, 4040 | 4094, 4041 | 4095, 4042 | 4096, 4043 | 4097, 4044 | 4098, 4045 | 4099, 4046 | 4100, 4047 | 4101, 4048 | 4102, 4049 | 4103, 4050 | 4104, 4051 | 4105, 4052 | 4106, 4053 | 4107, 4054 | 4108, 4055 | 4109, 4056 | 4110, 4057 | 4111, 4058 | 4112, 4059 | 4113, 4060 | 4114, 4061 | 4115, 4062 | 4116, 4063 | 4117, 4064 | 4118, 4065 | 4119, 4066 | 4120, 4067 | 4121, 4068 | 4122, 4069 | 4123, 4070 | 4124, 4071 | 4125, 4072 | 4126, 4073 | 4127, 4074 | 4128, 4075 | 4129, 4076 | 4130, 4077 | 4131, 4078 | 4132, 4079 | 4133, 4080 | 4134, 4081 | 4135, 4082 | 4136, 4083 | 4137 4084 | ]; 4085 | _planets[17] = [ 4086 | 4138, 4087 | 4139, 4088 | 4140, 4089 | 4141, 4090 | 4142, 4091 | 4143, 4092 | 4144, 4093 | 4145, 4094 | 4146, 4095 | 4147, 4096 | 4148, 4097 | 4149, 4098 | 4150, 4099 | 4151, 4100 | 4152, 4101 | 4153, 4102 | 4154, 4103 | 4155, 4104 | 4156, 4105 | 4157, 4106 | 4158, 4107 | 4159, 4108 | 4160, 4109 | 4161, 4110 | 4162, 4111 | 4163, 4112 | 4164, 4113 | 4165, 4114 | 4166, 4115 | 4167, 4116 | 4168, 4117 | 4169, 4118 | 4170, 4119 | 4171, 4120 | 4172, 4121 | 4173, 4122 | 4174, 4123 | 4175, 4124 | 4176, 4125 | 4177, 4126 | 4178, 4127 | 4179, 4128 | 4180, 4129 | 4181, 4130 | 4182, 4131 | 4183, 4132 | 4184, 4133 | 4185, 4134 | 4186, 4135 | 4187, 4136 | 4188, 4137 | 4189, 4138 | 4190, 4139 | 4191, 4140 | 4192, 4141 | 4193, 4142 | 4194, 4143 | 4195, 4144 | 4196, 4145 | 4197, 4146 | 4198, 4147 | 4199, 4148 | 4200, 4149 | 4201, 4150 | 4202, 4151 | 4203, 4152 | 4204, 4153 | 4205, 4154 | 4206, 4155 | 4207, 4156 | 4208, 4157 | 4209, 4158 | 4210, 4159 | 4211, 4160 | 4212, 4161 | 4213, 4162 | 4214, 4163 | 4215, 4164 | 4216, 4165 | 4217, 4166 | 4218, 4167 | 4219, 4168 | 4220, 4169 | 4221, 4170 | 4222, 4171 | 4223, 4172 | 4224, 4173 | 4225, 4174 | 4226, 4175 | 4227, 4176 | 4228, 4177 | 4229, 4178 | 4230, 4179 | 4231, 4180 | 4232, 4181 | 4233, 4182 | 4234, 4183 | 4235, 4184 | 4236, 4185 | 4237, 4186 | 4238, 4187 | 4239, 4188 | 4240, 4189 | 4241, 4190 | 4242, 4191 | 4243, 4192 | 4244, 4193 | 4245, 4194 | 4246, 4195 | 4247, 4196 | 4248, 4197 | 4249, 4198 | 4250, 4199 | 4251, 4200 | 4252, 4201 | 4253, 4202 | 4254, 4203 | 4255, 4204 | 4256, 4205 | 4257, 4206 | 4258, 4207 | 4259, 4208 | 4260, 4209 | 4261, 4210 | 4262, 4211 | 4263, 4212 | 4264, 4213 | 4265, 4214 | 4266, 4215 | 4267, 4216 | 4268, 4217 | 4269, 4218 | 4270, 4219 | 4271, 4220 | 4272, 4221 | 4273, 4222 | 4274, 4223 | 4275, 4224 | 4276, 4225 | 4277, 4226 | 4278, 4227 | 4279, 4228 | 4280, 4229 | 4281, 4230 | 4282, 4231 | 4283, 4232 | 4284, 4233 | 4285, 4234 | 4286, 4235 | 4287, 4236 | 4288, 4237 | 4289, 4238 | 4290, 4239 | 4291, 4240 | 4292, 4241 | 4293, 4242 | 4294, 4243 | 4295, 4244 | 4296, 4245 | 4297, 4246 | 4298, 4247 | 4299, 4248 | 4300, 4249 | 4301, 4250 | 4302, 4251 | 4303, 4252 | 4304, 4253 | 4305, 4254 | 4306, 4255 | 4307, 4256 | 4308, 4257 | 4309, 4258 | 4310, 4259 | 4311, 4260 | 4312, 4261 | 4313, 4262 | 4314, 4263 | 4315, 4264 | 4316, 4265 | 4317, 4266 | 4318, 4267 | 4319, 4268 | 4320, 4269 | 4321, 4270 | 4322, 4271 | 4323, 4272 | 4324, 4273 | 4325, 4274 | 4326, 4275 | 4327, 4276 | 4328, 4277 | 4329, 4278 | 4330, 4279 | 4331, 4280 | 4332, 4281 | 4333, 4282 | 4334, 4283 | 4335, 4284 | 4336, 4285 | 4337, 4286 | 4338, 4287 | 4339, 4288 | 4340, 4289 | 4341, 4290 | 4342, 4291 | 4343, 4292 | 4344, 4293 | 4345, 4294 | 4346, 4295 | 4347, 4296 | 4348, 4297 | 4349, 4298 | 4350, 4299 | 4351, 4300 | 4352, 4301 | 4353, 4302 | 4354, 4303 | 4355, 4304 | 4356, 4305 | 4357, 4306 | 4358, 4307 | 4359, 4308 | 4360, 4309 | 4361, 4310 | 4362, 4311 | 4363, 4312 | 4364, 4313 | 4365, 4314 | 4366, 4315 | 4367, 4316 | 4368, 4317 | 4369, 4318 | 4370, 4319 | 4371, 4320 | 4372, 4321 | 4373, 4322 | 4374, 4323 | 4375, 4324 | 4376, 4325 | 4377, 4326 | 4378, 4327 | 4379, 4328 | 4380, 4329 | 4381, 4330 | 4382, 4331 | 4383, 4332 | 4384, 4333 | 4385, 4334 | 4386, 4335 | 4387, 4336 | 4388, 4337 | 4389 4338 | ]; 4339 | _planets[18] = [ 4340 | 4390, 4341 | 4391, 4342 | 4392, 4343 | 4393, 4344 | 4394, 4345 | 4395, 4346 | 4396, 4347 | 4397, 4348 | 4398, 4349 | 4399, 4350 | 4400, 4351 | 4401, 4352 | 4402, 4353 | 4403, 4354 | 4404, 4355 | 4405, 4356 | 4406, 4357 | 4407, 4358 | 4408, 4359 | 4409, 4360 | 4410, 4361 | 4411, 4362 | 4412, 4363 | 4413, 4364 | 4414, 4365 | 4415, 4366 | 4416, 4367 | 4417, 4368 | 4418, 4369 | 4419, 4370 | 4420, 4371 | 4421, 4372 | 4422, 4373 | 4423, 4374 | 4424, 4375 | 4425, 4376 | 4426, 4377 | 4427, 4378 | 4428, 4379 | 4429, 4380 | 4430, 4381 | 4431, 4382 | 4432, 4383 | 4433, 4384 | 4434, 4385 | 4435, 4386 | 4436, 4387 | 4437, 4388 | 4438, 4389 | 4439, 4390 | 4440, 4391 | 4441, 4392 | 4442, 4393 | 4443, 4394 | 4444, 4395 | 4445, 4396 | 4446, 4397 | 4447, 4398 | 4448, 4399 | 4449, 4400 | 4450, 4401 | 4451, 4402 | 4452, 4403 | 4453, 4404 | 4454, 4405 | 4455, 4406 | 4456, 4407 | 4457, 4408 | 4458, 4409 | 4459, 4410 | 4460, 4411 | 4461, 4412 | 4462, 4413 | 4463, 4414 | 4464, 4415 | 4465, 4416 | 4466, 4417 | 4467, 4418 | 4468, 4419 | 4469, 4420 | 4470, 4421 | 4471, 4422 | 4472, 4423 | 4473, 4424 | 4474, 4425 | 4475, 4426 | 4476, 4427 | 4477, 4428 | 4478, 4429 | 4479, 4430 | 4480, 4431 | 4481, 4432 | 4482, 4433 | 4483, 4434 | 4484, 4435 | 4485, 4436 | 4486, 4437 | 4487, 4438 | 4488, 4439 | 4489, 4440 | 4490, 4441 | 4491, 4442 | 4492, 4443 | 4493, 4444 | 4494, 4445 | 4495, 4446 | 4496, 4447 | 4497, 4448 | 4498, 4449 | 4499, 4450 | 4500, 4451 | 4501, 4452 | 4502, 4453 | 4503, 4454 | 4504, 4455 | 4505, 4456 | 4506, 4457 | 4507, 4458 | 4508, 4459 | 4509, 4460 | 4510, 4461 | 4511, 4462 | 4512, 4463 | 4513, 4464 | 4514, 4465 | 4515, 4466 | 4516, 4467 | 4517, 4468 | 4518, 4469 | 4519, 4470 | 4520, 4471 | 4521, 4472 | 4522, 4473 | 4523, 4474 | 4524, 4475 | 4525, 4476 | 4526, 4477 | 4527, 4478 | 4528, 4479 | 4529, 4480 | 4530, 4481 | 4531, 4482 | 4532, 4483 | 4533, 4484 | 4534, 4485 | 4535, 4486 | 4536, 4487 | 4537, 4488 | 4538, 4489 | 4539, 4490 | 4540, 4491 | 4541, 4492 | 4542, 4493 | 4543, 4494 | 4544, 4495 | 4545, 4496 | 4546, 4497 | 4547, 4498 | 4548, 4499 | 4549, 4500 | 4550, 4501 | 4551, 4502 | 4552, 4503 | 4553, 4504 | 4554, 4505 | 4555, 4506 | 4556, 4507 | 4557, 4508 | 4558, 4509 | 4559, 4510 | 4560, 4511 | 4561, 4512 | 4562, 4513 | 4563, 4514 | 4564, 4515 | 4565, 4516 | 4566, 4517 | 4567, 4518 | 4568, 4519 | 4569, 4520 | 4570, 4521 | 4571, 4522 | 4572, 4523 | 4573, 4524 | 4574, 4525 | 4575, 4526 | 4576, 4527 | 4577, 4528 | 4578, 4529 | 4579, 4530 | 4580, 4531 | 4581, 4532 | 4582, 4533 | 4583, 4534 | 4584, 4535 | 4585, 4536 | 4586, 4537 | 4587, 4538 | 4588, 4539 | 4589, 4540 | 4590, 4541 | 4591, 4542 | 4592, 4543 | 4593, 4544 | 4594, 4545 | 4595, 4546 | 4596, 4547 | 4597, 4548 | 4598, 4549 | 4599, 4550 | 4600, 4551 | 4601, 4552 | 4602, 4553 | 4603, 4554 | 4604, 4555 | 4605, 4556 | 4606, 4557 | 4607, 4558 | 4608, 4559 | 4609, 4560 | 4610, 4561 | 4611, 4562 | 4612, 4563 | 4613, 4564 | 4614, 4565 | 4615, 4566 | 4616, 4567 | 4617, 4568 | 4618, 4569 | 4619, 4570 | 4620, 4571 | 4621, 4572 | 4622, 4573 | 4623, 4574 | 4624, 4575 | 4625, 4576 | 4626, 4577 | 4627, 4578 | 4628, 4579 | 4629, 4580 | 4630, 4581 | 4631, 4582 | 4632, 4583 | 4633, 4584 | 4634, 4585 | 4635, 4586 | 4636, 4587 | 4637, 4588 | 4638, 4589 | 4639, 4590 | 4640, 4591 | 4641 4592 | ]; 4593 | _planets[19] = [ 4594 | 4642, 4595 | 4643, 4596 | 4644, 4597 | 4645, 4598 | 4646, 4599 | 4647, 4600 | 4648, 4601 | 4649, 4602 | 4650, 4603 | 4651, 4604 | 4652, 4605 | 4653, 4606 | 4654, 4607 | 4655, 4608 | 4656, 4609 | 4657, 4610 | 4658, 4611 | 4659, 4612 | 4660, 4613 | 4661, 4614 | 4662, 4615 | 4663, 4616 | 4664, 4617 | 4665, 4618 | 4666, 4619 | 4667, 4620 | 4668, 4621 | 4669, 4622 | 4670, 4623 | 4671, 4624 | 4672, 4625 | 4673, 4626 | 4674, 4627 | 4675, 4628 | 4676, 4629 | 4677, 4630 | 4678, 4631 | 4679, 4632 | 4680, 4633 | 4681, 4634 | 4682, 4635 | 4683, 4636 | 4684, 4637 | 4685, 4638 | 4686, 4639 | 4687, 4640 | 4688, 4641 | 4689, 4642 | 4690, 4643 | 4691, 4644 | 4692, 4645 | 4693, 4646 | 4694, 4647 | 4695, 4648 | 4696, 4649 | 4697, 4650 | 4698, 4651 | 4699, 4652 | 4700, 4653 | 4701, 4654 | 4702, 4655 | 4703, 4656 | 4704, 4657 | 4705, 4658 | 4706, 4659 | 4707, 4660 | 4708, 4661 | 4709, 4662 | 4710, 4663 | 4711, 4664 | 4712, 4665 | 4713, 4666 | 4714, 4667 | 4715, 4668 | 4716, 4669 | 4717, 4670 | 4718, 4671 | 4719, 4672 | 4720, 4673 | 4721, 4674 | 4722, 4675 | 4723, 4676 | 4724, 4677 | 4725, 4678 | 4726, 4679 | 4727, 4680 | 4728, 4681 | 4729, 4682 | 4730, 4683 | 4731, 4684 | 4732, 4685 | 4733, 4686 | 4734, 4687 | 4735, 4688 | 4736, 4689 | 4737, 4690 | 4738, 4691 | 4739, 4692 | 4740, 4693 | 4741, 4694 | 4742, 4695 | 4743, 4696 | 4744, 4697 | 4745, 4698 | 4746, 4699 | 4747, 4700 | 4748, 4701 | 4749, 4702 | 4750, 4703 | 4751, 4704 | 4752, 4705 | 4753, 4706 | 4754, 4707 | 4755, 4708 | 4756, 4709 | 4757, 4710 | 4758, 4711 | 4759, 4712 | 4760, 4713 | 4761, 4714 | 4762, 4715 | 4763, 4716 | 4764, 4717 | 4765, 4718 | 4766, 4719 | 4767, 4720 | 4768, 4721 | 4769, 4722 | 4770, 4723 | 4771, 4724 | 4772, 4725 | 4773, 4726 | 4774, 4727 | 4775, 4728 | 4776, 4729 | 4777, 4730 | 4778, 4731 | 4779, 4732 | 4780, 4733 | 4781, 4734 | 4782, 4735 | 4783, 4736 | 4784, 4737 | 4785, 4738 | 4786, 4739 | 4787, 4740 | 4788, 4741 | 4789, 4742 | 4790, 4743 | 4791, 4744 | 4792, 4745 | 4793, 4746 | 4794, 4747 | 4795, 4748 | 4796, 4749 | 4797, 4750 | 4798, 4751 | 4799, 4752 | 4800, 4753 | 4801, 4754 | 4802, 4755 | 4803, 4756 | 4804, 4757 | 4805, 4758 | 4806, 4759 | 4807, 4760 | 4808, 4761 | 4809, 4762 | 4810, 4763 | 4811, 4764 | 4812, 4765 | 4813, 4766 | 4814, 4767 | 4815, 4768 | 4816, 4769 | 4817, 4770 | 4818, 4771 | 4819, 4772 | 4820, 4773 | 4821, 4774 | 4822, 4775 | 4823, 4776 | 4824, 4777 | 4825, 4778 | 4826, 4779 | 4827, 4780 | 4828, 4781 | 4829, 4782 | 4830, 4783 | 4831, 4784 | 4832, 4785 | 4833, 4786 | 4834, 4787 | 4835, 4788 | 4836, 4789 | 4837, 4790 | 4838, 4791 | 4839, 4792 | 4840, 4793 | 4841, 4794 | 4842, 4795 | 4843, 4796 | 4844, 4797 | 4845, 4798 | 4846, 4799 | 4847, 4800 | 4848, 4801 | 4849, 4802 | 4850, 4803 | 4851, 4804 | 4852, 4805 | 4853, 4806 | 4854, 4807 | 4855, 4808 | 4856, 4809 | 4857, 4810 | 4858, 4811 | 4859, 4812 | 4860, 4813 | 4861, 4814 | 4862, 4815 | 4863, 4816 | 4864, 4817 | 4865, 4818 | 4866, 4819 | 4867, 4820 | 4868, 4821 | 4869, 4822 | 4870, 4823 | 4871, 4824 | 4872, 4825 | 4873, 4826 | 4874, 4827 | 4875, 4828 | 4876, 4829 | 4877, 4830 | 4878, 4831 | 4879, 4832 | 4880, 4833 | 4881, 4834 | 4882, 4835 | 4883, 4836 | 4884, 4837 | 4885, 4838 | 4886, 4839 | 4887, 4840 | 4888, 4841 | 4889, 4842 | 4890, 4843 | 4891, 4844 | 4892, 4845 | 4893 4846 | ]; 4847 | _planets[20] = [ 4848 | 4894, 4849 | 4895, 4850 | 4896, 4851 | 4897, 4852 | 4898, 4853 | 4899, 4854 | 4900, 4855 | 4901, 4856 | 4902, 4857 | 4903, 4858 | 4904, 4859 | 4905, 4860 | 4906, 4861 | 4907, 4862 | 4908, 4863 | 4909, 4864 | 4910, 4865 | 4911, 4866 | 4912, 4867 | 4913, 4868 | 4914, 4869 | 4915, 4870 | 4916, 4871 | 4917, 4872 | 4918, 4873 | 4919, 4874 | 4920, 4875 | 4921, 4876 | 4922, 4877 | 4923, 4878 | 4924, 4879 | 4925, 4880 | 4926, 4881 | 4927, 4882 | 4928, 4883 | 4929, 4884 | 4930, 4885 | 4931, 4886 | 4932, 4887 | 4933, 4888 | 4934, 4889 | 4935, 4890 | 4936, 4891 | 4937, 4892 | 4938, 4893 | 4939, 4894 | 4940, 4895 | 4941, 4896 | 4942, 4897 | 4943, 4898 | 4944, 4899 | 4945, 4900 | 4946, 4901 | 4947, 4902 | 4948, 4903 | 4949, 4904 | 4950, 4905 | 4951, 4906 | 4952, 4907 | 4953, 4908 | 4954, 4909 | 4955, 4910 | 4956, 4911 | 4957, 4912 | 4958, 4913 | 4959, 4914 | 4960, 4915 | 4961, 4916 | 4962, 4917 | 4963, 4918 | 4964, 4919 | 4965, 4920 | 4966, 4921 | 4967, 4922 | 4968, 4923 | 4969, 4924 | 4970, 4925 | 4971, 4926 | 4972, 4927 | 4973, 4928 | 4974, 4929 | 4975, 4930 | 4976, 4931 | 4977, 4932 | 4978, 4933 | 4979, 4934 | 4980, 4935 | 4981, 4936 | 4982, 4937 | 4983, 4938 | 4984, 4939 | 4985, 4940 | 4986, 4941 | 4987, 4942 | 4988, 4943 | 4989, 4944 | 4990, 4945 | 4991, 4946 | 4992, 4947 | 4993, 4948 | 4994, 4949 | 4995, 4950 | 4996, 4951 | 4997, 4952 | 4998, 4953 | 4999, 4954 | 5000, 4955 | 5001, 4956 | 5002, 4957 | 5003, 4958 | 5004, 4959 | 5005, 4960 | 5006, 4961 | 5007, 4962 | 5008, 4963 | 5009, 4964 | 5010, 4965 | 5011, 4966 | 5012, 4967 | 5013, 4968 | 5014, 4969 | 5015, 4970 | 5016, 4971 | 5017, 4972 | 5018, 4973 | 5019, 4974 | 5020, 4975 | 5021, 4976 | 5022, 4977 | 5023, 4978 | 5024, 4979 | 5025, 4980 | 5026, 4981 | 5027, 4982 | 5028, 4983 | 5029, 4984 | 5030, 4985 | 5031, 4986 | 5032, 4987 | 5033, 4988 | 5034, 4989 | 5035, 4990 | 5036, 4991 | 5037, 4992 | 5038, 4993 | 5039, 4994 | 5040, 4995 | 5041, 4996 | 5042, 4997 | 5043, 4998 | 5044, 4999 | 5045, 5000 | 5046, 5001 | 5047, 5002 | 5048, 5003 | 5049, 5004 | 5050, 5005 | 5051, 5006 | 5052, 5007 | 5053, 5008 | 5054, 5009 | 5055, 5010 | 5056, 5011 | 5057, 5012 | 5058, 5013 | 5059, 5014 | 5060, 5015 | 5061, 5016 | 5062, 5017 | 5063, 5018 | 5064, 5019 | 5065, 5020 | 5066, 5021 | 5067, 5022 | 5068, 5023 | 5069, 5024 | 5070, 5025 | 5071, 5026 | 5072, 5027 | 5073, 5028 | 5074, 5029 | 5075, 5030 | 5076, 5031 | 5077, 5032 | 5078, 5033 | 5079, 5034 | 5080, 5035 | 5081, 5036 | 5082, 5037 | 5083, 5038 | 5084, 5039 | 5085, 5040 | 5086, 5041 | 5087, 5042 | 5088, 5043 | 5089, 5044 | 5090, 5045 | 5091, 5046 | 5092, 5047 | 5093, 5048 | 5094, 5049 | 5095, 5050 | 5096, 5051 | 5097, 5052 | 5098, 5053 | 5099, 5054 | 5100, 5055 | 5101, 5056 | 5102, 5057 | 5103, 5058 | 5104, 5059 | 5105, 5060 | 5106, 5061 | 5107, 5062 | 5108, 5063 | 5109, 5064 | 5110, 5065 | 5111, 5066 | 5112, 5067 | 5113, 5068 | 5114, 5069 | 5115, 5070 | 5116, 5071 | 5117, 5072 | 5118, 5073 | 5119, 5074 | 5120, 5075 | 5121, 5076 | 5122, 5077 | 5123, 5078 | 5124, 5079 | 5125, 5080 | 5126, 5081 | 5127, 5082 | 5128, 5083 | 5129, 5084 | 5130, 5085 | 5131, 5086 | 5132, 5087 | 5133, 5088 | 5134, 5089 | 5135, 5090 | 5136, 5091 | 5137, 5092 | 5138, 5093 | 5139, 5094 | 5140, 5095 | 5141, 5096 | 5142, 5097 | 5143, 5098 | 5144, 5099 | 5145 5100 | ]; 5101 | } 5102 | 5103 | // set modifier to call the function by nft smart contract only 5104 | modifier onlyNFTContract() { 5105 | require(msg.sender == _nftAddr, "unauthorized"); 5106 | _; 5107 | } 5108 | 5109 | // function to set nft smart contract address by admin 5110 | function setNftAddress(address nftAddress) external onlyOwner { 5111 | require(nftAddress != address(0), "Addr cant be empty"); 5112 | _nftAddr = nftAddress; 5113 | emit NftAddressUpdated(nftAddress); 5114 | } 5115 | 5116 | function setRandomTokenMetadata( 5117 | uint8 planetNo, 5118 | uint256 tokenId 5119 | ) external onlyNFTContract returns (uint16) { 5120 | require(planetNo > 0 && planetNo <= 20, "Invalid planet"); 5121 | uint256 planetLength = getAvailableNFTsbyPlanet(planetNo); 5122 | require(planetLength > 0, "No NFT available"); 5123 | uint256 randomNftIndex = _getRandomNumber(planetLength); 5124 | uint16 metadataId = _getNFTMetadataIdByIndex(planetNo, randomNftIndex); 5125 | _metadataIds[tokenId] = metadataId; 5126 | emit RandomMetadataAssigned(tokenId, planetNo, metadataId); 5127 | return metadataId; 5128 | } 5129 | 5130 | // function to be called by NFT smart contract for platinum and gold minting 5131 | function setTokenMetadataId( 5132 | uint256 tokenId, 5133 | uint16 metadataId 5134 | ) external onlyNFTContract { 5135 | _metadataIds[tokenId] = metadataId; 5136 | emit MetadataIdSet(tokenId, metadataId); 5137 | } 5138 | 5139 | // function to get metadataId of the token 5140 | function getMetadataIdByToken( 5141 | uint256 tokenId 5142 | ) external view returns (uint256) { 5143 | return _metadataIds[tokenId]; 5144 | } 5145 | 5146 | // Internal function to get planetNFTsIndex 5147 | function _getNFTMetadataIdByIndex( 5148 | uint8 planetNo, 5149 | uint256 index 5150 | ) internal returns (uint16) { 5151 | // Store `_planets[planetNo]` in memory for reduced storage access 5152 | uint16[] storage planetData = _planets[planetNo]; 5153 | // Fetch and remove metadataId in a single read 5154 | uint16 metadataId = planetData[index]; 5155 | // Replace with last element and pop only if necessary 5156 | uint256 lastIndex = planetData.length - 1; 5157 | if (index != lastIndex) { 5158 | planetData[index] = planetData[lastIndex]; 5159 | } 5160 | planetData.pop(); 5161 | return metadataId; 5162 | } 5163 | 5164 | // public function to get the length of a planet 5165 | function getAvailableNFTsbyPlanet( 5166 | uint8 planetNo 5167 | ) public view returns (uint256) { 5168 | return _planets[planetNo].length; 5169 | } 5170 | 5171 | // internal function for Random Number Generation 5172 | function _getRandomNumber(uint256 range) internal view returns (uint256) { 5173 | return 5174 | uint256( 5175 | keccak256( 5176 | abi.encodePacked( 5177 | block.timestamp, 5178 | block.prevrandao, 5179 | blockhash(block.gaslimit), 5180 | block.number, 5181 | msg.sender 5182 | ) 5183 | ) 5184 | ) % range; 5185 | } 5186 | } 5187 | --------------------------------------------------------------------------------