Minting an NFT on an android app (Part 1)
Peter Okwara
Posted on December 2, 2022
Introduction
Non-fungible means that something is unique and cannot be replaced by something else. Blockchains like Ethereum, polygon, and Iota act as a source of truth and help keep track of who's holding and trading NFTs
NFT's have multiple use cases, from Music, Fashion, Gaming, Luxury goods, Metaverse, Supply chain, and Ticket Sales. Nike recently partnered with Polygon to launch a web3 platform for NFTs in the form of digital shoes https://decrypt.co/114494/nike-swoosh-web3-platform-polygon-nfts
Most online blogs demonstrate how to mint an NFT using a web application. In this blog, we will be minting an NFT via an android app. The mobile app will be partly Java and, Kotlin.
The full working code can be found here
Prerequisite
The requirements are:
- Web3j: Lightweight Java and Android library for integration with Ethereum clients. https://www.web3labs.com/web3j-sdk
- Android Studio: Official integrated development environment for Google's Android operating system. https://developer.android.com/studio
- Truffle: Smart contract development tool. https://trufflesuite.com
- Ngrok: A reverse proxy service for fronting your web service. https://ngrok.com/
1. Create the smart contract
Create a new truffle project by running.
truffle init
Install openzeppelin within the truffle project by running.
npm install --save-dev @openzeppelin/contracts
Create a smart contract in the contract/ directory called lemurNFT.sol.
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
import "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol";
import "openzeppelin-solidity/contracts/utils/Counters.sol";
import "openzeppelin-solidity/contracts/token/ERC721/extensions/RRERC721URIStorage.sol";
contract lemurNFT is ERC721URIStorage {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
constructor() public ERC721("lemurNFT", "LMR") {}
function mintNFT(address recipient, string memory tokenURI)
public
returns (uint256)
{
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(recipient, newItemId);
_setTokenURI(newItemId, tokenURI);
return newItemId;
}
Within the smart contract:
- _tokenIds.increment(): Increase the counter by 1, thus enabling the code to generate a unique Token ID.
- uint256 newItemId = _tokenIds.current() assigns the current count number to a new instance of _tokenIds named newItemId.
- _safeMint(recipient, newItemId) mints newItemId and assigns it to recipient (an address).
- _setTokenURI(newItemId, tokenURI) sets tokenURI of the NFTs metadata file, that we created earlier.
2. Set up the development environment
Configure the truffle-config.js file to use the development environment by adding or commenting out the following section. Ensure WebSockets are enabled. This is because we will connect to our node via WebSockets.
development: {
host: "127.0.0.1", // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
websocket: true, // Enable EventEmitter interface for web3 (default: false)
}
3. Deploy the smart contract
To compile the smart contract, run
truffle compile
The output should look something similar to
Compiling your contracts...
===========================
> Compiling .\contracts\Migrations.sol
> Compiling .\contracts\lemurNFT.sol
> Compiling openzeppelin-solidity\contracts\token\ERC721\ERC721.sol
> Compiling openzeppelin-solidity\contracts\token\ERC721\IERC721.sol
> Compiling openzeppelin-solidity\contracts\token\ERC721\IERC721Receiver.sol
> Compiling openzeppelin-solidity\contracts\token\ERC721\extensions\ERC721URIStorage.sol
> Compiling openzeppelin-solidity\contracts\token\ERC721\extensions\IERC721Metadata.sol
> Compiling openzeppelin-solidity\contracts\utils\Address.sol
> Compiling openzeppelin-solidity\contracts\utils\Context.sol
> Compiling openzeppelin-solidity\contracts\utils\Counters.sol
> Compiling openzeppelin-solidity\contracts\utils\Strings.sol
> Compiling openzeppelin-solidity\contracts\utils\introspection\ERC165.sol
> Compiling openzeppelin-solidity\contracts\utils\introspection\IERC165.sol
> Compilation warnings encountered:
Warning: Visibility for constructor is ignored. If you want the contract to be non-deployable, making it "abstract" is sufficient.
--> project:/contracts/lemurNFT.sol:12:5:
|
12 | constructor() public ERC721("lemurNFT", "LMR") {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> Artifacts written to C:\Users\peter\Documents\GitHub\lemur-nft\contract\build\contracts
> Compiled successfully using:
- solc: 0.8.13+commit.abaa5c0e.Emscripten.clang
Run a local node by running
ganache
The output should look something similar to
ganache v7.5.0 (@ganache/cli: 0.6.0, @ganache/core: 0.6.0)
Starting RPC server
Available Accounts
==================
(0) 0x42703e1F2CCB08583088D96d71d4549Be08b52a7 (1000 ETH)
(1) 0x859e7f120E751C505EB14C942A192eC3448a12a1 (1000 ETH)
(2) 0x2864E9c62dE39c44a97866D31546069fD86DF71E (1000 ETH)
(3) 0x656d2d16C6ad06677A9A87e105e786E9f0420d5c (1000 ETH)
(4) 0x70BDc8926bb41e3a778e3F812Fe18db181aa2602 (1000 ETH)
(5) 0x05A2e3a72a63D2e0A85c7259E3aD6A5b9773cb79 (1000 ETH)
(6) 0x135A6788B85973890d11AdED97a76BfD5acd9F37 (1000 ETH)
(7) 0x19fCd2F3F957b9761e52C5583A14CE2669bbF257 (1000 ETH)
(8) 0xaf47CA6B33E5b895E68D937fDb2CaEEB4e462845 (1000 ETH)
(9) 0x61cDEbBCc8579560A79aaA41936fc553E22615E2 (1000 ETH)
Private Keys
==================
(0) 0xbea5ebe59d051534239ec8e81018b9d2f8458eee5864584d9d520a0c4307de90
(1) 0x5c0fd2fc115d1ab0c8e0ab3f367ac68dcb6e8e9c037a7de791cfba3d50dff993
(2) 0x3d0f2dc97eb7203213c42363f2f2fad34aa4bd0d1029347e72dfad41f900bc2c
(3) 0xa697dc1f7e4d08e899bdbdef17b303048d425bb7bfa712653071f4270fef6a7b
(4) 0x9dd1cccefa2486212ac11cd451d36de3a956ae2be9d4740783b55f94053fa837
(5) 0x1044bba3f188578d08a5c36aae4d4f0bacbfaa60e31e2b96791e5c2b099278a6
(6) 0x02314898ed8983c1c3598e74757bb3e933d4506e4d238706938479f756da8b94
(7) 0x7a7988d32c9a7ab50a9d4d4a9bf5ddf05db49cf2a0f5e196b5fb076d183f4bec
(8) 0xaac1f5cfc3c8ad7c2ed6eb843e0412668e00b4f87e82341bd6090239acb561fe
(9) 0x5b072abfa5a17d7a88fe456867789d0ba97c625e1c969485921765301c916b90
HD Wallet
==================
Mnemonic: truth manual elephant border predict castle payment suspect mimic insect wish acoustic
Base HD Path: m/44'/60'/0'/0/{account_index}
Default Gas Price
==================
2000000000
BlockGas Limit
==================
30000000
Call Gas Limit
==================
50000000
Chain Id
==================
1337
RPC Listening on 127.0.0.1:8545
Make sure you keep the process running. Store the seed in a secure place, it will be needed later for the android app. Don�t use the private keys or seed anywhere else or you risk being hacked.
The mnemonic (truth manual elephant border predict castle payment suspect mimic insect wish acoustic) will be used when setting up the android configuration.
We will also use the first public address 0x42703e1F2CCB08583088D96d71d4549Be08b52a7 as the wallet address.
Run the development command to deploy the smart contract.
truffle migrate --network development
If successful, it should show
Compiling your contracts...
===========================
> Compiling .\contracts\Migrations.sol
> Compiling .\contracts\lemurNFT.sol
> Compiling openzeppelin-solidity\contracts\token\ERC721\ERC721.sol
> Compiling openzeppelin-solidity\contracts\token\ERC721\IERC721.sol
> Compiling openzeppelin-solidity\contracts\token\ERC721\IERC721Receiver.sol
> Compiling openzeppelin-solidity\contracts\token\ERC721\extensions\ERC721URIStorage.sol
> Compiling openzeppelin-solidity\contracts\token\ERC721\extensions\IERC721Metadata.sol
> Compiling openzeppelin-solidity\contracts\utils\Address.sol
> Compiling openzeppelin-solidity\contracts\utils\Context.sol
> Compiling openzeppelin-solidity\contracts\utils\Counters.sol
> Compiling openzeppelin-solidity\contracts\utils\Strings.sol
> Compiling openzeppelin-solidity\contracts\utils\introspection\ERC165.sol
> Compiling openzeppelin-solidity\contracts\utils\introspection\IERC165.sol
> Compilation warnings encountered:
Warning: Visibility for constructor is ignored. If you want the contract to be non-deployable, making it "abstract" is sufficient.
--> project:/contracts/lemurNFT.sol:12:5:
|
12 | constructor() public ERC721("lemurNFT", "LMR") {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> Artifacts written to C:\Users\peter\Documents\GitHub\lemur-nft\contract\build\contracts
> Compiled successfully using:
- solc: 0.8.13+commit.abaa5c0e.Emscripten.clang
Starting migrations...
======================
> Network name: 'development'
> Network id: 1668618749782
> Block gas limit: 30000000 (0x1c9c380)
1_deploy_contract.js
====================
Deploying 'lemurNFT'
--------------------
> transaction hash: 0xbe14c78e8b59c002e2537a3cbc9a4f616fbbddafd94f4722a59c719d42137860
> Blocks: 0 Seconds: 0
> contract address: 0x129Ff5b9D7C128527F3Be5ca5fb4F2E7A991482d
> block number: 1
> block timestamp: 1668618761
> account: 0x7b2D85916C1fc56BBa4706DF0cE1D86532fB3839
> balance: 999.99153599275
> gas used: 2507854 (0x26444e)
> gas price: 3.375 gwei
> value sent: 0 ETH
> total cost: 0.00846400725 ETH
> Saving artifacts
-------------------------------------
> Total cost: 0.00846400725 ETH
1_initial_migration.js
======================
Deploying 'Migrations'
----------------------
> transaction hash: 0x7d4912cdabd080e6d49d0b244a88f86de933f61d491c76ef2847c133626e2d11
> Blocks: 0 Seconds: 0
> contract address: 0x12365fE3BA0F866A6E392BD3614B4322D73244C3
> block number: 2
> block timestamp: 1668618762
> account: 0x7b2D85916C1fc56BBa4706DF0cE1D86532fB3839
> balance: 999.990714509168638856
> gas used: 250154 (0x3d12a)
> gas price: 3.283911436 gwei
> value sent: 0 ETH
> total cost: 0.000821483581361144 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.000821483581361144 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.009285490831361144 ETH
Ensure that you store the contract address (in this example 0x129Ff5b9D7C128527F3Be5ca5fb4F2E7A991482d), located under 1_deploy_contracts since we will use this as the contract address when setting up the configurations for android.
4. Setting up a WebSocket
Before you perform this step, ensure that ganache is running. Once ganache is running, run the command:
ngrok http 8545
Ngrok will act as a forward proxy. It will expose the local network 127.0.0.1:8085 and give you a web address you can use to connect your android app to. The response should look like this:
ngrok
Add Okta or Azure to protect your ngrok dashboard with SSO: <https://ngrok.com/dashSSO>
Session Status: online
Account: P.Okwara (Plan: Free)
Version: 3.1.0
Region: United States (us)
Latency: 265ms
Web Interface: http://127.0.0.1:4040
Forwarding: <https://550b-105-163-1-231.ngrok.io> -> http://localhost:8545
Connections: ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00
We will use the forwarding address https://550b-105-163-1-231.ngrok.io as the websocket url. When adding to the config, we will change https: to wss so that the URL we will use in the end will look like this:
wss://550b-105-163-1-231.ngrok.io
5. Configure the metadata of your NFT using IPFS
Our lemurNFT smart contract function takes in a tokenURI parameter that should resolve to a JSON document describing the NFTs metadata, which is really what brings the NFT to life, allowing it to have configurable properties, such as a name, description, image, and other attributes.
The Interplanetary File System (IPFS) is a decentralized protocol and peer-to-peer network for storing and sharing data in a distributed file system.
We will use Pinata, a convenient IPFS API and toolkit, to store our NFT asset and metadata and ensure that our NFT is truly decentralized. If you don't have a Pinata account, sign up for a free account here.
Once you've created an account:
- Navigate to the Pinata Upload button on the top right
- Upload an image to pinata - this will be the image asset for your NFT. Feel free to name the asset whatever you wish
- After you upload, at the top of the page, there should be a green popup that allows you to view the hash of your upload ---> Copy that hashcode. You can view your upload at: https://gateway.pinata.cloud/ipfs/https://gateway.pinata.cloud/ipfs/<
In your root directory, make a new file called nft-metadata.json and add the following json code:
nft-metadata.json
{
"attributes" : [ {
"trait_type" : "Breed",
"value" : "Maltipoo"
}, {
"trait_type" : "Eye color",
"value" : "Mocha"
} ],
"description" : "The world's most adorable and sensitive pup.",
"image" : "https://gateway.pinata.cloud/ipfs/QmWmvTJmJU3pozR9ZHFmQC2DNDwi2XJtf3QGyYiiagFSWb",
"name" : "Ramses"
}
Feel free to change the data in the json. You can add or remove attributes. Most importantly, make sure the image field points to the location of your IPFS image otherwise, your NFT will not include a photo.
Once you're done editing the json file, save it and upload it to Pinata, following the same steps we did for uploading the image.
Remember the metadata.json you uploaded to Pinata? Get the hashcode from Pinata. We will use this hash and the url for the next step.
Posted on December 2, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.