Skip to main content

5 posts tagged with "ERC721"

View All Tags
Go back

· 2 min read
Ewerton Lopes

header

Introdução

Realizar um deploy de um token ERC721 em Celo é fácil e rápido ao utilizar ferramentas de integração como Tatum.

Neste video mostraremos como você pode realizar um deploy do smart contract ERC721 na rede Alfajores (rede de teste) da Celo, mas também commo criar uma carteira, realizar transações, realizar uma recarga utilizando a Faucet.

Pré-requisitos

Para este tutorial não é necessário conhecimento prévio. As ferramentas utilizadas serão:

  • Tatum: Tatum é a maneira mais rápida de criar, testar e executar aplicativos blockchain. Eles oferecem APIs e SDKs para você implementar sua ideia usando blockchain
  • Faucet: Faucet é utilizada para adicionar fundos a sua conta de teste na rede Alfajores
  • Alfajores: É a rede de teste da Celo que utilizaremos para demonstração a implantação de um contrato inteligente e também realizar transações dos ativos

Requisitos

  • Criar uma conta no Tatum.io
  • Utilizar uma ferramenta para consulta de API como Postman

Tutorial

Confira no video como utilizar Tatum para realizar o deploy e mint de um NFT em Celo.

Conclusão

Parabéns! Você concluiu o tutorial de como realizar chamadas API usando Tatum e o blockchain da Celo 🎉 .

Próximos passos

Como próximos passos sugiro a você consultar a documentação de API da Tatum e realizar novas chamadas não contempladas neste tutorial.

Além disso, convido você a ver nosso outro video que explica sobre a API Tatum para realizar deploy ERC20

Sobre o Autor

Eu sou um empreendedor serial, founder da Guizo Studios e sempre disponível para ajudar o ecossistema Celo.

LinkedIn

Go back

· 13 min read

header

🌱 Introduction

Welcome Developers, to the Step-by-Step Guide to Deploying your First Full-Stack Dapp on Celo! In this guide, we will walk you through the process of building and deploying a full-stack decentralized application (Dapp) on the Celo platform.

Celo is a decentralized platform that enables fast, secure, and scalable transactions on a global scale. It is built on top of the Ethereum blockchain and is designed to be easily accessible to developers and users alike.

Whether you are a seasoned blockchain developer or just getting started, this guide will provide the knowledge and tools you need to build and deploy your own Dapp on Celo.

So let's get started!

🗈 Prerequisites

  • A computer with an internet connection. You will need a computer with a stable internet connection to follow along with this guide.

  • Basic knowledge of programming. While we will provide step-by-step instructions, it will be helpful to have some basic knowledge of programming languages such as JavaScript and Solidity.

  • Node.js and npm installed. You will need to have Node.js and npm (the package manager for Node.js) installed on your computer. You can check if you have them installed by running the following commands in your terminal:

node -v
npm -v
  • A code editor. You will need a code editor to write and edit your code. Some popular options include Visual Studio Code and Atom.
  • A Metamask account. You will need a Metamask account to interact with the Celo blockchain from your web browser. If you don't already have one, you can create one by installing the Metamask extension for Chrome or Firefox.

⚠️ Requirements

  • Truffle: a development environment, testing framework, and asset pipeline for Ethereum
  • Node.js: a JavaScript runtime that allows you to run JavaScript on the command line
  • Yarn: a package manager for JavaScript
  • next: Next.js is a framework for building server-rendered or statically-exported React applications.
  • CeloCli - The celocli lets you interact with the Celo Protocol smart contracts.

What are NFTs?

Non-fungible tokens (NFTs) are digital assets that represent ownership of a unique item or concept. They are stored on a blockchain and cannot be exchanged for something else of equal value, like traditional currencies. NFTs are often used to represent digital art, collectibles, and other unique items and are bought and sold in online marketplaces. Their value is determined by their rarity and perceived value to collectors. NFTs provide a way to prove ownership and authenticity of digital assets and allow for the creation of scarcity in the digital world, which can increase the value of certain items. They are created using smart contracts on a blockchain platform, such as Ethereum, and are often represented as ERC-721 tokens.

Let's start building the future together!

Steps to set up the truffle project and its configs

  1. Install Node.js by following the instructions on the official website.

  2. Install Yarn by running the following command:

npm install -g yarn
  1. Install Truffle by running the following command:
yarn global add truffle
  1. Install HDWalletProvider by running the following command:
npm install @truffle/hdwallet-provider --save

image

  1. Install Celo Command Line Interface also install dotenv
npm install -g @celo/celocli
npm install dotenv
// dotenv will help us to load .env file as environment variables

  1. Create a new Truffle project by running the following command:
mkdir NFTsmartcontract
cd NFTsmartcontract
truffle init
tip

Learn more: If you are new to Truffle check out the Truffle docs.

This will create a new directory with the following structure:

NFTsmartcontract/
├── contracts/
│ └── Migrations.sol
├── migrations/
│ └── 1_initial_migration.js
├── test/
├── truffle-config.js
└── truffle.js
  1. Navigate to the truffle-config.js file in your project directory and Replace the following configuration for the Celo testnet:
const HDWalletProvider = require("@truffle/hdwallet-provider");
require("dotenv").config();
module.exports = {
contracts_directory: "./contracts",
contracts_build_directory: "./truffle_abis",
migrations_directory: "./migrations",
networks: {
local: {
host: "127.0.0.1",
port: 7545,
network_id: "*",
},
alfajores: {
provider: function () {
return new HDWalletProvider(
process.env.PRIVATE_KEY,
"https://alfajores-forno.celo-testnet.org"
);
},
network_id: 44787,
gas: 20000000, //make sure this gas allocation isn't over 20M, which is the max
},
celo: {
provider: function () {
return new HDWalletProvider(
process.env.PRIVATE_KEY,
"https://forno.celo.org"
);
},
network_id: 42220,
gas: 20000000, //make sure this gas allocation isn't over 20M, which is the max
},
},
mocha: {
// timeout: 100000
},
compilers: {
solc: {
version: "0.8.9", // Fetch exact version from solc-bin (default: truffle's version)
docker: false, // Use "0.5.1" you've installed locally with docker (default: false)
settings: {
// See the solidity docs for advice about optimization and evmVersion
optimizer: {
enabled: false,
runs: 200,
},
evmVersion: "istanbul",
},
},
},
};
  1. Create A Celo Account using Celo Cli
celocli account:new

image

  1. Create a .env File in the root directory and add PRIVATE_KEY that we got from Celocli Command or You can use PRIVATE_KEY from Metamask.
PRIVATE_KEY="62dda1a6a6ee2dasdasdsadasdassdas1e2200095661a1b1e9dsadsdsdsadasasd"
  1. Create .gitignore file

It is important to hide your mnemonic and other important files while developing applications. When using Git or GitHub, you can populate a .gitignore file with the code below to ensure you don’t accidentally publish these files.

# dependencies
/node_modules

# Mac users
.DS_Store

#hidden files
.env

Now we are done with setting up truffle

Steps for creating ERC721 Contract and Truffle Migration File

  1. Create a NFT.sol file in contracts/ folder.

  2. Add the following code to it.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract NFT is ERC721, ERC721URIStorage, Ownable {
using Counters for Counters.Counter;

Counters.Counter private _tokenIdCounter;
constructor() ERC721("NFTexample", "CELO") {}

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

// The following functions are overrides required by Solidity.

function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
}

function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
}
tip

Use the OpenZeppelin Wizard to easily create, deploy, and interact with smart contracts using the OpenZeppelin library.

  1. Install openzeppelin Library by running this command in the root folder.
npm install @openzeppelin/contracts
  1. Create a 2_deploy_contracts.js File in the migrations folder and follow the code.
const NFT = artifacts.require("NFT");
module.exports = function(deployer) {
deployer.deploy(NFT);
};

Now we are done creating NFT.sol And its Migration Config, next we gonna deploy it on Celo testnet Blockchain

Steps to deploy Smart Contract

  1. We need Faucet For deploying smart contracts on Celo Blockchain. Use Celo Testnet Faucet to get faucet money input your address which we got from celocli.

  2. Now we Gonna Compile the Smart Contract and Check if there are any problems with it.

truffle compile
  1. After successful Compilation We Now gonna deploy it on Celo Testnet
//Truffle migrate compiles AND migrates your contract. In the future, you can run truffle migrate to complete both steps but run only if you are deploying it on a Local server.

truffle migrate
// use truffle deploy --network network name to deploy on celo testnet
truffle deploy --network alfajores
//We can use other Chain as well by adding them in truffle-config.js

image

  1. After We Got our Smart Contract Address we can check it on Celo Blockchain explorer using Block Explorer.

ヾ(´⌣`)ノ Hurray we Deployed our First ERC721 Smart Contract Make Sure to Save the Smart Contract Address in a File We Gonna use it in Our Frontend. As For Our Smart Contract we have deployed is smart contract

Frontend using NextJS

Steps

  1. Set up a Next.js project:

Install Next.js and create a new Next.js project by running the following commands:

npm init next-app
cd next-app
npm run dev

image

  1. Install Ethers.js:

Ethers.js is a JavaScript library that allows you to interact with the Ethereum blockchain. To install it, run the following command in your terminal:

npm install ethers
  1. Install React-Bootstrap
npm install react-bootstrap bootstrap

image

  1. Now we have installed react-bootstrap we need to add its CSS to the \_app.js file.
import 'bootstrap/dist/css/bootstrap.min.css';

  1. Now We have our libraries which we gonna use. Now let's edit our pages/index.js file
import React from 'react';
import { ethers } from 'ethers';
import contractAbi from "./abi/NFT.json";
import { Button, Card, Container, Nav, Navbar } from 'react-bootstrap';

const contractAddress = '0xa5Dcc3EB1eC8E417A4eA6CA51bBE4119D323d6E4'; // Replace with your contract address
function App() {
const [walletAddress, setWalletAddress] = React.useState(null);

const connectToWallet = async () => {
try {
await window.ethereum.enable();
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
setWalletAddress(accounts[0]);
console.log('Wallet connected:', walletAddress);
} catch (error) {
console.error(error);
}
};

const mintNFT = async () => {
try {
const provider = new ethers.providers.Web3Provider(window.ethereum)
const signer = provider.getSigner();
const contract = new ethers.Contract(contractAddress, contractAbi.abi, signer);

// Replace with Your metadata
const metadata = 'https://ipfs.io/ipfs/QmTvsVaaHTuMNmwXgbfgkrztFEazAPyzmrb4VSS2PbqLjA?filename=the-chainlink-elf.json';

const result = await contract.safeMint(metadata, { from: walletAddress });
console.log(result);
} catch (error) {
console.error(error);
}
};

const disconnectFromWallet = async () => {
try {
await window.ethereum.request({ method: 'eth_requestAccounts', accounts: [] });
setWalletAddress(null);
console.log('Wallet disconnected');
} catch (error) {
console.error(error);
}
};
React.useEffect(() => {
const checkWalletConnection = async () => {
if (window.ethereum.isConnected()) {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
setWalletAddress(accounts[0]);
console.log('Wallet connected:', walletAddress);
} else {
console.log('Wallet not connected');
}
};
checkWalletConnection();
}, []);
return (
<div style={{ backgroundColor: 'white' }}>
<Navbar bg="light" expand="lg">
<Navbar.Brand href="/">NFT Minter</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="ml-auto">
{ !walletAddress ?
<Nav.Link href="#" onClick={connectToWallet}>Connect</Nav.Link> :
<Nav.Link href="#" onClick={disconnectFromWallet}>Disconnect</Nav.Link>
}
<Nav.Link href="viewnft">View NFTs</Nav.Link>
</Nav>
</Navbar.Collapse>
</Navbar>
<Container>
<Card style={{ width: '18rem' }} className="mx-auto mt-5">
<Card.Img variant="top" src={`https://ipfs.io/ipfs/QmTgqnhFBMkfT9s8PHKcdXBn1f5bG3Q5hmBaR4U6hoTvb1?filename=Chainlink_Elf.png`} />
<Card.Body>
{walletAddress && (<>
<Button variant="primary" onClick={mintNFT}>Mint NFT</Button>
</>
)}
</Card.Body>
</Card>
</Container>
</div>
);
}

export default App;
  1. We need to copy the Abi Json file from our smart contract folder truffle_abis/NFT.json and Create a new folder in our pages folder named ABI and paste it over there.

  2. Now we can run Our Command to Test it out.

npm run dev

image

ヾ(´⌣`)ノ Hurray We are done with our frontend Mint Function.

Let's Complete Our Tutorial With View NFT

Steps To Create View NFT's Page

  1. Create a new file in pages with the name viewnft.js

  2. Install axios in the project to fetch data from IPFS JSON link.

npm install axios
  1. Let's import all the files which we needed
import React from 'react';
import { ethers } from 'ethers';
import contractAbi from "./abi/NFT.json";
import { Button, Card, Container, Nav, Navbar,Row,Col } from 'react-bootstrap';
import axios from 'axios';
  1. After importing all files we gonna create functions to fetch all user nfts.
async function listTokensOfOwner() {
const provider = new ethers.providers.Web3Provider(window.ethereum)

const contract = new ethers.Contract(contractAddress, contractAbi.abi, provider);
//we are using logs to fetch users nft
const sentLogs = await contract.queryFilter(
contract.filters.Transfer(walletAddress, null),
);
const receivedLogs = await contract.queryFilter(
contract.filters.Transfer(null, walletAddress),
);

const logs = sentLogs.concat(receivedLogs)
.sort(
(a, b) =>
a.blockNumber - b.blockNumber ||
a.transactionIndex - b.TransactionIndex,
);

const owned = new Set();

for (const log of logs) {
const { from, to, tokenId } = log.args;

if (addressEqual(to, walletAddress)) {
owned.add(tokenId.toString());
} else if (addressEqual(from, walletAddress)) {
owned.delete(tokenId.toString());
}
}

const uri = [];
for (const own of owned) {
const tokenuri = await tokenUri(own);
const response = await axios.get(tokenuri);

uri.push(response.data)
}
setuserNFT(uri);
};
async function tokenUri(id){
//this function is to fetch tokenUri from smart contract
const provider = new ethers.providers.Web3Provider(window.ethereum)
const contract = new ethers.Contract(contractAddress, contractAbi.abi, provider);
const url =await contract.tokenURI(id);
return url.toString()
}
function addressEqual(a, b) {
//this functoin is for checking the address match because sometime metamask and our input wallet addresses are in different Cases.
return a.toLowerCase() === b.toLowerCase();
}
  1. Let's look at our Complete `viewnft.js`` How it looks.
import React from 'react';
import { ethers } from 'ethers';
import contractAbi from "./abi/NFT.json";
import { Button, Card, Container, Nav, Navbar,Row,Col } from 'react-bootstrap';
import axios from 'axios';

const contractAddress = '0xa5Dcc3EB1eC8E417A4eA6CA51bBE4119D323d6E4'; // Replace with your contract address
function App() {
const [walletAddress, setWalletAddress] = React.useState(null);
const [userNFt,setuserNFT] = React.useState(null);
const connectToWallet = async () => {
try {
await window.ethereum.enable();
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
setWalletAddress(accounts[0]);
console.log('Wallet connected:', walletAddress);
} catch (error) {
console.error(error);
}
};

const disconnectFromWallet = async () => {
try {
await window.ethereum.request({ method: 'eth_requestAccounts', accounts: [] });
setWalletAddress(null);
console.log('Wallet disconnected');
} catch (error) {
console.error(error);
}
};
React.useEffect(() => {
const checkWalletConnection = async () => {
if (window.ethereum.isConnected()) {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
setWalletAddress(accounts[0]);
console.log('Wallet connected:', walletAddress);
} else {
console.log('Wallet not connected');
}
};
checkWalletConnection();
if(walletAddress){
listTokensOfOwner();
}
}, [walletAddress]);
async function listTokensOfOwner() {
const provider = new ethers.providers.Web3Provider(window.ethereum)

const contract = new ethers.Contract(contractAddress, contractAbi.abi, provider);

const sentLogs = await contract.queryFilter(
contract.filters.Transfer(walletAddress, null),
);
const receivedLogs = await contract.queryFilter(
contract.filters.Transfer(null, walletAddress),
);

const logs = sentLogs.concat(receivedLogs)
.sort(
(a, b) =>
a.blockNumber - b.blockNumber ||
a.transactionIndex - b.TransactionIndex,
);

const owned = new Set();

for (const log of logs) {
const { from, to, tokenId } = log.args;

if (addressEqual(to, walletAddress)) {
owned.add(tokenId.toString());
} else if (addressEqual(from, walletAddress)) {
owned.delete(tokenId.toString());
}
}

const uri = [];
for (const own of owned) {
const tokenuri = await tokenUri(own);
const response = await axios.get(tokenuri);

uri.push(response.data)
}
setuserNFT(uri);
};
console.log(userNFt)
async function tokenUri(id){
const provider = new ethers.providers.Web3Provider(window.ethereum)

const contract = new ethers.Contract(contractAddress, contractAbi.abi, provider);
const url =await contract.tokenURI(id);
return url.toString()
}
function addressEqual(a, b) {
return a.toLowerCase() === b.toLowerCase();
}
return (
<div style={{ backgroundColor: 'white' }}>
<Navbar bg="light" expand="lg">
<Navbar.Brand href="/">NFT Minter</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="ml-auto">
{!walletAddress? <><Nav.Link href="#" onClick={connectToWallet}>Connect</Nav.Link></>:
<><Nav.Link href="#" onClick={disconnectFromWallet}>Disconnect</Nav.Link></>}
<Nav.Link href="/viewnft">View NFTs</Nav.Link>
</Nav>
</Navbar.Collapse>
</Navbar>
<Container>
{walletAddress && (<>

<Row xs={1} md={4} className="g-4">
{userNFt && userNFt.map((item,i)=>{return(
<Col>
<Card style={{ width: '18rem' }} className="mx-auto mt-5">
<Card.Img variant="top" src={item.image}/>
<Card.Body>
<Card.Title>{item.name}</Card.Title>
<Card.Text>
{item.description}
</Card.Text>
</Card.Body>
</Card></Col>
) })}
</Row>
</>
)}
</Container>
</div>
);
}

export default App;
  1. Now we again gonna run npm run dev to start our application locally.

image

🥳🥳🥳 Congratulations! 🥳🥳🥳 You have successfully deployed your full stack Dapp on the Celo blockchain.

Conclusion

In conclusion, the full stack dApp we have built using Next.js, Ether.js, and a smart contract is a powerful tool for creating decentralized applications. The use of a smart contract on the Ethereum blockchain ensures that our dApp is transparent, secure, and immutable, while the frontend built with Next.js allows for a smooth and intuitive user experience. The combination of these technologies has allowed us to create a truly decentralized application that can be used by anyone, anywhere, at any time. With the potential to revolutionize the way we interact and transact online, the future looks bright for dApps like ours.

About Author

Hi! My name is Kunal Dawar and I am a Full Stack web2/web3 Developer. I have participated in numerous hackathons and have been fortunate enough to win many of them.

One thing that I am truly passionate about is creating things that are reliable and don't break easily. I believe that creating high-quality products is important not only for the users but also for the overall growth and success of a business.

In my free time, I enjoy learning about new technologies and staying up-to-date with the latest trends in the field. I also love to share my knowledge with others and mentor those who are interested in pursuing a career in web development.

Go back

· 9 min read

header

Introduction

NFTs have taken the market by storm, and the industry is now whopping billions of dollars. NFT is not a new concept. The concept first came into existence in 2014 with the launch of Quantum. A non-fungible token was created back then.

Later in 2020, NFT became a hot topic, and everyone was talking about it, and their market grew by $250 million. Since then, sales of NFTs have sky-rocketed.

In this tutorial we will learn to create an NFT with royalties. I will show you how to mint them in the UI and transfer it to another address from Rarible. Once you transfer from Rarible, you will see a small percentage of CELO gets transferred which is Royalty.

So let's get started...

Prerequisites​

  • Prior knowledge of Solidity is required.
  • Prior knowledge of HTML, CSS, and JavaScript is required.
  • Knowledge of ERC-721 specification is required.
  • Knowledge of how to deploy smart contracts via Remix.

Requirements

  • Remix IDE to write smart contract.
  • Knowledge of HTML, CSS, and JavaScript to code the frontend so we can interact with the smart contract to create an NFT with Royalty.

So what is an NFT?

A non-fungible token or NFT is a permanent record in blockchain linked to a digital or physical asset. For example, if I have a unique image and insert its location inside the token, that piece is called a non-fungible token.

But first, we need to understand what “fungible” means.

A “fungible” item is replaceable with a similar item bearing the same value; that means every fungible item has the same utility and intrinsic value. An example of this would be a $10 bill that can be used to replace any other $10 bill—you can swap your $10 bill with your friend’s without actually causing its value to dip. However, if you have a $10 bill that some celebrity has signed or a unique serial number like all eights, it could be worth up to $1,000 or even more. Why so? Because a mere celebrity autograph can make a regular $10 bill rare, special, and hence, non-fungible.

Why do we need royalty in an NFT?

First, I want to explain what Royalty is?

NFT royalties are payouts that compensate the original creator every time a secondary sale of their digital asset occurs. The royalty percentage is set by the creator at the time of minting, commonly around 5-10%. This percentage is taken from the sale price and sent to the creator.

Enough talking, show me the code

So we are going to code two smart contracts this time.

  1. NFTMinter: It will be used to mint NFT and users will also pay using CELO via this smart contract. Only this smart contract will be able to mint an NFT.

  2. MyNFT: This smart contract will inherit ERC-2981, ERC-721 along with others.

Let's code MyNFT.sol first.

Below is the smart contract, I will paste the entire code first, then I will go line-by-line to tell you what it does.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/common/ERC2981.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract MyNFT is ERC721URIStorage, ERC2981, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;

constructor() ERC721("MyNFT", "MN0") {
_setDefaultRoyalty(msg.sender, 200);
}

function _baseURI() internal pure override returns (string memory) {
return "https://ipfs.filebase.io/ipfs/";
}

function supportsInterface(bytes4 interfaceId)
public view virtual override(ERC721, ERC2981)
returns (bool) {
return super.supportsInterface(interfaceId);
}

function _burn(uint256 tokenId) internal virtual override {
super._burn(tokenId);
_resetTokenRoyalty(tokenId);
}

function burnNFT(uint256 tokenId)
public onlyOwner {
_burn(tokenId);
}

function mintNFT(address recipient, string memory tokenURI)
public onlyOwner
returns (uint256) {
_tokenIds.increment();

uint256 newItemId = _tokenIds.current();
_safeMint(recipient, newItemId);
_setTokenURI(newItemId, tokenURI);

return newItemId;
}

function mintNFTWithRoyalty(address recipient, string memory tokenURI, address royaltyReceiver, uint96 feeNumerator)
public onlyOwner
returns (uint256) {
uint256 tokenId = mintNFT(recipient, tokenURI);
_setTokenRoyalty(tokenId, royaltyReceiver, feeNumerator);

return tokenId;
}
}
// SPDX-License-Identifier: MIT

This represents license, in this case, it is MIT license.

pragma solidity ^0.8.0;

This line represents solidity version, in our case, we are using version greater then 0.8.0.

import "@openzeppelin/contracts/token/common/ERC2981.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

Here, we are importing couple of libraries. Below I will tell you what each library does.

  • ERC2981: Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information.

  • ERC721URIStorage: ERC721 token with storage based token URI management.

  • Ownable: Contract module which provides a basic access control mechanism, where there is an account (an owner) that can be granted exclusive access to specific functions.

  • Counters: Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number of elements in a mapping, issuing ERC721 ids, or counting request ids.

    contract MyNFT is ERC721URIStorage, ERC2981, Ownable {}

    Here, we are defining MyNFT contract which inherits ERC721URIStorage, ERC2981 and Ownable.

using Counters for Counters.Counter;
Counters.Counter private _tokenIds;

_tokenIds variable is getting initialized, it's type is Counters.Counter and it has private scope.

  constructor() ERC721("MyNFT", "MN0") {
_setDefaultRoyalty(msg.sender, 200);
}

Here we are calling constructor function. It is a special function. It only runs once, it will only run at the time of initialization of smart contract.

Here, ERC721 is taking two arguments, NFT name and NFT symbol. We are also calling one more function, _setDefaultRoyalty(). It takes owner address and percentage we wish to set for royalty. In our case, we did set 2% royalty.

function _baseURI() internal pure override returns (string memory) {
return "https://ipfs.filebase.io/ipfs/";
}

_baseURI() returns the base URI, we did setup our base URI to https://ipfs.filebase.io/ipfs.

function supportsInterface(bytes4 interfaceId)
public view virtual override(ERC721, ERC2981)
returns (bool) {
return super.supportsInterface(interfaceId);
}

Returns true if this contract implements the interface defined by interfaceId.

function _burn(uint256 tokenId) internal virtual override {
super._burn(tokenId);
_resetTokenRoyalty(tokenId);
}

function burnNFT(uint256 tokenId)
public onlyOwner {
_burn(tokenId);
}

These two functions are used to burn an NFT by giving a tokenId. It also reset the token royalty for a given NFT.

function mintNFT(address recipient, string memory tokenURI)
public onlyOwner
returns (uint256) {
_tokenIds.increment();

uint256 newItemId = _tokenIds.current();
_safeMint(recipient, newItemId);
_setTokenURI(newItemId, tokenURI);

return newItemId;
}

function mintNFTWithRoyalty(address recipient, string memory tokenURI, address royaltyReceiver, uint96 feeNumerator)
public onlyOwner
returns (uint256) {
uint256 tokenId = mintNFT(recipient, tokenURI);
_setTokenRoyalty(tokenId, royaltyReceiver, feeNumerator);

return tokenId;
}

Here we have two functions. In our NFTMinter smart contract, we will call mintNFTWithRoyalty() function. This function takes recipient, tokenURI, royaltyReceiver and feeNumerator.

  • recipient takes an address whose NFT we need to mint.
  • tokenURI contains IPFS hash of an image.
  • royaltyReceiver takes an address where we do need to send the royalty.
  • feeNumerator takes an integer, here we decide how much royalty do we need to send to the creator.

Now comes our second smart contract. NFTMinter.sol. Below is the code.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import './MyNFT.sol';

contract NFTMinter {

address public owner;
uint256 public basePrice;
address public MyNFTContractAddress;

// modifiers
modifier onlyOwner() {
require(msg.sender == owner, "Ownable: caller is not the owner");
_;
}

constructor() {
owner = msg.sender;
basePrice = 1000000000000000; // this is 0.001 CELO
}

// function to add or update nft smart contract address
function updateMyNFTContractAddress(address _address) public onlyOwner {
MyNFTContractAddress = _address;
}

// function that calls the mint function. Anyone can call this function.
function mintNft(address _toAddress, string memory _uri) public payable {

if(msg.sender != owner) {
require(msg.value >= basePrice, "Not enought ETH sent");

(bool sent, bytes memory data) = owner.call{value: msg.value}("");
require(sent, "Failed to send CELO");
}

MyNFT my_nft = MyNFT(MyNFTContractAddress);
my_nft.mintNFTWithRoyalty(_toAddress, _uri, owner, 2500);
}
}

Here, we are creating NFTMinter contract.

address public owner;
uint256 public basePrice;
address public MyNFTContractAddress;

We have defined three variables here.

  • owner: holds owner address. One who instantiate smart contract becomes an owner.
  • basePrice: Price at which we are minting the NFT.
  • MyNFTContractAddress: It holds contract address of MyNFT smart contract.
modifier onlyOwner() {
require(msg.sender == owner, "Ownable: caller is not the owner");
_;
}

onlyOwner() is a modifier. If we add this modifier in a function, it can only be called by the owner.

constructor() {
owner = msg.sender;
basePrice = 1000000000000000; // this is 0.001 CELO
}

In the constructor, we are assigning owner as msg.sender, it is a special type of variable. It contains address of a person who invoked the contract. Also, we are assigning basePrice in terms of wei. When converted to CELO, it is 0.001 CELO.

function updateMyNFTContractAddress(address _address) public onlyOwner {
MyNFTContractAddress = _address;
}

This function updates MyNFTContractAddress variable. It can only be called by owner.

function mintNft(address _toAddress, string memory _uri) public payable {

if(msg.sender != owner) {
require(msg.value >= basePrice, "Not enought CELO sent");

(bool sent, bytes memory data) = owner.call{value: msg.value}("");
require(sent, "Failed to send CELO");
}

MyNFT my_nft = MyNFT(MyNFTContractAddress);
my_nft.mintNFTWithRoyalty(_toAddress, _uri, owner, 2500);
}

mintNft() is a special type of function. It is a payable function. Anyone can call this function. Owner can mint NFT without sending CELO.

First, it checks weather function invoker is owner or not. If invoker is an owner, then it directly mints an NFT.

If person is not an owner, it checks if CELO sent greater then or equal to basePrice, if not, contract will revert back.

If enough CELO is sent, it transfer CELO to the owner, then it mints NFT for the given address.

I have checked in both smart contracts and frontend here: https://github.com/avirajkhare00/celo-blogs/tree/main/nft-with-royalty.

It's time to code frontend part.

We are going to create a very basic frontend. You can mint NFT by clicking a button.

This is how frontend will look like:

NFT Mint Page

I have already uploaded an image to IPFS, here is the hash: QmWwtPHgG6G2679oNA9nMxuXeARjYrmVaaBHEDbr9pMg4E

When we will be minting the NFT, you can use this or insert an image of your choice. You can use https://filebase.com to upload an image to IPFS.

Since Rarible does not support Celo Testnet, I will be using goreli testnet to show you how royalty is transferred.

Go ahead and run the frontend code by using any http server. Mint an NFT, then go to Rarible testnet here: https://testnet.rarible.com/.

You can see that your NFT is getting displayed, now put it on sale for whatever amount you wish for.

Now ask your friend or choose another wallet and purchase the NFT.

Check for Etherscan transactions. You will see that 0.001 ether went to the address who minted NFT and 2.0 % went to the creater of the NFT.

Check the screenshot below:

Etherscan screenshot

Congrats, you just learnt how to create an NFT having royalty.

About the author

Aviraj Khare: Into web3 space since 2016. GitHub: https://github.com/avirajkhare00

References

Go back

· 11 min read

header

Introduction​

NFTs have taken the market by storm, and the industry is now whopping billions of dollars. NFT is not a new concept. The concept first came into existence in 2014 with the launch of Quantum. A non-fungible token was created back then.

Later in 2020, NFT became a hot topic, and everyone was talking about it, and their market grew by $250 million. Since then, sales of NFTs have sky-rocketed.

Prerequisites​

  • Prior knowledge of Solidity is required.
  • Prior knowledge of HTML, CSS, and JavaScript is required.
  • Knowledge of ERC-721, ERC-1155 specification is required.
  • Knowledge of how to deploy smart contracts via Remix.

Requirements

  • Remix IDE to write smart contract.
  • Knowledge of HTML, CSS, and JavaScript to code the frontend so we can interact with the smart contract to create an NFT.

So what is an NFT?

A non-fungible token or NFT is a permanent record in blockchain linked to a digital or physical asset. For example, if I have a unique image and insert its location inside the token, that piece is called a non-fungible token.

But first, we need to understand what “fungible” means.

A “fungible” item is replaceable with a similar item bearing the same value; that means every fungible item has the same utility and intrinsic value. An example of this would be a $10 bill that can be used to replace any other $10 bill—you can swap your $10 bill with your friend’s without actually causing its value to dip. However, if you have a $10 bill that some celebrity has signed or a unique serial number like all eights, it could be worth up to $1,000 or even more. Why so? Because a mere celebrity autograph can make a regular $10 bill rare, special, and hence, non-fungible.

The difference between ERC-721 and ERC-1155

ERC-721 and ERC-1155 are both Ethereum smart contract standards for creating tokens on the Ethereum blockchain. The main difference between the two standards is how they handle multiple tokens' creation and management.

ERC-721 is a standard for creating non-fungible tokens, unique tokens representing a one-of-a-kind item or asset. Each ERC-721 token is distinct and cannot be replaced or interchanged with another.

On the other hand, ERC-1155 is a standard for creating both fungible and non-fungible tokens. This means ERC-1155 tokens can represent either unique or interchangeable items, such as cryptocurrencies. The ERC-1155 standard allows for creating and managing multiple types of tokens within a single, smart contract, making it more efficient and cost-effective than using multiple contracts for different types of tokens.

The main difference between ERC-721 and ERC-1155 is that ERC-721 is specifically for non-fungible tokens, while ERC-1155 can be used for both non-fungible and fungible tokens.

Limitations of NFT

When we talk about NFTs, we think about some monkey picture, but that’s not about it. Any digital art can be made NFT. Right now, NFTs are all about static art files. A static file is stored in decentralized storage like IPFS, and its hash is stored inside a smart contract. A hash is a metadata for minted NFT. It contains different attributes for the art file.

This is what the ERC721 Metadata JSON Schema looks like:

{
"title": "Asset Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Identifies the asset to which this NFT represents"
},
"description": {
"type": "string",
"description": "Describes the asset to which this NFT represents"
},
"image": {
"type": "string",
"description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
}
}
}

Both image and metadata might be stored inside IPFS.

Right now, all we have is a digital file. We can see, listen to, and trade it, but we can’t interact with it, and they can’t interact with each other.

This is where NFTs are limited in capabilities. They are static, not dynamic. They can’t interact with Humans or other NFTs.

Interactive NFTs

Interactive NFTs are the NFTs with which you can interact. We have these traditional NFTs with which you can’t interact. You can see, hear, and view it but can’t interact with it. With interactive NFTs, you can also change the smart contract's state. In this tutorial, we won’t be changing the smart contract's state, but we will be able to interact with it.

Enough talking. Show me the code

In the interactive NFT, we have two parts. One is the smart contract. Another one is the Metadata of the NFT. We will hack the metadata to make our NFT interactive.

This tutorial will create an NFT that will flip the value.

Let's get started.

First, we need to create an ERC-721 contract.

Here is the code.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract MyNFT0 is ERC721, ERC721URIStorage, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;

constructor() ERC721("MyNFT0", "MNFT0") {}

function _baseURI() internal pure override returns (string memory) {
return "https://ipfs.filebase.io/ipfs/";
}

function supportsInterface(bytes4 interfaceId)
public view virtual override(ERC721)
returns (bool) {
return super.supportsInterface(interfaceId);
}

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


// The following functions are overrides required by Solidity.

function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
}

function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
}

Go ahead and paste this code. I will explain what every line does.

// SPDX-License-Identifier: MIT

This tells the license of the smart contract. In our case, it is MIT license.

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

We are importing a couple of openzeppelin smart contracts such as ERC721, ERC721URIStorage, Ownable, and Counters.

ERC721: We are importing the ERC721 smart contract because we need to use the functionality of the ERC721 contract, and MyNFT0 is an ERC721 contract.

ERC721URIStorage: ERC721URIStorage extension provides storage for token URIs which means that URIs are stored on-chain. Without this extension, URIs are NOT going to be stored on-chain.

Ownable: Our smart contract also inherits Ownable, which means that few functions are meant to be called by the owner only.

Counters: Provides counters that can only be incremented, decremented, or reset. This can be used, e.g., to track the number of elements in a mapping, issue ERC721 ids, or count request ids.

contract MyNFT0 is ERC721, ERC721URIStorage, Ownable {}

This line means that the contract name is MyNFT which is inheriting ERC721, ERC721URIStorage, Ownable.

using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;

Here, we assign _tokenIdCounter of type Counters.Counter. It is a private variable. It is used to track how many NFTs are being minted. In a case when NFT gets minted, _tokenIdCounter is incremented. In case a token is burned, _tokenIdCounter is decremented.

constructor() ERC721("MyNFT0", "MNFT0") {}

A constructor is a special function that is called when a smart contract is initialized. It is only called one time. In the constructor, the ERC721 contract is initialized with two arguments, token name and token id.

function _baseURI() internal pure override returns (string memory) {
return "https://ipfs.filebase.io/ipfs/";
}

_baseURI() function returns the URI. In our case, URI is pointing toward the IPFS domain name.

function supportsInterface(bytes4 interfaceId)
public view virtual override(ERC721)
returns (bool) {
return super.supportsInterface(interfaceId);
}

Returns true if this contract implements the interface defined by interfaceId.

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

mintNFT() function is used to mint an NFT. It takes two arguments, an address and an IPFS hash. It can either be called only by the owner can be called by anyone. In our case, only the owner can call the mintNFT function. This function is public in nature.

It increments _tokenIdCounter. tokenId becomes the current count of _tokenIdCounter. It calls the _safeMint() function with two arguments, address to and tokenId. It sets the tokenId to the IPFS hash.

function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
}

_burn() is used to destroy the NFT token. It takes tokenId as an argument. It does not return anything.

function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
}

tokenURI() function returns the IPFS hash. It takes tokenId.

After we code our ERC-721 contract, it is time to make an NFT interactive.

Since we cannot make changes in our smart contract to make our NFT interactive otherwise, it won't be called an ERC-721 contract. We need to make changes to our NFT Metadata. This time we will be extending our NFT metadata that will point to JavaScript code.

Inside the IPFS, we will be pushing NFT Metadata, and JavaScript code to make our NFT interactive.

Please note that our interactive NFTs won’t work with existing NFT platforms like OpenSea, Rarible, etc, since they won't understand our NFT Metadata file.

We need to code our frontend to make it work.

It’s time to define our Metadata.

{
"title": "MyNFT0",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "MyNFT0"
},
"description": {
"type": "string",
"description": "This is an interactive NFT."
},
"image": {
"type": "string",
"description": ""
},
"js": {
"type": "string",
"description": "QmUS8szs3GkafJPLXQjjJe19xUyeSeUWiQLSmJKW1EorXc
"
}
}
}

Here we are extending ERC-721 metadata and including the JavaScript code that will be executed to make an NFT interactive.

First, we must upload an image and js code file in IPFS. To do that, I am using https://filebase.com

You need to upload the following JS code in the IPFS.

function flip () {
return true || false;
}

Once you store it, copy its IPFS hash and paste it inside the description of js inside the metadata.json file. In the end, you need to upload the metadata file. Once you upload all these two things, your filebase dashboard will look something like this.

Filebase dashboard

Now go to https://remix.ethereum.org and paste the smart contract code I have already provided.

Deploy the smart contract in the VM as of now. Once you deploy it, go to the approve tab and give your wallet address in the to field and copy the IPFS hash of the metadata.json file from filebase dashboard.

If you call the tokenURI function from remix with tokenId 1, you will see it is returning the full URL of the metadata.json file.

You may go ahead and deploy the smart contract to the CELO testnet if you want.

Now it’s time to code the frontend part.We will be using plain HTML, CSS, JavaScript, and Bootstrap for this.

Here is the repository: https://github.com/avirajkhare00/celo-interactive-nft-tutorial

Clone this repository and run this code. You may run this code by serving the file via any http server.

If Python 3 is installed on your machine, you can execute the following command: python3 -m http.server

Here is the doc for http server: ​​https://docs.python.org/3/library/http.server.html

Let’s try to understand what frontend JS code is doing.

const MyNFT0ContractAddress = "0xC1A6E6C2FDBd58d8AE952e3888E18B231cBc48f1";

const MyNFT0Contract = new web3.eth.Contract(MyNFT0ABI, MyNFT0ContractAddress);

async function getResult() {
let result = await MyNFT0Contract.methods.tokenURI(2).call();
console.log(result);

fetch(result)
.then(response => response.json())
.then(data => {
console.log(data);
console.log(data.properties.js.description);
let jsCodeHash = data.properties.js.description;
let baseURL = 'https://ipfs.filebase.io/ipfs/';

fetch(baseURL + jsCodeHash)
.then(response => response.text())
.then(data => {
eval(data);
console.log(flip());
document.getElementById('flipBtn').onclick = () => {
document.getElementById('flipValue').innerText = flip().toString();
};
});
});
};

getResult();

Basically we have stored the JS code inside the IPFS. Once we fetch the JS code from the IPFS, we are calling the eval() function to execute that code since it came in the form of a string.

We can see that if you click on the Flip button, it will return you either true or false. This is a very basic example of dynamic NFT.

In the end, it will look something like this:

Flip NFT

Conclusion

Go ahead and create an NFT game, I might try to make a tutorial on NFT games that will be dynamic in nature.

Next Steps

Create a simple game in the form of dynamic NFT.

About the author

Aviraj Khare: Into web3 space since 2016. GitHub: https://github.com/avirajkhare00

References