GenLayer Intelligent Contract Structure: A Complete Technical Guide
A GenLayer intelligent contract is a Python class that inherits from gl.Contract, declares persistent state using deterministic collection types like TreeMap or DynArray, and exposes functionality through explicitly decorated public methods while isolating non-deterministic operations behind equivalence-principle wrappers.
A GenLayer intelligent contract enables developers to combine deterministic blockchain state management with non-deterministic AI capabilities like LLM inference and web scraping. The genlayerlabs/genlayer-project-boilerplate repository provides the canonical reference implementation in contracts/football_bets.py, demonstrating how Python classes transform into consensus-safe, AI-native smart contracts. Understanding the precise anatomy of these contracts—from storage declarations to method decorators—is essential for writing secure code that executes correctly across distributed validators.
Core Structural Components
Contract Inheritance and Base Class
Every intelligent contract must subclass gl.Contract to gain access to the GenLayer SDK runtime. This inheritance provides the foundational machinery for state serialization, consensus management, and validator coordination. Without this base class, the contract cannot be deployed or executed on the GenLayer network.
In contracts/football_bets.py at line 22:
class FootballBets(gl.Contract):
Persistent Storage Declarations
State variables must use GenLayer-specific collection types rather than native Python containers to ensure deterministic serialization across all validators. These specialized types handle merkelization and consensus verification automatically.
Common storage types include:
TreeMap[K, V]– Sorted key-value mapping for persistent storageDynArray[T]– Dynamic array with deterministic orderingu256– 256-bit unsigned integer for numeric valuesAddress– Validated blockchain address type
In contracts/football_bets.py at line 23:
bets: TreeMap[Address, TreeMap[str, Bet]]
points: TreeMap[Address, u256]
Constructor Restrictions
The __init__ method is optional but must remain purely deterministic. It cannot perform non-deterministic actions like web calls or LLM prompts; those operations belong in dedicated helper methods invoked after deployment. Use the constructor only for deterministic initialization or configuration constants.
From contracts/football_bets.py at line 26:
def __init__(self):
pass
Public Method Decorators
All callable contract methods require explicit visibility decorators to declare their intent and execution permissions:
@gl.public.view– Read-only operations that do not modify state; execute without gas fees for queries@gl.public.write– State-modifying operations that require consensus and consume gas@gl.public.write.payable– State-modifying operations that accept value transfers
In contracts/football_bets.py at line 58:
@gl.public.write
def create_bet(self, game_date: str, team1: str, team2: str, predicted_winner: str) -> None:
sender_address = gl.message.sender_address
# ... state modification logic
Non-Deterministic Operations and Equivalence Principle
External interactions—such as web scraping or LLM inference—violate traditional blockchain determinism. GenLayer solves this through the Equivalence Principle, which wraps non-deterministic calls in consensus mechanisms. These operations must be isolated in private helper methods using gl.nondet functions and wrapped with equivalence-principle validators.
Key patterns include:
gl.nondet.web.render– Fetches and renders web contentgl.nondet.exec_prompt– Executes LLM promptsgl.eq_principle.strict_eq– Requires exact consensus across validatorsgl.eq_principle.prompt_comparative– Uses LLM to compare outputs for equivalence
In contracts/football_bets.py lines 29-55, the private method _check_match demonstrates this architecture:
def _check_match(self, url: str, team1: str, team2: str) -> dict:
web_result = gl.nondet.web.render(url)
# ... processing logic using gl.eq_principle.strict_eq
Data Classes for Complex Storage
Custom data structures require both @allow_storage and @dataclass decorators to be storable within GenLayer collection types. The @allow_storage marker enables the runtime to serialize and deserialize the class for persistent storage in TreeMap or DynArray containers.
In contracts/football_bets.py lines 8-20:
@allow_storage
@dataclass
class Bet:
id: str
has_resolved: bool
game_date: str
resolution_url: str
team1: str
team2: str
predicted_winner: str
real_winner: str
real_score: str
Message Context and Access Control
Access the transaction sender's address via gl.message.sender_address to implement per-user state management and access control patterns. This provides the calling address for authentication and authorization logic.
In contracts/football_bets.py at line 70:
sender_address = gl.message.sender_address
self.bets[sender_address] = ...
Minimal Contract Example
The following "Hello World" contract demonstrates the essential structure: inheritance, storage declaration, write/view decorators, and message context.
from genlayer import *
class HelloWorld(gl.Contract):
# Persistent storage: a simple counter per address
counters: TreeMap[Address, u256]
@gl.public.write
def increment(self) -> None:
addr = gl.message.sender_address
self.counters[addr] = self.counters.get(addr, 0) + 1
@gl.public.view
def get_counter(self, addr: str) -> u256:
return self.counters.get(Address(addr), 0)
Production Implementation Reference
The contracts/football_bets.py file serves as the canonical production example, integrating all structural elements:
- Complex nested storage:
TreeMap[Address, TreeMap[str, Bet]]for user-bet relationships - Data class persistence: The
Betdataclass marked with@allow_storage - Non-deterministic resolution: Web scraping and LLM inference wrapped in
gl.eq_principle.strict_eq - Access control: Using
gl.message.sender_addressto isolate user data
This implementation demonstrates how to safely combine external data fetching with deterministic state transitions while maintaining consensus safety across validators.
Summary
- Inherit from
gl.Contractto activate the GenLayer runtime environment - Use GenLayer collection types (
TreeMap,DynArray,u256) for all persistent storage to ensure deterministic serialization - Apply visibility decorators (
@gl.public.view,@gl.public.write) to control method access and state modification permissions - Isolate non-deterministic logic in private methods and wrap with equivalence-principle functions like
gl.eq_principle.strict_eq - Mark dataclasses with
@allow_storageto enable storage of complex objects in collections - Access caller identity through
gl.message.sender_addressfor user-specific state management
Frequently Asked Questions
What makes a Python class a GenLayer intelligent contract?
A Python class becomes a GenLayer intelligent contract by subclassing gl.Contract and following the storage and method conventions defined in the GenLayer SDK. The inheritance provides consensus machinery, while the use of GenLayer-specific types for storage and decorators for methods ensures the contract can execute deterministically across distributed validators. According to the genlayerlabs/genlayer-project-boilerplate source code, without inheriting from gl.Contract, the runtime cannot process the class as a deployable contract.
Why can't I use standard Python lists and dictionaries for storage?
Standard Python containers like list and dict lack deterministic ordering guarantees and serialization formats required for blockchain consensus. GenLayer contracts use specialized types like TreeMap and DynArray because these implement deterministic ordering and merkelization, ensuring all validators reach identical state conclusions after execution. As implemented in contracts/football_bets.py, these types replace native containers to maintain consensus safety.
How do I handle external API calls in my contract?
External API calls must be isolated in private helper methods using gl.nondet.web.render or similar non-deterministic functions, then wrapped with equivalence-principle validators like gl.eq_principle.strict_eq. This pattern ensures that while validators may receive slightly different responses from external services, the network reaches consensus on the canonical result before state modification occurs. The football_bets.py contract demonstrates this in its _check_match method, which fetches web data and processes it through consensus mechanisms.
What is the difference between @gl.public.view and @gl.public.write?
@gl.public.view marks read-only methods that do not modify contract state and execute without gas fees, suitable for querying data. @gl.public.write marks state-modifying operations that require validator consensus, consume gas, and permanently alter the blockchain state. According to the reference implementation, view methods return serializable values immediately, while write methods trigger consensus rounds and equivalence-principle validation for any non-deterministic sub-operations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →