Interacting with Celo Smart Contracts through Flutter

Introduction

In the world of blockchain, creating decentralized applications (dApps) that are not only secure and transparent but also user-friendly, is crucial for mass adoption. Celo, with its mobile-first approach, is at the forefront of this shift, offering access to global financial services with just a phone number. What’s even more exciting is that it’s Ethereum-compatible! In this tutorial, we’ll harness the power of Celo and Flutter, a popular mobile development framework, to interact with Celo smart contracts, bringing blockchain capabilities directly to mobile devices.

Prerequisites

Before diving into this tutorial, it’s necessary to have some foundational knowledge and resources:

  • Basic understanding of blockchain technology, particularly Ethereum and Celo.
  • Familiarity with Flutter and Dart programming languages.
  • Basic understanding of solidity.

Requirements

Before we dive into the coding part of this tutorial, there are a few technologies we need to have installed:

  1. Flutter: We will be using Flutter for our mobile application development. Ensure that it’s installed and properly set up on your system.
  2. Dart: Since Flutter apps are written in Dart, you must have Dart SDK installed.
  3. MetaMask: We’ll need MetaMask for interacting with the Celo network.

Now that we have the requirements out of the way, let’s set up a Celo wallet and dive into creating our Flutter application!

Getting Started

Setting Up a Celo Wallet

To interact with a Celo network, we’ll need to create and configure a Celo wallet using Metamask. Follow these steps to create a new Celo wallet:

  1. Install the Metamask browser extension and create a new wallet.
  2. Click on the network dropdown in the Metamask window and select “Add network”.
  3. Click on “Add network manually” at the bottom of your screen
  4. Enter the following details for the Celo testnet:
  5. Click “Save” to add the Celo testnet to your Metamask network list.
    Make sure to fund your account with some testnet CELO and cUSD tokens from the Celo Testnet Faucet.

Setting Up the Flutter Development Environment

First, we need to ensure our Flutter development environment is set up and ready. If you’ve not yet installed Flutter, head over to the official Flutter installation guide and follow the instructions for your specific operating system. Once Flutter is installed, verify the installation by running the following command in your terminal:

flutter doctor

This command checks your environment and displays a report of the status of your Flutter installation. Ideally, you want to have no issues in this report, but if there are any, you might need to resolve them before proceeding.

Setting Up a New Flutter Project

Next, let’s create a new Flutter project. Open your terminal, navigate to where you want to store the project, and run the following command:

flutter create celo_flutter_dapp

You should have an output of this form:

This command creates a new Flutter project called celo_flutter_dapp.

Navigate into the new directory:

cd celo_flutter_dapp

Next, open the project in your preferred text editor or IDE.

Adding Dependencies

For our Flutter app to interact with the Celo blockchain, we need to add some dependencies to our pubspec.yaml file. The key library we’ll be using is web3dart , a Dart library that connects our Flutter app to the Ethereum blockchain (which also works for Celo since it’s Ethereum-compatible). We’ll also add http for handling network requests.

Open your terminal and run the following commands from the root directory of your project

flutter pub add web3dart
flutter pub add http

Your dependencies section in your pubspec.yaml file should look something like this:

With these dependencies added, your Flutter application is now ready to interact with the Celo blockchain.

Creating and Deploying a Celo Smart Contract

Before we interact with a smart contract, we need to create and deploy one. We’ll be using Remix IDE, an online Ethereum IDE that lets us write Solidity smart contracts, for this purpose.

Navigate to Remix Ethereum IDE.

Go to the “File Explorers” tab, and click on the second icon from the left to open the file explorer. Right-click on the contracts directory button to create a new file and name it HelloWorld.sol.

In the HelloWorld.sol file, write the following Solidity contract code:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract HelloWorld {
    string public greet = "Hello, Celo World!";

    function setGreet(string memory _greet) public {
        greet = _greet;
    }

    function getGreet() public view returns (string memory) {
        return greet;
    }
}

In this contract, we have a public state variable greet that’s initially set to “Hello, Celo World!”. We have a function setGreet that allows us to change the greet message and a function getGreet to fetch the current greet message.

Now, let’s compile the contract. Go to the “Solidity Compiler” tab (fourth icon from the left), select the correct compiler version (0.7.0 in our case), and click on “Compile HelloWorld.sol”.

After successful compilation ( The solidity compiler icon should show a check sign), go to the “Deploy & run transactions” tab (the one with the icon shown below).
Screenshot 2023-06-23 at 12.11.42

Ensure that the Environment is set to “Injected Provider - MetaMask” as shown below. This connects Remix to your Metamask Extension Wallet. Select the HelloWorld contract and click on “Deploy”.

A pop-up from Celo Extension Wallet will open asking you to confirm the transaction.

Confirm it and wait for the transaction to be mined.

Once the transaction is mined, your deployed contract will be available under “Deployed Contracts” as shown below.

You can copy the contract address of the deployed contract by clicking on the copy icon under the Deployed Contract section. Keep the contract address handy, we’ll need it for our Flutter application.

Interacting with the Smart Contract in Flutter

We’ll now start interacting with the Celo network. For this, we need to establish a connection to the Celo node. Let’s create a new Dart file called celo.dart inside a new folder named services in the lib directory.

In the celo.dart file, write

import 'package:http/http.dart';
import 'package:web3dart/web3dart.dart';
import 'package:web3dart/credentials.dart';

class Celo {
  late Web3Client _client;

  Celo() {
    Client httpClient = Client();
    _client = new Web3Client('https://alfajores-forno.celo-testnet.org', httpClient);
  }
}

In the above code, we create a Celo class with a Web3Client instance which is responsible for connecting to the Celo network. We are using the Celo Alfajores Testnet URL for this tutorial.

Now we need the ABI (Application Binary Interface) of the deployed contract to interact with it. The ABI is a JSON representation of our smart contract, which lets the contract’s methods be called in a format the Celo node can understand. Go back to Remix IDE, select the contract, go to the “Solidity compiler” tab, and under the “Contract” dropdown, click on the ABI button to copy the ABI to your clipboard.

Create a new file named celo.abi.json in the services folder and paste the copied ABI into it

In your lib/services/celo.dart file, add the ABI directory to the top of the class, contract address, and credentials as follows:


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

const abiDirectory = 'lib/services/celo.abi.json';

class Celo {
   late Web3Client _client;

   Celo() {
    Client httpClient = Client();
    _client = new Web3Client('https://alfajores-forno.celo-testnet.org', httpClient);
  }

   // Add these lines:
   final EthereumAddress _contractAddress = EthereumAddress.fromHex('<paste-the-contract-address-here>');
   final credentials = EthPrivateKey.fromHex('<paste-your-private-key-here>');

   //...

}

Make sure to replace <paste-the-contract-address-here> with your contract’s address and <paste-your-private-key-here> with the private key of the Alfajores wallet you created earlier. Also, we need to declare celo.abi.json as an asset in our pubspec.yaml file as shown below:

Reading from the Contract

To read the greet state from our contract, we’ll create a new method readGreet:

  Future<String> readGreet() async {
    final contractABI = await rootBundle.loadString(abiDirectory);
    final contract = DeployedContract(
        ContractAbi.fromJson(contractABI, "HelloWorld"), _contractAddress);
    final function = contract.function('getGreet');

    final response = await _client.call(
      contract: contract,
      function: function,
      params: [],
    );

    return response[0].toString();
  }

Below is the breakdown of the readGreet function:

Future<String> readGreet() async {
    final contractABI = await rootBundle.loadString(abiDirectory);

This function reads the greeting stored in our smart contract. It first loads the contract’s ABI from the directory specified earlier.

final contract = DeployedContract(
        ContractAbi.fromJson(contractABI, "HelloWorld"), _contractAddress);

Here, we created a new DeployedContract instance using the ABI and the contract address. This object represents our deployed contract and lets us interact with it.

final function = contract.function('getGreet');

This line gets the function getGreet from our contract.

final response = await _client.call(
      contract: contract,
      function: function,
      params: [],
    );

This line sends a call to the getGreet function of our contract. The call method is used for read-only operations in the contract.

return response[0].toString();

Finally, the function returns the greeting obtained from the smart contract.

Writing to the Contract

To change the greet message, we’ll create another method writeGreet:

  Future<String> writeGreet(String message) async {
    final contractABI = await rootBundle.loadString(abiDirectory);
    final contract = DeployedContract(
        ContractAbi.fromJson(contractABI, "HelloWorld"), _contractAddress);
    final function = contract.function('setGreet');

    final response = await _client.sendTransaction(
      credentials,
      Transaction.callContract(
        contract: contract,
        function: function,
        parameters: [message],
      ),
      chainId: 44787,
    );

    return response;
  }

The writeGreet function follows a similar pattern to readGreet, but instead of reading the greeting, it modifies it. It uses the sendTransaction method, which sends a transaction to the blockchain and modifies its state, unlike the call method used in readGreet.

That’s it! You can now interact with your Celo smart contract from your Flutter application.

Setting up the User Interface

Before we can display the greet message or change it, we’ll need to set up the user interface for our Flutter application.

Create a new file home_screen.dart in your lib folder and paste the following code to create a basic layout:

import 'package:celo_flutter_dapp/services/celo.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Celo Flutter Dapp'),
      ),
      // More code will go here
    );
  }
}

This will create a new screen with an app bar titled Celo Flutter Dapp.

Now, we need to add some logic and user interface elements to interact with the smart contract. Add the following code inside your _HomeScreenState class:

final _greetController = TextEditingController();
final _celo = Celo();

@override
void initState() {
  super.initState();
  _updateGreetMessage();
}

void _updateGreetMessage() async {
  final message = await _celo.readGreet();
  setState(() {
    _greetController.text = message;
  });
}

void _changeGreetMessage() async {
  await _celo.writeGreet(_greetController.text);
  _updateGreetMessage();
}

This creates a TextEditingController to handle the input for the greet message and an instance of our Celo class. When the screen is initialized (initState), we read the greet message from our contract and display it in a TextField. The _updateGreetMessage method reads the greet message from the contract and updates the TextField, while _changeGreetMessage changes the greet message in the contract and then updates the TextField.

Finally, we add the TextField and a button to our build method to enable the user to change the greet message:

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Celo Flutter Dapp'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(10.0),
        child: Column(
          children: [
            TextField(
              controller: _greetController,
              decoration: const InputDecoration(
                labelText: 'Greet Message',
              ),
            ),
            ElevatedButton(
              onPressed: _changeGreetMessage,
              child: const Text('Change Greet Message'),
            ),
          ],
        ),
      ),
    );
  }

Also, you would need to make changes to your main.dart file to ensure that HomeScreen is the screen that gets displayed when your app runs.

In your lib/main.dart file, make sure that you’re pointing to the HomeScreen class as your home route in the MaterialApp widget. Replace the code in your main.dart file with the code below:

import 'package:celo_flutter_dapp/home_screen.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomeScreen(),
    );
  }
}

This main.dart file sets up a new MaterialApp and makes the HomeScreen the default route when the app starts.

Now you have a complete Flutter application that interacts with your Celo smart contract! The TextField displays the current greet message and lets the user input a new one, and the RaisedButton changes the greet message in the contract when pressed.

When you debug the application to your emulator or device, you should have the layout below:

Conclusion

Congratulations! You’ve made it through this comprehensive tutorial on interacting with Celo smart contracts using Flutter. By now, you have gained the knowledge to set up a Flutter development environment, create and deploy a smart contract on the Celo network, and use web3dart to interact with the contract.

This knowledge equips you with the fundamentals to building powerful decentralized applications (DApps) on the Celo platform, extending their reach to mobile devices and improving user experience.

Next Steps

Building on what you’ve learned here, you can start exploring more complex smart contract interactions and dive deeper into the world of decentralized application development. Here are a few suggestions for your continued learning:

  • Experiment with different types of smart contracts, perhaps a voting system or a decentralized marketplace.
  • Delve deeper into the web3dart library and explore its various functionalities.
  • Develop a more interactive and feature-rich user interface for your Flutter application.
  • Explore the other components of the Celo ecosystem, like the Celo Wallet or the Celo Dollar (cUSD) stablecoin.

About the Author

I am a Flutter developer, a technical writer, and an ardent blockchain enthusiast. I enjoy transforming complex ideas into user-friendly content and applications. Connect with me on GitHub.

References

With that, we’ve reached the end of this tutorial. I hope it was a beneficial learning experience for you!

5 Likes

@Celo_Academy

1 Like

Congratulations on being our top contributor this week - your insights are incredibly valued! We’re excitedly looking forward to your upcoming tutorial. Keep up the fantastic work! :mortar_board: :seedling:

2 Likes

I hope this get approved; I can’t wait to understand more of your ideas. Thanks.

1 Like

Hi I will be reviewing this

okay @thompsonogoyi1t

@thompsonogoyi1t

Any update on this piece???

I will be reviewing this today

okay @thompsonogoyi1t