I’ve been recently exploring the problem of immutability of NFTs by building LooksMutable. I documented my process on Twitter and my first few takeaways in this blogpost.

But these resources were intentionally low-resolution, intented to be understandable by newcomers, a non-technical more mainstream audience that might find them of help, rather than a fully technical post full of jargon.

I’ve also encountered a few more interesting nuances than the ones described previously, as I go more and more from scratching the surface into the technical reality of NFTs, cryptocurrencies and Solidity smart contracts in general.

That’s why I wanted to make a technical blogpost outlining what the current problems with NFTs are in regards to immutability, and reflect on possible solutions or workarounds.

The problem of immutability

To make sure we’re all on the same page, let’s start by defining what the problem is.

NFTs are tokens stored in smart contracts on the blockchain. These tokens have a unique ID and belong to a single wallet.

Two modern standards define what a NFT is and should do: ERC721 and ERC1155.

Both standards define what NFT metadata should be, and how to get it. Details are in the standards linked above, but basically: NFTs should have a tokenURI or URI method that returns a link to the metadata of a certain token ID.

When you call the tokenURI method on a ERC721 contract, you get something like:

https://nft.rameerez.com/nft/0

Which is the URL that contains the metadata associated with the NFT. Such metadata is of the form:

{
  "name": "@rameerez's Profile Pic",
  "description": "This is my profile pic.",
  "image": "https:\/\/nft.rameerez.com\/assets\/rameerez.jpg"
}

In the case of ERC721, the metadata is defined by the ERC721 Metadata JSON Schema as defined in the EIP-721 specification.

But it’s essentially: the name of the NFT, its description, its image (most commonly: the image or video artwork associated with the NFT) and additional attributes many collections use for determining token rarity.

Now, while the metadata schema is pretty much all the same across NFTs, the URL can point to:

  1. A usual, centralized server (like nft.rameerez.com)
  2. Decentralized storage (like ipfs://hash)
  3. The URL can also just be a base64 encoded JSON, contained in the smart contract itself (like data:application/json;base64,...)

Data stored as described in 1 is completely mutable. Centralized servers we all know and love belong to a single individual or group and are easy to edit.

Data stored as 2 is more resistant to change, but IPFS has its own caveats as we’ll see.

Data stored as 3 is as immutable as the blockchain the NFT is in.

The problem of immutability is: the majority of all NFTs store their data in centralized servers. NFTs are portrayed as being fully on-chain, as something immutable that allows holders to fully own digital pieces of art forever. That’s just not the case. Well over half of all NFTs are mutable. Most of the time, what buyers are purchasing is just very expensive links to centralized servers.

ERC721 NFTs are mutable by design

If we read the ERC721 specification, we’ll find this:

The URI MAY be mutable (i.e. it changes from time to time). We considered an NFT representing ownership of a house, in this case metadata about the house (image, occupants, etc.) can naturally change.

This problem doesn’t seem like a problem for the writers of the specification, since NFTs were presumably concieved as mutable from the very beginning.

The most common use case we’re seeing nowadays (using NFTs as proof of ownership over pieces of immutable digital art) was probably not the intended use case.

Storing the data on decentralized storage systems like IPFS, or in the smart contract itself stored as base64 encoded JSONs is probably just a hack to work around this design flaw.

ERC1155 NFTs are mutable by design

If we read the ERC1155 specification, URIs are defined like this:

The URI value allows for ID substitution by clients. If the string {id} exists in any URI, clients MUST replace this with the actual token ID in hexadecimal form. This allows for a large number of tokens to use the same on-chain string by defining a URI once, for that large number of tokens.

Meaning:

ERC721s can potentially have a different URI for every token, but ERC1155 tries to unify all tokens in the same smart contract by introducing the comcept of URI “template”.

When you call the URI method on a ERC1155 contract, what you get is something like:

https://api.opensea.io/api/v1/metadata/0x{id}

Where {id} is a literal string you need to substitute with the actual token ID, which would result in something like:

https://api.opensea.io/api/v1/metadata/0x495f947276749Ce646f68AC8c248420045cb7b5e/80494307024529346018053650490912529916739680814770830097664395781112510545950

Now, this is a big problem for immutability.

It is a big problem because it forces centralized storage.

We can’t use decentralized storage like IPFS, because the URIs in IPFS are hashes of unique pieces of content and we can’t have completely unique URIs for every token in ERC1155.

We can’t also store the data as a base64 encoded JSON for the same reason.

We’re just left with a URI template and a literal string we must replace in order to get the actual NFT metadata.

Which means, in my understanding, that ERC1155s are just mutable by design, and can’t be turned into immutable NFTs.

Non-standard NFTs

There’s also non-standard NFTs, apart from the aforementioned ERC721 and ERC1155 standards. These kinds of NFTs were probably developed before the standards were drafted and released, and implement their own mechanisms and ideas of what an NFT is and should do.

They don’t all share a tokenURI or URI method, metadata is not specified, we do not now for sure if they allow for data immutability or not… one would just have to judge on a case-by-case examination of the contract source code.

One famous example of non-standard NFTs are Cryptopunks, which arguably inspired the ERC721 specification.

Problems with mutable NFTs

There are currently a number of problems derived from the fact that NFTs are eminently mutable. I’d like to explore a few ones.

Rarity is not determined at mint and metadata is manually assignable after minting

Say a collection launches as mutable. One popular mechanism for launching NFT collections nowadays is what’s known as a “reveal”, this is: the artwork associated with the NFT at launch is something non unique, like an egg, that later “hatches” into the final NFT artwork in a reveal event where the NFT owners change the metadata associated with the NFT to its presumably final data.

Now, this is a big, big problem.

Say you own one of these collections. You launch it as “eggs”. People start minting them and buying them. Say all of the tokens in the collection are minted. Since all NFTs have been minted and assigned ownership, you now have a complete list of owners together with the exact token_ids they own. Something like:

token_id        |        owner
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
0               |        alice.eth
1               |        bob.eth
2               |        alice.eth

Now, say bob.eth is either part of the team, or a stakeholder, or a friend of the owners, or just someone we would like to make rich.

Since no final metadata has been revealed yet and we have full control over the server that’s serving the NFT metadata, we can just make it such that the NFT with id 1 gets some of the most rare traits in the collection, or the most beautiful artwork, or something equally valuable that could be potentially traded in the secondary market for a huge profit.

What this means is NFT owners can rig the process of assigning rarity and metadata to NFTs even after minting, as long as the NFTs are mutable.

In mutable NFTs, NFT rarity is not determined at minting and can be changed and assigned later with privileged information.

Not all holders have an equal chance of getting a rare NFT at mint time. Theoretically it can all be rigged down to every single NFT and owner.

Rarity is not permanent

One collorary of this is that rarity analyses are useless with mutable NFTs. The attributes that give the NFTs their rarity could change at any moment.

Imagine the most rare NFT in a collection just loses all its rarity attributes, its market value would sink. Or imagine one NFT character or object in a game that changes its attributes, said character or object could lose all its “powers” in the game at any moment.

The tokenURI might just change over time

While this is not part of the ERC721 specification, one can implement a method to change the baseURI of the tokenURI well after deployment.

That method is usually an onlyOwner method, or has a similar Solidity-based access restriction like onlyRole.

This in theory allow for bad actors to change NFT metadata even if said metadata is stored somewhere decentralized, even after mint and even after the reveal.

There could be some legit, benign use cases for this, such as performing an honest reveal.

Good practices could be to burn the keys after the final metadata has been revealed, or implementing a uri_change_count kind of variable in the smart contract that tracks the number of changes that have been made to the URI and only allows the URI to change once or twice.

CORS and data innacesibility

One common practice in mutable NFTs is to implement CORS (Cross-Origin Resource Sharing) restrictions in the server that’s serving the NFT metadata.

What ends up happening, practically speaking, is that only well-known services like OpenSea, Rarible, Infura, etc. are allowed to fetch the metadata from the tokenURI, rendering all other services useless since CORS policy would just ban the request. While implementing LooksMutable, this was a common occurrence: I could just not fetch the metadata of many mutable NFTs because the servers would just reject requests from looksmutable.com –– which means new services that come up will also face this problem.

Data should be just universally accessible in web3.

If web3 is all about decentralization and eliminating censorship and gatekeeping to data, this doesn’t seem like the most congruent approach to the ideal. What we’re de-facto currently living with is just centralized data, with a crpto/NFT branding layer on top to make it more marketeable and sellable to potential buyers.

Service-dependent rendering

Signal founder Moxie Marlinspike did a really good experiment to demonstrate mutable NFTs can be deceiptful.

Since one owns the metadata server, one can serve different data to different requesters. In his experiment, Moxie served different images for the same NFT, depending on whether the request was coming from OpenSea, Rarible or Metamask.

In the case of OpenSea and Rarible, it was just two different artworks. But when the request originated from Infura (the Ethereum node provider Metamask uses), the image associated with the NFT would be the poop emoji.

So the same NFT would be rendered very differently in different services.

Moxie got his collection taken down from OpenSea when they realized his trick.

Again, wasn’t web3 about decentralization?

The problem with IPFS and decentralized storage

The most common solution to the mutability problem is using decentralized storage instead of centralized servers to store the NFT metadata. The most known decentralized storage solution is probably IPFS (Inter Planetary File System).

IPFS is a protocol for decentralized file sharing. Files are identified by a unique hash, which is actually the URI of resources in the protocol.

So instead of using links like https://ipfs.io/user/image.jpg, resources in IPFS can be identified as just ipfs://hash, where hash is a hash of the content that one is trying to fetch. This means that if a content changes, its hash necessarily changes, and the URI would necessarily need to be different, which pretty much enforces immutability.

The network is made up of nodes with the ability of replicating the same pieces of content over different nodes, to add redundancy.

While this seems like a good solution to the mutability problem of NFTs (just store the content in IPFS instead of a centralized server), there are a few problems with IPFS itself.

IPFS can turn into regular centralized storage, in practice

Content in IPFS doesn’t get automatically replicated, nor a minimum redundancy is guaranteed. IPFS just allows for that to happen, but it’s a tool and not an enforcement.

What this means is that you could potentially set up a really big IPFS node, fill it up with content, and that content could only live in your node and not anywhere in the network.

This can happen if the content you store in your node is not relevant enough (as in: it’s not accessed often) for the network to replicate it elsewhere.

IPFS is not forever: content hash addressing does not imply persistence. Someone needs to pay the bills

The aforementioned IPFS nodes can get really big, and really costly. If the owner goes out of business or just stops paying for the IPFS server, taking down a big node might imply taking down most of the content contained in that node forever.

This has happened before.

NFT service Hic Et Nunc (HEN) left some 50k users wondering where their NFTs were when they stopped paying for their IPFS nodes. The story is well told here.

In a nutshell: the community contacted Infura and they started paying for the IPFS servers so the content wouldn’t go down.

The good news is, since the hash of the content is known, you at least know which pieces of content you exactly need to resurface, and links don’t break. And since IPFS is not a private, centralized service, the community was able to just build mirror sites and reimplement the functionality of the site as soon as the IPFS node was back up.

What happened with HEN was sort of a miracle, the problem still exists: if a huge IPFS node goes down, chances are most of the content stored in that node is going to go down. Storage isn’t free, someone ultimately needs to pay for that storage.

The need for a centralized gateway

Since we’re all essentially just building web-based applications, we need to be able to fetch resources via HTTP. To do that, we use IPFS HTTP gateways. IPFS has its own one, so one can turn any IPFS URI from ipfs://hash to:

https://ipfs.io/ipfs/hash

ipfs.io then resolves the hash into the correct content and returns it.

There are multiple IPFS gateways/resolvers, like that by IPFS itself, but there’s also one provided by Moralis at ipfs.moralis.io:2053, and I’m sure plenty more.

The concern is not big, but in theory we’re all mostly relying on ipfs.io to resolve our hashes to the right content and not tamper with them in the process. I think it’s very unlikely they themselves do that, but as more resolvers get implemented a bad actor could potentially disguise an ill-intentioned, tampering service as a IPFS relay.

Or, a most common problem: if the tokenURI in an NFT is pointing to a HTTP relay instead of addressing the content as a IPFS-like hash URI, the metadata could go down if the relay goes down.

P.S.: Follow me on Twitter to stay in the loop. I'm writing a book called Bold Hackers on making successful digital products as an indie hacker. Read other stories I've written. Subscribe below to get an alert when I publish a new post:

No spam ever, unsubscribe with one single click.