Solidity

The Complete Guide to Full Stack dApp Development

In this tutorial we will build and deploy a full stack dApp (decentralized app) on the Ethereum blockchain, using Solidity, Ethers.js and React.

While it’s easy to find pretty good documentation on each of these topics individually, it’s hard to find a tutorial that wraps all of these together, and that’s exactly the purpose of this post.

The app itself is quite simple – allowing us to store a short message (string) on the Ethereum Blockchain.

The steps for the tutorial are as follows –

  • Build the smart contract in Solidity.
  • Compile the contract using Remix (an online Solidity IDE).
  • Deploy the smart contract to the Ethereum Blockchain with Ethers.js.
  • Write a front end app that interacts with the contract in React.

Let’s get started.

Step 1 – Coding the contract

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

contract MessageStore {
    string message;
    function get() public view returns (string memory) {
        return message;
    }
    function set(string memory _message) public {
        message = _message;
    }
}

What you see above is a source code in Solidity. Solidity is an object-oriented, high-level language for implementing smart contracts. Since smart contracts are preferably open source, the first line in the code indicates the license of the contract. Next, we define the compiler version to be ^0.8.7, meaning the code won’t compile with a compiler earlier than version 0.8.7, and it also won’t compile on a compiler starting from version 0.9.0. This is important since some compiler versions might introduce incompatible changes.

This smart contract is very basic. It sets a message variable and exposes two functions – get and set which are available to every user to interact with. The message variable will be stored on the blockchain and every user can retrieve its value using the get method, or update its value using the set method.

Step 2 – Compiling the contract

In this step we will use Remix – https://remix.ethereum.org. Remix is a Solidity IDE that’s used to write and compile Solidity code. We will use the web version of Solidity to spare you unnecessary installs.

In Remix, create a new file under the contracts folder. Name the file MessageStore.sol and copy the source code of our contract –

Then, right click the contract file and press compile, or use the compile tab on the left. As a result, an artifacts folder will be generated with a file named MessageStore.json. This file contains the compiled binary code of our contract, which is required by Ethers.js to deploy the contract to the Ethereum Blockchain.

Step 3 – Deploy the contract 🚀🚀🚀

Once we have our MessageStore.json artifact ready, we can use Ethers.js to deploy the contract to the Ethereum Blockchain.

Prerequisites

  1. NodeJS installed on your local machine.
  2. MetaMask Chrome extension installed in your browser.

In order to create a new contract we need to send a transaction to the ethereum blockchain with a payload that contains the compiled contract code. The transaction recipient must be set to null.

The following NodeJS code implements such a transaction using the Ethers.js lib –

const ethers = require('ethers');
const fs = require('fs');
(async () => {
  // Deploy the contract to Ethereum test network - Ropsten
  const provider = ethers.providers.getDefaultProvider('ropsten')

  // Use your wallet's private key to deploy the contract
  const privateKey = '0x8ad2a6e1d19b817b033c5efc96fc62496639862527fc6d259abca79c901d14a3'
  const wallet = new ethers.Wallet(privateKey, provider)

  // Read the contract metadata, which was generated by Remix
  const metadata = JSON.parse(fs.readFileSync('MessageStore.json').toString())

  // Set gas limit and gas price, using the default Ropsten provider
  const price = ethers.utils.formatUnits(await provider.getGasPrice(), 'gwei')
  const options = {gasLimit: 1000000, gasPrice: ethers.utils.parseUnits(price, 'gwei')}

  // Deploy the contract
  const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, wallet)
  const contract = await factory.deploy(options)
  await contract.deployed()
  console.log(`Deployment successful! Contract Address: ${contract.address}`)
})()

To run the code, you need to create a new NodeJS project. I recommend using WebStorm

Once you’ve created the project, you need to place MessageStore.json inside the project folder and use npm to install Ethers.js –

npm install ethers

You should now be able to run the code, and if the transaction is successful, you’ll get the following message –

Deployment successful! Contract Address: 0xb61d5D0BAC90987a7c34967750Ffc77D1b687ec1

As you can see from the code, we deployed the contract to an Ethereum testnet – Ropsten. In the code, I used a private key for a wallet that i’ve created. Since it’s only a testnet, you’re welcome to use it 😉

If you want to use your own private key -> In MetaMask, create a new wallet –

Then, in “Account details”, export your private key.

You’ll also need to fund your account with test Ether which is required to create the transaction. For this part, you can use the Ropsten Faucet. Just enter your wallet address and press “Send me test Ether”.

Take note that the wallet address is your public key which is different than your private key. On a mainnet, you should never expose your private key, not even to a Faucet.

Step 4 – Creating the FrontEnd App

In the last step we deployed our contract to the Ropsten Ethereum testnet. You can use etherscan watch the deployed contract – https://ropsten.etherscan.io/address/0xb61d5D0BAC90987a7c34967750Ffc77D1b687ec1

In this next step we are going to create a frontend app (in React) that enables us to interact with the contract using Ethers.js. We are not going to worry about building a beautiful UI, but rather focus on core functionality.

First, create a new React app using npx –

npx create-react-app message-store

Then, in your new react app, install Ethers.js and place MessageStore.json (from the previous WS project) in the src folder

cd message-store
npm install ethers
cp ~/WebstormProjects/message-store/MessageStore.json src/

Then, open src/App.js and update it with the following code –

import { useState } from 'react'
import { ethers } from 'ethers'
import MessageStore from './MessageStore.json'
const contractAddress = "0xb61d5D0BAC90987a7c34967750Ffc77D1b687ec1"

function App() {
  const [message, setMessage] = useState()

  async function fetchMessage() {
    if (typeof window.ethereum === 'undefined') {
      return
    }
    const provider = new ethers.providers.Web3Provider(window.ethereum)
    const contract = new ethers.Contract(contractAddress, MessageStore.abi, provider)
    try {
      const message = await contract.get()
      window.alert('message: ' + message)
    } catch (err) {
      window.alert(err)
    }
  }

  async function setMessageValue() {
    if (!message || typeof window.ethereum === 'undefined') {
      return
    }
    await window.ethereum.request({ method: 'eth_requestAccounts' })
    const provider = new ethers.providers.Web3Provider(window.ethereum)
    const signer = provider.getSigner()
    const contract = new ethers.Contract(contractAddress, MessageStore.abi, signer)
    const transaction = await contract.set(message)
    await transaction.wait()
  }

  return (
      <div className="App">
        <button onClick={fetchMessage}>Fetch Message</button>
        <button onClick={setMessageValue}>Set Message</button>
        <input onChange={e => setMessage(e.target.value)} placeholder="Hello, World!" />
      </div>
  )
}

export default App

To test it out, start the app.

npm start

When the app loads, you should be able to fetch the current message and also be able to update the message by signing the contract with your MetaMask wallet.

You can use etherscan to watch your transactions as they go live –

Congrats! You’ve just deployed and tested your first dApp on the Ethereum Blockchain 😎

Python

Create a Binance Client in Python from Scratch

In this tutorial we will create a Python wrapper for the Binance exchange API. Binance is one of the largest crypto exchanges out there, and since using external packages always carries out a risk, it is significant to create a Binance client in pure Python.

Prerequisites

I’ll assume you are working on MacOS, and have a basic Python knowledge. That’s it.

Step 1 – Download and Install Python

Download the and install the latest stable release from python.org (I’ll be using Python 3.10.0 which is the latest at the time of writing)

Since Python comes pre-installed on MacOS, we are going to replace the default version with the version we’ve just installed.

In Terminal, run

sudo ln -s -f /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 /usr/local/bin/python

Step 2 – Create a new Python project

Python comes with a collection of packages, which is called The Python Standard Library (STL). One of those packages is venv, which allows us to create lightweight “virtual environments”.

To create an environment, open a new Terminal window and run

python -m venv binance-client

Now we can create the client.py file and run it inside the virtual environment

echo "print('Hello, World!')" >> binance-client/client.py
./binance-client/bin/python binance-client/client.py

Finally, install the requests library which is needed to send HTTP requests to Binance

./binance-client/bin/python -m pip install requests

Step 3 – Coding the client

Open the client.py file we created in the previous step, and update it with the following code

import requests, hmac, hashlib, time, json, logging
from urllib.parse import urljoin, urlencode

class BinanceClient:
    """ This class is a wrapper to binance API """

    def __init__(self, base_url, key, secret):
        self.base_url = base_url
        self.key = key
        self.secret = secret

    @staticmethod
    def get_timestamp():
        return int(time.time() * 1000)

    def get_signature(self, params):
        query_string = urlencode(params).encode("utf-8")
        secret = self.secret.encode("utf-8")
        return hmac.new(secret, query_string, hashlib.sha256).hexdigest()

    def add_signature(self, params):
        params = {**params, "timestamp": self.get_timestamp()}
        params = {**params, "signature": self.get_signature(params)}
        return params

    def request(self, request_type, url, params, add_signature=False):
        if add_signature:
            params = self.add_signature(params)
        headers = {"X-MBX-APIKEY": self.key}
        if request_type == "GET":
            response = requests.get(url, headers=headers, params=params)
        if request_type == "POST":
            response = requests.post(url, headers=headers, params=params)
        return self.format_response(response)

    @staticmethod
    def format_response(response):
        try:
            code = response.status_code
            content = json.loads(response.content)
            message = {"status": response.status_code, "content": content}
            logging.debug(str(message))
            if code == 200:
                return json.loads(response.content)
            raise Exception(message)
        except Exception as e:
            raise Exception("Failed to parse response", e)

Now that we have our request method ready, we can extend our client according to Binance API documentation

    def get_account(self):
        url = urljoin(self.base_url, "/api/v3/account")
        return self.request("GET", url, {}, True)

    def get_ticker(self, symbol):
        url = urljoin(self.base_url, "/api/v3/ticker/bookTicker")
        return self.request("GET", url, {"symbol": symbol})

    def get_book(self, symbol, limit):
        url = urljoin(self.base_url, "/api/v3/depth")
        return self.request("GET", url, {"symbol": symbol, "limit": limit})

Step 4 – Using the client

Having our client ready, we can start making API calls

client = BinanceClient("https://api.binance.com", "API_KEY", "API_SECRET")
account_info = client.get_account()
print(account_info)