forked from elizaOS/eliza
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Create new plugin for Pyth Data integration - Add EventSource client for price feeds - Configure plugin in agent - Update dependencies and configuration files This plugin enables real-time price feed data from Pyth Network, including price updates, TWAPs, and publisher caps information.
- Loading branch information
Showing
39 changed files
with
6,718 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import eslintGlobalConfig from "../../eslint.config.mjs"; | ||
|
||
export default [ | ||
...eslintGlobalConfig, | ||
{ | ||
files: ["src/**/*.ts"], | ||
rules: { | ||
// Disable problematic rules | ||
"@typescript-eslint/no-unused-expressions": "off", | ||
"@typescript-eslint/no-explicit-any": "warn", | ||
"@typescript-eslint/no-unsafe-member-access": "off", | ||
"@typescript-eslint/no-unsafe-assignment": "off", | ||
"@typescript-eslint/no-unsafe-return": "off", | ||
"@typescript-eslint/no-unsafe-call": "off", | ||
}, | ||
}, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
{ | ||
"name": "@elizaos/plugin-pyth-data", | ||
"version": "1.0.0", | ||
"description": "Pyth Network data plugin for Eliza", | ||
"type": "module", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"scripts": { | ||
"build": "tsup --format esm --dts", | ||
"test": "vitest", | ||
"lint": "eslint src --ext .ts", | ||
"clean": "rimraf dist", | ||
"build:schemas": "openapi-zod-client ./schema.json --output src/types/zodSchemas.ts", | ||
"pull:schema": "curl -o schema.json -z schema.json https://hermes.pyth.network/docs/openapi.json", | ||
"prebuild": "pnpm run pull:schema && pnpm run build:schemas" | ||
}, | ||
"dependencies": { | ||
"@elizaos/core": "^0.1.7", | ||
"@pythnetwork/client": "^2.22.0", | ||
"@solana/web3.js": "^1.98.0", | ||
"@zodios/core": "^10.9.6", | ||
"ajv": "^8.12.0", | ||
"buffer": "6.0.3", | ||
"chalk": "^5.4.1", | ||
"cli-table3": "^0.6.5", | ||
"cross-fetch": "^4.0.0", | ||
"eventsource": "^3.0.2", | ||
"jstat": "^1.9.6", | ||
"ora": "^8.1.1", | ||
"zod": "^3.23.8" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^20.8.2", | ||
"@typescript-eslint/eslint-plugin": "^6.7.4", | ||
"@typescript-eslint/parser": "^6.7.4", | ||
"eslint": "^8.50.0", | ||
"openapi-zod-client": "^1.18.1", | ||
"rimraf": "^5.0.5", | ||
"tsup": "^8.0.0", | ||
"typescript": "^5.2.2", | ||
"vitest": "^1.0.0" | ||
}, | ||
"peerDependencies": { | ||
"@elizaos/core": "^0.1.7" | ||
}, | ||
"engines": { | ||
"node": ">=16.0.0" | ||
}, | ||
"keywords": [ | ||
"eliza", | ||
"plugin", | ||
"pyth", | ||
"oracle", | ||
"price-feed" | ||
], | ||
"author": "Eliza Team", | ||
"license": "MIT" | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
252 changes: 252 additions & 0 deletions
252
packages/plugin-pyth-data/src/actions/actionGetLatestPriceUpdates.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
import { Action, elizaLogger } from "@elizaos/core"; | ||
import { IAgentRuntime, Memory, State, HandlerCallback, Content, ActionExample } from "@elizaos/core"; | ||
import { HermesClient } from "../hermes/HermesClient"; | ||
import { DataError, ErrorSeverity, DataErrorCode } from "../error"; | ||
import { validatePythConfig, getNetworkConfig, getConfig } from "../environment"; | ||
import { ValidationSchemas } from "../types/types"; | ||
import { validateSchema } from "../utils/validation"; | ||
import { schemas } from "../types/zodSchemas"; | ||
import { z } from "zod"; | ||
|
||
// Get configuration for granular logging | ||
const config = getConfig(); | ||
const GRANULAR_LOG = config.PYTH_GRANULAR_LOG; | ||
|
||
// Enhanced logging helper | ||
const logGranular = (message: string, data?: unknown) => { | ||
if (GRANULAR_LOG) { | ||
elizaLogger.info(`[PriceUpdates] ${message}`, data); | ||
console.log(`[PriceUpdates] ${message}`, data ? JSON.stringify(data, null, 2) : ''); | ||
} | ||
}; | ||
|
||
interface GetLatestPriceUpdatesContent extends Content { | ||
text: string; | ||
priceIds: string[]; | ||
options?: { | ||
encoding?: "hex" | "base64"; | ||
parsed?: boolean; | ||
}; | ||
success?: boolean; | ||
data?: { | ||
updates?: Array<{ | ||
price_feed_id: string; | ||
price: number; | ||
conf: number; | ||
expo: number; | ||
publish_time: number; | ||
ema_price?: { | ||
price: number; | ||
conf: number; | ||
expo: number; | ||
}; | ||
}>; | ||
error?: string; | ||
}; | ||
} | ||
|
||
export const getLatestPriceUpdatesAction: Action = { | ||
name: "GET_LATEST_PRICE_UPDATES", | ||
similes: ["FETCH_LATEST_PRICES", "GET_CURRENT_PRICES", "CHECK_PRICE_FEED"], | ||
description: "Retrieve latest price updates from Pyth Network", | ||
examples: [[ | ||
{ | ||
user: "user", | ||
content: { | ||
text: "Get latest BTC/USD price updates", | ||
priceIds: ["ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"], | ||
options: { | ||
encoding: "base64", | ||
parsed: true | ||
} | ||
} as GetLatestPriceUpdatesContent | ||
} as ActionExample, | ||
{ | ||
user: "assistant", | ||
content: { | ||
text: "Here is the latest BTC/USD price", | ||
success: true, | ||
priceIds: ["ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"], | ||
data: { | ||
updates: [{ | ||
price_feed_id: "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", | ||
price: 42000000000, | ||
conf: 100000000, | ||
expo: -8, | ||
publish_time: 1641034800, | ||
ema_price: { | ||
price: 41950000000, | ||
conf: 95000000, | ||
expo: -8 | ||
} | ||
}] | ||
} | ||
} as GetLatestPriceUpdatesContent | ||
} as ActionExample | ||
]], | ||
|
||
validate: async (_runtime: IAgentRuntime, message: Memory): Promise<boolean> => { | ||
logGranular("Validating GET_LATEST_PRICE_UPDATES action", { | ||
content: message.content | ||
}); | ||
|
||
try { | ||
const content = message.content as GetLatestPriceUpdatesContent; | ||
|
||
// Validate against schema | ||
try { | ||
await validateSchema(content, ValidationSchemas.GET_LATEST_PRICE); | ||
logGranular("Schema validation passed"); | ||
} catch (error) { | ||
logGranular("Schema validation failed", { error }); | ||
if (error instanceof DataError) { | ||
elizaLogger.error("Schema validation failed", { | ||
errors: error.details?.errors | ||
}); | ||
throw error; | ||
} | ||
throw new DataError( | ||
DataErrorCode.VALIDATION_FAILED, | ||
"Schema validation failed", | ||
ErrorSeverity.HIGH, | ||
{ error } | ||
); | ||
} | ||
|
||
// Validate priceIds array | ||
if (!content.priceIds || !Array.isArray(content.priceIds)) { | ||
throw new DataError( | ||
DataErrorCode.VALIDATION_FAILED, | ||
"priceIds must be an array of strings", | ||
ErrorSeverity.HIGH | ||
); | ||
} | ||
|
||
if (content.priceIds.length === 0) { | ||
throw new DataError( | ||
DataErrorCode.VALIDATION_FAILED, | ||
"priceIds array cannot be empty", | ||
ErrorSeverity.HIGH | ||
); | ||
} | ||
|
||
// Validate each price ID is a valid hex string | ||
content.priceIds.forEach((id, index) => { | ||
if (!/^[0-9a-fA-F]+$/.test(id)) { | ||
throw new DataError( | ||
DataErrorCode.VALIDATION_FAILED, | ||
`Invalid price ID at index ${index}: ${id}`, | ||
ErrorSeverity.HIGH | ||
); | ||
} | ||
}); | ||
|
||
return true; | ||
} catch (error) { | ||
logGranular("Validation failed", { error }); | ||
elizaLogger.error("Validation failed for GET_LATEST_PRICE_UPDATES", { | ||
error: error instanceof Error ? error.message : String(error) | ||
}); | ||
return false; | ||
} | ||
}, | ||
|
||
handler: async ( | ||
runtime: IAgentRuntime, | ||
message: Memory, | ||
_state?: State, | ||
_options: { [key: string]: unknown } = {}, | ||
callback?: HandlerCallback | ||
): Promise<boolean> => { | ||
logGranular("Executing GET_LATEST_PRICE_UPDATES action"); | ||
|
||
try { | ||
const messageContent = message.content as GetLatestPriceUpdatesContent; | ||
const { priceIds, options = {} } = messageContent; | ||
|
||
// Get Pyth configuration | ||
const config = await validatePythConfig(runtime); | ||
if (!config) { | ||
throw new DataError( | ||
DataErrorCode.VALIDATION_FAILED, | ||
"Invalid Pyth configuration", | ||
ErrorSeverity.HIGH | ||
); | ||
} | ||
|
||
// Get network configuration | ||
const networkConfig = getNetworkConfig(config.PYTH_NETWORK_ENV); | ||
|
||
// Initialize Hermes client | ||
const hermesClient = new HermesClient(networkConfig.hermes); | ||
|
||
logGranular("Initialized HermesClient", { | ||
endpoint: networkConfig.hermes | ||
}); | ||
|
||
try { | ||
// Get latest price updates | ||
const updates = await hermesClient.getLatestPriceUpdates(priceIds, { | ||
parsed: true, | ||
encoding: options?.encoding as "hex" | "base64" | undefined | ||
}); | ||
|
||
logGranular("Successfully retrieved price updates", { | ||
count: updates.parsed?.length || 0 | ||
}); | ||
|
||
// Create callback content | ||
const callbackContent: GetLatestPriceUpdatesContent = { | ||
text: `Retrieved ${updates.parsed?.length || 0} price updates`, | ||
success: true, | ||
priceIds, | ||
data: { | ||
updates: (updates as z.infer<typeof schemas.PriceUpdate>).parsed?.map((update) => ({ | ||
price_feed_id: update.id, | ||
price: Number(update.price.price), | ||
conf: Number(update.price.conf), | ||
expo: Number(update.price.expo), | ||
publish_time: update.price.publish_time, | ||
ema_price: update.ema_price ? { | ||
price: Number(update.ema_price.price), | ||
conf: Number(update.ema_price.conf), | ||
expo: Number(update.ema_price.expo) | ||
} : undefined | ||
})) || [] | ||
} | ||
}; | ||
|
||
// Call callback with results | ||
if (callback) { | ||
await callback(callbackContent); | ||
} | ||
|
||
return true; | ||
} catch (error) { | ||
logGranular("Failed to process price updates request", { error }); | ||
if (error instanceof DataError) { | ||
throw error; | ||
} | ||
throw new DataError( | ||
DataErrorCode.VALIDATION_FAILED, | ||
"Failed to process price updates request", | ||
ErrorSeverity.HIGH, | ||
{ originalError: error } | ||
); | ||
} | ||
} catch (error) { | ||
logGranular("Failed to get latest price updates", { error }); | ||
if (error instanceof DataError) { | ||
throw error; | ||
} | ||
throw new DataError( | ||
DataErrorCode.NETWORK_ERROR, | ||
"Failed to get latest price updates", | ||
ErrorSeverity.HIGH, | ||
{ originalError: error } | ||
); | ||
} | ||
} | ||
}; | ||
|
||
export default getLatestPriceUpdatesAction; |
Oops, something went wrong.