Introducción
¿Dónde están los NFTs?
Veamos un ejemplo
No siempre es así: storage centralizado
Falta de inmutabilidad y permisos peligrosos
En búsqueda de la inmutabilidad on-chain
Inmutabilidad on-chain + storage descentralizado
Cuadrante: Trustlessness de NFTs
Sobre IPFS
Conclusión
Cuando se habla de cryptos, de NFTs y de la web 3, una de las palabras más utilizadas es “descentralización”. Tiene sentido, ya que todo corre sobre protocolos distribuídos, open-source, con una historia inmutable, con nodos “permissionless”, etc. Sin embargo, cuando uno empieza a mirar de cerca, a veces se encuentra con todo tipo de contraargumentos. Por ejemplo, que algunas blockchains se caracterizan por una gran concentración en sus nodos validadores, y que también es común que los smart contracts posean permisos privilegiados que representan un riesgo potencial enorme para los usuarios.
Por eso, un buen ejercicio para hacer cuando uno interactúa con la web 3 es preguntarse: ¿Cuál es el nivel de “trustlessness” de este protocolo/aplicación? Al término “trustless” lo entendemos como la “inexistencia de la necesidad de confiar en terceros”, una de las principales propuestas innovadoras de la web 3.
El nivel de trustlessness no es blanco o negro, hay distintos grados intermedios. En lo que respecta a este artículo, vamos a analizar las distintas implementaciones técnicas de NFTs en Ethereum, el protocolo L1 con smart contracts más descentralizado y trustless. Nos concentraremos en los NFTs que representan imágenes, particularmente en las llamadas PFPs (“Profile Pictures”), la categoría responsable del mayor porcentaje del volumen negociado en el mercado.
Primero hay que comprender que Ethereum (y cualquier otra blockchain) no está diseñado para almacenar grandes volúmenes de datos. Su función es llevar un simple registro distribuido de cuentas y transacciones (por eso, a esta tecnología también se la conoce por el nombre de “Distributed Ledger”).
La imágen de un NFT nunca está en Ethereum. Lo que sí está en Ethereum es lo siguiente:
El registro de la propiedad de cierto “Token ID” (un número) correspondiente a cierto contrato específico (cada colección de PFPs es un contrato que suele tener miles de Token IDs).
Una conexión entre ese Token ID y algún lugar del “mundo off-chain” (fuera de Ethereum) en donde efectivamente se encuentra guardada la imágen.
El registro de la propiedad es muy sencillo. Si tomamos el estándar ERC-721, cada contrato realiza un mapping: a cada Token ID le asigna una address, proceso en el cual se determina quién es el propietario de ese Token ID. Únicamente esa address tiene el poder de transferir la propiedad de ese Token ID para, entre otras cosas, venderlo en marketplaces como OpenSea, LooksRare, etc. En otras palabras, decir “soy dueño de este NFT” es equivalente a decir “la address que aparece como owner de este Token ID N° X, en el contrato Y, está en mi poder”.

Pero ahora llegamos al eje de la cuestión: ¿Cómo se realiza la conexión entre un Token ID y su respectiva imágen off-chain? Para responder a la pregunta, primero hay que introducir el concepto de “metadata”. La metadata de un NFT es (casi siempre) un archivo en formato JSON, en donde se especifican ciertos detalles del NFT (entre ellos una dirección hacia la imágen). Este archivo JSON, como veremos más adelante, suele guardarse de distintas formas, algunas más trustless y otras menos trustless.
El segundo concepto importante es el de “URI” (Uniform Resource Identifier). El URI es una cadena de caracteres que se almacena dentro del contrato (en Ethereum) y nos dice, en sencillas palabras, a donde buscar la metadata fuera de Ethereum. En el contrato, el URI de cada Token ID es la concatenación del “baseURI” y el mismo número del Token ID, y se lo obtiene utilizando la función “tokenURI”. El baseURI es establecido por los owners del contrato. Veremos más adelante que esto último es uno de los puntos críticos, ya que en la mayoría de los contratos los owners pueden, en cualquier momento, cambiar el baseURI mediante una función llamada “setBaseURI”.
Aclaración: dentro del estándar ERC-721, la utilización de un tokenURI pertenece a la extensión llamada “ERC721Metadata”. Ver la implementación de OpenZeppelin.
Para ver todo de forma clara, veamos un ejemplo. Lleguemos, paso a paso, a la imágen del Bored Ape #456 (es decir, el Token ID N° 456 del contrato 0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D)
Primero vamos al contrato del Bored Ape Yacht Club en Etherscan y le pedimos a la función tokenURI que nos pase el URI del Token ID N° 456.

Así obtenemos el tokenURI: “ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/456” (el baseURI es exactamente igual pero sin el “456”). En este caso, la metadata se encuentra almacenada en IPFS, un protocolo peer-to-peer de storage distribuído y descentralizado en el cual profundizaremos al final de este artículo. También veremos más adelante que no todas las colecciones de NFTs utilizan IPFS.
Para acceder a la metadata en IPFS desde un web browser como Chrome, necesitamos un URL compuesto por un “gateway” y el URI. Debajo dejo el URL que utiliza el gateway público de IPFS (es decir, “https://ipfs.io”), pero también se podría acceder con cualquier otro gateway que se conecte al protocolo de IPFS.
URL: https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/456
Ahí encontramos el JSON, es decir, la metadata. Además de ver los atributos del NFT, podemos ver un nuevo URI: el de la imágen. En este caso, la imágen también se encuentra en IPFS.
URL: https://ipfs.io/ipfs/QmQGQ6xSWmrUHn6yBVofJzRUoxu5w6goCbMdutKHmCZ51s

En el caso del Bored Ape Yacht Club, tanto la metadata (JSON) como la imágen están en IPFS. Sin embargo, no siempre es así. Muchas veces los proyectos guardan la metadata y/o la imágen en servidores centralizados (como AWS o Google) y ofrecen su propia API para acceder a ellas. Por ejemplo, la colección de los Cool Cats (0x1A92f7381B9F03921564a437210bB9396471050C).

En este caso, los Cool Cats almacenan la metadata en servidores centralizados, accesible mediante su propia API: https://api.coolcatsnft.com/cat/456
Esto es cualquier cosa excepto trustless, ya que por más que la imágen sí se encuentre en IPFS (https://ipfs.io/ipfs/QmPd8gJKgdWFDjxHr4PviRSj3FJpqEZ6EuMwXWFj9juL7i), la metadata podría ser modificada en cualquier momento por los administradores del servidor, perjudicando así a los holders de esos NFTs. Por ejemplo, al modificar la metadata podrían alternar el orden intra-colección de los 10,000 NFTs, o aún peor, cambiar las imágenes por completo mediante una modificación del URI que aparece en cada JSON. La metadata está “a un click” de mostrar cualquier cosa excepto a los Cool Cats. Además de tener que confiar en que el equipo administrador actúe de buena fe, también hay que confiar en que tienen un sistema de seguridad sólido frente a hackers.
Como resumen, se podría categorizar el storage de una colección de cuatro formas:
Storage de la metadata y de la imágen descentralizado (e.g. Bored Ape Yacht Club)
Storage de la metadata descentralizado y de la imágen centralizado (e.g. MetaHero)
Storage de la metadata centralizado y de la imágen descentralizado (e.g. Cool Cats)
Storage de la metadata y de la imágen centralizado (e.g. Clone X)
Cuanto más descentralizado es el sistema de storage utilizado, más trustless es una colección de NFTs.
El storage no lo es todo. Antes había mencionado a la función setBaseURI. En la mayoría de los proyectos, esta peligrosa función está disponible para el owner del contrato. Sin ir más lejos, el contrato del Bored Ape Yacht Club, una de las colecciones de referencia, todavía le otorga a su owner el poder de modificar el baseURI:

Si el owner modificara el baseURI, el contrato dejaría de referenciar al contenido almacenado en IPFS que vimos antes. Se perdería la conexión entre los Token IDs y la metadata/imágenes. La existencia de esta función podría estar justificada, por ejemplo, por una posible necesidad futura de modificar el baseURI debido a un cambio en el sistema de storage utilizado (e.g. migrar de IPFS a Arweave), pero definitivamente reduce el nivel de trustlessness. Todos los BAYC holders confían en que el owner del contrato no actúe maliciosamente. Esto pasa en la mayoría de las colecciones.
Hay (al menos) dos formas de lograr que una colección de PFPs sea 100% inmutable. La primera es la utilizada por los CryptoPunks. La segunda implica que el owner del contrato renuncie al poder de modificar el baseURI.
Los CryptoPunks no tienen el problema de la falta de inmutabilidad porque en su momento (antes de que existiera el estándar ERC-721) decidieron subir al contrato el hash (SHA256) de la imagen que contiene a los 10,000 Punks ordenados y al máximo detalle. Dicho hash está almacenado como una constante inmodificable. De esta forma, si uno tuviera la imágen original en el mundo off-chain y generara su hash, obtendría como output el mismo valor que está asignado en el contrato de forma inmutable. Nadie, ni siquiera el owner del contrato (si lo hubiera), puede modificar dicha constante (en este caso, llamada imageHash). Gracias a la fuerte resistencia a la colisión de esta función hash, es prácticamente imposible encontrar otra imágen cuyo hash sea igual al hash de la imágen original.


Todos pueden verificarlo descargando la imágen en https://www.larvalabs.com/public/images/cryptopunks/punks.png y generando el hash en https://hash.online-convert.com/sha256-generator o cualquier otro programa que corra la función SHA256.

Sin embargo, el método de los CryptoPunks solo soluciona el problema de la falta de inmutabilidad, pero no el problema de storage. El contrato nunca hace referencia a la imágen en el mundo off-chain. Por lo tanto, cada vez que un marketplace o cualquier otra persona desee buscar la imágen, muy probablemente la consiga mediante el uso de servicios centralizados. Aún así, este último problema no es grave comparado a la modificación del baseURI que habíamos visto antes, ya que en cualquier momento se puede verificar si la imágen obtenida es la original.
La segunda forma sí logra conseguir inmutabilidad on-chain y al mismo tiempo mantener la conexión entre el contrato y un protocolo de storage descentralizado como IPFS. El owner debe renunciar al poder de modificar el baseURI. Una idea sería transferir el ownership a la address 0x0 (utilizada para quemar tokens porque “no tiene dueño”), pero en muchos casos esto representa un problema: el owner necesita seguir siendo owner para poder utilizar otras funciones no relacionadas al baseURI.
Una posible solución es diseñar la renuncia desde un principio, utilizando una función específica. Ejemplo:
string public baseURI;
bool public isUriMutable;
event ImmutableURI(string baseURI);
//The constructor previously set the isUriMutable variable to true
function _setBaseURI(string memory baseURI_) internal virtual {
baseURI = baseURI_;
}
function setBaseURI(string memory _baseURI) public onlyOwner {
require(isUriMutable);
_setBaseURI(_baseURI);
}
function makeImmutableURI() public onlyOwner {
require(isUriMutable);
isUriMutable = false;
emit ImmutableURI(baseURI);
}
De esta forma, el owner puede esperar hasta que todo funcione de forma correcta y, una vez que esté 100% seguro de la eficacia del URI y de la solidez del sistema de storage descentralizado elegido, llamar a la función makeImmutableURI. De esta forma, el URI nunca más podrá modificarse. No hay vuelta atrás.
Vale la pena destacar que los dos métodos detallados podrían implementarse al mismo tiempo, lo que maximizaría los niveles de trustlessness de la colección de PFPs.
Teniendo en cuenta lo que vimos, se podría categorizar a cada colección de PFPs de forma bidimensional según su:
Grado de descentralización del storage (metadata e imágenes)
Grado de inmutabilidad on-chain

Esta representación es solo un ejemplo simplificado. Como dije al principio, la realidad (casi) siempre es una escala de grises.
Todavía no profundizamos en IPFS (InterPlanetary File System) y su funcionamiento. No es el objetivo de este artículo, pero veamos un resumen de lo que sí nos importa.
Una vez cargado cualquier tipo de contenido (archivos, carpetas, imágenes, etc.), IPFS no especifica su ubicación o path (proceso llamado “Location addressing”) porque esta suele cambiar con el tiempo. En su lugar, IPFS trabaja con un proceso llamado “Content addressing”: a cada contenido cargado, el sistema le asigna una address mediante la utilización de una función hash. A esa address se la llama “CID” (Content Identifier). Como lo aclara el mismo nombre, los CIDs son utilizados para la identificación y la solicitud de cada contenido en toda la red de nodos peer-to-peer. Bajo esta metodología, a IPFS no le importa el “dónde” del contenido, sino el “qué”.
Al trabajar con funciones hash, este sistema tiene los siguientes beneficios:
Determinismo: El mismo contenido genera siempre el mismo CID, sin importar quién, dónde y cuándo se realiza su carga a IPFS.
Unicidad: Debido a la fuerte resistencia a la colisión, es prácticamente imposible que dos contenidos distintos generen el mismo CID.
Pseudo-aleatorización: Debido al efecto avalancha, cualquier mínima diferencia en el contenido (input) genera un CID (output) absolutamente distinto.
Integridad: Luego de solicitar un CID específico al sistema, se puede verificar de forma rápida y sencilla si el contenido recibido es exactamente el deseado. Realizando el hash del contenido recibido, el output debería ser exactamente igual que el CID utilizado anteriormente en la solicitud.
Eficiencia: Sin importar el tamaño del contenido en bytes, el CID siempre será de 46 caracteres.
Para una colección de NFTs, la integridad del contenido que proporciona IPFS es fundamental. No hace falta confiar en que la imágen sea la verdadera: se puede verificar haciendo su hash, comparando con lo que dice la metadata y luego realizar el hash de la carpeta de los JSONs y compararlo con el hash/CID/baseURI que dice el contrato. Es un proceso trustless.
Veamos un ejemplo utilizando nuevamente al Bored Ape Yacht Club:
Usemos el mismo URI que habíamos visto antes: “ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/456”. En este caso, la parte que empieza con “QmeS” y termina con “aWtq” es el CID (o en otras palabras, el hash) de toda la carpeta en donde se encuentran los 10,000 JSONs. Veamos como se vé el CID en IPFS Desktop:

Con el número que se encuentra después del CID (es decir, el Token ID), uno le especifica a los nodos de IPFS que desea el archivo que se encuentra en el path “456” de esa misma carpeta. Ese archivo es la metadata del Bored Ape #456.
Con la imágen sucede lo mismo:
“ipfs/QmQGQ6xSWmrUHn6yBVofJzRUoxu5w6goCbMdutKHmCZ51s”. El CID comienza con “QmQG” y termina con “Z51s”. En este caso, el CID representa el hash de la imágen del Bored Ape #456.

IPFS tampoco es un sistema perfecto. Los nodos trabajan de forma colaborativa y no tienen una obligación intrínseca de mantener el contenido de forma persistente en el tiempo. Mediante un proceso llamado “Garbage collection”, cada nodo elimina el contenido en desuso. Sin embargo, hay distintas formas de sobrepasar este problema (no profundizaremos en ellas en este artículo): desde servicios de pinning, hasta integraciones con Filecoin. Más información en https://docs.ipfs.io.
Pero sí veamos qué pasaría en el peor escenario (muy poco probable): en caso de que hubiera una gran falla generalizada que afecte a IPFS, a las empresas que venden servicios de pinning, a Filecoin, y a cualquier otro participante en el sistema de storage descentralizado, tampoco sería extremadamente grave. Con que haya al menos una persona en el mundo que tenga guardada la carpeta con los 10,000 JSONs de forma local en su PC (o pendrive, hard disk, etc.), la metadata seguiría existiendo tal cual como se especifica el contrato on-chain. Si se hiciera el hash de esa carpeta, el output sería exactamente igual que el baseURI. Lo mismo sucede con las imágenes: que cada holder tenga su propia imágen (o que una sola persona tenga las 10,000 imágenes) es suficiente para que el sistema no caiga por completo. En cualquier momento se podría reconstruir IPFS (u otro sistema que trabaje con la misma función hash anterior) y todo seguiría funcionando a la perfección.
Trabajar con sistemas de storage descentralizado que utilicen Content addressing como metodología le otorga mucha resiliencia a una colección de NFTs en el largo plazo.
Como vimos, no todo es perfecto en la web 3. La mayoría de las implementaciones en las colecciones de NFTs dejan mucho que desear. Sin embargo, si el objetivo es crear una coleccón verdaderamente trustless, la conclusión es que sí se puede. Es solo cuestión de hacerlo bien.
