February 2018 hot-potato collectible game on Ethereum. Five tokens: Red, Blue, Lime, Yellow, Orange — each storing R/G/B values on-chain.
Historical Significance
A small, self-contained early Ethereum NFT contract from the CryptoKitties-imitator wave of early 2018, distinguished by its compact five-token set, on-chain RGB encoding, and minimalist scope (no COO role, no separate name/symbol functions, no implementsERC721 marker). Source byte-for-byte reproduced from the deployed bytecode.
Context
Era. February 2018 was the heart of the early Ethereum collectibles wave: CryptoKitties had launched in late 2017, CryptoCelebrities had spun up in January 2018, and over the following weeks dozens of fork-of-a-fork projects appeared on mainnet — most of them sharing the CelebrityToken hot-potato template by Axiom Zen / Dieter Shirley. CryptoColors arrived during this brief window, taking the template and stripping it down rather than building on it.
Template variations. Where CryptoCelebrities was a public-facing collectible game with a roster of celebrity tokens and a 6% dev fee, CryptoColors trimmed the design: only five tokens, RGB values rather than a name-and-image, no COO role (the CEO does all administration), no implementsERC721() marker, and no lowercase name() / symbol() getters. The dev fee was raised to 7%, but the three pricing tiers were retained (with thresholds adjusted to 0.05 / 0.5 ETH and multipliers 200/150/115 over 93).
The deployer. 0xfea4bc27a8af27fb317bd1a8538083f648202d1f is an EOA with no ENS, no public tags, no published code repository, and no other known related contracts. The deployment funds and the five minted tokens were sent to addresses controlled by the same operator. The contract has run quietly since: a handful of transfers and one round of purchases, no resales.
Token Information
Key Facts
Description
CryptoColors is a "hot-potato" collectible game on Ethereum, deployed February 3, 2018. It mints five ERC-721-draft tokens, each representing a named color whose RGB values are stored on-chain in the token's struct: Red (255,0,0), Blue (0,0,255), Lime (0,255,0), Yellow (255,255,0), and Orange (255,127,0).
The hot-potato mechanic. Anyone can take a Color from its current owner by calling purchase(uint256) and paying the current sellingPrice. The contract retains a 7% dev fee and forwards 93% to the previous owner. After every forced sale the price ratchets up: 200/93× (≈2.15×) while the price is below 0.05 ETH, 150/93× (≈1.61×) up to 0.5 ETH, and 115/93× (≈1.24×) beyond. The starting price for every Color is 0.001 ETH.
On-chain storage. Each Color is a struct { uint8 r; uint8 g; uint8 b; string name }, with r/g/b packed into a single 32-byte slot using Solidity 0.4.x's automatic field packing. The companion view function getColor(uint256) returns (uint256 r, uint256 g, uint256 b, string name, uint256 sellingPrice, address owner) — all six fields read directly from contract storage.
Lineage. A fork of the CelebrityToken (CryptoCelebrities) template, with the central struct replaced by Color, three pricing stages adjusted to 200/150/115 over 93, no cooAddress (CEO-only contract), no separate name()/symbol() getters (only the uppercase NAME()/SYMBOL() constants), and explicit require(uint8(_x) == _x) range checks on the RGB creation arguments.
On-chain activity. The deployer (0xfea4bc27a8af27fb317bd1a8538083f648202d1f) minted all five Colors at creation, and the contract has seen modest trading since: as of May 2026 Red sits at 0.0099 ETH (purchased three times from the 0.001 ETH start), while Blue, Lime, Yellow and Orange sit at 0.00215 ETH (purchased once each). All five are currently held by a single owner (0x4c8a531b6e0ff5438DB7114679c1536a114BF9c5).
Source Verified
Source reconstructed from on-chain bytecode (CelebrityToken-derived template with Color struct having packed uint8 r,g,b fields and a string name). Runtime and creation bytecode byte-identical to on-chain except for source metadata swarm hash. Sourcify v2 reports match (full + creation).. Optimizer: OFF
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 through compiler archaeology and exact bytecode matching.
View Verification ProofShow source code (Solidity)
// Submitted by EthereumHistory (ethereumhistory.com)
pragma solidity ^0.4.18; // solhint-disable-line
/// @title Interface for contracts conforming to ERC-721: Non-Fungible Tokens
/// @author Dieter Shirley <dete@axiomzen.co> (https://github.com/dete)
contract ERC721 {
// Required methods
function approve(address _to, uint256 _tokenId) public;
function balanceOf(address _owner) public view returns (uint256 balance);
function ownerOf(uint256 _tokenId) public view returns (address addr);
function takeOwnership(uint256 _tokenId) public;
function totalSupply() public view returns (uint256 total);
function transferFrom(address _from, address _to, uint256 _tokenId) public;
function transfer(address _to, uint256 _tokenId) public;
event Transfer(address indexed from, address indexed to, uint256 tokenId);
event Approval(address indexed owner, address indexed approved, uint256 tokenId);
// Optional
// function name() public view returns (string name);
// function symbol() public view returns (string symbol);
// function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256 tokenId);
// function tokenMetadata(uint256 _tokenId) public view returns (string infoUrl);
}
contract CryptoColors is ERC721 {
/*** EVENTS ***/
/// @dev The Released event is fired whenever a new color comes into existence.
event Released(uint256 tokenId, string name, address owner);
/// @dev The ColorSold event is fired whenever a token is sold.
event ColorSold(uint256 tokenId, uint256 oldPrice, uint256 newPrice, address prevOwner, address winner, string name);
/// @dev Transfer event as defined in current draft of ERC721.
/// ownership is assigned, including births.
event Transfer(address from, address to, uint256 tokenId);
/*** CONSTANTS ***/
/// @notice Name and symbol of the non fungible token, as defined in ERC721.
string public constant NAME = "CryptoColors"; // solhint-disable-line
string public constant SYMBOL = "COLOR"; // solhint-disable-line
uint256 private startingPrice = 0.001 ether;
uint256 private constant PROMO_CREATION_LIMIT = 1000000;
uint256 private firstStepLimit = 0.05 ether;
uint256 private secondStepLimit = 0.5 ether;
/*** STORAGE ***/
/// @dev A mapping from color IDs to the address that owns them. All colors have
/// some valid owner address.
mapping (uint256 => address) public colorIndexToOwner;
// @dev A mapping from owner address to count of tokens that address owns.
// Used internally inside balanceOf() to resolve ownership count.
mapping (address => uint256) private ownershipTokenCount;
/// @dev A mapping from ColorIDs to an address that has been approved to call
/// transferFrom(). Each Color can only have one approved address for transfer
/// at any time. A zero value means no approval is outstanding.
mapping (uint256 => address) public colorIndexToApproved;
// @dev A mapping from ColorIDs to the price of the token.
mapping (uint256 => uint256) private colorIndexToPrice;
// The address of the account that can execute special actions.
address public ceoAddress;
uint256 public promoCreatedCount;
/*** DATATYPES ***/
struct Color {
uint8 r;
uint8 g;
uint8 b;
string name;
}
Color[] private colors;
/*** ACCESS MODIFIERS ***/
/// @dev Access modifier for CEO-only functionality
modifier onlyCEO() {
require(msg.sender == ceoAddress);
_;
}
/*** CONSTRUCTOR ***/
function CryptoColors() public {
ceoAddress = msg.sender;
}
/*** PUBLIC FUNCTIONS ***/
/// @notice Grant another address the right to transfer token via takeOwnership() and transferFrom().
/// @param _to The address to be granted transfer approval. Pass address(0) to
/// clear all approvals.
/// @param _tokenId The ID of the Token that can be transferred if this call succeeds.
/// @dev Required for ERC-721 compliance.
function approve(
address _to,
uint256 _tokenId
) public {
// Caller must own token.
require(_owns(msg.sender, _tokenId));
colorIndexToApproved[_tokenId] = _to;
Approval(msg.sender, _to, _tokenId);
}
/// For querying balance of a particular account
/// @param _owner The address for balance query
/// @dev Required for ERC-721 compliance.
function balanceOf(address _owner) public view returns (uint256 balance) {
return ownershipTokenCount[_owner];
}
/// @dev Creates a new promo Color with the given r, g, b, name, with given _price and assignes it to an address.
function createPromoColor(uint256 _r, uint256 _g, uint256 _b, string _name, address _owner, uint256 _price) public onlyCEO {
require(promoCreatedCount < PROMO_CREATION_LIMIT);
address colorOwner = _owner;
if (colorOwner == address(0)) {
colorOwner = ceoAddress;
}
if (_price <= 0) {
_price = startingPrice;
}
promoCreatedCount++;
_createColor(_r, _g, _b, _name, colorOwner, _price);
}
/// @dev Creates a new Color with the given r, g, b, name.
function createContractColor(uint256 _r, uint256 _g, uint256 _b, string _name) public onlyCEO {
_createColor(_r, _g, _b, _name, address(this), startingPrice);
}
/// @notice Returns all the relevant information about a specific color.
/// @param _tokenId The tokenId of the color of interest.
function getColor(uint256 _tokenId) public view returns (
uint256 r,
uint256 g,
uint256 b,
string colorName,
uint256 sellingPrice,
address owner
) {
Color storage color = colors[_tokenId];
r = color.r;
g = color.g;
b = color.b;
colorName = color.name;
sellingPrice = colorIndexToPrice[_tokenId];
owner = colorIndexToOwner[_tokenId];
}
/// For querying owner of token
/// @param _tokenId The tokenID for owner inquiry
/// @dev Required for ERC-721 compliance.
function ownerOf(uint256 _tokenId)
public
view
returns (address owner)
{
owner = colorIndexToOwner[_tokenId];
require(owner != address(0));
}
function payout(address _to) public onlyCEO {
_payout(_to);
}
// Allows someone to send ether and obtain the token
function purchase(uint256 _tokenId) public payable {
address oldOwner = colorIndexToOwner[_tokenId];
address newOwner = msg.sender;
uint256 sellingPrice = colorIndexToPrice[_tokenId];
// Making sure token owner is not sending to self
require(oldOwner != newOwner);
// Safety check to prevent against an unexpected 0x0 default.
require(_addressNotNull(newOwner));
// Making sure sent amount is greater than or equal to the sellingPrice
require(msg.value >= sellingPrice);
uint256 payment = uint256(SafeMath.div(SafeMath.mul(sellingPrice, 93), 100));
uint256 purchaseExcess = SafeMath.sub(msg.value, sellingPrice);
// Update prices
if (sellingPrice < firstStepLimit) {
// first stage
colorIndexToPrice[_tokenId] = SafeMath.div(SafeMath.mul(sellingPrice, 200), 93);
} else if (sellingPrice < secondStepLimit) {
// second stage
colorIndexToPrice[_tokenId] = SafeMath.div(SafeMath.mul(sellingPrice, 150), 93);
} else {
// third stage
colorIndexToPrice[_tokenId] = SafeMath.div(SafeMath.mul(sellingPrice, 115), 93);
}
_transfer(oldOwner, newOwner, _tokenId);
// Pay previous tokenOwner if owner is not contract
if (oldOwner != address(this)) {
oldOwner.transfer(payment); //(1-0.07)
}
ColorSold(_tokenId, sellingPrice, colorIndexToPrice[_tokenId], oldOwner, newOwner, colors[_tokenId].name);
msg.sender.transfer(purchaseExcess);
}
function priceOf(uint256 _tokenId) public view returns (uint256 price) {
return colorIndexToPrice[_tokenId];
}
/// @dev Assigns a new address to act as the CEO. Only available to the current CEO.
/// @param _newCEO The address of the new CEO
function setCEO(address _newCEO) public onlyCEO {
require(_newCEO != address(0));
ceoAddress = _newCEO;
}
/// @notice Allow pre-approved user to take ownership of a token
/// @param _tokenId The ID of the Token that can be transferred if this call succeeds.
/// @dev Required for ERC-721 compliance.
function takeOwnership(uint256 _tokenId) public {
address newOwner = msg.sender;
address oldOwner = colorIndexToOwner[_tokenId];
// Safety check to prevent against an unexpected 0x0 default.
require(_addressNotNull(newOwner));
// Making sure transfer is approved
require(_approved(newOwner, _tokenId));
_transfer(oldOwner, newOwner, _tokenId);
}
/// @param _owner The owner whose color tokens we are interested in.
/// @dev This method MUST NEVER be called by smart contract code. First, it's fairly
/// expensive (it walks the entire Colors array looking for colors belonging to owner),
/// but it also returns a dynamic array, which is only supported for web3 calls, and
/// not contract-to-contract calls.
function tokensOfOwner(address _owner) public view returns(uint256[] ownerTokens) {
uint256 tokenCount = balanceOf(_owner);
if (tokenCount == 0) {
// Return an empty array
return new uint256[](0);
} else {
uint256[] memory result = new uint256[](tokenCount);
uint256 totalColors = totalSupply();
uint256 resultIndex = 0;
uint256 colorId;
for (colorId = 0; colorId <= totalColors; colorId++) {
if (colorIndexToOwner[colorId] == _owner) {
result[resultIndex] = colorId;
resultIndex++;
}
}
return result;
}
}
/// For querying totalSupply of token
/// @dev Required for ERC-721 compliance.
function totalSupply() public view returns (uint256 total) {
return colors.length;
}
/// Owner initates the transfer of the token to another account
/// @param _to The address for the token to be transferred to.
/// @param _tokenId The ID of the Token that can be transferred if this call succeeds.
/// @dev Required for ERC-721 compliance.
function transfer(
address _to,
uint256 _tokenId
) public {
require(_owns(msg.sender, _tokenId));
require(_addressNotNull(_to));
_transfer(msg.sender, _to, _tokenId);
}
/// Third-party initiates transfer of token from address _from to address _to
/// @param _from The address for the token to be transferred from.
/// @param _to The address for the token to be transferred to.
/// @param _tokenId The ID of the Token that can be transferred if this call succeeds.
/// @dev Required for ERC-721 compliance.
function transferFrom(
address _from,
address _to,
uint256 _tokenId
) public {
require(_owns(_from, _tokenId));
require(_approved(_to, _tokenId));
require(_addressNotNull(_to));
_transfer(_from, _to, _tokenId);
}
/*** PRIVATE FUNCTIONS ***/
/// Safety check on _to address to prevent against an unexpected 0x0 default.
function _addressNotNull(address _to) private pure returns (bool) {
return _to != address(0);
}
/// For checking approval of transfer for address _to
function _approved(address _to, uint256 _tokenId) private view returns (bool) {
return colorIndexToApproved[_tokenId] == _to;
}
/// For creating Color
function _createColor(uint256 _r, uint256 _g, uint256 _b, string _name, address _owner, uint256 _price) private {
require(_r == uint8(_r));
require(_g == uint8(_g));
require(_b == uint8(_b));
Color memory _color = Color({
r: uint8(_r),
g: uint8(_g),
b: uint8(_b),
name: _name
});
uint256 newColorId = colors.push(_color) - 1;
// It's probably never going to happen, 4 billion tokens are A LOT, but
// let's just be 100% sure we never let this happen.
require(newColorId == uint256(uint32(newColorId)));
Released(newColorId, _name, _owner);
colorIndexToPrice[newColorId] = _price;
// This will assign ownership, and also emit the Transfer event as
// per ERC721 draft
_transfer(address(0), _owner, newColorId);
}
/// Check for token ownership
function _owns(address claimant, uint256 _tokenId) private view returns (bool) {
return claimant == colorIndexToOwner[_tokenId];
}
/// For paying out balance on contract
function _payout(address _to) private {
if (_to == address(0)) {
ceoAddress.transfer(this.balance);
} else {
_to.transfer(this.balance);
}
}
/// @dev Assigns ownership of a specific Color to an address.
function _transfer(address _from, address _to, uint256 _tokenId) private {
// Since the number of colors is capped to 2^32 we can't overflow this
ownershipTokenCount[_to]++;
//transfer ownership
colorIndexToOwner[_tokenId] = _to;
// When creating new colors _from is 0x0, but we can't account that address.
if (_from != address(0)) {
ownershipTokenCount[_from]--;
// clear any previously approved ownership exchange
delete colorIndexToApproved[_tokenId];
}
// Emit the transfer event.
Transfer(_from, _to, _tokenId);
}
}
library SafeMath {
/**
* @dev Multiplies two numbers, throws on overflow.
*/
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;
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}