June 2018 NFT exoplanet collectible game with 9-tier hot-potato pricing, presale mode, and per-planet life rate / crypto match / tech bonuses. Extends the Crypt
Token Information
Key Facts
Description
ExoPlanets is a June 2018 NFT collectible game on Ethereum where players bought, traded, and managed virtual exoplanets in a CryptoCelebrities-style hot-potato mechanic.
Each Exoplanet has a name, crypto-match identifier, life rate (uint8), prices in both ETH and ExoTokens (uint32 each), scientific data and three tech bonus fields — all stored on-chain in a packed struct. The contract extends the CryptoCelebrities/CelebrityToken ERC-721-draft template with 9-tier escalating pricing (200%, 150%, 135%, 125%, 119%, 117%, 115%, 113%, 110% multipliers as price crosses 5/10/26/36/47/59/68/77 ETH thresholds) and a 7-9% fee structure that decreases with higher prices.
Special features include a presale mode (inPresaleMode toggle controlling when prices can be edited vs auto-set during purchase), a paused/unpaused lifecycle controlled by CEO, and a Birth event emitted when new exoplanets are added to the universe. CEO and COO admin roles control listing, transfers of unowned planets, and contract upgrades.
Token: ExoPlanets (XPL). Metadata URL: https://exoplanets.io/metadata/planet_<id>.
Deployer 0x76990237ea27b27e598c3923fbc8ebb52a01e394 deployed at block 5,870,490 (June 28 2018) — part of the second wave of NFT collectibles following CryptoKitties/CryptoCelebrities, with extensive game-specific additions beyond the base template.
Source Verified
Source reconstructed by analyzing on-chain bytecode and decompiled output. Compiles with solc v0.4.18+commit.9cf6e910, optimizer OFF, to a 17,726-byte runtime that matches the on-chain bytecode EXACTLY (0 non-metadata bytes differ; only the trailing 32-byte bzzr0 swarm hash differs as it encodes the local source filename). Key reconstructed source-shape findings: (1) purchase() locals declared in order sellingPrice / purchaseExcess / fee / multiplier / payment / oldOwner; (2) strConcat local-decl order is _bs / _ba / _bb / ab / bab / k — _bs must precede _ba so the call to uintToString writes into the correct stack slot; (3) uintToPackedBytes uses an if/else shape (not early-return): the v==0 branch assigns ret = bytes32("0"), the else branch runs the while loop, and a single return ret; at the end — this is what produces the function-exit JUMPDEST pattern (DUP1 SWAP1 POP after tag_exit instead of before); (4) Birth event passes _numOfTokensBonusOnPurchase (not _priceInExoTokens) as the lifeRate slot — revealed by DUP8 vs DUP10 at PC 14631; (5) createContractExoplanet selector 0x1d8b4dd1 from signature (string, uint256, uint32, string, uint32, uint8, string). All 50 function selectors, all 5 event topics (Birth, TokenSold, Transfer, Approval, ContractUpgrade), and storage layout are identical. Verified on Sourcify (runtimeMatch: match) and Etherscan (Pass - Verified).
Heuristic Analysis
The following characteristics were detected through bytecode analysis and may not be accurate.
Byzantium Era
First Metropolis hard fork. Added zk-SNARK precompiles, REVERT opcode, and staticcall.
Bytecode Overview
Verified Source Available
Source verified on Etherscan.
View Verification ProofShow source code (Solidity)
// Submitted by EthereumHistory (ethereumhistory.com)
pragma solidity ^0.4.18;
contract ExoPlanets {
using SafeMath for uint256;
/*** EVENTS ***/
event Birth(uint256 indexed tokenId, string name, uint32 lifeRate, address owner);
event TokenSold(uint256 tokenId, uint256 oldPrice, uint256 newPrice, address prevOwner, address winner, string name);
event Transfer(address from, address to, uint256 tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ContractUpgrade(address newContract);
/*** CONSTANTS ***/
string public constant NAME = "ExoPlanets";
string public constant SYMBOL = "XPL";
string public constant BASE_URL = "https://exoplanets.io/metadata/planet_";
uint256 private constant PROMO_CREATION_LIMIT = 4700;
/*** STORAGE ***/
// slot 0: ownerOf (public mapping named currentOwner for auto-getter)
mapping (uint256 => address) public currentOwner;
// slot 1: balanceOf
mapping (address => uint256) private ownershipTokenCount;
// slot 2: approvedToTransfer
mapping (uint256 => address) public approvedToTransfer;
// slot 3: priceOf
mapping (uint256 => uint256) private exoplanetIndexToPrice;
// slot 4: ceoAddress
address public ceoAddress;
// slot 5: cooAddress (packed with inPresaleMode at byte 20 and paused at byte 21)
address public cooAddress;
bool public inPresaleMode;
bool public paused;
// slot 6: newContractAddress
address public newContractAddress;
/*** DATATYPES ***/
struct Exoplanet {
uint8 lifeRate;
uint32 priceInExoTokens;
uint32 numOfTokensBonusOnPurchase;
string name;
string cryptoMatch;
string techBonus1;
string techBonus2;
string techBonus3;
string scientificData;
}
// slot 7: exoplanets array
Exoplanet[] private exoplanets;
/*** ACCESS MODIFIERS ***/
modifier onlyCEO() {
require(msg.sender == ceoAddress);
_;
}
modifier onlyCOO() {
require(msg.sender == cooAddress);
_;
}
modifier onlyCLevel() {
require(
msg.sender == ceoAddress ||
msg.sender == cooAddress
);
_;
}
modifier whenNotPaused() {
require(!paused);
_;
}
modifier whenPaused() {
require(paused);
_;
}
modifier whenNotInPresale() {
require(!inPresaleMode);
_;
}
modifier whenInPresale() {
require(inPresaleMode);
_;
}
/*** CONSTRUCTOR ***/
function ExoPlanets() public {
inPresaleMode = true;
paused = false;
ceoAddress = msg.sender;
cooAddress = msg.sender;
}
/*** PUBLIC FUNCTIONS ***/
function approve(address _to, uint256 _tokenId) public {
require(_owns(msg.sender, _tokenId));
approvedToTransfer[_tokenId] = _to;
Approval(msg.sender, _to, _tokenId);
}
function balanceOf(address _owner) public view returns (uint256 balance) {
return ownershipTokenCount[_owner];
}
function ownerOf(uint256 _tokenId) public view returns (address owner) {
return currentOwner[_tokenId];
}
function priceOf(uint256 _tokenId) public view returns (uint256 price) {
return exoplanetIndexToPrice[_tokenId];
}
function createContractExoplanet(
string _name,
uint256 _initialPrice,
uint32 _priceInExoTokens,
string _cryptoMatch,
uint32 _numOfTokensBonusOnPurchase,
uint8 _lifeRate,
string _scientificData
) public onlyCLevel {
_createExoplanet(_name, address(this), _initialPrice, _priceInExoTokens, _cryptoMatch, _numOfTokensBonusOnPurchase, _lifeRate, _scientificData);
}
function transferUnownedPlanet(address _to, uint256 _tokenId) public onlyCLevel {
require(currentOwner[_tokenId] == address(this));
require(_to != address(0));
_transfer(currentOwner[_tokenId], _to, _tokenId);
TokenSold(_tokenId, exoplanetIndexToPrice[_tokenId], exoplanetIndexToPrice[_tokenId], address(this), _to, exoplanets[_tokenId].name);
}
function getExoplanet(uint256 _tokenId) public view returns (
string exoName,
uint256 sellingPrice,
address _currentOwner,
uint8 lifeRate,
uint32 priceInExoTokens,
uint32 numOfTokensBonusOnPurchase,
string cryptoMatch,
string scientificData
) {
Exoplanet storage planet = exoplanets[_tokenId];
exoName = planet.name;
lifeRate = planet.lifeRate;
priceInExoTokens = planet.priceInExoTokens;
numOfTokensBonusOnPurchase = planet.numOfTokensBonusOnPurchase;
cryptoMatch = planet.cryptoMatch;
scientificData = planet.scientificData;
sellingPrice = exoplanetIndexToPrice[_tokenId];
_currentOwner = currentOwner[_tokenId];
}
function getName(uint256 _tokenId) public view returns (string) {
return exoplanets[_tokenId].name;
}
function getCryptoMatch(uint256 _tokenId) public view returns (string) {
return exoplanets[_tokenId].cryptoMatch;
}
function getLifeRate(uint256 _tokenId) public view returns (uint8) {
return exoplanets[_tokenId].lifeRate;
}
function getScientificData(uint256 _tokenId) public view returns (string) {
return exoplanets[_tokenId].scientificData;
}
function getPriceInExoTokens(uint256 _tokenId) public view returns (uint32) {
return exoplanets[_tokenId].priceInExoTokens;
}
function getNumOfTokensBonusOnPurchase(uint256 _tokenId) public view returns (uint32) {
return exoplanets[_tokenId].numOfTokensBonusOnPurchase;
}
function getTechBonus1(uint256 _tokenId) public view returns (string) {
return exoplanets[_tokenId].techBonus1;
}
function getTechBonus2(uint256 _tokenId) public view returns (string) {
return exoplanets[_tokenId].techBonus2;
}
function getTechBonus3(uint256 _tokenId) public view returns (string) {
return exoplanets[_tokenId].techBonus3;
}
function setPriceInEth(uint256 _tokenId, uint256 _newPrice) public whenNotInPresale {
require(_owns(msg.sender, _tokenId));
exoplanetIndexToPrice[_tokenId] = _newPrice;
}
function setPriceInExoTokens(uint256 _tokenId, uint32 _newPrice) public whenNotInPresale {
require(_owns(msg.sender, _tokenId));
exoplanets[_tokenId].priceInExoTokens = _newPrice;
}
function setScientificData(uint256 _tokenId, string _scientificData) public onlyCLevel {
exoplanets[_tokenId].scientificData = _scientificData;
}
function setTechBonus1(uint256 _tokenId, string _techBonus1) public {
require(_owns(msg.sender, _tokenId));
exoplanets[_tokenId].techBonus1 = _techBonus1;
}
function setTechBonus2(uint256 _tokenId, string _techBonus2) public {
require(_owns(msg.sender, _tokenId));
exoplanets[_tokenId].techBonus2 = _techBonus2;
}
function setTechBonus3(uint256 _tokenId, string _techBonus3) public {
require(_owns(msg.sender, _tokenId));
exoplanets[_tokenId].techBonus3 = _techBonus3;
}
function setPresaleMode(bool _presaleMode) public onlyCEO {
inPresaleMode = _presaleMode;
}
function pause() public onlyCEO whenNotPaused {
paused = true;
}
function unpause() public onlyCEO whenPaused {
paused = false;
}
function setNewAddress(address _v2Address) public onlyCEO whenPaused {
newContractAddress = _v2Address;
ContractUpgrade(_v2Address);
}
function tokenURI(uint256 _tokenId) public view returns (string) {
return strConcat(BASE_URL, _tokenId);
}
function implementsERC721() public pure returns (bool) {
return true;
}
function name() public pure returns (string) {
return NAME;
}
function symbol() public pure returns (string) {
return SYMBOL;
}
function payout() public onlyCLevel {
ceoAddress.transfer(this.balance);
}
function payoutPartial(uint256 _amount) public onlyCLevel {
require(_amount <= this.balance);
ceoAddress.transfer(_amount);
}
function purchase(uint256 _tokenId) public payable whenNotPaused whenInPresale {
uint256 sellingPrice;
uint256 purchaseExcess;
uint256 fee;
uint256 multiplier;
uint256 payment;
address oldOwner;
require(currentOwner[_tokenId] != msg.sender);
require(_addressNotNull(msg.sender));
sellingPrice = exoplanetIndexToPrice[_tokenId];
require(msg.value >= sellingPrice);
purchaseExcess = msg.value.sub(sellingPrice);
if (sellingPrice <= 5 ether) {
fee = 93; multiplier = 200;
} else if (sellingPrice <= 10 ether) {
fee = 93; multiplier = 150;
} else if (sellingPrice <= 26 ether) {
fee = 93; multiplier = 135;
} else if (sellingPrice <= 36 ether) {
fee = 94; multiplier = 125;
} else if (sellingPrice <= 47 ether) {
fee = 94; multiplier = 119;
} else if (sellingPrice <= 59 ether) {
fee = 95; multiplier = 117;
} else if (sellingPrice <= 67.85 ether) {
fee = 95; multiplier = 115;
} else if (sellingPrice <= 76.67 ether) {
fee = 95; multiplier = 113;
} else {
fee = 96; multiplier = 110;
}
exoplanetIndexToPrice[_tokenId] = sellingPrice.mul(multiplier).div(100);
payment = sellingPrice.mul(fee).div(100);
oldOwner = currentOwner[_tokenId];
if (oldOwner != address(this)) {
oldOwner.transfer(payment);
}
_transfer(oldOwner, msg.sender, _tokenId);
TokenSold(_tokenId, sellingPrice, exoplanetIndexToPrice[_tokenId], oldOwner, msg.sender, exoplanets[_tokenId].name);
msg.sender.transfer(purchaseExcess);
}
function setCEO(address _newCEO) public onlyCEO {
require(_newCEO != address(0));
ceoAddress = _newCEO;
}
function setCOO(address _newCOO) public onlyCEO {
require(_newCOO != address(0));
cooAddress = _newCOO;
}
function takeOwnership(uint256 _tokenId) public whenNotPaused {
require(_addressNotNull(msg.sender));
require(_approved(msg.sender, _tokenId));
_transfer(currentOwner[_tokenId], msg.sender, _tokenId);
}
function tokensOfOwner(address _owner) public view returns(uint256[] ownerTokens) {
uint256 tokenCount = balanceOf(_owner);
if (tokenCount == 0) {
return new uint256[](0);
} else {
uint256[] memory result = new uint256[](tokenCount);
uint256 totalExoplanets = totalSupply();
uint256 resultIndex = 0;
uint256 planetId;
for (planetId = 0; planetId <= totalExoplanets; planetId++) {
if (currentOwner[planetId] == _owner) {
result[resultIndex] = planetId;
resultIndex++;
}
}
return result;
}
}
function totalSupply() public view returns (uint256 total) {
return exoplanets.length;
}
function transfer(address _to, uint256 _tokenId) public whenNotPaused {
require(_owns(msg.sender, _tokenId));
require(_addressNotNull(_to));
_transfer(msg.sender, _to, _tokenId);
}
function transferFrom(address _from, address _to, uint256 _tokenId) public whenNotPaused {
require(_approved(_from, _tokenId));
require(_addressNotNull(_to));
_transfer(_from, _to, _tokenId);
}
/*** PRIVATE FUNCTIONS ***/
function _addressNotNull(address _to) private pure returns (bool) {
return _to != address(0);
}
function _approved(address _to, uint256 _tokenId) private view returns (bool) {
return approvedToTransfer[_tokenId] == _to;
}
function _createExoplanet(
string _name,
address _owner,
uint256 _price,
uint32 _priceInExoTokens,
string _cryptoMatch,
uint32 _numOfTokensBonusOnPurchase,
uint8 _lifeRate,
string _scientificData
) private {
require(totalSupply() < uint256(uint32(PROMO_CREATION_LIMIT)));
Exoplanet memory _exoplanet = Exoplanet(_lifeRate, _priceInExoTokens, _numOfTokensBonusOnPurchase, _name, _cryptoMatch, "", "", "", _scientificData);
uint256 newPlanetId = exoplanets.push(_exoplanet) - 1;
require(newPlanetId == uint256(uint32(newPlanetId)));
Birth(newPlanetId, _name, _numOfTokensBonusOnPurchase, _owner);
exoplanetIndexToPrice[newPlanetId] = _price;
_transfer(address(0), _owner, newPlanetId);
}
function _owns(address claimant, uint256 _tokenId) private view returns (bool) {
return claimant == currentOwner[_tokenId];
}
function _transfer(address _from, address _to, uint256 _tokenId) private {
ownershipTokenCount[_to]++;
currentOwner[_tokenId] = _to;
if (_from != address(0)) {
ownershipTokenCount[_from]--;
delete approvedToTransfer[_tokenId];
}
Transfer(_from, _to, _tokenId);
}
/*** STRING UTILS ***/
function strConcat(string _a, uint _b) internal pure returns (string) {
string memory _bs = uintToString(_b);
bytes memory _ba = bytes(_a);
bytes memory _bb = bytes(_bs);
string memory ab = new string(_ba.length + _bb.length);
bytes memory bab = bytes(ab);
uint k = 0;
for (uint i = 0; i < _ba.length; i++) bab[k++] = _ba[i];
for (i = 0; i < _bb.length; i++) bab[k++] = _bb[i];
return string(bab);
}
function uintToString(uint _v) internal pure returns (string str) {
uint v = _v;
bytes32 packed = uintToPackedBytes(v);
str = packedBytesToString(packed);
}
function uintToPackedBytes(uint v) internal pure returns (bytes32 ret) {
if (v == 0) {
ret = bytes32("0");
} else {
while (v > 0) {
ret = bytes32(uint(ret) / 256);
ret = ret | bytes32((v % 10 + 48) * 2 ** 248);
v = v / 10;
}
}
return ret;
}
function packedBytesToString(bytes32 _x) internal pure returns (string) {
bytes memory bytesString = new bytes(32);
uint charCount = 0;
for (uint j = 0; j < 32; j++) {
byte char = byte(bytes32(uint(_x) * 2 ** (8 * j)));
if (char != 0) {
bytesString[charCount] = char;
charCount++;
}
}
bytes memory bytesStringTrimmed = new bytes(charCount);
for (j = 0; j < charCount; j++) {
bytesStringTrimmed[j] = bytesString[j];
}
return string(bytesStringTrimmed);
}
}
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a / b;
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
External Links
Related contracts
BankWallet
Same eraProxy wallet that forwards received ETH to a configured bank address. Owner can sweep ETH and ERC-20 token balances to that same destination.
0x449ad8...17a931November 14, 2017UserWallet
Same eraExchange deposit wallet with ETH and ERC20 sweep functions. One of the most widely deployed bytecodes on Ethereum.
0x24eb88...a068a7January 4, 2018Contract 0x3d40c2...1ec557
Same eraToken relay contract. Forwards ERC20 token transfers via transferToken(token, recipient, amount) on behalf of a configured owner.
0x3d40c2...1ec557January 4, 2018IMG
Same eraOn-chain JPEG of a tabby cat: a 128x128 JFIF baseline image returned as a hex-text string by a single read() function. January 2018.
0x2fabe6...86c7a1January 21, 2018Contract 0xda0a31...93b07c
Same eraToken distributor contract. Transfers ERC20 tokens from the contract to recipients via sendCoin(recipient, amount, tokenAddress).
0xda0a31...93b07cJanuary 27, 2018CryptoColors
Same eraFebruary 2018 hot-potato collectible game on Ethereum. Five tokens: Red, Blue, Lime, Yellow, Orange — each storing R/G/B values on-chain.
0x3116c4...ef9b96February 3, 2018