How to Build an AI Prediction Smart Contract Using GenLayer

cover
25 Jun 2024

AI-powered smart contracts use AI to access real-time web data and interpret natural language instructions, enhancing traditional smart contracts.

👩‍💻 What We'll Do...

In this tutorial, we'll cover:

  • What AI-powered smart contracts are and the concept of Intelligent Contracts.
  • Steps to build a prediction intelligent contract
  • Using the GenLayer simulator to test, deploy, and execute the contract.

Note: Some knowledge of Python is assumed and needed in this tutorial.

💡 Why AI-Powered Smart Contracts?

Smart contracts have been game-changers, no doubt. They are self-executing by nature, with the terms of the agreement directly written into code. When predetermined conditions are met, they deploy on a blockchain and ensure that transactions are processed securely, transparently without needing a third party.

However, smart contracts only follow specific instructions and cannot handle unexpected situations or complex requirements not in their programming. They don't learn or adapt based on what happens over time. Also, they cannot access external data independently. They need third-party services like Oracles to feed external data to smart contracts, enabling them to react to real-world events.

🤔 What is an Intelligent Contract?

This limitations of smart contracts is what GenLayer is trying to solve by creating Intelligent Contract that retains all the capabilities of traditional smart contracts but can also:

  • Use LLM models like GPT-4 and LLaMA to understand and process natural language instructions.

  • Access and use real-time data from the internet without the need for third-party tools.

GenLayer uses the Optimistic Democracy consensus method to validate transactions and operations of Intelligent Contracts. A key part of this consensus method is the Equivalence Principle. The Equivalence Principle is a specific rule or set of criteria used within the Optimistic Democracy framework to ensure accuracy and consistency when dealing with non-deterministic outputs, such as those generated by LLMs or real-time web data. As we go forward, I will explain more about the Equivalence Principle and how it works when we execute our intelligent contract.

For this blog, we are going to look at how to build a football prediction intelligent contract that can fetch real-time data from the web and process it using LLM to predict match outcomes. Sounds interesting, right?

Let's get right into it :).

⚒️ Setting up the GenLayer Simulator

Before we start building our contract, we need to set up the environment where we will run it. The GenLayer's Simulator is an interactive sandbox that we can use to build and test our Intelligent Contracts. Let’s set it up.

Prerequisites

  • Ensure you have the latest version of Docker installed and running.
  • Ensure both Node.js and npm are updated to their latest versions to avoid compatibility issues.

Install

Go to your terminal and copy-paste the following to install GenLayer on your computer:

npm install -g genlayer

Once installed, run the init command to start the process of setting up your development environment:

genlayer init

When you run this command, it initializes the setup with 5 validators and prompts you to select your preferred LLM provider(s).

There are three options you can choose from:

  • OpenAI: Fastest and most reliable option for running validators)

  • Ollama: Free and open-source option, it may perform slower than other options

  • Heurist: Inference provider for open source AI models

After you have made your selection, it automatically downloads and configures the necessary Docker containers for the GenLayer simulator environment. Once the setup is complete, you can access the GenLayer Simulator at http://localhost:8080/.

Now, let’s get to building our contract!

⚽ Building the Football Prediction Contract

The simulator has a code editor for writing code.

Intelligent Contracts are written in Python, making it ideal for handling data and string operations necessary for web interactions and natural language processing.

For this prediction contract, we will be retrieving our web data from the BBC Sport website and then we use an LLM to process the retrieved data to determine which team is the winning team. Let's see the step-by-step process to do this

If you want to skip the walkthrough, check the code on GitHub and go to the "Deploy Contract" section below.

1. Importing Required Libraries and Modules

First, we’ll import the libraries and modules that we will be using for our Intelligent Contract:

import json
from genvm.base.equivalence_principle import EquivalencePrinciple
from genvm.base.icontract import IContract

  • json: This module is used for parsing and handling JSON data, which is a common format for data interchange.
  • EquivalencePrinciple: This ensures that the results are consistent and accurate across different validators. It plays a crucial role in maintaining the integrity of non-deterministic outputs by comparing results from multiple validators.
  • IContract: The base class for creating Intelligent Contracts on GenLayer, providing essential properties and behaviors. It ensures that the contract integrates smoothly within the GenLayer (GenVM) environment.

2. Defining the Intelligent Contract Class

Now, we need to define our Intelligent Contract class in this case it is Prediction Market. Our Intelligent Contract contract class inherits from IContract. Inheriting from IContract is necessary to ensure that the contract executes properly within the GenLayer framework:

class PredictionMarket(IContract):

3. Adding Constructor Parameters

Next, we are going to initialize the state of the contract and set up any necessary parameters. This step is crucial as it defines the initial conditions and properties that our contract will use throughout its execution:

class PredictionMarket(IContract):
    def __init__(self, game_date: str, team1: str, team2: str):
        self.has_resolved = False
        self.game_date = game_date
        self.resolution_url = 'https://www.bbc.com/sport/football/scores-fixtures/' + game_date
        self.team1 = team1
        self.team2 = team2

In this constructor, we define the following parameters:

  • game_date: The date of the game formatted as 'YYYY-MM-DD'.
  • team1: The name of the first team participating in the match.
  • team2: The name of the second team participating in the match.
  • has_resolved: Indicates whether the game's outcome has already been resolved, preventing redundant processing.
  • resolution_url: The URL of the BBC Sport website from which the game results can be retrieved.

These parameters define the initial state of our contract, making it ready to process game outcomes.

4. Resolving the Game's Outcome

Now, let's add a method to determine the outcome of the game. This method ensures that we only process the game's outcome if it hasn't been resolved yet:

async def resolve(self) -> None:
    if self.has_resolved:
        return "Already resolved"

    final_result = {}

This method first checks if the outcome has already been determined by inspecting self.has_resolved. This prevents redundant processing and ensures efficiency. If the game hasn't been resolved yet, we initialize final_result to store the results. This dictionary will hold the final validated results of the game.

5. Writing the Equivalence Principle

The Equivalence Principle is very important when writing an Intelligent Contract. When you access the web or call LLMs, inconsistencies can arise. The Equivalence Principle like we said earlier is a specific rule or set of criteria used to validate the final outputs of non-deterministic operations (web or LLM calls). This principle uses multiple validators, with one acting as the leader to propose an outcome, and others validating this outcome based on the defined criteria or rule.

Therefore, in our contract, we need to define our Equivalence Principle to prevent inconsistencies in our output from the web or when processes with LLM:

async with EquivalencePrinciple(
        result=final_result,
        principle="The score and the winner have to be exactly the same",
        comparative=True,
    ) as eq:

For our prediction contract, the Equivalence Principle states that "The score and the winner have to be exactly the same." Validators will use this principle to compare their results with the leader’s result. If the result match according to the Equivalence Principle, then the final result is accepted. The comparative=True flag indicates that both the leader and the validators perform identical tasks and compare their results to ensure consistency.

Within this Equivalence Principle, we are going to fetch the web data about the game and process it using LLM.

Fetching Web Data

Moving forward, within the Equivalence Principle block we are going to retrieve the webpage content from the BBC Sport news website URL:

web_data = await eq.get_webpage(self.resolution_url)
print(web_data)

Once the data is retrieved, we are going to process it with the LLM to check the result and determine the winning team from the retrieved webpage.

Process retrieved data using LLM

To process the information from the retrieved webpage, we are going to craft prompts to send to the LLM, telling it exactly what it needs to do. When interacting with LLMs, it's important to create prompts that are clear and specific to guide the model in providing accurate and relevant responses. This is the prompt we crafted below:

task = f"""In the following web page, find the winning team in a matchup between the following teams:
Team 1: {self.team1}
Team 2: {self.team2}

Web page content:
{web_data}
End of web page data.

If it says "Kick off [time]" between the names of the two teams, it means the game hasn't started yet.
If you fail to extract the score, assume the game is not resolved yet.

Respond with the following JSON format:
{{
    "score": str, // The score with numbers only, e.g., "1:2", or "-" if the game is not resolved yet
    "winner": int, // The number of the winning team, 0 for draw, or -1 if the game is not yet finished
}}
"""
result = await eq.call_llm(task)
print(result)

The prompt we have crafted instructs the LLM to:

  • Identify the winning team and score from the retrieved web page

  • We also included a condition for the LLM to check if the game has not started yet. If the phrase "Kick off [time]" appears between the names of the two teams, it indicates that the game hasn't started. The LLM is instructed to recognize this scenario and understand that no result can be extracted yet.

  • We also included another condition to handle for the LLM to assume that the game is not resolved if it cannot extract the score. This ensures that incomplete or ongoing games are handled appropriately.

  • Finally, we ask the LLM to respond using a JSON format

This detailed prompt handles different scenarios and ensures that the LLM extracts and processes the required information accurately and consistently. Once the prompt is crafted, we send it to the LLM using the call_llm method.

5. Setting the Result According to the Equivalence Principle

Once our result is obtained from the LLM, it will then be checked and validated according to the Equivalence Principle defined above: "The score and the winner have to be exactly the same.” If the result match according to the Equivalence Principle, the final result is accepted.

eq.set(result)

Note: The validators don’t validate every step in the Equivalence Principle block and only focus on the end result to reduce the need for complex validations, saves resources and simplify the contract's operations

6. Parsing and Evaluating the Result

Once the result is validated and finalized, we can now parse the result using json.loads(). This converts the result into a format that can be easily manipulated and evaluated. From our parsed result, we will extract the winner and score:

result_json = json.loads(final_result['output'])

if result_json['winner'] > -1:
    self.has_resolved = True
    self.winner = result_json['winner']
    self.score = result_json['score']

return result_json

If the game's outcome is determined (winner > -1), the contract's state is updated accordingly. This ensures that the final outcome is accurately recorded.

Now we are ready to deploy our contract!

Let’s see our contract in action!

🚀 Deploy Your Football Prediction Intelligent Contract

  1. In the GenLayer Simulator, click on the play button to run your contract.

  2. In the constructor parameters section, provide the game date and the names of the two teams you want to check. For example, you might set game_date to "2024-06-05", team1 to "Brazil", and team2 to "Jamaica".

  3. Once the game details are set, click on Deploy

🎯 Executing Transactions

To interact with the deployed contract, go to the Execute Transactions section. Here, you can call the resolve method to process the game's result.

When the resolve method is executed:

  • The nodes running by the simulator retrieve the data from the specified URL, which is the BBC Sport URL in this case and then the LLM process it
  • The final game's outcome is then validated according to the Equivalence Principle defined in the code.

How the Equivalence Principle Works When an Intelligent Contract is Executed:

  • Leader Calculation: The Leader validator fetches the game data from BBC Sport and determines the final score and the winning team. For instance, the Leader calculates the score as "1:2" with Jamaica (Team 2) winning against Brazil (Team 1).
  • Validators' Calculations: Each validator independently fetches the game data from the same URL and performs the same task to determine the final score and the winner. For example, one validator might also calculate the score as "1:2" with Jamaica (Team 2) winning against Brazil (Team 1).
  • Comparison: The validators compare their results with the Leader's results. According to the Equivalence Principle, the score and the winner must be exactly the same. If the Leader's result is "1:2" with Jamaica (Team 2) as the winner, then each validator's result should also be "1:2" with Jamaica (Team 2) as the winner.
  • Decision: If all the validators' results match the Leader's result, they accept the Leader's result as valid.

This process ensures consistency and accuracy across the network. If the validators return "1:3" with Jamaica (Team 2) as the winner and the leader returns "1:2" with Jamaica (Team 2), the validators will reject the result.

Handling Different Scenarios:

  • If the game has finished, the JSON response will include the final score and the winning team.
  • If the game has started but not finished, the JSON response will indicate the game is not resolved yet.
  • If the game hasn't started, the JSON response will indicate this status.

View the logs to see detailed information about the contract interaction.

💭 Final Thoughts: Other Use Cases for AI Smart Contracts

🙌 Congrats if you read all the way through!!!

The future looks bright for AI-powered smart contracts. Aside from the football prediction contract, there are other intelligent contract ideas you can build and test with the GenLayer Simulator:

  • Weather Reporter
  • ERC-20 Token
  • Stock Price Checker
  • News Summarizer
  • Travel Planner
  • Book Finder
  • Logic Puzzles
  • Wizard of Coin

There are more example ideas in the GenLayer docs on how to achieve some of the above too.