“246 Lines” 
Exhibition 
Text
for 10,000

Written for NODE  

 


With 246 lines of code, Matt Hall and John Watkinson (Larva Labs) deployed the CryptoPunks contract in June 2017.

What looked like 10,000 pixel avatars was in fact a new protocol for art and ownership itself. The contract established a complete set of rules for the collection and the community that would build around it.

These rules define how many Punks exist, how ownership is assigned, and how ownership can change. The artwork operates through the execution of this code and economic logic.

At launch, the contract referenced a single composite image containing all 10,000 CryptoPunks. This reflected the technical conditions of the time, when ownership logic could be placed onchain more readily than image data itself. These data structures are used to track Punk ownership, allowing the system to operate as code first and image second.

The system operates within fixed constraints. Every offer, bid, sale and transfer unfolds inside this closed set. Ownership is recorded directly by the contract, resolving each Punk index to a single address.

Each Punk occupies a distinct position within the system. Visual differences matter because they are bounded. Typology becomes legible because it is constrained.

Each Punk is unique, valued not only for its attributes, but for how those traits are perceived — how desirable they are in the eyes of a community. Those perceptions are in constant motion, shaped by the marketplace itself: through circulation, comparison, and exchange.

Value here isn't decided by curators, critics, or algorithms. It emerges organically from consensus and collective actions. This is where subjective value meets quantitative scarcity: an economy of aesthetics, an aesthetics of economics.

Only 10,000 CryptoPunks exist. That number does not change. CryptoPunks are scarce.

CRYPTOPUNKS: A word that fuses code with aesthetics and subculture, currency with rebellion.

When the contract was first deployed, anyone could claim a Punk for free, paying only a small network fee to have ownership recorded permanently on Ethereum (aka the "World Computer"). These early claims were acts of curiosity rather than commerce, participation in a system whose value had not yet been established.

This early experiment became foundational, informing the ERC-721 standard that now defines digital ownership across the art world.

Owning a Punk asks us to locate ourselves in a matrix of visual data: do you identify with a mohawk or a pipe? An alien or a zombie? Is that identification ironic, aspirational, symbolic?

They are tradable, ownable typologies for a networked species: human, ape, alien, zombie — each a node in a system that records our fascination with uniqueness.

From there, the contract continues to operate without intervention, reaffirming ownership block by block. Each transfer or exchange emits an event, marking moments of activity as they occur. The artwork persists through continuous execution. 

CryptoPunks are not static images; they are living digital objects that have inspired new identities and real-world communities. Their state changes over time.

As the marketplace activates, these distinctions acquire consequence. Ownership can be transferred, offered, bid on, and exchanged.

Here, the code defines how value is proposed and settled. Offers and bids record price, transfers update ownership, and events make activity visible across the network.

Roughly every twelve seconds, Ethereum mines a new block, reaffirming who owns what. Every bid, offer and sale is recorded on the blockchain — a small pulse in the network contributing a new event to the living artwork.

To read the contract's code is to read how the artwork operates. To display it here is to recognize the artwork not as an object, but as a contract between art and its audience; for what is culture if not encoded? They show how value — monetary, cultural, and critical — can be produced and negotiated through software.

CryptoPunks are at once images, identities, and instruments: portraits for a networked age.

246 lines of Solidity, deployed once, running in perpetuity.

- Text by Natalie Stone & Amanda Schmitt 


































  • pragma solidity ^0.4.15;


    contract CryptoPunksMarket {
    // You can use this hash to verify the
    // image file containing all the punks
    string public imageHash =
    "ac39af4793119ee46bbff351d8cb6b5f23da60222126add4268e261199a2921b";
    address owner;
    string public standard = 'CryptoPunks';
    string public name;
    string public symbol;
    uint8 public decimals;
    uint256 public totalSupply;
    uint public nextPunkIndexToAssign = 0;
    bool public allPunksAssigned = false;
    uint public punksRemainingToAssign = 0;
    mapping (uint => address) public punkIndexToAddress;
    mapping (address => uint256) public balanceOf;
    struct offer{ 
    bool isForSale;
    uint punkIndex;
    address seller;
    uint minValue;
    address onlySellTo;
    }
    struct Bid {
    bool hasBid;
    uint punkIndex;
    address bidder;
    uint value;
    }
    // A record of punks that are offered for sale at a specific
    // minimum value, and perhaps to a specific person
    mapping (uint => Offer) public punksOfferedForSale;
    // A record of the highest punk bid
    mapping (uint => Bid) public punkBids;
    // Pending balance of ETH available to withdraw per address
    mapping (address => uint) public pendingWithdrawals;
    uint256 address address event PunkNoLongerForSale(uint indexed punkIndex);
    function CryptoPunksMarket() payable {
    owner = msg.sender;
    totalSupply = 10000;
    punksRemainingToAssign = totalSupplyname = "CRYPTOPUNKS";
    symbol = "";
    decimals = 0;
    event Assign(address indexed to, uint256 punkIndex);
    event Transfer(address indexed from, address indexed to,
    value);
    event PunkTransfer(address indexed from,
    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,
    indexed fromAddress, address indexed toAddress);
    }
    function setInitialOwner(address to, uint punkIndex) {
    require(msg.sender == owner);
    require(!allPunksAssigned);
    require(punkIndex < 10000);
    if (punkIndexToAddress[punkIndex] != to) {
    if (punkIndexToAddress[punkIndex] != 0x0) {
    balanceOf[punkIndexToAddress[punkIndex]]--;
    } else {
    punksRemainingToAssign--;
    }
    punkIndexToAddress[punkIndex] = to;
    balanceOf[to]++;
    Assign(to, punkIndex);
    }
    }
    // Claim a punk
    function getPunk(uint punkIndex) {
    require(allPunksAssigned);
    require(punksRemainingToAssign > 0);
    require(punkIndexToAddress[punkIndex] == 0x0);
    require(punkIndex < 10000);
    punkIndexToAddress[punkIndex] = msg.sender;
    balanceOf[msg.sender]++;
    punksRemainingToAssign--;
    Assign(msg.sender, punkIndex);
    }
    // Transfer ownership of a punk to another
    // user without requiring payment
    function transferPunk(address to, uint punkIndex) {
    require(allPunksAssigned);
    require(punkIndexToAddress[punkIndex] == msg.sender);
    require(punkIndex < 10000);
    if (punksOfferedForSale[punkIndex].isForSale) {
    punkNoLongerForSale(punkIndex);
    }
    punkIndexToAddress[punkIndex] = to;
    balanceOf[msg.sender]--;
    balanceOf[to]++;
    Transfer(msg.sender, to, 1);
    PunkTransfer(msg.sender, to, punkIndex);
    Bid storage bid = punkBids[punkIndex];
    if (bid.bidder == to) {
    pendingWithdrawals[to] += bid.value;
    punkBids[punkIndex] = Bid(false,
    punkIndex, 0x0, 0);
    }
    }
    // De-list a punk that was offered for sale
    function punkNoLongerForSale(uint punkIndex) {
    require(allPunksAssigned);
    require(punkIndexToAddress[punkIndex] == msg.sender);
    require(punkIndex < 10000);
    punksOfferedForSale[punkIndex] = Offer(false, punkIndex,
    msg.sender, 0, 0x0);
    PunkNoLongerForSale(punkIndex);
    }
    // Offer a punk for sale for specified price
    function offerPunkForSale(uint punkIndex,
    uint minSalePriceInWei) {
    require(allPunksAssigned);
    require(punkIndexToAddress[punkIndex] == msg.sender);
    require(punkIndex < 10000);
    punksOfferedForSale[punkIndex] = Offer(true, punkIndex,
    msg.sender, minSalePriceInWei, 0x0);
    PunkOffered(punkIndex, minSalePriceInWei, 0x0);
    }
    // Offer a punk for sale to a specific
    // buyer address for a specified price
    function offerPunkForSaleToAddress(uint punkIndex,
    uint minSalePriceInWei, address toAddress) {
    require(allPunksAssigned);
    require(punkIndexToAddress[punkIndex] == msg.sender);
    require(punkIndex < 10000);
    punksOfferedForSale[punkIndex] = Offer(true, punkIndex,
    msg.sender, minSalePriceInWei, toAddress);
    PunkOffered(punkIndex, minSalePriceInWei, toAddress);
    }
    // Withdraw funds held by the contract
    // after a sale or when outbid
    function withdraw() {
    require(allPunksAssigned);
    uint amount = pendingWithdrawals[msg.sender];
    pendingWithdrawals[msg.sender] = 0;
    msg.sender.transfer(amount);
    }// Purchase a punk that is listed for sale
    function buyPunk(uint punkIndex) payable {
    require(allPunksAssigned);
    require(punkIndex < 10000);
    Offer storage offer = punksOfferedForSale[punkIndex];
    require(offer.isForSale);
    require (offer.onlySellTo == 0x0 ||
    offer.onlySellTo == msg.sender);
    require(msg.value >= offer.minValue);
    require(offer.seller == punkIndexToAddress[punkIndex]);
    address seller = offer.seller;
    punkIndexToAddress[punkIndex] = msg.sender;
    balanceOf[seller]--;
    balanceOf[msg.sender]++;
    Transfer(seller, msg.sender, 1);
    punkNoLongerForSale(punkIndex);
    pendingWithdrawals[seller] += msg.value;
    PunkBought(punkIndex, msg.value, seller, msg.sender);
    Bid storage bid = punkBids[punkIndex];
    if (bid.bidder == msg.sender) {
    pendingWithdrawals[msg.sender] += bid.value;
    punkBids[punkIndex] = Bid(false, punkIndex, 0x0, 0);
    }}
    // Bid on a punk, placing funds in escrow
    function enterBidForPunk(uint punkIndex) payable {
    require(punkIndex < 10000);
    require(allPunksAssigned);
    require(punkIndexToAddress[punkIndex] != 0x0);
    require(punkIndexToAddress[punkIndex] != msg.sender);
    require(msg.value > 0);
    Bid storage existing = punkBids[punkIndex];
    require(msg.value >= existing.value);
    if (existing.value > 0) {
    pendingWithdrawals[existing.bidder] += existing.value;
    }
    punkBids[punkIndex] = Bid(true, punkIndex,
    msg.sender, msg.value);
    PunkBidEntered(punkIndex, msg.value, msg.sender);
    }
    // Accept a bid for a punk, if the value of
    // the bid exceeds the specified amount
    function acceptBidForPunk(uint punkIndex, uint minPrice) {
    require(punkIndex < 10000);
    require(allPunksAssigned);
    require(punkIndexToAddress[punkIndex] == msg.sender);
    address seller = msg.sender;
    Bid storage bid = punkBids[punkIndex];
    require(bid.value > 0);
    require(bid.value >= minPrice);
    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);
    }
    // Remove a previous unsuccessful
    // bid for a punk and retrieve funds
    function withdrawBidForPunk(uint punkIndex) {
    require(punkIndex < 10000);
    require(allPunksAssigned);
    require(punkIndexToAddress[punkIndex] != 0x0);
    require(punkIndexToAddress[punkIndex] != msg.sender);
    Bid storage bid = punkBids[punkIndex];
    require(bid.bidder == msg.sender);
    PunkBidWithdrawn(punkIndex, bid.value, msg.sender);
    uint amount = bid.value;
    punkBids[punkIndex] = Bid(false, punkIndex, 0x0, 0);
    msg.sender.transfer(amount);
    }
    }

  • - Code by Matt Hall & John Watkison