Medinah Country Club · Sept 24–28, 2025

Presidents Cup
Live on Base.

No Polymarket market. No external data feed. We deployed a contract on Base, pointed Quicknode Streams at it, and this scoreboard reads the chain directly — 81ms, no middlemen.

-- Days
-- Hours
-- Min
-- Sec
0
USA points
0
Intl points
9
Sessions total
81ms
Avg RPC latency
Two Teams. Nine Sessions. One Chain.
USA
Loading roster…
International
Loading roster…
Scoreboard
Team F1F2F3F4 B1B2B3B4 Singles Pts
🇺🇸 USA 0
🌍 International 0
Read from Base via eth_getLogs · Quicknode Core RPC · updates every 20s
Call every session. Top predictor wins.
Top Predictors
One contract. Nine sessions. Zero databases.
01

Contract deployed on Base

A single-function Solidity contract accepts session results from one owner wallet. No funds held. The moment a session is official, one transaction writes it permanently onchain.

02

Quicknode Streams watches it

A JS filter function in the Streams dashboard decodes every ResultLogged event on Base. Fires the moment the block lands — no polling, no missed events.

03

Core RPC reads the chain

This scoreboard calls eth_getLogs on the contract every 20 seconds. 81ms average on Base. Every cell is a live chain read — no database behind this page.

// PresidentsCupResults.sol — single-purpose, no funds
pragma solidity ^0.8.20;

contract PresidentsCupResults {
    address public owner;
    mapping(uint256 => bool) public sessionLocked;

    event ResultLogged(
        uint256 indexed sessionId,
        uint8   indexed team,    // 0 = USA, 1 = Intl
        uint256         points
    );

    constructor() { owner = msg.sender; }

    function logResult(uint256 sessionId, uint8 team, uint256 points)
        external
    {
        require(msg.sender == owner, "not owner");
        require(!sessionLocked[sessionId], "locked");
        sessionLocked[sessionId] = true;
        emit ResultLogged(sessionId, team, points);
    }
}
// Quicknode Streams — filter function
// Paste into the Streams dashboard under "Filter Function"

function main(data) {
  const CONTRACT = process.env.CONTRACT_ADDRESS.toLowerCase();
  const TOPIC    = process.env.EVENT_TOPIC;   // keccak256("ResultLogged(...)")

  return data.logs
    .filter(log =>
      log.address.toLowerCase() === CONTRACT &&
      log.topics[0] === TOPIC
    )
    .map(log => ({
      sessionId: parseInt(log.topics[1], 16),
      team:      parseInt(log.topics[2], 16) === 0 ? "usa" : "intl",
      points:    parseInt(log.data, 16),
      txHash:    log.transactionHash,
      block:     parseInt(log.blockNumber, 16),
    }));
}
// Pages Function — /functions/api/standings.js
// Calls Base Mainnet RPC, keeps endpoint URL server-side

const res = await fetch(env.QUICKNODE_BASE_ENDPOINT, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    jsonrpc: "2.0", id: 1,
    method: "eth_getLogs",
    params: [{
      fromBlock: "0x0",
      toBlock:   "latest",
      address:   env.PRESIDENTS_CUP_CONTRACT,
      topics:    [env.PRESIDENTS_CUP_TOPIC],
    }],
  }),
  signal: AbortSignal.timeout(4000),
});

const { result: logs } = await res.json();
// decode topics → sessionId, team, points → return standings JSON
Every piece runs on Quicknode.
Streams

Watch any event, forever

One filter function in the Streams dashboard watches every Base block. Fires on every ResultLogged event — no missed blocks, no polling.

Streams docs →
Webhooks

Decoded events to any destination

Streams delivers the decoded JSON payload within seconds of the block landing. Point it at Slack, a database, or a custom endpoint — same config.

Configure destinations →
Core RPC

81ms reads on Base

Every scoreboard cell is a direct chain read via eth_getLogs. 99.99% uptime. No third-party data feed. The contract is the source of truth.

Base docs →
SQL Explorer · optional

Prediction market layer

If a Presidents Cup outcome market lists on a supported chain, one SQL query pulls live odds from the indexed tables.

SQL Explorer →

Deploy this on any
EVM chain in an afternoon.

The contract is 30 lines. The Streams filter is 15. The scoreboard reads directly from the chain. Start free — no credit card.

Start building for free Read the Streams docs