Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ LLAMA_API_BASE_URL=http://localhost:8000

# Blockchain
ETH_RPC_URL=https://mainnet.infura.io/v3/your-project-id
BSC_RPC_URL=https://bsc-dataseed.binance.org
CHAIN_ID=1

# Redis (for caching and WebSocket)
Expand Down
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { Wallet } from "./core/auth/entities/wallet.entity";
// Oracle entities
import { SignedPayload } from "./blockchain/oracle/entities/signed-payload.entity";
import { SubmissionNonce } from "./blockchain/oracle/entities/submission-nonce.entity";
import { PriceRecord } from "./blockchain/oracle/entities/price-record.entity";

// Audit entities
import { AgentEvent } from "./infrastructure/audit/entities/agent-event.entity";
Expand Down Expand Up @@ -145,6 +146,7 @@ import { ProfilingMiddleware } from "./profiling/profiling.middleware";
Wallet,
SignedPayload,
SubmissionNonce,
PriceRecord,
AgentEvent,
ComputeResult,
ProvenanceRecord,
Expand Down
64 changes: 64 additions & 0 deletions src/blockchain/oracle/dto/price-feed.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { IsEnum, IsString, IsOptional, IsInt, Min, Max } from "class-validator";
import { SupportedChain, PriceSource } from "../entities/price-record.entity";

export class GetPriceDto {
@ApiProperty({ example: "ETH", description: "Asset symbol" })
@IsString()
asset: string;

@ApiProperty({ enum: SupportedChain, example: SupportedChain.ETHEREUM })
@IsEnum(SupportedChain)
chain: SupportedChain;
}

export class GetHistoricalPricesDto {
@ApiProperty({ example: "ETH" })
@IsString()
asset: string;

@ApiProperty({ enum: SupportedChain })
@IsEnum(SupportedChain)
chain: SupportedChain;

@ApiPropertyOptional({ example: 100, default: 100, minimum: 1, maximum: 1000 })
@IsOptional()
@IsInt()
@Min(1)
@Max(1000)
limit?: number = 100;
}

export class SourcePriceDto {
@ApiPropertyOptional({ example: 2000.5 })
chainlink?: number;

@ApiPropertyOptional({ example: 2001.0 })
band?: number;

@ApiPropertyOptional({ example: 1999.8 })
uniswap_twap?: number;
}

export class PriceResponseDto {
@ApiProperty({ example: "ETH" })
asset: string;

@ApiProperty({ enum: SupportedChain })
chain: SupportedChain;

@ApiProperty({ example: 2000.43 })
price: number;

@ApiProperty({ type: SourcePriceDto })
sourcePrices: Record<PriceSource, number>;

@ApiProperty({ example: false })
deviationAlert: boolean;

@ApiProperty({ example: 0.0612 })
maxDeviationPercent: number;

@ApiProperty()
timestamp: Date;
}
56 changes: 56 additions & 0 deletions src/blockchain/oracle/entities/price-record.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
Index,
} from "typeorm";

export enum SupportedChain {
ETHEREUM = "ethereum",
BSC = "bsc",
POLYGON = "polygon",
ARBITRUM = "arbitrum",
OPTIMISM = "optimism",
AVALANCHE = "avalanche",
}

export enum PriceSource {
CHAINLINK = "chainlink",
BAND = "band",
UNISWAP_TWAP = "uniswap_twap",
}

@Entity("price_records")
@Index(["asset", "chain", "createdAt"])
@Index(["asset", "createdAt"])
export class PriceRecord {
@PrimaryGeneratedColumn("uuid")
id: string;

/** Asset symbol, e.g. "ETH", "BTC" */
@Column({ type: "varchar", length: 20 })
asset: string;

@Column({ type: "enum", enum: SupportedChain })
chain: SupportedChain;

/** Canonical median price in USD */
@Column({ type: "decimal", precision: 30, scale: 8 })
price: number;

/** Raw prices per source, e.g. { chainlink: 2000.5, band: 2001.0, uniswap_twap: 1999.8 } */
@Column({ type: "jsonb" })
sourcePrices: Record<PriceSource, number>;

/** Whether a >5% deviation was detected between sources */
@Column({ type: "boolean", default: false })
deviationAlert: boolean;

/** Max % deviation observed between sources */
@Column({ type: "decimal", precision: 8, scale: 4, default: 0 })
maxDeviationPercent: number;

@CreateDateColumn()
createdAt: Date;
}
9 changes: 7 additions & 2 deletions src/blockchain/oracle/oracle.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import { NonceManagementService } from "./services/nonce-management.service";
import { SubmitterService } from "./services/submitter.service";
import { SubmissionBatchService } from "./services/submission-batch.service";
import { SubmissionVerifierService } from "./submission-verifier.service";
import { PriceFeedService } from "./services/price-feed.service";
import { PriceFeedController } from "./price-feed.controller";
import { SignedPayload } from "./entities/signed-payload.entity";
import { SubmissionNonce } from "./entities/submission-nonce.entity";
import { PriceRecord } from "./entities/price-record.entity";
import { AuditModule } from "src/infrastructure/audit/audit.module";

/**
Expand All @@ -18,18 +21,19 @@ import { AuditModule } from "src/infrastructure/audit/audit.module";
*/
@Module({
imports: [
TypeOrmModule.forFeature([SignedPayload, SubmissionNonce]),
TypeOrmModule.forFeature([SignedPayload, SubmissionNonce, PriceRecord]),
ConfigModule,
AuditModule,
],
controllers: [OracleController],
controllers: [OracleController, PriceFeedController],
providers: [
OracleService,
PayloadSigningService,
NonceManagementService,
SubmitterService,
SubmissionBatchService,
SubmissionVerifierService,
PriceFeedService,
],
exports: [
OracleService,
Expand All @@ -38,6 +42,7 @@ import { AuditModule } from "src/infrastructure/audit/audit.module";
SubmitterService,
SubmissionBatchService,
SubmissionVerifierService,
PriceFeedService,
],
})
export class OracleModule {}
Expand Down
39 changes: 39 additions & 0 deletions src/blockchain/oracle/price-feed.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Controller, Get, Param, Query } from "@nestjs/common";
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { PriceFeedService } from "./services/price-feed.service";
import {
GetHistoricalPricesDto,
PriceResponseDto,
} from "./dto/price-feed.dto";
import { SupportedChain } from "./entities/price-record.entity";

@ApiTags("Price Feed")
@Controller("price-feed")
export class PriceFeedController {
constructor(private readonly priceFeedService: PriceFeedService) {}

@Get(":chain/:asset")
@ApiOperation({ summary: "Get current aggregated price for an asset on a chain" })
@ApiResponse({ status: 200, type: PriceResponseDto })
async getCurrentPrice(
@Param("chain") chain: SupportedChain,
@Param("asset") asset: string,
): Promise<PriceResponseDto> {
return this.priceFeedService.getCurrentPrice(asset, chain);
}

@Get(":chain/:asset/history")
@ApiOperation({ summary: "Get historical prices for an asset on a chain" })
@ApiResponse({ status: 200, type: [PriceResponseDto] })
async getHistoricalPrices(
@Param("chain") chain: SupportedChain,
@Param("asset") asset: string,
@Query() query: GetHistoricalPricesDto,
): Promise<PriceResponseDto[]> {
return this.priceFeedService.getHistoricalPrices(
asset,
chain,
query.limit,
);
}
}
Loading
Loading