openoracle-node-sdk
Version:
OpenOracle Node.js SDK - Intelligent Oracle Routing with Multiple LLM Providers
488 lines (434 loc) • 14.6 kB
text/typescript
/**
* JSON Schema Validation for Oracle Contracts
*
* This module ensures that JSON responses from LLM providers match exactly
* with the smart contract structures defined in:
* /Users/samsavage/flow-nft/contracts/contracts/interfaces/IOracle.sol
*
* All interfaces here MUST match the Solidity structs exactly.
*/
// ============ Contract Enums (matching IOracle.sol) ============
export enum OracleProvider {
CHAINLINK = 'CHAINLINK', // 0
PYTH = 'PYTH', // 1
UMA = 'UMA', // 2
API3 = 'API3', // 3
CUSTOM = 'CUSTOM' // 4
}
export enum MarketStatus {
ACTIVE = 'ACTIVE', // 0
RESOLVED = 'RESOLVED', // 1
CANCELLED = 'CANCELLED', // 2
DISPUTED = 'DISPUTED' // 3
}
// ============ Core Contract Structures ============
/**
* Matches IOracle.OracleData struct exactly
*/
export interface OracleData {
value: bigint // uint256 value
timestamp: bigint // uint256 timestamp
confidence: number // uint256 confidence (scaled by 1e4, max 10000)
dataId: string // bytes32 dataId (hex string)
source: string // string source
}
/**
* Matches IOracle.PriceData struct exactly
*/
export interface PriceData {
price: bigint // uint256 price
timestamp: bigint // uint256 timestamp
decimals: number // uint8 decimals (0-18)
confidence: number // uint256 confidence (scaled by 1e4)
feedId: string // bytes32 feedId (hex string)
}
/**
* Matches IOracle.ResolutionData struct exactly
*/
export interface ResolutionData {
result: bigint // uint256 result
resolved: boolean // bool resolved
timestamp: bigint // uint256 timestamp
proof: string // bytes proof (hex string)
metadata: string // string metadata
}
// ============ Router Structures ============
/**
* Matches IOracleRouter.RouteConfig struct exactly
*/
export interface RouteConfig {
provider: OracleProvider // OracleProvider provider
oracleAddress: string // address oracleAddress
priority: bigint // uint256 priority
maxCost: bigint // uint256 maxCost
isActive: boolean // bool isActive
}
/**
* Matches IOracleRouter.RouteResult struct exactly
*/
export interface RouteResult {
success: boolean // bool success
selectedProvider: OracleProvider // OracleProvider selectedProvider
oracleAddress: string // address oracleAddress
estimatedCost: bigint // uint256 estimatedCost
reason: string // string reason
}
// ============ Prediction Market Structures ============
/**
* Matches IPredictionMarket.Market struct exactly
*/
export interface Market {
marketId: bigint // uint256 marketId
question: string // string question
creator: string // address creator
endTime: bigint // uint256 endTime
status: MarketStatus // MarketStatus status
totalPool: bigint // uint256 totalPool
paymentToken: string // address paymentToken
oracleDataType: string // bytes32 oracleDataType
assignedOracle: string // address assignedOracle
oracleParams: string // bytes oracleParams
}
/**
* Matches IPredictionMarket.Position struct exactly
*/
export interface Position {
amount: bigint // uint256 amount
outcome: number // uint8 outcome (0-255)
timestamp: bigint // uint256 timestamp
multiplier: bigint // uint256 multiplier
}
// ============ LLM Response Schemas ============
/**
* JSON schema for LLM responses when selecting oracle providers
*/
export interface OracleRoutingResponse {
selectedOracle: OracleProvider
reasoning: string // min 50 chars
confidence: number // 0-1
estimatedCost?: number // USD
estimatedTime?: number // seconds
fallbackOptions: OracleProvider[]
}
/**
* JSON schema for LLM responses when resolving prediction markets
*/
export interface PredictionMarketResolution {
winningOutcome: number // 0-255
resolutionValue?: bigint // actual value that determined outcome
confidence: number // 0-1
dataSources: string[] // min 1 item
reasoning: string // min 100 chars
timestamp: bigint
proofHash?: string
}
/**
* JSON schema for validating oracle data quality
*/
export interface OracleDataValidation {
isValid: boolean
confidenceScore: number // 0-1
anomalyDetected: boolean
dataFreshness: number // age in seconds
sourceReliability: number // 0-1
issues: string[]
recommendations: string[]
}
// ============ Validation Functions ============
export function isValidEthereumAddress(address: string): boolean {
return /^0x[a-fA-F0-9]{40}$/.test(address)
}
export function isValidBytes32(value: string): boolean {
return /^0x[a-fA-F0-9]{64}$/.test(value)
}
export function isValidHexBytes(value: string): boolean {
return /^0x[a-fA-F0-9]*$/.test(value)
}
export function validateOracleData(data: any): data is OracleData {
return (
typeof data === 'object' &&
typeof data.value === 'bigint' &&
typeof data.timestamp === 'bigint' &&
typeof data.confidence === 'number' &&
data.confidence >= 0 && data.confidence <= 10000 &&
typeof data.dataId === 'string' &&
isValidBytes32(data.dataId) &&
typeof data.source === 'string'
)
}
export function validatePriceData(data: any): data is PriceData {
return (
typeof data === 'object' &&
typeof data.price === 'bigint' &&
typeof data.timestamp === 'bigint' &&
typeof data.decimals === 'number' &&
data.decimals >= 0 && data.decimals <= 18 &&
typeof data.confidence === 'number' &&
data.confidence >= 0 && data.confidence <= 10000 &&
typeof data.feedId === 'string' &&
isValidBytes32(data.feedId)
)
}
export function validateRouteResult(data: any): data is RouteResult {
return (
typeof data === 'object' &&
typeof data.success === 'boolean' &&
Object.values(OracleProvider).includes(data.selectedProvider) &&
typeof data.oracleAddress === 'string' &&
(data.oracleAddress === '0x0000000000000000000000000000000000000000' ||
isValidEthereumAddress(data.oracleAddress)) &&
typeof data.estimatedCost === 'bigint' &&
typeof data.reason === 'string'
)
}
export function validateMarket(data: any): data is Market {
return (
typeof data === 'object' &&
typeof data.marketId === 'bigint' &&
typeof data.question === 'string' &&
typeof data.creator === 'string' &&
isValidEthereumAddress(data.creator) &&
typeof data.endTime === 'bigint' &&
Object.values(MarketStatus).includes(data.status) &&
typeof data.totalPool === 'bigint' &&
typeof data.paymentToken === 'string' &&
isValidEthereumAddress(data.paymentToken) &&
typeof data.oracleDataType === 'string' &&
isValidBytes32(data.oracleDataType) &&
typeof data.assignedOracle === 'string' &&
isValidEthereumAddress(data.assignedOracle) &&
typeof data.oracleParams === 'string' &&
isValidHexBytes(data.oracleParams)
)
}
// ============ JSON Schema Generation for LLMs ============
export interface JSONSchema {
type: string
properties: Record<string, any>
required: string[]
}
export function generateJSONSchemaForLLM<T>(schemaName: string): JSONSchema {
const schemas: Record<string, JSONSchema> = {
OracleRoutingResponse: {
type: 'object',
properties: {
selectedOracle: {
type: 'string',
enum: Object.values(OracleProvider),
description: 'The selected oracle provider'
},
reasoning: {
type: 'string',
minLength: 50,
description: 'Detailed reasoning for selection'
},
confidence: {
type: 'number',
minimum: 0,
maximum: 1,
description: 'Confidence in selection (0-1)'
},
estimatedCost: {
type: 'number',
description: 'Estimated cost in USD'
},
estimatedTime: {
type: 'number',
description: 'Estimated response time in seconds'
},
fallbackOptions: {
type: 'array',
items: {
type: 'string',
enum: Object.values(OracleProvider)
},
description: 'Alternative providers'
}
},
required: ['selectedOracle', 'reasoning', 'confidence', 'fallbackOptions']
},
PredictionMarketResolution: {
type: 'object',
properties: {
winningOutcome: {
type: 'number',
minimum: 0,
maximum: 255,
description: 'Winning outcome index'
},
resolutionValue: {
type: 'string', // bigint as string for JSON
description: 'Actual value that determined outcome'
},
confidence: {
type: 'number',
minimum: 0,
maximum: 1,
description: 'Confidence in resolution'
},
dataSources: {
type: 'array',
items: { type: 'string' },
minItems: 1,
description: 'Data sources used'
},
reasoning: {
type: 'string',
minLength: 100,
description: 'Detailed reasoning for resolution'
},
timestamp: {
type: 'string', // bigint as string for JSON
description: 'Resolution timestamp'
},
proofHash: {
type: 'string',
description: 'Hash of proof data'
}
},
required: ['winningOutcome', 'confidence', 'dataSources', 'reasoning', 'timestamp']
},
OracleDataValidation: {
type: 'object',
properties: {
isValid: {
type: 'boolean',
description: 'Whether data passes validation'
},
confidenceScore: {
type: 'number',
minimum: 0,
maximum: 1,
description: 'Data quality confidence'
},
anomalyDetected: {
type: 'boolean',
description: 'Whether anomalies were found'
},
dataFreshness: {
type: 'number',
description: 'Age of data in seconds'
},
sourceReliability: {
type: 'number',
minimum: 0,
maximum: 1,
description: 'Source reliability score'
},
issues: {
type: 'array',
items: { type: 'string' },
description: 'List of issues found'
},
recommendations: {
type: 'array',
items: { type: 'string' },
description: 'Recommendations for improvement'
}
},
required: ['isValid', 'confidenceScore', 'anomalyDetected', 'dataFreshness', 'sourceReliability', 'issues', 'recommendations']
}
}
const schema = schemas[schemaName]
if (!schema) {
throw new Error(`Schema ${schemaName} not found`)
}
return schema
}
export function createLLMPromptWithSchema(
taskDescription: string,
schemaName: string,
examples?: any[]
): string {
const schema = generateJSONSchemaForLLM(schemaName)
let prompt = `Task: ${taskDescription}
You must respond with valid JSON that exactly matches this schema:
${JSON.stringify(schema, null, 2)}
Requirements:
- All fields marked as required must be present
- Follow the exact field names and types specified
- Ensure enum values match exactly (case-sensitive)
- Addresses must be valid Ethereum addresses (0x + 40 hex chars)
- Bytes32 values must be 0x + 64 hex chars
- Confidence scores must be between 0 and 1
- Use string format for bigint values (e.g., "12345" not 12345)
`
if (examples) {
prompt += 'Examples of valid responses:\n\n'
examples.forEach((example, i) => {
prompt += `Example ${i + 1}:\n${JSON.stringify(example, null, 2)}\n\n`
})
}
prompt += 'Respond with valid JSON only. Do not include any additional text or explanation.'
return prompt
}
// ============ Contract Compatibility ============
export function validateContractCompatibility(
data: any,
contractStruct: string
): boolean {
switch (contractStruct) {
case 'OracleData':
return validateOracleData(data)
case 'PriceData':
return validatePriceData(data)
case 'RouteResult':
return validateRouteResult(data)
case 'Market':
return validateMarket(data)
default:
return false
}
}
// ============ Examples ============
export const EXAMPLE_ORACLE_DATA: OracleData = {
value: BigInt('4250000000000000000000'), // $4250.00 with 18 decimals
timestamp: BigInt('1703097600'),
confidence: 9500, // 95.00%
dataId: '0x4254432d5553440000000000000000000000000000000000000000000000000', // BTC-USD
source: 'chainlink_aggregator'
}
export const EXAMPLE_ROUTE_RESULT: RouteResult = {
success: true,
selectedProvider: OracleProvider.CHAINLINK,
oracleAddress: '0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419',
estimatedCost: BigInt('500000000000000000'), // 0.5 ETH
reason: 'Best price-reliability ratio'
}
export const EXAMPLE_ORACLE_ROUTING_RESPONSE: OracleRoutingResponse = {
selectedOracle: OracleProvider.CHAINLINK,
reasoning: 'Chainlink is optimal for BTC price data due to its robust aggregation of multiple high-quality price feeds with proven reliability and sub-minute updates',
confidence: 0.92,
estimatedCost: 0.25,
estimatedTime: 30,
fallbackOptions: [OracleProvider.PYTH, OracleProvider.API3]
}
export const EXAMPLE_PREDICTION_RESOLUTION: PredictionMarketResolution = {
winningOutcome: 0,
resolutionValue: BigInt('105000'),
confidence: 0.98,
dataSources: ['coinbase', 'binance', 'kraken_spot'],
reasoning: 'Bitcoin price reached $105,000 on December 15, 2024, exceeding the $100k threshold. This was confirmed across multiple major exchanges with consistent pricing.',
timestamp: BigInt('1734220800'),
proofHash: '0xa7b4c9d3e2f1a8b5c6d7e8f9a1b2c3d4e5f6a7b8c9d0'
}
// ============ Schema Registry ============
export const CONTRACT_SCHEMAS = {
// Core Oracle Interfaces
OracleData: 'OracleData',
PriceData: 'PriceData',
ResolutionData: 'ResolutionData',
// Router Schemas
RouteConfig: 'RouteConfig',
RouteResult: 'RouteResult',
// Prediction Market Schemas
Market: 'Market',
Position: 'Position',
// LLM Response Schemas
OracleRoutingResponse: 'OracleRoutingResponse',
PredictionMarketResolution: 'PredictionMarketResolution',
OracleDataValidation: 'OracleDataValidation'
} as const
export function listAvailableSchemas(): string[] {
return Object.keys(CONTRACT_SCHEMAS)
}