Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add switchboard feed simulation tool #246

Merged
merged 11 commits into from
Jan 30, 2025
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,17 @@ 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);
```

## Examples

### LangGraph Multi-Agent System
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,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",
Expand Down
11 changes: 7 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 getPriceInferenceAction from "./allora/getPriceInference";
import getAllTopicsAction from "./allora/getAllTopics";
import getInferenceByTopicIdAction from "./allora/getInferenceByTopicId";
Expand Down Expand Up @@ -159,6 +160,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,
GET_PRICE_INFERENCE_ACTION: getPriceInferenceAction,
GET_ALL_TOPICS_ACTION: getAllTopicsAction,
Expand Down
63 changes: 63 additions & 0 deletions src/actions/switchboard/simulate_feed.ts
Original file line number Diff line number Diff line change
@@ -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<string, any>) => {
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;
8 changes: 8 additions & 0 deletions src/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
get_asset,
get_assets_by_authority,
get_assets_by_creator,
simulate_switchboard_feed,
getPriceInference,
getAllTopics,
getInferenceByTopicId,
Expand All @@ -142,7 +143,7 @@
DasApiAssetList,
GetAssetsByAuthorityRpcInput,
GetAssetsByCreatorRpcInput,
SearchAssetsRpcInput,

Check warning on line 146 in src/agent/index.ts

View workflow job for this annotation

GitHub Actions / check

'SearchAssetsRpcInput' is defined but never used
} from "@metaplex-foundation/digital-asset-standard-api";
import { AlloraInference, AlloraTopic } from "@alloralabs/allora-sdk";

Expand Down Expand Up @@ -1045,4 +1046,11 @@
async getInferenceByTopicId(topicId: number): Promise<AlloraInference> {
return getInferenceByTopicId(this, topicId);
}

async simulateSwitchboardFeed(
feed: string,
crossbarUrl: string,
): Promise<string> {
return simulate_switchboard_feed(this, feed, crossbarUrl);
}
}
6 changes: 6 additions & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
5 changes: 4 additions & 1 deletion src/langchain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export * from "./helius";
export * from "./drift";
export * from "./voltr";
export * from "./allora";
export * from "./switchboard";

import type { SolanaAgentKit } from "../agent";
import {
Expand Down Expand Up @@ -138,6 +139,7 @@ import {
SolanaAlloraGetPriceInference,
SolanaAlloraGetAllTopics,
SolanaAlloraGetInferenceByTopicId,
SolanaSwitchboardSimulateFeed,
} from "./index";

export function createSolanaTools(solanaKit: SolanaAgentKit) {
Expand Down Expand Up @@ -250,8 +252,9 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
new SolanaGetAssetTool(solanaKit),
new SolanaGetAssetsByAuthorityTool(solanaKit),
new SolanaGetAssetsByCreatorTool(solanaKit),
new SolanaAlloraGetPriceInference(solanaKit),
new SolanaSwitchboardSimulateFeed(solanaKit),
new SolanaAlloraGetAllTopics(solanaKit),
new SolanaAlloraGetInferenceByTopicId(solanaKit),
new SolanaAlloraGetPriceInference(solanaKit),
];
}
1 change: 1 addition & 0 deletions src/langchain/switchboard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./switchboard_simulate_feed";
47 changes: 47 additions & 0 deletions src/langchain/switchboard/switchboard_simulate_feed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
import { SwitchboardSimulateFeedResponse } from "../../index";
import { PublicKey } from "@solana/web3.js";

Check warning on line 4 in src/langchain/switchboard/switchboard_simulate_feed.ts

View workflow job for this annotation

GitHub Actions / check

'PublicKey' is defined but never used

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<string> {
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);
}
}
}
1 change: 1 addition & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ export * from "./meteora";
export * from "./helius";
export * from "./voltr";
export * from "./allora";
export * from "./switchboard";
1 change: 1 addition & 0 deletions src/tools/switchboard/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./simulate_feed";
33 changes: 33 additions & 0 deletions src/tools/switchboard/simulate_feed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { SolanaAgentKit } from "../../index";
import { PublicKey } from "@solana/web3.js";

Check warning on line 2 in src/tools/switchboard/simulate_feed.ts

View workflow job for this annotation

GitHub Actions / check

'PublicKey' is defined but never used
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<string> {
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}`);
}
}
8 changes: 8 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,11 @@ export interface AlloraGetInferenceByTopicIdResponse {
message?: string;
code?: string;
}

export interface SwitchboardSimulateFeedResponse {
status: "success" | "error";
feed?: string;
value?: number;
message?: string;
code?: string;
}
Loading