Sitemap

Advice on Creating On-Chain Generated NFT Art

5 min readApr 24, 2023

--

Press enter or click to view image in full size
(not full snippet, obviously. check out my project: https://github.com/rubydusa/proof-of-life)

When I tried to dive in On-Chain Generated Art with no prior experience I made plentiful crucial mistakes, and as a result I’ve wasted precious time I could have saved if only I’ve known better from the start

Hopefully this will serve as a helpful guide for anyone wanting to experiment with on-chain generated art. This article focuses specifically on EVM-based chains, however, some ideas are more general and may apply to other blockchains as well

Introduction

Why generate the art on-chain at all? There are three main reasons:

  1. Dynamic Content — Unlike static images and videos, generated art can read data from the blockchain and shape itself accordingly
  2. Censorship Resilience — Centralized hosting of media is unreliable in the light of censorship and goes against the spirit of decentralization. IPFS hosting and other decentralized hosting methods are also partially prone to censorship, and also are unreliable if there are only a few nodes storing the media
  3. Composability — If done correctly, on-chain generated art can be composed with one another

However, blockchains are usually not designed in mind to handle generative art. One of the biggest factors is the gas cost of deploying on-chain art, so if you are planning on deploying on a chain like Ethereum, expect to pay hundreds of dollars in gas costs.

The main concept of how to do generative NFT art lies in Data URLs — Data URLs allow you to pass images and other forms of media in the URL itself. NFT Metadata Standards support the usage of data URLs in token metadata so your NFT tokens will render on OpenSea and other major NFT marketplaces.

Helpful Sources

Opensea Metadata Standards — Opensea’s Metadata Standard. You should also refer to the official EIP-721 metadata specification

Data URLs — The official Mozilla docs on data URLs and how they work

Relic Protocol — Fully On-Chain NFTs (Relic Protocol)— This is a great article by Relic Protocol that includes optimization tips for storing images on-chain and insight to the benefits of on-chain art

On-Chain generative 3D roller coaster (xtremetom) — Cool twitter thread with great tips and tools for on-chain generative art

Scripty.sol (@0xthedude and xtremetom) — Gas-efficient HTML builder that can combine multiple JS and a data storage solution that allows on-chain composable generative art (description copied from repo)

fully-on-chain.sol (@nounsfes) — A collection of solidity libraries to generate SVGs on-chain

The Formats You Can Use

The two main formats of media you can use are HTML and SVG. Both are tag languages which makes it easy to create HTML and SVG files programatically.

These two formats are not exclusive — you can embed HTML into SVG files using foreignObject (always make sure to check support for intended platforms) and SVG elements work directly inside an HTML file.

SVGs are slightly easier to compose with themselves than HTMLs since laying out elements is more straightforward and HTML based art may not rely on elements at all and use exclusively the canvas element.

Get Rubydusa’s stories in your inbox

Join Medium for free to get updates from this writer.

HTML is by far more expressive and gives you far greater artistic freedom:

  • Interactivity is easy to achieve with Javascript compared to CSS
  • You can use existing Javascript graphics libraries
  • Custom art generation logic by utilizing the Canvas API

However, if your content is generative but not animated or interactive it may be preferable to use SVGs for the sake of simplicity, and also because rendering batches of SVGs is more efficient than of HTMLs

Gas Usage In View Functions Still Matters!

You may think that because on-chain art relies on view functions it doesn’t matter how much gas do you use to generate your art. Well, it does.

Many RPC providers will block view calls that exceed the gas limit and for a good reason: High gas usage in a view call may indicate a high amount of reads to the state of blockchain — which is expensive. Even if the executed bytecode is mostly arithmetic, an RPC server has no way to know.

With that said, don’t optimize prematurely. Start by writing your code as you normally would, but beware the possibility of getting blocked.

The main gas pitfalls in on-chain generative art have to do with string manipulation, and it’s really easy to accidentally write inefficient code because Solidity was not designed in mind for cheap string manipulation.

Here is a comparison between three functions whose purpose is to produce a string that represents a rectangle element with data inside of it:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/Strings.sol";

contract Test {
using Strings for uint256;

bytes constant START_STRING = "<rect>";
bytes constant END_STRING = "</rect>";

// 3744 gas
function svg0(uint256 data) external pure returns (bytes memory) {
return bytes.concat(
START_STRING,
bytes(data.toString()),
END_STRING
);
}

// 3748 gas
function svg1(uint256 data) external pure returns (bytes memory) {
bytes memory start = "<rect>";
bytes memory end = "</rect>";

return bytes.concat(
start,
bytes(data.toString()),
end
);
}

bytes6 constant START = "<rect>";
bytes7 constant END = "</rect>";

// 3220 gas
function svg2(uint256 data) external pure returns (bytes memory) {
return bytes.concat(
START,
bytes(data.toString()),
END
);
}
}

Slightly more than 500 gas units saved and all these functions do is wrap a number around a rect tag.

The difference is that the first two functions load the strings into memory and then create a copy of each. Because in the last function a string variable is never declared, the string data is put in its destination directly, without making a copy.

Another tip is that for large constant strings, you should look into saving the string on-chain as the bytecode of a contract and use EXTCODECOPY to retrieve it (Make sure the first byte in your contract’s source code is 0 to ensure it cannot be ran and accidentally be self-destructed)

In general, if you end up having issues with gas you should research more in depth how to optimize around string manipulation. There’s also a chance you’ll never have to deal with optimization.

Use URL character encoding!

Some characters that are often used in HTML and SVG files (such as <, >, “) are invalid URL characters.

A way to deal with this is to encode the files in base64, but the problem is that for large files they significantly bring up the gas cost.

More so, in certain cases you might need to do base64 conversion multiple times (e.g. if you want the function to return JSON metadata, you’ll need the convert once the SVG/HTML itself to base64 encoding, and then the whole JSON to base64 encoding)

The solution is to use URL encoding, which is a way to encode special characters inside a URL. That way, all strings are valid and composable with each other easily.

Rendering The Art In A Website

This is probably obvious to people familiar with front-end development, but the ideal way to render the art in a website is to use an iframe tag.

The reason I’m mentioning this at all is because in my personal project I used plain SVG generation, and I tried various ways to render the art on the front-end and all of them caused issues.

--

--