diff --git a/README.md b/README.md index 2f7b8c70..ac5e0f17 100644 --- a/README.md +++ b/README.md @@ -531,6 +531,16 @@ const inference = await agent.getInferenceByTopicId(42); console.log("Allora inference for topic 42:", inference); ``` +### Simulate a Switchboard feed + +Simulate a given Switchboard feed. Find or create feeds [here](https://ondemand.switchboard.xyz/solana/mainnet). + +```typescript +const value = await agent.simulateSwitchboardFeed( + "9wcBMATS8bGLQ2UcRuYjsRAD7TPqB1CMhqfueBx78Uj2", // TRUMP/USD + "http://crossbar.switchboard.xyz");; +console.log("Simulation resulted in the following value:", value); + ### Cross-Chain Swap ```typescript @@ -544,6 +554,7 @@ const signature = await agent.swap( toToken: "0x0000000000000000000000000000000000000000", dstAddr: "0xc2d3024d64f27d85e05c40056674Fd18772dd922", ); + ``` ## Examples diff --git a/package.json b/package.json index c7569d6a..371fc9bf 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@solana/spl-token": "^0.4.9", "@solana/web3.js": "^1.98.0", "@sqds/multisig": "^2.1.3", + "@switchboard-xyz/common": "^2.5.15", "@tensor-oss/tensorswap-sdk": "^4.5.0", "@tiplink/api": "^0.3.1", "@voltr/vault-sdk": "^0.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 51e342d9..fda8d1e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,6 +110,9 @@ importers: '@sqds/multisig': specifier: ^2.1.3 version: 2.1.3(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@switchboard-xyz/common': + specifier: ^2.5.15 + version: 2.5.15(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@tensor-oss/tensorswap-sdk': specifier: ^4.5.0 version: 4.5.0(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) @@ -1507,8 +1510,8 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - '@switchboard-xyz/common@2.5.12': - resolution: {integrity: sha512-D10cYwo0nMk8Y/jiz8hhyN0yhDIohdpXURVRF7E+co8qQ5a4kqs38yIKQFLkCyY4xlKHdTFNl91wWm2B8ZKCCg==} + '@switchboard-xyz/common@2.5.15': + resolution: {integrity: sha512-W4ub5Na0pf+OIBp8a8JhHzDIqleNI8iClNE5SeQeAMeElzT99fzfEXlY0gVXA7tXOrsLer9I383dCku0H7TDEw==} engines: {node: '>=12'} '@switchboard-xyz/on-demand@1.2.42': @@ -7835,7 +7838,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@switchboard-xyz/common@2.5.12(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@switchboard-xyz/common@2.5.15(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@solana/web3.js': 1.98.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) axios: 1.7.9 @@ -7860,7 +7863,7 @@ snapshots: '@coral-xyz/anchor-30': '@coral-xyz/anchor@0.30.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)' '@solana/web3.js': 1.98.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@solworks/soltoolkit-sdk': 0.0.23(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(utf-8-validate@5.0.10) - '@switchboard-xyz/common': 2.5.12(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@switchboard-xyz/common': 2.5.15(bufferutil@4.0.9)(utf-8-validate@5.0.10) axios: 1.7.9 big.js: 6.2.2 bs58: 5.0.0 diff --git a/src/actions/index.ts b/src/actions/index.ts index 18919057..9c4f460e 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -76,6 +76,7 @@ import getAssetAction from "./metaplex/getAsset"; import getAssetsByAuthorityAction from "./metaplex/getAssetsByAuthority"; import getAssetsByCreatorAction from "./metaplex/getAssetsByCreator"; import getInfoAction from "./agent/get_info"; +import switchboardSimulateFeedAction from "./switchboard/simulate_feed"; import swapAction from "./mayan/swap"; import getPriceInferenceAction from "./allora/getPriceInference"; import getAllTopicsAction from "./allora/getAllTopics"; @@ -172,6 +173,7 @@ export const ACTIONS = { WITHDRAW_VOLTR_STRATEGY_ACTION: withdrawVoltrStrategyAction, GET_ASSET_ACTION: getAssetAction, GET_ASSETS_BY_AUTHORITY_ACTION: getAssetsByAuthorityAction, + SWITCHBOARD_FEED_ACTION: switchboardSimulateFeedAction, GET_ASSETS_BY_CREATOR_ACTION: getAssetsByCreatorAction, SWAP_ACTION: swapAction, GET_PRICE_INFERENCE_ACTION: getPriceInferenceAction, diff --git a/src/actions/switchboard/simulate_feed.ts b/src/actions/switchboard/simulate_feed.ts new file mode 100644 index 00000000..15fccdc5 --- /dev/null +++ b/src/actions/switchboard/simulate_feed.ts @@ -0,0 +1,63 @@ +import { Action } from "../../types/action"; +import { SolanaAgentKit } from "../../agent"; +import { z } from "zod"; +import { simulate_switchboard_feed } from "../../tools"; +import { SWITCHBOARD_DEFAULT_CROSSBAR } from "../../constants"; + +const switchboardSimulateFeedAction: Action = { + name: "SWITCHBOARD_SIMULATE_FEED", + similes: [ + "simulate switchboard price feed", + "simulate switchboard feed", + "switchboard oracle feed", + "get switchboard price", + "check switchboard price", + "switchboard price", + "switchbaord feed", + ], + description: + "Simulates a given switchboard price feed and returns the value.", + examples: [ + [ + { + input: { + feed: "6qmsMwtMmeqMgZEhyLv1Pe4wcqT5iKwJAWnmzmnKjf83", // BTC/USDT price feed + }, + output: { + status: "success", + value: "104097.59", + message: "Simulation result: 104097.59", + }, + explanation: + "Get the current BTC/USDT price by simulating a Switchbaord feed", + }, + ], + ], + schema: z.object({ + feed: z + .string() + .describe("The address of the Switchboard feed to simulate"), + crossbarUrl: z + .string() + .default(SWITCHBOARD_DEFAULT_CROSSBAR) + .describe("The url of the crossbar server to use"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const { feedAddress, crossbarUrl } = input; + const result = await simulate_switchboard_feed(feedAddress, crossbarUrl); + return { + status: "success", + feed: feedAddress, + message: `Simulation result: ${result}`, + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to simulate Switchboard feed: ${error.message}`, + }; + } + }, +}; + +export default switchboardSimulateFeedAction; diff --git a/src/agent/index.ts b/src/agent/index.ts index 7b88c571..3648853e 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -117,6 +117,7 @@ import { get_asset, get_assets_by_authority, get_assets_by_creator, + simulate_switchboard_feed, swap, getPriceInference, getAllTopics, @@ -1068,4 +1069,11 @@ export class SolanaAgentKit { async getInferenceByTopicId(topicId: number): Promise { return getInferenceByTopicId(this, topicId); } + + async simulateSwitchboardFeed( + feed: string, + crossbarUrl: string, + ): Promise { + return simulate_switchboard_feed(this, feed, crossbarUrl); + } } diff --git a/src/constants/index.ts b/src/constants/index.ts index 472213fd..a961e611 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -47,3 +47,9 @@ export const METEORA_DLMM_PROGRAM_ID = new PublicKey( */ export const MINIMUM_COMPUTE_PRICE_FOR_COMPLEX_ACTIONS = 0.000003 * 1000000 * 1000000; + +/** + * Switchboard public crossbar instance. + * https://docs.switchboard.xyz/docs/switchboard/crossbar-and-task-runner + */ +export const SWITCHBOARD_DEFAULT_CROSSBAR = "https://crossbar.switchboard.xyz"; diff --git a/src/langchain/index.ts b/src/langchain/index.ts index f455ad21..12e659c9 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -30,6 +30,7 @@ export * from "./drift"; export * from "./voltr"; export * from "./mayan"; export * from "./allora"; +export * from "./switchboard"; import type { SolanaAgentKit } from "../agent"; import { @@ -140,6 +141,7 @@ import { SolanaAlloraGetPriceInference, SolanaAlloraGetAllTopics, SolanaAlloraGetInferenceByTopicId, + SolanaSwitchboardSimulateFeed, } from "./index"; export function createSolanaTools(solanaKit: SolanaAgentKit) { @@ -247,9 +249,10 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) { new SolanaGetAssetTool(solanaKit), new SolanaGetAssetsByAuthorityTool(solanaKit), new SolanaGetAssetsByCreatorTool(solanaKit), + new SolanaSwitchboardSimulateFeed(solanaKit), new SolanaCrossChainSwapTool(solanaKit), - new SolanaAlloraGetPriceInference(solanaKit), new SolanaAlloraGetAllTopics(solanaKit), new SolanaAlloraGetInferenceByTopicId(solanaKit), + new SolanaAlloraGetPriceInference(solanaKit), ]; } diff --git a/src/langchain/switchboard/index.ts b/src/langchain/switchboard/index.ts new file mode 100644 index 00000000..557d10ca --- /dev/null +++ b/src/langchain/switchboard/index.ts @@ -0,0 +1 @@ +export * from "./switchboard_simulate_feed"; diff --git a/src/langchain/switchboard/switchboard_simulate_feed.ts b/src/langchain/switchboard/switchboard_simulate_feed.ts new file mode 100644 index 00000000..25e00c9b --- /dev/null +++ b/src/langchain/switchboard/switchboard_simulate_feed.ts @@ -0,0 +1,46 @@ +import { Tool } from "langchain/tools"; +import { SolanaAgentKit } from "../../agent"; +import { SwitchboardSimulateFeedResponse } from "../../index"; + +export class SolanaSwitchboardSimulateFeed extends Tool { + name = "switchboard_simulate_feed"; + description = `Simluates a Switchboard price feed given the feed's public key. + Input should be a JSON string with the following format: + { + "feed": string (required) - the public key (a.k.a. feed hash) of the feed to simulate + "crossbarUrl": string (optional) - the url of the crossbar instance to use. Defaults to "https://crossbar.switchboard.xyz" + } + `; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + async _call(input: string): Promise { + try { + const InputFormat = JSON.parse(input); + const feed = InputFormat.feed; + const crossbarUrl = InputFormat.crossbarUrl; + + const value = await this.solanaKit.simulateSwitchboardFeed( + feed, + crossbarUrl, + ); + + const response: SwitchboardSimulateFeedResponse = { + status: "success", + feed, + value: Number.parseInt(value), + }; + + return JSON.stringify(response); + } catch (error: any) { + const response: SwitchboardSimulateFeedResponse = { + status: "error", + message: error.message, + code: error.code, + }; + return JSON.stringify(response); + } + } +} diff --git a/src/tools/index.ts b/src/tools/index.ts index 343f2396..eff0e276 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -29,3 +29,4 @@ export * from "./helius"; export * from "./voltr"; export * from "./mayan"; export * from "./allora"; +export * from "./switchboard"; diff --git a/src/tools/switchboard/index.ts b/src/tools/switchboard/index.ts new file mode 100644 index 00000000..056e4dfb --- /dev/null +++ b/src/tools/switchboard/index.ts @@ -0,0 +1 @@ +export * from "./simulate_feed"; diff --git a/src/tools/switchboard/simulate_feed.ts b/src/tools/switchboard/simulate_feed.ts new file mode 100644 index 00000000..19b967d3 --- /dev/null +++ b/src/tools/switchboard/simulate_feed.ts @@ -0,0 +1,32 @@ +import { SolanaAgentKit } from "../../index"; +import { SWITCHBOARD_DEFAULT_CROSSBAR } from "../../constants"; +import { CrossbarClient } from "@switchboard-xyz/common"; + +/** + * Simulate a switchboard feed + * @param agent SolanaAgentKit instance + * @param feed Public key of the feed to simulate as base58 + * @param crossbarUrl The url of the crossbar instance to use + * @returns Result of the simulation + */ + +export async function simulate_switchboard_feed( + agent: SolanaAgentKit, + feed: string, + crossbarUrl: string = SWITCHBOARD_DEFAULT_CROSSBAR, +): Promise { + try { + const crossbar = new CrossbarClient(crossbarUrl, true); + const results = await crossbar.simulateSolanaFeeds("mainnet", [feed]); + + if (results.length === 0) { + throw new Error( + `Error simulating feed ${feed}. Did you provide the right mainnet feed hash?`, + ); + } + + return results[0].results.toString(); + } catch (error: any) { + throw new Error(`Error: ${error.message}`); + } +} diff --git a/src/types/index.ts b/src/types/index.ts index 554f62c8..5729a778 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -292,3 +292,11 @@ export interface AlloraGetInferenceByTopicIdResponse { message?: string; code?: string; } + +export interface SwitchboardSimulateFeedResponse { + status: "success" | "error"; + feed?: string; + value?: number; + message?: string; + code?: string; +}