Skip to content

Handling Orders using Web3 and Ethereum

One of the big draws to blockchain is that you can execute large financial transactions in minutes instead of days or weeks. One day, you could be able to sell your house in 15 minutes and all of the ownership information will be stored securely on the blockchain.

That’s why we’re going to build a small ordering Dapp to show how this concept works. We’ll create a Dapp that lets users create orders on the blockchain, including payments. All of this will be neatly wrapped in a Redwood app and use Cloudinary for our image hosting.

There are a few things we need to install before we touch the code. First, you’ll need to install Ganache. This is a free app that lets us develop smart contracts and Dapps against a local Ethereum network. That way we don’t have to worry about paying for transactions on the blockchain.

Next, we’ll actually create the Redwood app we’ll use for our Dapp. In a terminal, run the following command:

$ yarn create redwood-app order-dapp

This will generate the front-end code in the web directory and back-end code in the api directory we’ll need to make this app. There are just a couple more things we need to install. In your terminal, go to the web directory and install these packages.

$ yarn add truffle truffle-contract web3

These three packages are going to help us interact with the local Ethereum network with our smart contract and also the front-end. Now we have everything installed and set up so we can dive into writing a smart contract.

We’re going to use Truffle to set up our smart contract. So in your terminal go to the web directory and run the following command:

$ truffle init

This will create a new contracts folder inside the web directory. If you take a look in the contracts folder, you’ll see an initial smart contract called Migrations.sol.

It sets the owner of the smart contract to the address that deploys it and gives us access to some restricted functionality for the smart contract.

Now we’ll be making our own smart contract with the functionality to handle the orders users want to make. That way you’ll see some of the differences in blockchain development instead of back-end development.

We need to make a config file to handle the connections to the local EVM. In the web directory add or update the truffle-config.js file. In this file, add the following code:

module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 7545,
      network_id: "*",
    },
  },
  compilers: {
    solc: {
      optimizer: {
        enabled: true,
        runs: 200
      },
    }
  },
};
Code language: JavaScript (javascript)

The networks object defines the connection to the local EVM network. You can get all of this info from the server settings in Ganache, but these are the default values. We have enabled the optimizer for the compiler to make our smart contract run smoother.

That’s all we need to connect to the network!

In the contracts folder, add a new file called OrderMaker.sol. This is where we’ll define the function to add orders to the blockchain and make orders accessible to our Dapp.

Open this file and add the following line:

pragma solidity ^0.5.0;
Code language: CSS (css)

Every smart contract starts with the version we’re using for the compiler. Then we’ll define the contract itself.

contract OrderMaker {

}
Code language: CSS (css)

There are some similarities between Solidity and JavaScript and a little C++. You’ll see them as we fill in this contract. Inside the contract, let’s add a struct that defines the order.

struct Order {
  string itemName;
  uint256 price;
  uint256 quantity;
}

This is very similar to an interface in TypeScript. It’s a type that defines the data we expect in an order. Next, we’ll initialize a global array that holds all of the orders for the smart contract.

Order[] private orders;
Code language: CSS (css)

This is a private variable which means it can’t be accessed outside of the contract. Then we’ll add a constructor that creates an initial order on the blockchain.

constructor() public {
  _createOrder('Jalapeno', 9, 3);
}
Code language: JavaScript (javascript)

A constructor in a smart contract only gets executed one time, when the contract is deployed to the network. There are a few things that we’ll define before we get to the _createOrder, but it’s coming up.

For now, let’s define a couple of mappings for the orders that can be accessed outside of the contract like in our Dapp.

mapping(address => Order) public ordersByUser;
mapping(uint256 => Order) public ordersById;
Code language: JavaScript (javascript)

A mapping in Solidity is like an object in JavaScript. An example of the ordersById mapping would look like this in JavaScript.

let ordersById = {
  1: {
    itemName: 'Jalapeno',
    price: 9,
    quantity: 4
  },
}
Code language: JavaScript (javascript)

After these mappings, we need to make a helper function to get the price of the items in the orders users make. This will introduce an interesting thing with Solidity.

function _lookupPrice(string memory _itemName)
  private
  pure
  returns (uint256 price)
{
  if (
    keccak256(abi.encodePacked(_itemName)) ==
    keccak256(abi.encodePacked('Jalapeno'))
  ) {
    return 9;
  } else if (
    keccak256(abi.encodePacked(_itemName)) ==
    keccak256(abi.encodePacked('Feta'))
  ) {
    return 12;
  } else if (
    keccak256(abi.encodePacked(_itemName)) ==
    keccak256(abi.encodePacked('Water'))
  ) {
    return 18;
  } else if (
    keccak256(abi.encodePacked(_itemName)) ==
    keccak256(abi.encodePacked('Lemon'))
  ) {
    return 15;
  } else {
    return 5;
  }
}
Code language: JavaScript (javascript)

If you take a look, we’re comparing strings to decide what price to return. In Solidity, there’s no direct way to handle string comparison. That’s why we have all of the keccak256(abi.encodePacked(_itemName)) == keccak256(abi.encodePacked('Jalapeno')) statements. It compares the hashes of the strings and is how we handle string comparison in Solidity smart contracts.

Now we can define that function we called in the constructor to add an initial order.

function _createOrder(
  string memory _itemName,
  uint256 _quantity,
  uint256 _price
) internal {
  orders.push(Order(_itemName, _price, _quantity));

  uint256 id = orders.length;

  ordersByUser[msg.sender] = orders[id - 1];

  ordersById[id] = orders[id - 1];
}
Code language: JavaScript (javascript)

This is an internal function which means only this contract and contracts that inherit this one can call this _createOrder function. We pass in the three inputs needed to make a new order. Then we add the new order to the private orders array, get an id, and add the order to our public mappings.

The last thing to add is the function that lets users create orders from our Dapp.

function createOrder(string memory _itemName, uint256 _quantity) public payable {
  require(msg.value == 0.001 ether);
  uint256 itemPrice = _lookupPrice(_itemName);
  _createOrder(_itemName, _quantity, itemPrice);
}
Code language: JavaScript (javascript)

One important thing to note is that this function has the payable modifier. That means a user is able to send a payment along with their request. This is part of what makes blockchain technology so interesting. It’s the only way to securely send money with online transactions.

Can you imagine trying to send money along with an API request? That’s one of the problems blockchain solves.

We have the truffle-config.js file in web which sets up the connection to the local Ethereum network. If you’re running Ganache like we set up earlier, these are the network defaults. This is also where you would define connections to test networks or the real Ethereum network.

With this config file updated and the smart contracts written, we can deploy these contracts to the local EVM. In your terminal in the web directory, run this command:

$ truffle migrate

You should see some output in your terminal that tells you about the smart contracts you just deployed. Make sure that you get the contract address for the OrderMaker contract. We’ll need that to connect the Dapp to this smart contract.

You’ve deployed a smart contract to the EVM! All that’s left is creating the front-end code and giving users access to the functionality of the smart contract.

In your terminal, go to the root of the project and run the following command:

$ yarn redwood generate page order

This will create a new directory called OrderPage in web > src > pages. In this new folder, you’ll see several files: a test file, a Storybook story, and the component file. Open the OrderPage.tsx file. This is where we’ll make the user interface.

Before we do that, there’s one more config file we need to set up to access the smart contract on our local Ethereum network.

In the web > src directory, add a new file called config.tsx. We’ll have two variables in this file. The first will be the address for the smart contract we deployed. At the top of your file, add the following code but be sure to use your contract address:

export const ORDER_MAKER_ADDRESS = '0x369B41C6951B712e0Cb9c3e13E2737999Ef202c2'
Code language: JavaScript (javascript)

The next variable we need to add is the ABI (application binary interface) for the smart contract. When you deployed the smart contract a couple of steps back, the web > build > contracts directory was added. In this directory, you’ll find OrderMaker.json.

Open that file and copy the abi value and add it to the config.tsx file with our address. The code will look like this:

export const ORDER_MAKER_ABI: any = [
  {
    "constant": true,
    "inputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "ordersById",
    "outputs": [
      {
        "name": "itemName",
        "type": "string"
      },
      {
        "name": "price",
        "type": "uint256"
      },
      {
        "name": "quantity",
        "type": "uint256"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function",
    "signature": "0x2c4cb11a"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "",
        "type": "address"
      }
    ],
    "name": "ordersByUser",
    "outputs": [
      {
        "name": "itemName",
        "type": "string"
      },
      {
        "name": "price",
        "type": "uint256"
      },
      {
        "name": "quantity",
        "type": "uint256"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function",
    "signature": "0x933f7ae8"
  },
  {
    "inputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "constructor",
    "signature": "constructor"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_itemName",
        "type": "string"
      },
      {
        "name": "_quantity",
        "type": "uint256"
      }
    ],
    "name": "createOrder",
    "outputs": [],
    "payable": true,
    "stateMutability": "payable",
    "type": "function",
    "signature": "0x97de706f"
  }
]
Code language: JavaScript (javascript)

Think of the contract address as the API we would connect to and the ABI is like the definition for the functions and parameters we’re able to interact with. Now we can work on the OrderPage component.

We generated our page earlier so you can open the OrderPage.tsx file and delete all of the existing code. We’ll start fresh by adding the imports we’ll need to make this Dapp work. At the top of your file, add the following code:

import { useEffect, useState } from 'react'
import Web3 from 'web3'

import { ORDER_MAKER_ABI, ORDER_MAKER_ADDRESS } from '../../config'
Code language: JavaScript (javascript)

Then we can add the shell for the OrderPage component. Add this code to your file:

const OrderPage = () => {

}

export default OrderPage
Code language: JavaScript (javascript)

Now we can start filling in this component to finish the logic and UI for users to work with.

We’ll add the states that we need to handle rendering. Inside the component, add this code:

const [account, setAccount] = useState<string>('')
const [order, setOrder] = useState(null)
const [orderMaker, setOrderMaker] = useState(null)

const web3 = new Web3('http://localhost:7545')
Code language: JavaScript (javascript)

The main thing to note is that we’re creating a new instance of Web3 that connects to the local EVM. This is similar to how you might define a connection to an API.

Then we need to set the states for our component based on the data we get from our blockchain connection. To do that, add this code below the EVM connection.

useEffect(() => {
  loadData()
}, [])

const loadData = async () => {
  const accounts = await web3.eth?.getAccounts()
  setAccount(accounts[0])

  const orderMaker = new web3.eth.Contract(
    ORDER_MAKER_ABI,
    ORDER_MAKER_ADDRESS
  )
  setOrderMaker(orderMaker)

  const order = await orderMaker.methods.ordersById(1).call()
  setOrder(order)
}
Code language: JavaScript (javascript)

We’re calling this loadData function in the useEffect hook one time when the component is loaded to get the values we need from the EVM.

Then we define the loadData function. This starts by getting all of the account addresses that are on the network and we’re taking the first one to work with. In a production Dapp, you’d have a more sophisticated way to work with addresses.

Next, we create an object that lets us access the methods and parameters available from the OrderMaker smart contract we deployed. We do that by using the contract address and ABI from our configs to connect to this specific smart contract on the network. This is like calling a specific endpoint on an API.

After that, we get the first order from the smart contract. This was created in the constructor function so we’d have something to read from the blockchain initially. We call the ordersById mapping with an id value.

One thing to note is that we use the call method for read requests from the blockchain. This will be an important difference when you see what it looks like to create a new order. We’ll add the function to handle this below the loadData function.

const createOrder = async (event) => {
  event.preventDefault()

  const { itemName, quantity } = event.target.elements

  await orderMaker.methods.createOrder(itemName.value, quantity.value).send({
    from: account,
    gas: 4712388,
    value: web3.utils.toWei("0.001")
  })
}
Code language: JavaScript (javascript)

This createOrder function will take the values from a form submission and store the user’s order on the blockchain. The main thing to note is how we make this call to the smart contract.

We’re passing the values from the form as the parameters. Then we call the send method to execute this transaction on the blockchain. The user’s account address is passed into the send method so we know who executed the request.

Since this is a write request, we need to send some gas with the request. That’s why we’re working with a local EVM so that we do have to spend real Ether.

We also need to send a payment with the request because we are calling the payable function from the smart contract. We’re using toWei because this smallest Ether unit and is the standard for working with payments.

All that’s left is adding the UI. You are welcome to make something far nicer than what we’re about to create, but all of the core functionality will be in place.

We’ll finish this component by adding the return statement with the elements we want to show the user. Add this code below the createOrder function.

return (
  <>
    <h1>Order from EVM</h1>
    {order &&
      <>
        <div>Item: {order.itemName}</div>
        <div>Price: {order.price}</div>
        <div>Quantity: {order.quantity}</div>
      </>
    }
    <form onSubmit={createOrder}>
      <div>
        <label htmlFor='itemName'>Item Name:</label>
        <input name='itemName' type='text' />
      </div>
      <div>
        <label htmlFor='quantity'>Amount:</label>
        <input name='quantity' type='number' />
      </div>
      <button type='submit'>Submit</button>
    </form>
  </>
)
Code language: HTML, XML (xml)

If we have an order set in the component, it’ll render the info directly from the blockchain. We also have a form that will allow us to make new orders on the blockchain by calling the function from our smart contract.

Here’s what that page should look like when you run your app. In a terminal, go to the root of your project and run the following command:

$ yarn redwood dev

This will start the dev server and you should see something like this in your browser.

order from the EVM and the form to make new orders

Now you have a fully functional Dapp that stores new info on the blockchain and reads existing info into the browser for your users to see!

You can find the complete code in the order-dapp folder of this repo.

You can also check out the front-end code in this Code Sandbox.

Blockchain technology is just starting to gain traction so there are a lot of differences in jargon and programming workflows that will take time to adjust to. Now is a great time to start learning so that you can be one of the early developers!

Back to top

Featured Post