Integrating Celo Identity-Preserving DApp Development into Mobile App using Flutter

Introduction

In today’s digital age, privacy and identity are paramount. Yet, conventional systems are struggling to meet these demands. Enter blockchain, a technology that has the potential to revolutionize our world, placing a strong emphasis on privacy and identity. One such blockchain platform that stands out in this regard is Celo. Celo, with its emphasis on identity preservation, opens up numerous possibilities in decentralized application (DApp) development. In this tutorial, we will explore how to create an identity-preserving DApp using Celo and Flutter.

Prerequisites

Before starting this tutorial, it is recommended to have some prior knowledge and complete the following prerequisites:

  1. Basic understanding of blockchain technology (specifically CELO) and smart contracts.
  2. Familiarity with the Flutter framework and Dart language.
  3. Prior experience with EVM would be helpful, as Celo is fully compatible with Ethereum’s tooling and coding conventions.

If you haven’t met these prerequisites, I recommend you take the time to familiarize yourself with the basics of blockchain and Flutter development before proceeding.

Requirements

Before we begin, there are several tools that you need to have installed:

  1. Flutter: Our mobile app development framework. Install Flutter from HERE
  2. Dart SDK: Flutter uses Dart language. Ensure Dart SDK is installed by following the instructions HERE
  3. Celo Wallet: This will be needed to interact with the Celo blockchain. Install it from HERE
  4. Node.js: Ensure Node.js (version 12.0.1 or later) is installed. It can be downloaded from HERE

Writing the Smart Contract

We’ll be writing the smart contract for saving the address and phone number of users on the celo blockchain so as to securely preserve the identity of the user in a decentralized platform.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract Accounts {
    // Mapping from hashed phone numbers to array of addresses
    mapping (string => address[]) private accounts;

    // Event emitted when a new address is added
    event AddressAdded(string hashedPhoneNumber, address account);

    // Add a new address to a phone number
    function addAddress(string memory hashedPhoneNumber, address account) public {
        // Ensure the address isn't null
        require(account != address(0), "Invalid address");

        // Check that the address isn't already added
        address[] memory associatedAddresses = accounts[hashedPhoneNumber];
        for (uint256 i = 0; i < associatedAddresses.length; i++) {
            require(associatedAddresses[i] != account, "Address already added");
        }

        // Add the address
        accounts[hashedPhoneNumber].push(account);

        // Emit an event
        emit AddressAdded(hashedPhoneNumber, account);
    }

    // Get the addresses associated with a phone number
    function lookupAccounts(string memory hashedPhoneNumber) public view returns (address[] memory) {
        return accounts[hashedPhoneNumber];
    }
}

This contract includes a mapping from hashed phone numbers (stored as strings) to arrays of addresses.

  • The mapping links hashed phone numbers to an array of Ethereum addresses related to them.
  • The event AddressAdded triggers and logs when a new address is added to a hashed phone number.
  • The addAddress function allows an address to be added to the list of addresses associated with a hashed phone number, provided the address isn’t null and hasn’t already been added.
  • The lookupAccounts function returns the list of addresses associated with a hashed phone number.

Deploy the Smart Contract

We will be compiling and testing our smart contract using 2 methods which have been deeply explained in some tutorials in the academy:

  1. Deploying smart contract on remix: Check the Creating and Deploying a Celo Smart Contract section of this tutorial.
  2. Deploying smart contract using Celo Composer and terminal: Check the Building the Smart Contract section of this tutorial.

Getting started with Flutter and Celo

We will begin by setting up our development environment and configuring a new Flutter project for Celo. Subsequently, we will delve into the specifics of integrating Celo’s identity protocol into our DApp.

Setting up Flutter Project

Start by initializing a new Flutter project by executing the following command in your terminal:

flutter create identity_preserving_dapp

Next, navigate into your new Flutter project:

cd identity_preserving_dapp

Configuring the Project for Celo

To interact with Celo’s network, we need to install a couple of packages. We’re going to use riverpod, web3dart and http for this tutorial. riverpod is used for managing the state of the Dapp, web3dart is a Dart library that allows us to interact with Ethereum compatible chains like Celo, and http is used to make HTTP requests.

Add the following dependencies to your pubspec.yaml file:

dependencies:
   flutter:
     sdk: flutter
   web3dart: ^2.6.1
   http: ^1.1.0
  flutter_ riverpod: ^2.3.4
 flutter_dotenv:  ^5.0.2

Then, install these dependencies by running the following command in your terminal:

flutter pub get

We’ll use Celo’s Alfajores test network for this tutorial. To connect to it, you need to set the RPC URL as an environment variable. Create a .env file in the root of your project and add the following line:

CELO_RPC_URL=https://alfajores-forno.celo-testnet.org

You’ll also need your Celo account’s private key. Warning: Never share your private key and never commit it into version control. For the purposes of this tutorial, add it to your .env file like this:

CELO_PRIVATE_KEY=YOUR_PRIVATE_KEY

Now your project is configured for Celo!

Integrating Celo’s Identity Protocol

With the project setup complete, it’s time to integrate Celo’s identity protocol. For simplicity, we’ll focus on the phone number verification aspect of the protocol.
Celo’s identity protocol is built around the Accounts smart contract. This contract maps addresses to metadata, including phone numbers.

  • First, let’s import the necessary libraries in your celo_id.dart Dart file:
import 'package:http/http.dart';
import 'package:web3dart/web3dart.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart' as dotenv;
  • Initialize your web3 client using the RPC URL from the environment variable:
var apiUrl = dotenv.env['CELO_RPC_URL'];
final client = Web3Client(apiUrl, Client());
  • Get your account credentials using your private key:
final credentials = await client.credentialsFromPrivateKey(dotenv.env['CELO_PRIVATE_KEY']);
  • Let’s create a function called addPhoneNumberAddress that takes the user address and phone number, encrypt the number and then pass it to the smart contract for mapping.
Future addPhoneNumberAddress(String phoneNumber, String address) async {
    // final client = Web3Client(apiUrl ?? "", Client());

    final contract = await deployedAccountContract;

    // Hash the phone number
    var phoneNumberHash =
        keccak256(Uint8List.fromList(phoneNumber.codeUnits)).toString();

    print("phone number ===>> $phoneNumberHash");

    // Get the addAddress function from the contract
    final addAddressFunction = contract.function('addAddress');

    // Call the addAddress function
    final response = await client.sendTransaction(
        credentials,
        Transaction.callContract(
          contract: contract,
          function: addAddressFunction,
          parameters: [phoneNumberHash, EthereumAddress.fromHex(address)],
        ),
        chainId: 44787);

    final dynamic receipt = await awaitResponse(response);
    return receipt;
  }

  • Now let’s get the address linked to a phone number. We’ll use the lookupAccounts method of the Accounts contract. This method takes the phone number hash and returns a list of addresses associated with it.
Future<List<EthereumAddress>> lookupAccounts(String phoneNumber) async {

  // Hash the phone number

  var phoneNumberHash = keccak256(Uint8List.fromList(phoneNumber.codeUnits));

   // Get the Accounts contract

   var accountsContract = DeployedContract(

     ContractAbi.fromJson('ACCOUNTS_CONTRACT_ABI', 'Accounts'),

      EthereumAddress.fromHex('ACCOUNTS_CONTRACT_ADDRESS'));

  // Get the lookupAccounts function

  var lookupAccountsFunction = accountsContract.function('lookupAccounts');

  // Call the function

 var result = await client.call(

    contract: accountsContract,

    function: lookupAccountsFunction,

    params: [phoneNumberHash],

 );

 // Parse the result

 var addresses = result[0] as List<EthereumAddress>;

  return addresses;

}
  • Replace ACCOUNTS_CONTRACT_ABI and ACCOUNTS_CONTRACT_ADDRESS with the actual ABI and address of the Accounts contract.

Here is the full code:

import 'package:flutter/services.dart';
import 'package:http/http.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';

var apiUrl = dotenv.env['CELO_RPC_URL'];
final client = Web3Client(apiUrl ?? "", Client());

class CeloPreservingIdentity {
  EthPrivateKey get credentials {
    var credentials =
        EthPrivateKey.fromHex(dotenv.env['CELO_PRIVATE_KEY'] ?? "");
    // var address = credentials.address;
    return credentials;
  }

  /// Get deployed Accounts contract
  Future<DeployedContract> get deployedAccountContract async {
    const String abiDirectory = 'lib/service/contract.abi.json';
    String contractABI = await rootBundle.loadString(abiDirectory);

    final DeployedContract contract = DeployedContract(
      ContractAbi.fromJson(contractABI, 'Accounts'),
      EthereumAddress.fromHex(dotenv.env['CONTRACT_ADDRESS'] ?? ""),
    );

    return contract;
  }

  Future addPhoneNumberAddress(String phoneNumber, String address) async {
    // final client = Web3Client(apiUrl ?? "", Client());

    final contract = await deployedAccountContract;

    // Hash the phone number
    var phoneNumberHash =
        keccak256(Uint8List.fromList(phoneNumber.codeUnits)).toString();

    print("phone number ===>> $phoneNumberHash");

    // Get the addAddress function from the contract
    final addAddressFunction = contract.function('addAddress');

    // Call the addAddress function
    final response = await client.sendTransaction(
        credentials,
        Transaction.callContract(
          contract: contract,
          function: addAddressFunction,
          parameters: [phoneNumberHash, EthereumAddress.fromHex(address)],
        ),
        chainId: 44787);

    final dynamic receipt = await awaitResponse(response);
    return receipt;
  }

  Future<List<dynamic>> lookupAccounts(String phoneNumber) async {
    // Hash the phone number
    var phoneNumberHash =
        keccak256(Uint8List.fromList(phoneNumber.codeUnits)).toString();

    // Get the Accounts contract
    var accountsContract = await deployedAccountContract;

    // Get the lookupAccounts function
    var lookupAccountsFunction = accountsContract.function('lookupAccounts');

    // Call the function
    var result = await client.call(
      contract: accountsContract,
      function: lookupAccountsFunction,
      params: [phoneNumberHash],
    );

    // Parse the result
    print("result ===>> ${result[0]}");
    List addresses = result[0];

    return addresses;
  }

  Future<dynamic> awaitResponse(dynamic response) async {
    int count = 0;
    while (true) {
      final TransactionReceipt? receipt =
          await client.getTransactionReceipt(response);
      if (receipt != null) {
        print('receipt ===>> $receipt');
        return receipt.logs[0].data;
      }
      // Wait for a while before polling again
      await Future<dynamic>.delayed(const Duration(seconds: 1));
      if (count == 6) {
        return null;
      } else {
        count++;
      }
    }
  }
}

Now you have integrated Celo’s identity protocol into your Flutter DApp! Users can input their phone number, and the DApp will return the Celo addresses linked to that number.

Now that you have a basic understanding of how to look up accounts linked to a specific phone number, let’s add a user interface to our Flutter app to use this functionality.
Open the file lib/main.dart and replace its contents with the following code:


import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:identity_preserving_dapp/screen/home_page.dart';

void main() async {
  await dotenv.load(fileName: '.env');
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ProviderScope(
      child: MaterialApp(
        title: 'Celo Identity DApp',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const HomePage(),
      ),
    );
  }
}

The source code contains the whole flow of the tutorial in code. The Screen directory contains the view or the uuser interface of the dApp, the controller connects the view to the web3 helper class named CeloPreservingIdentity class which connects to the smart contract through web3dart

This code creates a simple Flutter app with a text field to enter a phone number and a button to look up the associated Celo accounts. It will display the addresses of the accounts linked to the input phone number.
Remember to replace ACCOUNTS_CONTRACT_ABI and ACCOUNTS_CONTRACT_ADDRESS with the actual ABI and address of the Accounts contract.

This is a basic implementation and can be expanded according to your needs. With this knowledge, you can integrate Celo’s identity protocol into your DApp in a way that fits your use case. Remember to always handle users’ phone numbers with care to respect their privacy.

This is the UI of the dApp:

Conclusion

By following this tutorial, you’ve built a decentralized mobile application using Flutter and Celo. We started from setting up a development environment for Flutter, configuring a new Flutter project for Celo, and finally integrating Celo’s identity protocol into our DApp.
Congratulations on building an identity-preserving DApp with Celo and Flutter!

Next Steps

Now that you’ve built an identity-preserving DApp, why not explore other features of Celo? Here are some ideas for further learning:

  1. Building a payment DApp using Celo
  2. Implementing Celo’s stablecoin in your DApp
  3. Adding more complex smart contract functionalities to your DApp

About the Author

John Igwe Eke, a proficient mobile developer and a technical writer, combines his expertise in software development with his enthusiasm for blockchain and Web3 technologies. He leverages this unique blend of skills to distill complex concepts into understandable, engaging content, thereby aiding in the democratization of these emerging technologies. Connect with me on twitter, LinkedIn, and Github.

References

  1. Flutter Official Documentation
  2. Celo Official Documentation
  3. Celo’s Identity Protocol
  4. Dart Language Tour
  5. Source Code
4 Likes

Fantastic news! Your proposal has landed in this week’s top voted list. As you begin your project journey, remember to align with our community and technical guidelines, ensuring a high quality platform for our developers. Congratulations! :mortar_board: :seedling:

2 Likes

i will review this

1 Like

Alright @yafiabiyyu