π Live Demo β tempo-weather-market.vercel.app
Prediction market infrastructure built natively on Tempo Network | Weather as first use case | MPP Β· Payment Memo Β· Fee Sponsorship Β· Scheduled Transactions | pathUSD/USDC settlement
Deployed on both Testnet and Mainnet.
| Network | Contract Address |
|---|---|
| Tempo Mainnet (4217) | 0x072a3a0c04cf8cdcaf5b4a73a4ed4ff5a841531f |
| Tempo Moderato Testnet (42431) | 0xcAC5B9d2817325E78090E3Ce4b9C299C819cF953 |
This project wasn't ported from another chain. Every design decision maps directly to a Tempo protocol capability, and the contract would look meaningfully different β or require off-chain workarounds β on a generic EVM chain.
| Problem | Generic EVM approach | Tempo-native approach |
|---|---|---|
| Oracle needs compensation for settlement work | Side payment, then manual verification | MPP β fee collected atomically inside submitResult() |
| Prove what outcome was settled and why | Parse event logs after the fact | Payment Memo β structured string written on-chain at settlement time |
| Users shouldn't need gas tokens just to place a bet | Require users to bridge and hold native token | Fee Sponsorship β relayer submits the tx, gasTank covers the cost |
| Market must stop accepting bets at a precise time | External keeper or cron job | Scheduled Transactions β IScheduler precompile called from within createMarket() (built into the contract; not yet enabled on mainnet β see Scheduled Transactions below) |
The result is a contract that handles its own lifecycle end-to-end: markets lock themselves, oracle payments are enforced by the contract rather than by trust, and bettors interact with a single stablecoin approval.
oracle-server (Express + TypeScript)
β
βββ POST /oracle/settle β n8n calls this after lockTime
βββ GET /oracle/market/:id β n8n polls market status
βββ GET /oracle/health
β
βββ WeatherAPI.com + Open-Meteo Archive API β dual-source median oracle
βββ WeatherMarket.sol β submitResult() on-chain
Settlement uses a dual-source median oracle: WeatherAPI.com + Open-Meteo Archive API. Each source's individual reading is recorded in the Payment Memo (wa:/om:) alongside the median finalTemp. OpenWeather is used elsewhere in the stack (see below) but is not part of the settlement median.
The oracle calls submitResult() to settle markets. When oracleFee > 0, the caller must first transfer stablecoins to the oracle address and provide a verified paymentTxHash. The contract records a SettlementReceipt for every settled market, queryable via getReceipt(marketId).
Every settlement writes a structured memo to the contract:
{city}/{predictionType}/{finalTemp}/{outcome}
// e.g. "Tokyo/HIGH_TEMP/315/WIN"
The memo is emitted in the ResultSubmitted event and stored on-chain, providing a human-readable audit trail for each market outcome.
A gasTank mechanism allows the contract owner to pre-fund gas costs so that users can bet without holding the native fee token. Approved relayers call placeBetFor(marketId, bucket, amount, bettor) on behalf of users β bettors only need to hold and approve the stablecoin.
Status: implemented in the contract, not yet enabled on mainnet. The deployed mainnet contract's
scheduleraddress is currentlyaddress(0)(seedeployments/tempo.json), so this path is inactive.lockMarket()is called manually today β via the owner-only Admin UI (/admin) or maintenance scripts β oncelockTimepasses.
When a scheduler address is configured via setScheduler(), createMarket() calls the IScheduler precompile to schedule an automatic lockMarket() call at lockTime, removing the need for off-chain cron jobs or manual calls.
bytes32 taskId = IScheduler(scheduler).schedule(
address(this),
abi.encodeCall(this.lockMarket, (marketId)),
lockTime
);| Network | Chain ID | RPC | Explorer |
|---|---|---|---|
| Tempo Moderato (Testnet) | 42431 | https://rpc.moderato.tempo.xyz | https://explorer.moderato.tempo.xyz |
| Tempo (Mainnet) | 4217 | https://rpc.tempo.xyz | https://explorer.tempo.xyz |
Stablecoin addresses
| Network | Token | Address |
|---|---|---|
| Testnet | pathUSD | 0x20c0000000000000000000000000000000000000 |
| Mainnet | USDC.e | 0x20C000000000000000000000b9537d11c60E8b50 |
- Node.js 18+
- Docker & Docker Compose (for Oracle server)
- WeatherAPI.com key (free tier, 1M calls/month) β settlement oracle source
- OpenWeather API key (free tier) β powers the live weather-strip display only, not settlement
- Tempo testnet tokens from faucet
git clone https://github.com/pplmaverick/tempo-weather-market
cd tempo-weather-market
npm install
# Setup Oracle server
cd oracle-server
cp .env.example .env
# Edit .env: fill in WEATHERAPI_KEY, OPENWEATHER_API_KEY and ORACLE_PRIVATE_KEY
docker compose up -d
# Run tests
cd ..
npx hardhat test| Network | Chain ID | RPC | Stablecoin |
|---|---|---|---|
| Testnet (Moderato) | 42431 | https://rpc.moderato.tempo.xyz |
pathUSD |
| Mainnet | 4217 | https://rpc.tempo.xyz |
USDCE |
Switch networks in oracle-server/.env:
TEMPO_NETWORK=testnet # or mainnet
// Owner: create a new market
createMarket(city, predictionType, targetDate, buckets, lockTime)
// Anyone: place a bet into a bucket
placeBet(marketId, bucket, amount)
// Relayer (Fee Sponsorship): place a bet on behalf of a user
placeBetFor(marketId, bucket, amount, bettor)
// Anyone: lock the market after lockTime (also called by Scheduler)
lockMarket(marketId)
// Oracle: settle with real weather data (MPP endpoint)
submitResult(marketId, finalTemp, memo)
// Winner: claim winnings after settlement
claimWinnings(marketId)Temperatures are stored as int256 with one decimal place precision:
315 = 31.5Β°C
-12 = -1.2Β°C
Bucket boundaries use the same x10 encoding as temperatures. Given buckets = [250, 280, 310, 340]:
| Bucket | Range |
|---|---|
| 0 | β€ 25.0Β°C |
| 1 | 25.1Β°C β 28.0Β°C |
| 2 | 28.1Β°C β 31.0Β°C |
| 3 | 31.1Β°C β 34.0Β°C |
| 4 | > 34.0Β°C |
Fees
- Market fee: 2% of total pool, deducted from winnings
- Oracle fee: configurable via
setOracleFee(), paid in stablecoin per settlement - No winner: all bets refunded in full, no fee taken
Security
- Oracle address set at deployment, updatable only by owner
- Relayer whitelist managed by owner
ReentrancyGuardonclaimWinnings- All token transfers use OpenZeppelin
SafeERC20
| Layer | Technology |
|---|---|
| Smart contract | Solidity ^0.8.28, OpenZeppelin 5.x |
| Development | Hardhat 3 + Viem |
| Oracle server | Node.js + Express + TypeScript |
| Automation | n8n workflow |
| Testnet stablecoin | pathUSD (0x20c000...) |
| Mainnet stablecoin | USDC.e (0x20C000...) |
| Milestone | Description | Status |
|---|---|---|
| M1 | Deploy to testnet + mainnet, Oracle server on VPS, 44/44 tests | β Complete |
| M2 | Oracle retry logic (3 attempts, 2s delay) | β Complete |
| M3 | Multi-city support: Taipei, Tokyo, New York, Seoul | β Complete |
| M4 | Developer docs: .env.example with inline comments, README setup guide | β Complete |
| M5 | React frontend on Vercel | β Complete |
| M6 | Dual-source weather median oracle: WeatherAPI + Open-Meteo (OpenWeather History API is not available on this key's plan, dropped from settlement) | β Complete |
| M7 | TypeScript SDK | π Planned |
GitHub: pplmaverick
Wallet: 0xed2B...78F5 β deployed on both Tempo Testnet and Mainnet
MIT