Decentralized Fashion Place Booking System on Celo

Decentralized Fashion Place Booking System on Celo https://celo.academy/uploads/default/optimized/2X/6/67989c3e1dcd7139ce622697ddf99fef7b4aaadd_2_1024x576.png
none 0.0 0

Introduction

Welcome to the fashionHub smart contract tutorial! In this tutorial, we’ll explore how to create a decentralized fashion place booking platform using Ethereum and Solidity.

The fashionHub contract allows users to:

  • Add fashion places with details like name, image, description, location, and price.
  • Book a fashion place for a specific duration, making it temporarily unavailable to other users.
  • End a booking or cancel a reservation.
  • Check fashion place details and availability.
  • Set the availability status of a fashion place.
  • Payments are facilitated using the ERC-20 token interface, specifically the cUSD token. This tutorial will guide you through the process of building a decentralized fashion place booking system step by step.

By the end of this tutorial, you’ll have a solid understanding of how to develop smart contracts for similar booking platforms on the Ethereum blockchain. Let’s get started with fashionHub!

Requirement

To take this tutorial, you will need:

  • Access to a code editor or text editor such as Remix.
  • A reliable internet browser and internet connection

Prerequisite

  • Basic knowledge of Javascript.
  • Understand how Blockchain works.
  • Have a basic knowledge of solidity.

Let’s dive into developing our smart contract for the decentralized fashion place booking platform. We’ll start by writing the code for the fashionHub contract using Solidity, the programming language for Ethereum.

In this tutorial, we’ll be creating a smart contract called fashionHub using Solidity. This contract will serve as the foundation for a decentralized fashion place booking platform on the Celo blockchain.

  • Complete code of this session:
// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

// erc-20 interface so the contract can interact withn it
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 fashionHub {
    // initialize length of fashion places
    uint internal fashionPlacesLength = 0;
    // cUSD token address
    address internal cUsdTokenAddress =
        0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1;

    // group fashion place details
    struct FashionPlace {
        address payable owner;
        address currentCustomer;
        string name;
        string image;
        string description;
        string location;
        uint price;
        bool available;
        uint unavailableTill;
    }

    // map each fashion place to an unsigned integer
    mapping(uint => FashionPlace) private fashionPlaces;

    modifier onlyPlaceOwner(uint _index) {
        require(
            fashionPlaces[_index].owner == msg.sender,
            "Only fashion place owner"
        );
        _;
    }

    /// @dev this modifier checks if current timestamp is greater or equal to UnavailableTill
    modifier checkOver(uint _index) {
        require(
            block.timestamp >= fashionPlaces[_index].unavailableTill,
            "Fashion place's current booking isn't over"
        );
        _;
    }

    /// @dev write new fashion place to the contract
    function addFashionPlace(
        string calldata _name,
        string calldata _image,
        string calldata _description,
        string calldata _location,
        uint _price
    ) external {
        require(bytes(_name).length > 0, "Empty name");
        require(bytes(_image).length > 0, "Empty image url");
        require(bytes(_description).length > 0, "Empty description");
        require(bytes(_location).length > 0, "Empty location");
        bool _available = true;
        uint _unavailableTill = 0;
        fashionPlaces[fashionPlacesLength] = FashionPlace(
            payable(msg.sender),
            address(0),
            _name,
            _image,
            _description,
            _location,
            _price,
            _available,
            _unavailableTill
        );
        fashionPlacesLength++;
    }

    /// @dev read a fashion place from smartcontract
    function getFashionPlace(uint _index)
        public
        view
        returns (
            address payable owner,
            address currentCustomer,
            string memory name,
            string memory image,
            string memory description,
            string memory location,
            uint price,
            bool available,
            uint unavailableTill
        )
    {
        owner = fashionPlaces[_index].owner;
        currentCustomer = fashionPlaces[_index].currentCustomer;
        name = fashionPlaces[_index].name;
        image = fashionPlaces[_index].image;
        description = fashionPlaces[_index].description;
        location = fashionPlaces[_index].location;
        price = fashionPlaces[_index].price;
        available = fashionPlaces[_index].available;
        unavailableTill = fashionPlaces[_index].unavailableTill;
    }

    /// @dev function to order or book a fashion place
    ///  @notice fashion place is now unavailable
    function orderFashionPlace(uint _index, uint _time) public payable {
        require(
            fashionPlaces[_index].owner != msg.sender,
            "You can't book your own fashion place"
        );
        require(
            fashionPlaces[_index].available,
            "Fashion place is currently unavailable"
        );
        require(_time > 0, "Duration of booking must be at least one hour");
        fashionPlaces[_index].available = false;
        fashionPlaces[_index].currentCustomer = msg.sender;
        fashionPlaces[_index].unavailableTill = block.timestamp + _time;
        require(
            IERC20Token(cUsdTokenAddress).transferFrom(
                msg.sender,
                fashionPlaces[_index].owner,
                fashionPlaces[_index].price
            ),
            "Transfer failed."
        );
    }

    /// @dev allows a fasion place owner to end the order
    function endOrder(uint _index)
        public
        onlyPlaceOwner(_index)
        checkOver(_index)
    {
        fashionPlaces[_index].unavailableTill = 0;
        fashionPlaces[_index].currentCustomer = address(0);
        fashionPlaces[_index].available = true;
    }
    /// @dev allows a customer to cancel an order
function cancelOrder(uint _index) public {
    require(
        fashionPlaces[_index].currentCustomer == msg.sender,
        "Only current customer can cancel the order"
    );
    require(
        !fashionPlaces[_index].available,
        "Fashion place is already available"
    );
    fashionPlaces[_index].unavailableTill = 0;
    fashionPlaces[_index].currentCustomer = address(0);
    fashionPlaces[_index].available = true;
    require(
        IERC20Token(cUsdTokenAddress).transfer(
            msg.sender,
            fashionPlaces[_index].price
        ),
        "Transfer failed."
    );
}


    /// @dev get length of fashion place
    function getFashionPlacesLength() public view returns (uint) {
        return (fashionPlacesLength);
    }

    /// @dev set fashion place available or not
    function setAvailability(uint _index)
        public
        onlyPlaceOwner(_index)
        checkOver(_index)
    {
        fashionPlaces[_index].available = !fashionPlaces[_index].available;
    }
}

Step 1: Setting Up the Contract Structure

// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

To begin, let’s open a new Solidity file and add the necessary SPDX-License-Identifier at the top of the contract. This ensures that our contract complies with the MIT license.

Next, we specify the version of Solidity we’ll be using (in this case, >=0.7.0 <0.9.0) to ensure compatibility with our contract.

Step 2: Creating the ERC-20 Token Interface

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
    );
}

In this step, we’ll define an interface called IERC20Token. This interface allows our contract to interact with the ERC-20 token, which will be used for payments within our fashion place booking platform.

An interface acts as a blueprint for functions and events that an external contract must implement in order to be compatible with our contract.

Here’s a breakdown of the interface functions and events:

  • transfer: Allows transferring tokens from one address to another.

  • approve: Approves an address to spend a specific amount of tokens on behalf of the sender.

  • transferFrom: Transfers tokens on behalf of the owner, following prior approval.

  • totalSupply: Retrieves the total supply of tokens.

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

  • allowance: Retrieves the remaining token allowance for a spender address.

  • Transfer event: Emits an event when tokens are transferred between addresses.

  • Approval event: Emits an event when an approval for token spending is granted.

Note: In this tutorial, we’ll be utilizing the existing ERC-20 token interface, so we don’t need to implement it ourselves. Let’s proceed to the next step!

Step 3: Defining the Contract Structure and Modifiers

contract fashionHub {
    // initialize length of fashion places
    uint internal fashionPlacesLength = 0;
    // cUSD token address
    address internal cUsdTokenAddress =
        0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1;

    // group fashion place details
    struct FashionPlace {
        address payable owner;
        address currentCustomer;
        string name;
        string image;
        string description;
        string location;
        uint price;
        bool available;
        uint unavailableTill;
    }

    // map each fashion place to an unsigned integer
    mapping(uint => FashionPlace) private fashionPlaces;

    modifier onlyPlaceOwner(uint _index) {
        require(
            fashionPlaces[_index].owner == msg.sender,
            "Only fashion place owner"
        );
        _;
    }

    /// @dev this modifier checks if current timestamp is greater or equal to UnavailableTill
    modifier checkOver(uint _index) {
        require(
            block.timestamp >= fashionPlaces[_index].unavailableTill,
            "Fashion place's current booking isn't over"
        );
        _;
    }

In this step, we’ll define the structure of our fashionHub contract and introduce two modifiers that will be used throughout the contract.

Contract Structure:

  • We initialize a variable called fashionPlacesLength and set it to 0. This variable will keep track of the number of fashion places in our platform.

  • We declare an address variable called cUsdTokenAddress and assign it the address of the cUSD token. This address will be used for payment transactions.

  • Next, we define a struct named FashionPlace to represent the details of each fashion place. It includes attributes such as owner, currentCustomer, name, image, description, location, price, available, and unavailableTill.

  • To store multiple fashion places, we use a mapping called fashionPlaces. It maps an unsigned integer (index) to a FashionPlace struct.

Modifiers:

  • We define a modifier called onlyPlaceOwner which ensures that a function can only be executed by the owner of a specific fashion place. It checks if the msg.sender matches the owner’s address for that place.

  • Another modifier called checkOver verifies if the current timestamp is greater than or equal to the unavailableTill value of a fashion place. This modifier is used to check if a booking period is over.

Note: Modifiers allow us to add reusable conditions to functions in a secure and efficient manner. Now that we have the basic structure and modifiers in place, let’s move on to the next step of our tutorial.

Step 4: Adding Fashion Places to the Contract

function addFashionPlace(
        string calldata _name,
        string calldata _image,
        string calldata _description,
        string calldata _location,
        uint _price
    ) external {
        require(bytes(_name).length > 0, "Empty name");
        require(bytes(_image).length > 0, "Empty image url");
        require(bytes(_description).length > 0, "Empty description");
        require(bytes(_location).length > 0, "Empty location");
        bool _available = true;
        uint _unavailableTill = 0;
        fashionPlaces[fashionPlacesLength] = FashionPlace(
            payable(msg.sender),
            address(0),
            _name,
            _image,
            _description,
            _location,
            _price,
            _available,
            _unavailableTill
        );
        fashionPlacesLength++;
    }

In this step, we’ll implement a function called addFashionPlace that allows users to add new fashion places to our contract.

Here’s a breakdown of the function:

Function Signature:

  • The function is defined as addFashionPlace and takes in several parameters: _name, _image, _description, _location, and _price.

The calldata keyword indicates that the function parameters are passed by reference.

Input Validation:

We include several require statements to validate the inputs provided by the user.
Each require statement checks if the length of the respective string inputs is greater than 0. If any of them are empty, an error message is thrown.

Initializing Fashion Place:

  • We create two local variables: _available (initialized as true) and _unavailableTill (initialized as 0). These variables represent the availability status and the timestamp until which the fashion place is unavailable for booking.

  • Next, we add the fashion place to our fashionPlaces mapping. The fashionPlacesLength acts as the index for the new fashion place, and we assign a new instance of the FashionPlace struct to it. We set the owner as msg.sender, the current customer as the zero address, and assign the remaining input values.

Incrementing Fashion Places Length:

  • After adding the new fashion place, we increment the fashionPlacesLength variable to keep track of the total number of fashion places in our platform.

Now, we have a way to add fashion places to our contract. Let’s move on to the next step of our tutorial!

Step 5: Retrieving Fashion Place Details

User
  function getFashionPlace(uint _index)
        public
        view
        returns (
            address payable owner,
            address currentCustomer,
            string memory name,
            string memory image,
            string memory description,
            string memory location,
            uint price,
            bool available,
            uint unavailableTill
        )
    {
        owner = fashionPlaces[_index].owner;
        currentCustomer = fashionPlaces[_index].currentCustomer;
        name = fashionPlaces[_index].name;
        image = fashionPlaces[_index].image;
        description = fashionPlaces[_index].description;
        location = fashionPlaces[_index].location;
        price = fashionPlaces[_index].price;
        available = fashionPlaces[_index].available;
        unavailableTill = fashionPlaces[_index].unavailableTill;
    }

In this step, we’ll implement a function called getFashionPlace that allows users to retrieve the details of a specific fashion place by providing its index.

Here’s an overview of the function:

Function Signature:

  • The function is defined as getFashionPlace and takes in a parameter _index representing the index of the fashion place we want to retrieve.

  • The function is marked as public and view to indicate that it doesn’t modify the contract’s state and can be called externally.

Returning Fashion Place Details:

  • The function returns multiple values corresponding to the details of a fashion place.

  • We declare variables owner, currentCustomer, name, image, description, location, price, available, and unavailableTill with their respective types.

Inside the function body, we assign the corresponding values from the fashionPlaces mapping at the given _index to these variables.
Finally, we return these variables as the output of the function.

By using the getFashionPlace function, users can retrieve specific details about a fashion place, such as its owner, current customer, name, image, description, location, price, availability status, and the timestamp until it’s unavailable.

Now that we have implemented this functionality, let’s move on to the next step of our tutorial!

Step 6: Ordering a Fashion Place

function orderFashionPlace(uint _index, uint _time) public payable {
        require(
            fashionPlaces[_index].owner != msg.sender,
            "You can't book your own fashion place"
        );
        require(
            fashionPlaces[_index].available,
            "Fashion place is currently unavailable"
        );
        require(_time > 0, "Duration of booking must be at least one hour");
        fashionPlaces[_index].available = false;
        fashionPlaces[_index].currentCustomer = msg.sender;
        fashionPlaces[_index].unavailableTill = block.timestamp + _time;
        require(
            IERC20Token(cUsdTokenAddress).transferFrom(
                msg.sender,
                fashionPlaces[_index].owner,
                fashionPlaces[_index].price
            ),
            "Transfer failed."
        );
    }

In this step, we’ll implement a function called orderFashionPlace that allows users to book a fashion place for a specific duration.

Here’s an overview of the function:

Function Signature:

  • The function is defined as orderFashionPlace and takes in two parameters: _index representing the index of the fashion place to be booked, and _time representing the duration of the booking in hours.

  • The function is marked as public to allow external access.

Input Validation:

  • We include several require statements to validate the conditions for booking a fashion place.

  • The first require statement checks if the caller is not the owner of the fashion place, ensuring that users can’t book their own places.

  • The second require statement checks if the fashion place is currently available for booking.

  • The third require statement checks if the _time parameter is greater than 0, ensuring that the booking duration is at least one hour.

Updating Fashion Place Details:

  • We update the details of the fashion place to reflect the booking. We set the available status to false to mark it as unavailable for other users.

  • The currentCustomer is set to the address of the user who placed the booking (msg.sender).

  • We calculate the unavailableTill timestamp by adding the current timestamp (block.timestamp) with the booking duration (_time).

Token Transfer:

  • We use the transferFrom function from the IERC20Token interface to transfer the required payment (fashionPlaces[_index].price) from the user (msg.sender) to the owner of the fashion place (fashionPlaces[_index].owner).

  • The transferFrom function is called on the cUsdTokenAddress contract address.

By calling the orderFashionPlace function and providing the appropriate parameters, users can book a fashion place, ensuring its availability for the specified duration. The payment for the booking is transferred from the user to the fashion place owner.

Congratulations! We have completed this step of the tutorial. Let’s proceed to the next step!

Step 7: Ending an Order

  function endOrder(uint _index)
        public
        onlyPlaceOwner(_index)
        checkOver(_index)
    {
        fashionPlaces[_index].unavailableTill = 0;
        fashionPlaces[_index].currentCustomer = address(0);
        fashionPlaces[_index].available = true;
    }

In this step, we’ll implement a function called endOrder that allows the owner of a fashion place to end a current booking.

Here’s an overview of the function:

Function Signature:

  • The function is defined as endOrder and takes in one parameter: _index representing the index of the fashion place.

  • The function is marked as public to allow external access.

Modifiers:

  • We include two modifiers in the function signature: onlyPlaceOwner(_index) and checkOver(_index).

  • The onlyPlaceOwner modifier ensures that only the owner of the fashion place can end the booking.

  • The checkOver modifier checks if the current booking duration has ended, based on the unavailableTill timestamp.

Updating Fashion Place Details:

  • We reset the booking details of the fashion place to make it available for future bookings.

  • The unavailableTill timestamp is set to 0, indicating that the fashion place is now available.

  • The currentCustomer address is set to 0x0 (address(0)), signifying that there is no current customer.

The available status is set to true, indicating that the fashion place is available for booking.

By calling the endOrder function and providing the appropriate index of the fashion place, the owner can end the current booking, making the place available for other users.

Congratulations! We have completed this step of the tutorial. Let’s move on to the next step!

Step 8: Canceling an Order

function cancelOrder(uint _index) public {
    require(
        fashionPlaces[_index].currentCustomer == msg.sender,
        "Only current customer can cancel the order"
    );
    require(
        !fashionPlaces[_index].available,
        "Fashion place is already available"
    );
    fashionPlaces[_index].unavailableTill = 0;
    fashionPlaces[_index].currentCustomer = address(0);
    fashionPlaces[_index].available = true;
    require(
        IERC20Token(cUsdTokenAddress).transfer(
            msg.sender,
            fashionPlaces[_index].price
        ),
        "Transfer failed."
    );
}

In this step, we’ll implement a function called cancelOrder that allows the current customer to cancel their booking for a fashion place.

Here’s an overview of the function:

Function Signature:

  • The function is defined as cancelOrder and takes in one parameter: _index representing the index of the fashion place.

  • The function is marked as public to allow external access.

Requirements and Validations:

  • We include two require statements to validate the cancellation conditions:

  • The first require statement ensures that only the current customer who made the booking can cancel the order.

  • The second require statement verifies that the fashion place is not already available for booking.

Updating Fashion Place Details:

  • If the cancellation conditions are met, we proceed to update the fashion place details.

  • We set the unavailableTill timestamp to 0, indicating that the fashion place is now available.

  • The currentCustomer address is set to 0x0 (address(0)), indicating that there is no current customer.

  • The available status is set to true, indicating that the fashion place is available for booking.

Refund:

  • After canceling the order, we initiate a refund to the customer by calling the transfer function of the IERC20Token contract.

  • The customer’s address (msg.sender) receives the refunded amount, which is the price of the fashion place.

  • By calling the cancelOrder function with the appropriate index, the current customer can cancel their booking and receive a refund.

Congratulations! We have completed this step of the tutorial. Let’s move on to the next step!

Step 9: Getting Fashion Places Length and Setting Availability

In this step, we’ll add two functions: getFashionPlacesLength and setAvailability.

Function: getFashionPlacesLength

  • This function allows us to retrieve the length of the fashion places array.

  • It is marked as public and has the view modifier, indicating that it only reads data and doesn’t modify the contract state.

  • The function simply returns the value of fashionPlacesLength, which represents the number of fashion places that have been added to the contract.

Function: setAvailability

  • This function allows the owner of a fashion place to update its availability status.

  • The function takes in one parameter, _index, representing the index of the fashion place.

  • It is marked as public and includes the onlyPlaceOwner and checkOver modifiers to ensure that only the owner can modify the availability and only when the current booking period is over.

  • Inside the function, we toggle the available status of the specified fashion place using the logical NOT operator (!).

If the fashion place was available, it becomes unavailable, and vice versa.

By calling the getFashionPlacesLength function, we can retrieve the total number of fashion places stored in the contract. This information can be useful for displaying statistics or iterating over the fashion places in a frontend application.

The setAvailability function enables the owner of a fashion place to control its availability based on specific conditions. This allows for flexibility in managing the booking and availability of fashion places.

Congratulations! We have completed this step of the tutorial. Let’s move on to the final step and wrap up the tutorial!

Click here to get the complete code for this session

Contract Deployment

To deploy the FashionHub smart contract on the Celo blockchain, you would need the following:

CeloExtensionWallet: Download and install the Celo Extension Wallet from the Google Chrome store, create a wallet, and securely store your key phrase. Click here to intall the celo extension wallet

Celo Faucet: Fund your wallet by copying your wallet address and pasting it into the Celo Faucet, then confirm. Click here to access celo faucet

Celo Remix Plugin: Open Remix and create a new Solidity file, paste the FashionHub contract code into the file, and ensure the Solidity compiler is set to version 0.8.7 or later. Click here to access to access the remix ide

Compile the contract by clicking the Compile FashionHub.sol button in the Solidity Compiler tab in Remix.

In the Deploy & Run Transactions tab, select the Celo network from the dropdown menu, connect your wallet to Remix by clicking Connect to wallet, and select FashionHub from the Contract dropdown menu.

Click the Deploy button, confirm the transaction in your wallet, and wait for the transaction to be confirmed on the Celo blockchain.

Once the transaction is confirmed, the FashionHub contract will be deployed on the Celo blockchain and you can interact with it using Remix.

Conclusion

In this tutorial, we learned how to create a Solidity smart contract for a decentralized fashion hub. We covered adding fashion places, booking and canceling orders, and managing availability. We also used an ERC20 token for payments. By following this tutorial, you gained a practical understanding of smart contract development for decentralized applications. Happy coding!

About the author

I’m David Ikanji, a web3 developer residing in Nigeria, and I have a strong passion for working with blockchain technology.

3 Likes

Approved for you to get started. You can manage the tutorial here by changing the category to Proposals > In Progress then Proposals > Review as you complete the tutorial. Thanks!

1 Like

Hi @Ikanji i will be reviewing this

Hi @Ikanji remove the unnecessary space after the conclusion

@thompsonogoyi1t I am done making changes

Great work. you can move to publish

1 Like

@thompsonogoyi1t ok thanks

1 Like

Nicely written article , welldone.

1 Like

Amazing content👍

2 Likes

Love your detailed explanation. Thanks for contributing this

3 Likes

this is a brand need the right capitalisation