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.get call, 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 True only 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_unsafe when the leader function does not call spawn_sandbox; otherwise use run_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:

  1. Mock the endpoint using direct_vm.mock_web with regex patterns to match URLs.
  2. Define deterministic responses including status codes and byte payloads.
  3. 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.get to perform HTTP GET requests from within GenLayer contracts, importing from the core SDK as shown in CLAUDE.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 (or glvm.run_nondet when using sandboxes) to validate results before storage.
  • Test locally with direct_vm.mock_web to simulate API responses and verify consensus logic without network latency.
  • Reference contracts/football_bets.py for complex contract patterns, substituting get for render when 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:

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 →