Cryptopunks is a very popular NFT collection, with the top Cryptopunk selling for more than $11M. The CryptoPunks are 10,000 uniquely generated characters. No two are exactly alike, and each one of them can be officially owned by a single person on the Ethereum blockchain. Originally, they could be claimed for free by anybody with an Ethereum wallet, but all 10,000 were quickly claimed. Let’s explore how the crypto punk source code actually works:
If you want a quick summary checkout our 2-min video on this topic:
Launched in June of 2017 by product studio Larva Labs, CryptoPunks is one of the first NFT collections on the Ethereum blockchain, consisting of 10,000 unique 24×24 pixel art images depicting humans (male and female), apes, zombies and aliens. Each Punk can exhibit a combination of 87 unique attributes, or, as most call them – traits. Some can even have 0 traits, but the max amount a single punk can have is 7 traits.
As the NFT space was a far cry from the robust market we see today, Punks initially started out slow – garnering a group of collectors that believed in the technology that the Ethereum blockchain had to offer. They were given away for free at the start to whoever wanted them. Because you needed an Ethereum wallet to collect one, supply was limited to those who were already invested in crypto.
Unlike the more recent projects it inspired, CryptoPunks didn’t initially set out to create a community – it didn’t even have a project roadmap. The project was an experiment conceived by Canadian software developers and Larva Labs founders Matt Hall and John Watkinson.
On July 6th, 2017, Alien Punk #3100 sold for 8 ETH ($2,127). At the time this would’ve seemed like an absolutely ludicrous price to pay for a JPEG. Yet, almost four years later on March 11th, 2021, that same Punk sold for a staggering 4,200 ETH ($7.58M).
Auction house Christie’s has to be given a great deal of credit for the development of the Punk marketplace by exposing those in more traditional art spaces to Punks. As a follow-up to the major auction house’s $69 million Beeple sale in March, Christie’s presented a collection of nine CryptoPunks in May that sold for a whopping $16.9 million.
Considering their history of growth and development alongside the Ethereum blockchain, owning a Punk, some would argue, is like placing a bet on the importance of NFTs and the Ethereum blockchain. The early supporters of Punks, for the most part, didn’t just chance upon the project. As GMoney put it, “you had to be around in crypto early on to get involved with CryptoPunks.”
Punk 8219 bought for 140 ETH ($150,274.60 USD) by 0xf0d699 from 0x33eaae. https://t.co/FZvhFN16zU #cryptopunks #ethereum pic.twitter.com/VBU9u9D1gI
— CryptoPunks Bot (@cryptopunksbot)
Source Code
Cryptopunk source code is available on github and is written in solidity
Let’s review the code and understand how it works:
The contract source contains the hash of the image containing all cryptopunks so that it can easily be verified
string public imageHash = "ac39af4793119ee46bbff351d8cb6b5f23da60222126add4268e261199a2921b";
The constructor function sets the owner, a total supply of 10,000 and name of the token.
/* Initializes contract with initial supply tokens to the creator of the contract */
function CryptoPunksMarket() payable {
// balanceOf[msg.sender] = initialSupply; // Give the creator all initial tokens
owner = msg.sender;
totalSupply = 10000; // Update total supply
punksRemainingToAssign = totalSupply;
name = "CRYPTOPUNKS"; // Set the name for display purposes
symbol = "Ͼ"; // Set the symbol for display purposes
decimals = 0; // Amount of decimals for display purposes
}
The getPunk function takes a punkIndex and assigns it to an ethereum address to claim ownership of a punk, this function can no longer be used as all the punks have already been claimed
function getPunk(uint punkIndex) {
if (!allPunksAssigned) throw;
if (punksRemainingToAssign == 0) throw;
if (punkIndexToAddress[punkIndex] != 0x0) throw;
if (punkIndex >= 10000) throw;
punkIndexToAddress[punkIndex] = msg.sender;
balanceOf[msg.sender]++;
punksRemainingToAssign--;
Assign(msg.sender, punkIndex);
}
The transferPunk function allows transfer of a particular punk to a particular address. This can only be called by the owner of the punk as there is no payment involved.
// Transfer ownership of a punk to another user without requiring payment
function transferPunk(address to, uint punkIndex) {
if (!allPunksAssigned) throw;
if (punkIndexToAddress[punkIndex] != msg.sender) throw;
if (punkIndex >= 10000) throw;
if (punksOfferedForSale[punkIndex].isForSale) {
punkNoLongerForSale(punkIndex);
}
punkIndexToAddress[punkIndex] = to;
balanceOf[msg.sender]--;
balanceOf[to]++;
Transfer(msg.sender, to, 1);
PunkTransfer(msg.sender, to, punkIndex);
// Check for the case where there is a bid from the new owner and refund it.
// Any other bid can stay in place.
Bid bid = punkBids[punkIndex];
if (bid.bidder == to) {
// Kill bid and refund value
pendingWithdrawals[to] += bid.value;
punkBids[punkIndex] = Bid(false, punkIndex, 0x0, 0);
}
}
Cryptopunk contract as several functions which facilitates bidding of punks:
The offerPunkForSale function puts a punk for sale when called by its owner for a minimum sale price
function offerPunkForSale(uint punkIndex, uint minSalePriceInWei) {
if (!allPunksAssigned) throw;
if (punkIndexToAddress[punkIndex] != msg.sender) throw;
if (punkIndex >= 10000) throw;
punksOfferedForSale[punkIndex] = Offer(true, punkIndex, msg.sender, minSalePriceInWei, 0x0);
PunkOffered(punkIndex, minSalePriceInWei, 0x0);
}
offerPunkForSaleToAddress is used to offer one of the punks for some minimum price, but only to the address specified and can be used to sell a punk to a specific person
function offerPunkForSaleToAddress(uint punkIndex, uint minSalePriceInWei, address toAddress) {
if (!allPunksAssigned) throw;
if (punkIndexToAddress[punkIndex] != msg.sender) throw;
if (punkIndex >= 10000) throw;
punksOfferedForSale[punkIndex] = Offer(true, punkIndex, msg.sender, minSalePriceInWei, toAddress);
PunkOffered(punkIndex, minSalePriceInWei, toAddress);
}
enterBidForPunk is used to enter a bid for a specific punk and withdrawBidForPunk returns the bid back to the sender if not already accepted
<br /> function enterBidForPunk(uint punkIndex) payable {
if (punkIndex >= 10000) throw;
if (!allPunksAssigned) throw;
if (punkIndexToAddress[punkIndex] == 0x0) throw;
if (punkIndexToAddress[punkIndex] == msg.sender) throw;
if (msg.value == 0) throw;
Bid existing = punkBids[punkIndex];
if (msg.value <= existing.value) throw;
if (existing.value > 0) {
// Refund the failing bid
pendingWithdrawals[existing.bidder] += existing.value;
}
punkBids[punkIndex] = Bid(true, punkIndex, msg.sender, msg.value);
PunkBidEntered(punkIndex, msg.value, msg.sender);
}
function withdrawBidForPunk(uint punkIndex) {
if (punkIndex >= 10000) throw;
if (!allPunksAssigned) throw;
if (punkIndexToAddress[punkIndex] == 0x0) throw;
if (punkIndexToAddress[punkIndex] == msg.sender) throw;
Bid bid = punkBids[punkIndex];
if (bid.bidder != msg.sender) throw;
PunkBidWithdrawn(punkIndex, bid.value, msg.sender);
uint amount = bid.value;
punkBids[punkIndex] = Bid(false, punkIndex, 0x0, 0);
// Refund the bid money
msg.sender.transfer(amount);
}
acceptBidForPunk accepts the bid for a punk and transfers the punk to the new owner, this function can only be called by the punk owner
function acceptBidForPunk(uint punkIndex, uint minPrice) {
if (punkIndex >= 10000) throw;
if (!allPunksAssigned) throw;
if (punkIndexToAddress[punkIndex] != msg.sender) throw;
address seller = msg.sender;
Bid bid = punkBids[punkIndex];
if (bid.value == 0) throw;
if (bid.value < minPrice) throw;
punkIndexToAddress[punkIndex] = bid.bidder;
balanceOf[seller]--;
balanceOf[bid.bidder]++;
Transfer(seller, bid.bidder, 1);
punksOfferedForSale[punkIndex] = Offer(false, punkIndex, bid.bidder, 0, 0x0);
uint amount = bid.value;
punkBids[punkIndex] = Bid(false, punkIndex, 0x0, 0);
pendingWithdrawals[seller] += amount;
PunkBought(punkIndex, bid.value, seller, bid.bidder);
}
After accepting bid, the withdraw function can be used to claim ether sent during bid
function withdraw() {
if (!allPunksAssigned) throw;
uint amount = pendingWithdrawals[msg.sender];
// Remember to zero the pending refund before
// sending to prevent re-entrancy attacks
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);
}
Lastly, the punkNoLongerForSale function removes a punk from bidding process
function punkNoLongerForSale(uint punkIndex) {
if (!allPunksAssigned) throw;
if (punkIndexToAddress[punkIndex] != msg.sender) throw;
if (punkIndex >= 10000) throw;
punksOfferedForSale[punkIndex] = Offer(false, punkIndex, msg.sender, 0, 0x0);
PunkNoLongerForSale(punkIndex);
}
There are several events emitted for various lifecycle of a cryptopunk transactions:
event Assign(address indexed to, uint256 punkIndex);
event Transfer(address indexed from, address indexed to, uint256 value);
event PunkTransfer(address indexed from, address indexed to, uint256 punkIndex);
event PunkOffered(uint indexed punkIndex, uint minValue, address indexed toAddress);
event PunkBidEntered(uint indexed punkIndex, uint value, address indexed fromAddress);
event PunkBidWithdrawn(uint indexed punkIndex, uint value, address indexed fromAddress);
event PunkBought(uint indexed punkIndex, uint value, address indexed fromAddress, address indexed toAddress);
event PunkNoLongerForSale(uint indexed punkIndex);
In 2018, NFT transaction specification was standardized in ERC 721 that is currently used by all NFTs on ethereum blockchain.
During initial launch the Cryptopunks image was not stored on Ethereum blockchain, the contract only stored information about which punk was owned by what address.
If you want to know more about it, feel free to check the code on etherscan
Conclusion
Beyond the utility that is still to be imagined, the most interesting part of the CryptoPunks journey is that it’s still too early in the game to truly make sense of the ecosystem. Blockchain technology is disrupting the financial infrastructure of the world, and NFTs are shifting how we think about art, media, community and even the broader concept of consumer ownership.
Leave a Reply