How NFTs and digital art are stored and accessed on the Ethereum blockchain
Part 2 - We review how digital art and NFTs like CryptoPunks are stored and accessed using on-chain technology
Dear frontrunners,
Previously we outlined the centralization and risk vectors of digital art (NFTs) on the Ethereum blockchain. We reviewed how the majority of NFTs are stored or accessed via “web2 rails” and therefore don’t align with crypto first principles of a permissionless, decentralized, trustless system. Digital art, digital music, and any NFT derivative stored on traditional cloud solutions like AWS or Google Cloud are inherently trust-based because we must trust that a closed-knit system of operators will act with objectivity and as proper stewards of our information.
We know this to be an impossibility and point to the most recent example of the FTX NFT exchange: all digital art stored on the FTX centralized servers is now lost to history: including Coachella, Tomorrowland, and the infamous “test” NFT sold for $270,000 just 12 months ago.
We estimate that about 70% of all Ethereum NFTs are exposed to web2 centralization vectors and implore all NFT holders to, at a minimum, be aware of the risk exposure specific to their digital art collection.
In this analysis, we’ll explore how digital art is stored and accessed on the blockchain or ‘on-chain’. For those who need a refresher on how NFTs are stored and accessed using AWS, Google Cloud, HTTP and IFPS, please consider reviewing part 1 of this guide first.
On-chain NFT CryptoPunks
I can’t think of a more perfect example of how to walk through on-chain digital art storage than with CryptoPunks. For the uninitiated, CryptoPunks is an OG Ethereum NFT from 2017.
Launched by Larva Labs in June 2017, CryptoPunks consists of 10,000 unique 8-bit-style, low-resolution images of so-called punks. CryptoPunks consisted of 5 punk types: Alien, Ape, Zombie, Female, or Male, with up to 8 attributes, including Pigtails, Top Hat, Choker, Tiara, Hoodie, and more.
As of this writing, the CryptoPunk ecosystem has a fully diluted market cap of $1.2 billion or 840K ethereum and is the number 1 Ethereum NFT by volume, floor price, and market cap in the history of the blockchain.
CryptoPunks ownership model version 1 - You’re buying a position in an array.
When Larva labs launched v1 of CryptoPunks, the initial smart contract deployed included a string that represents a SHA256 encrypted version of one image that depicts 100 rows and 100 columns of CryptoPunks, called a “composite image”. Each position in the composite image is a unique CryptoPunk.
The smart contract didn’t store the individual CryptoPunks on-chain or in a decentralized file system like IPFS. The smart contract didn’t even store the composite image itself. Rather, the original contract referenced a SHA-256 hash of the aforementioned composite image, and authenticity was determined by developers cross-referencing the hash in the smart contract against their downloaded copy.
Note the line in the smart contract source code CryptoPunksMarket.sol which contains “imageHash”.
pragma solidity ^0.4.8;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;
The “imageHash” string is the only section in the entire CryptoPunks smart contract which makes reference to the 100x100 grid of CryotpPunk images. Again, the “imageHash” variable is not actually an image, it is SHA256 encryption of the composite image saved as a string. Developers enforced authenticity by referencing the embedded hash string against their downloaded image, you can do this too. Download the grid image and generate your own SHA256 hash. The values will match.
At this point, we’ve ascertained that what’s stored on-chain is a string that represents an encrypted version of the 100x100 composite image grid of 10,000 CryptoPunks.
The individual CryptoPunk profile pics were not stored on-chain in any capacity.
You can also see the imageHash output in the original CryptoPunk smart contract on etherscan 0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB:
So what were CryptoPunk NFT holders actually buying?
The original holders of the CryptoPunk NFT were buying a position in a smart contract array called a “punk index”. We can see in the original smart contract code a function called getPunk that maps punkIndexToAddress to msg.sender which is the Ethereum address of the buyer.
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 ‘punkIndexToAddress’ variable is the only on-chain attribute that maps the punk index aka grid position to the corresponding owner’s Ethereum address. Each position in the grid is numbered (left to right, top to bottom) from values 1 through 10,000.
For example, calling punkIndexToAddress(1) would return the owner of the punk index at position 1…
…and calling punkIndexToAddress(9347) would return the owner of the punk index at position 9347:
Every time a CryptoPunk NFT was sold, what was transferred from the old owner to the new owner was not an image. The new owner was buying an index in an array. The old owner was selling an index in an array. The ownership of the array position was verified via the punkIndexToAddress.
This is a snippet of the buyPunk function from the original smart contract, we can see it just updates the Ethereum address at punkIndexToAddress with the address of msg.sender (the buyer). That’s it!
function buyPunk(uint punkIndex) payable {
punkIndexToAddress[punkIndex] = msg.sender;
balanceOf[seller]--;
balanceOf[msg.sender]++;
Transfer(seller, msg.sender, 1);
}}
What information is on-chain?
The SHA256 hashed 100x100 composite grid image saved as a string and the Ethereum address of the CryptoPunk holder mapped to the ‘punk index’ were the only attributes of this NFT collection stored on the chain. What we cannot infer from any on-chain data is the specific image that corresponds to the composite grid.
When a buyer purchased punkIndexToAddress[10] how did they know they’d receive the 10th image in the 100x100 composite image?
When a buyer purchased punkIndexToAddress[9347] how did they know they’d receive the 9347th image in the 100x100 composite image?
With version 1, owners had to trust the centralized Larva Labs servers to ensure that punkIndexToAddress[10] returns an image of CryptoPunks #10…
…and not Trump Digital Trading Card #10 (which is currently trending as number 1 on opsensea!)
With Cryptopunks v1, the individual CryptoPunk profile pics were stored on a centralized web server. It sounds extreme, but when crypto natives compare and contrast “trusted systems” vs “trustless systems”, this is what they mean. NFT holders trusted the centralized Larva Labs web service and its corresponding cloud provider that purchasing a Crypto Index [10] would return the CryptoPunk NFT which corresponds to the 10th position in the 100x100 grid.
CryptoPunks ownership model version 2 - Your image is on-chain, well, at least a description of it.
5 years after its launch, CryptoPunks finally moved its images on-chain:
The specific on-chain breakthrough was the ability to store images on the blockchain as “SVG” or scalable vector graphics. SVGs allow developers to store images in XML viewable as text that can be stored on-chain. Rather than store large image data, Larva Labs “describes” the intended image as text, and our browsers and NFT platforms like OpenSea facilitate the conversation and image rendering on our behalf.
Parker Ferguson outlines the value of this in his tutorial on how to create on-chain NFTs as follows:
Think about it this way, if I want to send you a simple image I can either email or text you a high resolution PNG, or you could simply describe it in a few words and let your recipient render or generate it for you. If data or the transmission of data is expensive we can make a ‘trade off’ by reducing that cost via increasing the cost, as effort, by the recipient. - Parker Ferguson
Given the limited storage capacity of current state blockchain technology, it’s proven more economical to store descriptions of pictures on-chain rather than the picture itself.
In version 2 of the CryptoPunks smart contract, we can see Larva Labs deployed a “punkImageSvg” function below, which accepts a Punk Index position as an input.
In part 1, we outlined how other smart contracts return a centralized web provider URL with an ‘http’ preface. This is not true for PunkImageSvg, since it actually returns a “description of CryptoPunk 10” described using SVG:
svg string : data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" version="1.2" viewBox="0 0 24 24"><rect x="13" y="3" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="14" y="3" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="12" y="4" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="13" y="4" width="1" height="1" shape-rendering="crispEdges" fill="#a66e2cff"/><rect x="14" y="4" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="11" y="5" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="12" y="5" width="1" height="1" shape-rendering="crispEdges" fill="#a66e2cff"/><rect x="13" y="5" width="1" height="1" shape-rendering="crispEdges" fill="#a66e2cff"/><rect x="14" y="5" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="10" y="6" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="11" y="6" width="1" height="1" shape-rendering="crispEdges" fill="#85561eff"/><rect x="12" y="6" width="1" height="1" shape-rendering="crispEdges" fill="#a66e2cff"/><rect x="13" y="6" width="1" height="1" shape-rendering="crispEdges" fill="#a66e2cff"/><rect x="14" y="6" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="9" y="7" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="10" y="7" width="1" height="1" shape-rendering="crispEdges" fill="#85561eff"/><rect x="11" y="7" width="1" height="1" shape-rendering="crispEdges" fill="#a66e2cff"/><rect x="12" y="7" width="1" height="1" shape-rendering="crispEdges" fill="#a66e2cff"/><rect x="13" y="7" width="1" height="1" shape-rendering="crispEdges" fill="#a66e2cff"/><rect x="14" y="7" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="8" y="8" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="9" y="8" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="10" y="8" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="11" y="8" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="12" y="8" width="1" height="1" shape-rendering="crispEdges" fill="#a66e2cff"/><rect x="13" y="8" width="1" height="1" shape-rendering="crispEdges" fill="#a66e2cff"/><rect x="14" y="8" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="15" y="8" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="7" y="9" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="8" y="9" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="9" y="9" width="1" height="1" shape-rendering="crispEdges" fill="#8b532cff"/><rect x="10" y="9" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="11" y="9" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="12" y="9" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="13" y="9" width="1" height="1" shape-rendering="crispEdges" fill="#a66e2cff"/><rect x="14" y="9" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="15" y="9" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="16" y="9" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="7" y="10" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="8" y="10" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="9" y="10" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="10" y="10" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="11" y="10" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="12" y="10" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="13" y="10" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="14" y="10" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="15" y="10" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="16" y="10" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="7" y="11" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="8" y="11" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="9" y="11" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="10" y="11" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="11" y="11" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="12" y="11" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="13" y="11" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="14" y="11" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="15" y="11" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="16" y="11" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="6" y="12" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="7" y="12" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="8" y="12" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="9" y="12" width="1" height="1" shape-rendering="crispEdges" fill="#3c5659ff"/><rect x="10" y="12" width="1" height="1" shape-rendering="crispEdges" fill="#3c5659ff"/><rect x="11" y="12" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="12" y="12" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="13" y="12" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="14" y="12" width="1" height="1" shape-rendering="crispEdges" fill="#3c5659ff"/><rect x="15" y="12" width="1" height="1" shape-rendering="crispEdges" fill="#3c5659ff"/><rect x="16" y="12" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="6" y="13" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="7" y="13" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="8" y="13" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="9" y="13" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="10" y="13" width="1" height="1" shape-rendering="crispEdges" fill="#485d5dff"/><rect x="11" y="13" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="12" y="13" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="13" y="13" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="14" y="13" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="15" y="13" width="1" height="1" shape-rendering="crispEdges" fill="#485d5dff"/><rect x="16" y="13" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="6" y="14" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="7" y="14" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="8" y="14" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="9" y="14" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="10" y="14" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="11" y="14" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="12" y="14" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="13" y="14" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="14" y="14" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="15" y="14" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="16" y="14" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="7" y="15" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="8" y="15" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="9" y="15" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="10" y="15" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="11" y="15" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="12" y="15" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="13" y="15" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="14" y="15" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="15" y="15" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="16" y="15" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="7" y="16" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="8" y="16" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="9" y="16" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="10" y="16" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="11" y="16" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="12" y="16" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="13" y="16" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="14" y="16" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="15" y="16" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="16" y="16" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="7" y="17" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="8" y="17" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="9" y="17" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="10" y="17" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="11" y="17" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="12" y="17" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="13" y="17" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="14" y="17" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="15" y="17" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="16" y="17" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="7" y="18" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="8" y="18" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="9" y="18" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="10" y="18" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="11" y="18" width="1" height="1" shape-rendering="crispEdges" fill="#4a1201ff"/><rect x="12" y="18" width="1" height="1" shape-rendering="crispEdges" fill="#4a1201ff"/><rect x="13" y="18" width="1" height="1" shape-rendering="crispEdges" fill="#4a1201ff"/><rect x="14" y="18" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="15" y="18" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="16" y="18" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="8" y="19" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="9" y="19" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="10" y="19" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="11" y="19" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="12" y="19" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="13" y="19" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="14" y="19" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="15" y="19" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="8" y="20" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="9" y="20" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="10" y="20" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="11" y="20" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="12" y="20" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="13" y="20" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="14" y="20" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="8" y="21" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="9" y="21" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="10" y="21" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="11" y="21" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="12" y="21" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="13" y="21" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="8" y="22" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="9" y="22" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="10" y="22" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="11" y="22" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="12" y="22" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="8" y="23" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/><rect x="9" y="23" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="10" y="23" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="11" y="23" width="1" height="1" shape-rendering="crispEdges" fill="#713f1dff"/><rect x="12" y="23" width="1" height="1" shape-rendering="crispEdges" fill="#000000ff"/></svg>
Platforms like opensea and rariable will then programmatically convert the returned SVG string into CryptoPunk10 but we can confirm this independently by going to sites like Free Code Format and manually convert the SVG to its two-dimensional graphic equivalent.
Cool right? Images are stored on the blockchain as text-based descriptions to be converted into a two-dimensional picture via an SVG-compatible web browser and/or NFT platform.
So why can’t we move all web2 NFTs on-chain?
Technically it’s possible, but it’s a non-trivial cost. The CyberBrokers NFT collection self-described as “… a first-of-its-kind art collectibles ecosystem centered around 10,001 unique and on-chain CyberBroker NFTs.” was not originally an on-chain NFT.
In March of 2022 the NFT collection moved its 10,001 pieces of digital art on the blockchain at a cost of 91.04 ETH or $232,761!
Approximately 13 Ethereum was spent on uploading the NFT collection metadata: string descriptors & attributes like “mind”, “body”, “soul”, “female-14_looter_googles_blue”, etc while ~78 Ethereum was allocated to uploading the actual SVG layers of the CyberBrokers collection:
What was the total cost to upload the CyberBroker’s binary SVG layers? 3,108,758,797 gwei. An on-chain migration of NFTs is not a cheap endeavor.
Does any of this even matter?
If an NFT is stored on the blockchain, aren’t there still vectors of centralization risk?
Yes. Your browser could have a new patch that censors certain types of SVG descriptors, perhaps those deemed as a “national security threat” from America’s intelligence apparatus.
Maybe the ISP you use to access your NFT carte blanche bans IPs of PFP trading platforms like Opensea at the behest of the US Lobbying industry funded by Apple, Facebook, and even Twitter.
Moreover, OFAC could ban NFT wallet and smart contract addresses on the Ethereum blockchain, and in turn, OFAC-compliant node operators would be obligated to remove these illicit blocks, and your NFT, from the now-censored blockchain.
Does the typical NFT collector desire a future where their profile pics are stored on a decentralized network of permissionless nodes or even on the blockchain? Probably not. Clearly, decentralization is a spectrum but I form my opinion on the current attitudes and ideas which permeate our society. It appears that most people are happy to live in a censored universe where dictates from unelected bureaucrats define what we can and cannot say, do, and access.
Much like my first analysis, unless there is a forcing function within the broader NFT ecosystem e.g. “all NFTs minted on opensea will be stored via IPFS” we will continue to see centralization vectors across the digital art space.
My bet, after this fallout, is that digital art collectors elevate their awareness of centralization risk vectors such that acts of destruction like the FTX NFT market collapse have as small of an impact as possible.
To knowledge and wisdom,
John Cook
December 16th, 2022
San Francisco, CA
www.frontruncrypto.com
Article cover generated by DALL-E: “A pencil and watercolor drawing of young people trying to explain NFTs to their grandparents”