A decentralized fact-verification primitive for the GenLayer blockchain. It cross-references multiple independent web sources to determine whether a given statement is TRUE, FALSE, or UNVERIFIABLE -- powered by AI consensus among decentralized validator nodes.
Traditional fact-checking is centralized: a single entity decides what is true. CrossSourceFactVerifier distributes this process across a network of independent AI validators, each fetching sources and reasoning independently. The result is a trustless, censorship-resistant verification record stored on-chain.
Key differentiator: Requires a minimum of 2 independent source URLs, enforcing genuine cross-verification rather than single-source reliance.
Verify a factual statement against multiple sources.
| Parameter | Type | Description |
|---|---|---|
statement |
str |
The claim to verify (must not be empty) |
source_urls |
str |
Comma-separated URLs (min 2, max 10, each must start with http:// or https://) |
Raises:
UserError("statement must not be empty")-- empty or whitespace-only statementUserError("no valid source URLs provided")-- no URLs start with http/httpsUserError("at least 2 source URLs required for cross-verification")-- only 1 valid URL
Returns a single verification record as JSON.
{
"id": "0",
"statement": "Tesla delivered 500000 vehicles in Q3 2025",
"sources": "https://reuters.com/article1,https://bbc.com/article2",
"verdict": "TRUE",
"agreement_ratio": 85,
"conflicting_sources": "None",
"verifier": "0xabc123..."
}Raises: UserError("record not found") if record_id does not exist.
Returns all verification records as a JSON array.
Returns aggregate statistics:
{
"total_verified": 10,
"true_count": 6,
"false_count": 2,
"unverifiable_count": 2
}CrossSourceFactVerifier uses gl.vm.run_nondet_unsafe(leader_fn, validator_fn) -- the most powerful consensus pattern in GenLayer, where you write your own validator logic.
- The leader validator fetches content from each source URL using
gl.nondet.web.render(url, mode="text"). - Source content is truncated (3000 chars per source, 8000 total) and assembled into labeled sections.
- The leader calls
gl.nondet.exec_prompt()with a structured fact-checking prompt, requesting JSON output withverdict,agreement_ratio, andconflicting_sources. - Results are normalized (verdict mapped to TRUE/FALSE/UNVERIFIABLE, ratio clamped 0-100).
Each validator independently:
- Re-runs the entire leader logic -- fetching sources and querying their own LLM.
- Compares semantics, not exact text:
- Verdict must match (both say TRUE, or both say FALSE, etc.) after normalization
- Agreement ratio must fall in the same band:
- Band 0 (LOW): 0-35%
- Band 1 (MID): 36-70%
- Band 2 (HIGH): 71-100%
conflicting_sourcestext is not compared (different LLMs may list sources differently)
This band-based comparison allows different LLMs to reach the same semantic conclusion without requiring identical numeric outputs.
- Not a format check: Validators verify the meaning of the result, not just JSON structure.
- Robust to LLM variance: Different models can word things differently but still agree on the core verdict and confidence level.
- Genuine cross-source analysis: Each validator independently fetches and analyzes the sources.
| Use Case | Description |
|---|---|
| News Verification | Verify breaking news claims before sharing on decentralized social media |
| Research Claims | Check scientific claims against multiple journal/news sources |
| Due Diligence | Verify company information from independent sources before investing |
| Prediction Markets | Resolve event outcomes using multiple independent news sources |
| Content Platforms | Label user-submitted articles as "Verified" or "Disputed" |
| Insurance Claims | Cross-reference claim details against public records and news |
| Supply Chain | Verify product origin and certification claims from multiple registries |
Method: verify_fact
Args:
statement: "SpaceX successfully launched Starship on June 15 2025"
source_urls: "https://reuters.com/space/spacex-starship,https://bbc.com/news/science-spacex,https://cnbc.com/spacex-launch"
Method: get_verification
Args:
record_id: "0"
Method: get_stats
Args: (none)
VerificationRecord (on-chain struct):
- statement: str
- sources: str (comma-separated URLs)
- verdict: str (TRUE | FALSE | UNVERIFIABLE)
- agreement_ratio: bigint (0-100)
- conflicting_sources: str (URLs or "None")
- verifier: str (caller address as hex)
Contract state:
- records: TreeMap[str, VerificationRecord]
- next_id: bigint
- total_verified: bigint
- true_count: bigint
- false_count: bigint
- unverifiable_count: bigint
| Scenario | Behavior |
|---|---|
| Empty statement | UserError("statement must not be empty") |
| No valid URLs | UserError("no valid source URLs provided") |
| Only 1 valid URL | UserError("at least 2 source URLs required...") |
| All sources unreachable | Verdict = UNVERIFIABLE, ratio = 0 |
| Some sources fail | Analyzes reachable sources only |
| Only 1 source reachable | Analyzes it, notes "Insufficient reachable sources" |
| LLM returns bad JSON | Fallback to UNVERIFIABLE, ratio = 0 |
| Ratio out of range | Clamped to 0-100 |
| More than 10 URLs | Only first 10 are used |
| Same statement verified twice | Allowed (information may change over time) |
The contract is actively deployed on the GenLayer test network (studionet):
- Contract Address:
0x4afC08645cC7cd2074A8774e03C2fD162a5ee43f - Network:
studionet
Below is a realistic, worked example demonstrating how the primitive processes a claim.
Call verify_fact with a statement and at least two source URLs:
- Statement:
"GenLayer is a blockchain Layer-1 that integrates AI at the consensus level." - Source URLs:
"https://genlayer.com,https://github.com/genlayer"
Call get_verification("0"). It returns the following verification record JSON:
{
"id": "0",
"statement": "GenLayer is a blockchain Layer-1 that integrates AI at the consensus level.",
"sources": "https://genlayer.com,https://github.com/genlayer",
"verdict": "TRUE",
"agreement_ratio": 100,
"conflicting_sources": "None",
"verifier": "0x58f3a56e32a9637e8bcd44ccaeae794508b9b25e"
}- Open GenLayer Studio.
- Click "New Contract" or use the file explorer.
- Paste the contents of
contracts/CrossSourceFactVerifier.pyinto the editor. - Select network Studionet.
- Click "Deploy".
- Wait for deployment confirmation -- you should see the contract address.
- Test via the Studio UI:
- Call
verify_factwith a statement and comma-separated URLs. - Call
get_verificationwithrecord_id = "0"to see the result. - Call
get_statsto see aggregate counts.
- Call
- Python 3.11+
genlayer-testpackage (gltest)
pip install genlayer-testcd cross-source-fact-verifier
pytest tests/test_cross_source_fact_verifier.py -v- Happy paths: TRUE, FALSE, UNVERIFIABLE verdicts
- Edge cases: Empty statement, no URLs, single URL, nonexistent record
- Stats tracking: Counters increment correctly across multiple verifications
- Multiple verifications: Re-verifying updates stats properly
- Go to the GenLayer Builder Portal.
- Navigate to the Builder track, Intelligent Contracts category.
- Fill in the submission form:
- Title: CrossSourceFactVerifier
- Description: A decentralized fact-verification primitive that cross-references multiple independent web sources using AI consensus to determine statement truthfulness.
- Repository: Link to your repo or upload the project files.
- Contract file:
contracts/CrossSourceFactVerifier.py - Test file:
tests/test_cross_source_fact_verifier.py
- Submit and wait for review.
cross-source-fact-verifier/
contracts/
CrossSourceFactVerifier.py # The intelligent contract
tests/
test_cross_source_fact_verifier.py # Test suite with mocks
README.md # This file
MIT