No Polymarket market. No external data feed. We deployed a contract on Base Sepolia, pointed Quicknode Streams at it, and every result lands here within seconds of the block. 81ms, no middlemen.
| Session | Max pts | USA | Intl | Tx |
|---|---|---|---|---|
| Thu · Four-ball | 5 | – | – | |
| Fri · Foursomes | 5 | – | – | |
| Sat · Foursomes | 4 | – | – | |
| Sat · Four-ball | 4 | – | – | |
| Sun · Singles | 12 | – | – | |
| Total · first to 15.5 wins | 0 | 0 |
A single Solidity contract accepts five session results from one owner wallet. No funds held. The moment a session is official, one transaction writes it permanently onchain.
A filter function in the Streams dashboard decodes every ResultLogged event on Base Sepolia and pushes the decoded JSON to this server. No polling, no missed blocks.
The webhook writes parsed standings to Cloudflare KV. This scoreboard reads KV on every poll — 81ms reads, no chain calls needed on page load. If KV is cold, a bounded RPC fallback reads the last 2,000 blocks.
// 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 — bounded RPC fallback (KV is primary) // fromBlock is current − 2000 to avoid block-range limits const hRes = await fetch(env.QUICKNODE_BASE_ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_blockNumber", params: [] }), }); const latest = parseInt((await hRes.json()).result, 16); const fromBlock = "0x" + Math.max(0, latest - 2000).toString(16); const res = await fetch(env.QUICKNODE_BASE_ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "eth_getLogs", params: [{ fromBlock, toBlock: "latest", address: env.PRESIDENTS_CUP_CONTRACT, topics: [env.PRESIDENTS_CUP_TOPIC] }] }), signal: AbortSignal.timeout(4000), }); const { result: logs } = await res.json(); // decode topics[1] → sessionId, topics[2] → usaX2, topics[3] → intlX2
One filter function in the Streams dashboard watches every Base block. Fires on every ResultLogged event — no missed blocks, no polling.
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 →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.
If a Presidents Cup outcome market lists on a supported chain, one SQL query pulls live odds from the indexed tables.
SQL Explorer →The contract is 30 lines. The Streams filter is 15. The scoreboard reads directly from the chain. Start free — no credit card.