diff --git a/README.md b/README.md
index 8fe9f39..ac936bb 100644
--- a/README.md
+++ b/README.md
@@ -1,108 +1,99 @@
-# Sample GenLayer project
-[](https://opensource.org/license/mit/)
-[](https://discord.gg/8Jm4v89VAu)
-[](https://t.me/genlayer)
-[](https://x.com/GenLayer)
-[](https://star-history.com/#yeagerai/genlayer-js)
+# Signal Quality Oracle โ GenLayer Bradbury Hackathon
-## ๐ About
-This project includes the boilerplate code for a GenLayer use case implementation, specifically a football bets game.
-
-## ๐ฆ What's included
-- Basic requirements to deploy and test your intelligent contracts locally
-- Configuration file template
-
-- An example of an intelligent contract (Football Bets)
-- Example end-to-end tests for the contract provided
-- A production-ready Next.js 15 frontend with TypeScript, TanStack Query, and Radix UI
-
-## ๐ ๏ธ Requirements
-- A running GenLayer Studio (Install from [Docs](https://docs.genlayer.com/developers/intelligent-contracts/tooling-setup#using-the-genlayer-studio) or work with the hosted version of [GenLayer Studio](https://studio.genlayer.com/)). If you are working locally, this repository code does not need to be located in the same directory as the Genlayer Studio.
-- [GenLayer CLI](https://github.com/genlayerlabs/genlayer-cli) globally installed. To install or update the GenLayer CLI run `npm install -g genlayer`
-
-## ๐ Steps to run this example
-
-### 1. Deploy the contract
- Deploy the contract from `/contracts/football_bets.py` using the GenLayer CLI:
- 1. Choose the network that you want to use (studionet, localnet, or tesnet-*): `genlayer network`
- 2. Execute the deploy command `genlayer deploy`. This command is going to execute the deploy script located in `/deploy/deployScript.ts`
-
-### 2. Setup the frontend environment
- 1. All the content of the dApp is located in the `/frontend` folder.
- 2. Copy the `.env.example` file in the `frontend` folder and rename it to `.env`, then fill in the values for your configuration. The provided NEXT_PUBLIC_GENLAYER_RPC_URL value is the backend of the hosted GenLayer Studio.
- 3. Add the deployed contract address to the `/frontend/.env` under the variable `NEXT_PUBLIC_CONTRACT_ADDRESS`
-
-### 4. Run the frontend Next.js app
- Execute the following commands in your terminal:
-
- **Using bun:**
- ```shell
- cd frontend
- bun install
- bun dev
- ```
-
- **Using npm:**
- ```shell
- cd frontend
- npm install
- npm run dev
- ```
-
- The terminal should display a link to access your frontend app (usually at ).
- For more information on the code see [GenLayerJS](https://github.com/yeagerai/genlayer-js).
-
-### 5. Test contracts
-1. Install the Python packages listed in the `requirements.txt` file in a virtual environment.
-2. Make sure your GenLayer Studio is running. Then execute the following command in your terminal:
- ```shell
- gltest
- ```
-
-## โฝ How the Football Bets Contract Works
-
-The Football Bets contract allows users to create bets for football matches, resolve those bets, and earn points for correct bets. Here's a breakdown of its main functionalities:
-
-1. Creating Bets:
- - Users can create a bet for a specific football match by providing the game date, team names, and their predicted winner.
- - The contract checks if the game has already finished and if the user has already made a bet for this match.
-
-2. Resolving Bets:
- - After a match has concluded, users can resolve their bets.
- - The contract fetches the actual match result from a specified URL.
- - If the Bet was correct, the user earns a point.
-
-3. Querying Data:
- - Users can retrieve all bets.
- - The contract also allows querying of points, either for all players or for a specific player.
-
-4. Getting Points:
- - Points are awarded for correct bets.
- - Users can check their total points or the points of any player.
-
-## ๐งช Tests
-
-This project includes integration tests that interact with the contract deployed in the Studio. These tests cover the main functionalities of the Football Bets contract:
-
-1. Creating a bet
-2. Resolving a bet
-3. Querying bets for a player
-4. Querying points for a player
-
-The tests simulate real-world interactions with the contract, ensuring that it behaves correctly under various scenarios. They use the GenLayer Studio to deploy and interact with the contract, providing a comprehensive check of the contract's functionality in a controlled environment.
-
-To run the tests, use the `gltest` command as mentioned in the "Steps to run this example" section.
-
-
-## ๐ฌ Community
-Connect with the GenLayer community to discuss, collaborate, and share insights:
-- **[Discord Channel](https://discord.gg/8Jm4v89VAu)**: Our primary hub for discussions, support, and announcements.
-- **[Telegram Group](https://t.me/genlayer)**: For more informal chats and quick updates.
-
-Your continuous feedback drives better product development. Please engage with us regularly to test, discuss, and improve GenLayer.
+> AI-powered news signal quality scoring. Only possible on GenLayer.
-## ๐ Documentation
-For detailed information on how to use GenLayerJS SDK, please refer to our [documentation](https://docs.genlayer.com/).
+## What It Does
-## ๐ License
-This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+The Signal Quality Oracle is an intelligent contract that **autonomously evaluates news signals** against editorial standards using AI. It leverages GenLayer's unique capabilities โ LLM access and web rendering โ to do something no other blockchain can:
+
+1. **Score signal quality** (0-100) using LLM analysis
+2. **Verify claims against source material** by reading web URLs
+3. **Track correspondent reputation** on-chain
+4. **Enforce editorial standards** per beat category
+
+## Why GenLayer?
+
+This contract is **impossible on any other blockchain** because it requires:
+- `gl.nondet.exec_prompt()` โ Running LLM inference on-chain
+- `gl.nondet.web.render()` โ Fetching and reading web content
+- `gl.eq_principle.strict_eq()` โ Consensus validation of non-deterministic outputs
+
+Traditional smart contracts can't read the internet or think. GenLayer contracts can.
+
+## Architecture
+
+```
+contracts/
+ signal_oracle.py # Main intelligent contract
+deploy/
+ deployScript.ts # Deployment script
+frontend/ # Next.js web interface
+test/ # Integration tests
+```
+
+## Key Features
+
+### 1. Signal Submission with AI Scoring
+```python
+@gl.public.write
+def submit_signal(headline, body, beat) -> dict
+```
+Submits a signal for evaluation. The LLM analyzes it against editorial standards and returns a score, approval status, and actionable feedback.
+
+### 2. Web Verification (GenLayer-Exclusive)
+```python
+@gl.public.write
+def verify_web_signal(headline, source_url) -> dict
+```
+Reads the source URL directly and verifies whether the headline's claims are supported by the actual content. **This is the killer feature** โ on-chain fact-checking.
+
+### 3. Reputation Tracking
+```python
+@gl.public.view
+def get_submitter_reputation(addr) -> int
+```
+Tracks how many signals each correspondent has gotten approved, creating an on-chain reputation system.
+
+### 4. Configurable Editorial Standards
+```python
+@gl.public.write
+def update_standard(beat, ...) -> None
+```
+Each beat can have different editorial standards (length requirements, banned topics, approval thresholds).
+
+## Editorial Standards
+
+Default configuration:
+- Headlines: 20-120 characters
+- Body: 150-400 words
+- Minimum 2 data points/numbers required
+- Banned topics: bitcoin price, sentiment index, external hacks, ETF flows
+- Approval threshold: 70/100 score
+
+## Use Cases
+
+- **News organizations**: Automated quality control for contributor submissions
+- **DAOs**: On-chain content curation with AI verification
+- **Bounty platforms**: Verify that bounty submissions meet requirements
+- **Academic publishing**: Pre-screen papers against editorial standards
+
+## Running Locally
+
+```bash
+# Start GenLayer simulator
+genlayer up
+
+# Deploy contract
+npm run deploy
+
+# Run frontend
+cd frontend && npm run dev
+```
+
+## Hackathon Track
+
+**Builders Track** โ Deployed intelligent contract demonstrating GenLayer's core differentiator: AI-native smart contracts that can read, think, and judge.
+
+## Team
+
+Built for the GenLayer Bradbury Hackathon (March 20 - April 10, 2026)
diff --git a/contracts/signal_oracle.py b/contracts/signal_oracle.py
new file mode 100644
index 0000000..c476f4e
--- /dev/null
+++ b/contracts/signal_oracle.py
@@ -0,0 +1,228 @@
+# { "Depends": "py-genlayer:test" }
+
+import json
+from dataclasses import dataclass
+from genlayer import *
+
+
+@allow_storage
+@dataclass
+class SignalSubmission:
+ signal_id: str
+ submitter: Address
+ headline: str
+ body: str
+ beat: str
+ quality_score: int # 0-100
+ is_approved: bool
+ feedback: str
+ timestamp: u256
+
+
+@allow_storage
+@dataclass
+class EditorialStandard:
+ name: str
+ min_headline_len: int
+ max_headline_len: int
+ min_body_words: int
+ max_body_words: int
+ required_numbers: int
+ banned_topics: str # JSON array
+ approval_threshold: int # min score to approve
+
+
+class SignalQualityOracle(gl.Contract):
+ """
+ AI-powered signal quality oracle for news correspondents.
+ Uses GenLayer's LLM to score news signals against editorial standards.
+ Unique to GenLayer: contracts that READ and JUDGE content using AI.
+ """
+
+ submissions: TreeMap[str, SignalSubmission]
+ standards: TreeMap[str, EditorialStandard]
+ submitter_scores: TreeMap[Address, u256]
+ total_submissions: u256
+ owner: Address
+
+ def __init__(self):
+ self.owner = gl.message.sender_address
+ self.total_submissions = 0
+ # Initialize default editorial standard
+ self.standards["default"] = EditorialStandard(
+ name="Default Editorial Standard",
+ min_headline_len=20,
+ max_headline_len=120,
+ min_body_words=150,
+ max_body_words=400,
+ required_numbers=2,
+ banned_topics=json.dumps(["bitcoin_price", "sentiment_index", "external_hacks", "etf_flows"]),
+ approval_threshold=70,
+ )
+
+ @gl.public.write
+ def submit_signal(self, headline: str, body: str, beat: str) -> dict:
+ """
+ Submit a news signal for quality evaluation.
+ The contract uses LLM to analyze the signal against editorial standards.
+ """
+ sender = gl.message.sender_address
+ signal_id = f"{str(sender)}_{str(self.total_submissions)}"
+
+ # Get editorial standard for this beat (or default)
+ standard_key = beat if beat in self.standards else "default"
+ standard = self.standards[standard_key]
+
+ # AI-powered quality evaluation
+ def evaluate_signal() -> str:
+ prompt = f"""You are a strict news editor evaluating a correspondent's signal.
+
+EDITORIAL STANDARDS:
+- Headline: {standard.min_headline_len}-{standard.max_headline_len} characters
+- Body: {standard.min_body_words}-{standard.max_body_words} words
+- Must contain at least {standard.required_numbers} specific numbers/data points
+- Banned topics: {standard.banned_topics}
+- Beat: {beat}
+
+SIGNAL TO EVALUATE:
+Headline: {headline}
+Body: {body}
+
+Score this signal 0-100 on:
+1. Specificity (quantified claims, not vague)
+2. Prescriptive value (what should agents DO, not just what happened)
+3. Editorial compliance (meets length/format requirements)
+4. Not a banned topic
+5. Novelty (not just repeating known info)
+
+Respond in JSON only:
+{{
+ "score": int, // 0-100
+ "approved": bool, // true if score >= {standard.approval_threshold}
+ "feedback": str, // brief actionable feedback
+ "issues": [str] // list of specific issues found
+}}
+It is mandatory that you respond only using the JSON format above, nothing else."""
+
+ result = gl.nondet.exec_prompt(prompt, response_format="json")
+ return json.dumps(result, sort_keys=True)
+
+ eval_result = json.loads(gl.eq_principle.strict_eq(evaluate_signal))
+
+ submission = SignalSubmission(
+ signal_id=signal_id,
+ submitter=sender,
+ headline=headline,
+ body=body,
+ beat=beat,
+ quality_score=int(eval_result.get("score", 0)),
+ is_approved=bool(eval_result.get("approved", False)),
+ feedback=str(eval_result.get("feedback", "")),
+ timestamp=u256(gl.block.timestamp),
+ )
+
+ self.submissions[signal_id] = submission
+ self.total_submissions += 1
+
+ # Track submitter reputation
+ if sender not in self.submitter_scores:
+ self.submitter_scores[sender] = 0
+ if submission.is_approved:
+ self.submitter_scores[sender] += 1
+
+ return {
+ "signal_id": signal_id,
+ "score": submission.quality_score,
+ "approved": submission.is_approved,
+ "feedback": submission.feedback,
+ }
+
+ @gl.public.view
+ def get_submission(self, signal_id: str) -> dict:
+ """Get details of a signal submission."""
+ sub = self.submissions[signal_id]
+ return {
+ "signal_id": sub.signal_id,
+ "submitter": sub.submitter.as_hex,
+ "headline": sub.headline,
+ "beat": sub.beat,
+ "quality_score": sub.quality_score,
+ "is_approved": sub.is_approved,
+ "feedback": sub.feedback,
+ "timestamp": int(sub.timestamp),
+ }
+
+ @gl.public.view
+ def get_submitter_reputation(self, addr: str) -> int:
+ """Get the approval count for a submitter."""
+ return int(self.submitter_scores.get(Address(addr), 0))
+
+ @gl.public.view
+ def get_leaderboard(self) -> dict:
+ """Get top correspondents by approval count."""
+ return {k.as_hex: int(v) for k, v in self.submitter_scores.items()}
+
+ @gl.public.view
+ def get_standard(self, beat: str) -> dict:
+ """Get editorial standard for a beat."""
+ key = beat if beat in self.standards else "default"
+ std = self.standards[key]
+ return {
+ "name": std.name,
+ "headline_len": f"{std.min_headline_len}-{std.max_headline_len}",
+ "body_words": f"{std.min_body_words}-{std.max_body_words}",
+ "required_numbers": std.required_numbers,
+ "banned_topics": std.banned_topics,
+ "threshold": std.approval_threshold,
+ }
+
+ @gl.public.write
+ def update_standard(self, beat: str, name: str, min_headline: int, max_headline: int,
+ min_body: int, max_body: int, req_numbers: int,
+ banned: str, threshold: int) -> None:
+ """Update editorial standard (owner only)."""
+ if gl.message.sender_address != self.owner:
+ raise Exception("Only owner can update standards")
+ self.standards[beat] = EditorialStandard(
+ name=name,
+ min_headline_len=min_headline,
+ max_headline_len=max_headline,
+ min_body_words=min_body,
+ max_body_words=max_body,
+ required_numbers=req_numbers,
+ banned_topics=banned,
+ approval_threshold=threshold,
+ )
+
+ @gl.public.write
+ def verify_web_signal(self, headline: str, source_url: str) -> dict:
+ """
+ Verify a signal by checking its source URL.
+ Uses GenLayer's web access to validate claims against source material.
+ UNIQUE TO GENLAYER: No other blockchain can do this.
+ """
+ def verify_against_source() -> str:
+ web_content = gl.nondet.web.render(source_url, mode="text")
+
+ prompt = f"""A news correspondent claims: "{headline}"
+
+Source content from {source_url}:
+{web_content[:3000]}
+
+Verify: Does the source content support this headline?
+Score accuracy 0-100.
+
+Respond in JSON only:
+{{
+ "accuracy": int,
+ "supported": bool,
+ "source_quality": str,
+ "discrepancies": [str]
+}}
+It is mandatory that you respond only using the JSON format above, nothing else."""
+
+ result = gl.nondet.exec_prompt(prompt, response_format="json")
+ return json.dumps(result, sort_keys=True)
+
+ verification = json.loads(gl.eq_principle.strict_eq(verify_against_source))
+ return verification
diff --git a/deploy/deployScript.ts b/deploy/deployScript.ts
index ecd65f0..8ac4b44 100644
--- a/deploy/deployScript.ts
+++ b/deploy/deployScript.ts
@@ -1,49 +1,34 @@
-import { readFileSync } from "fs";
-import path from "path";
-import {
- TransactionHash,
- TransactionStatus,
- GenLayerClient,
- DecodedDeployData,
- GenLayerChain,
-} from "genlayer-js/types";
-import { localnet } from "genlayer-js/chains";
-
-export default async function main(client: GenLayerClient) {
- const filePath = path.resolve(process.cwd(), "contracts/football_bets.py");
-
- try {
- const contractCode = new Uint8Array(readFileSync(filePath));
-
- await client.initializeConsensusSmartContract();
-
- const deployTransaction = await client.deployContract({
- code: contractCode,
- args: [],
- });
-
- const receipt = await client.waitForTransactionReceipt({
- hash: deployTransaction as TransactionHash,
- status: TransactionStatus.ACCEPTED,
- retries: 200,
- });
-
- if (
- receipt.status !== 5 &&
- receipt.status !== 6 &&
- receipt.statusName !== "ACCEPTED" &&
- receipt.statusName !== "FINALIZED"
- ) {
- throw new Error(`Deployment failed. Receipt: ${JSON.stringify(receipt)}`);
- }
-
- const deployedContractAddress =
- (client.chain as GenLayerChain).id === localnet.id
- ? receipt.data.contract_address
- : (receipt.txDataDecoded as DecodedDeployData)?.contractAddress;
-
- console.log(`Contract deployed at address: ${deployedContractAddress}`);
- } catch (error) {
- throw new Error(`Error during deployment:, ${error}`);
- }
+import { createClient } from "genlayer-js";
+import { SimulatorTransport } from "genlayer-js/simulator";
+import { privateKeyToAccount } from "genlayer-js/accounts";
+import { abi } from "./SignalOracle.json";
+
+// Initialize client
+const transport = new SimulatorTransport(process.env.RPC_URL || "https://studio.genlayer.com/api");
+const client = createClient({ transport });
+
+// Account from CLI keystore
+const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
+
+async function deploy() {
+ console.log("Deploying SignalQualityOracle...");
+
+ const contractPath = "./contracts/signal_oracle.py";
+ const fs = await import("fs");
+ const contractCode = fs.readFileSync(contractPath, "utf-8");
+
+ const txHash = await client.deployContract({
+ account,
+ code: contractCode,
+ args: [],
+ });
+
+ console.log(`Deploy tx: ${txHash}`);
+
+ const receipt = await client.waitForTransactionReceipt({ hash: txHash });
+ console.log(`Contract deployed at: ${receipt.contractAddress}`);
+ console.log(`\nAdd this to frontend/.env:`);
+ console.log(`NEXT_PUBLIC_CONTRACT_ADDRESS=${receipt.contractAddress}`);
}
+
+deploy().catch(console.error);