Tutorial for building your Web3 and NFT

It was quite hard for me to understand what the essence of web3 and NFT which are really common phrases nowadays. Therefore, I’d like to fully understand NFT and Web3 by creating crypto assets, NFT, Web3, and the blockchain itself that is the basis of it, and developing original coins.

What I have tried were

  • Build own Ethererum network
  • Issue original coins
  • Develop and publish your own NFT
  • API and display the issued NFT

Blockchain conceptual diagram

Blockchain is a P2P network. There is no server that manages the whole, and computers (called nodes) connect to each other and process transactions while exchanging information.

Building Ethereum Network

I use Macbook Air and macOS Monterey. The browser is Chrome with the Metamask extension. You need to add Metamask extension before you start.

I created the following directory structure.

<directory>
┣ nodes-dir // data directory for running ethereum nodes
┃ ┣ genesis.json // setting file for initializing blockchain
┃ ┣ node1 // Working directory for node 1
┃ ┣ node2 // Working directory for node 2
┃ ┣ node3 // Working directory for node 3
┃ ┗ node4 // Working directory for node 4
┗ server// root of web server
┣ metadata // NFT metadata file storage
┣ images // NFT image storage
┗ index.html // Script for NFT viewer

Install geth

geth is the official client of Ethereum implemented in Go. You can run your own Ethereum by running geth on your local machine.
In the first place, follow the official tutorial to install using brew (if you don’t have brew, follow the steps here to install brew first)

$ brew tap ethereum/ethereum 
$ brew install ethereum

Preparation for node

Secondly, create a working directory for the nodes of the blockchain. Since setting up 4 nodes are planed, I also create 4 working directories.

$ mkdir nodes-dir/node1 nodes-dir/node2 nodes-dir/node3 nodes-dir/node4

Create a Genesis block next. Blockchain is literally the data that the block which is the transaction history is connected on the chain, and the first block is the genesis block.

Create the configuration file for Genesis block generation. See here for the meaning of each setting. If you put the wallet address and amount in the last alloc, it will start with the balance specified at that address at the time of blockchain initialization.

{   
"config": {
"chainId": 15,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"ethash": {}
},
"difficulty": "1",
"gasLimit": "8000000",
"alloc": {
"7df9a875a174b3bc565e6424a0050ebc1b2d1d82": {
"balance": "300000"
},
"f41c74c9ae680c1aa78f42e5647a62f353b7bdde": {
"balance": "400000"
}
}
}

Generate a genesis block using this configuration file above(genesis.json).

$ geth init --datadir nodes-dir/node11 nodes-dir/genesis.json

Since we will set up 4 nodes in total, create a genesis block with the same settings for the remaining 3 nodes.

$ geth init --datadir nodes-dir/node2/ nodes-dir/genesis.json 
$ geth init --datadir nodes-dir/node3/ nodes-dir/genesis.json
$ geth init --datadir nodes-dir/node4/ nodes-dir/genesis.json

Now we are ready to start the nodes !

Start the nodes

Start the nodes. First of all, the first node.

$ geth — datadir nodes-dir/node1 — networkid 15 — http — http.addr [YOUR_LOCAL_IP] — http.port 8101 — http.vhosts=*

Input the IP address of your machine in place of YOUR_LOCAL_IP. You can use 127.0.0.1, but don’t forget to use the same one every time you specify the IP of the node in the future.

I am using zsh for my shell. Therefore, I got the error like:

zsh: no matches found

If you use * or? or [ or ], kinda glob expansion is done. (Wildcard-like) If you have same problem, you should add

setopt nonomatch

to your .zshrc
You can edit your .zshrc with

$ open ~/.zshrc

It is started when the log starts without any error. Let’s connect to the node just started with the following command from another terminal.

$ geth attach nodes-dir/node1/geth.ipcWelcome to the Geth JavaScript console!instance: Geth/v1.10.17-stable/darwin-arm64/go1.18
at block: 56682 (Thu May 18 2022 16:20:15 GMT+0900 (JST))
datadir: YOUR_PATH
modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
To exit, press ctrl-d or type exit
>

The command console would be launched. You can enter the command admin.nodeInfo to see information about this node.

{
enode: "enode://834772b849cfa18f51c1a936ba20bdb0e14187e9d575cbe80e4d268fdbe6a985a16bf12bd136adfa85424ef99cca6614e6c73b853d83e709ee21db4a3371f45c@127.0.0.1:30303",
enr: "enr:-KO4QJXMxKo78soj6NiCugD2SBpdk5zATKSBzRTyVW_aJCXpVd58cZ_NXZxeWa5lKfv0vdIb3Nz2uTSL8vLWFw_0ubyGAYDQXSRqg2V0aMfGhJ2pRlqAgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQKDR3K4Sc-hj1HBqTa6IL2w4UGH6dV1y-gOTSaP2-aphYRzbmFwwIN0Y3CCdl-DdWRwgnZf",
id: "d12f417b3117bfb17771ecc516fef7a55564f909036a4b96a1aba114c4c5d70e",
ip: "127.0.0.1",
listenAddr: "[::]:30303",
name: "Geth/v1.10.17-stable/darwin-amd64/go1.18",
ports: {
discovery: 30303,
listener: 30303
},
protocols: {
eth: {
config: {
byzantiumBlock: 0,
chainId: 15,
constantinopleBlock: 0,
eip150Block: 0,
eip150Hash: "0x0000000000000000000000000000000000000000000000000000000000000000",
eip155Block: 0,
eip158Block: 0,
ethash: {},
homesteadBlock: 0,
petersburgBlock: 0
},
difficulty: 1921902727,
genesis: "0xc3638c5057e516005c90f14b439798979abfe13b491a15af85b0bdc514f97051",
head: "0x8fcfe2c7351f061cf244ddbe8e782490e2896d673449424c9236083fce255e49",
network: 15
},
snap: {}
}
}

Make a note of the string starting with enr: . This output is the identification information for this node, and we use it later. This is randomly generated every you run.

Then, the second and third nodes will be started in cooperation with this node. You have to keep it running, so try to start it in a different terminal or in the background.

$ geth --datadir nodes-dir/node2 --networkid 15 --port 30305 --bootnodes "enr:-KO4QJXMxKo78soj6NiCugD2SBpdk5zATKSBzRTyVW_aJCXpVd58cZ_NXZxeWa5lKfv0vdIb3Nz2uTSL8vLWFw_0ubyGAYDQXSRqg2V0aMfGhJ2pRlqAgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQKDR3K4Sc-hj1HBqTa6IL2w4UGH6dV1y-gOTSaP2-aphYRzbmFwwIN0Y3CCdl-DdWRwgnZf"$ geth --datadir nodes-dir/node3 --networkid 15 --port 30306 --bootnodes "enr:-KO4QJXMxKo78soj6NiCugD2SBpdk5zATKSBzRTyVW_aJCXpVd58cZ_NXZxeWa5lKfv0vdIb3Nz2uTSL8vLWFw_0ubyGAYDQXSRqg2V0aMfGhJ2pRlqAgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQKDR3K4Sc-hj1HBqTa6IL2w4UGH6dV1y-gOTSaP2-aphYRzbmFwwIN0Y3CCdl-DdWRwgnZf"

The string specified after
— bootnodes
is the enr of the first node.

You now have your own blockchain with 3 nodes connected.

Mining

There is no minor at present. We set the fourth node as a minor. A miner is a node that is in charge of verifying and recording blockchain transaction details (such as remittance) in the same way as Bitcoin mining, without which transactions will not be processed.

$ geth --datadir nodes-dir/node4 --networkid 15 --port 30307 --bootnodes "enr:-KO4QJXMxKo78soj6NiCugD2SBpdk5zATKSBzRTyVW_aJCXpVd58cZ_NXZxeWa5lKfv0vdIb3Nz2uTSL8vLWFw_0ubyGAYDQXSRqg2V0aMfGhJ2pRlqAgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQKDR3K4Sc-hj1HBqTa6IL2w4UGH6dV1y-gOTSaP2-aphYRzbmFwwIN0Y3CCdl-DdWRwgnZf" --mine --miner.threads=1 --miner.etherbase=[YOUR_WALLET_ADDRESS]

This node will be a minor with the — mine option, and since — miner.etherbase= is your wallet address to which the mining reward will be transferred, specify your own wallet address in your Metamask.

Now that the minor node has also started, the blockchain that can be traded has started to work.

Connect to Metamask

Connect your own blockchain to Metamask.
The Network tab pull-down at the top of the Metamask screen, click Add Network.

Add your own network you just built. The test network is a network called tishow test net, and the coins issued will be called TIS. For the RPC URL, enter the IP address and port specified when starting the node, and for the chain ID, enter the same number as specified as the chainId in the Genesis block configuration file.

Then “keep” with this,

I was able to connect to my own tishow test network.
Since the address of this wallet is the address specified as the transfer destination of the mining reward when the minor node is started, the mining reward has already been accumulated. Moreover, after a while, the coins will gradually increase with mining rewards.

Issue an NFT

NFT is a smart contract (a program) implemented based on one of the Ethereum standards called ERC-721, or a token generated from that program.

NFT art that collectors name are exchanging now is mainly composed of three data.
1) Smart contract (code written in the language Solidity)
2) Metadata (JSON format text data)
3) Image data (PNG, SVG, etc.)
Specifically, when you hit one of the APIs of the smart contract, the URL of the metadata that defines the NFT is returned, and when you read the contents of the metadata, the URL that refers to the image is written, and that URL When you access it, the PNG image of the actual NFT art contents will be returned.

In other words, if you make your own NFT art, you will have to make these three steps.

Prepare the original data of NFT

I will create images and metadata to make NFT art.

Next, create the metadata that references images.

{
"title": "TiShow NFT Collection",
"type": "object",
"name": "TiShow NFT Collection",
"description": "This is the test NFT collection",
"image": "http://127.0.0.1:8080/images/tishow.png"
}

Create as many JSON files as there are images to reference the images. In this metadata, the URL that can refer to the above avatar image from the browser is described. Set above file in metadata directory. File name is 1.

NFT smart contracts

Next, I write a smart contract (a program that runs on the Ethereum blockchain) necessary for issuing and distributing NFT on the blockchain. I write in my own programming language called Solidity, but since there is a very sophisticated web-based development environment called Remix, we can use the rest of the work with Remix.

Create a new Solidity source file with New File from Home. I created a file named TiShowNFT.sol.

Then write the following code.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract NFT is ERC721, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
constructor() ERC721("TiShow NFT Collection", "TISNFT"){}
function _baseURI() internal pure override returns (string memory) {
return "http://127.0.0.1:8080/metadata/";
}
function safeMint(address to) public onlyOwner returns (uint256) {
require(_tokenIdCounter.current() < 100);
_tokenIdCounter.increment();
_safeMint(to, _tokenIdCounter.current());
return _tokenIdCounter.current();
}
}
  • Import an existing template
  • Write a constructor that specifies the NFT collection name, etc.
  • Define an API that returns a URL that references the metadata
  • Write a process to issue while automatically incrementing the ID when mint is done

Compile this code. The fourth tab from the top on the left side of the Remix screen is the compile menu, so open it and press “Complie TiShowNFT.sol” to compile the currently displayed source code.

Deploy NFT smart contracts

After compiling, deploy this smart contract on the blockchain you just created to get it working. Open the Deploy screen, which is one below the compilation on the left side of the Remix screen.

If you select “Injected Web3” in ENVIRONMENT, Metamask will confirm the cooperation, so please approve it (at this time, make sure that Metamask is connected to the reality network you created earlier, otherwise Ethereum or another network Will lead to).
When the integration is complete, ACCOUNT should display the wallet address you are currently using in Metamask, so check that and select RealityAvatar.sol that you compiled earlier from the CONTRACT pull-down.

Now when you press the Deploy button, the transaction approval screen will appear on the Metamask side, and if you approve it, this smart contract will be deployed to your blockchain and will be operational.

Mint the NFT and put it in your wallet

Remix has the ability to call the API of a deployed smart contract, so it’s quick to use it.

At the bottom of the same screen that you just deployed, there is a section called “Deployed Contracts”, where you can see the API list of the smart contracts you just deployed.

Now enter your wallet address in the box to the right of the orange button labeled “safeMint” and press the “safeMint” button.

A transaction confirmation screen will appear in Metamask, press the “Confirm” button and wait for a while. The transaction is completed. Now you have the NFT you just mint in your wallet.

If you put 1 in the TokenID (ID that indicates the number of the NFT collection) to the right of the ownerOf button and press the ownerOf button, the wallet address that owns this NFT will be displayed below it. ..
If you get your wallet address with this, it means that the NFT with Token ID №1 is in your wallet.
(The reason why the №1 NFT is included is that the function called SafeMint used for mint earlier issues NFTs in order from №1, so the ID of the one that was issued first and entered in my wallet. Is 1)

Next, let’s see how this NFT image is referenced.
Similarly, there is a button called tokenURI at the bottom of the API list, so if you enter 1 to the right and press the tokenURI button, the metadata URL will be displayed below it.

When you access this URL, the JSON file you created earlier will be displayed, and when you open the URL of the image in that JSON file, the avatar image will be displayed.(You need run web server)

This series of steps is a mechanism to issue an NFT, enter it in the user’s wallet, and display the image of that NFT.

Show NFT

Here, I will implement an insanely primitive web front end that displays the NFT I received in Javascript.

I’ll just put the code. If you place it as server/index.html in the directory list at the beginning and access it from a browser, it will work.

<html>
<script type="text/javascript" src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js"></script>
<script type='application/javascript'>
var provider;
var signer;
var myAddress;
// Connect to Metamask
async function connectWallet() {
const { ethereum } = window;
provider = new ethers.providers.Web3Provider(ethereum, "any");
await provider.send("eth_requestAccounts", []);
signer = provider.getSigner();
myAddress = await signer.getAddress();
document.getElementById('connect-button').remove();
document.getElementById('wallet-address').innerHTML = myAddress;
document.getElementById('wallet-info').style.visibility = 'visible';
}
// Get and display the sent NFT list from the specified contract address
async function fetchNFTs() {
// Prepare API definition to access the NFT smart contract created this time
const abi = [
'event Transfer(address indexed from, address indexed to, uint256 value)',
'function tokenURI(uint256 _tokenId) view returns (string memory)'
];
const contractAddress = document.getElementById('contract-address').value;
const contract = new ethers.Contract(contractAddress, abi, signer);
// Filter for extracting NFT issuance events
filter = contract.filters.Transfer(null, myAddress)
// Get a list of NFTs issued to your address
txs = await contract.queryFilter(filter);
// Show NFT
txs.forEach(async function (item, index, ar) {
tokenId = item.topics[3];
url = await contract.tokenURI(tokenId);
// Read the metadata URL obtained from the smart contract tokenURI ()
fetch(url, { method: 'GET', cache: 'no-cache' })
.then((response) => {
return response.json()
})
.then((result) => {
displayNFT(result);
});
});
}
// generate html shows obtained NFT
function displayNFT(metadata) {
div = Object.assign(document.createElement('div'), { className: 'nft-item' });
div.appendChild(Object.assign(document.createElement('img'), { src: metadata.image }));
div.appendChild(Object.assign(document.createElement('p'), { innerHTML: metadata.name }));
console.log(div);
document.getElementById('items').appendChild(div);
}
</script>
<style>
* {
font-family: Roboto, Helvetica, Arial, sans-serif;
font-weight: 500;
color: #555555;
}
div#connect-wallet button {
margin-bottom: 20px;
padding: 20px;
font-weight: bold;
}
input#contract-address {
width: 300px;
}
div.nft-item {
width: 300px;
text-align: center;
float: left;
}
div.nft-item img {
height: 250px;
width: 250px;
}
div.nft-item p {
margin: 5px;
text-align: center;
}
</style>
<body>
<div id='connect-wallet'>
<div id='connect-button'>
<button onClick="connectWallet()"> Connect to the Metamask </button>
</div>
<div id='wallet-info' style="visibility:hidden">
Your wallet address is: <span id='wallet-address'></span>
</div>
</div>
<div>
<input type="text" id="contract-address" name="contract-address" placeholder="NFT contract address" />
<button onclick="fetchNFTs()">Show my NFTs</button>
</div>
<hr />
<div id='items'>
</div>
</body>
</html>
  • Press the Connect Metamask button and work with Metamask to get your wallet address
  • Enter the contract address of the NFT smart contract you issued earlier in the text box and press the “Get my NFTs” button, access the blockchain through Metamask, get the NFT list you mint and display it

I used Python3 to briefly build server in local

python -m http.server 8080

Then you can check index.html at the following URL.

http: //127.0.0.1:8080

Happy coding !

References

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store