How to Make HTTP GET Requests from a GenLayer Contract: Complete Implementation Guide
GenLayer contracts execute HTTP GET requests using the gl.nondet.web.get API within a leader-validator consensus pattern, where the leader fetches external data and validators independently verify the response to ensure deterministic consensus.
The genlayer-project-boilerplate repository provides the foundational patterns for integrating external HTTP APIs into intelligent contracts. Making HTTP GET requests from a GenLayer contract requires understanding non-deterministic execution contexts, where network responses must be validated across the validator network to maintain blockchain integrity.
Understanding Non-Deterministic Web Calls
External HTTP requests are inherently non-deterministic because different nodes may receive varying responses, timestamps, or server errors. The GenLayer SDK wraps these calls in a consensus mechanism that requires explicit validation logic. According to the source code in CLAUDE.md, the gl.nondet.web.get function returns a Response object containing status, headers, and body bytes, but this data cannot be stored directly without validator agreement.
The Leader-Validator Architecture
Every HTTP GET operation requires defining two distinct functions:
- Leader function: Executes the initial
gl.nondet.web.getcall, processes the response (typically decoding bytes to strings), and proposes the result to the network. - Validator function: Re-issues the identical HTTP request and confirms the response matches the leader's proposed result, returning
Trueonly if the data matches exactly.
This pattern ensures that only consensus-validated external data enters the contract state, preventing malicious or corrupted API responses from affecting the network.
Implementing HTTP GET Requests with gl.nondet.web.get
The primary API signature for fetching data is defined in CLAUDE.md as:
gl.nondet.web.get(url: str, *, headers: dict = {}) -> Response
The Response object contains status (int), headers (dict), and body (bytes) attributes. When implementing contracts that use this API, you must decode the body bytes before storage or comparison.
Basic Fetch-and-Store Contract
The following implementation from tests/integration/test_new_features.py demonstrates the complete pattern for making HTTP GET requests in a production contract:
from genlayer import *
import genlayer.gl.vm as glvm
class FetchContract(gl.Contract):
last_result: str
def __init__(self):
self.last_result = ""
@gl.public.write
def fetch_and_store(self, url: str) -> str:
# ---------- Leader ----------
def leader() -> str:
resp = gl.nondet.web.get(url)
return resp.body.decode("utf-8", errors="replace")
# ---------- Validator ----------
def validator(result: glvm.Result) -> bool:
if not isinstance(result, glvm.Return):
return False
resp = gl.nondet.web.get(url)
return resp.body.decode("utf-8", errors="replace") == result.calldata
# Execute consensus
value = glvm.run_nondet_unsafe.lazy(leader, validator).get()
self.last_result = value
return value
Key implementation details:
- Use
run_nondet_unsafewhen the leader function does not callspawn_sandbox; otherwise userun_nondet. - Always decode bytes using
resp.body.decode()with error handling to convert HTTP payloads to contract-compatible strings. - Store state only after the validator consensus completes successfully via the
.get()call.
Testing HTTP GET Requests in Local Development
The repository includes direct-mode testing utilities that mock HTTP responses, eliminating external network dependencies during development. The test file tests/integration/test_new_features.py demonstrates this workflow:
def test_fetch_and_store(direct_vm, direct_deploy, nondet_contract_path):
# Configure the mock response
direct_vm.mock_web(r".*api.example.com.*", {
"method": "GET",
"status": 200,
"body": b"hello world",
})
# Deploy and execute
contract = direct_deploy(nondet_contract_path)
result = contract.fetch_and_store("https://api.example.com/data")
assert result == "hello world"
assert contract.last_result == "hello world"
# Verify validator consensus
assert direct_vm.run_validator() is True
Testing workflow:
- Mock the endpoint using
direct_vm.mock_webwith regex patterns to match URLs. - Define deterministic responses including status codes and byte payloads.
- Validate consensus by calling
direct_vm.run_validator()to ensure the validator function correctly approves the leader's result.
Real-World Integration Patterns
While contracts/football_bets.py primarily demonstrates gl.nondet.web.render for HTML retrieval, the pattern for raw GET requests follows identical structural logic. When you need JSON or binary data instead of rendered HTML, substitute get for render:
def fetch_api_data(self, api_endpoint: str) -> dict:
# Fetch raw JSON instead of rendered HTML
resp = gl.nondet.web.get(api_endpoint)
raw_data = resp.body.decode("utf-8", errors="replace")
# Process data (e.g., parse JSON or pass to LLM)
return processed_result
This approach maintains the same leader-validator requirements while enabling integration with REST APIs, price oracles, or any external HTTP service.
Summary
- Use
gl.nondet.web.getto perform HTTP GET requests from within GenLayer contracts, importing from the core SDK as shown inCLAUDE.md. - Implement leader-validator pairs to handle non-deterministic external calls, ensuring validators re-issue identical requests and compare responses byte-for-byte.
- Execute consensus using
glvm.run_nondet_unsafe(orglvm.run_nondetwhen using sandboxes) to validate results before storage. - Test locally with
direct_vm.mock_webto simulate API responses and verify consensus logic without network latency. - Reference
contracts/football_bets.pyfor complex contract patterns, substitutinggetforrenderwhen raw HTTP responses are required.
Frequently Asked Questions
What is the difference between gl.nondet.web.get and gl.nondet.web.render?
gl.nondet.web.get returns raw HTTP response bytes, allowing contracts to process JSON, XML, or binary data, while gl.nondet.web.render (as used in contracts/football_bets.py) executes JavaScript rendering on the target page and returns the final HTML. Use get for API endpoints and render for single-page applications requiring browser execution.
Why must validators re-issue the same HTTP request?
Validators independently fetch the URL to verify the leader's honesty and ensure network consensus. Because external APIs can change between calls or return different data to different nodes, the validator's comparison function confirms that the leader's proposed result matches a fresh, independently obtained response. This prevents compromised leaders from injecting false data into the blockchain state.
When should I use run_nondet_unsafe versus run_nondet?
Use run_nondet_unsafe when the leader function does not call spawn_sandbox, which is the typical pattern for simple HTTP GET requests. According to the source implementation, run_nondet is required only when the leader creates isolated execution environments; otherwise, the unsafe variant provides the necessary consensus validation without sandbox overhead.
How do I handle HTTP headers in GenLayer GET requests?
Pass headers as a dictionary to the headers keyword argument: gl.nondet.web.get(url, headers={"Authorization": "Bearer token", "Accept": "application/json"}). The validator function must include identical headers when re-issuing the request to ensure consistent comparison results. Store any required authentication tokens in contract state or pass them as method arguments to both leader and validator functions.
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 →