genlayer-utils provides reusable patterns to GenLayer Intelligent Contracts. Stop rewriting boilerplate — drop in battle-tested helpers for web access, AI prompts, access control, and more.
Every GenLayer contract I've built starts the same way.
Define an inner function. Call gl.nondet.web.render(). Build a prompt. Call gl.nondet.exec_prompt(). Serialize with json.dumps(sort_keys=True). Wrap everything in gl.eq_principle.strict_eq(). Deserialize the result.
That's 15–20 lines of code. And it's identical in structure across every single contract.
After building TruthPost, a decentralized AI fact-checker, I realized I'd written the same patterns three times in a single project. So I extracted them into a library.
genlayer-utils is the first utility library for GenLayer Intelligent Contracts. It's open source, MIT licensed, and ready to use today.
The Problem
GenLayer is an AI-native blockchain. Contracts can browse the internet and call LLMs natively, no oracles needed. It's genuinely powerful.
But there's a cost: every contract that uses these features repeats the same boilerplate. There are zero reusable libraries in the ecosystem. Every developer starts from scratch.
Here's what a typical web + AI call looks like in a GenLayer contract:
def _fact_check(self, claim_text, source_url):
def check_claim():
web_data = gl.nondet.web.render(source_url, mode="text")
prompt = f"""You are a fact-checker. Based on the evidence,
determine whether this claim is true or false.
CLAIM: {claim_text}
EVIDENCE: {web_data}
Respond ONLY with JSON: {{"verdict": "<true|false>",
"explanation": "..."}}"""
result = gl.nondet.exec_prompt(prompt, response_format="json")
return json.dumps(result, sort_keys=True)
raw = gl.eq_principle.strict_eq(check_claim)
return json.loads(raw)
Now imagine writing this for every method that touches the web or AI. It adds up fast.
The Solution: genlayer-utils
Five modules. Copy what you need. That's it.
1. nondet — Non-Deterministic Block Helpers
The biggest time-saver. Turns the entire web → LLM → consensus pattern into a single function call.
Before (20 lines):
def _check(self, url):
def _inner():
data = gl.nondet.web.render(url, mode="text")
result = gl.nondet.exec_prompt(
f"Analyze: {data}", response_format="json"
)
return json.dumps(result, sort_keys=True)
return json.loads(gl.eq_principle.strict_eq(_inner))
After (1 line):
result = web_llm_strict(url=url, prompt_template="Analyze: {web_data}")
Three functions cover most use cases:
| Function | When to use |
|---|---|
web_llm_strict() |
Web + AI with exact consensus (facts, categories, JSON) |
llm_strict() |
AI-only, no web fetch (classification, yes/no) |
web_llm_comparative() |
Web + AI where outputs can vary (summaries, descriptions) |
2. llm — Prompt Templates
Writing prompts that reliably pass strict_eq consensus is tricky. Validators need to independently produce the same output. These templates are pre-optimized for that:
# Classify text into categories
prompt = classify_prompt(
text="This product is terrible",
categories=["positive", "negative", "neutral"]
)
result = llm_strict(prompt)
# {"category": "negative", "confidence": "high", "reason": "..."}
# Fact-check a claim against web evidence
prompt = fact_check_prompt(
claim="Python was created in 1991",
evidence="{web_data}"
)
result = web_llm_strict(url="https://en.wikipedia.org/wiki/Python", prompt_template=prompt)
# {"verdict": "true", "explanation": "..."}
# Extract structured data from text
prompt = extract_prompt(
text="John Smith, 32, works at Google",
fields={"name": "full name", "age": "numeric age", "company": "employer"}
)
# {"name": "John Smith", "age": "32", "company": "Google"}
Four templates: classify_prompt(), fact_check_prompt(), extract_prompt(), yes_no_prompt(). Plus validators to check the LLM's response format.
3. access_control — Owner & Role Guards
GenLayer has no built-in access control. Every contract that needs it checks gl.message.sender_address manually. genlayer-utils provides drop-in guards:
@gl.public.write
def admin_action(self):
require_sender(self._owner) # One line. Done.
# ... only owner reaches here
@gl.public.write.payable
def purchase(self):
require_value(100) # Must send at least 100 GEN
# ... process purchase
Also includes documented patterns for Ownable (single owner) and Role-Based Access (admin, moderator, editor, etc.) that you can copy into your contract.
4. web_oracle — Web Data Extraction
Domain-specific helpers with consensus built in:
# Fetch an asset price from any web source
result = fetch_price(
url="https://www.coingecko.com/en/coins/bitcoin",
asset_name="Bitcoin"
)
# {"price": "67500.42", "currency": "USD", "timestamp": "..."}
# Fetch a sports score
result = fetch_score(
url="https://www.espn.com/match/12345",
team1="Arsenal", team2="Chelsea"
)
# {"score": "2:1", "winner": 1, "status": "finished"}
# Fetch any JSON API
data = fetch_json_api("https://api.example.com/data")
5. storage — TreeMap & DynArray Helpers
Small but saves repetitive code:
# Before (3 lines)
if sender not in self.reputation:
self.reputation[sender] = 0
self.reputation[sender] += 10
# After (1 line)
increment_or_init(self.reputation, sender, 10)
Also: treemap_paginate() for paginated views, address_map_to_dict() for converting address-keyed maps, and treemap_count().
Example Contracts
The repo includes 4 complete, deployable contracts that demonstrate the patterns:
| Contract | What it does | Modules used |
|---|---|---|
| fact_checker.py | AI fact-checking dApp | nondet, llm, access_control, storage |
| price_feed.py | Decentralized price oracle | web_oracle, access_control |
| content_moderator.py | AI content classification | nondet, llm, storage |
| voting.py | On-chain voting with roles | access_control, storage |
Each one is a real contract you can deploy on GenLayer Studio right now.
How to Use
GenLayer contracts run as single Python files inside GenVM. There's no pip install. So you copy the functions you need into the top of your contract:
# { "Depends": "py-genlayer:test" }
import json
from genlayer import *
# ─── genlayer-utils: nondet ─────────────────
def web_llm_strict(url, prompt_template, *, mode="text", response_format="json"):
# ... (paste from the repo)
# ─── genlayer-utils: llm ────────────────────
def fact_check_prompt(claim, evidence, verdicts=None):
# ... (paste from the repo)
# ─── Your contract ──────────────────────────
class MyContract(gl.Contract):
...
This is how all contract libraries start, even OpenZeppelin began as copy-paste patterns. Zero dependencies, full transparency, and you only include what you use.
What's Next
This is v0.1.0. Here's what I'm planning:
-
CLI inject tool - A script that reads
# genlayer-utils: nondet, llmdirectives in your contract and auto-injects the relevant functions before deployment - More prompt templates - Sentiment analysis, summarization, comparison
- More oracle patterns - Weather, news, social media data extraction
- Community contributions - If you have patterns you reuse, submit a PR
Get Started
git clone https://github.com/luch91/genlayer-utils.git
Browse the source, copy what you need, and start building.
- Repository: github.com/luch91/genlayer-utils
- Docs: docs/ folder
- Examples: examples/ folder
- GenLayer Docs: docs.genlayer.com
Built as a contribution to the GenLayer Builder Program. If you're building on GenLayer, I'd love to hear what patterns you need, open an issue or drop a comment below.
Top comments (0)