Skip to main content

11 posts tagged with "NFT"

View All Tags
Go back

· 13 min read

header

Introduction​

In this tutorial, we will be creating an escrow NFT platform on the Celo network using Eth-Brownie Python. Escrow is a financial arrangement where a third party holds and regulates the transfer of funds or assets between two other parties. In this case, the third party is the smart contract. The two parties are the buyer and the seller. The escrow NFT platform will work like this, the seller will lock NFT in smart contract and the buyer will be able to buy the NFT by paying the price set by the seller. The smart contract will hold the NFT until the buyer pays the price. If the buyer pays the price, the NFT will be transferred to the buyer. If the buyer fails to pay the price, the NFT will be returned to the seller. In this tutorial, we will be using the Celo network, but the same concept can be applied to other evm-compatible blockchain networks.

Prerequisites

These tutorials assume that you have some basic knowledge of solidity and python.

Requirements

This is a list of what we’ll cover 🗒

  • Step 1: Project setup
  • Step 2: Write project code
  • Step 3: Configure deployment settings
  • Step 4: Deploy your Contract
  • Step 5: Integration with frontend

Step 1: Project setup

First, we will create a new directory for our project. Open your terminal and run the following command to create a new directory called escrow-nft and change directory to it.

mkdir escrow-nft && cd escrow-nft

next, we will install eth-brownie, python-dotenv, and ganache-cli. Run the following command to install them.

# Install eth-brownie and python-dotenv
pip3 install eth-brownie python-dotenv

# Install ganache
npm install -g ganache

Now, after installing all the required packages, we need to initialize our project. Run the following command to initialize our project.

brownie init

Here's what a successful initialization looks like: init

Next, after initializing our project, we will create two files, brownie-config.yaml and .env in root directory. brownie-config.yaml is a configuration file for brownie. It contains the default settings for our project. We will use this file to configure our project settings. .env is a file that contains environment variables. We will use this file to store our mnemonic phrase. We will use this mnemonic phrase to deploy our contract to the Celo network.

brownie-config.yaml file

reports:
exclude_contracts:
- SafeMath
depedencies:
- OpenZeppelin/openzeppelin-contracts@4.8.0
compiler:
solc:
version: 0.8.15
optimizer:
enabled: true
runs: 200
remappings:
- "@openzeppelin=OpenZeppelin/openzeppelin-contracts@4.8.0"
networks:
default: celo-alfajores
console:
show_colors: true
color_style: monokai
auto_suggest: true
completions: true
editing_mode: emacs
dotenv: .env
wallets:
from_mnemonic: ${MNEMONIC}

.env file

MNEMONIC="your mnemonic phrase"

if you want to read more about brownie-config.yaml file, you can read it here.

lastly for this step, we will add Celo network to our brownie project. Run the following command to add Celo network to our brownie project.

brownie networks add Celo celo-mainnet host=https://forno.celo.org chainid=42220 explorer=https://explorer.celo.org

brownie networks add Celo celo-alfajores host=https://alfajores-forno.celo-testnet.org chainid=44787 explorer=https://alfajores-blockscout.celo-testnet.org

You can check if the network has been added by running the following command.

brownie networks list

result if the network has been added successfully.

networks list

You can see that we have added two networks, celo-mainnet and celo-alfajores to our brownie network list.

Step 2: Write project code

In this step, we will write the code for our smart contract. Create a new file called escrowNFT.sol in the contracts directory. This is where we will write our smart contract code. Here is an look at what our smart contract code will look like.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

contract escrowNFT is Ownable {
using SafeMath for uint256;

// Declare state variables
uint256 public fee;
uint256 private escrowDigit = 16;
uint256 private modulus = 10**escrowDigit;

enum Status {
Pending,
Accepted,
Rejected,
Cancelled
}

struct Escrow {
uint256 tokenId;
uint256 paymentAmount;
uint256 deadline;
address tokenAddress;
address buyerAddress;
address sellerAddress;
Status status;
}

mapping(uint256 => Escrow) public escrow;

event NewEscrow(
uint256 txId,
uint256 tokenId,
uint256 paymentAmount,
address tokenAddress
);

event CancleEscrow(
uint256 txId,
uint256 tokenId,
uint256 paymentAmount,
address tokenAddress
);

event PayEscrow(
uint256 txId,
uint256 timestamp,
uint256 tokenId,
uint256 paymentAmount
);

event RejectEscrow(
uint256 txId,
uint256 tokenId,
uint256 paymentAmount,
address tokenAddress
);



modifier onlySeller(uint256 _txId) {
require(
msg.sender == escrow[_txId].sellerAddress,
"Only seller can call this function"
);
_;
}

modifier onlyBuyer(uint256 _txId) {
require(msg.sender == escrow[_txId].buyerAddress, "Only buyer can call this function");
_;
}

constructor(uint256 _fee) {
fee = _fee;
}

function updateFee(uint256 _fee) external onlyOwner {
fee = _fee;
}

function claimFee() external onlyOwner {
(bool status, ) = payable(msg.sender).call{value: address(this).balance}("");
require(status, "Transfer failed");
}

function generateTxId(
address _sellerAddress,
address _buyerAddress,
address _nftAddress,
bytes memory _secret
) external view returns (uint256 txId) {
txId =
uint256(
keccak256(
abi.encodePacked(
_sellerAddress,
_buyerAddress,
_nftAddress,
_secret
)
)
) %
modulus;
}

function createEscrow(
uint256 _txId,
uint256 _tokenId,
uint256 _paymentAmount,
address _tokenAddress,
address _buyerAddress
) external {
require(_paymentAmount > 0, "Payment amount must be greater than 0");
require(_tokenAddress != address(0), "Token address cannot be 0x0");
require(_buyerAddress != address(0), "Buyer address cannot be 0x0");
IERC721 nft = IERC721(_tokenAddress);
nft.transferFrom(msg.sender, address(this), _tokenId);
escrow[_txId] = Escrow(
_tokenId,
_paymentAmount,
block.timestamp + 1 days,
_tokenAddress,
_buyerAddress,
msg.sender,
Status.Pending
);
emit NewEscrow(_txId, _tokenId, _paymentAmount, _tokenAddress);
}

function cancleEscrow(uint256 _txId) external onlySeller(_txId) {
require(
block.timestamp > escrow[_txId].deadline,
"Deadline not reached"
);
require(
escrow[_txId].status == Status.Pending,
"Escrow is not in pending status"
);
IERC721 nft = IERC721(escrow[_txId].tokenAddress);
escrow[_txId].status = Status.Cancelled;
nft.transferFrom(address(this), msg.sender, escrow[_txId].tokenId);
emit CancleEscrow(
_txId,
escrow[_txId].tokenId,
escrow[_txId].paymentAmount,
escrow[_txId].tokenAddress
);
}

function payEscrow(uint256 _txId) external payable onlyBuyer(_txId) {
require(block.timestamp < escrow[_txId].deadline, "Deadline reached");
require(
escrow[_txId].status == Status.Pending,
"Escrow is not in pending status"
);
IERC721 nft = IERC721(escrow[_txId].tokenAddress);
uint256 amountAfterFee = _calculateFee(msg.value);
escrow[_txId].status = Status.Accepted;
(bool status, ) = payable(escrow[_txId].sellerAddress).call{value: amountAfterFee}("");
require(status, "Transfer failed");
nft.transferFrom(address(this), msg.sender, escrow[_txId].tokenId);
emit PayEscrow(
_txId,
block.timestamp,
escrow[_txId].tokenId,
escrow[_txId].paymentAmount
);
}

function rejectEscrow(uint256 _txId) external onlyBuyer(_txId) {
require(block.timestamp < escrow[_txId].deadline, "Deadline reached");
require(
escrow[_txId].status == Status.Pending,
"Escrow is not in pending status"
);
IERC721 nft = IERC721(escrow[_txId].tokenAddress);
escrow[_txId].status = Status.Rejected;
nft.transferFrom(address(this), escrow[_txId].sellerAddress, escrow[_txId].tokenId);
emit RejectEscrow(
_txId,
escrow[_txId].tokenId,
escrow[_txId].paymentAmount,
escrow[_txId].tokenAddress);
}

function _calculateFee(uint256 _paymentAmount) private view returns(uint256 amountAfterFee) {
uint256 feeAmount = _paymentAmount.mul(fee).div(100);
amountAfterFee = _paymentAmount.sub(feeAmount);
}
}

Let us analyze the code line by line.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

First line, we declare the SPDX license identifier of the relevant license for the contract. The second line, we declare the solidity version we are using.

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

We'll be using OpenZeppelin contracts for our project. Ownable contract will be used to make sure only the owner of the contract can call certain functions. IERC721 is the interface for ERC721 tokens. SafeMath is a library that provides mathematical functions and prevents integer overflow.

uint256 public fee;
uint256 private escrowDigit = 16;
uint256 private modulus = 10**escrowDigit;

The fee variable will be used to store the fee percentage. The escrowDigit variable will be used to generate a unique transaction ID. The modulus variable will be used to generate a unique transaction ID.

enum Status {
Pending,
Accepted,
Rejected,
Cancelled
}

Enum Status will be used to store the status of the escrow.

struct Escrow {
uint256 tokenId;
uint256 paymentAmount;
uint256 deadline;
address tokenAddress;
address buyerAddress;
address sellerAddress;
Status status;
}

Escrow struct will be used to store the details of the escrow.

mapping(uint256 => Escrow) public escrow;

The escrow mapping will be used to map the transaction ID to the Escrow struct.

event NewEscrow(
uint256 txId,
uint256 tokenId,
uint256 paymentAmount,
address tokenAddress
);

event CancleEscrow(
uint256 txId,
uint256 tokenId,
uint256 paymentAmount,
address tokenAddress
);

event PayEscrow(
uint256 txId,
uint256 timestamp,
uint256 tokenId,
uint256 paymentAmount
);

event RejectEscrow(
uint256 txId,
uint256 tokenId,
uint256 paymentAmount,
address tokenAddress
);

These events are emitted when a new escrow is created, an escrow is cancelled, an escrow is paid, and an escrow is rejected.

modifier onlySeller(uint256 _txId) {
require(
msg.sender == escrow[_txId].sellerAddress,
"Only seller can call this function"
);
_;
}

modifier onlyBuyer(uint256 _txId) {
require(msg.sender == escrow[_txId].buyerAddress, "Only buyer can call this function");
_;
}

These modifiers are used to make sure only the seller or the buyer can call certain functions.

function updateFee(uint256 _fee) external onlyOwner {
fee = _fee;
}

function claimFee() external onlyOwner {
(bool status, ) = payable(msg.sender).call{value: address(this).balance}("");
require(status, "Transfer failed");
}

The updateFee function is used to update the fee percentage. The claimFee function is used to claim the fee collected by the contract.

function generateTxId(
address _sellerAddress,
address _buyerAddress,
address _nftAddress,
bytes memory _secret
) external view returns (uint256 txId) {
txId =
uint256(
keccak256(
abi.encodePacked(
_sellerAddress,
_buyerAddress,
_nftAddress,
_secret
)
)
) %
modulus;
}

generateTxId function is used to generate a unique transaction ID to be used for the escrow. The transaction ID is generated using the seller address, buyer address, NFT address, and the secret. secret parameter is used to make sure the transaction ID is unique for each escrow.

function createEscrow(
uint256 _txId,
uint256 _tokenId,
uint256 _paymentAmount,
address _tokenAddress,
address _buyerAddress
) external {
require(_paymentAmount > 0, "Payment amount must be greater than 0");
require(_tokenAddress != address(0), "Token address cannot be 0x0");
require(_buyerAddress != address(0), "Buyer address cannot be 0x0");
IERC721 nft = IERC721(_tokenAddress);
nft.transferFrom(msg.sender, address(this), _tokenId);
escrow[_txId] = Escrow(
_tokenId,
_paymentAmount,
block.timestamp + 1 days,
_tokenAddress,
_buyerAddress,
msg.sender,
Status.Pending
);
emit NewEscrow(_txId, _tokenId, _paymentAmount, _tokenAddress);
}

createEscrow is used to create a new escrow. The function takes the transaction ID, token ID, payment amount, token address, and buyer address as parameters. The function first checks if the payment amount is greater than 0, and check token, buyer address are not 0x0. Then it transfers the NFT from the seller to the contract. Then it creates a new escrow in the escrow mapping and emits the NewEscrow event.

function cancleEscrow(uint256 _txId) external onlySeller(_txId) {
require(
block.timestamp > escrow[_txId].deadline,
"Deadline not reached"
);
require(
escrow[_txId].status == Status.Pending,
"Escrow is not in pending status"
);
IERC721 nft = IERC721(escrow[_txId].tokenAddress);
escrow[_txId].status = Status.Cancelled;
nft.transferFrom(address(this), msg.sender, escrow[_txId].tokenId);
emit CancleEscrow(
_txId,
escrow[_txId].tokenId,
escrow[_txId].paymentAmount,
escrow[_txId].tokenAddress
);
}

cancleEscrow function is used to cancel an escrow. The function takes the transaction ID as a parameter. First, function will check if the deadline is reached and the escrow status is pending. If both conditions are true, the function will transfer the NFT back to the seller and emit the CancleEscrow event.

function payEscrow(uint256 _txId) external payable onlyBuyer(_txId) {
require(block.timestamp < escrow[_txId].deadline, "Deadline reached");
require(
escrow[_txId].status == Status.Pending,
"Escrow is not in pending status"
);
IERC721 nft = IERC721(escrow[_txId].tokenAddress);
uint256 amountAfterFee = _calculateFee(msg.value);
escrow[_txId].status = Status.Accepted;
(bool status, ) = payable(escrow[_txId].sellerAddress).call{value: amountAfterFee}("");
require(status, "Transfer failed");
nft.transferFrom(address(this), msg.sender, escrow[_txId].tokenId);
emit PayEscrow(
_txId,
block.timestamp,
escrow[_txId].tokenId,
escrow[_txId].paymentAmount
);
}

payEscrow function is used to pay an escrow. The function takes the transaction ID as a parameter. First, function will check if the deadline is not reached and the escrow status is pending and buyer only sends the payment amount equal to the payment amount in the escrow. If all conditions are true, the function will transfer the NFT to the buyer and pay the seller the payment amount minus the fee. Then it emits the PayEscrow event.

function rejectEscrow(uint256 _txId) external onlyBuyer(_txId) {
require(block.timestamp < escrow[_txId].deadline, "Deadline reached");
require(
escrow[_txId].status == Status.Pending,
"Escrow is not in pending status"
);
IERC721 nft = IERC721(escrow[_txId].tokenAddress);
escrow[_txId].status = Status.Rejected;
nft.transferFrom(address(this), escrow[_txId].sellerAddress, escrow[_txId].tokenId);
emit RejectEscrow(
_txId,
escrow[_txId].tokenId,
escrow[_txId].paymentAmount,
escrow[_txId].tokenAddress);
}

rejectEscrow function is used to reject an escrow. The function takes the transaction ID as a parameter. First, function will check if the deadline is not reached and the escrow status is pending. If both conditions are true, the function will transfer the NFT back to the seller and emit the RejectEscrow event.

function _calculateFee(uint256 _paymentAmount) private view returns(uint256 amountAfterFee) {
uint256 feeAmount = _paymentAmount.mul(fee).div(100);
amountAfterFee = _paymentAmount.sub(feeAmount);
}

_calculateFee private function is used to calculate the fee amount. The function takes the payment amount as a parameter and returns the amount after fee.

Step 4: Deploy your Contract

Now that we have written our smart contract, we need to deploy it to the blockchain. First, we need to compile our smart contract, and then we will deploy it to the blockchain. Use the following command to compile the smart contract.

brownie compile

result if the compilation is successful brownie compile

Now, we need to deploy our smart contract to the blockchain. First create a new file scripts/deploy.py and add the following code to it.

from brownie import accounts, config, escrowNFT

def main():
# Get the account to use
account = accounts.from_mnemonic(config["wallets"]["from_mnemonic"])

# Deploy the contract
contract = escrowNFT.deploy(2, {"from": account})

# Wait for the transaction to be mined
contract.tx.wait(3)

print("Contract deployed to:", contract.address)

main function is used to deploy the contract. First, we get the account to use. Then we deploy the contract and wait for the transaction to be mined. Finally, we print the contract address. Now, we need to deploy the contract. Use the following command to deploy the contract.

# Deploy the contract to the Celo Alfajores testnet
brownie run scripts/main.py --network celo-alfajores

# Deploy the contract to the Celo Mainnet
brownie run scripts/main.py --network celo-mainnet

result if the deployment is successful brownie run

Step 5: Integration with frontend

Now that we have deployed our smart contract to the blockchain, we need to integrate it with our frontend. You can clone the frontend we have created for this tutorial. Use the following command to clone the frontend.

# Clone the frontend
git clone https://github.com/yafiabiyyu/CeloSageFE.git

# Go to the frontend directory
cd CeloSageFE

# Install the dependencies
npm install --save

After cloning the frontend, we need to update the src/utils/contract.tsx, You need to update the contract address and ABI. You can get the contract address from the deployment result and you can get the ABI from the brownie project folder build/contracts/escrowNFT.json.

after updating the contract address and ABI, You can run the frontend using the following command.

npm start

result if the frontend is running successfully frontend result

Conclusion

In this tutorial, we have learned how to create an escrow smart contract using Solidity and Brownie. We have also learned how to deploy the smart contract to the Celo blockchain.

Next Steps

For your next steps, if you a python developer and want to learn how to create a smart contract using python, you can check about vyper. Vyper is a python-based smart contract programming language. You can check the Vyper documentation to learn more about Vyper.

About the Author

I am a blockchain and crypto enthusiast. I am also a software engineer. I love to learn new things and share my knowledge with others. You can find me on GitHub and LinkedIn.

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

Go back

· 10 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-20, 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.

What are NFTs?​

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.

What are fractionalized NFTs?​

As the name suggests, a fractionalized NFT is an NFT spitted into fractions that can be sold individually. Each fraction represents ownership of an NFT, enabling multiple people to own a single NFT.

Fractionalization lowers the threshold to own an NFT, enabling more people to own a single NFT collectively.

Fractionalized NFTs work just like shares of a company or shared ownership of a property.

When an NFT is fractionalized, the original NFT is locked up in a vault, and a limited supply of fungible tokens representing fractions of the NFT’s ownership is issued. Interested buyers can then invest in these individual fractions of the NFT and claim shared ownership.

How to fractionalize an NFT?​

Both ERC-721 and ERC-1155 are used to create NFTs on Ethereum. However, to create alt tokens, the ERC-20 specification is used.

The fractionalization process requires creating limited ERC-20(fungible) tokens out of ERC-721(non-fungible) tokens. Those who hold this ERC-20 token own a fraction of the NFT.

Now let’s dive deeper and understand the process to fractionalize an NFT:

  • To fractionalize an NFT, it must first be secured in a smart contract that breaks this ERC-721 or ERC-1155 token into several smaller parts, each representing an individual ERC-20 token.
  • Each ERC-20 token represents partial ownership of the NFT. After the owner of the NFT sells it, holders of the ERC-20 tokens can redeem their tokens for their share of the money received from the sale.
  • From deciding on the number of ERC-20 tokens to be created to fixing each token’s price, the owner of the NFT makes all major decisions regarding the fractionalization process.
  • An open sale is then organized for the fractional shares at a fixed price for a set period or until they are sold out.

Enough talking. Let’s get to the coding!​

We will need two smart contracts to achieve this task.

  • NFT smart contract: This is a simple NFT smart contract
  • Fractionalized NFT smart contract: This smart contract fractionalizes the NFT and gives out ERC-20 token. Users can also buy NFT from this NFT. Once a sale is made, ERC-20(fraction) holders can redeem their tokens with ETH.

Go to Open Zepplin wizard and create an ERC-721 token: https://docs.openzeppelin.com/contracts/4.x/wizard

It looks like this.

Open Zepplin Wizard](https://docs.openzeppelin.com/contracts/4.x/wizard)

Give your NFT smart contract name and symbol. Make sure you select Ownable from the menu.

Below is the code for ERC-721 smart contract.

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

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyToken is ERC721, Ownable {
constructor() ERC721("MyToken", "MTK") {}
}

You can paste it in Remix IDE.

Now I will explain line-by-line this contract.

// SPDX-License-Identifier: MIT

Above line specifies the license. In our case, it is an MIT license.

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

Above two are import statements which are importing ERC721 and Ownable smart contract.

contract  MyToken  is  ERC721, Ownable {

constructor() ERC721("MyToken", "MTK") {}

}

Name of contract is MyToken which inherits ERC721 and Ownable.

We have a constructor, a special function that runs when a smart contract is created.

ERC721 is taking two arguments, token name and token symbol.

After we are done with creating the NFT smart contract, it’s time to create Fractionalized NFT smart contract.

Below is the code.

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

import "@openzeppelin/contracts@4.6.0/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts@4.6.0/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts@4.6.0/access/Ownable.sol";
import "@openzeppelin/contracts@4.6.0/token/ERC20/extensions/draft-ERC20Permit.sol";
import "@openzeppelin/contracts@4.6.0/token/ERC721/utils/ERC721Holder.sol";

contract FractionalizedNFT is ERC20, Ownable, ERC20Permit, ERC721Holder {
IERC721 public collection;
uint256 public tokenId;
bool public initialized = false;
bool public forSale = false;
uint256 public salePrice;
bool public canRedeem = false;

constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}

function initialize(address _collection, uint256 _tokenId, uint256 _amount) external onlyOwner {
require(!initialized, "Already initialized");
require(_amount > 0, "Amount needs to be more than 0");
collection = IERC721(_collection);
collection.safeTransferFrom(msg.sender, address(this), _tokenId);
tokenId = _tokenId;
initialized = true;
_mint(msg.sender, _amount);
}

function putForSale(uint256 price) external onlyOwner {
salePrice = price;
forSale = true;
}

function purchase() external payable {
require(forSale, "Not for sale");
require(msg.value >= salePrice, "Not enough CELO sent");
collection.transferFrom(address(this), msg.sender, tokenId);
forSale = false;
canRedeem = true;
}


function redeem(uint256 _amount) external {
require(canRedeem, "Redemption not available");
uint256 totalCelo = address(this).balance;
uint256 toRedeem = _amount * totalCelo / totalSupply();
_burn(msg.sender, _amount);
payable(msg.sender).transfer(toRedeem);
}
}

Let’s try to understand what this smart contract does.

Here FractionalizedNFT inherits ERC20, Ownable, ERC20Permit, ERC721Holder.

IERC721  public  collection;
uint256 public tokenId;
bool public initialized = false;
bool public forSale = false;
uint256 public salePrice;
bool public canRedeem = false;

collection variable of type IERC721 holds NFT smart contract address we deployed earlier. tokenId of type uint256 holds the id of the NFT.

initialized, forSale, canRedeem of type bool are flags. initialized flag is is used tell that price of NFT is set, NFT address is stored in collection variable, tokenId gets initialized, NFT gets transferred from owner to smart contract and ERC-20 tokens are minted for the NFT owner. forSale flag is used to tell that NFT is for sale. canRedeem flag is used to tell that NFT is sold and users can redeem their ERC-20 token. salePrice is the price of the NFT.

constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}

This is the function it runs when the smart contract is deployed. ERC20 takes the token name and token symbol as an argument, while ERC20Permit only takes the token name as an argument.

ERC20Permit is EIP specification which allows users to approve token transfers without spending gas fees.

function  initialize(address  _collection, uint256  _tokenId, uint256  _amount) external  onlyOwner {
require(!initialized, "Already initialized");
require(_amount > 0, "Amount needs to be more than 0");
collection = IERC721(_collection);
collection.safeTransferFrom(msg.sender, address(this), _tokenId);
tokenId = _tokenId;
initialized = true;
_mint(msg.sender, _amount);
}

function initialize is taking NFT address, _tokenId and _amount. The owner can only invoke this function.

It checks if the contract is already initialized. If not, it checks for the other condition, checking amount sent is greater than 0. Here amount refers to total number of ERC-20 tokens that are going to be minted.

collection variable of type IERC721 gets initialized Here collection variable holds address of an NFT. safeTransferFrom function transfers the NFT from the owner to the smart contract, i.e., itself. tokenId variable stores the token id. initialized flag is marked true. _mint function mints erc20 tokens to the owner.

function  putForSale(uint256 price) external  onlyOwner {
salePrice = price;
forSale = true;
}

putForSale function takes price as an argument. It sets the salePrice variable of type uint256. It is the price of an NFT. Also, the function makes the forSale flag to be true.

function  purchase() external  payable {
require(forSale, "Not for sale");
require(msg.value >= salePrice, "Not enough CELO sent");
collection.transferFrom(address(this), msg.sender, tokenId);
forSale = false;
canRedeem = true;
}

purchase function is a payable function, i.e., you can send CELO to this function.

First, it checks if the forSale flag is true and if the sent CELO is greater than or equal to the sale price. If both the conditions are fulfilled, then NFT is transferred from the NFTFractionalized contract to the buyer who sent the CELO.

forSale flag is set to false, and now people can redeem their ERC-20 tokens with CELO. This flag is set to true.

function  redeem(uint256 _amount) external {
require(canRedeem, "Redemption not available");
uint256 totalCelo = address(this).balance;
uint256 toRedeem = _amount * totalCelo / totalSupply();

_burn(msg.sender, _amount);
payable(msg.sender).transfer(toRedeem);
}

redeem function takes the amount as an argument. People can send either partial or complete ERC-20 tokens they are holding for that NFT.

First, it checks if canRedeem flag is set to true. Then it checks the total CELO smart contract that is holding. Then it calculates the amount of CELO that needs to be redeemed. It burns the sent ERC-20 token and transfers the calculated CELO to the caller of this function.

Here is the final code: https://github.com/avirajkhare00/nft-fractionalization

Frontend

Once we are done with coding our smart contracts, let’s get started with our Frontend.

For the frontend we will use the following libraries:

  • create-react-app
  • Web3
  • @celo/contractkit

Frontend code is already done. Please clone this repo: https://github.com/avirajkhare00/celo-fractionalized-nft-ui

We will create a single page so that anyone can purchase the NFT by sending the required CELO amount.

Those holding ERC-20 tokens of fractionalized NFT can redeem them back to the CELO.

Let’s get started with the coding part.

We will edit the page App.jsOur UI and logic will go on that page.

We need to take a look at two functions already defined inside src/App.js

1. buyNFT()

export async function buyNFT() {
await window.celo.enable();
const web3 = new Web3(window.celo);
let kit = newKitFromWeb3(web3);
const accounts = await kit.web3.eth.getAccounts();
const user_address = accounts[0];
const contract = new kit.web3.eth.Contract(
FractionalizedNFTABI,
contractAddress
);
let result = await contract.methods.purchase().send({ from: user_address });
console.log(result);
}

When we trigger this function inside our dApp. Anyone will be able to buy the NFT by paying the price.

2. redeemNFT()

export async function redeemNFT() {
let amount = document.getElementById("redeemNFTAmount").value;
console.log(amount);
await window.celo.enable();
const web3 = new Web3(window.celo);
let kit = newKitFromWeb3(web3);
const accounts = await kit.web3.eth.getAccounts();
const user_address = accounts[0];
const contract = new kit.web3.eth.Contract(
FractionalizedNFTABI,
contractAddress
);
let result = await contract.methods
.redeem(amount)
.send({ from: user_address });
console.log(result);
}

People will be able to get the CELO back once they send in their ERC-20 tokens to the contract. Actually, they don’t have to send the tokens. They just need to execute the function, their tokens will be burned inside the contract.

Final frontend looks like this.

Fractionalized dApp

When you open up this page. CeloExtensionWallet will ask you to connect to this site.

Below is a small video on how this functionality works.

Since I don’t have enough CELO, I won’t be able to buy the NFT, but if you have enough CELO, you will be able to buy it.

Here is the link to demo: https://youtu.be/mVknObb1bEc

I hope you loved this tutorial.

Thank You

About the Author

  • Aviraj Khare: Ex Gojek, Into Web3 space since 2016

References

Go back

· 6 min read

Send, pay, and spend cryptocurrency like everyday money — all from the palm of your hand.

header

Hello Developers 🌱

Welcome to today’s post, where we’ll break down a blockchain topic into bite-sized pieces to help you learn and apply your new skills in the real world.

Today’s topic is Getting started with Valora on Celo.

Here’s a list of what we’ll cover 🗒

  • Step 1: Download Valora
  • Step 2: Create your Valora account
  • Step 3: Use the Valora app

By the end of this post, you’ll be up and running with Valora and ready to send Celo assets to your friends directly from your mobile phone.

Let’s go! 🚀

Introduction to Valora

Valora is a mobile wallet focused on making global peer-to-peer payments simple and accessible to anyone. It’s built on Celo and allows your to use your mobile phone number to send payments to your friends.

image

Valora syncs to your contacts list, which means you can make payments, send international remittances or simply split the bill with someone in your contact list — no matter where they are in the world.

✅ Step 1: Download Valora

To get started you’ll download Valora app. It is available in the App Store for iOS devices and Play Store for Android devices (links available on our home page.

image

Open the Valora app once it is done installing on your mobile device.

image

Read and navigate through the introductory screens and click Get Started.

image

✅ Step 2: Create your Valora account

From your Valora app, select create a new account to start setting up your new wallet. You may also Restore an account if you have an existing Valora account.

image

Terms and Conditions

Read and accept the terms and conditions.

image

Name and Profile

Create a name for your account and add a profile photo.

image

Pin Number

Create a memorable but secure pin number. You will need your PIN whenever you make a transaction, so make it memorable but not easy to guess.

image

Face ID

Click Turn on Face ID if you would like to secure your wallet and make it easier to access Valora.

image

Connect your phone number

Finally, connect your phone number to Valora. You will need to connect your own phone number if you wish to send funds directly to the phone number of your friends and family members.

image

You’ll receive 3 confirmation texts, each with a unique code. Just enter these in the respective fields. Hold tight, as the codes might take a few seconds to arrive. Once your number is connected, you can sync your contact information.

✅ Step 3: Use the Valora app

At this point, you have completed your account setup and can begin using Valora. Click Add Funds on the popup to fund your account from a debit card, bank account, or cryptocurrency exchange.

image

For now, you may close this popup to skip this step use Valora without adding funds. You’ll instead add funds later after you create your recovery phrase.

Select the menu on the top left of your app to view other screens on the Valora app. These screens include home, CELO, Dapps, Recovery Phrase, Add & Withdraw, Settings, and Help. This menu also shows your account address which you may share with others to receive transactions on Valora.

image

Home

You can send or request payments with your contacts from the home screen! To sync your contact list, tap send on your Valora home screen to send funds from a users name, phone number, address, or QR code.

image

Search and select the contact you want to send funds to and follow the prompts on the screen. If your contact doesn’t have Valora yet, don’t worry, the funds will be kept safe until they create their Valora account.

CELO

The CELO screen allows you to view the current price of CELO and your current CELO balance. You may also withdraw CELO and view all of your CELO activity from this screen.

image

Dapps

The Dapp screen is Valora’s Dapp explorer that allows you to view all Celo applications that integrate with Valora. You may use your Celo assets to make use of any application listed on this menu.

image

Selecting a Dapp will take you to its home page where you can learn more about getting started with the Dapp and its Valora integration.

Recovery Phrase

Set this up before funding your account. Your Valora recovery phrase is an extremely important part of your account. Without this phrase you may not be able to recover your account in the future, and with this phrase anyone can access your account.

image

Read more aboutwhat your Recovery Phrase is and why it is important.

Add & Withdraw

Now that you have a recovery phrase, you may add or withdraw Celo assets at any time using your debit card, bank account, or cryptocurrency exchange. Use the link provided for additional details on funding your account.

image

You can also send Celo assets to your account from any other wallet using the Account Address shown on the bottom of your menu.

Settings

The settings page allows you to manage important account information like your profile, security, data, and legal information. Use this anytime you’d like to edit important account details.

image

Help

The help page links you to frequently asked questions, forum, and contact information to allow you to get support from the Valora team. Use this anytime you run into issues with Valora or would like to contact the Valora team.

image

Supercharge

By adding Celo Dollars or Celo Euros to your account, you will automatically start earning rewards on those balances. Select the Rewards icon from the menu to learn more about supercharging your rewards.

image

Congratulations 🎉

That wraps up today’s topic on Getting started with Valora on Celo. You can review each of the items we covered below and check that you’re ready to apply these new skills.

Here’s a quick review of what we covered 🤔

  • Step 1: Download Valora
  • Step 2: Create your Valora account
  • Step 3: Use the Valora app

If you made it this far, you downloaded Valora, created an account, and understand how to use each of its basic features. You’re now ready to send Celo assets to your friends from your mobile phone.

GN! 👋

Go back

· 6 min read

How to deploy a smart contract to Celo testnet, mainnet, or a local network using Hardhat.

header

Hello Developers 🌱

Welcome to today’s post, where we’ll break down a blockchain topic into bite-sized pieces to help you learn and apply your new skills in the real world.

Today’s topic is How to quickly build an NFT collection on Celo.

Here’s a list of what we’ll cover 🗒

  • Step 1: Connect to MetaMask
  • Step 2: Upload your NFT with Pinata
  • Step 3: Create your Smart Contract (low-code)
  • Step 4: Deploy your Smart Contract
  • Step 5: Mint your NFT

By the end of this post, you’ll have an NFT collection built using MetaMask, Celo, Pinata, IPFS, OpenZeppelin, and Remix. These tools allow you to create, deploy, and manage NFTs quickly without writing any code.

Let’s go! 🚀

Before getting started

This post uses a variety of tools to help you quickly deploy your NFT on Celo. While the post allows you to follow along with no background in these tools, it may help to review some of the links below.

✅ Step 1: Connect to MetaMask

MetaMask allows you to connect to the Celo blockchain from your browser. To get started, install the Metamask browser extension.

image

  • Add Alfajores Testnet to MetaMask using the manual setup option here
  • Add CELO to your test account using the Alfajores Testnet Faucet
tip

Learn more: Check out 3 Simple Steps to Connect your MetaMask Wallet To Celo for more details.

✅ Step 2: Upload your NFT ​with Pinata

Pinata allows you to easily upload files to IPFS to use as your NFT.

image

To get started, go to Pinata and log in or sign up for an account.

image

Once logged in, select the + Upload button

image

Choose the files you would like to upload and complete the upload process.

image

You should now see the name and content identifier (CID) hash for each file.

image

You’ll now prepare the token metadata. The example uses a folder named prosper factory metadata. View the contents of the folder here.

image

Select any file to view the NFT metadata from within a browser that supports IPFS (example: Brave Browser) to see the unique image reference.

{
"image": "ipfs://QmVKCcW7c5aUs3GzW92atgFUz7N6rox7EzeibEbyJ6jBMi"
}

Next, create a folder containing metadata for each NFT you’d like to create. Upload a folder containing all of the token metadata to Pinata. This will make your NFT data publicly available.

image

This example uses a folder name prosper factory metadata. You can view the contents of the folder here. The folder contains 14 files, numbered 0–13. The names of these files are important. These file names correspond to the token ID of each NFT that will be created by the contract. Make sure that there are no extensions (.txt, .json, .jpeg, .png) on your files.

✅ Step 3: Create your Smart Contract (low-code)​

Now that your NFT image is on IPFS, you’ll create and deploy a smart contract. In this post, you’ll use OpenZeppelin, a well known smart contract library to create your smart contract without writing any code.

image

Select ERC721 as your token choice and learn more about ERC721 here.

image

  • Enter your token information (name, symbol).
  • Enter the Content Identifier (CID) from Pinata as the Base URI

image

  • Select Mintable and Auto increment ids. This gives each NFT a unique identifier that increments as new NFTs are minted.
  • Select Ownable. This restricts minting to the owner of the contract. The owner is the address you used to deploy your smart contract.

image

✅ ​Step 4: Deploy your Smart Contract

Remix is an online tool that allows you to develop, deploy, and manage smart contracts on Celo. Now that your contract is complete, you’ll use Remix to interact with your smart contract.

image

  • From the OpenZeppelin Wizard, click Open in Remix.
  • Remix will open with your OpenZeppelin contract available.
  • Click the blue button labeled Compile Contract.

image

After compiling the contract, click the Ethereum logo on the left panel to open the Deploy & Run transactions tab.

image

  • In the Environment dropdown, select Injected Web3 to connect Remix to MetaMask.
  • Check that MetaMask is connected to the correct network (example: Alfajores Testnet). This network will appear as Custom (44787).

image

Select the contract you would like to deploy (example: ProsperityFactory).

image

  • Click Deploy and confirm the transaction from MetaMask.
  • View your deployed contract from the dropdown on the bottom left corner of Remix.

image

Expand the dropdown to see each of your contract’s functions.

✅ Step 5: Mint your NFT

You’re finally ready to mint your NFT!

  • Call the safeMint function using your wallet address as the input.
  • Confirm the transaction in MetaMask to mint your first NFT.

image

You can verify that the token was minted by calling the tokenURI function with the expected token ID. Calling the contract with tokenURI = 0 will return the NFTs IPFS reference

image

This IPFS reference will show the token metadata for that NFT.

Example Image

{
"image": "ipfs://QmVKCcW7c5aUs3GzW92atgFUz7N6rox7EzeibEbyJ6jBMi"
}

Navigate to the IPFS reference to view the image for the token.

image

Congratulations 🎉

That wraps up today’s topic on how to quickly build an NFT collection on Celo. You can review each of the items we covered below and check that you’re ready to apply these new skills.

Here’s a quick review of what we covered 🤔

  • Step 1: Connect to MetaMask
  • Step 2: Upload your NFT with Pinata
  • Step 3: Create your Smart Contract (low-code)
  • Step 4: Deploy your Smart Contract
  • Step 5: Mint your NFT

Hopefully, you now have an NFT collection built using MetaMask, Celo, Pinata, IPFS, OpenZeppelin, and Remix. Use can now use these tools whenever you’d like to create, deploy, and manage NFTs quickly without writing any code.

GN! 👋

Go back

· 5 min read

How the Celo light client became 1.7 million times lighter than Ethereum.

header

Hello Developers 🌱

Welcome to today’s post, where we’ll break down a blockchain topic into bite-sized pieces to help you learn and apply your new skills in the real world.

Today’s topic is Plumo: An Ultralight Blockchain Client on Celo.

Here’s a list of what we’ll cover 🗒

  • ✅ Introduction to Plumo
  • ✅ Why is Plumo important?
  • ✅ A Simple Plumo Demonstration
  • ✅ Additional Plumo Resources

By the end of this post, you’ll have a basic introduction to Plumo, tried a demo showcasing Plumo’s functionality, and have resources to help you learn more about Celo’s ultralight blockchain client.

Let’s go! 🚀

✅ Introduction to Plumo

Celo has announced the arrival of Plumo, Celo’s advanced ZK-snark-based light client protocol. Plumo, meaning feather in Esperanto, lives up to its name as an incredibly light client for the Celo blockchain.

Previously, Celo’s previous light client protocol was around 17,000 times lighter than Ethereum’s light client protocol. With Plumo, Celo is now 1.7 million times lighter.

With Plumo, Celo is now 1.7 million times lighter than Ethereum!

Anyone can now generate Plumo snark proofs that let dApps sync with the chain in a fully trustless manner. cLabs is hosting the first server that’s generating these proofs daily but anyone can run one of these.

Celo has also announced the launch of a WASM based library that lets web apps verify Plumo proofs, sync with the chain, and then verify state that’s happening as it’s fetched from full nodes on the network.

This means that for the first time in the crypto industry, applications will be able to connect to the P2P network, connect to the chain in a fully trustless manner, sync near instantly and verify state that they can request from any full node. All this is possible without having to have any trust assumptions, with those full nodes.

✅ Why is Plumo important?

Syncing the latest state of a blockchain can be a resource-intensive task, driving users towards centralized services offering easier access. To expand decentralized services to anyone with a mobile phone, Celo created a consensus-agnostic compiler for constructing ultralight clients. This provides secure and highly efficient blockchain syncing via a sequence of SNARK-based state transition proofs with formally proven security.

Plumo allows Celo nodes sync to the Celo blockchain faster with less data.

Devices can sync the latest network state summary in just a few seconds even on a low-end mobile phone. Using Plumo, each transition proof covers four months of blockchain history and can be produced for just \$25.

✅ A Simple Plumo Demonstration

You can see Plumo in action here using the Celo Wallet.

Create New Account

Once on the site, either Create New Account or Use Existing Account if you would like to use your Celo account. Follow the instructions provided to get your account details, set your password, and login to your wallet.

image

Your Celo Wallet

Once logged in, you’ll be able to view your account details.

image

Do you notice anything amazing? 🤔

Not yet?

Select the button that says Connected on the bottom right of your screen.

image

This will show you the Connection Status. Take note of the Last Block Number and close this window. Here it is 12946675.

image

Wait a few seconds… 🦗🦗🦗

Now select the Connected button again!

image

You should see that the Last Block Number is showing a newer block than it was before. In the image above the block is 12946680…an entire 5 blocks have synced since last checked.

What you’re seeing is one of many possible examples of Plumo in action. It’s live syncing Celo network data to your device in real-time using an ultralight client. This could happen on a computer, a tablet, a phone, and even a cheap phone with low bandwidth-and before Plumo it could never happen so fast.

✅ Additional Plumo Resources

A large amount of research, development, and innovation has gone into creating this light client, and there are many resources you can explore to learn more. Here are a few to help you get started.

Plumo Whitepaper

This whitepaper describes the design of Plumo as a method to develop scalable interoperable blockchains using ultra light validation systems.

image

tip

Learn more: Plumo Whitepaper

Plumo Documentation

Introduction to Plumo Ultralight Sync, its core concepts, architecture, process, and implementation.

image

tip

Celo Tech Talks Plumo

In this Celo Tech Talk, Michael Straka, Cryptography Engineering Partner will introduce you to the Plumo Protocol.

tip

Watch more: Kobi Gurkan on Plumo & Kobi Gurkan on Optimistic SNARK setups for Plumo

Additional Resources

Congratulations 🎉

That wraps up today’s topic on Plumo: An Ultralight Blockchain Client on Celo. You can review each of the items we covered below and check that you’re ready to apply these new skills.

Here’s a quick review of what we covered 🤔

  • ✅ Introduction to Plumo
  • ✅ Why is Plumo important?
  • ✅ A Simple Plumo Demonstration
  • ✅ Additional Plumo Resources

At this point, you’ve read a basic introduction to Plumo, tried a demo showcasing Plumo’s functionality, and have resources to help you access more details about Celo’s ultralight blockchain client.

GN! 👋

Go back

· 8 min read

The idea with this tutorial is not build complex app is show new possibilities to use Celo and other EVM (Ethereum virtual machine) with languages other than JavaScript in this case C#, You will learning how build an android app with C# and connect to Celo network to retriever NFT metadata and display NFT in the app.

header