Welcome to the "From Zero to GenLayer" tutorial! In this series, you'll embark on a hands-on journey to understand and build decentralized applications (dApps) on the GenLayer network. We'll demystify core GenLayer concepts and guide you through creating a practical dApp: a Decentralized Truth Machine.
This tutorial is designed for developers new to GenLayer, providing a clear path from foundational knowledge to a working dApp. By the end, you'll have a solid understanding of GenLayer's unique features and the skills to start building your own innovative projects.
- GenLayer Core Concepts: Dive into Optimistic Democracy Consensus and the Equivalence Principle.
- Intelligent Contracts: Write a Python-based Intelligent Contract that leverages AI for decision-making.
- Frontend Development: Build a simple web interface using
genlayer-jsto interact with your Intelligent Contract. - Deployment: Understand how to deploy and manage your dApp using GenLayer Studio.
The Decentralized Truth Machine is a dApp where users can submit claims, and the GenLayer network, powered by its Intelligent Contracts, will determine the truthfulness of these claims. This project will showcase how GenLayer can be used to create reliable, AI-driven dispute resolution or fact-checking mechanisms.
This tutorial is divided into several parts:
- Part 1: Understanding GenLayer's Core - Optimistic Democracy & Equivalence Principle
- Part 2: Crafting Your Intelligent Contract - The Truth Machine (Python)
- Part 3: Building the Frontend - Interacting with
genlayer-js - Part 4: Deploying Your dApp with GenLayer Studio
Let's get started!
GenLayer introduces a novel approach to decentralized consensus, combining the efficiency of optimistic systems with the power of AI. At its heart are two fundamental concepts: Optimistic Democracy and the Equivalence Principle.
Optimistic Democracy is GenLayer's AI-native consensus mechanism designed to validate transactions and operations of Intelligent Contracts [1]. Unlike traditional blockchain consensus mechanisms that require all nodes to execute every transaction, Optimistic Democracy operates on an optimistic model. This means that transactions are assumed to be valid unless challenged. Validators monitor the network and only intervene if they detect a fraudulent or incorrect operation. This significantly increases throughput and reduces latency compared to traditional methods.
Key aspects of Optimistic Democracy:
- Efficiency: Transactions are processed quickly without requiring redundant computation from all nodes.
- Scalability: The optimistic nature allows for a higher volume of transactions.
- Security: A robust challenge mechanism ensures that invalid transactions can be identified and penalized.
The Equivalence Principle is a crucial component that works in conjunction with Optimistic Democracy, especially for Intelligent Contracts that involve AI computations. Intelligent Contracts often produce non-deterministic outputs (e.g., from an LLM). The Equivalence Principle addresses this by defining criteria for an acceptable summary or output, rather than requiring an exact match [2].
For example, if an Intelligent Contract asks an AI to summarize a document, different AI models or even the same model run at different times might produce slightly different but equally valid summaries. The Equivalence Principle allows validators to approve any summary that meets predefined criteria (e.g., accuracy, relevance, length) instead of demanding a byte-for-byte identical output.
Key aspects of the Equivalence Principle:
- AI Integration: Enables the use of AI in Intelligent Contracts by accommodating non-deterministic outputs.
- Criteria-Based Validation: Validators evaluate outputs against a set of criteria, not an exact match.
- Flexibility: Allows for variations in AI responses while maintaining integrity and consensus.
Together, Optimistic Democracy and the Equivalence Principle create a powerful and efficient framework for building AI-driven dApps on GenLayer. They allow for the speed and scalability needed for modern applications while ensuring the decentralized security and trustworthiness of the blockchain.
In this part, we'll write the core logic of our Decentralized Truth Machine using a Python Intelligent Contract. This contract will allow users to submit a claim, and then use an AI (Large Language Model) to determine if the claim is true or false based on predefined criteria. The Equivalence Principle will be key here to handle the AI's non-deterministic output.
Before we start coding, ensure you have the necessary tools installed. You'll need Python and the GenLayer SDK. For simplicity, we'll assume you're working within a virtual environment.
pip install py-genlayerLet's create a file named truth_machine.py for our Intelligent Contract:
# { "Depends": "py-genlayer" }
from genlayer import *
import typing
class TruthMachine(gl.Contract):
claims: dict[str, str] # Stores claims and their truth status
def __init__(self):
self.claims = {}
@gl.public.write
def submit_claim(self, claim_id: str, statement: str) -> None:
# Use AI to determine the truthfulness of the statement
# The Equivalence Principle ensures validators agree on the 'truth' based on criteria
truth_status = gl.eq_principle.prompt_non_comparative(
lambda: f"Is the following statement true or false? Answer with 'True' or 'False'. Statement: {statement}",
task=f"Determine if the statement '{statement}' is true or false.",
criteria="The answer must be either 'True' or 'False' based on common knowledge or factual information."
)
self.claims[claim_id] = truth_status
@gl.public.view
def get_claim_status(self, claim_id: str) -> str:
return self.claims.get(claim_id, "Claim not found")
@gl.public.view
def get_all_claims(self) -> dict[str, str]:
return self.claims# { "Depends": "py-genlayer" }: This special comment tells the GenLayer VM that our contract depends on thepy-genlayerlibrary.TruthMachine(gl.Contract): Our contract class inherits fromgl.Contract, making it a valid GenLayer Intelligent Contract.claims: dict[str, str]: This declares a state variableclaimswhich is a dictionary. It will storeclaim_id(string) as keys and theirtruth_status(string, either "True" or "False") as values.__init__(self): The constructor initializes theclaimsdictionary as empty.@gl.public.write def submit_claim(self, claim_id: str, statement: str) -> None:@gl.public.write: This decorator markssubmit_claimas a public write method, meaning it can modify the contract's state and be called by external users.- Inside this method, we use
gl.eq_principle.prompt_non_comparativeto interact with an AI model. This function takes three arguments:- A
lambdafunction that defines the prompt sent to the AI. We ask the AI to determine if a givenstatementis true or false. task: A brief description of the task for the AI.criteria: This is where the Equivalence Principle comes into play. We define strict criteria: "The answer must be either 'True' or 'False' based on common knowledge or factual information." This allows different AI models (or runs) to produce slightly different reasoning, but as long as their final answer is 'True' or 'False' and adheres to the criteria, validators will agree on the outcome.
- A
- The AI's
truth_statusis then stored in ourclaimsdictionary.
@gl.public.view def get_claim_status(self, claim_id: str) -> str:@gl.public.view: This decorator marksget_claim_statusas a public view method. It can read the contract's state without modifying it, and thus doesn't cost gas.- It returns the truth status for a specific
claim_idor "Claim not found" if the ID doesn't exist.
@gl.public.view def get_all_claims(self) -> dict[str, str]:- Another public view method that returns the entire
claimsdictionary, allowing anyone to see all submitted claims and their determined truth statuses.
- Another public view method that returns the entire
This Intelligent Contract demonstrates how GenLayer enables AI-powered logic directly on-chain, with the Equivalence Principle ensuring consensus even with non-deterministic AI outputs. Next, we'll build a frontend to interact with this contract. contract.
Now that our Intelligent Contract is ready, let's build a simple web frontend to interact with it. We'll use genlayer-js, the official JavaScript SDK for GenLayer, to connect to the network, deploy our contract (conceptually, as deployment will be covered in Part 4), and call its methods.
For this tutorial, we'll keep the frontend very simple, using plain HTML, CSS, and JavaScript. Create a new folder for your frontend and inside it, create an index.html file and a script.js file.
First, install genlayer-js in your frontend project directory:
npm init -y
npm install genlayer-jsThis file will contain the basic structure of our dApp, including input fields for claims and buttons to interact with the contract.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Decentralized Truth Machine</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; }
.container { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); max-width: 600px; margin: auto; }
input[type="text"], textarea { width: calc(100% - 22px); padding: 10px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 4px; }
button { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
button:hover { background-color: #45a049; }
.result { margin-top: 20px; padding: 10px; background-color: #e9e9e9; border-radius: 4px; }
</style>
</head>
<body>
<div class="container">
<h1>Decentralized Truth Machine</h1>
<h2>Submit a Claim</h2>
<label for="claimIdInput">Claim ID:</label>
<input type="text" id="claimIdInput" placeholder="Enter a unique claim ID">
<label for="statementInput">Statement:</label>
<textarea id="statementInput" rows="4" placeholder="Enter the statement to verify"></textarea>
<button id="submitClaimBtn">Submit Claim</button>
<h2>Check Claim Status</h2>
<label for="checkClaimIdInput">Claim ID:</label>
<input type="text" id="checkClaimIdInput" placeholder="Enter claim ID to check">
<button id="checkStatusBtn">Check Status</button>
<div class="result" id="claimStatusResult"></div>
<h2>All Claims</h2>
<button id="getAllClaimsBtn">Get All Claims</button>
<pre class="result" id="allClaimsResult"></pre>
</div>
<script type="module" src="script.js"></script>
</body>
</html>This JavaScript file will contain the logic to interact with our TruthMachine Intelligent Contract using genlayer-js.
import { createClient, createAccount } from 'genlayer-js';
import { localnet } from 'genlayer-js/chains';
import { TransactionStatus } from 'genlayer-js/types';
// --- Configuration ---
// IMPORTANT: Replace with your actual deployed contract address
const CONTRACT_ADDRESS = '0x...'; // You will get this after deploying in Part 4
// --- Initialize GenLayer Client ---
const account = createAccount(); // Generates a new local account for signing transactions
const client = createClient({
chain: localnet, // Connect to the local GenLayer network
account: account,
});
// --- Helper Function to Display Results ---
function displayResult(elementId, message, isError = false) {
const element = document.getElementById(elementId);
element.textContent = message;
element.style.color = isError ? 'red' : 'green';
}
// --- Contract Interaction Functions ---
async function submitClaim() {
const claimId = document.getElementById('claimIdInput').value;
const statement = document.getElementById('statementInput').value;
if (!claimId || !statement) {
displayResult('claimStatusResult', 'Please enter both Claim ID and Statement.', true);
return;
}
displayResult('claimStatusResult', 'Submitting claim... This may take a moment.', false);
try {
// Ensure the consensus smart contract is initialized
await client.initializeConsensusSmartContract();
const txHash = await client.writeContract({
address: CONTRACT_ADDRESS,
functionName: 'submit_claim',
args: [claimId, statement],
value: 0n,
});
displayResult('claimStatusResult', `Transaction sent: ${txHash}. Waiting for confirmation...`, false);
const receipt = await client.waitForTransactionReceipt({
hash: txHash,
status: TransactionStatus.ACCEPTED,
retries: 100,
interval: 5000,
});
displayResult('claimStatusResult', `Claim '${claimId}' submitted successfully! Status: ${receipt.status}`, false);
} catch (error) {
console.error('Error submitting claim:', error);
displayResult('claimStatusResult', `Error: ${error.message}`, true);
}
}
async function checkClaimStatus() {
const claimId = document.getElementById('checkClaimIdInput').value;
if (!claimId) {
displayResult('claimStatusResult', 'Please enter a Claim ID to check.', true);
return;
}
displayResult('claimStatusResult', `Checking status for claim '${claimId}'...`, false);
try {
const status = await client.readContract({
address: CONTRACT_ADDRESS,
functionName: 'get_claim_status',
args: [claimId],
});
displayResult('claimStatusResult', `Status for '${claimId}': ${status}`, false);
} catch (error) {
console.error('Error checking claim status:', error);
displayResult('claimStatusResult', `Error: ${error.message}`, true);
}
}
async function getAllClaims() {
displayResult('allClaimsResult', 'Fetching all claims...', false);
try {
const allClaims = await client.readContract({
address: CONTRACT_ADDRESS,
functionName: 'get_all_claims',
args: [],
});
document.getElementById('allClaimsResult').textContent = JSON.stringify(allClaims, null, 2);
document.getElementById('allClaimsResult').style.color = 'black';
} catch (error) {
console.error('Error fetching all claims:', error);
displayResult('allClaimsResult', `Error: ${error.message}`, true);
}
}
// --- Event Listeners ---
document.getElementById('submitClaimBtn').addEventListener('click', submitClaim);
document.getElementById('checkStatusBtn').addEventListener('click', checkClaimStatus);
document.getElementById('getAllClaimsBtn').addEventListener('click', getAllClaims);
// Initial check to ensure client is ready
client.initializeConsensusSmartContract().then(() => {
console.log('GenLayer client initialized and ready.');
}).catch(error => {
console.error('Failed to initialize GenLayer client:', error);
alert('Failed to connect to GenLayer network. Please ensure your localnet is running.');
});- Save the
index.htmlandscript.jsfiles in the same directory. - Open your terminal in that directory and run
npm install genlayer-js. - You'll need a local web server to serve these files. A simple way is to use
live-server(install withnpm install -g live-server) or Python'shttp.server(python -m http.server). - Open
index.htmlin your browser via the local server. You should see the dApp interface.
Note: The CONTRACT_ADDRESS in script.js needs to be updated with the actual address of your deployed TruthMachine contract. We will cover deployment in the next part.
Deploying your Intelligent Contract to the GenLayer network is a straightforward process, especially when using the GenLayer Studio. GenLayer Studio provides a user-friendly interface to manage your contracts, deploy them to various networks (local, testnet, mainnet), and interact with them.
Before deploying, ensure you have:
- GenLayer CLI installed: This is often part of the GenLayer SDK setup.
- GenLayer localnet running: For development and testing, you'll typically deploy to a local GenLayer network instance.
-
Start GenLayer Studio: Open your terminal and run the GenLayer Studio command (refer to GenLayer documentation for specific command, usually
genlayer studio). This will typically open a web interface in your browser. -
Connect to Your Network: In GenLayer Studio, ensure you are connected to your local GenLayer network. You might need to configure the endpoint if it's not automatically detected.
-
Upload Your Contract: Navigate to the contract deployment section in GenLayer Studio. You will typically have an option to upload your Python Intelligent Contract file (
truth_machine.py). -
Deploy the Contract: Once uploaded, GenLayer Studio will guide you through the deployment process. Since our
TruthMachinecontract has no constructor arguments, deployment is simple. Confirm the deployment. -
Note the Contract Address: After successful deployment, GenLayer Studio will provide you with the contract address. This is a unique identifier for your deployed contract on the GenLayer network. Copy this address carefully.
-
Update Your Frontend: Go back to your
script.jsfile in your frontend project and replace the placeholderCONTRACT_ADDRESS = '0x...';with the actual address you copied from GenLayer Studio.// --- Configuration --- // IMPORTANT: Replace with your actual deployed contract address const CONTRACT_ADDRESS = 'YOUR_DEPLOYED_CONTRACT_ADDRESS_HERE';
-
Test Your dApp: With the contract deployed and your frontend updated, refresh your
index.htmlin the browser. You can now use the frontend to:- Submit new claims, which will trigger the
submit_claimmethod on your Intelligent Contract. - Check the status of specific claims using
get_claim_status. - View all claims and their truth statuses using
get_all_claims.
- Submit new claims, which will trigger the
Congratulations! You have successfully deployed your first GenLayer dApp, the Decentralized Truth Machine, and built a frontend to interact with it. This project demonstrates the power of Intelligent Contracts and the ease of deployment with GenLayer Studio.
[1] GenLayer Documentation. "Optimistic Democracy." https://docs.genlayer.com/understand-genlayer-protocol/core-concepts/optimistic-democracy [2] GenLayer Documentation. "Equivalence Principle Mechanism." https://docs.genlayer.com/understand-genlayer-protocol/core-concepts/optimistic-democracy/equivalence-principle [3] GenLayer Documentation. "GenLayerJS SDK Reference." https://docs.genlayer.com/api-references/genlayer-js [4] GenLayer Documentation. "LlmHelloWorldNonComparative Contract." https://docs.genlayer.com/developers/intelligent-contracts/examples/llm-hello-world-non-comparative