Onchain avatar registry: each avatar is stored as packed bytes describing its shapes, colors and positions, with no external art.
Historical Significance
An early example of fully onchain generative character art: each avatar exists only as packed layer, color and position bytes in contract storage, with no image file or offchain metadata, months before CryptoPunks and the ERC-721 era.
Context
Deployed on 14 March 2017 by 0x4376342d52aacc8b971d83664b4164f7f86fc3c8 and verified on Etherscan with solc v0.4.9. The deployer registered the first avatars the same day. The avatarsCount counter in storage now stands at 4,455, while only 63 successful register() calls came as direct transactions (41 in 2017, the rest from 2021 to 2023), so most avatars were registered through other contracts calling it.
Key Facts
Description
Avatars stores composable character portraits entirely onchain. register() takes four hex strings and packs each into a bytes32: a gender flag plus the chosen part for each of 13 layers (backs, clothes, ears, eyebrows, eyes, iris, face shape, glasses, hair, mouth, nose, beard, mustache), the colors of the first five layers, the colors of the last eight, and an up or down offset for every layer. Each avatar is keyed by the keccak hash of its shapes string, so a given combination of parts can be registered only once, and the registering address is recorded as its owner. get(), getOwner() and isExists() read an avatar back by its hash. The contract has no admin functions and no payable functions.
Source Verified
Heuristic Analysis
The following characteristics were detected through bytecode analysis and may not be accurate.
Spurious Dragon Era
Continued DoS protection. State trie clearing.
Bytecode Overview
Verified Source Available
Source verified on Etherscan.
View Verification ProofShow source code (Solidity)
pragma solidity ^0.4.3;
contract Avatars {
uint avatarsCount = 0;
struct Avatar {
uint id;
/**
* Avatar's owner.
*/
address owner;
/**
* First byte is gender, 1 / 0 for male / female.
* Then every byte describe choosen avatar part.
* The order is : backs, clothes, ears, eyebrows, eyesfront, eyesiris, faceshape, glasses, hair, mouth, nose, beard, mustache.
*/
bytes32 shapes;
/**
* Each 3 bytes describe color for 5 first shapes.
*/
bytes32 colorsPrimary;
/**
* Each 3 bytes describe color for 8 last shapes.
*/
bytes32 colorsSecondary;
/**
* Each byte describes up/down position for every shape.
* High nibble depicts the sign of number, 1 - up, 0 - down.
* Low nibble shows number of steps to move the shape in selected direction.
*
*/
bytes32 positions;
}
mapping(bytes32 => Avatar) avatars;
/**
* Stores an avatar on the blockchain.
* Throws if avatar with such shapes combination is already exists.
*
* @param shapes - hex string, depicts gender and combinations of shapes.
* @param colorsPrimary - hex string, colors of the first 5 shapes.
* @param colorsSecondary - hex string, colors of the last 8 shapes.
* @param positions - hex string, up/down positions of all shapes
*
* @return Hash of the avatar.
*/
function register(string shapes, string colorsPrimary, string colorsSecondary, string positions) returns (bytes32 avatarHash) {
bytes32 shapesBytes = strToBytes(shapes);
bytes32 colorsPrimaryBytes = strToBytes(colorsPrimary);
bytes32 colorsSecondaryBytes = strToBytes(colorsSecondary);
bytes32 positionsBytes = strToBytes(positions);
// unique by shapes composition
bytes32 hash = sha3(shapes);
Avatar memory existingAvatar = avatars[hash];
if (existingAvatar.id != 0)
throw;
Avatar memory avatar = Avatar(++avatarsCount, msg.sender,
shapesBytes,
colorsPrimaryBytes,
colorsSecondaryBytes,
positionsBytes);
avatars[hash] = avatar;
return hash;
}
/**
* Returns an avatar by it's hash.
* Throws if avatar is not exists.
*/
function get(bytes32 avatarHash) constant returns (bytes32 shapes, bytes32 colorsPrimary, bytes32 colorsSecondary, bytes32 positions) {
Avatar memory avatar = getAvatar(avatarHash);
shapes = avatar.shapes;
colorsPrimary = avatar.colorsPrimary;
colorsSecondary = avatar.colorsSecondary;
positions = avatar.positions;
}
/**
* Returns an avatar owner address by avatar's hash.
* Throws if avatar is not exists.
*/
function getOwner(bytes32 avatarHash) constant returns (address) {
Avatar memory avatar = getAvatar(avatarHash);
return avatar.owner;
}
/**
* Returns if avatar of the given hash exists.
*/
function isExists(bytes32 avatarHash) constant returns (bool) {
Avatar memory avatar = avatars[avatarHash];
if (avatar.id == 0)
return false;
return true;
}
/**
* Returns an avatar by it's hash.
* Throws if avatar is not exists.
*/
function getAvatar(bytes32 avatarHash) private constant returns (Avatar) {
Avatar memory avatar = avatars[avatarHash];
if (avatar.id == 0)
throw;
return avatar;
}
/**
* @dev Low level function.
* Converts string to bytes32 array.
* Throws if string length is more than 32 bytes
*
* @param str string
* @return bytes32 representation of str
*/
function strToBytes(string str) constant private returns (bytes32 ret) {
// var g = bytes(str).length;
// if (bytes(str).length > 32) throw;
assembly {
ret := mload(add(str, 32))
}
}
}External Links
Related contracts
Contract 0x24fe45...1837fc
Same eraThe public counter that sells permissioned storage deployments for a fee
0x24fe45...1837fcNovember 23, 2016Crowdsale
Same eraThe ethereum.org crowdsale tutorial: contributions run to a deadline against a goal, pay out a separate reward token, and are refunded if the goal is missed.
0xf90503...bf62efNovember 23, 2016Contract 0xf8b094...41732a
Same eraThe public counter that sells mintable token deployments for a fee
0xf8b094...41732aNovember 24, 2016Contract 0xad8d30...369f49
Same eraThe public counter that sells DAO container deployments for a fee
0xad8d30...369f49November 24, 2016TokenTraderFactory
Same eraThe factory behind the TokenTrader contracts: it deploys a trader per asset and price, hands it to the caller, and vouches for what it built.
0x780a5c...600d50November 26, 2016Contract 0x15b0fa...478ba1
Same eraAn AIRA congress, produced by its builder
0x15b0fa...478ba1November 26, 2016