This drop is the first of its kind because of the technologies involved. And putting it all together in under 4 days is a personal feat of mine :sunglasses:
I would like to go into more detail about each part of this process to show some of the technical features and hopefully encourage others to mint their own NFTs using this method.
Chaink VRF | A deterministic, fair raffle
We took a snapshot of all Boonji tokens and their OpenSea listings on the night of December 13th. We chose 33 random accounts from these listings. As long as the account didn’t have their token listed or it was listed for at least 3.3 eth, they qualified for the drop.
The goal was to find a process that was non-biased, cheap, and simple enough that anyone could replicate it. For entropy in Solidity, developers can turn to Chainlink VRF(Verifiable Random Function). The VRF contract generates the random number and a cryptographic proof of how that number was determined. The request/callback nature of this procedure means that the process can be easily replicated.
We can use ethers.js to query the blockchain for the log that the ChainlinkVRF contract emits after completing a request. Each event object contains the random number generated, which we can use.
Storj | Decentralized storage of assets and metadata
The recommended protocol for storing token metadata is IPFS, though you would still need to use a pinning service like Pinata.
At Jupiter Group, we use Storj to host and serve our content. We integrated it successfully for the Boonji drop, and so we’re using it for future projects. It’s secure, highly available, and open source – everything we need for a web3 project.
Upload content using the uplink CLI and share it via a bash script.
uplink cp ./scripts/storj/assets/1.png sj://boonji-joa/assets/1.png
uplink share sj://boonji-joa/assets --url --not-after=none
Naturally, we want to automate as much of the process as possible. So the steps required to prep the NFT metadata are:
- have all NFT assets in a local directory
- rename the files to be deterministic based on
tokenId
- upload and get the public read-access URL
- prepare all token metadata JSON files with the apppropriate values for
name
,image
, etc - upload all metadata JSON files and get the public read-access URL
The only thing that the ERC721 contract needs in order to work is the baseURI, which will resolve to a link like https://link.us1.storjshare.io/raw/. Providing the /raw/ flag will automatically download the file from the browser.
zkSync | Minting NFTs
The project will mint tokens on a zk-rollup and enable withdrawals to L1. The team respects the values behind zkSync and the team at Matter Labs. Their developer resources are top notch. The team was able to put the entire project together in under 4 days without even looking at their docs prior.
There’s many layers to understanding zk-rollups, the UX/transactions, etc. If you want a deep dive, feel free to check out their Medium page. For our purposes, we’re just highlight the features of zkSync
and move on to the steps to minting:
- extremely low transaction fees, as long as you stay on l2 🙂
- funds are cryptographically secure
- users are always in control of their funds
The resources used were their guide for NFTs, Javascript SDK docs, and environment for deployed contracts.
There are a few things that need to be done before we can mint on zkSync.
- instantiate an ethers wallet to sign transactions on
zkSync
- activate our account
- guarantee that our tokens can be withdrawn to L1
- mint tokens on behalf of other accounts
Step three is in bold because it’s the most important one – any tokens we mint using the zkSync
SDK only live on the L2, and withdrawal to L1 must be supported. From their docs, there’s 3 components to withdrawals to L1:
- Factory: L1 contract that can mint L1 NFT tokens
- Creator: user which mints NFT on L2
- NFTOwner: user which owns NFT on L2
Withdrawals happen when you trade a token on the L2 network for one on the L1 network. This is done by destroying the token on L2 and creating a new one on L1. zkSync provides a default contract that does this on L1, but developers can create their own custom contracts as long as they have a mint function and register it with the L1 Governance contract.
We needed to create a custom factory contract because the default contract assumes that each token’s contentHash is an IPFS CID. However, since we are using Storj, we have to construct the tokenURI differently.
The BoonjixJoaFactory contract implements ERC721 and the required mintNFTFromZkSync() function to mint a given token from l2. The token id is recovered from contentHash, and a counter is used to keep track of the current number of tokes minted (see totalSupply()).
The easiest way to do this is to have the contract inherit from a Mintable contract To fulfill the first requirement, the contract must have a specific function for minting. The easiest way to do this is to have the contract inherit from a contract that already has a mint function. This function is called from the ZkSync smart contract when a user goes through the withdrawal process on L2, and takes care of minting on L1 (like calling _safeMint()):
/// @dev mints a token from zksync l2
/// @notice only the zksync contract can call
/// @param creator original minter on l2
/// @param recipient account to receive token on l1
/// @param creatorAccountId creator account id on l2
/// @param serialId enumerable id of tokens minted by the creator
/// @param contentHash bytes32 hash of token uri
/// @param tokenId the token id (from l2)
function mintNFTFromZkSync(
address creator,
address recipient,
uint32 creatorAccountId,
uint32 serialId,
bytes32 contentHash,
uint256 tokenId
) external override onlyZkSync {}
To satisfy the second requirement – the contract must be registered with the L1 Governance
contract. We have to include in our smart contract the following:
/// @dev registers a creator with the zksync Governance contract to bridge tokens from l2
/// @param governance the zksync Governance contract
/// @param _creatorAccountId the creator account id on zksync
/// @param creator a whitelisted creator
/// @param signature payload signed by creator
function registerFactory(
address governance,
uint32 _creatorAccountId,
address creator,
bytes calldata signature
) external onlyOwner {
IGovernance(governance).registerNFTFactoryCreator(_creatorAccountId, creator, signature);
}
. In order to generate the required signature, consult their documentation. However, to make things easier for people who are following along, here is my script that formats all inputs as expected in the signature. You can verify this by looking at the implementation of the Governance contract on the zkSync repository.
const factory = await _getFactoryContract(ethWallet);
const _accountId = await syncWallet.getAccountId();
const _accountIdHex = hre.ethers.utils.hexValue(_accountId);
const accountId = `000${_accountIdHex.split('0x')[1]}`;
const creatorAddress = address.split('0x')[1].toLowerCase();
const factoryAddress = factory.address.split('0x')[1].toLowerCase();
const msg = `nCreator's account ID in zkSync: ${accountId}nCreator: ${creatorAddress}nFactory: ${factoryAddress}`;
const signature = await ethWallet.signMessage(msg);
const tx = await factory.registerFactory(GOVERNANCE_MAINNET, _accountId, address, signature);
We are now able to guarantee that token holders can withdraw their NFTs to L1 safely and a custom factory will be able to handle the minting.
The process of minting can be explained simply, though there are a few potential complications.
This js function will enable you to mint an NFT on zkSync with a given contentHash.
const mintNFT = async (syncProvider, syncWallet, recipient, contentHash) => {
const fee = await _logTxFeeSync(syncProvider, syncWallet);
const nft = await syncWallet.mintNFT({
recipient,
contentHash,
feeToken: 'ETH',
fee,
});
await nft.awaitReceipt();
};
Here, recipient
is the address to receive the freshly minted NFT. However, if the recipient never activated their account on zkSync, you’ll get the following error:
ZKSyncTxError: zkSync transaction failed: Recipient account not found
One way around this is to first transfer a little bit of ETH to this address. From the docs:
Users can transfer NFTs to existing accounts and transfer to addresses that have not yet registered a zkSync account. TRANSFER and TRANSFER_TO_NEW opcodes will work the same
We can either create an NFT for ourselves and then transfer it, or transfer a small amount of ETH to the recipient to trigger that TRANSFER_TO_NEW opcode.
.zksync.io is where you can check the status of your state change. If a user wants to check if their transaction went through, they can go to the zkSync wallet (wallet.zksync.io) and check the status of their “state change.” Transactions on zkSync go through a state change from committed to verified. Finality is usually achieved within an hour.
Once your token reaches finality and is in the verified state, you can withdraw it to L1 here: https://wallet.zksync.io/transaction/nft/withdraw.
How to Mint an NFT on Polygon
NFTs have surged in popularity in the past year and show no signs of slowing down. They are used for art, PFPs, lifestyle brands, sports, games, the metaverse, and more. However, there are a few problems with NFTs as they stand. For example, minting them on Ethereum can be expensive, and the metadata associated with them is often centralized.
This article provides a solution to the problem of storing NFT metadata by creating and deploying an NFT on the Polygon Network and using IPFS. In our example, we’ll use Truffle as our development environment and Infura to connect and interact with the blockchain and IPFS.
Fungible tokens are cryptographic tokens that can be exchanged for any other token of the same type. For example, one Bitcoin is interchangeable with any other Bitcoin. Other examples of fungible tokens include ETH and BTC.
are non-fungible, meaning that one unit is not interchangeable with another unit. This is in contrast to fungible assets like ETH and ERC-20 tokens, where every unit is worth the same as every other unit. An NFT is a digital asset that represents ownership of a digital item, and can be used to represent any kind of digital property, such as images, videos, or audio files.
Layer 1 of the Ethereum Network is expensive to transact on for the average person. This is due to the greater sense of security and decentralization that it offers.
The popularity of several solutions to this problem has grown over the course of 2021. Layer 2 solutions, such as Arbitrum, zkSync, and the Polygon Network, provide a more user-friendly experience. Transaction fees on these networks are a fraction of what they would be on Layer 1, and in most cases, much faster.
A problem with NFTs is that the metadata they contain isn’t always stored in a decentralized manner. If the organization hosting the metadata disappears, so does the value of your NFT. The best solution is to host the metadata using a global, peer-to-peer file system such as InterPlanetary File Storage (IPFS).
One way to store your NFTs in a more decentralized way is to use a combination of Polygon and IPFS. While you could use no-code services from ConsenSys, it’s more fun to deploy one yourself, and you’ll get a better understanding of how it all works.
So let’s say you want to create an NFT and deploy it to the Polygon Network. You would use Truffle as your development environment and Infura to connect to the blockchain and store the metadata with IPFS.
We will be testing our blockchain on a local instance of Ganache before we deploy it. When it is ready to go live, we will deploy it on the Polygon Mumbai Test Network and verify the NFT on OpenSea, which is the world’s most popular NFT platform.
Setting Up Accounts
We’ll use Infura to connect to Ethereum nodes. Before we start building and testing, we need to set up our accounts and get everything in place. This will ensure the rest of the process goes smoothly. We’ll use Infura to connect to Ethereum nodes.
Infura provides nodes and an API for the Ethereum network which will enable us to connect to the Polygon Network easily and use IPFS conveniently.
To use the Polygon Network on Infura, we first need to head to Infura.io and set up a free account. We can access the dashboard after verifying our email address, and create new projects. The Polygon Network isn’t enabled by default, but we can use it by following a few steps.
Add the Polygon network by selecting the “Add-Ons” tab on the left side of your screen. Scroll down to “Network Add-Ons” and select “Polygon PoS Add-On.” Follow the prompts to finish adding it to your plan.
Now that we’re done with the set up, we can create a new project. To do that, we’ll go back to the dashboard and click on the “Create New Project” button. We’ll select Ethereum as the product and name our project Polygon-NFT.
Once our project is created, there are several things to note:
- The Project ID
- The project secret
- Endpoints selection
- Our endpoint link
We will need to change our Endpoint from MAINNET to POLYGON MUMBAI in order to use the Polygon testnet. Our Endpoint link will then change. We will need the values from this page later, but we are done with the project page for now.
Conclusion
putting all the pieces together, doing a few dry runs on the rinkeby network, and deploying on the main network was quite an experience; it’s exciting to be on the forefront. With zkSync soon supporting smart contracts written in Solidity, it’s almost certain that protocols will take advantage of cheap and fast transaction finality and port their Solidity code over.
We had DefiSummer in 2020.
NFTs took over in 2021.
Leave a Reply