How to Mint 1B+ NFT Images on the Fly from an iOS Device by@relatedcode

How to Mint 1B+ NFT Images on the Fly from an iOS Device

image
Related Code HackerNoon profile picture

Related Code

Mobile Application Development. Scalable Architecture Design. Custom Backend Development. Application Security.

You probably have heard about CryptoPunks, Bored APE, or Metroverse. All of these are NFTs (or Non-fungible tokens). A buzzword that can pay off big.

The NFT minting process is not rocket science. However, it can be challenging, especially from an iOS app. As of today, there is no ready-to-use method to interact with the blockchain (or with the smart contract) from iOS.

So we have decided to build a private API to mint NFT images on the fly.

image

Here is the list of what we need to have and what we need to do:

1., Tons of Images 2., Payment processing 3., A Smart contract 4., Decentralized file Storage 5., Interacting with the Blockchain 6., Fetching the Collectible Id 7., iOS Integration

Also, you can download the demo app from the App Store.

1., Images

We have 6 collections. Within the collections, we have 6-8 layers. Each layer contains 10-50 separated images. If we do the math, then the number of variations is easily above 1 billion.

image

Since minting an NFT is not for free, we decided not to pre-mint all the NFTs, but mint them one by one (on the fly). Whenever the user would like to have an NFT, we initiate the process from the beginning.

image

2., Payment processing

As we are talking about NFTs and Blockchain, it seems kind of obvious that the payment should happen by using crypto (like Bitcoin, Ethereum, etc). But good luck to convince Apple to let the users pay with crypto within an iOS app.

Because of Apple's strict rules, we need the user payments to be managed by Apple. In this case, this means an in-app purchase.

So we receive the payments from Apple (in USD), and at some point, we need to use our own crypto to pay the NFT minting (and Smart contract) transaction fees.

3. Smart Contract

What is a smart contract?

A "smart contract" is simply a program that runs on the Ethereum blockchain. It's a collection of code (its functions) and data (its state) that resides at a specific address on the Ethereum blockchain.

There are two types of accounts in the Ethereum blockchain:

  • Externally-owned accounts (the one you have in MetaMask or any wallets). You can control this type of account with the private key.
  • Contract accounts. This type is controlled by code.

So the NFTs you have in your wallets are associated with a contract that lives on the blockchain. As an example, the CryptoPunks contract address is: 0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB.

But how do we "create" a smart contract? The contract itself is coded in Solidity programming language and then deployed on the Ethereum blockchain. Let's see the details.

ECR721 Contract

ERC721 is a standard for representing ownership of non-fungible tokens, that is, where each token is unique.

However, creating an ECR721 token and complying with all the standards from scratch is not easy. This is why we decided to use the OpenZeppelin Wizard which allows us to have all the pre-built functionalities of an ECR721 contract in a few lines of code.

In our case, the contract needs to have the following features:

  • Mintable (with auto-increment IDs)
  • URI Storage (this is where we put the NFT image URI)

Our mint function looks very similar to the wizard's default one:

function safeMint(address to, string memory uri) public onlyOwner {
    uint256 tokenId = _tokenIdCounter.current();
    _tokenIdCounter.increment();
    _safeMint(to, tokenId);
    _setTokenURI(tokenId, uri);
}

Compilation and deployment

Once we have the smart contract code (written in Solidity), we need to compile and deploy it on the blockchain.

Hardhat is an Ethereum development environment, that can be used for this job. For more details please check the full tutorial.

We can easily deploy the contract by running a 2-lines command:

# Compile
npx hardhat compile

# Deploy it on mainnet
npx hardhat run scripts/deploy.js --network mainnet

After the deployment, we should see something like that:

Deploying contracts with the account: 0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB

The contract is now deployed on the blockchain with its own address.

4., Decentralized file Storage

First, we need to upload the image to IPFS. The InterPlanetary File System (IPFS) is a protocol and peer-to-peer network for storing and sharing data in a distributed file system.

We will use Pinata for this task. Pinata is an easy-to-use and pay-as-you-go API that allows you to interact with IPFS without having to run your own node.

When we upload an image to IPFS, Pinata returns a content identifier (CID) that can be used as a reference of the image. That identifier is unique to the asset:

const imagePin = await pinata.pinFileToIPFS(
  fs.createReadStream(imagePath),
  options
);

console.log(imagePin.IpfsHash);

Once we have the CID of our newly uploaded image, we must create the metadata of the NFT.

What is NFT metadata?

The NFT metadata is a simple JSON file with some unique info related to the NFT (and the collection it belongs to). The wallets (like MetaMask, or Coinbase Wallet) and marketplaces (like OpenSea) are retrieving this JSON file directly from the blockchain to display the NFT.

If you work with an ERC-721 URI Storage token, you can use the tokenURI contract function to retrieve the JSON metadata.

{
  "name": "Block #4793",
  "image": "ipfs://QmYLSvE6K1iVEoVDRbF4kbqqNF33ZsdkLvT8ZveUT4sNGb/4793.jpeg",
  "description": "Metroverse is a land trading NFT strategy game on the Ethereum blockchain. Collect, trade, and stake your city blocks to earn $MET, the utility token of Metroverse."
}

Once the metadata is generated as a JSON file, we can upload it to IPFS. As a result, it will return the metadata CID, which can be used in the next step to mint the NFT.

image

5. Interacting with the Blockchain

Once we have the IPFS metadata CID, we can mint the NFT by interacting with the smart contract that lives on the blockchain.

But we have a problem. Interacting with blockchain is hard. It requires solid technical knowledge and a powerful computer.

We need to manage a full node, and we need to download the whole blockchain data (often a few hundreds of gigabytes). These tasks cannot be done from an iOS app.

A possible solution for this problem is Alchemy.

Alchemy is an API that allows you to interact with the blockchain without worrying about the knowledge and resources. By using the Alchemy API (via the Node.js SDK) we can interact with the smart contract.

Load the contract

First, let's load our contract using the contract address. Since the contract lives on the blockchain, it has an address like every wallet.

const nftContract = new web3.eth.Contract(
  contract.abi,
  contractAddress
);

Build the transaction object

Then we can build the transaction object which is a set of parameters that will be used to interact with the smart contract (sender, recipient, payload).

const nonce = await web3.eth.getTransactionCount(
  publicKey,
  "latest"
);

let tx = {
  from: publicKey,
  to: contractAddress,
  nonce: nonce,
  data: nftContract.methods.safeMint(recipient, tokenURI).encodeABI(),
};

Estimate the Gas fee

When you deal with transactions on the blockchain, you have to take care of the transaction fees.

As the fuel allows your car to move, the gas on the blockchain is what enables the miner to validate (or "mine") the actions you want to do on the smart contract (minting the NFT in our case).

The NFT minting gas fee is not constant. It changes all the time along with the ETH or MATIC prices, and with the "complexity" of the minting process.

So we need to estimate how much gas is needed to mint our NFT in a short amount of time. We also use Alchemy to do the hard part for us.

const estimatedGas = await web3.eth.estimateGas(tx);
const price = await web3.eth.getMaxPriorityFeePerGas();

tx = {
  ...tx,
  gas: estimatedGas,
  maxPriorityFeePerGas: price,
};

Sign and send the transaction

Our transaction is now ready to be sent to the blockchain for validation. The last step is to sign this transaction using our wallet private key.

For security reasons, your wallet's private key should never be sent across the blockchain. So the only way to authenticate yourself is to use "cryptographic signing".

const signedTx = await web3.eth.accounts.signTransaction(
  tx, privateKey
);
const transactionReceipt = await web3.eth.sendSignedTransaction(
  signedTx.rawTransaction
);

image

6., Fetching the Collectible Id

Once the transaction has been mined, we can retrieve the NFT tokenId by using the transaction hash. However, this step can also be complex, so we will use a blockchain explorer tool like Etherscan or Polygonscan.

We decided to automate this process using the Polygonscan API.

We can retrieve the NFT Collectible Id with a simple GET request:

const explorerResponse = await axios.get(
  `https://api.polygonscan.com/api?module=account&action=tokennfttx&contractaddress=${contractAddress}&address=${walletAddress}&page=1&offset=1&sort=desc&apikey=${apiKey}`
);

const transaction = explorerResponse.data.result[0];

console.log(transaction.tokenID);

7. iOS Integration

Once we have implemented all the steps above, we only need to integrate it with the iOS codebase.

We decided to deploy the Node.js code as Firebase Cloud Functions. We also use Firebase Realtime Database to store some NFT minting-related data.

From the iOS perspective, the whole process is just a Cloud Function call and some Realtime Database management.

Once we have the Collectible Id and the Contract address, the user can easily import the NFT into his/her MetaMask wallet.

The demo app can be downloaded from the App Store.


You probably have heard about CryptoPunks, Bored APE, or Metroverse. All of these are NFTs (or Non-fungible tokens). A buzzword that can pay off big.

The NFT minting process is not rocket science. However, it can be challenging, especially from an iOS app. As of today, there is no ready-to-use method to interact with the blockchain (or with the smart contract) from iOS.

So we have decided to build a private API to mint NFT images on the fly.

image

Here is the list of what we need to have and what we need to do:

1., Tons of Images 2., Payment processing 3., A Smart contract 4., Decentralized file Storage 5., Interacting with the Blockchain 6., Fetching the Collectible Id 7., iOS Integration

Also, you can download the demo app from the App Store.

1., Images

We have 6 collections. Within the collections, we have 6-8 layers. Each layer contains 10-50 separated images. If we do the math, then the number of variations is easily above 1 billion.

image

Since minting an NFT is not for free, we decided not to pre-mint all the NFTs, but mint them one by one (on the fly). Whenever the user would like to have an NFT, we initiate the process from the beginning.

image

2., Payment processing

As we are talking about NFTs and Blockchain, it seems kind of obvious that the payment should happen by using crypto (like Bitcoin, Ethereum, etc). But good luck to convince Apple to let the users pay with crypto within an iOS app.

Because of Apple's strict rules, we need the user payments to be managed by Apple. In this case, this means an in-app purchase.

So we receive the payments from Apple (in USD), and at some point, we need to use our own crypto to pay the NFT minting (and Smart contract) transaction fees.

3. Smart Contract

What is a smart contract?

A "smart contract" is simply a program that runs on the Ethereum blockchain. It's a collection of code (its functions) and data (its state) that resides at a specific address on the Ethereum blockchain.

There are two types of accounts in the Ethereum blockchain:

  • Externally-owned accounts (the one you have in MetaMask or any wallets). You can control this type of account with the private key.
  • Contract accounts. This type is controlled by code.

So the NFTs you have in your wallets are associated with a contract that lives on the blockchain. As an example, the CryptoPunks contract address is: 0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB.

But how do we "create" a smart contract? The contract itself is coded in Solidity programming language and then deployed on the Ethereum blockchain. Let's see the details.

ECR721 Contract

ERC721 is a standard for representing ownership of non-fungible tokens, that is, where each token is unique.

However, creating an ECR721 token and complying with all the standards from scratch is not easy. This is why we decided to use the OpenZeppelin Wizard which allows us to have all the pre-built functionalities of an ECR721 contract in a few lines of code.

In our case, the contract needs to have the following features:

  • Mintable (with auto-increment IDs)
  • URI Storage (this is where we put the NFT image URI)

Our mint function looks very similar to the wizard's default one:

function safeMint(address to, string memory uri) public onlyOwner {
    uint256 tokenId = _tokenIdCounter.current();
    _tokenIdCounter.increment();
    _safeMint(to, tokenId);
    _setTokenURI(tokenId, uri);
}

Compilation and deployment

Once we have the smart contract code (written in Solidity), we need to compile and deploy it on the blockchain.

Hardhat is an Ethereum development environment, that can be used for this job. For more details please check the full tutorial.

We can easily deploy the contract by running a 2-lines command:

# Compile
npx hardhat compile

# Deploy it on mainnet
npx hardhat run scripts/deploy.js --network mainnet

After the deployment, we should see something like that:

Deploying contracts with the account: 0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB

The contract is now deployed on the blockchain with its own address.

4., Decentralized file Storage

First, we need to upload the image to IPFS. The InterPlanetary File System (IPFS) is a protocol and peer-to-peer network for storing and sharing data in a distributed file system.

We will use Pinata for this task. Pinata is an easy-to-use and pay-as-you-go API that allows you to interact with IPFS without having to run your own node.

When we upload an image to IPFS, Pinata returns a content identifier (CID) that can be used as a reference of the image. That identifier is unique to the asset:

const imagePin = await pinata.pinFileToIPFS(
  fs.createReadStream(imagePath),
  options
);

console.log(imagePin.IpfsHash);

Once we have the CID of our newly uploaded image, we must create the metadata of the NFT.

What is NFT metadata?

The NFT metadata is a simple JSON file with some unique info related to the NFT (and the collection it belongs to). The wallets (like MetaMask, or Coinbase Wallet) and marketplaces (like OpenSea) are retrieving this JSON file directly from the blockchain to display the NFT.

If you work with an ERC-721 URI Storage token, you can use the tokenURI contract function to retrieve the JSON metadata.

{
  "name": "Block #4793",
  "image": "ipfs://QmYLSvE6K1iVEoVDRbF4kbqqNF33ZsdkLvT8ZveUT4sNGb/4793.jpeg",
  "description": "Metroverse is a land trading NFT strategy game on the Ethereum blockchain. Collect, trade, and stake your city blocks to earn $MET, the utility token of Metroverse."
}

Once the metadata is generated as a JSON file, we can upload it to IPFS. As a result, it will return the metadata CID, which can be used in the next step to mint the NFT.

image

5. Interacting with the Blockchain

Once we have the IPFS metadata CID, we can mint the NFT by interacting with the smart contract that lives on the blockchain.

But we have a problem. Interacting with blockchain is hard. It requires solid technical knowledge and a powerful computer.

We need to manage a full node, and we need to download the whole blockchain data (often a few hundreds of gigabytes). These tasks cannot be done from an iOS app.

A possible solution for this problem is Alchemy.

Alchemy is an API that allows you to interact with the blockchain without worrying about the knowledge and resources. By using the Alchemy API (via the Node.js SDK) we can interact with the smart contract.

Load the contract

First, let's load our contract using the contract address. Since the contract lives on the blockchain, it has an address like every wallet.

const nftContract = new web3.eth.Contract(
  contract.abi,
  contractAddress
);

Build the transaction object

Then we can build the transaction object which is a set of parameters that will be used to interact with the smart contract (sender, recipient, payload).

const nonce = await web3.eth.getTransactionCount(
  publicKey,
  "latest"
);

let tx = {
  from: publicKey,
  to: contractAddress,
  nonce: nonce,
  data: nftContract.methods.safeMint(recipient, tokenURI).encodeABI(),
};

Estimate the Gas fee

When you deal with transactions on the blockchain, you have to take care of the transaction fees.

As the fuel allows your car to move, the gas on the blockchain is what enables the miner to validate (or "mine") the actions you want to do on the smart contract (minting the NFT in our case).

The NFT minting gas fee is not constant. It changes all the time along with the ETH or MATIC prices, and with the "complexity" of the minting process.

So we need to estimate how much gas is needed to mint our NFT in a short amount of time. We also use Alchemy to do the hard part for us.

const estimatedGas = await web3.eth.estimateGas(tx);
const price = await web3.eth.getMaxPriorityFeePerGas();

tx = {
  ...tx,
  gas: estimatedGas,
  maxPriorityFeePerGas: price,
};

Sign and send the transaction

Our transaction is now ready to be sent to the blockchain for validation. The last step is to sign this transaction using our wallet private key.

For security reasons, your wallet's private key should never be sent across the blockchain. So the only way to authenticate yourself is to use "cryptographic signing".

const signedTx = await web3.eth.accounts.signTransaction(
  tx, privateKey
);
const transactionReceipt = await web3.eth.sendSignedTransaction(
  signedTx.rawTransaction
);

image

6., Fetching the Collectible Id

Once the transaction has been mined, we can retrieve the NFT tokenId by using the transaction hash. However, this step can also be complex, so we will use a blockchain explorer tool like Etherscan or Polygonscan.

We decided to automate this process using the Polygonscan API.

We can retrieve the NFT Collectible Id with a simple GET request:

const explorerResponse = await axios.get(
  `https://api.polygonscan.com/api?module=account&action=tokennfttx&contractaddress=${contractAddress}&address=${walletAddress}&page=1&offset=1&sort=desc&apikey=${apiKey}`
);

const transaction = explorerResponse.data.result[0];

console.log(transaction.tokenID);

7. iOS Integration

Once we have implemented all the steps above, we only need to integrate it with the iOS codebase.

We decided to deploy the Node.js code as Firebase Cloud Functions. We also use Firebase Realtime Database to store some NFT minting-related data.

From the iOS perspective, the whole process is just a Cloud Function call and some Realtime Database management.

Once we have the Collectible Id and the Contract address, the user can easily import the NFT into his/her MetaMask wallet.

The demo app can be downloaded from the App Store.

Related Code HackerNoon profile picture
by Related Code @relatedcode.Mobile Application Development. Scalable Architecture Design. Custom Backend Development. Application Security.
Read my stories

Comments

Signup or Login to Join the Discussion

Tags

Related Stories