Many projects often use blindbox/reveal strategy to sell their NFT. That is great, making NFT sales more exciting. But there are some doubt about this strategy:

  1. How do I know this process is fair? The resources for each token_id are uploaded either by api or by IPFS gateway. The devs(deveployers) can decide which tokens are rare, which are normal. There is no guarantee that devs won’t keep rare ones in their pockets.
  2. How can I know those images/gifs/videos are actually created when the blindboxes are open for sale? Maybe the devs are raising money by selling blindboxes to pay the artists. Even worse, there won’t be revelation, devs can take money and run away.

There is an alternative approach to make it more transparent. The main idea is to remove blindboxes. If a project shows all resources with corresponding token_ids, the second issue is closed. When someone executes the mint function, he or she will get a random token_id generated by randomizer on blockchain and there is no room for chatting. Thus the first issue is closed.

Let’s implement this function.

First, we pick a range, let’s say our project has 8888 unique token_ids. We want the randomizer to generate one token_id from 1 to 8888 each time, no repeat. Building an array [1,2,3…8888] and pop elements randomly seems fine. But to write this array in contract storage will be a huge waste of gas.

So, instead of building a huge array, we use a query function to mimic array indexing. At first the function is just query(x) => x. Next, somebody mint token_id 5000 away, leaving:

[1,2,3...4999,5001...8888]

To avoid moving a lot of elements, we should copy the last one(currently 8888) to the empty slot of 5000:

[1,2,3...4999,8888,5001...8888]

Then we shrink the array size by 1:

[1,2,3...4999,8888,5001...8887]

Now the query function should behave like this:

query(x) => x (1<=x<=8887 and x!=5000) query(5000) => 8888

We can track the altered position value using a mapping structure.

As for the random element picking part, that’s easy: just hash some seeds and do modular arithmetic over the current array length.

Full implementation code:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.6;

abstract contract RandomPickInRange {
    
    struct RandomPickArray {
        uint256 length; 
        uint256 shifted;
        mapping(uint256=>uint256) altered;
    }

    function _getPosition(RandomPickArray storage _array, uint256 _position) internal view returns(uint256){
        require(_position <= _array.length, "index out of range");
        return _array.altered[_position] == 0? _position : _array.altered[_position];
    }

    function _randomPick(RandomPickArray storage _array) internal returns (uint256){
        require(_array.length >0 , "array exhausted");
        uint256 rand = uint256(keccak256(abi.encodePacked(_array.length, msg.sender, block.difficulty, block.timestamp)));
        uint256 position = (rand % _array.length) + 1;
        uint256 choosen = _getPosition(_array, position);
        _array.altered[position] = _getPosition(_array, _array.length);
        if(_getPosition(_array, _array.length) != _array.length){
            delete _array.altered[_array.length];
        }
        _array.length -= 1;
        return choosen + _array.shifted;
    }
}

If you want to try this approach. You should know the risk of scam projects for your resources are available to the public.