# Genlayer Dev Claw Skill

> GenLayer Intelligent Contracts

- Skill: `acastellana/genlayer-dev-claw-skill` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add acastellana/genlayer-dev-claw-skill`
- Raw SKILL.md: https://api.skillmd.com/api/skills/acastellana/genlayer-dev-claw-skill/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: acastellana (https://skillmd.com/u/acastellana)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/acastellana/genlayer-dev-claw-skill

---


# GenLayer Intelligent Contracts

GenLayer enables **Intelligent Contracts** - Python smart contracts that can call LLMs, fetch web data, and handle non-deterministic operations while maintaining blockchain consensus.

## Quick Start

### Minimal Contract
```python
# v0.1.0
# { "Depends": "py-genlayer:latest" }
from genlayer import *

class MyContract(gl.Contract):
    value: str
    
    def __init__(self, initial: str):
        self.value = initial
    
    @gl.public.view
    def get_value(self) -> str:
        return self.value
    
    @gl.public.write
    def set_value(self, new_value: str) -> None:
        self.value = new_value
```

### Contract with LLM
```python
# v0.1.0
# { "Depends": "py-genlayer:latest" }
from genlayer import *
import json

class AIContract(gl.Contract):
    result: str
    
    def __init__(self):
        self.result = ""
    
    @gl.public.write
    def analyze(self, text: str) -> None:
        prompt = f"Classify sentiment as positive, negative, or neutral. Text: {text}"
        
        def leader():
            raw = gl.nondet.exec_prompt(prompt)
            return _parse_llm_json(raw)
        
        def validator(leader_result):
            raw = gl.nondet.exec_prompt(prompt)
            my_result = _parse_llm_json(raw)
            # Validators independently evaluate — compare classification
            return leader_result.get("sentiment") == my_result.get("sentiment")
        
        # Leader and validator both execute independently — the 90% default
        self.result = json.dumps(gl.vm.run_nondet(leader=leader, validator=validator))
    
    @gl.public.view
    def get_result(self) -> str:
        return self.result
```

### Contract with Web Access
```python
# v0.1.0
# { "Depends": "py-genlayer:latest" }
from genlayer import *

class WebContract(gl.Contract):
    content: str
    
    def __init__(self):
        self.content = ""
    
    @gl.public.write
    def fetch(self, url: str) -> None:
        url_copy = url  # Capture for closure
        
        def leader():
            return gl.nondet.web.render(url_copy, mode="text")
        
        def validator(leader_result):
            # Each validator independently fetches and compares
            my_content = gl.nondet.web.render(url_copy, mode="text")
            return leader_result == my_content
        
        # Leader and validators both execute independently — the 90% default
        self.content = gl.vm.run_nondet(leader=leader, validator=validator)
    
    @gl.public.view
    def get_content(self) -> str:
        return self.content
```

## Core Concepts

### Contract Structure
1. **Version header**: `# v0.1.0` (required)
2. **Dependencies**: `# { "Depends": "py-genlayer:latest" }`
3. **Import**: `from genlayer import *`
4. **Class**: Extend `gl.Contract` (only ONE per file)
5. **State**: Class-level typed attributes
6. **Constructor**: `__init__` (not public)
7. **Methods**: Decorated with `@gl.public.view` or `@gl.public.write`

### Method Decorators
| Decorator | Purpose | Can Modify State |
|-----------|---------|------------------|
| `@gl.public.view` | Read-only queries | No |
| `@gl.public.write` | State mutations | Yes |
| `@gl.public.write.payable` | Receive value + mutate | Yes |

### Storage Types
Replace standard Python types with GenVM storage-compatible types:

| Python Type | GenVM Type | Usage |
|-------------|------------|-------|
| `int` | `u32`, `u64`, `u256`, `i32`, `i64`, etc. | Sized integers |
| `int` (unbounded) | `bigint` | Arbitrary precision (avoid) |
| `float` | `float` | 8-byte double (works directly) |
| `datetime` | `datetime.datetime` | With timezone support |
| `list[T]` | `DynArray[T]` | Dynamic arrays |
| fixed array | `Array[T, Literal[N]]` | Fixed-size arrays |
| `dict[K,V]` | `TreeMap[K,V]` | Ordered maps |
| `str` | `str` | Strings (unchanged) |
| `bool` | `bool` | Booleans (unchanged) |

**⚠️ `int` is NOT supported!** Always use sized integers. In production, `int` may cause opaque `exit_code 1` crashes.

### Address Type
```python
# Creating addresses
addr = Address("0x03FB09251eC05ee9Ca36c98644070B89111D4b3F")

# Get sender
sender = gl.message.sender_address

# Conversions
hex_str = addr.as_hex      # "0x03FB..."
bytes_val = addr.as_bytes  # bytes
```

### Custom Data Types
```python
from dataclasses import dataclass

@allow_storage
@dataclass
class UserData:
    name: str
    balance: u256
    active: bool

class MyContract(gl.Contract):
    users: TreeMap[Address, UserData]
```

## Non-Deterministic Operations

### The Problem
LLMs and web fetches produce different results across validators. GenLayer solves this with the **Equivalence Principle**.

### Equivalence Principles

#### 1. Custom Leader/Validator (`gl.vm.run_nondet`) — THE DEFAULT (~90% of use cases)
Leader fetches+evaluates, validator independently fetches+evaluates, then compares.
Used by Rally, MergeProof, Molly.fun. This is how GenLayer is designed to work.
```python
def leader():
    data = gl.nondet.web.render(url, mode="text")
    return parse_price(data)

def validator(leader_result):
    # Independently fetch and evaluate
    my_data = gl.nondet.web.render(url, mode="text")
    my_price = parse_price(my_data)
    # Allow 5% tolerance for timing differences
    return abs(leader_result - my_price) / my_price < 0.05

result = gl.vm.run_nondet(leader=leader, validator=validator)
```

Best for: **Everything with web fetches or LLM calls** — which is almost every real contract.

#### 2. Strict Equality (`strict_eq`)
Shortcut: all validators must produce **identical** results. NOT for LLM text outputs.
```python
def check_flag():
    content = gl.nondet.web.render(url, mode="text")
    return "maintenance" in content.lower()  # boolean → deterministic

result = gl.eq_principle.strict_eq(check_flag)
```

Best for: Deterministic-only results — booleans, exact parsed values, checksums.
**Warning:** LLMs rarely produce identical text across validators. Use `run_nondet` instead.

#### 3. Prompt Comparative (`prompt_comparative`)
Shortcut: LLM compares leader's result against validators' results. Extra cost.
```python
def get_analysis():
    return gl.nondet.exec_prompt(prompt)

result = gl.eq_principle.prompt_comparative(
    get_analysis,
    "The sentiment classification must match"
)
```

Best for: Simple LLM classifications where you want LLM-based semantic comparison.

#### 4. Prompt Non-Comparative (`prompt_non_comparative`)
**ONLY for pure NLP tasks with NO web fetching.** Validators don't re-execute — they
only check whether the leader's output looks reasonable. Using this with `web.get` or
`web.render` means validators blindly trust the leader's fetch — this defeats the
entire purpose of multi-validator consensus.
```python
result = gl.eq_principle.prompt_non_comparative(
    lambda: input_data,  # What to process
    task="Summarize the key points",
    criteria="Summary must be under 100 words and factually accurate"
)
```

Best for: Pure text NLP with no external data. Example: summarize a string passed as argument.

### Non-Deterministic Functions

| Function | Purpose |
|----------|---------|
| `gl.nondet.exec_prompt(prompt, images=[], response_format='text')` | LLM prompt (text/json, optional images) |
| `gl.nondet.web.get(url)` | HTTP GET (returns Response with .status, .body) |
| `gl.nondet.web.post(url, body=)` | HTTP POST |
| `gl.nondet.web.render(url, mode=, wait_after_loaded=)` | Render webpage (`"text"`, `"html"`, `"screenshot"`) |

**⚠️ Rules:**
- Must be called inside equivalence principle functions
- Cannot access storage directly
- Copy storage data to memory first with `gl.storage.copy_to_memory()`

## Contract Interactions

### Call Other Contracts
```python
# Dynamic typing
other = gl.get_contract_at(Address("0x..."))
result = other.view().some_method()

# Static typing (better IDE support)
@gl.contract_interface
class TokenInterface:
    class View:
        def balance_of(self, owner: Address) -> u256: ...
    class Write:
        def transfer(self, to: Address, amount: u256) -> bool: ...

token = TokenInterface(Address("0x..."))
balance = token.view().balance_of(my_address)
```

### Emit Messages (Async Calls)
```python
other = gl.get_contract_at(addr)
other.emit(on='accepted').update_status("active")
other.emit(on='finalized').confirm_transaction()
```

### Deploy Contracts
```python
child_addr = gl.deploy_contract(code=contract_code, salt=u256(1))
```

### EVM Interop
```python
@gl.evm.contract_interface
class ERC20:
    class View:
        def balance_of(self, owner: Address) -> u256: ...
    class Write:
        def transfer(self, to: Address, amount: u256) -> bool: ...

token = ERC20(evm_address)
balance = token.view().balance_of(addr)
token.emit().transfer(recipient, u256(100))  # Messages only on finality
```

## CLI Commands

### Setup
```bash
npm install -g genlayer
genlayer init      # Download components
genlayer up        # Start local network
```

### Deployment
```bash
# Direct deploy
genlayer deploy --contract my_contract.py

# With constructor args
genlayer deploy --contract my_contract.py --args "Hello" 42

# To testnet
genlayer network set testnet-asimov
genlayer deploy --contract my_contract.py
```

### Interaction
```bash
# Read (view methods)
genlayer call --address 0x... --function get_value

# Write
genlayer write --address 0x... --function set_value --args "new_value"

# Get schema
genlayer schema --address 0x...

# Check transaction
genlayer receipt --tx-hash 0x...
```

### Networks
```bash
genlayer network                    # Show current
genlayer network list               # Available networks
genlayer network set localnet       # Local dev
genlayer network set studionet      # Hosted dev
genlayer network set testnet-asimov # Testnet
```

## Best Practices

### Prompt Engineering
```python
prompt = f"""
Analyze this text and classify the sentiment.

Text: {text}

Respond using ONLY this JSON format:
{{"sentiment": "positive" | "negative" | "neutral", "confidence": float}}

Output ONLY valid JSON, no other text.
"""
```

### Security: Prompt Injection
- **Restrict inputs**: Minimize user-controlled text in prompts
- **Restrict outputs**: Define exact output formats
- **Validate**: Check parsed results match expected schema
- **Simplify logic**: Clear contract flow reduces attack surface

### Error Handling

Use `gl.vm.UserError` (not `from genlayer import UserError`). Use error prefixes for
easier debugging and monitoring:

```python
@gl.public.write
def safe_operation(self, value: u256, url: str) -> None:
    # [EXPECTED] — business logic validation
    if value == u256(0):
        raise gl.vm.UserError("[EXPECTED] Value must be positive")
    
    def leader():
        resp = gl.nondet.web.get(url)
        # [EXTERNAL] — API/web fetch failures
        if resp.status != 200:
            raise gl.vm.UserError(f"[EXTERNAL] Fetch failed: {resp.status}")
        # [TRANSIENT] — 5xx, timeouts (caller should retry)
        if resp.status >= 500:
            raise gl.vm.UserError(f"[TRANSIENT] Server error: {resp.status}")
        return resp.body
    
    def validator(leader_result):
        try:
            my_resp = gl.nondet.web.get(url)
            return leader_result == my_resp.body
        except Exception:
            # [LLM_ERROR] — LLM failures
            raise gl.vm.UserError("[LLM_ERROR] Validation fetch failed")
    
    self.result = gl.vm.run_nondet(leader=leader, validator=validator)
```

### LLM Response Parsing (`_parse_llm_json`)

`exec_prompt` can return `dict` **or** `str` depending on the GenVM runtime and LLM backend.
Using bare `json.loads(str(raw))` fails when GenVM returns a dict (Python repr uses single quotes,
not valid JSON). **Always use this helper:**

```python
import json

def _parse_llm_json(raw):
    """Parse LLM response — handles both string and dict returns from exec_prompt."""
    if isinstance(raw, dict):
        return raw
    s = str(raw).strip()
    s = s.replace("```json", "").replace("```", "").strip()
    start = s.find("{")
    end = s.rfind("}") + 1
    if start >= 0 and end > start:
        s = s[start:end]
    s = s.replace("'", '"')
    return json.loads(s)
```

This was the root cause of 7 consecutive UNDETERMINED failures in ERC-8183 bounty contracts.

### Memory Management
```python
# Copy storage to memory for non-det blocks
data_copy = gl.storage.copy_to_memory(self.some_data)

def process():
    return gl.nondet.exec_prompt(f"Process: {data_copy}")

result = gl.eq_principle.strict_eq(process)
```

## Common Patterns

### Token with AI Transfer Validation
See `references/examples.md` → LLM ERC20

### Prediction Market
See `references/examples.md` → Football Prediction Market

### Vector Search / Embeddings
See `references/examples.md` → Log Indexer

## Tooling

### GenVM Linter (mandatory pre-deploy)

Run before every deploy — catches common mistakes that cause silent crashes on GenVM:

```bash
pip install genvm-linter  # or: pipx install genvm-linter
genvm-lint check contract.py    # lint + validate (recommended)
genvm-lint lint contract.py     # fast AST checks only (~50ms)
```

**Common catches:**
- `ValueError` → should be `gl.vm.UserError`
- Unreachable nondet blocks
- Forbidden imports: `random`, `os`, `time` (non-deterministic)
- Incorrect storage type usage

### Direct Mode Testing

Test locally without Studionet — catches logic bugs fast (~0.4s vs ~100s):

```bash
pip install genlayer-test
# Write pytest tests, run locally against mocked GenVM
# See: https://github.com/genlayerlabs/genlayer-testing-suite
```

Local tests catch Python/logic bugs. Studionet catches GenVM + consensus bugs.
Always test on Studionet before declaring production-ready.

## Debugging

1. **GenLayer Studio**: Use `genlayer up` for local testing
2. **Logs**: Filter by transaction hash, debug level
3. **`gl.trace()`**: Trace output visible in validator logs
4. **`gl.trace_time_micro()`**: Measure execution time

## Reference Files
- `references/sdk-api.md` - Complete SDK API reference
- `references/equivalence-principles.md` - Consensus patterns in depth
- `references/examples.md` - Full annotated contract examples (incl. production oracle)
- `references/deployment.md` - CLI, networks, deployment workflow
- `references/genvm-internals.md` - VM architecture, storage, ABI details
- `references/production-learnings.md` - Hard-won lessons: storage gotchas, multimodal prompting, evidence design for AI jury, local vs studionet gaps, bridge relay tips

## Links
- Docs: https://docs.genlayer.com
- SDK: https://sdk.genlayer.com
- Studio: https://studio.genlayer.com
- GitHub: https://github.com/genlayerlabs

