[ Click here for the original Japanese version of the article / 日本語版はこちら ]
I have created a smart contract on the Flow blockchain that allows for a snapshot of the list of NFTs owned by a particular address.
Once a snapshot is created, it becomes easy to prove ownership even later on. This accuracy is guaranteed by the smart contract's code.
I have also developed a feature to display the snapshot. This is extendable by anyone.
Let's think about the importance of ownership history accessibility.
One thing that is lacking in every NFT standard is the inability to access past ownership history from within the contract. The primary reason for not providing this feature is likely due to storage capacity. In the past, an Ethereum ERC-20 contract was created that recorded all transfer histories. However, this storage capacity became vast and became an issue within the Ethereum network.
On the other hand, directly retrieving past transaction information from nodes becomes more challenging as the number of blockchain users increases. For instance, in Ethereum, transaction data from over a year ago is no longer retained by nodes (EIP-4444). In Flow, when a periodic update called a "spoke" occurs, past transaction data is only retained in special archive-like nodes.
Considering the composability of smart contracts, it is crucial to be able to retrieve past ownership history on-chain. If you can verify the NFT ownership history within the contract, it becomes easier to enhance the trustworthiness of NFT transactions or offer services based on past histories. There are also secondary benefits. For example, you can keep a cherished NFT that you've let go of in another form forever. Also, it becomes easier to identify the identity of an NFT if you know what NFTs were together in the same address at a certain point. Think of the ownership history snapshot as something akin to a family photo.
My idea is to create a standard or reference smart contract that can record a snapshot of which address owned which NFT at runtime. I wanted to show an example of how such open snapshot information can be utilized.
Behind this idea is a hypothesis about the composability of smart contracts. Composability has been touted as important for years, but I haven't seen many great ideas that are fully on-chain. Most go through off-chain, which diminishes the benefits of Web3 services. Perhaps the inability to complete on-chain is due to a lack of NFT identity information accessible from smart contracts?
NFT project communities hope their NFTs are used in various use cases. I recently attended a Doodles meetup in Japan and spoke with holders. They wished to see NFT illustrations and merchandise in many places. However, I didn't hear many specific utilization ideas. I suspect this is because there is still a lack of information on-chain. This is just my speculation.
One reason I wanted to create this snapshot idea on Flow is that Flow's smart contracts are designed to easily access an unspecified number of NFTs. Flow has a common NFT interface mechanism, and you can succinctly check whether it conforms to the standard specification. Also, since NFTs are stored within each account's storage, you can retrieve a complete list without omission.
I struggled with how to store NFT ownership information in what data structure.
Flow's NFT standard has been updated several times, and some older NFT contracts couldn't keep up with the latest specifications. Therefore, I couldn't retrieve all NFT information in a common way. For example, accounts from the early days of NBA Top Shot didn't expose the NonFungibleToken.CollectionPublic
interface to the Collection, so I had to refer to the TopShot.MomentCollectionPublic
interface. This complicates the code.
I also explored whether I could dynamically add processing for specific types, but I couldn't use variables in type-specified places <T>
like fun getCapability<T>(_ at: CapabilityPath)
, making code simplification challenging. I managed to add logic using struct interfaces, but it might not be the best solution.
I really like the intuitive ownership representation due to resource oriented programming, something not yet realized in most smart contract languages.
I also admire the developers of the Cadence language for their fearless pursuit of the ideal form, even if it means drastic changes. I've experienced several breaking changes so far, but I believe each was a good decision. With its simple and clear syntax and practical features like UTF-8 support, the Cadence language is much more approachable than the Move language.
Additionally, this might be more about the Flow protocol than Cadence, but I really like that the source code string is included directly in the transaction. I believe program code is art, so I feel this specification is crucial for all code artists.
The most crucial resource within the Snapshot
contract is Snap
. The Snap
resource represents a single snapshot unit, containing the timestamp when the snapshot was created, the target address, the type of logic structure used for retrieval, and multiple NFT information (NFTInfo
structure) owned by the address at that point. The NFTInfo
includes collection information, type, ID, and metadata for each NFT. The created Snap
resources are stored in another resource called Album
. The Album
resource offers the following three functions:
Create a snapshot
Obtain proof of ownership
Display the snapshot
When creating a snapshot, you need to pass logic as an argument. This logic should be of a type that implements the following struct interface:
pub struct interface ILogic {
pub fun getOwnedNFTs(address: Address): {String: {UInt64: NFTInfo}}
}
I implemented the basic processing logic type in the SnapshotLogic
contract. If someone wants to customize the process, they can freely implement it. However, to actually use the logic type, it needs to be added to the allowed list by the administrator of the Snapshot
contract. This is to prevent malicious actions like forging NFT information.
The state of account storage after retrieving the snapshot is as follows:
Here, this account specifies its own address to store NFT information, such as TopShot and Doodles, within the Album as a Snap resource. However, you can also specify any address other than your own.
The information of the created Snap
resource can be output in any format, such as SVG images or HTML, by passing the following viewer type. Anyone can implement and use this.
pub struct interface IViewer {
pub fun getView(snap: &Snap): AnyStruct
}
This time, I implemented a viewer that renders in HTML format in the SnapshotViewer
contract. This implementation embeds the metadata of the NFTs contained in the snapshot into a simple web app in HTML. When I tried to render a snapshot of the NFT information I own, the following HTML is displayed.
The code for the smart contract, transactions, and scripts from this time is stored in the following repository.
The smart contract has already been deployed to the mainnet.
Being able to visually display a list of owned NFTs is more fun and memorable than I thought. NFTs are supposed to be a technology where you can trace the history, even if you let go of them. However, in reality, it's challenging to verify old transaction information on-chain. I feel that building an ownership history using this contract is essential in the age of composability. I encourage everyone to record their ownership history. I hope this contract serves as a catalyst to improve the accessibility of ownership history.
Zeroichi Arakawa