Skip to content

Commit

Permalink
Feat: Add switchboard feed simulation tool (sendaifun#246)
Browse files Browse the repository at this point in the history
# Pull Request Description
[Switchboard](https://switchboard.xyz/) is a decentralized oracle
network that provides reliable and tamper-proof data feeds to smart
contracts on blockchain platforms. It allows developers to access
real-world data, such as price feeds, weather information, and other
external data sources, which can be used in decentralized applications
(dApps). Switchboard aims to enhance the functionality of smart
contracts by enabling them to interact with off-chain data securely and
efficiently.

## Changes Made
This PR adds the following changes:
- This PR adds the tools and actions needed to enable agents to simulate
switchboard feeds. You must provide the feed hash of a Switchboard feed
(you can browse feeds
[here](https://ondemand.switchboard.xyz/solana/mainnet)) and the agent
will simulate it to get the current value.
  
## Implementation Details
<!-- Provide technical details about the implementation -->
- Followed `guides/add_your_own_tool.md` directions.
- 

## Transaction executed by agent 
N/A

## Prompt Used
<!-- If relevant, include the prompt or configuration used -->
```
- simulate the following switchboard feed: DwYF1yveo8XTF1oqfsqykj332rjSxAd7bR6Gu6i4iUET
- simulate the following switchboard feed: DwYF1yveo8XTF1oqfsqykj332rjSxAd7bR6Gu6i4iUET using this crossbar URL: https://crossbar.switchboard.xyz
```

## Additional Notes
Screenshot:
<img width="1791" alt="image"
src="https://github.com/user-attachments/assets/df84d6a5-3409-4f07-b002-9ec2d19d8490"
/>


## Checklist
- [x] I have tested these changes locally
- [x] I have updated the documentation
- [x] I have added a transaction link
- [x] I have added the prompt used to test it
  • Loading branch information
michaelessiet authored Jan 30, 2025
2 parents 76e7400 + f66ebc2 commit 6d67bdc
Show file tree
Hide file tree
Showing 14 changed files with 191 additions and 5 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -544,6 +554,7 @@ const signature = await agent.swap(
toToken: "0x0000000000000000000000000000000000000000",
dstAddr: "0xc2d3024d64f27d85e05c40056674Fd18772dd922",
);
```

## Examples
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
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 swapAction from "./mayan/swap";
import getPriceInferenceAction from "./allora/getPriceInference";
import getAllTopicsAction from "./allora/getAllTopics";
Expand Down Expand Up @@ -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,
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 @@ import {
get_asset,
get_assets_by_authority,
get_assets_by_creator,
simulate_switchboard_feed,
swap,
getPriceInference,
getAllTopics,
Expand Down Expand Up @@ -1068,4 +1069,11 @@ export class SolanaAgentKit {
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 @@ -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 {
Expand Down Expand Up @@ -140,6 +141,7 @@ import {
SolanaAlloraGetPriceInference,
SolanaAlloraGetAllTopics,
SolanaAlloraGetInferenceByTopicId,
SolanaSwitchboardSimulateFeed,
} from "./index";

export function createSolanaTools(solanaKit: SolanaAgentKit) {
Expand Down Expand Up @@ -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),
];
}
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";
46 changes: 46 additions & 0 deletions src/langchain/switchboard/switchboard_simulate_feed.ts
Original file line number Diff line number Diff line change
@@ -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<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 @@ -29,3 +29,4 @@ export * from "./helius";
export * from "./voltr";
export * from "./mayan";
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";
32 changes: 32 additions & 0 deletions src/tools/switchboard/simulate_feed.ts
Original file line number Diff line number Diff line change
@@ -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<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 @@ -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;
}

0 comments on commit 6d67bdc

Please sign in to comment.