How to Define Public Write Methods in GenLayer Contracts

In GenLayer, a public write method is a state-modifying contract function that external callers invoke by decorating the Python function with @gl.public.write, which registers it as a transaction entry point in the VM.

The genlayer-project-boilerplate repository demonstrates how to implement these critical contract operations. This guide covers the decorator syntax, state access patterns, and determinism requirements for creating robust public write methods that safely alter on-chain storage.

Understanding the @gl.public.write Decorator

The GenLayer VM recognizes state-changing functions through the @gl.public.write decorator. When the contract compiles, this decorator registers the function as a transaction-type entry point, allowing external callers to pay gas and trigger persistent state modifications.

In contracts/football_bets.py, the create_bet method illustrates this registration pattern:

@gl.public.write
def create_bet(self, game_date: str, team1: str, team2: str, predicted_winner: str) -> None:
    # Build a URL that will later be used to resolve the match outcome

    match_resolution_url = (
        "https://www.bbc.com/sport/football/scores-fixtures/" + game_date
    )
    # ... implementation continues

Unlike view functions, these methods commit changes to storage fields such as TreeMap, u256, and other contract attributes defined in the class.

Method Signature and State Access

Public write methods accept any serializable arguments and must declare a return type (or None). The returned value, if any, is sent back to the caller after the transaction completes. Inside the method, you read and write storage fields using standard Python attribute syntax.

According to contracts/PatternTest.py (line 26 and lines 71-76), a minimal increment operation looks like this:

@gl.public.write
def increment(self) -> u256:
    """Increase the contract's `count` by one."""
    self.count = u256(int(self.count) + 1)
    return self.count

As shown in contracts/football_bets.py (lines 73-78), complex storage structures like TreeMap instances are accessible directly via self and can be modified using standard dictionary-style operations or method calls like get_or_insert_default.

Accessing Transaction Context

Inside public write methods, the caller's blockchain identity is available via gl.message.sender_address. Use this property to associate data with specific wallet addresses or implement user-scoped access control.

The create_bet method in contracts/football_bets.py (lines 70-73) demonstrates this pattern:

sender_address = gl.message.sender_address
bet_id = f"{game_date}_{team1}_{team2}".lower()

# Prevent duplicate bets from the same sender

if sender_address in self.bets and bet_id in self.bets[sender_address]:
    raise Exception("Bet already created")

This approach ensures each user's data remains isolated by their wallet address, preventing cross-user data contamination.

Error Handling and Validation

Raising exceptions within a public write method aborts the entire transaction and reverts all state changes to their pre-transaction values. The GenLayer VM treats standard Python exceptions (or gl.vm.UserError) as transaction failures, ensuring atomic operations.

In contracts/football_bets.py (lines 74-75), validation logic prevents duplicate entries:

if sender_address in self.bets and bet_id in self.bets[sender_address]:
    raise Exception("Bet already created")

Always perform validation checks before modifying storage to ensure the contract maintains consistent state.

Determinism Requirements for State Changes

All non-deterministic operations—such as web fetches or LLM calls—must be wrapped in an equivalence-principle pattern before their results affect contract state. This ensures deterministic consensus across the GenLayer network.

As implemented in contracts/football_bets.py (lines 54-55), use patterns like gl.eq_principle.strict_eq or gl.vm.run_nondet_unsafe to handle external data safely. Only deterministic values returned from these wrappers should be written to persistent storage fields.

Integration with Frontend SDK

Public write methods automatically expose themselves to the TypeScript frontend SDK without requiring manual registration. The build process generates corresponding TypeScript definitions in frontend/lib/contracts/*.ts, allowing seamless interaction between your Python contract logic and the web interface.

Summary

  • Decorator: Apply @gl.public.write to register functions as transaction entry points callable by external users
  • State Access: Modify TreeMap, u256, and other storage fields using standard Python attribute syntax on self
  • Identity: Access caller address via gl.message.sender_address for user-scoped data and access control
  • Validation: Raise standard Python exceptions to revert transactions when business logic conditions fail
  • Determinism: Wrap non-deterministic calls (web fetches, LLM calls) in equivalence principles like gl.eq_principle.strict_eq before writing to state
  • Frontend Integration: Methods automatically expose to TypeScript SDK at frontend/lib/contracts/*.ts

Frequently Asked Questions

What is the difference between @gl.public.write and regular Python methods in GenLayer?

Regular Python methods inside a GenLayer contract are internal helpers that cannot be invoked externally. Only methods decorated with @gl.public.write become transaction entry points that external callers can invoke to modify contract state, as demonstrated in contracts/football_bets.py and contracts/PatternTest.py.

How do I prevent unauthorized state changes in public write methods?

Use gl.message.sender_address to identify the caller and implement validation logic, as shown in contracts/football_bets.py (lines 70-73). Raise exceptions when conditions aren't met—this reverts the transaction and prevents any state modifications from persisting to storage.

Can public write methods return values to the caller?

Yes. Public write methods must declare a return type (or None) and can return serializable values to the caller after execution. For example, the increment method in contracts/PatternTest.py returns a u256 value representing the updated counter state, which the caller receives as the transaction result.

What happens if my public write method calls external APIs?

External API calls introduce non-determinism and must be wrapped in equivalence-principle patterns like gl.eq_principle.strict_eq before their results affect storage, as shown in contracts/football_bets.py (lines 54-55). Directly writing non-deterministic data to state violates GenLayer's consensus requirements and will cause transaction failures.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →