“246 Lines”
Exhibition
Text
for 10,000
Written for NODE
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