# How to Define Public Write Methods in GenLayer Contracts

> Learn how to define public write methods in GenLayer contracts. Use the @gl.public.write decorator to register state-modifying functions as transaction entry points, simplifying contract development.

- Repository: [GenLayer Labs/genlayer-project-boilerplate](https://github.com/genlayerlabs/genlayer-project-boilerplate)
- Tags: how-to-guide
- Published: 2026-08-20

---

**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`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py), the `create_bet` method illustrates this registration pattern:

```python
@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`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/PatternTest.py) (line 26 and lines 71-76), a minimal increment operation looks like this:

```python
@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`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/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`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py) (lines 70-73) demonstrates this pattern:

```python
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`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py) (lines 74-75), validation logic prevents duplicate entries:

```python
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`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/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`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py) and [`contracts/PatternTest.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/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`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/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`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/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`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py) (lines 54-55). Directly writing non-deterministic data to state violates GenLayer's consensus requirements and will cause transaction failures.