<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Big Boss</title>
    <description>The latest articles on Forem by Big Boss (@usman3801).</description>
    <link>https://forem.com/usman3801</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3781134%2F2b4f0d5f-1a83-4f7f-b402-8e31ca0bbd6c.jpeg</url>
      <title>Forem: Big Boss</title>
      <link>https://forem.com/usman3801</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/usman3801"/>
    <language>en</language>
    <item>
      <title>Build a Trustless Dispute Resolution dApp</title>
      <dc:creator>Big Boss</dc:creator>
      <pubDate>Thu, 19 Feb 2026 18:08:06 +0000</pubDate>
      <link>https://forem.com/usman3801/build-a-trustless-dispute-resolution-dapp-2fb9</link>
      <guid>https://forem.com/usman3801/build-a-trustless-dispute-resolution-dapp-2fb9</guid>
      <description>&lt;p&gt;A hands-on guide to Intelligent Contracts, Optimistic Democracy, and building your first AI-powered decentralized application.&lt;/p&gt;

&lt;p&gt;What You'll Learn Part 1 - Understanding GenLayer's Core Ideas&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is GenLayer?&lt;/li&gt;
&lt;li&gt;Optimistic Democracy Consensus&lt;/li&gt;
&lt;li&gt;The Equivalence Principle Part 2 - Setting Up Your Environment&lt;/li&gt;
&lt;li&gt;Installing the GenLayer CLI&lt;/li&gt;
&lt;li&gt;Using GenLayer Studio Part 3 - Writing the Intelligent Contract (Python)&lt;/li&gt;
&lt;li&gt;Designing the Dispute Contract&lt;/li&gt;
&lt;li&gt;Full Contract Code&lt;/li&gt;
&lt;li&gt;Code Walkthrough&lt;/li&gt;
&lt;li&gt;Common Pitfalls I Encountered Part 4 - Deploying &amp;amp; Testing in Studio Part 5 - Building the Frontend with genlayer-js&lt;/li&gt;
&lt;li&gt;Project Setup&lt;/li&gt;
&lt;li&gt;Connecting to the Hosted Studio&lt;/li&gt;
&lt;li&gt;Frontend Code (Updated &amp;amp; Working)&lt;/li&gt;
&lt;li&gt;Troubleshooting Real Issues Part 6 - Where to Go From Here&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuba3r16wvgstw5uxdbsa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuba3r16wvgstw5uxdbsa.png" alt=" " width="702" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Welcome! In this tutorial, you'll go from knowing nothing about GenLayer to having a fully working Dispute Resolution dApp - a mini application where two parties submit their side of a disagreement, and GenLayer's AI-powered validators autonomously decide who's right.&lt;/p&gt;

&lt;p&gt;This project is perfect for learning GenLayer because it exercises every key feature: non-deterministic AI reasoning, the Equivalence Principle, and a frontend that interacts with the contract via genlayer-js.&lt;/p&gt;

&lt;p&gt;Prerequisites: Basic Python knowledge, familiarity with JavaScript/TypeScript, and comfort using a terminal. No blockchain experience is required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;¬ PART 1 &lt;br&gt;
What is GenLayer?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Traditional blockchains are deterministic - the same input always produces the same output. That's great for math-based operations like transferring tokens, but it means smart contracts can't reason about subjective, real-world situations. They can't read a website, interpret a clause in natural language, or judge whether a task was completed satisfactorily.&lt;/p&gt;

&lt;p&gt;GenLayer changes this. It's the first blockchain designed to handle non-deterministic operations natively. Its smart contracts - called Intelligent Contracts - are written in Python and can call LLMs (like GPT or LLaMA), fetch live web data, and make subjective decisions, all while maintaining blockchain-grade trust and security.&lt;/p&gt;

&lt;p&gt;Think of it this way: Bitcoin gave us trustless money, Ethereum gave us trustless computation, and GenLayer gives us trustless decision-making.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimistic Democracy Consensus&lt;/strong&gt;&lt;br&gt;
How does GenLayer reach agreement when AI outputs are inherently variable? Through a consensus mechanism called Optimistic Democracy, which is an enhanced Delegated Proof of Stake model. Here's how it works:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1g3m4us81qmb9txp0ney.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1g3m4us81qmb9txp0ney.png" alt=" " width="689" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;KEY INSIGHT: Each validator on GenLayer runs its own AI model. When a transaction contains a non-deterministic component - like interpreting a dispute - each validator processes it independently. They don't need to produce identical outputs. They just need to produce equivalent outputs. That's where the Equivalence Principle comes in.&lt;/p&gt;

&lt;p&gt;If there's a disagreement among validators, an appeal process kicks in: more validators are recruited to re-evaluate, and the dispute is settled by a larger jury. This is inspired by Condorcet's Jury Theorem - the idea that a majority of independent, reasonably accurate decision-makers will almost certainly reach the correct conclusion.&lt;/p&gt;

&lt;p&gt;The Equivalence Principle&lt;br&gt;
The Equivalence Principle is the mechanism that lets validators agree on non-deterministic results. As a developer, you define what "equivalent" means for your contract. GenLayer provides three built-in strategies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Strict Equality - gl.eq_principle_strict_eq(fn) &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Validators must produce the exact same output. Best for deterministic-like operations - for example, checking whether a webpage contains a specific keyword (returns True or False).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Comparative - gl.eq_principle_prompt_comparative(fn, principle) &lt;br&gt;
Both the leader and validators perform the same task, then an LLM compares their results using the principle you provide. Example principle: "Both answers should identify the same winning party and give similar reasoning."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Non-Comparative - gl.eq_principle_prompt_non_comparative(fn, task=…, criteria=…) &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only the leader performs the task. Validators then evaluate the leader's output against your criteria without re-doing the work. This is faster and cheaper - ideal for subjective outputs like text summaries or, in our case, dispute rulings.&lt;/p&gt;

&lt;p&gt;In this Dispute Resolution dApp, I'll use the comparative principle: both leader and validators will independently analyze the dispute, and I'll define that their rulings are "equivalent" if they pick the same winner and provide logically consistent reasoning.&lt;/p&gt;

&lt;p&gt;¬PART 2 &lt;br&gt;
GenLayer provides two ways to develop: a local environment via the CLI, or the hosted GenLayer Studio at studio.genlayer.com. For this tutorial, I used the hosted Studio - it's the fastest way to get started with zero Docker setup.&lt;/p&gt;

&lt;p&gt;Option A: Hosted Studio (What I Used)&lt;/p&gt;

&lt;p&gt;Simply visit studio.genlayer.com in your browser. No installation required. You get a code editor, deployment tools, and transaction logs immediately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9dpfi16pllhfvrecl10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9dpfi16pllhfvrecl10.png" alt=" " width="698" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Option B: Local Development&lt;br&gt;
If you prefer running locally, you'll need Docker 26+, Node.js 18+, and Python 3.10+:&lt;/p&gt;

&lt;p&gt;Install the CLI globally&lt;br&gt;
npm install -g genlayer&lt;/p&gt;

&lt;p&gt;Initialize your local environment&lt;br&gt;
genlayer init&lt;/p&gt;

&lt;p&gt;During initialization, you'll be prompted to select your preferred LLM provider(s) and enter API keys. The setup spins up a local GenLayer network with 5 validators. Once complete, the Studio opens at &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;http://localhost:3000/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;¬ PART 3 &lt;br&gt;
Here's the idea for our dApp: two parties have a disagreement. Each submits their side of the story. The contract uses an LLM to analyze both arguments and deliver a ruling - all on-chain, all trustless. No judge, no middleman, no bias.&lt;/p&gt;

&lt;p&gt;Let's think through the design:&lt;/p&gt;

&lt;p&gt;State: We need to store the dispute topic, both parties' arguments, the ruling, and a status field. &lt;/p&gt;

&lt;p&gt;Write methods: submit_argument() for each party, and resolve_dispute() to trigger AI-powered resolution. &lt;/p&gt;

&lt;p&gt;Read methods: get_dispute() to view the current state. &lt;/p&gt;

&lt;p&gt;Equivalence: When resolving, we'll use eq_principle_prompt_comparative so that multiple validators independently analyze the dispute and agree on the outcome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full Contract Code&lt;br&gt;
{ "Depends": "py-genlayer:test" }&lt;/strong&gt;&lt;br&gt;
from genlayer import *&lt;/p&gt;

&lt;p&gt;class DisputeResolver(gl.Contract): # ── State variables ── topic: str argument_a: str argument_b: str ruling: str status: str # "open", "pending", "resolved"&lt;br&gt;
&lt;code&gt;def __init__(self, topic: str):&lt;br&gt;
      """Deploy the contract with a dispute topic."""&lt;br&gt;
      self.topic = topic&lt;br&gt;
      self.argument_a = ""&lt;br&gt;
      self.argument_b = ""&lt;br&gt;
      self.ruling = ""&lt;br&gt;
      self.status = "open"&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@gl.public.write
  def submit_argument(self, argument: str):
      """Either party submits their side of the dispute."""
      if self.status != "open":
          raise Exception("Dispute is no longer accepting arguments")
      if self.argument_a == "":
          self.argument_a = argument
      elif self.argument_b == "":
          self.argument_b = argument
          self.status = "pending"
      else:
          raise Exception("Both arguments already submitted")
  @gl.public.write
  def resolve_dispute(self):
      """Trigger AI-powered dispute resolution."""
      if self.status != "pending":
          raise Exception("Dispute is not ready for resolution")
      topic = self.topic
      arg_a = self.argument_a
      arg_b = self.argument_b
      def analyze_dispute():
          prompt = f"""You are an impartial arbitrator. Analyze this dispute and provide a fair ruling.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DISPUTE TOPIC: {topic}&lt;br&gt;
PARTY A's ARGUMENT: {arg_a}&lt;br&gt;
PARTY B's ARGUMENT: {arg_b}&lt;br&gt;
Provide your ruling in this exact format: WINNER: [Party A or Party B or Draw] REASONING: [Your 2–3 sentence explanation]"""&lt;/p&gt;

&lt;p&gt;&lt;code&gt;result = gl.exec_prompt(prompt)&lt;br&gt;
          return result&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Comparative equivalence: both leader and validators
      # independently analyze, then compare results
      self.ruling = gl.eq_principle_prompt_comparative(
          analyze_dispute,
          """The rulings are equivalent if they identify the same
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;winning party (Party A, Party B, or Draw) and the reasoning is logically consistent, even if worded differently.""" ) self.status = "resolved"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@gl.public.view
  def get_dispute(self) -&amp;gt; dict:
      """Return the full dispute state."""
      return {
          "topic": self.topic,
          "argument_a": self.argument_a,
          "argument_b": self.argument_b,
          "ruling": self.ruling,
          "status": self.status,
      }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;problems i encountered &lt;/p&gt;

&lt;p&gt;When I first wrote this contract, I ran into two issues that aren't obvious from the docs:&lt;br&gt;
Don't use &lt;a class="mentioned-user" href="https://dev.to/gl"&gt;@gl&lt;/a&gt;.contract decorator - the class just needs to extend gl.Contract. Adding the decorator caused deployment errors.&lt;br&gt;
Don't use Address type fields - I originally had party_a: Address and party_b: Address, but these caused schema parsing errors in Studio. Sticking to str for all state variables works reliably.&lt;/p&gt;

&lt;p&gt;These are the kinds of gotchas you only discover by building - and exactly why hands-on tutorials matter.&lt;/p&gt;

&lt;p&gt;Code Walkthrough&lt;br&gt;
The dependency header # { "Depends": "py-genlayer:test" } tells the GenVM which SDK version to use. Every Intelligent Contract needs this.&lt;br&gt;
The class declaration - DisputeResolver extends gl.Contract, which is the base class that ensures the contract runs correctly inside GenLayer's execution environment. State variables are declared as type-annotated class attributes (like topic: str); GenLayer automatically handles their on-chain storage.&lt;br&gt;
The init method runs once at deployment. It takes the dispute topic as a constructor argument and initializes all state.&lt;br&gt;
submit_argument() is decorated with &lt;a class="mentioned-user" href="https://dev.to/gl"&gt;@gl&lt;/a&gt;.public.write, meaning it modifies state and requires a transaction. The logic is simple: first caller becomes Party A, second becomes Party B, and after both submit, the status moves to "pending".&lt;br&gt;
resolve_dispute() is where the magic happens. Notice the pattern:&lt;br&gt;
We define an inner function analyze_dispute() that calls gl.exec_prompt() - this sends a prompt to whatever LLM the validator is running.&lt;br&gt;
We wrap that function with gl.eq_principle_prompt_comparative(), passing our equivalence principle as the second argument.&lt;br&gt;
The leader validator runs analyze_dispute() and proposes a result. Other validators also run it independently, then compare their output to the leader's using the principle we defined.&lt;br&gt;
If the majority agrees the results are equivalent (same winner, consistent logic), the transaction is accepted and the ruling is stored on-chain.&lt;/p&gt;

&lt;p&gt;WHY THIS PATTERN MATTERS: The inner-function-plus-equivalence-wrapper pattern is the core of every non-deterministic operation in GenLayer. All calls to gl.exec_prompt() or gl.get_webpage() must happen inside a function passed to an eq_principle method. This is what allows multiple validators to independently verify the result. Think of it as GenLayer's version of "trust, but verify."&lt;br&gt;
get_dispute() is a read-only view method (no transaction needed) that returns the full dispute state as a dictionary.&lt;/p&gt;

&lt;p&gt;¬ PART 4 &lt;br&gt;
Now let's see our contract in action. Open GenLayer Studio (either local at &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;http://localhost:3000/&lt;/a&gt; or hosted at studio.genlayer.com).&lt;/p&gt;

&lt;p&gt;Step 1&lt;br&gt;
Load the Contract In the Studio's code editor, paste the full contract code from above. The editor supports Python syntax highlighting and will flag basic errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo27pk813fe3fxyg9wzp8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo27pk813fe3fxyg9wzp8.png" alt=" " width="684" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 2&lt;br&gt;
Deploy In the deploy section, our contract takes one constructor argument - the dispute topic. Enter something like:&lt;/p&gt;

&lt;p&gt;"Who makes better pizza: New York or Chicago?"&lt;/p&gt;

&lt;p&gt;Click Deploy. You'll see the transaction process through the validators. Once finalized, note your contract address - you'll need it for the frontend. The address appears in the left sidebar (it looks like 0x33…A538).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzu6uaw4foea3dvejn86d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzu6uaw4foea3dvejn86d.png" alt=" " width="284" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 3&lt;br&gt;
Submit Arguments In the "Write Methods" section, expand submit_argument and call it twice:&lt;/p&gt;

&lt;p&gt;First call (Party A): "New York pizza is superior because the thin, foldable crust allows the quality of ingredients to shine. The high-gluten bread flour creates the perfect crispy-yet-chewy texture."&lt;/p&gt;

&lt;p&gt;Second call (Party B): "Chicago deep-dish is the true pizza because it is a complete meal. The buttery crust, layered cheese, and chunky tomato sauce create a richer, more satisfying experience."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11qyqayilkldmv1tql72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11qyqayilkldmv1tql72.png" alt=" " width="500" height="791"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 4&lt;br&gt;
Verify the State Call get_dispute() in the "Read Methods" section. You should see both arguments stored and the status as "pending".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1y8pvpl43j7ggy0ep2dl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1y8pvpl43j7ggy0ep2dl.png" alt=" " width="482" height="861"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 5&lt;br&gt;
Resolve the Dispute Call resolve_dispute() in Write Methods. Watch the logs - you'll see the leader validator send the prompt to its LLM, generate a ruling, and then the other validators independently do the same and compare results. This can take 30–60 seconds on the hosted Studio.&lt;br&gt;
Step 6: Read the Result Call get_dispute() again. You should now see the AI-generated ruling and the status set to "resolved".&lt;br&gt;
Debugging Tips &lt;br&gt;
If a transaction fails, check the Studio's log panel at the bottom. Common issues:&lt;br&gt;
Transaction stuck at "PROPOSING" - the hosted Studio can be slow. Wait 30–60 seconds.&lt;br&gt;
"Both arguments already submitted" - the contract already has two arguments. Deploy a fresh instance.&lt;br&gt;
Syntax errors appear on a red panel - check for Python indentation issues.&lt;/p&gt;

&lt;p&gt;¬PART 5 &lt;br&gt;
Now let's build a web frontend so real users can interact with our contract. We'll use the GenLayer project boilerplate and genlayer-js - the official TypeScript SDK.&lt;br&gt;
Step 1: Clone the Boilerplate&lt;br&gt;
&lt;code&gt;git clone https://github.com/genlayerlabs/genlayer-project-boilerplate &lt;br&gt;
cd genlayer-project-boilerplate/frontend npm install&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Windows users: &lt;br&gt;
Use PowerShell.&lt;br&gt;
 The boilerplate structure has frontend/app/page.tsx (no src folder).&lt;/p&gt;

&lt;p&gt;Step 2: Configure the Environment&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linux/Mac&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cp .env.example .env&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Windows PowerShell&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;copy .env.example .env
Open .env and set your values:
NEXT_PUBLIC_GENLAYER_RPC_URL=https://studio.genlayer.com/api NEXT_PUBLIC_CONTRACT_ADDRESS=0xYOUR_DEPLOYED_CONTRACT_ADDRESS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;IMPORTANT NOTES:&lt;/strong&gt;&lt;br&gt;
The RPC URL for the hosted Studio must include /api at the end.&lt;br&gt;
Get your contract address from Studio's sidebar - it's the short hex address shown next to "Deployed at", NOT the long transaction hash.&lt;br&gt;
Common mistake: Transaction hashes are 64+ hex characters. Contract addresses are 40 hex characters (after 0x). If your address looks too long, you've copied the wrong thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Understanding genlayer-js&lt;/strong&gt;&lt;br&gt;
The genlayer-js SDK follows a client-based pattern:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;import { createClient, createAccount } from 'genlayer-js'; import { TransactionStatus } from 'genlayer-js/types';&lt;br&gt;
// Create an account and client const account = createAccount(); const client = createClient({ chain: { id: 61999, name: "genlayer-studio", … }, account: account, });&lt;br&gt;
// READ from a contract (free, no transaction) const dispute = await client.readContract({ address: contractAddress, functionName: 'get_dispute', args: [], });&lt;br&gt;
// WRITE to a contract (sends a transaction) const hash = await client.writeContract({ address: contractAddress, functionName: 'submit_argument', args: ['My argument text here'], });&lt;br&gt;
// Wait for confirmation const receipt = await client.waitForTransactionReceipt({ hash: hash, status: TransactionStatus.ACCEPTED, });&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key patterns:&lt;/strong&gt;&lt;br&gt;
readContract() is free and instant - it queries current state without a transaction.&lt;br&gt;
writeContract() sends a transaction and returns a hash immediately.&lt;br&gt;
waitForTransactionReceipt() polls until the transaction reaches your desired status. Use ACCEPTED (not FINALIZED) for the hosted Studio - it's much faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: The Frontend Code (Updated &amp;amp; Working)&lt;/strong&gt;&lt;br&gt;
This is the real, tested code that actually works with the hosted GenLayer Studio. Replace the contents of frontend/app/page.tsx with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
"use client";
import { useState, useEffect, useCallback } from "react"; import { createClient, createAccount } from "genlayer-js"; import { TransactionStatus } from "genlayer-js/types";
const CONTRACT_ADDRESS = process.env.NEXT_PUBLIC_CONTRACT_ADDRESS!; const RPC_URL = process.env.NEXT_PUBLIC_GENLAYER_RPC_URL!;
interface DisputeState { topic: string; argument_a: string; argument_b: string; ruling: string; status: string; }
export default function DisputePage() { // Create the GenLayer client with a custom chain config // pointing to the hosted Studio RPC const [client] = useState(() =&amp;gt; { const account = createAccount(); return createClient({ chain: { id: 61999, name: "genlayer-studio", nativeCurrency: { name: "GEN", symbol: "GEN", decimals: 18 }, rpcUrls: { default: { http: [RPC_URL] }, }, }, account: account, }); });
const [dispute, setDispute] = useState&amp;lt;DisputeState | null&amp;gt;(null);
const [argument, setArgument] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
// Fetch dispute state via direct JSON-RPC call
// We use a direct fetch because the hosted Studio's gen_call
// method returns hex-encoded data that needs manual decoding
const fetchDispute = useCallback(async () =&amp;gt; {
  try {
    const response = await fetch(RPC_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        jsonrpc: "2.0",
        method: "gen_call",
        params: [{
          type: "read",
          to: CONTRACT_ADDRESS,
          from: "0x0000000000000000000000000000000000000000",
          data: "0xd6940e066d6574686f645c6765745f6469737075746500",
          transaction_hash_variant: "latest-nonfinal",
        }],
        id: 1,
      }),
    });
    const json = await response.json();
    if (json.result) {
      // Decode hex-encoded response to text
      const hex = json.result.startsWith("0x")
        ? json.result.slice(2) : json.result;
      const bytes = new Uint8Array(
        hex.match(/.{1,2}/g)!.map((b: string) =&amp;gt; parseInt(b, 16))
      );
      const text = new TextDecoder().decode(bytes);
      // Extract fields using regex
      // (the response uses a binary serialization format, not JSON)
      const topicMatch = text.match(/topic[^\w]*([\s\S]*?)(?=argument_a)/);
      const argAMatch = text.match(/argument_a[^\w]*([\s\S]*?)(?=argument_b)/);
      const argBMatch = text.match(/argument_b[^\w]*([\s\S]*?)(?=ruling)/);
      const rulingMatch = text.match(/ruling[^\w]*([\s\S]*?)(?=status)/);
      const statusMatch = text.match(/status[^\w]*([\s\S]*?)$/);
      setDispute({
        topic: topicMatch
          ? topicMatch[1].replace(/[\x00-\x1f]/g, "").trim() : "",
        argument_a: argAMatch
          ? argAMatch[1].replace(/[\x00-\x1f]/g, "").trim() : "",
        argument_b: argBMatch
          ? argBMatch[1].replace(/[\x00-\x1f]/g, "").trim() : "",
        ruling: rulingMatch
          ? rulingMatch[1].replace(/[\x00-\x1f]/g, "").trim() : "",
        status: statusMatch
          ? statusMatch[1].replace(/[\x00-\x1f]/g, "").trim() : "open",
      });
      setError("");
    }
  } catch (err: any) {
    setError("Failed to load dispute: " + err.message);
  }
}, []);
useEffect(() =&amp;gt; { fetchDispute(); }, [fetchDispute]);
// Submit an argument (uses genlayer-js writeContract)
async function handleSubmitArgument() {
  if (!argument.trim()) return;
  setLoading(true);
  setError("");
  try {
    const txHash = await client.writeContract({
      address: CONTRACT_ADDRESS,
      functionName: "submit_argument",
      args: [argument],
      value: 0,
    });
    await client.waitForTransactionReceipt({
      hash: txHash,
      status: TransactionStatus.ACCEPTED,
    });
    setArgument("");
    await fetchDispute();
  } catch (err: any) {
    setError("Failed to submit: " + err.message);
  }
  setLoading(false);
}
// Trigger AI-powered resolution
async function handleResolve() {
  setLoading(true);
  setError("");
  try {
    const txHash = await client.writeContract({
      address: CONTRACT_ADDRESS,
      functionName: "resolve_dispute",
      args: [],
      value: 0,
    });
    await client.waitForTransactionReceipt({
      hash: txHash,
      status: TransactionStatus.ACCEPTED,
    });
    await fetchDispute();
  } catch (err: any) {
    setError("Resolution failed: " + err.message);
  }
  setLoading(false);
}
// Loading state
if (!dispute) {
  return (
    &amp;lt;div style={{ padding: "40px", fontFamily: "sans-serif" }}&amp;gt;
      &amp;lt;p&amp;gt;Loading dispute...&amp;lt;/p&amp;gt;
      {error &amp;amp;&amp;amp; &amp;lt;p style={{ color: "red" }}&amp;gt;{error}&amp;lt;/p&amp;gt;}
    &amp;lt;/div&amp;gt;
  );
}
// Main UI
return (
  &amp;lt;div style={{
    maxWidth: 700, margin: "0 auto",
    padding: "40px 20px", fontFamily: "sans-serif"
  }}&amp;gt;
    &amp;lt;h1&amp;gt;⚖️ Dispute Resolution&amp;lt;/h1&amp;gt;
    &amp;lt;h2&amp;gt;{dispute.topic}&amp;lt;/h2&amp;gt;
    &amp;lt;p&amp;gt;
      Status:{" "}
      &amp;lt;span style={{
        padding: "4px 12px", borderRadius: "12px",
        fontSize: "14px", fontWeight: "bold", color: "#fff",
        background: dispute.status === "open" ? "#3b82f6"
          : dispute.status === "pending" ? "#f59e0b" : "#22c55e",
      }}&amp;gt;
        {dispute.status.toUpperCase()}
      &amp;lt;/span&amp;gt;
    &amp;lt;/p&amp;gt;
    {dispute.argument_a &amp;amp;&amp;amp; (
      &amp;lt;div style={{
        background: "#f0f4ff", padding: "16px",
        borderRadius: "8px", margin: "16px 0"
      }}&amp;gt;
        &amp;lt;strong&amp;gt;Party A says:&amp;lt;/strong&amp;gt;
        &amp;lt;p&amp;gt;{dispute.argument_a}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    )}
    {dispute.argument_b &amp;amp;&amp;amp; (
      &amp;lt;div style={{
        background: "#fff7ed", padding: "16px",
        borderRadius: "8px", margin: "16px 0"
      }}&amp;gt;
        &amp;lt;strong&amp;gt;Party B says:&amp;lt;/strong&amp;gt;
        &amp;lt;p&amp;gt;{dispute.argument_b}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    )}
    {dispute.status === "open" &amp;amp;&amp;amp; (
      &amp;lt;div style={{ margin: "24px 0" }}&amp;gt;
        &amp;lt;h3&amp;gt;Submit Your Argument&amp;lt;/h3&amp;gt;
        &amp;lt;textarea value={argument}
          onChange={(e) =&amp;gt; setArgument(e.target.value)}
          placeholder="Type your side of the dispute here..."
          rows={5}
          style={{
            width: "100%", padding: "12px", fontSize: "16px",
            borderRadius: "8px", border: "1px solid #ccc",
          }}
        /&amp;gt;
        &amp;lt;button onClick={handleSubmitArgument}
          disabled={loading || !argument.trim()}
          style={{
            marginTop: "12px", padding: "12px 24px",
            fontSize: "16px",
            background: loading ? "#ccc" : "#3b82f6",
            color: "#fff", border: "none", borderRadius: "8px",
            cursor: loading ? "not-allowed" : "pointer",
          }}&amp;gt;
          {loading ? "Submitting..." : "Submit Argument"}
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )}
    {dispute.status === "pending" &amp;amp;&amp;amp; (
      &amp;lt;div style={{ margin: "24px 0" }}&amp;gt;
        &amp;lt;p&amp;gt;Both sides have submitted. Ready for AI resolution!&amp;lt;/p&amp;gt;
        &amp;lt;button onClick={handleResolve} disabled={loading}
          style={{
            padding: "16px 32px", fontSize: "18px",
            background: loading ? "#ccc" : "#7c3aed",
            color: "#fff", border: "none", borderRadius: "8px",
            cursor: loading ? "not-allowed" : "pointer",
          }}&amp;gt;
          {loading ? "⏳ AI is deliberating..." : "⚖️ Resolve Dispute"}
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )}
    {dispute.status === "resolved" &amp;amp;&amp;amp; (
      &amp;lt;div style={{
        background: "#f0fdf4", border: "2px solid #22c55e",
        padding: "24px", borderRadius: "12px", margin: "24px 0",
      }}&amp;gt;
        &amp;lt;h2&amp;gt;📜 Ruling&amp;lt;/h2&amp;gt;
        &amp;lt;p style={{ whiteSpace: "pre-wrap" }}&amp;gt;{dispute.ruling}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    )}
    {error &amp;amp;&amp;amp; (
      &amp;lt;p style={{ color: "red", marginTop: "16px" }}&amp;gt;{error}&amp;lt;/p&amp;gt;
    )}
  &amp;lt;/div&amp;gt;
);
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why Direct RPC Instead of readContract()?&lt;br&gt;
You might notice that fetchDispute() uses a raw fetch() call to the RPC endpoint instead of client.readContract(). Here's why:&lt;br&gt;
When connecting to the hosted GenLayer Studio, the SDK's readContract() returned an empty object {}. After debugging with browser DevTools, I discovered that the hosted Studio's gen_call method returns hex-encoded binary data that the SDK wasn't automatically decoding.&lt;br&gt;
By capturing the exact RPC payload that Studio itself uses (via the Network tab in DevTools), I was able to replicate the call format and manually decode the hex response. The data field "0xd6940e06…" is the encoded call to the get_dispute function.&lt;br&gt;
This is a great example of real-world debugging - sometimes the SDK doesn't handle every edge case, and you need to go lower-level. The writeContract() function works fine through the SDK for sending transactions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Run the Frontend&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;npm run dev&lt;/code&gt;&lt;br&gt;
Open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; in your browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9uegsk3r68rlmc0df3n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9uegsk3r68rlmc0df3n.png" alt=" " width="669" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Troubleshooting Real Issues I Encountered&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Unable to acquire lock" when running npm run dev: &lt;/p&gt;

&lt;p&gt;Another dev server instance is running. &lt;/p&gt;

&lt;p&gt;Fix it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Windows PowerShell&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;taskkill /F /IM node.exe Remove-Item .next\dev\lock -Force npm run dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linux/Mac&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;killall node rm -f .next/dev/lock npm run dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;"Incorrect address format" error: You pasted a transaction hash instead of a contract address. Go to Studio, find the short address shown next to your deployed contract in the sidebar, and copy that instead.&lt;br&gt;
readContract() returning empty {}: This is the main issue with the hosted Studio. The frontend code above uses the direct RPC workaround. If you're running a local Studio, the standard readContract() approach should work fine.&lt;br&gt;
"Transaction status is not FINALIZED" timeout: The hosted Studio can be slow to finalize. Use TransactionStatus.ACCEPTED instead of TransactionStatus.FINALIZED for faster confirmation.&lt;br&gt;
Duplicate page files causing errors: If you accidentally have both page.ts and page.tsx in the app/ folder, Next.js will throw "Duplicate page detected". Delete the .ts file and keep only page.tsx.&lt;br&gt;
.env changes not taking effect: After editing .env, you must restart the dev server (Ctrl+C then npm run dev). Also do a hard refresh in the browser with Ctrl+Shift+R.&lt;br&gt;
¬ PART 6&lt;br&gt;
Congratulations - you've built a fully functional Dispute Resolution dApp on GenLayer! Let's recap what you've learned:&lt;br&gt;
Optimistic Democracy - how validators with diverse AI models reach consensus on subjective outcomes&lt;br&gt;
The Equivalence Principle - the three strategies for defining what "agreement" means for your contract&lt;br&gt;
Intelligent Contracts - writing Python contracts that call LLMs and make AI-powered decisions&lt;br&gt;
GenLayer Studio - deploying, testing, and debugging contracts interactively&lt;br&gt;
genlayer-js - building a frontend that reads from and writes to your contract&lt;br&gt;
Real-world debugging - working through RPC format issues, chain configuration, and SDK edge cases&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ideas to Extend This Project&lt;/strong&gt;&lt;br&gt;
Add web evidence: Use gl.get_webpage() inside the resolution function to let the contract fetch real-world evidence (news articles, records) to inform its ruling.&lt;br&gt;
Multi-round disputes: Allow parties to submit rebuttals before final resolution.&lt;br&gt;
Token staking: Require both parties to stake GEN tokens, with the winner receiving the loser's stake.&lt;br&gt;
Non-comparative evaluation: Try switching to eq_principle_prompt_non_comparative for faster resolution - the validators evaluate the leader's ruling instead of running their own analysis.&lt;br&gt;
Multiple disputes: Use TreeMap to store multiple disputes in a single contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources&lt;/strong&gt;&lt;br&gt;
GenLayer: the intelligence layer of the Internet - Documentation&lt;br&gt;
GenLayer the intelligence layer of the Internet - Documentation.docs.genlayer.com&lt;br&gt;
GitHub - genlayerlabs/genlayer-project-boilerplate&lt;br&gt;
Contribute to genlayerlabs/genlayer-project-boilerplate development by creating an account on GitHub.github.com&lt;br&gt;
&lt;a href="https://github.com/genlayerlabs/genlayer-js%C2%A0" rel="noopener noreferrer"&gt;https://github.com/genlayerlabs/genlayer-js &lt;/a&gt;&lt;br&gt;
GenLayer - The Intelligence Layer of the Internet&lt;br&gt;
GenLayer is a new L2 Blockchain that shatters the bounds of Smart Contracts by making them AI-Powered and connected to…studio.genlayer.com&lt;br&gt;
&lt;a href="https://sdk.genlayer.com" rel="noopener noreferrer"&gt;https://sdk.genlayer.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>blockchain</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
