ThriftBooks Smart Contract: Decentralized Marketplace for Selling Used Books on Celo

ThriftBooks Smart Contract: Decentralized Marketplace for Selling Used Books on Celo https://celo.academy/uploads/default/optimized/2X/0/0c9dd7c8e51007671e8c575d8226e01a6df5ba73_2_1024x576.png
none 0.0 0

Introduction

Welcome to the ThriftBook Marketplace tutorial! In this tutorial, we will walk through the process of building a Book Marketplace smart contract using Solidity. The goal is to create a decentralized platform where users can buy and sell thrift books securely.

Requirement

To successfully follow this tutorial, you will need:

  • A code editor or text editor, such as Remix or any other Solidity development environment.
  • An internet browser and a reliable internet connection to access the necessary tools and resources.

By having these tools ready, you can effectively write, compile, and deploy the Marketplace contract in the subsequent steps of this tutorial.

Prerequisite

To successfully complete this tutorial, it is recommended that you have the following:

  • Familiarity with the JavaScript programming language, as it will help you understand the concepts and syntax used in the code.
  • A basic understanding of blockchain technology and how it works, including concepts like smart contracts and decentralized applications.
  • Basic knowledge of the Solidity programming language, which is used for developing smart contracts on the Ethereum blockchain. This includes understanding data types, functions, and contract deployment.

To start this tutorial, we will use the Remix IDE as our development environment for writing the smart contract. We will guide you through the process of setting up Remix and creating the contract step by step. Let’s get started and embark on this exciting journey of building a ThriftBook Marketplace!

Smart Contract

The complete code for this tutorial:

  // SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";


interface IERC20Token {
   function transfer(address, uint256) external returns (bool);

    function approve(address, uint256) external returns (bool);

    function transferFrom(
        address,
        address,
        uint256
    ) external returns (bool);

    function totalSupply() external view returns (uint256);

    function balanceOf(address) external view returns (uint256);

    function allowance(address, address) external view returns (uint256);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
}
 
 contract  Thriftbooks {
    
    
    uint internal booksLength = 0;
    address internal cUsdTokenAddress =  0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1;

    struct  Book {
        address payable owner;
        string url;
        string title;
        string author;
         uint price;
         uint like;
         
      
    }
      mapping (uint =>  Book) internal books;
      //  function use to add books
     function  addBook(
        string memory _url, 
        string memory _title,
         string memory _author,
        uint _price
       
        
        
    ) public {
         uint _like = 0;

         books [booksLength] =  Book(
            payable(msg.sender),
            
            _url,
            _title,
            _author,
            _price,
             _like
            
             
        );

        booksLength++;
    
    }

    //function is used to edit  book
     function editBook(
        uint256 _index,
        string memory _url,
        string memory _title,
        string memory _author,
        uint _price
      
    ) public {
          uint _like = books[_index].like;
        require(msg.sender == books[_index].owner, "Error"); 
        books[_index] =  Book(
            payable(msg.sender),
            _url,
            _title,
            _author,
            _price,
             _like
            
        );
    }

     function getBook(uint _index) public view returns (
        address payable,
        string memory,
        string memory,  
        string memory,
        uint,
        uint
        
      
    ) {
        return (  
            books[_index].owner,
              books[_index].url,
            books[_index].title, 
             books[_index].author,
              books[_index].price,
               books[_index].like


                   
        );
    }

    function deleteBook(uint _index) external {
	        require(msg.sender == books[_index].owner, "Only owner can delete book");
	        delete(books[_index]);
	    }

          function buyBook(uint _index) public payable  {
        require(
          IERC20Token(cUsdTokenAddress).transferFrom(
            msg.sender,
            books[_index].owner,
             books[_index].price
          ),
          "Transfer failed."
        );

       books[_index].owner = payable(msg.sender);
         
    }

     function Like (uint _index)public{
        books[_index].like ++;
    } 

    function getbooksLength() public view returns (uint) {
        return (booksLength);
    }
}


Step 1: Project Introduction and Contract Setup

In this tutorial, we will guide you through the process of building a Book Marketplace smart contract using Solidity. The goal is to create a decentralized platform where users can buy and sell books securely.

To begin, we need to set up the project and define the necessary contract structure. We start by specifying the required SPDX license identifier and the Solidity version range:

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

Additionally, we import the SafeMath library from the OpenZeppelin contracts library. This library provides secure mathematical operations that we will utilize in our contract:

import "@openzeppelin/contracts/utils/math/SafeMath.sol";

By importing this library, we can make use of its functions for arithmetic calculations with enhanced safety features.

This concludes step 1 of our tutorial. In the next step, we will proceed with defining the contract’s state variables and data structures.

Step 2: Defining the ERC20 Token Interface

In this step, we will define an interface for an ERC20 token. The interface will specify the required functions and events that the token contract must implement to be compatible with our Book Marketplace contract.

interface IERC20Token {
   function transfer(address, uint256) external returns (bool);

    function approve(address, uint256) external returns (bool);

    function transferFrom(
        address,
        address,
        uint256
    ) external returns (bool);

    function totalSupply() external view returns (uint256);

    function balanceOf(address) external view returns (uint256);

    function allowance(address, address) external view returns (uint256);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
}
  • To interact with ERC20 tokens, we need to define the following functions in our interface:

  • transfer: Allows the transfer of tokens from one address to another.

  • approve: Approves the spender to spend a specific amount of tokens on behalf of the owner.

  • transferFrom: Transfers tokens from the sender’s address to another address, given the sender has been approved to spend tokens on behalf of the owner.

  • totalSupply: Retrieves the total supply of tokens in circulation.

  • balanceOf: Retrieves the token balance of a specific address.

  • allowance: Retrieves the remaining token balance that the spender is allowed to transfer on behalf of the owner.

  • We also specify two events:

  • Transfer: Triggered when tokens are transferred from one address to another.

  • Approval: Triggered when the approval status for token transfer is changed.
    By defining this interface, we ensure that any token contract we interact with adheres to the ERC20 standard and provides the required functionality.

This concludes step 2 of our tutorial. In the next step, we will proceed with implementing the core functionality of our Book Marketplace contract.

Step 3: Defining the Book Structure and Mapping

In this step, we will define the structure of a book and create a mapping to store our books in the Thriftbooks contract.

contract  Thriftbooks {
    
    
    uint internal booksLength = 0;
    address internal cUsdTokenAddress =  0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1;

    struct  Book {
        address payable owner;
        string url;
        string title;
        string author;
         uint price;
         uint like;
         
      
    }
      mapping (uint =>  Book) internal books;

We start by declaring an internal variable booksLength to keep track of the number of books in our marketplace. This variable will be used to assign unique identifiers to each book.

Next, we declare an internal variable cUsdTokenAddress which represents the address of an ERC20 token contract. This address will be used for handling payments within the marketplace.

We define a struct called Book to represent the details of a book. It includes the following properties:

  • owner: An address payable representing the owner of the book, who can receive payments.

  • url: A string representing the URL of the book.

  • title: A string representing the title of the book.

  • author: A string representing the author of the book.

  • price: An unsigned integer representing the price of the book.

  • like: An unsigned integer representing the number of likes received by the book.

We create a mapping called books, which maps a unique identifier (uint) to a Book struct. This mapping allows us to store and retrieve book information efficiently.

By using this structure and mapping, we can easily manage and track the books in our marketplace.

This concludes step 3 of our tutorial. In the next step, we will implement functions to add, edit, and retrieve books from the marketplace.

Step 4: Adding a Book to the Marketplace

In this step, we will implement a function called addBook in the ``Thriftbooks` contract. This function allows users to add a new book to the marketplace.

function  addBook(
        string memory _url, 
        string memory _title,
         string memory _author,
        uint _price
       
        
        
    ) public {
         uint _like = 0;

         books [booksLength] =  Book(
            payable(msg.sender),
            
            _url,
            _title,
            _author,
            _price,
             _like
            
             
        );

        booksLength++;
    
    }
  • The addBook function takes several parameters: _url (string), _title (string), _author (string), and _price (uint). These parameters represent the URL, title, author, and price of the book to be added.

Inside the function, we initialize a local variable _like to 0, indicating that the book has no likes yet.

Next, we assign the book details to the books mapping at the booksLength index. The booksLength acts as the unique identifier for the book. We create a new instance of the Book struct, initializing its properties with the provided parameters and the _like value.

After storing the book in the mapping, we increment the booksLength variable to ensure the next book added will have a different identifier.

By calling this function, users can add their books to the marketplace, providing the necessary details such as the URL, title, author, and price.

This completes step 4 of our tutorial. In the next step, we will implement functions to edit and retrieve book information from the marketplace.

Step 5: Editing a Book in the Marketplace

In this step, we will implement a function called editBook in the Thriftbooks contract. This function allows the owner of a book to edit its details in the marketplace.

 function editBook(
        uint256 _index,
        string memory _url,
        string memory _title,
        string memory _author,
        uint _price
      
    ) public {
          uint _like = books[_index].like;
        require(msg.sender == books[_index].owner, "Error"); 
        books[_index] =  Book(
            payable(msg.sender),
            _url,
            _title,
            _author,
            _price,
             _like
            
        );
    }

The editBook function takes several parameters: _index (uint256), _url (string), _title (string), _author (string), and _price (uint). These parameters represent the index of the book to be edited and the updated details of the book.

Inside the function, we first retrieve the current number of likes for the book from the books mapping using the provided _index.

Next, we use a require statement to check if the caller of the function is the owner of the book. If the condition is not met, an error is thrown, and the function execution stops.

If the caller is the owner of the book, we update the book’s details in the books mapping. We assign a new instance of the Book struct to the existing book at the specified _index, replacing its previous details with the updated values provided as function parameters. We also keep the _like value unchanged.

By calling this function with the appropriate parameters, the owner of a book can modify its URL, title, author, and price in the marketplace.

This completes step 5 of our tutorial. In the next step, we will implement a function to retrieve the details of a book from the marketplace.

Step 6: Retrieving and Deleting a Book from the Marketplace

In this step, we will implement two functions in the Thriftbooks contract: getBook and deleteBook. These functions allow users to retrieve the details of a book and delete a book from the marketplace, respectively.

function getBook(uint _index) public view returns (
        address payable,
        string memory,
        string memory,  
        string memory,
        uint,
        uint
        
      
    ) {
        return (  
            books[_index].owner,
              books[_index].url,
            books[_index].title, 
             books[_index].author,
              books[_index].price,
               books[_index].like  
        );
    }

    function deleteBook(uint _index) external {
	        require(msg.sender == books[_index].owner, "Only owner can delete book");
	        delete(books[_index]);
	    }

The getBook function takes an _index parameter of type uint and is declared as a public view function. It returns a tuple containing the following information about the book: the owner's address (payable), the URL (string), the title (string), the author (string), the price (uint), and the number of likes (uint).

Inside the function, we simply access the corresponding fields of the book stored in the books mapping at the given _index. We retrieve the owner's address, URL, title, author, price, and number of likes. We then return these values as a tuple.

The deleteBook function is declared as external and takes an _index parameter of type uint. It allows the owner of a book to delete it from the marketplace.

First, we use a require statement to check if the caller of the function is the owner of the book at the specified _index. If the condition is not met, an error is thrown, and the function execution stops.

If the caller is the owner of the book, we use the delete keyword to remove the book from the books mapping at the given _index. This effectively deletes the book from the marketplace.

By calling the getBook function with a valid _index, users can retrieve the details of a book stored in the marketplace. The deleteBook function allows the owner to remove a book from the marketplace entirely.

This completes step 6 of our tutorial. In the next step, we will implement a function to allow users to purchase a book from the marketplace.

Step 7: Buying a Book and Liking a Book

In this step, we will implement two functions in the Thriftbooks contract: buyBook and Like. These functions allow users to purchase a book from the marketplace and like a book, respectively.

function buyBook(uint _index) public payable  {
        require(
          IERC20Token(cUsdTokenAddress).transferFrom(
            msg.sender,
            books[_index].owner,
             books[_index].price
          ),
          "Transfer failed."
        );

       books[_index].owner = payable(msg.sender);
         
    }

     function Like (uint _index)public{
        books[_index].like ++;
    } 

The buyBook function takes an _index parameter of type uint and is declared as a public function. It requires the caller to send a specified amount of payment to purchase the book. The payment is made using an ERC20 token, indicated by the IERC20Token interface.

Inside the function, we use a require statement to ensure that the transfer of the specified payment from the caller’s address to the owner’s address is successful. The transferFrom function of the ERC20 token contract is called, specifying the caller’s address as the sender, the owner’s address as the recipient, and the book’s price as the transfer amount.

If the transfer is successful, we update the owner of the book at the given _index to the address of the caller, indicating that the caller is now the new owner of the book.

The Like function takes an _index parameter of type uint and is declared as a public function. It allows users to like a book in the marketplace.

Inside the function, we simply increment the like counter of the book at the specified _index by one.

By calling the buyBook function with a valid _index, users can purchase a book from the marketplace by transferring the specified payment to the book’s owner. The Like function allows users to increase the number of likes for a book.

This completes step 7 of our tutorial. In the next step, we will implement a function to retrieve the total number of books in the marketplace.

Step 8: Retrieving the Total Number of Books

In this final step of our tutorial, we will add a function to the Thriftbooks contract that allows users to retrieve the total number of books available in the marketplace.

  function getbooksLength() public view returns (uint) {
        return (booksLength);
    }
}

The getbooksLength function is a public function that returns the value of the booksLength variable, which represents the total number of books stored in the marketplace.

By calling the getbooksLength function, users can obtain the current count of books available for purchase or exploration in the marketplace.

This concludes the tutorial on building a Book Marketplace smart contract. We have covered the essential functions needed to add, edit, retrieve, and purchase books in the marketplace, as well as the ability to like books. You can further enhance this contract by adding additional features such as search functionality, sorting, or user ratings.

Feel free to experiment, customize, and extend the functionality of this contract to suit your specific requirements.

Click here to get the complete code for this session

Contract Deployment

To deploy the Thriftbooks smart contract on the Celo blockchain, you will need to have the following prerequisites:

To ensure a smooth deployment of our smart contract, it is essential to download the Celo extension wallet from the given link, Celo Extension wallet. Once done, the next step is to fund the wallet that we have created, Celo faucet. This can be accomplished by accessing the Celo Alfojares faucet using the provided link.

With a funded wallet, we can now deploy the smart contract using the Celo plugin in Remix. The Celo plugin provides a convenient way to interact with the Celo blockchain directly from Remix’s interface.

Conclusion

In conclusion, we have developed a Book Marketplace smart contract called Thriftbooks that allows users to add, edit, retrieve, purchase, and like books within the marketplace. The smart contract utilizes various functionalities such as data structures, mappings, modifiers, and events to ensure secure and efficient operations.

By using the Remix IDE and incorporating the OpenZeppelin SafeMath library, we have created a robust and reliable smart contract. The contract supports the interaction with an ERC20 token, allowing users to buy books using the specified token.

Throughout the tutorial, we have followed a step-by-step approach, explaining each function and modifier in detail, and providing a comprehensive overview of the contract’s capabilities. We have also utilized the we perspective to create a more engaging learning experience.

By understanding and applying the concepts covered in this tutorial, you now have the foundation to build and customize your own book marketplace or expand upon the existing functionalities. Smart contracts like this can empower authors, readers, and book enthusiasts to engage in a decentralized and transparent marketplace for books.

Remember to continue exploring and learning about smart contract development to enhance your skills and knowledge. With further customization and additional features, you can adapt this project to suit your unique requirements and create an innovative and user-friendly book marketplace.

Next Step

Great job! It’s always helpful to provide additional resources for further learning. Don’t hesitate to reach out if you have any more questions or if you need further assistance. You can also connect with me on Discord @adammm#0518 if you have any follow-up questions or want to continue the conversation. Happy learning and best of luck with your decentralized marketplace project!

About the Author

I’m Adama Ebenezer, a web3 developer from Nigeria with a strong passion for blockchain technology. I’m excited to leverage my skills and knowledge to contribute to the blockchain space.

5 Likes

Hi @adamms i will be reviewing this

1 Like

Hi @adamms your tutorial looks good. remove some unnecssary spaces before the Next step and about author header

1 Like

And also add a github repo to your tutorial

3 Likes

Hi @thompsonogoyi1t i am done making changes

1 Like

ok great you can move to publish

1 Like

An interesting article i must say.

Good job brother,:clap:

2 Likes

A detailed tutorial
Good job.

1 Like

fixing capitalisation

Useful topic. Interesting read. Kudos

I love how you explained the code. It gives insight into the contract. And the code compiles successfully.

10 Likes