Minimalistic on-chain cross-game NFT attributes

A proposal to extend the ERC721 standard with a struct and a few functions to allow players, for example, games, to use NFTs storing data on the token itself.

I abandoned this proposal, see below

Extended info and implementation at

The reason why ERC721 metadata are off-chain makes perfect sense in general, in particular for immutable collectibles. Still, it does not allow pure on-chain games to interact with the NFT because they cannot access the metadata. Working on Syn City and Everdragons2 Origins, I realized that we need to track a minimal set of data on-chain to allow the games evolving in a future fully-decentralized direction.

Having data on-chain also has implications for the value of an NFT.
Imagine that there are two NFTs with the same rarity. Now, imagine that the two NFTs are used in a game. If one of the two gets outstanding attributes, whoever buys the NFT will also purchase levels in the game where the NFT played. So, the value on the market of the “good” NFT will be higher than the value of the NFT who is a bad player.

This proposal suggests a simple Struct containing 32 uint8 values that can be used for anything that makes sense to be on-chain for a game.
A peculiar characteristic is that only the NFT’s owner can create a record for a game, but later, only the game can modify the data.

I will implement it in Origins and Syn City, and every feedback is appreciated. Thanks so much

1 Like

I think having an NFT token format standard for games is a neat idea as a concept.

I do not think you should be enforcing as much as you are - like a version. The version could just be an attribute described in the external JSON format if it is needed. Could be byte 0 inside the a uint256/bytes32.
Perhaps attributes can be packed outside of Solidity, described in the JSON format, into a single uint256 or bytes32 in alignment with EVM word size.

Not sure why setting or getting attributes needs more than the token ID - like a player id.

ERC721 tokens usually have a map of token ID to Address, given an ID you can find the owner.

Also, ERC721 implements a tokenURI method. You might want a method dedicated to replying back with the JSON URL to describe your attributes, similar methodology?

Thanks for your comment.

There is no need for a new standard if we put data in the metadata.json since, despite the fields required by OpenSea, Enjin, etc., in that JSON file, you can already put whatever you like.
But that would not solve the issue we are trying to solve, because a purely decentralized game cannot access off-chain files. That is why we propose a uint8[31] array that can be managed with maximum freedom by games.

The version is unnecessary, and it could just be the first element of the uint8[]. Still, it is here because if a marketplace needs to understand what every field represents, they must find the information somewhere.
The idea is that any player/game could expose an API that describes what every field is. And since that can change, specifying the version of the data can be helpful. Thus said, I do not have a strong opinion about it. In Syn City, it makes sense to have info about the data version; maybe it does not make sense in other games.

About packing the info in a single bytes32 or uin256, yes, that is possible. But using a struct with an array of uint8 (or bytes1) offers the advantage that you can modify a single element of the array, spending around 1000 gas. While updating a bytes32 would cost more that 20,000 gas.

An array of bytes1 actually gets padded with blank space in EVM and cosumes a lot more gas than bytes32

Correct me if I am wrong but updating one element will consume a full 32 bytes of gas when using storage due to EVM word size.

The version thing seems use case specific. If you needed it, and another project didn’t, whatever API is used to provide definitions of properties could pull the version from your array. Gas is so expensive on L1 and every byte is precious, that’s why I am bringing it up. Maybe another project doesn’t maintain rolling versions for NFTs on layer 2, maybe they do rolling upgrades on them.

Gotcha - for completely on chain games. What about a contract with a view function that returns metadata about properties, and setting the property definition contract on the game NFT?

I am interested in this just because I am also looking at making a game that uses NFTs. If everyone starts following a convention then interopability becomes possible.

Since the array is in the struct, it will update only the specified value.
I tested it, and if you change only a single uint8, you spent a bit more that 1000 gas.
Of course, there is the cost of the standard transaction, but if you update many parameters at the same time, you end up spending much less than updating a single bytes32.

The proposal talks about an info contract that explains what any property is in the chapter about integration with NFT marketplaces but that part is not defined yet, because it makes sense only after than this first step is supported by the community.

NOTICE: this proposal has been abandoned after trying to use it in a couple of projects because in almost every scenario uint8 values are not enough for what it is needed and when working with conversions, the cost to save the changes becomes higher because of the calculation compared with using custom struct in the contract. This kills the advantage coming for using a common structure amongst games.

Your use case may be answered partially by EIP-5185: NFT Updatable Metadata Extension if you want to have a look.
In this case you would be able to store offchain lot’s of variable data that would be shared between chains.

I know that proposal. The problem there is that a contract cannot access the attributes, which was the primary goal of my proposal.

BTW, I am working on a different format — GitHub - ndujaLabs/attributable: A proposal for standard attributes on chain — which is not incompatible with EIP-5185, since that works off-chain, while my proposal focuses on on-chain data.