How to Create Payable Transactions in GenLayer Contracts
To accept native tokens in a GenLayer contract, decorate your method with @gl.public.write.payable and read the attached value through gl.message.value as a u256 type.
GenLayer contracts are written in Python and executed on the GenVM, using the GenLayer SDK to expose public endpoints. Unlike Ethereum's Solidity where payable is a keyword, GenLayer implements payable functionality through decorators that signal the virtual machine to expect attached value. This guide demonstrates how to implement payable transactions using the genlayer-project-boilerplate repository.
The Payable Decorator Pattern
In GenLayer, contract methods are classified by their visibility and mutability using decorators from the genlayer module. To enable a method to receive native tokens, you must use @gl.public.write.payable instead of the standard @gl.public.write.
According to the repository's CLAUDE.md file, the payable decorator explicitly tells the GenVM that the method may receive a value attached to the transaction. This pattern mirrors Solidity's payable keyword but leverages Python's decorator syntax for cleaner implementation.
When a payable method is invoked, the transaction's attached value becomes accessible through gl.message.value, which returns a u256 representing the token amount in the smallest denomination.
Step-by-Step Implementation
Step 1: Apply the Payable Decorator
Import the GenLayer SDK and apply the decorator to your contract method. The decorator must wrap any function intended to receive funds.
from genlayer import *
class PaymentContract(gl.Contract):
@gl.public.write.payable
def deposit(self) -> None:
"""Accepts native token deposits."""
pass
Step 2: Access Transaction Value
Inside the payable method, retrieve the sender's address and the attached amount using the message context object.
sender = gl.message.sender_address
amount = gl.message.value # Returns u256
The gl.message object provides transaction metadata, where value specifically contains the amount of native tokens sent by the caller.
Step 3: Validate and Process Payments
Always validate the incoming amount before executing business logic. This prevents underpayment attacks and ensures contract invariants.
@gl.public.write.payable
def buy_item(self, price: u256) -> None:
sender = gl.message.sender_address
sent = gl.message.value
if sent < price:
raise Exception("Insufficient payment")
# Execute purchase logic here
Step 4: Handle Refunds
If the sent amount exceeds the required price, return the excess using gl.vm.transfer. This built-in SDK function transfers native tokens from the contract balance to the specified address.
excess = sent - price
if excess > 0:
gl.vm.transfer(sender, excess)
Complete Working Example
The following contract demonstrates a deposit system and a purchase function with refund logic, showcasing all payable patterns found in the boilerplate:
from genlayer import *
class ExamplePayable(gl.Contract):
# Persistent storage for user balances
deposits: TreeMap[Address, u256]
@gl.public.write.payable
def deposit(self) -> None:
"""Records any amount of native tokens sent to the contract."""
sender = gl.message.sender_address
amount = gl.message.value
# Initialize balance if first deposit
if sender not in self.deposits:
self.deposits[sender] = 0
# Accumulate deposit
self.deposits[sender] += amount
@gl.public.write.payable
def buy_item(self, price: u256) -> None:
"""Requires exact or excess payment with automatic refunds."""
sender = gl.message.sender_address
sent = gl.message.value
# Validate minimum payment
if sent < price:
raise Exception("Insufficient payment")
# Process purchase (emit event or update state here)
# Calculate and return surplus
excess = sent - price
if excess > 0:
gl.vm.transfer(sender, excess)
@gl.public.view
def get_deposit(self, addr: str) -> u256:
"""Read-only query for a user's balance."""
return self.deposits.get(Address(addr), 0)
Validation Patterns and Security Considerations
When implementing payable transactions, follow these security best practices derived from the genlayer-project-boilerplate structure:
- Explicit amount checks: Never assume
gl.message.valuemeets your minimum requirements. Always compare against expected prices using explicit conditionals. - Overflow protection: The
u256type handles large integers, but validate subtraction operations when calculating refunds to prevent underflow. - State updates before transfers: Update contract state before issuing refunds via
gl.vm.transferto prevent reentrancy-style issues, though GenVM's architecture provides additional safety guarantees compared to EVM. - View functions for balances: Use
@gl.public.viewdecorators for read-only balance queries to avoid unnecessary gas costs and transaction requirements.
The contracts/football_bets.py file in the repository provides additional context for contract structure, though it focuses on non-payable patterns that you can adapt using the payable decorator syntax documented in CLAUDE.md.
Summary
- Use
@gl.public.write.payableto mark methods that accept native tokens, as documented inCLAUDE.mdlines 70-71. - Access value via
gl.message.value, which returns au256type representing the attached amount. - Validate minimum payments using explicit comparisons to prevent underpayment vulnerabilities.
- Issue refunds using
gl.vm.transfer(address, amount)to return excess funds to callers. - Store balances in
TreeMap[Address, u256]structures for efficient persistent storage of user deposits.
Frequently Asked Questions
What is the difference between @gl.public.write and @gl.public.write.payable?
The @gl.public.write decorator creates a state-modifying method that cannot receive native tokens, while @gl.public.write.payable explicitly signals the GenVM to allow and process attached value. Without the payable decorator, any transaction sending funds to the method will be rejected by the protocol.
How do I refund excess payments in a GenLayer contract?
Use the gl.vm.transfer(recipient_address, amount) SDK function to send native tokens from the contract balance back to a user. Calculate the excess by subtracting the required price from gl.message.value, then conditionally execute the transfer only if the excess amount exceeds zero.
What type is gl.message.value and how is it handled?
gl.message.value returns a u256 type, an unsigned 256-bit integer representing the smallest denomination of the native token. This type supports standard arithmetic operations but requires careful validation when comparing amounts or calculating refunds to ensure precise token accounting.
Where can I find examples of payable contracts in the GenLayer boilerplate?
While the contracts/football_bets.py file in the genlayer-project-boilerplate repository primarily demonstrates non-payable patterns, the CLAUDE.md file contains the definitive payable decorator syntax and usage patterns. Adapt the structure from football_bets.py by adding the .payable suffix to your decorators and implementing gl.message.value access as shown in this guide.
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 →