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:
- Verifying Signatures: Ensuring data updates are signed by authorized Substrate nodes.
- Managing State: Storing the latest oracle data (prices) in UTXO datums.
- Distributing Rewards: Crediting nodes for their participation in data aggregation.
- 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 forcheck_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:
- Feed Update: Consumes the
AggStateandRewardAccountUTXOs. verifiesOracleMessagesignatures against theOracleSettings. - Governance: (Implied) Allows updating
OracleSettingsif authorized by the platform.
- Feed Update: Consumes the
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 buildTest
Run the unit tests defined in the validators/ folder (e.g., test_oracle_aggregate.ak).
aiken checkIntegration 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
FeedUpdateredeemer, 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