How to Proxy Into an NFT Minting DAO

Organize NFT collections from a minting DAO with small proxy contracts

Sean Lawless
Better Programming

--

A magnifying glass view of block component, with alternate interface.
Conceptual proxy view into an existing third-party smart contract. Photo courtesy of geralt of Pixabay.com.

The Immutable Ecosystem DAO (or Immutable DAO) is a small collection of smart contracts created by ImmutableSoft, an open public benefit 501(c)(3) nonprofit. Members who join this open DAO can secure their digital ownership and license end-user entitlements of their creations to users.

By recording their digital creation’s file checksum (SHA256) within an NFT minted on-chain, a timestamped and immutable transaction record signed by the digital creator’s wallet demonstrates the first provenance, a vital step in proving ownership.

Originally designed for securing public-facing file/software releases, forgery and fraud have attracted traditional NFT creators to our DAO. See Creator Provenance Within NFTs for more on this movement. This article continues the journey of our DAO, describing the details of how our members can deploy their own smart contract proxy to distinguish specific collections of their NFTs.

For NFT collections, the problem arises when viewing art NFTs minted with the Immutable DAO outside our dApp (i.e., NFT viewers and third-party exchanges). For historical reasons (Metadata standard) the name() the ERC721 smart contract function becomes the “collection” name. Every member of our DAO mints their own unique NFTs — membership ID is embedded in each minted tokenId (see Serializing Data Within Large Integers).

However, as all the NFTs of all DAO members are created by the same smart contract, they appear all in the same “collection” on third-party exchanges (OpenSea, Rarible, etc.). This can create a less than premium experience (to say the least) for high-end NFT art auctions, etc.

While it would be nice to add an optional tokenId parameter to ERC721 name() function, or a new function collectionName(tokenId) This likely won't happen anytime to support the emergence of NFT minting DAOs. As an immediate problem needing a solution, we approached the problem by allowing members of the Immutable DAO to deploy a small smart contract that overrides the collection “name” yet proxies all other smart contract ERC721 calls to the associated DAO smart contract (in this case CreatorToken).

Then third parties who view the “collection” through the proxy smart contract address will see the unique collection name and only NFTs specific to this collection. Other parts of the token implementation can also be overridden. This proxy could be the foundation for moving the NFTs out of the DAO completely if desired (a proxy with an escape clause).

Much has been discussed regarding proxies and upgradeable smart contracts; however, this type of proxy is less complex because the master data always resides on the DAO smart contracts, not the proxy. This means that, unlike upgradeable proxies, delegateCall() is not used.

Instead, initializing the proxy with the address of the DAO master contract (see CreatorToken on Polygonscan) allows every ERC721 token interface function to proxy the call into the Immutable DAO smart contract. This is a one-to-one relationship for the most part.

A one-time initialization is required to configure the proxy with the Immutable DAO smart contracts and the entity and product id (as defined in the DAO) to limit the proxy collection. However, the ERC721 and associated enumerable interface, such as totalSupply(), tokenByIndex(), balanceOf() and tokenOfOwnerByIndex() are not one-to-one translatable. These ERC721 standard functions must return only the subset of NFTs in the Immutable DAO corresponding to the specific creator’s product (i.e., this collection). To accomplish this for all four (4) core functions above, we created an internal function to support all variants, from counting the number of tokens to which index corresponds to which token.

This proxy smart contract is small and inexpensive to deploy. For extensibility, the proxy itself can be an upgradeable smart contract. This double proxy is preferred for NFT collections that may morph, change or otherwise be open to further development. The above DAO proxy for NFT collections would require initialization changes to inherit OpenZeppelin-based upgradeability.

The final goal of this proxy is to translate the token ID from the DAO to use only the release ID and not be serialized with the entity and product identifiers. Serialized DAO tokens are very large integers and not compatible or understood using NFT viewers or exchanges. Removing the serialization allows the NFT proxy token identifiers to start with 1 and go in chronological order as expected for a native ERC721 token. To do this we add a helper function idToDAO() and use it to translate from the proxy to each subsequent call to the DAO. The proxy NFT collection is now indistinguishable on 3rd party exchanges from a native token.

  /// @notice Convert tokenID from this proxy to ImmutableSoft DAO
/// @param tokenId The proxy identifier (only release id)
/// @return The CreatorToken (ImmutableSoft) token Id
function idToDAO(uint256 tokenId)
public view returns (uint256)
{
return ((_entity << commonInterface.EntityIdOffset()) &
commonInterface.EntityIdMask()) |
((_product << commonInterface.ProductIdOffset()) &
commonInterface.ProductIdMask()) |
((tokenId << commonInterface.ReleaseIdOffset()) &
commonInterface.ReleaseIdMask());
}
/// @notice Look up the release URI from the token Id
/// @param tokenId The unique token identifier
/// @return the file name and/or URI secured by this token
function tokenURI(uint256 tokenId) public view
returns (string memory)
{
return creatorToken.tokenURI(idToDAO(tokenId));
}
...

Using this proxy to override tokenURI() is also possible. This is helpful with legal contracts where the Immutable DAO NFT secures a private file (signed contract, etc.) with a PoE hash. Yet, the proxy smart contract returns a public NFT Metadata image of the certificate verifying the private file by a third party. See our other article Ricardian Contract Interoperability, for more information on this Contract Service Provider scenario.

Another important use case is game items where a tokenURI() can be overloaded to allow dynamic changing of the entire collection base URI for example. With an override in place for tokenURI(), a new setTokenBaseURI() function could be added for the proxy owner to move all the NFTs of their game to a new web2 domain with a single call, borrowing from the efficiency of the ERC1155 standard.

Through a base collection URI, the token id (or trailing filename from Immutable DAO private file) is added to the URI base and output by tokenURI(). The proxy owner is responsible for hosting the web2 URI endpoint that serves the JSON Metadata details used to display this NFT. Some projects may also wish to expose mint() in some way as well to enforce consistency in NFT creation and/or simplify the interface compared to the DAO.

To see the complete implementation, it can be found here, in our core GitHub repository. As the entity and product identifiers are defined at deployment, everyone can use the same smart contract without changes. We end with an example that shows a customer deploying their own proxy using the 10_deploy_collection_proxy.js deployment script.

Our example member has registered their entity name and created a product in the marketplace. The Developers option of the created product displays the unique and immutable entity and product numbers, respectively, the values two (2) and zero (0). See this picture from our dApp.

The DAO user discovers their entity and product identifiers on the Developers tab of the dApp.

First, we clone the repository and install dependencies with NPM using the command prompt (MS Windows in this example). This requires NPM, so be sure NPM is already installed on your development system or do so now before proceeding.

D:\>git clone https://github.com/ImmutableSoft/ImmutableEcosystem.git
Cloning into 'ImmutableEcosystem'...
remote: Enumerating objects: 803, done.
remote: Counting objects: 100% (85/85), done.
remote: Compressing objects: 100% (60/60), done.
Receiving objects: 98% (787/803), 10.53 MiB | 10.52 MiB/sremote: Total 803 (delta 44), reused 42 (delta 24), pack-reused 718
Receiving objects: 100% (803/803), 14.72 MiB | 10.65 MiB/s, done.
Resolving deltas: 100% (451/451), done.
D:\>cd ImmutableEcosystemD:\ImmutableEcosystem>npm install
...
D:\ImmutableEcosystem>truffle compile --all
...
D:\>

If there is an error on the last step, be sure truffle is installed globally for NPM (npm install -g truffle). Once the contracts are compiled, we open the deployment file 10_deploy_collection_proxy.js and change the initialization parameters (in bold below) to our unique entity and the product identifier of this collection.

In this case, the collection name is “Computer Systems,” the token symbol is “CSI,” and the Immutable DAO entity is two (2) and product zero (0) (from the picture above).

Once the deployment file is changed, the final step is to run only this deployment on the EVM network (Polygon Mainnet in this example). You will need to populate secrets.json with your wallet recovery words for the smart contract deployment transaction to be signed with the correct private key.

Be sure to delete the recovery words from this file after deployment — never commit them to a git repository.

Using a wallet with no high value of crypto (just gas) to deploy and manage smart contract ownership is recommended to avoid your dev system being a target for hackers.

D:\>ImmutableEcosystem>truffle migrate --f 10--to 10--compile-none --skip-dry-run --network polygon_mainnet

With the deployed proxy smart contract address output from above, browse your favorite third-party NFT exchange and see only this product’s NFTs under the new collection name. Note that deployment will require the proxy owner be the same wallet address registered with the Immutable DAO for that entity id. This will help secure the proxy ecosystem. See the source code link above to learn the details of how we do this.

In conclusion, there is no limit to the number of proxies a DAO member can deploy. Still, each must be for a unique product, and all must be products owned by the registered entity address for that product (and the name of DAO product must match the collection name!). Carpe partem for DAO minted NFT collections represented by a proxy smart contract!

Please leave a comment if you enjoyed this path through the wilds of the blockchain jungle.

--

--

Advocates using the Ethereum blockchain root of trust to facilitate the secure automation of digital software sales and distribution. https://immutablesoft.org