Skip to main content

Building Refund Mobile and Web Dapps by Location Smart Contract

building-refund-mobile-and-web-dapps-by-location-smart-contract

Introduction

In this project, We are going to build a decentralized app in the Ethereum blockchain. This project aims to produce an Ethereum based dApp that has both the smart contract tested and deployed in a testnet and a front end that will allow monitoring of the status.

Objective of the Project

The refund by location smart contract is aimed to be used when one party, for example an employer, agrees to pay another party, for example an employee, for being present in a certain geographic area for a certain duration. The employee’s phone sends its GPS location to a smart contract at a certain interval. Based on the pre-agreed contract codified in an Ethereum smart contract, a cryptocurrency payment is executed when all the agreed conditions are met. If, at any point, the GPS sensor indicates that an employee is outside the range of the agreed GPS area, the contract state will be updated to indicate that it is out of compliance.

Understanding of the Ethereum Blockchain

A single, canonical computer (known as the Ethereum Virtual Machine, or EVM) exists in the Ethereum universe, and everyone on the Ethereum network agrees on its current state. Every Ethereum node, or participant in the network, maintains a copy of this computer's state. Any member may also broadcast a command to this computer to carry out any computation. Every time a request of this nature is broadcast, other network users verify, validate, and perform (or "execute") the computation. As a result of this execution, the EVM's state changes, which are committed and distributed across the whole network.

How does it actually work?

Before we discuss how Ethereum blockchain works, let's define some key words in the ethereum blockchain.

Ethereum Virtual Machine (EVM) - is the sandboxed runtime and a completely isolated environment for smart contracts in Ethereum. This means that every smart contract running inside the EVM has no access to the network, file system, or other processes running on the computer hosting the VM.

Smart Contracts - Computer programs that run on the EVM. They are a reusable snippet of code which a developer publishes into EVM state. Anyone can request that the smart contract code be executed by making a transaction request.

Accounts - objects that interact with one another through a message-passing framework. An address in Ethereum is a 160-bit identifier that is used to identify any account. ● Externally owned accounts, which are controlled by private keys and have no code associated with them. ● Contract accounts, which are controlled by

In Ethereum, transactions are chained together into blocks. A transaction must be valid in order to induce a change from one state to the next. A transaction must go through the mining validation procedure in order to be accepted as valid. A collection of nodes (i.e., computers) engage in mining when they use their computing power to compile a block of legitimate transactions. When uploading a block to the blockchain, each miner includes a mathematical "proof," which serves as a guarantee. "Proof of work" refers to the procedure of validating each block by requesting a mathematical justification from the miner. A fixed quantity of "Ether," an inherent digital asset of Ethereum, is awarded to a miner who validates a new block

building-refund-mobile-and-web-dapps-by-location-smart-contract

Some concepts on Ethereum blockchain

● Only Externally owned accounts can initiate a transaction on their own

● contract accounts can only fire transactions in response to other transactions they have received

● Every computation that occurs as a result of a transaction on the Ethereum network incurs a fee called Gas. Gas is the amount of Ether you are willing to spend on every unit of gas, and is measured in “gwei.” “Wei” is the smallest unit of Ether, where 1⁰¹⁸ Wei represents 1 Ether. One gwei is 1,000,000,000 Wei.

● Gas is applied for storage also

● There are two types of transactions ○ message calls - are transactions that call other contracts ○ contract creations - are transactions that create new Ethereum contracts

Used Tools To perform The Project

  • Database: The Ethereum’s Testnet Ropsten blockchain. For testing purposes, I have used ganache on the local machine.
  • Admin Panel (Employer): I have used Reactjs.
  • App (Employee): I have used Flutter.
  • Contract’s programing language: Solidity, is the most famous language for developing Smart Contracts at the time of writing.
  • Frontend contracts: web3.js to use those contracts in your web user interface.
  • Frameworks: Truffle to deploy, test, and compile our contracts.
  • Metamask: To use the final application as the end user would.
  • Genache: To create Ethereum blockchain on local machine

Environment setup and installations

To begin working on this project we need to do the following things first.

  1. On terminal - sudo apt install npm nodejs
  2. On terminal - npm install -g truffle
  3. On terminal - truffle unbox react
  4. On terminal - Download Gnache AppImage
  5. Install Flutter and flutter tools

Smart Contract Writing

As a starter in writing smart contracts, For this project, I have written a simple smart contract that handles new refund contract creation and timely checkups if that contract is being fulfilled. In a smart contract, we will start by creating the Employee (one party that will handle the job) and the Employer (one party that will create the contract). And mapping is used to map each employee's address to their contracts (For simplicity sake). We would also create Events to notify completion or failure of a contract.

Scroll to Continue

Refund.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
import "@openzeppelin/contracts/utils/Strings.sol";

contract Refund {
    event CompletedEvent (
        address employeeaddress
    );

    event FailedEvent (
        address employeeaddress
    );
    // employee data
    struct Employee {
        uint id;
        string name;
        address public_address;
    }

    //employer data
    struct Employer {
        uint id;
        string name;
        address public_address;
    }

    //contract data with boundary points , durations , completion checkers
    struct contract_data{
        uint id;
        uint minimum_point_long;
        uint minimum_point_lat;
        uint maximum_point_long;
        uint maximum_point_lat;
        uint starting_time;
        uint duration;
        uint gathered_location_count;
        bool contract_truth;
        bool completed;
        Employee employees;
        Employer employer;
    }
    
    // contracts that have been recorded 
    mapping(address => contract_data) public contracts;
    
    uint public Contractcount;

    // Employees that have been recorded
    mapping(address => Employee) public employees;

    mapping (uint => address) public employee_mapping;
 
    uint public Employeecount;

    // employer that have been recorded
    mapping(address => Employer) public employers;
    uint public Employercount;
}

The next thing to do is create the two functions that initiate employee and employer creation if needed. And also two functions to create the contract and to check if the contract is being fulfilled. To create the contract we need to add the employee and employer data, minimum and maximum location points, starting time, and duration. Checking if the contract is being fulfilled is being done by using the longitude and latitude sent from the mobile app (employee) and checking it against the boundary points provided by the employer on the contract.

Refund.sol

function  initialize_employers(address[] memory a) public {
        for (uint add=0 ; add < a.length; add++){
            addEmployer(string.concat("Employer " , Strings.toString(add)) , a[add]);
            }
    }
    function  initialize_employees(address[] memory a) public{
         for (uint add=0 ; add < a.length; add++){
            addEmployee(string.concat("Employee " , Strings.toString(add)) , a[add]);
            }
    }

    function addEmployer (string memory _name , address user_address) private {
        Employercount ++;
        employers[user_address] = Employer(Employercount, _name, user_address);
    }
    function addEmployee (string memory _name , address user_address) private {
        Employeecount ++;
        employees[user_address] = Employee(Employeecount, _name, user_address);
    }

    // create contract data using admin panel
    function Create_contract_data( uint[2] memory minimum_points, uint[2] memory maximum_points, uint duration, string memory employee_name , address employee_address, address employer_address) public{
        if (! (employees[employee_address].id > 0)){
            addEmployee(employee_name, employee_address);
            employee_mapping[Employeecount] = employee_address;
         }
        Contractcount++;
        contracts[employee_address] = contract_data(Contractcount, minimum_points[0] , minimum_points[1] ,maximum_points[0] , maximum_points [1], block.timestamp ,duration , 0 ,true , false ,employees[employee_address],employers[employer_address]);
    }

    // get location data from the mobile app
    function get_location( uint  longitude, uint  latitude) public  returns (bool){
        
        contract_data memory found_contract = contracts[msg.sender];
        if (found_contract.id > 0){
        if ( found_contract.completed != true){
            uint duration = (block.timestamp - found_contract.starting_time) / 60 ;
            if (duration < found_contract.duration){ 
                found_contract.gathered_location_count = found_contract.gathered_location_count + 1;
                if (! (found_contract.minimum_point_long <= longitude && found_contract.maximum_point_long >= longitude && found_contract.minimum_point_lat <= latitude && found_contract.maximum_point_lat >= latitude) ){
                    found_contract.contract_truth = false;
                }
            }else if ( duration >=found_contract.duration){
                check_completion(found_contract);
               
            }
        }
            return true;
            }else{
                return false;
            }
    }

    function check_completion(contract_data memory found_contract) private{
        uint minimum_check = found_contract.duration * 3 / 4 ;
        if (found_contract.contract_truth && minimum_check <= found_contract.gathered_location_count){
                    found_contract.completed = true;
                    emit CompletedEvent(found_contract.employees.public_address);
                }
                else{
                    found_contract.completed = false;
                    emit FailedEvent(found_contract.employees.public_address);
                }
    }

After writing the smart contract go to the truffle folder and write the following bash command. This command will compile and migrate your solidity code into your local machine Ethereum blockchain (Genache).

  • cd truffle
  • truffle migrate --network development


Writing the Admin Panel (Employer's page)

After writing the above smart contract next step is to enable employers to actually create a contract for their employees. For this project, I have used Reactjs to create a contract between the two parties. I have used web3.js to use those contracts in the admin panel. The front-end uses JSON generated from the compilation of the contract to create an artifact that enables it to connect to the contract.

Ethprovider.jsx

import React, { useReducer, useCallback, useEffect } from "react";
import Web3 from "web3";
import EthContext from "./EthContext";
import { reducer, actions, initialState } from "./state";

function EthProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);

  const init = useCallback(
    async artifact => {
      if (artifact) {
        const web3 = new Web3(Web3.givenProvider || "ws://localhost:7545");
        const accounts = await web3.eth.requestAccounts();
        const networkID = await web3.eth.net.getId();
        const { abi } = artifact;
        let address, contract;
        try {
          address = artifact.networks[networkID].address;
          contract = new web3.eth.Contract(abi, address);
        } catch (err) {
          console.error(err);
        }
        dispatch({
          type: actions.init,
          data: { artifact, web3, accounts, networkID, contract }
        });
      }
    }, []);

  useEffect(() => {
    const tryInit = async () => {
      try {
        const artifact = require("../../contracts/Refund.json");
        init(artifact);
      } catch (err) {
        console.error(err);
      }
    };

    tryInit();
  }, [init]);

  useEffect(() => {
    const events = ["chainChanged", "accountsChanged"];
    const handleChange = () => {
      init(state.artifact);
    };

    events.forEach(e => window.ethereum.on(e, handleChange));
    return () => {
      events.forEach(e => window.ethereum.removeListener(e, handleChange));
    };
  }, [init, state.artifact]);

  return (
    <EthContext.Provider value={{
      state,
      dispatch
    }}>
      {children}
    </EthContext.Provider>
  );
}

export default EthProvider;

By now we have connected the react front-end with the contract the next step here is to list the contract and add the contract using the admin panel.

Forms.jsx

import { useState, useEffect } from "react";
import useEth from "../../contexts/EthContext/useEth";

function MainForm({setEmployeesLs}) {
  const { state: { contract, accounts } } = useEth();


  useEffect(()=>{
      getEmployees();
  },[])

    async function getEmployees() {

      let count = await contract.methods.Employeecount().call({ from: accounts[0] })
      console.log(count);

      let Employees = []

      for(let i=1;i<=count;i++){
          let addr = await contract.methods.employee_mapping(i).call({ from: accounts[0] })
          let employees = await contract.methods.employees(addr).call({ from: accounts[0] })
          Employees.push(employees) 
      }

      console.log(Employees)
      setEmployeesLs(Employees)
  }

const write = async e => {
    e.preventDefault();
    if(timer!=="" && employeeName !== "" && employeeAddress!=="" && lat !== "" && lat2!=="" && lng !== "" && lng2!=="" ){
      let lt1 = lat.split(".").join('')
      let lt2 = lat2.split(".").join('')
      let ln1 = lng.split(".").join('')
      let ln2 = lng2.split(".").join('')
      await contract.methods.Create_contract_data(
        [lt1, lt2],[ln1, ln2], timer, employeeName, employeeAddress, 0
      ).send({ from: accounts[0] });
      getEmployees();
    }

}

After we finished writing react code you can run the react server using the following command.

  • cd client
  • npm run start

Writing Flutter code for Employees

By now we have created a new contract involving an employee so let's get an app that will handle identifying the location of an employee and send it to the boundary checker on that smart contract code. The below code will enable connection with the contract on the blockchain.

import 'dart:convert';

import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:web3dart/web3dart.dart';
import 'package:http/http.dart' as http;
import 'package:web_socket_channel/io.dart';

final ethUtilsProviders = StateNotifierProvider<EthereumUtils, bool>((ref) {
  return EthereumUtils();
});

class EthereumUtils extends StateNotifier<bool> {
  EthereumUtils() : super(true) {
    initialSetup();
  }

  //!Add to the README.md file

  final String _rpcUrl = "http://192.168.0.172:7545";
  final String _wsUrl = "ws://192.168.0.172:7545/";
  final String _privateKey = "";

  Web3Client? _ethClient; //connects to the ethereum rpc via WebSocket
  bool isLoading = true; //checks the state of the contract

  String? _abi; //used to read the contract abi
  EthereumAddress? _contractAddress; //address of the deployed contract

  EthPrivateKey? _credentials; //credentials of the smart contract deployer

  DeployedContract? _contract; //where contract is declared, for Web3dart
  ContractFunction?
      _userName; // stores the name getter declared in the HelloWorld.sol
  ContractFunction?
      _writeName; // stores the setName function declared in the HelloWorld.sol

  String? deployedName; //will hold the name from the smart contract

  initialSetup() async {
    http.Client _httpClient = http.Client();
    _ethClient = Web3Client(_rpcUrl, _httpClient, socketConnector: () {
      return IOWebSocketChannel.connect(_wsUrl).cast<String>();
    });

    await getAbi();
    await getCredentials();
    await getDeployedContract();
  }

  readPKey(String key) async {
    final prefs = await SharedPreferences.getInstance();

    return prefs.getString(key) != null
        ? json.decode(prefs.getString(key)!)
        : null;
  }

  Future<void> getAbi() async {
    //Reading the contract abi
    String abiStringFile =
        await rootBundle.loadString("assets/contracts_abis/Refund.json");
    var jsonAbi = jsonDecode(abiStringFile);
    _abi = jsonEncode(jsonAbi["abi"]);

    _contractAddress =
        EthereumAddress.fromHex(jsonAbi["networks"]["5777"]["address"]);
  }

  Future<void> getCredentials() async {
    String pk = "";
    var val = await readPKey("p_key");
    if (val != null) {
      _credentials = EthPrivateKey.fromHex(val);
    } else {
      pk = "6c3e36c0fe9fdc3441297a92d0496c686d9d939e5395c525c672a3fe7dd457ea";
      _credentials = EthPrivateKey.fromHex(pk);
    }
  }

  Future<void> getDeployedContract() async {
    // Telling Web3dart where our contract is declared.
    _contract = DeployedContract(
        ContractAbi.fromJson(_abi!, "SimpleStorage"), _contractAddress!);

    // Extracting the functions, declared in contract.
    _writeName = _contract!.function("get_location");
    // getValue();
  }

  setValue(BigInt latitude, BigInt longitude) async {
    // Setting the name to nameToSet(name defined by user)
    isLoading = true;
    state = isLoading;
    // notifyListeners();
    await _ethClient!.sendTransaction(
        _credentials!,
        Transaction.callContract(
            contract: _contract!,
            function: _writeName!,
            parameters: [latitude, longitude]));
    // getValue();
  }
}

The next thing to do here is to send the location of the user to the contract for verification. The below code will fetch the data from the mobile app and send it to the contract function.

Future<Position?> getCurrentLocation() async {
    try {
      Position position = await Geolocator.getCurrentPosition(
          desiredAccuracy: LocationAccuracy.medium);
      return position;
    } catch (e) {
      print(e);
      return null;
    }
  }

  checkPermission() async {
    PermissionStatus permission =
        await LocationPermissions().checkPermissionStatus();
    return permission;
  }

  void checkLocation(ethUtils) async {
    PermissionStatus st = await checkPermission();
    if (st == PermissionStatus.granted) {
      Position? position = await getCurrentLocation();
      if (position != null) {
        setState(() {
          latitude = position.latitude;
          longitude = position.longitude;
        });
        // final ethUtils = ref.watch(ethUtilsProviders.notifier);
        BigInt lt = BigInt.from(
            int.parse(position.latitude.toString().split('.').join("")));
        BigInt ln = BigInt.from(
            int.parse(position.latitude.toString().split('.').join("")));
        ethUtils.setValue(lt, ln);
      } else {
        ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
          backgroundColor: kErrorColor,
          content: Text(
            "Location Error",
            style: TextStyle(
              fontSize: 15,
              color: kPrimaryColor,
            ),
          ),
          duration: Duration(seconds: 2),
          behavior: SnackBarBehavior.floating,
        ));
      }
    } else {
      await LocationPermissions().requestPermissions();
      checkLocation(ethUtils);
    }
  }

After writing this flutter code you can run the app using

  • flutter run lib/main.dart (press run button on vscode or Android Studio)

Security Analysis of the Project

On this project, we built a working contract that handles location checkups and new contract creation. As this is my first solidity code the focus was more on making it work rather the writing secure code as much as possible. But some analyses that can be taken from the codes written include

  • code readability and Simplicity-wise - I believe as the contract is not hard to write the complexity was not too high. Only one function handles more tasks.
  • Code quality and reuse - At this point, the quality of the code needs to be updated as more testing should be done before migrating to the main Ethereum blockchain. But the functions are simple and reusable, and also some have been taken from other solidity codes found while researching.
  • Test coverage - Testing has been performed for the functions but the more rigorous tests should be performed before making them ready in the main Ethereum network.
  • Some of the Known vulnerabilities have been researched and have used preventive approaches as much as possible. For example

    • Reentrancy and DelegateCall - these attacks will not happen in our current code as we don't have any callback functions in our contract.
    • Arithmetic Over/Underflows - this attack is less likely to be used against our contract as we used data types like "uint" instead of "uint8" or "uint256".

      Function Visibilities - We have set visibilities for all of our functions and I believe they are appropriate ones but that doesn't mean there is room for improvement. For example, anyone with the employee address can send location data that would be a problem if it is deployed on the main blockchain

Conclusion and Future work

This Project is meant to challenge me to jump into the Web3 environment and write a refund Dapp using a location smart contract. So the end product can be tested and deployed on the Testnet but I believe it is not ready to be deployed on the Mainnet for the following reasons.

  1. The contract has to be tested thoroughly to make sure it passes every possible failure scenarios and security procedures.
  2. The App and admin panel need to add more features and an upgrade on their UI design is needed to be used by a lot of users.
  3. A payment feature can be added to automate everything including the refund payment.

In the future, I will try to apply the above future works and update this blog as needed.

  • Github
    Using Ethereum smart contract to refund someone based on that person staying in one area in a given time

This content is accurate and true to the best of the author’s knowledge and is not meant to substitute for formal and individualized advice from a qualified professional.

Related Articles