Cover photo

Nouns and Adding New Traits To Your Collection

On August 8th, 2022 (Noun’s first birthday), 13 new traits were added to the Nouns protocol: 8 heads, 3 accessories, and 2 glasses (https://nouns.wtf/vote/125). Let’s walk through the code that makes this possible.

post image

The NounsToken contract uses three separate smart contracts to retrieve a given Noun’s art, which all lives on-chain:

  • NounsDescriptor: constructs an image for each Noun using artwork stored in the NounsArt contract

  • NounsArt: stores approved color palettes for the collection and the image data for each Nouns trait

  • SVGRenderer: takes stored pixel data and returns a corresponding SVG string that can be rendered on a browser

Step-by-step now:

  1. On minting a new Noun, the NounsToken contract generates a unique seed (a pseudo-random collection of integers) that determines the NFT’s attributes

  2. To render this Noun, NounsToken calls the tokenURI function in the NounsDescriptor contract which returns the corresponding token’s data

  3. The NounsDescriptor retrieves the pixel data associated with a given seed from the NounsArt contract and passes it to the SVGRenderer to get an SVG image for the Noun

Okay cool, but how do I add a new trait to the collection?

Adding a trait to the collection means adding pixel data to the NounsArt contract. Let’s consider an example: how would you add a shark head PNG as a new Nouns trait?

baby shark doo doo doo doo doo.png
baby shark doo doo doo doo doo.png

Shark head journey

post image

RLE Encoding

First, the shark head image gets encoded using a technique called Run-Length-Encoding (RLE). This is done because on-chain data storage is expensive and encoding the image reduces the image’s size, and thus, the cost of storing it too. The trait image is encoded off-chain in a TypeScript class, PNGCollectionEncoder, which lives in the nouns-sdk.

A quick look at the code reveals that the bulk of the encoding logic lives in an Image class function, toRLE(), which takes in a collection of colors stored in NounsArt as a parameter.

post image

The toRLE function does three things:

  1. Calculates a boundary around the non-transparent part of a trait image

  2. Loops through the pixels within this boundary and stores their values in indexes, an array containing the trait’s color data

  3. Run-length encodes the data in indexes and returns it as a string of padded hex-encoded values

The GIF below illustrates how the program calculates this image boundary:

post image
RLE implementation per part C
RLE implementation per part C

The output of toRLE is then encoded further as a bytes array before being officially added to the Nouns protocol.

To push the trait on-chain, the Nouns DAO must vote to execute an Ethereum transaction that calls the appropriate addTraits function in the NounsDescriptor contract (i.e. addHeads, addGlasses, etc) and passes the encoded image as a parameter.

This function then calls addPage which uses SSTORE2 (a solidity library for reading and writing data to persistent storage on-chain) to store the encoded image in the NounsArt contract.

The descriptor contract calls addHeads in NounsArt and passes in trait image data
The descriptor contract calls addHeads in NounsArt and passes in trait image data
The addHeads function passes an encoded head trait to the
The addHeads function passes an encoded head trait to the

And… voila! A new shark head can now be assigned to a Noun as a trait.

Wrapping it up

If you want to learn more about Nouns and their plans to add new traits to the collection, check out #noundry in their discord.

I wrote this walkthrough to highlight the affordances that come with storing data on-chain and to help others make sense of the Nouns smart contracts. If you’re working on a project with mechanics for showcasing collective group identities, I would love to chat :)

Huge shoutout to Julian for edits and thoughts on the walkthrough.