This article provides a guide on building a Celo-based NFT marketplace that is both scalable and competitive. We will start by discussing scalability, emphasizing the importance of scalability and building using a scalable architecture and Celo’s SDK to optimize the marketplace for high output.
The second aspect of this tutorial is going to be about standing out from the competition by offering unique features such as auctions, bidding, and custom minting. The article includes code samples and practical insights to help developers build a robust and unique NFT marketplace.
A Step-by-Step Guide to Building a Celo-based NFT Marketplace
Introduction
In this Tutorial you will learn how to Build a Celo-based NFT Marketplace that can handle high volumes of transactions using solidity. Every step of the way we will guide you through implementing new features like such as auctions, bidding and custom minting. Overall we will guide you through Building a Celo-based NFT marketplace that is scalable and competitive in the current market.
Prerequisites
To begin, you should have a fundamental understanding of the following before you can build a Celo-based NFT Marketplace
-
Blockchain Fundamentals: Understand the basic concepts of blockchain technology, including decentralized networks, consensus mechanisms, smart contracts, and token standards like ERC-721 and ERC-1155. Familiarize yourself with the Celo blockchain and its unique features.
-
Smart Contract Development: Have experience in developing smart contracts using Solidity or similar programming languages. Understand concepts like contract deployment, function implementation, event handling, and interacting with blockchain data.
-
Web Development: Possess strong skills in web development technologies like HTML, CSS, and JavaScript. Familiarity with popular front-end frameworks like React or Angular will be helpful for building the user interface of your marketplace.
-
Back-End Development: Have knowledge of server-side technologies such as Node.js, Express, and databases like MongoDB. Understanding RESTful APIs, server setup, routing, and data storage will be essential for building the back-end of your marketplace.
-
Integration with Wallets and External Services: Have knowledge of integrating blockchain wallets (e.g., Celo Wallet, MetaMask) and interacting with external services like IPFS (InterPlanetary File System) for decentralized storage. Understand how to handle wallet connections, transactions, and integrating with other APIs or payment gateways.
Requirements
The technologies below will be required to build a Celo-based NFT Marketplace:
-
Celo Development Kit (CDK): Utilize the Celo Development Kit for building Celo-based smart contracts.
-
OpenZeppelin: Use the OpenZeppelin library to implement standard NFT contracts (e.g., ERC-721 or ERC-1155).
-
Web Development: web development technologies like React, JavaScript, and HTML/CSS to build the front-end interface.
-
web3.js: web3.js library to interact with the Celo blockchain and the deployed smart contracts.
-
Node.js: Node.js as the server-side runtime environment.
-
Express.js: Build the backend using the Express.js framework.
-
MongoDB: Integrate MongoDB as the database system for storing user data, NFT metadata, transaction history, etc.
-
User Authentication and Authorization: Implement user authentication and authorization using Passport.js or JWT (JSON Web Tokens).
Steps and processes for Building a Celo-based NFT Marketplace
Step 1: Define Requirements:
Feature Set:
We’ll determine the core features our marketplace will offer. This includes NFT creation, buying and selling, auctions, bidding, collections, and exploring NFTs.
Consider additional features like royalties, secondary market support, social features (profiles, comments, likes), and integration with external services (wallets, payment gateways, IPFS).
User Roles and Permissions:
Identify different user roles in our marketplace, such as creators, buyers, sellers, and administrators.
Define the permissions and access levels for each role. For example, creators should have the ability to mint NFTs, sellers can list NFTs for sale, and administrators have overall control of the platform.
NFT Metadata and Standards:
Determine the metadata attributes associated with NFTs in your marketplace, such as title, description, image, artist name, and additional custom attributes.
Choose the NFT standard you’ll be using, such as ERC-721 or ERC-1155. Consider the standard’s compatibility with the Celo ecosystem and existing tooling.
Search and Filtering:
Define the search and filtering capabilities within our marketplace. Users should be able to browse and search for NFTs based on attributes like title, category, artist, price, and popularity.
Consider implementing advanced filtering options to enhance the user experience, such as sorting by recently added, highest bids, or trending NFTs.
Wallet Integration:
Decide how users will interact with their wallets on the Celo blockchain. Determine the wallet type (Celo Wallet) and integrate the necessary functionality for connecting, authorizing transactions, and managing NFTs.
Security and Compliance:
We’ll define the security measures to implement so as to protect user assets and data and as well Consider practices such as secure key management, two-factor authentication, and SSL encryption.
Ensure our marketplace complies with any applicable regulatory requirements, such as Know Your Customer (KYC) or Anti-Money Laundering (AML) regulations, if necessary.
Scalability and Performance:
Assess the scalability requirements of our marketplace, considering potential growth in user base and NFT transactions.
Determine strategies to handle increased load, such as utilizing off-chain or layer 2 solutions, optimizing smart contracts, and employing efficient database architectures.
UI/UX Design:
Define the visual and interactive design of our marketplace, keeping in mind the target audience and the overall branding of our platform.
Focus on creating an intuitive and user-friendly interface that allows users to easily navigate, explore, and interact with NFTs.
Localization and Internationalization:
Determine if our marketplace will support multiple languages and cater to a global audience.
Step 2: Smart Contract Development
Once you’ve defined your requirements, you can begin developing the smart contracts that will power our NFT marketplace. You’ll need to set up the development environment for Celo blockchain, write and deploy smart contracts that handle NFT creation, ownership, and trading, and implement contract functionality for minting, transferring, and verifying NFTs.
Smart Contract:
The smart contract will define the NFT standard, manage the creation and ownership of NFTs, and implement functionality like buying and selling NFTs.
ERC-721 compliant smart contract for Celo:
//solidity
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract MyNFT is ERC721 {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
constructor() ERC721("MyNFT", "MNFT") {}
function mint(address recipient, string memory tokenURI) public returns (uint256) {
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(recipient, newItemId);
_setTokenURI(newItemId, tokenURI);
return newItemId;
}
}
This smart contract uses the OpenZeppelin library to implement the ERC-721 standard, which allows for the creation and management of unique NFTs.
Step 3: NFT Sales and Purchases
In order to facilitate purchases and sales of NFTs, we need to add additional functionality to our smart contract. This includes allowing the owner of an NFT to list it for sale and specifying a price, as well as allowing other users to purchase listed NFTs.
Here, we will use the OpenZeppelin library’s ERC721 and Ownable contracts. The Ownable contract provides authorization control functionalities. It simplifies the implementation of “user permissions”.
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyNFT is ERC721, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
mapping(uint => uint) public tokenPrice;
constructor() ERC721("MyNFT", "MNFT") {}
function mint(address recipient, string memory tokenURI) public onlyOwner returns (uint256) {
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(recipient, newItemId);
_setTokenURI(newItemId, tokenURI);
return newItemId;
}
function setPrice(uint _tokenId, uint _price) public {
require(_isApprovedOrOwner(_msgSender(), _tokenId), "ERC721: caller is not owner nor approved");
tokenPrice[_tokenId] = _price;
}
function buyToken(uint _tokenId) public payable {
require(msg.value >= tokenPrice[_tokenId], "Not enough Ether to purchase the token.");
require(ownerOf(_tokenId) != msg.sender, "You are the owner of this token.");
address tokenOwner = ownerOf(_tokenId);
payable(tokenOwner).transfer(msg.value);
_transfer(tokenOwner, msg.sender, _tokenId);
}
}
Here, mint
function can only be called by the owner of the contract which is set when the contract is deployed.
The setPrice
function is used by the owner of a token to list it for sale with a specific price.
The buyToken
function is used by any user to buy a listed token. The function first checks if the caller has sent enough Ether to cover the price. If the check passes, it transfers the Ether to the current owner of the token and then transfers the token to the buyer.
Step 4: Backend Development
Here’s the web3.js script that allows a user to connect their Celo wallet, retrieve their account address, and interact with the smart contract:
//javascript
const Web3 = require('web3');
const Contract = require('web3-eth-contract');
const web3 = new Web3(new Web3.providers.HttpProvider('https://celo.infura.io/v3/YOUR_PROJECT_ID'));
// Connect to smart contract
const contractAddress = '0x123456...';
const contractABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"string","name":"tokenURI","type":"string"}],"name":"mint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}];
const contractInstance = new Contract(contractABI, contractAddress);
// Connect to Celo wallet
const cUSDContractAddress = '0x765DE816845861e75A25fCA122bb6898B8B1282a';
const cUSDContractABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}
],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}];
const cUSDContractInstance = new Contract(cUSDContractABI, cUSDContractAddress);
async function connectWallet() {
const accounts = await web3.eth.getAccounts();
const account = accounts[0];
console.log('Connected account:', account);
}
async function mintNFT() {
const tokenURI = 'https://example.com/nft-metadata';
const result = await contractInstance.methods.mint(web3.eth.defaultAccount, tokenURI).send({ from: web3.eth.defaultAccount });
console.log('NFT minted:', result);
}
connectWallet();
mintNFT();
This code uses the web3.js library to interact with the Celo blockchain and the deployed smart contract. It connects to the user’s Celo wallet, retrieves the account address, and allows the user to mint an NFT by calling the mint
function of the smart contract.
The back-end of our NFT marketplace will handle transactions and interactions with our smart contracts, implement APIs for integrating with external services, and set up servers and infrastructure to support the marketplace’s scalability requirements.
We are going to use server-side technologies like Node.js, Express, and MongoDB to build our back-end.
Server Setup:
Install Node.js and initialize a new Node.js project.
Set up a server framework, such as Express.js, using the following code:
//javascript
Copy code
const express = require('express');
const app = express();
const port = 3000;
// Add your middleware and routes here
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
This code sets up an Express server that listens on port 3000.
Smart Contract Integration:
Install the necessary dependencies, including the web3.js library, to interact with the Celo blockchain and smart contracts.
Connect to the Celo network and instantiate a web3 object with the following code:
//javascript
Copy code
const Web3 = require('web3');
// Connect to Celo network
const web3 = new Web3('https://celo.infura.io/v3/YOUR_PROJECT_ID');
This code establishes a connection to the Celo network using Infura as the provider.
Transaction Handling:
Implement functions or endpoints to handle transactions, such as buying, selling, and transferring NFTs.
Here’s an example of a buy NFT endpoint using Express.js:
//javascript
const contractAddress = '0x123456...'; // Address of the deployed NFT contract
app.post('/nfts/buy', async (req, res) => {
const { tokenId, price } = req.body;
const account = req.user.account; // Assuming user authentication is implemented
// Approve the contract to spend user's cUSD
const cUSDContract = new web3.eth.Contract(cUSDContractABI, cUSDContractAddress);
await cUSDContract.methods.approve(contractAddress, price).send({ from: account });
// Call the buy function on the NFT contract
const nftContract = new web3.eth.Contract(nftContractABI, contractAddress);
await nftContract.methods.buy(tokenId).send({ from: account, value: price });
res.sendStatus(200);
});
User Authentication and Authorization:
Implement user authentication and authorization using Passport.js or JWT (JSON Web Tokens).
//javascript
const passport = require('passport');
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
// Parse incoming request bodies
app.use(bodyParser.json());
app.use(passport.initialize());
// Replace this with your own function to fetch a user from your database
async function getUserById(userId) {
// Implement your logic to fetch user from database
}
passport.use(
new JwtStrategy(
{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'your-secret-key', // make sure this key is safe and secure
},
async (payload, done) => {
const user = await getUserById(payload.userId);
if (user) {
return done(null, user);
}
return done(null, false);
}
)
);
This code sets up JWT authentication using Passport.js. It verifies the token in the request’s authorization header and fetches the user’s data, which can be used in protected routes.
Database Integration:
- Integrate a database system to store user data, NFT metadata, transaction history, etc.
- Here’s an example of using MongoDB with the Mongoose library:
javascript
const mongoose = require('mongoose');
// Connect to MongoDB
mongoose.connect('mongodb://localhost/nft-marketplace', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
// Define a User schema and model
const userSchema = new mongoose.Schema({
name: String,
email: String,
password: String,
});
const User = mongoose.model('User', userSchema);
// Example usage: Creating a new user
const user = new User({
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
});
user.save();
Here is the complete code for Step 4:
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const { create, getNFTs, buyNFT } = require('./nftContract');
// Parse incoming request bodies
app.use(bodyParser.json());
// Create a new NFT
app.post('/nfts', async (req, res) => {
const { name, description, image, price } = req.body;
try {
const nftId = await create(name, description, image, price);
res.json({ success: true, nftId });
} catch (error) {
console.error('An error occurred while creating NFT:', error);
res.status(500).json({ success: false, error: 'Failed to create NFT.' });
}
});
// Get the list of NFTs
app.get('/nfts', async (req, res) => {
try {
const nfts = await getNFTs();
res.json(nfts);
} catch (error) {
console.error('An error occurred while fetching NFTs:', error);
res.status(500).json({ error: 'Failed to fetch NFTs.' });
}
});
// Buy an NFT
app.post('/nfts/:id/buy', async (req, res) => {
const { id } = req.params;
try {
const success = await buyNFT(id);
if (success) {
res.json({ success: true });
} else {
res.status(400).json({ success: false, error: 'Failed to buy NFT.' });
}
} catch (error) {
console.error('An error occurred while buying NFT:', error);
res.status(500).json({ success: false, error: 'Failed to buy NFT.' });
}
});
// Start the server
const port = 3000;
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});
Step 5: Frontend Development
We are going to develop the user interface for the marketplace using React, Integrate with the backend API to fetch data and perform transactions.
Using the web3.js library to interact with the Celo blockchain and smart contracts.
//.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My NFT Marketplace</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>My NFT Marketplace</h1>
<div id="nfts-container"></div>
<script src="script.js"></script>
</body>
</html>
//.css
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
}
h1 {
text-align: center;
}
.nft-card {
display: flex;
align-items: center;
border: 1px solid #ccc;
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
}
.nft-card img {
width: 100px;
height: 100px;
object-fit: cover;
margin-right: 10px;
}
.nft-card-content {
flex: 1;
}
.nft-card h2 {
margin: 0;
}
.nft-card p {
margin: 5px 0;
}
.nft-card button {
padding: 5px 10px;
background-color: #4caf50;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
//javascript
document.addEventListener("DOMContentLoaded", async () => {
const nftsContainer = document.getElementById("nfts-container");
// Fetch the list of NFTs
const response = await fetch("/nfts");
const nfts = await response.json();
// Display the NFTs
nfts.forEach(nft => {
const card = createNFTCard(nft);
nftsContainer.appendChild(card);
});
});
function createNFTCard(nft) {
const card = document.createElement("div");
card.classList.add("nft-card");
const image = document.createElement("img");
image.src = nft.image;
card.appendChild(image);
const content = document.createElement("div");
content.classList.add("nft-card-content");
const title = document.createElement("h2");
title.textContent = nft.name;
content.appendChild(title);
const description = document.createElement("p");
description.textContent = nft.description;
content.appendChild(description);
const price = document.createElement("p");
price.textContent = "Price: " + nft.price + " ETH";
content.appendChild(price);
const buyButton = document.createElement("button");
buyButton.textContent = "Buy";
buyButton.addEventListener("click", () => buyNFT(nft.id));
content.appendChild(buyButton);
card.appendChild(content);
return card;
}
async function buyNFT(nftId) {
try {
const response = await fetch(`/nfts/${nftId}/buy`, { method: "POST" });
if (response.ok) {
alert("NFT purchased successfully!");
} else {
alert("Failed to purchase NFT.");
}
} catch (error) {
console.error("An error occurred while purchasing NFT:", error);
alert("An error occurred while purchasing NFT.");
}
}
This code connects to the backend API and fetches a list of NFTs. It then renders the list in the UI and provides a button to buy an NFT. When the button is clicked, it sends a request to the backend to buy the NFT, passing the NFT ID and authentication token in the request header.
Step 6: Prepare Backend Server
Configure your backend server, such as setting up load balancing, scaling, and securing the server environment.
Here’s an example of setting up a production-ready Express.js server using the express-cluster package for load balancing:
//javascript
const cluster = require('express-cluster');
const express = require('express');
const app = express();
const port = 3000;
// Add your middleware and routes here
cluster(function(worker) {
app.listen(port, () => {
console.log(`Worker ${worker.id} listening on port ${port}`);
});
}, { count: 4 }); // Number of worker processes
cluster.run();
This code sets up a clustered Express.js server with four worker processes to handle incoming requests and improve performance.
Deploy Backend Code:
Deploy your backend code to a production server or cloud hosting platform.
# docker-compose.yml
version: '3'
services:
app:
build: .
ports:
- '3000:3000'
environment:
- NODE_ENV=production
This configuration file defines a Docker Compose service that builds and deploys the backend application. It maps port 3000 from the host to the container to make the application accessible.
Deploy Frontend Code:
Deploy your frontend code to a suitable hosting platform.
Here’s an example of deploying a React application to Netlify using a continuous deployment process:
yaml
# netlify.toml
[build]
command = "npm run build"
publish = "build/"
This configuration file specifies the build and publish settings for Netlify. When you push your code to a repository connected to Netlify, it will automatically build and deploy your frontend application.
Set Up Domain and SSL:
Set up domain configurations and SSL certificates to enable secure access to your marketplace using Let’s Encrypt and Nginx.
nginx
server {
listen 80;
server_name example.com;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
location / {
proxy_pass http://localhost:3000; // Forward requests to your backend server
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
This Nginx configuration sets up HTTP to HTTPS redirection and enables SSL for secure communication. Replace example.com with your actual domain, and provide the paths to your SSL certificate and private key.
Monitor and Manage:
Monitor and manage your deployed application to ensure its stability, availability, and performance.
Use monitoring tools like Prometheus, Grafana, or AWS CloudWatch to collect and analyze performance metrics.
Implement logging and error tracking using services like Loggly, Sentry, or the ELK Stack (Elasticsearch, Logstash, and Kibana).
Set up alerts and notifications to proactively address any issues or anomalies in your application’s performance or availability.
Conclusion
This tutorial has taught us how to Build a Celo-based NFT Marketplace that can handle high volumes of transactions using solidity. After defining the requirements, we developed the Smart Contract Development, developed the Front-end and Backend and deployed them. We also
discussed Monitoring and managing our deployed application to ensure its stability, availability, and performance.
About the Author
Hello there! I’m a passionate Website Developer with a profound love for the ever-evolving cryptocurrency ecosystem. My fascination with the seamless intertwining of technology and finance is what led me into the exciting world of blockchain and crypto development.
Connect with me on LinkedIn