Why Art Blocks Uses JavaScript in Its Smart Contract | by Nazar Ilamanov | Apr, 2022

Art Blocks is a platform for creating on-chain generative NFTs. But do you know what’s actually kept on-chain vs off-chain? And why do they need JavaScript in their smart contract?

We will find out by breaking down the smart contract of Art Blocks. We will also learn about how images are generated/rendered and where Art Blocks gets the randomness needed to generate them.

Here is the outline of this article

  • Background on ERC-721 — the NFT standard
  • Art Blocks contract source code
  • Generating the art

First, a little background on Art Blocks.

Art Blocks is a platform (really just a smart contract) where you can create generative NFTs. Artists submit scripts that can generate images. Art Blocks stores these scripts and when someone wants to mint an NFT, it creates a unique hash. This hash is used as a seed to the image generation algorithm and the generated image will be unique to the minter.

Here are some examples of generated images:

Popular Art Blocks collections: Ringers, Chromie Squiggle, Fidenza.

In order to understand the Art Blocks smart contract, we first need to learn about ERC-721. ERC-721 is a standard used for implementing NFT smart contracts. In order to be considered ERC-721 compliant, a contract needs to implement these functions:

  • name and symbol are NFT descriptors. For example, for Art Blocks, they are “Art Blocks” and “BLOCKS”.
  • tokenUri – path to token metadata (image url, rarity attributes, etc)
  • totalSupply – count NFTs tracked by this contract
  • tokenByIndex – return tokenId of token at specified index. index is [0, totalSupply).
  • tokenOfOwnerByIndex – enumerate tokens of owner and return tokenId at index
  • balanceOf – number of NFTs owner has
  • ownerOf – owner of specified token
  • approve – allow someone else to manage (transfer, sell, etc) one’s token. Used by 3rd parties, like OpenSea, to manage tokens. (There is a similar function setApprovalForAll(address _operator, bool _approved) which is like approve but gives permission for all tokens rather than just one. Skipped for brevity)
  • transferFrom – transfer the token. The caller needs to be a pre-approved address.

All NFT smart contracts need to implement the ERC-721 standard. This allows third parties like OpenSea to interact with the NFT contracts in a standardized way (all contracts will have the same ownerOf function, for example). Check out my article on BoredApeYachtClub smart contract breakdown to learn more about the ERC-721 standard.

Let’s now learn about how Art Blocks implements this standard and creates generative NFTs.

The blockchain backend of Art Blocks consists of just one big smart contract called GenArt721Core.sol. This smart contract is broken down into 2 pieces:

  1. a contract implementing the ERC-721 standard
  2. the main contract GenArt721Core.sol responsible for storing the data needed to render NFTs

GenArt721Core.sol inherits from the ERC-721 contract. The source code can be found on Etherscan and Github.

Art Blocks also has 2 more lightweight contracts: GenArt721Minter (mints tokens and accepts payments) and Randomizer (generates pseudo-random numbers). But these won’t be covered in this article.

ERC-721 implementation

Art Blocks implements the ERC-721 interface using an off-the-shelf implementation by OpenZeppelin. OpenZeppelin is a library of implementations of the most common standards.

The implementation has no surprises. Everything you would expect from a standard implementation:

  • They use mappings to manage ownership of tokens:
  • Here is how ownership is transferred:
  • and how approvals are managed:
  • Although, not part of the ERC-721 standard, OpenZeppelin’s ERC-721 implementation includes mint and burn functions:
  • The implementation has a few more mappings to store additional information (the setter/getter functions for these mappings will be omitted for brevity):
  • Finally, here are the rest of the ERC-721 functions:
  • The one leftover function from the ERC-721 spec, tokenUri, will be explained later in the article.

The man contract extends the ERC-721 contract to add functionality specific to Art Blocks: “storing project info” and “generating NFTs”. Let’s start with the storing project info part.

Storing project info

Each NFT collection is considered to be a separate project (like Chromie Squiggle, Ringers, etc). The main contract defines a data structure for a Project:

NFTs for all projects are stored in one big smart contract — we don’t create a new contract for each collection. All the projects are stored in one big mapping, called projects, where the key is just the index of the project (0,1,2,…):

As you may have noticed from the above screenshot, the contract uses a few more data structures to keep track of everything:

Let me explain the last 4 lines:

  • tokenId is the ID of an NFT and projectId is the ID of the project. The contract keeps track of the 2-way mapping between the two.
  • hash is the keccak256 hash value of the combination of [1) index of NFT, 2) block number, 3) block hash of prev. block, 4) address of the minter, 5) random value from a randomizer contract]. We will get to the randomizer contract in a bit. The hash value is computed during the mint function:

The project parameters can be changed by artists via a bunch of setters such as these:

But once the project is locked, many variables can never be changed.

That’s it for the “storing project info” functionality. Let’s move on to the next functionality implemented by the GenArt721Core.sol contract.

The entry point for generating the art is the tokenUri function. It’s one of the functions in the ERC-721 standard and is supposed to return the metadata (like images or attributes) of the NFT. Here is the implementation of tokenUri:

It has many if conditions, but it’s essentially just constructing the metadata path conditionally. Projects have the option of storing the metadata on IPFS (as an image or a JSON file) or, if the project is dynamic, the metadata can be served from a traditional HTTP API. Most of the projects are dynamic so we will focus on that case.

For example, the Fidenza collection (projectId=78) has the following metadata path:

You can get this information from Etherscan. Just scroll down to “tokenURI”. If we navigate to this HTTP path, we get this JSON file:

Notice that the JSON file has a bunch of different information for trait types and project descriptions. It also has a link to the actual image:

So, what do you really own when you buy an NFT? In this case, you just own the tokenId. tokenUri function then maps the tokenId to either IPFS or HTTP link depending on the project settings. This link either points to the image directly or to a JSON that has attributes and a nested link to the image.

But how is the image generated/rendered? Unfortunately, the image is not generated on-chain. The smart contract only stores a JavaScript script needed to render the image. Art Blocks’ frontend then queries this script and generates the image on-demand in its traditional backend, not the blockchain backend.

Why is the image not generated/rendered on-chain? It’s because the scripts have library dependencies. The scripts depend on common JavaScript libraries such as p5.js and processing which are commonly used by designers to create generative images. It would be very expensive to put these dependency libraries on-chain and that’s why images are generated off-chain.

The instructions to render images (the rendering scripts) are stored on-chain though. You can check the stored scripts for yourself by navigating to projectScriptInfo on Etherscan. This will show you what library dependency the project script needs and how many scripts it has (if script is too long, it will be broken down into many pieces):

The actual scripts are in projectScriptByIndex:

The scripts are stored as plain strings in the Project data structure:

You might wonder how the random patterns in the NFT collections are generated. When generating the images, the frontend does not pull just the scripts from the smart contract. It also pulls the hash string. Remember the hash string?

This hash can be read off from the contract from the tokenIdToHash mapping. The hash string is used as the input/seed during the image generation process. The hash string controls the parameters of the image (for example, how squiggly the Chromie Squiggle becomes).

Lots of information is combined to produce the hash. One of the inputs is the address of the minter. This way, the minter participates in the image generation process and the NFT becomes unique to the minter. (If someone else were to mint the same token under the same exact conditions, he would get a different image because his address would be different).

Another input to the hash is the returnValue from a randomizerContract. It appears that this contract is not open-source (not verified on Etherscan) so we can’t see its code. But it’s most likely a pseudo-random number generator that generates random numbers on-chain from sources such as the block number of the last mint.

Leave a Comment