Unique NFT ASA Implementation

I am looking to create a game on Algorand. Here is how I see it.

Participants can “redeem” prime numbers as NFTs. They can exchange it between each other freely, and I am planning to create a marketplace app where all prime numbers would be listed, and users could bid/ask on them.

The only problem I have is that there is no straightforward way to ensure NFTs uniqueness. It would be nice to have an extra parameter UniqueID that the user can set during NFT creation, and the network would enforce it.

I am thinking about how to work around that limitation, but nothing solid comes to my mind. Here are the weak ones:

  1. I can create a backend endpoint that a user can call. The endpoint checks uniqueness in DB and then creates an asset and passes it to the user.
    An obvious drawback is that it is centralised and could potentially censorship users.

  2. It is all about the marketplace app. It could list just the ones that were created first. I don’t like it either. It requires to have DB that tracks all the NFTs. Plus, there is an edge case when you make 2 ASAs in a single block.

  3. I was thinking about a trick with ManagerAddr that has to be set to a specific contract address that would allow any other user to destroy the ASA if it is not unique. This seems fun and would even potentially add more gamification to the app, which is good. The bad thing is that it is probably not possible to implement. There is no way to store all created ASAs in some hashmap or hashmap like entity in the blockchain.

I would appreciate any feedback. Thank you.

1 Like

That looks interesting.

First two remarks:

  • I don’t think you will be able to check primality on-chain. But I guess this is not a real issue as you can do it offchain.
  • Block proposers can “steal” prime numbers: if they see the creation of an NFT for a prime number, they can just not include the transaction and create it themselves.

Let’s first ignore this second issue.
You can ensure uniqueness of NFT by issuing the NFT from a smart contract account where the prime number p is hardcoded.

Concretely, you first create an application App (stateful TEAL) whose role will be described later.
Let C_p be the smart contract account for the stateless TEAL script that:

  • have p hardcoded as a constant
  • require any transaction from C_p to be in a group of transactions with a call to App

Note that the address C_p can be computed from a smart contract application. See Bond Implementation - #7 by fabrice

Now, to create an NFT for the prime p, Alice would send the following group of transactions:

  • Alice sends 1 Algo to C_p
  • C_p opts-in to App - App will check that C_p was not already opt-in. If not, it will store in the local storage of C_p two variables: p = p and creator=Alice
  • C_p creates the NFT

Then Alice opts in to the NFT.

Finally, Alice send the following group of transactions:

  • C_p calls App. App check that p and creator are set, and erase creator. It also checks the second transaction is an NFT transfer from C_p to creator.
  • C_p sends the NFT to Alice

Now to solve the issue that a block proposer can steal the prime number, what you can do is add a registration phase to the App

To register p, Alice opts in to App providing a hash value h = SHA256(Alice || r || p), where Alice is Alice’s address and r is a 32-byte random number chosen by Alice, and || is the string concatenation.
The App stores in Alice’s local storage the round round of the App call and h.

Then to create an NFT for p as above, Alice needs also to provide r and have in its local storage the above h. Furthermore, the App restricts the creation of an NFT for a registration h to happen after round round + 10,000.

Now, if a block proposer try to steal p, it will first see h. h does not reveal anything about p, so the block proposer cannot do anything meaningful about it.
Then, 10,000 rounds later, Alice will reveal r and p to allow the creation of the NFT for p.
To be able to steal p, the block proposer would then need to re-register p using h' = SHA256(r' || BadBlockProposer || p) and wait 10,000 rounds! This means that the block proposer needs to prevent the transaction from Alice to be committed for 10,000 rounds. This is virtually impossible (except if the bad block proposer has virtually all the Algos in the blockchain).

4 Likes

Great idea. Thank you. Let me try to build something around that concept.