Interacting with Intelligent Contracts
Getting Contract References
Access other contracts by their address:
contract_address = Address("0x03FB09251eC05ee9Ca36c98644070B89111D4b3F")
dynamically_typed_contract = gl.get_contract_at(contract_address)
@gl.contract_interface
class GenLayerContractIface:
class View:
def method_name(self, a: int, b: str): ...
class Write:
pass
statically_typed_contract = GenLayerContractIface(contract_address)Both approaches result in the same runtime value, however the statically typed approach provides type checking and autocompletion in IDEs.
Calling View Methods
Call read-only methods on other contracts:
addr: Address = ...
other = gl.get_contract_at(addr)
result = other.view().get_token_balance()Emitting Messages
Send asynchronous messages to other contracts:
other = gl.get_contract_at(addr)
other.emit(on='accepted').update_status("active")
other.emit(on='finalized').update_status("active")Deploying New Contracts
gl.deploy_contract(code=contract_code)
salt: u256 = u256(1) # not zero
child_address = gl.deploy_contract(code=contract_code, salt=salt)View vs Emit
view()is synchronous — it reads state from another contract and returns the result immediately. The target contract's state is read as of the current block.emit()is asynchronous — it queues a write call that executes after the current transaction completes. The call is not blocking.
Accepted vs Finalized
# Fast — executes after the transaction is accepted by initial consensus
other.emit(on='accepted').do_something()
# Safe — waits until the transaction is fully finalized (after appeal window)
other.emit(on='finalized').do_something()on='accepted' has important implications. If the emitting transaction is appealed, two things can happen:
- The appeal changes the contract state such that the message should not have been emitted — but it was already sent and cannot be recalled.
- The transaction is re-executed during the appeal, and the message (or a similar one) is emitted again. This can repeat across multiple appeal rounds — up to ~6 times depending on the validator set size.
The receiving contract must be idempotent — it must handle duplicate or unexpected messages gracefully. If the receiving logic cannot tolerate duplicates or messages that "shouldn't have been sent" based on the final state, use on='finalized' instead.
Factory Pattern
Deploy child contracts from a parent:
def __init__(self, num_workers: int):
with open("/contract/Worker.py", "rt") as f:
worker_code = f.read()
for i in range(num_workers):
addr = gl.deploy_contract(
code=worker_code.encode("utf-8"),
args=[i, gl.message.contract_address],
salt_nonce=i + 1,
on="accepted",
)
self.worker_addresses.append(addr)Child contracts are immutable after deployment. To update worker logic, redeploy through the factory.