Skip to Content
Canonicals docs are in active development.
DocsArchitecture

Canonicals - Smart Contract Architecture

Overview

What is Canonicals?

Canonicals is an EVM-blockchain-based Time-Series Prediction (TSP) platform that enables users to predict specific future values of real-world data sources, rather than simple binary outcomes. Built entirely with Solidity smart contracts, the platform combines mathematical precision with blockchain transparency to create a fair, verifiable prediction marketplace.

Core Innovation: Mathematical Predictions

Unlike traditional prediction markets that ask “Will X happen? (Yes/No)”, Canonicals asks “What specific value will X be?”

Example Comparison:

  • Traditional: “Will Bitcoin be above $50,000?” → Binary outcome (Yes/No)
  • Canonicals: “What will Bitcoin’s price be on March 15, 2026?” → Predict specific value (e.g., $52,347.82)

This precision is achieved through statistical distributions (normal and lognormal) with time-sensitive winning ranges, where earlier predictions receive wider error margins, creating a sophisticated risk-reward dynamic.

Technical Architecture Highlights

1. Factory Pattern with Clone Deployment (EIP-1167)

  • One implementation contract serves as a template
  • New markets/expiries deployed as minimal proxy clones (~45 bytes each)
  • Result: ~90% gas cost savings compared to full contract deployment
  • Enables unlimited markets without prohibitive costs

2. Centralized Balance Management

  • ALL user tokens stored in a single TSP_Manager contract (central vault)
  • Predictions update allowances and locked amounts, not actual token transfers
  • Two-level approval system: Users → TSP_Manager → Individual Expiries
  • Result: Enhanced security, reduced gas costs, simplified accounting

3. Oracle Integration via Chainlink Functions

  • Markets define oracle configuration (JavaScript code, API endpoints, secrets)
  • Each expiry provides specific arguments (dates, identifiers) as string[]
  • Chainlink executes requests across multiple nodes for consensus
  • Resolved values stored on-chain, immutable and verifiable
  • Result: Decentralized, tamper-proof real-world data resolution

4. Hierarchical Contract Structure

TSP_Manager (Factory & Treasury) ↓ creates TSP_Market (Market Container) ↓ creates TSP_Expiry (Individual Prediction Contest) ↓ requests data from OracleData_Manager (Resolved Values Storage) ↓ fetches via Chainlink Functions (External Oracle Network)

Platform Participants

Market Creators: Deploy prediction markets for specific data sources (oil prices, COE auctions, crypto prices)

  • Earn tax fees from every prediction in their market
  • Can create multiple expiries (different future dates)
  • Define oracle configurations and mathematical parameters

Forecasters: Make predictions by staking cryptocurrency

  • Predict values within specified ranges
  • Earlier predictions = wider error tolerance (higher risk, more flexibility)
  • Later predictions = tighter error tolerance (lower risk, needs accuracy)
  • Winners share prize pool proportionally to their stakes

Resolvers: Trigger oracle requests to fetch real-world data

  • Anyone can resolve an expiry during the resolution period
  • Earn oracle requester rewards (% of prize pool)
  • Help ensure timely market settlement

Key Platform Features

Mathematical Precision:

  • Normal distribution for values that can be negative (temperature, GDP growth)
  • Lognormal distribution for prices (stocks, commodities, crypto - cannot be negative)
  • Winning range formula: Error Tolerance = σ × √(Time Until Expiry)

Time-Sensitive Rewards:

  • Prediction made 30 days early: ±$27.40 error tolerance
  • Prediction made 15 days early: ±$19.40 error tolerance
  • Prediction made 1 day early: ±$5.00 error tolerance
  • Balances information advantage vs. timing risk

Flexible Oracle System:

  • Markets store shared oracle configuration (JavaScript, API, static arguments)
  • Each expiry provides its own specific arguments (string[]) for customization
  • Example: Same oil price API, different date strings per expiry
  • Supports any HTTP-accessible data source

Multi-Tier Fee Structure:

  1. Market creation fee → Platform
  2. Expiry creation fee → Platform
  3. Tax fee → Market owner
  4. TSP owner fee → Platform
  5. Oracle requester reward → Resolver

Use Cases

Financial Forecasting:

  • Predict monthly oil prices
  • Forecast quarterly stock index values
  • Estimate cryptocurrency prices at specific dates

Economic Predictions:

  • Predict government auction results (COE)
  • Forecast inflation rates
  • Estimate GDP growth

Weather Predictions:

  • Predict temperature on specific dates
  • Forecast precipitation amounts
  • Estimate climate metrics

Custom Applications:

  • Any time-series data with periodic updates
  • Data accessible via APIs
  • Measurable, verifiable outcomes

Architecture Overview

Contract Hierarchy

┌─────────────────────────────────────────────────────────────────────────────────────┐ │ Canonicals Smart Contract Architecture │ └─────────────────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────┐ │ │ │ TSP_Manager │ │ (Central Factory) │ │ │ │ • Holds all user deposits │ │ • Creates markets (clone) │ │ • Manages allowances │ │ • Collects creation fees │ │ │ └───────┬────────────────────────┘ │ createTSPMarket() ┌──────────────────────────────────┐ │ │ │ TSP_Market │ │ (Market Container) │ │ │ │ • One per data source │ │ • Stores parameters (σ, α) | | • Stores oracle configuration: | | - JavaScript code (shared) | | - API endpoint (shared) | | - Static args (string[]) | | - Secrets (shared) | │ • Creates expiries │ │ • Accumulates tax fees │ │ │ └────────┬─────────────────────────┘ │ createTSPExpiry() ┌──────────────────────────────────────┐ │ │ │ TSP_Expiry │ │ (Prediction Market) │ │ │ │ • One per expiry date │ │ • Records predictions │ │ • Manages lifecycle stages │ │ • Stores expiryArgs (string[]) │ │ for oracle requests │ │ • Calculates winners │ │ • Distributes rewards │ │ │ └─────────┬────────────────────────────┘ │ resolve() / requestOracle() │ (gets config from market + │ provides own expiryArgs) ┌──────────────────────────────────────────┐ │ │ │ OracleData_Manager │ │ (Resolved Values Storage) │ │ │ │ • Stores resolved values per expiry │ │ • Access control (locked) │ │ │ └──────────┬───────────────────────────────┘ │ setResolvedValue() / getResolvedValue() ┌──────────────────────────────────────────┐ │ │ │ Chainlink Functions │ │ (External Oracle) │ │ │ │ • Fetches real-world data from APIs │ │ • Consensus across nodes │ │ • Returns verified value │ │ │ └──────────────────────────────────────────┘

Core Smart Contracts

1. TSP_Manager (Factory & Central Hub)

Role: The central hub and factory for the entire Canonicals platform.

What It Does

Factory Pattern
Creates new prediction markets on demand using the clone pattern. When someone wants to create a market, TSP_Manager clones a pre-deployed template contract (TSP_Market implementation) instead of deploying a full new contract. This saves ~90% of gas costs.

Centralized Treasury
Holds ALL user deposits in one secure location. Instead of scattering tokens across hundreds of individual market contracts, everything stays here. Think of it like a bank vault where users have accounts.

Two-Level Security
Users control their funds through a two-step approval process:

  1. Deposit: User sends tokens to TSP_Manager (full control retained)
  2. Approve: User grants specific expiry contracts permission to use a certain amount
  3. Prediction: Expiry contract can only use the approved amount, nothing more

This prevents any single market from draining a user’s entire balance.

Fee Collection
Charges and collects creation fees when new markets and expiries are created. This prevents spam and funds platform development.

Token Registry
Maintains a whitelist of supported ERC20 tokens. Only approved tokens can be used for predictions.

Implementation Upgrades
Controls which template contracts are used for new markets. Can upgrade templates for future markets without affecting existing ones.

Market Verification
Acts as a quality gate for markets:

  • New markets start as “Unverified” (anyone can create)
  • Market creators request verification
  • Platform admin reviews and approves/rejects
  • Verified markets get highlighted in the UI

Oracle Management
Connects to the OracleData_Manager and can upgrade it during development. Once locked for production, this connection becomes permanent for security.


2. TSP_Market (Market Container)

Role: A container for a specific time-series prediction market (e.g., “Brent Oil Prices”).

What It Does

Market Definition
Each TSP_Market represents ONE specific data source being predicted. For example:

  • Market A: Brent crude oil prices (lognormal distribution)
  • Market B: Singapore COE auction prices (normal distribution)
  • Market C: Bitcoin prices (lognormal distribution)

Expiry Management
Creates and manages multiple “expiries” - different future dates when predictions will be resolved. A single market might have:

  • March 15, 2026 expiry
  • April 1, 2026 expiry
  • May 15, 2026 expiry
  • etc.

Each expiry is its own prediction contest with the same underlying rules but different resolution dates.

Parameter Template
Stores the mathematical “rules of the game” that apply to ALL expiries in this market:

  • Distribution type: Normal or lognormal (affects winner calculation)
  • Volatility (σ): How much error tolerance to allow
  • Time cost & alpha: How time affects winning range
  • Min/max bounds: Valid range for predictions
  • Fee percentages: Tax fee, oracle reward, update penalties

All expiries inherit these parameters - they can’t have different rules.

Fee Collection
Accumulates tax fees from every prediction made across all expiries in the market. The market owner can withdraw these fees at any time. This creates incentive for creating high-quality, popular markets.

Oracle Configuration
Stores the complete Chainlink Functions setup directly in the market contract that will be used to fetch real-world data:

  • **JavaScript code: Shared across all expiries (contains API calls and data processing logic)
  • **Static arguments (args): String array parameters passed to the JavaScript
  • Secrets: API authentication keys (encrypted)
  • Scaling factors: Decimal precision conversion
  • Subscription ID and DON ID: Chainlink network configuration

This oracle configuration is stored once in the TSP_Market during creation and is immutable afterward.

Expiry-Specific Arguments (expiryArgs): While the JavaScript code and API endpoint are shared, each individual expiry can provide its own string array arguments (expiryArgs) when requesting oracle data. This allows the same JavaScript code to fetch different data for each expiry.

This architecture allows one market to serve multiple expiries with different time periods while reusing the same oracle configuration.

Verification Badge
Tracks whether the platform has verified this market:

  • Unverified: Just created, not reviewed
  • Pending: Owner requested review
  • Verified: Platform approved - gets official badge
  • Rejected: Platform rejected - flagged as problematic

Verification helps users identify trustworthy markets with reliable data sources.

Dated vs Non-Dated
Markets can be either:

  • Dated: Expiries must align to specific calendar dates (midnight UTC). Used for markets tied to daily data releases.
  • Non-Dated: Expiries can be any timestamp. Used for continuous data streams.

This ensures consistency with how the underlying data is published.


3. TSP_Expiry (Individual Prediction Market)

Role: Manages predictions for a specific expiry date/time.

What It Does

Single Prediction Contest
Each TSP_Expiry is one complete prediction competition. For example:

  • “Predict Brent oil price on March 15, 2026 at 00:00 UTC”
  • Users submit predictions during forecasting period
  • Oracle fetches actual price after expiry time
  • Winners share the prize pool

Lifecycle Management
Progresses through distinct stages automatically based on time:

NOT_STARTED (before startTime) ↓ time passes FORECASTING (accepting predictions) ↓ expiryTime arrives OBSERVING (waiting for data availability) ↓ observation period ends RESOLVING (oracle request window) ↓ value received OR timeout CLOSING (processing settlements) ↓ all claims processed CLOSED (finalized)

Alternative path if oracle fails:

RESOLVING ↓ timeout expires without resolution CANCELLING (processing refunds) ↓ all refunds processed CLOSED

Prediction Recording
Stores each user’s predictions with critical metadata:

  • Predicted value: The number they’re forecasting
  • Stake amount: How much they’re betting (after tax deduction)
  • Timestamp: When prediction was made (crucial for winner calculation)

Users can make UNLIMITED predictions per expiry. During the forecasting phase, users can:

  • Submit multiple new predictions with different values and stakes
  • Update existing predictions (value and/or stake amount)
  • Cancel predictions and withdraw stakes
  • All modifications must occur before the forecasting phase ends

Time-Based Winner Calculation
After resolution, determines winners using the mathematical formula:

For each prediction: timeUntilExpiry = expiryTime - predictionTimestamp errorTolerance = σ × √(timeUntilExpiry) actualError = |predictedValue - resolvedValue| If actualError ≤ errorTolerance → WINNER

Key insight: Earlier predictions get larger error tolerance because they’re made with less information. A prediction 30 days early might tolerate ±$5 error, while a prediction 1 day early might only tolerate ±$0.50.

Proportional Rewards
Winners don’t split equally - rewards are proportional to stake:

Prize Pool = Total Stakes - Tax Fees - TSP Owner Fee - Oracle Reward Individual Reward = (Your Stake / Total Winner Stakes) × Prize Pool

Someone who staked $100 gets twice the reward of someone who staked $50 (assuming both won).

Oracle Integration
Connects to Chainlink Functions to fetch real-world data:

  1. Anyone can trigger oracle request during resolution period
  2. Requester address is recorded (for reward distribution)
  3. TSP_Expiry retrieves oracle configuration from its parent TSP_Market
  4. TSP_Expiry provides its own expiry-specific arguments (expiryArgs as string[]) to customize the request
  5. Combined configuration sent to Chainlink (shared code + expiry-specific string arguments)
  6. Chainlink fetches data using the expiry’s specific arguments (e.g., date strings, identifiers)
  7. Resolved value stored in OracleData_Manager
  8. Winner calculation automatically triggered

Expiry-Specific Oracle Arguments (expiryArgs): While the JavaScript code and API endpoint come from the market, each expiry stores its own string array (expiryArgs) that gets passed to the oracle during resolution. This allows markets to serve multiple expiries with different parameters using the same oracle code.

Update Mechanism
Users can update predictions during forecasting period:

  • Decrease stake or withdraw all (cancel prediction)
  • Timestamp resets to update time (affects winner calculation)
  • Penalty fees based on update

Timeout Protection
If oracle fails to deliver within timeout period:

  • Any user can trigger cancellation
  • All stakes refunded (minus any fees already collected)
  • Prevents indefinite locked funds

Carry-Over System
After resolution, unclaimed amounts can be migrated to future expiries:

  • Only market owner can initiate
  • Requires waiting period (carry notice period)
  • Only transfers to expiries in same market
  • Creates continuity for active communities

Resolution Status
Tracks the outcome:

  • PENDING: Waiting for oracle data
  • RESOLVED: Value received, winners calculated
  • FAILED: Oracle request failed (rare)
  • CANCELLED: Timeout occurred, refunds available

Expiry lifecycle Stages

TSP_Expiry State Machine: [Contract Created] ┌──────────────┐ │ NOT_STARTED │ ──► Before startTime └──────┬───────┘ │ startTime arrives ┌──────────────┐ │ FORECASTING │ ──► Users submit & update predictions └──────┬───────┘ │ expiryTime arrives ┌──────────────┐ │ OBSERVING │ ──► Waiting for data availability └──────┬───────┘ │ observation period ends ┌──────────────┐ │ RESOLVING │ ──► Anyone can trigger oracle └──┬───────┬───┘ │ │ │ └─────► (timeout) ────┐ │ │ │ oracle success │ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ RESOLVED │ │ CANCELLING │ └──────┬───────┘ └──────┬───────┘ │ │ │ claims processed │ refunds processed ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ CLOSING │ │ CLOSING │ └──────┬───────┘ └──────┬───────┘ │ │ └─────────┬───────────────┘ ┌──────────────┐ │ CLOSED │ └──────────────┘

4. OracleData_Manager

Role: Storage for resolved values from oracle requests.

What It Does

Resolved Value Storage
After Chainlink fetches real-world data, it’s stored here with:

  • The actual value (as an integer)
  • Timestamp when the value was measured
  • Existence flag (to distinguish between “no data” and “value is zero”)

Immutability
Once a resolved value is stored for an expiry, it cannot be overwritten. This guarantees data integrity.

Version Management
Supports different data structure versions for future upgrades while maintaining backward compatibility with existing markets.


5. Oracle System - Getting Real-World Data

Role: Bridge between blockchain predictions and real-world data sources.

How It Works

The Problem
Blockchains can’t directly access external data (APIs, websites, databases). They need a trusted intermediary to fetch and verify real-world information.

The Solution: Chainlink Functions
A decentralized oracle network that:

  • Executes custom JavaScript code off-chain
  • Fetches data from any HTTP API
  • Reaches consensus across multiple independent nodes
  • Delivers verified results back to the blockchain

Request Flow

Step 1: User triggers resolution Step 2: TSP_Expiry retrieves oracle config from its parent TSP_Market - Gets JavaScript code (shared) - Gets static arguments - args (string[], shared) - Provides its own expiryArgs (string[], expiry-specific) Step 3: Request sent to Chainlink with COMBINED parameters: - JavaScript code (from market) - Static arguments (from market, string[]) or Expiry-specific arguments if exists (from this expiry, string[]) - Secrets (API keys, encrypted) Step 4: Chainlink nodes execute independently: - Each node runs the JavaScript with ALL arguments - Each fetches from the API using expiry-specific parameters - Results are aggregated - Consensus reached Step 5: Verified value returned to blockchain Step 6: Value stored in OracleData_Manager (just the result) Step 7: TSP_Expiry retrieves resolved value from OracleData_Manager

Oracle Configuration Components

JavaScript Code
Custom logic that runs on Chainlink nodes:

  • Constructs API request URL
  • Makes HTTP call
  • Parses JSON response
  • Extracts the specific value needed
  • Returns formatted result

This design allows market creators to build flexible, reusable oracle configurations while giving each expiry the ability to specify its own parameters as strings.

Important: Oracle Data Storage Architecture

To clarify the separation of concerns:

TSP_Market stores:

  • ✅ Oracle configuration (JavaScript code, API endpoints, secrets, static args as string[])
  • ✅ One configuration per market, used by all its expiries
  • ✅ Set during market creation, immutable afterward

TSP_Expiry stores:

  • ✅ Expiry-specific arguments (expiryArgs as string[])
  • ✅ Each expiry has different expiryArgs
  • ✅ Set during expiry creation, immutable afterward
  • ✅ expiryArgs are passed during oracle request, not stored in market

OracleData_Manager stores:

  • ✅ Resolved values only (the actual data fetched by Chainlink)
  • ✅ One value per expiry after resolution
  • ✅ No oracle configurations or arguments stored here

Why this architecture?

  • Each market has its own specific data source and API configuration
  • Expiries in the same market use the same oracle configuration (code + static args)
  • But each expiry can specify its own string parameters (dates, timestamps, identifiers)
  • Only the resolved values (results) need centralized storage
  • This keeps oracle configs close to their markets for better organization
  • Allows maximum flexibility: same JavaScript, different string parameters per expiry