Substrate-Cardano Oracle (Aiken)

The Substrate-Cardano Oracle repository contains the On-Chain Verification Layer of the Charli3 bridge. It is written in Aiken and compiles to Plutus V2/V3 scripts.

Project Overview

The contracts in this repo are responsible for:

  1. Verifying Signatures: Ensuring data updates are signed by authorized Substrate nodes.
  2. Managing State: Storing the latest oracle data (prices) in UTXO datums.
  3. Distributing Rewards: Crediting nodes for their participation in data aggregation.
  4. Governance: Allowing the platform (via Auth NFT) to update settings like the authorized node list.

Directory Structure

  • validators/: Contains the entry points for the smart contracts.
    • oracle.ak: The main file containing both the Minting Policy (oracle_nfts) and Spending Validator (oracle_manager).
  • lib/oracle/:
    • datum.ak: Core data types (OracleMessage, PriceData, OracleSettings).
    • multisig.ak: Logic for check_collective_message (Ed25519 signature verification).
    • checks.ak: Helper functions for UTXO validation logic.
  • specs/: CDDL specifications for datums.

Core Concepts

1. The Oracle Message

Data is bridged from Substrate via an OracleMessage (defined in lib/oracle/datum.ak).

pub type OracleMessage {
  channel_id: PolicyId,                  // Identifies the deployment
  prices_and_age: List<OraclePrice>,     // Payload
  creation_time: PosixTime,              // Timestamp
  rewards: List<(VerificationKey, Int)>, // Which nodes signed this update
}

The Hash of this message is what gets signed by the Substrate nodes.

2. Validators

oracle_nfts (Minting Policy)

  • Purpose: Controls the minting/burning of the 3 key state tokens (C3CS, C3AS, C3RA).
  • Rule: Can only mint if the Platform Auth NFT is present in the transaction inputs. This ensures only the Charli3 platform can deploy.

oracle_manager (Spending Validator)

  • Purpose: Controls how the oracle UTXOs can be spent/updated.
  • Paths:
    1. Feed Update: Consumes the AggState and RewardAccount UTXOs. verifies OracleMessage signatures against the OracleSettings.
    2. Governance: (Implied) Allows updating OracleSettings if authorized by the platform.

3. Tokens & UTXOs

A deployed oracle consists of three distinct UTXOs, identified by unique NFTs (Policy ID is derived from the oracle_nfts script):

  • CoreSettings (C3CS): Holds the list of authorized nodes and fee settings. (Reference Input)
  • AggState (C3AS): Holds the actual price data. (State UTXO)
  • RewardAccount (C3RA): Holds the accumulated rewards for nodes. (State UTXO)

Development Commands

Prerequisites: Aiken

Build

Compile the project to Plutus Core (generates plutus.json).

aiken build

Test

Run the unit tests defined in the validators/ folder (e.g., test_oracle_aggregate.ak).

aiken check

Integration Note

This Aiken code is consumed by the Off-Chain Bridge (TypeScript). The plutus.json generated here is loaded by the TS bridge to build transactions.

  • Deployment: The TS bridge uses the compiled code to deploy the initial UTXOs.
  • Feed Updates: The TS bridge constructs transactions that call the FeedUpdate redeemer, providing the signatures collected from Substrate.

Oracle Datum Standard v2

Below is the CDDL specification for the Oracle Datum stored on-chain. This datum contains shared metadata, a list of prices (generic data), and optional extended provider data.

; Oracle Datum CDDL Specification
; The oracle datum contains shared data (common to all pairs), 
; generic data (list of price data, one per currency pair),
; and extended data (list of extended info, one per currency pair)

oracle_datum = [
  shared_data: shared_data / null,
  generic_data: [* price_data],     ; List of price data (ordered list, one per currency pair)
  extended_data: [* extended_data] / null  ; List of extended data (one per currency pair)
]

; Shared data: single list with common fields for all currency pairs
shared_data = [
  channel_id,                       ; 0: Channel ID
  creation_time,                    ; 1: Creation timestamp
  expiration_time,                  ; 2: Expiration timestamp
  * any                             ; Additional shared fields can be added
]

; Price data: dynamic fields for one specific currency pair
; Static metadata (precision, asset names, etc.) stored in CIP-68 Metadata UTxOs
price_data = [
  price,                            ; 0: Exchange rate (quote per base)
  age,                              ; 1: Age of the price (blocks ago)
  * any                             ; Additional price fields can be added
]

; Extended data: provider-specific fields for one currency pair
extended_data = [
  oracle_provider_id / null,        ; 0: Oracle provider identifier
  * any                             ; Additional extended fields can be added
]

; Type definitions
timestamp = uint                  ; Unix timestamp
channel_id = bytes                ; Channel identifier (e.g., 5-byte hex)

price = uint                      ; Exchange rate value
age = uint                        ; Age in blocks
oracle_provider_id = uint         ; Provider identifier