Read the full contract on Etherscan and check out the associated frontend: https://ReallyLongLine.XYZ
To create a fully on-chain NFT that expresses my artistic vision of the ReallyLongLine.
To incentivize the purchase, exchange, and continued ownership of a work of art that connects its participants in the larger vision of the ReallyLongLine.
Proof-of-concept a native Web3 digital advertising and social media technology.
The really long line is based off a sketch I made one night

Which inspired me make this a continuous paintings of the parts of this line that would look really cool in an art gallery all lined up together, so I painted.

Ever since I heard about the amazing world of NFT’s, I’ve been obsessed with the concept of art that is fully “on-chain”.
To me, this is the ultimate challenge, creating a work of art that will permanently be a part of the blockchain for as long as the chain exists.
The biggest challenge when dealing with on-chain NFT’s is the usually very high cost of gas associated with the minting them. The reason the associated gas fees are so high is because the typical ERC721 NFT contract extends the ERC721Enumerable and sometimes ERC721URIStorage interfaces, both of which allow for easier tracking of minted tokens, but can have potential downsides when dealing with larger blocks of data.
The ERC721Enumerable interface is used because it natively allows stores minted token id’s as a part of your smart contract which allows for tracking totalSupply information, but this can for the most part be very easily replaced with a custom totalSupply function
uint256 public nextTokenId = 0;
// nextTokenId += amount when minting
function totalSupply() public view returns (uint256) {
return nextTokenId;
}
The biggest challenge with minting an on-chain NFT is the in defining the tokenURI function. This function is what’s usually called by websites such as OpenSea to fetch metadata about a token, including its image reference. Typically, an NFT’s token URI points to an external IPFS endpoint which returns a JSON response with the token’s metadata
// Typical NFT tokenURI
tokenURI(18) => https://gateway.pinata.cloud/ipfs/QmdYT1tJTHrYx9Z1hwCkrWcX9dgekuMjEqaag64vn18CDr/18
but in the case of an on-chain NFT, the token URI is not as deterministic and is typically a much larger string to return
// On-chain NFT tokenUI
tokenURI(18) => data:application/json;base64,eyJuYW1lIjoiTGluZVNlZ21lbnQiLCAiZGVzY3JpcHRpb24iOiAiUGFydCBvZiB0aGUgUmVhbGx5IExvbmcgTGluZSIsICJhdHRyaWJ1dGVzIjogeyJjMSI6IigxNDYsMTA0LDIxMSwwLjczMCkiLCAiYzIiOiIoMTQ4LDE5NCw4LDAuODcyKSIsICJjMyI6Iig5MywxMSwyOCwxLjAwMCkiLCAidGV4dCI6ICIifSwgImltYWdlIjoiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhkcFpIUm9QU0kxTkRBaUlHaGxhV2RvZEQwaU5UUXdJajQ4Y21WamRDQm1hV3hzUFNKeVoySmhLREkxTlN3eU5UVXNNalUxTERFcElpQm9aV2xuYUhROUlqVTBNQ0lnZDJsa2RHZzlJalUwTUNJZ2VUMGlNQ0lnZUQwaU1DSXZQanh5WldOMElHWnBiR3c5SW5KblltRW9NVFEyTERFd05Dd3lNVEVzTUM0M016QXBJaUJvWldsbmFIUTlJakkzTUNJZ2QybGtkR2c5SWpVME1DSWdlVDBpTUNJZ2VEMGlNQ0l2UGp4eVpXTjBJR1pwYkd3OUluSm5ZbUVvTVRRNExERTVOQ3c0TERBdU9EY3lLU0lnYUdWcFoyaDBQU0l5TnpBaUlIZHBaSFJvUFNJMU5EQWlJSGs5SWpJM01DSWdlRDBpTUNJdlBqeHlaV04wSUdacGJHdzlJbkpuWW1Fb09UTXNNVEVzTWpnc01Ta2lJR2hsYVdkb2REMGlPREVpSUhkcFpIUm9QU0kxTkRBaUlIazlJakl5T1M0MUlpQjRQU0l3SWk4K1BDOXpkbWMrIn0=
The above string (starting with data:application/json;base64,) can be pasted into any web browser as a URL and be rendered as a json (which is what is base64 encoded within the string).
The generation of a base64 encoded string like this is usually non-deterministic, resulting in some of these on-chain NFT’s using the ERC721URIStorage class as a part of their project, choosing to generate the tokenURI upon minting and then storing that string as a part of their smart contract.
Doing so is in most cases completely unnecessary and a huge waste of gas!
One major point of note here is that it costs Ethereum in order to write data to the network, such as when minting a token. It does not, however, cost any gas to read from a contract.
As such, I decided to implement the ReallyLongLine in a way that almost all the actual processing of the image is done ONLY on reads, with very minimal contract interactions actually taking place during the mint itself.
function safeMint(string memory _text, uint numberOfTokensMax5) public payable {
require(msg.sender == tx.origin, "sender!=user");
require(MINT_ACTIVE, "Mint not active");
require(bytes(_text).length < 55, "Text too long!");
require(numberOfTokensMax5 < 6,"Max mint 5");
require((nextTokenId + numberOfTokensMax5) < MAX_SUPPLY,
"Exceeds max supply");
require(msg.value == (MINT_PRICE * numberOfTokensMax5),
"Not enough ETH sent");
for (uint i = 0; i < numberOfTokensMax5; i++) {
_mint(msg.sender, nextTokenId);
_textMessages[nextTokenId] = _text;
emit tokenChanged(nextTokenId);
nextTokenId += numberOfTokensMax5;
}
}
and then only when reading from the contract (calling the tokenURI() function), is the actual encoded URI generated, not upon minting but upon reading!
function tokenURI(uint256 tokenId) public view override returns (string memory) {
return formatTokenURI(tokenId);
}
function formatTokenURI(uint256 _tokenId)
internal view returns (string memory) {
string[4][3] memory colors = getColors(_tokenId);
string memory _imageURI = getImage(colors);
string memory _properties = getPropertiesPartial(colors);
string memory _text = _textMessages[_tokenId];
return string(
abi.encodePacked(
"data:application/json;base64,",
Base64.encode(
bytes(
abi.encodePacked(
'{"name":"', "LineSegment",
'", "description": "description"',
', "attributes": ', string(abi.encodePacked(_properties, ', "text": "', _text, '"}')),
', "image":"', _imageURI, '"}'
)
)
)
)
);
}
With this approach, I am able to save users an immense amount of gas during the on-chain NFT minting process, choosing instead to delegate the actual generation of their NFT images to happen only when reading from the contract.

