Deep diving into Cyrptopunk Source Code

Rahul Ravindran

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.”

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.

In August 2021, Larva labs deployed another contract which stored the images in svg format on the blockchain along with all the attributes for punks.

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.

0 Comments

Leave a Reply

More great articles

How To Become a Blockchain Developer: Step-by-Step Plan

Do you want to become a become Blockchain developer? With higher salaries, reports of Ethereum Dapps becoming very successful overnight,…

Read Story

Dirt Cheap Gas Prices using Starknet

Overview As the number of people using Ethereum has grown, the blockchain has reached certain capacity limitations. This has driven…

Read Story

Crypto 2021 Rewind – Glancing Through Key Happenings

2021 was a crazy year to say the least. Let’s pause for a moment to wrap things up and see…

Read Story

Never miss a minute

Get great content to your inbox every week. No spam.
[contact-form-7 id="6" title="Footer CTA Subscribe Form"]
Arrow-up