Skip to content

Commit

Permalink
Adds Allora Network price prediction to the CDP AgentKit (typescript)
Browse files Browse the repository at this point in the history
  • Loading branch information
fernandofcampos committed Jan 16, 2025
1 parent 58a7dd0 commit 3f9d81f
Show file tree
Hide file tree
Showing 8 changed files with 374 additions and 3 deletions.
1 change: 1 addition & 0 deletions cdp-agentkit-core/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"typescript"
],
"dependencies": {
"@alloralabs/allora-sdk": "^0.0.4",
"@coinbase/coinbase-sdk": "^0.13.0",
"twitter-api-v2": "^1.18.2",
"viem": "^2.21.51",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { z } from "zod";
import { AlloraAPIClient } from "@alloralabs/allora-sdk";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type AlloraActionSchemaAny = z.ZodObject<any, any, any, any>;

/**
* Represents the base structure for Allora Actions.
*/
export interface AlloraAction<TActionSchema extends AlloraActionSchemaAny> {
/**
* The name of the action.
*/
name: string;

/**
* A description of what the action does
*/
description: string;

/**
* Schema for validating action arguments
*/
argsSchema: TActionSchema;

/**
* The function to execute for this action
*/
func:
| ((client: AlloraAPIClient, args: z.infer<TActionSchema>) => Promise<string>)
| ((args: z.infer<TActionSchema>) => Promise<string>);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { AlloraAction } from "./allora_action";
import { AlloraAPIClient } from "@alloralabs/allora-sdk";
import { z } from "zod";

const GET_ALL_TOPICS_PROMPT = `
This tool will get all available topics from Allora Network.
A successful response will return a message with a list of available topics from Allora Network in JSON format:
[
{
"topic_id": 1,
"topic_name": ""Bitcoin 8h",
"description": "Bitcoin price prediction for the next 8 hours",
"epoch_length": 100,
"ground_truth_lag": 10,
"loss_method": "method1",
"worker_submission_window": 50,
"worker_count": 5,
"reputer_count": 3,
"total_staked_allo": 1000,
"total_emissions_allo": 500,
"is_active": true,
"updated_at": "2023-01-01T00:00:00Z"
}
]
`;

/**
* Input schema for get all topics action.
*/
export const GetAllTopicsInput = z
.object({})
.strip()
.describe("Instructions for getting all topics");

/**
* Gets all available topics from Allora Network.
*
* @param client - The Allora API client.
* @param args - The input arguments for the action.
* @returns A message containing the topics.
*/
export async function getAllTopics(
client: AlloraAPIClient,
_: z.infer<typeof GetAllTopicsInput>,
): Promise<string> {
try {
const topics = await client.getAllTopics();
const topicsJson = JSON.stringify(topics);
return `The available topics at Allora Network are:\n ${topicsJson}`;
} catch (error) {
return `Error getting all topics: ${error}`;
}
}

/**
* Get price prediction action.
*/
export class GetAllTopicsAction implements AlloraAction<typeof GetAllTopicsInput> {
public name = "get_all_topics";
public description = GET_ALL_TOPICS_PROMPT;
public argsSchema = GetAllTopicsInput;
public func = getAllTopics;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { AlloraAction } from "./allora_action";
import {
AlloraAPIClient,
PricePredictionTimeframe,
PricePredictionToken,
} from "@alloralabs/allora-sdk";
import { z } from "zod";

const GET_PRICE_PREDICTION_PROMPT = `
This tool will get the future price prediction for a given crypto asset from Allora Network.
It takes the crypto asset and timeframe as inputs.
`;

/**
* Input schema for get price prediction action.
*/
export const GetPricePredictionInput = z
.object({
asset: z.string().describe("The crypto asset to get the price prediction for, e.g. 'BTC'"),
timeframe: z
.string()
.describe("The timeframe to get the price prediction for, e.g. '5m' or '8h'"),
})
.strip()
.describe("Instructions for getting the price prediction");

/**
* Gets the future price prediction for a given crypto asset from Allora Network.
*
* @param client - The Allora API client.
* @param args - The input arguments for the action.
* @returns A message containing the price prediction.
*/
export async function getPricePrediction(
client: AlloraAPIClient,
args: z.infer<typeof GetPricePredictionInput>,
): Promise<string> {
const getPricePredictionArgs = {
asset: args.asset,
timeframe: args.timeframe,
};

try {
const asset = getPricePredictionArgs.asset as PricePredictionToken;
const timeframe = getPricePredictionArgs.timeframe as PricePredictionTimeframe;

const pricePrediction = await client.getPricePrediction(asset, timeframe);

return `The future price prediction for ${asset} in ${timeframe} is ${pricePrediction.inference_data.network_inference_normalized}`;
} catch (error) {
return `Error getting price prediction: ${error}`;
}
}

/**
* Get price prediction action.
*/
export class GetPricePredictionAction implements AlloraAction<typeof GetPricePredictionInput> {
public name = "get_price_prediction";
public description = GET_PRICE_PREDICTION_PROMPT;
public argsSchema = GetPricePredictionInput;
public func = getPricePrediction;
}
25 changes: 25 additions & 0 deletions cdp-agentkit-core/typescript/src/actions/cdp/allora/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* This module exports various Allora action instances and their associated types.
*/

import { AlloraAction, AlloraActionSchemaAny } from "./allora_action";
import { GetPricePredictionAction } from "./get_price_prediction";
import { GetAllTopicsAction } from "./get_all_topics";
/**
* Retrieve an array of Allora action instances.
*
* @returns {AlloraAction<AlloraActionSchemaAny>[]} An array of Allora action instances.
*/
export function getAllAlloraActions(): AlloraAction<AlloraActionSchemaAny>[] {
return [new GetPricePredictionAction(), new GetAllTopicsAction()];
}

/**
* All available Allora actions.
*/
export const ALLORA_ACTIONS = getAllAlloraActions();

/**
* All Allora action types.
*/
export { AlloraAction, AlloraActionSchemaAny, GetPricePredictionAction, GetAllTopicsAction };
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { AlloraAPIClient } from "@alloralabs/allora-sdk";
import { GetAllTopicsAction, getAllTopics } from "../actions/cdp/allora/get_all_topics";

describe("GetAllTopicsAction", () => {
const mockTopics = [
{
topic_id: 1,
topic_name: "Bitcoin 8h",
description: "Bitcoin price prediction for the next 8 hours",
epoch_length: 100,
ground_truth_lag: 10,
loss_method: "method1",
worker_submission_window: 50,
worker_count: 5,
reputer_count: 3,
total_staked_allo: 1000,
total_emissions_allo: 500,
is_active: true,
updated_at: "2023-01-01T00:00:00Z",
},
];

it("should have correct action properties", () => {
const action = new GetAllTopicsAction();
expect(action.name).toBe("get_all_topics");
expect(action.description).toContain(
"This tool will get all available topics from Allora Network",
);
expect(action.argsSchema).toBeDefined();
});

describe("getAllTopics", () => {
it("should return topics successfully", async () => {
const mockClient = {
getAllTopics: jest.fn().mockResolvedValue(mockTopics),
} as unknown as jest.Mocked<AlloraAPIClient>;

const result = await getAllTopics(mockClient, {});

expect(mockClient.getAllTopics).toHaveBeenCalledTimes(1);
expect(result).toContain("The available topics at Allora Network are:");
expect(result).toContain(JSON.stringify(mockTopics));
});

it("should handle errors gracefully", async () => {
const mockError = new Error("API Error");
const mockClient = {
getAllTopics: jest.fn().mockRejectedValue(mockError),
} as unknown as jest.Mocked<AlloraAPIClient>;

const result = await getAllTopics(mockClient, {});

expect(mockClient.getAllTopics).toHaveBeenCalledTimes(1);
expect(result).toContain("Error getting all topics:");
expect(result).toContain(mockError.toString());
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
AlloraAPIClient,
PricePredictionTimeframe,
PricePredictionToken,
AlloraInference,
} from "@alloralabs/allora-sdk";
import {
getPricePrediction,
GetPricePredictionInput,
} from "../actions/cdp/allora/get_price_prediction";

describe("Get Price Prediction Input", () => {
it("should successfully parse valid input", () => {
const validInput = {
asset: "BTC",
timeframe: "5m",
};

const result = GetPricePredictionInput.safeParse(validInput);

expect(result.success).toBe(true);
expect(result.data).toEqual(validInput);
});

it("should fail parsing empty input", () => {
const emptyInput = {};
const result = GetPricePredictionInput.safeParse(emptyInput);

expect(result.success).toBe(false);
});
});

describe("Get Price Prediction Action", () => {
let mockAlloraClient: jest.Mocked<AlloraAPIClient>;

beforeEach(() => {
mockAlloraClient = {
getPricePrediction: jest.fn(),
} as unknown as jest.Mocked<AlloraAPIClient>;
});

it("should successfully get price prediction", async () => {
const args = {
asset: "BTC",
timeframe: "5m",
};
const mockPrediction = {
signature: "mockSignature",
inference_data: { network_inference_normalized: "45000.00" },
};

mockAlloraClient.getPricePrediction.mockResolvedValue(mockPrediction as AlloraInference);

const response = await getPricePrediction(mockAlloraClient, args);

expect(mockAlloraClient.getPricePrediction).toHaveBeenCalledWith(
args.asset as PricePredictionToken,
args.timeframe as PricePredictionTimeframe,
);
expect(response).toBe(
`The future price prediction for BTC in 5m is ${mockPrediction.inference_data.network_inference_normalized}`,
);
});

it("should handle errors gracefully", async () => {
const args = {
asset: "BTC",
timeframe: "5m",
};

const error = new Error("Failed to fetch price prediction");
mockAlloraClient.getPricePrediction.mockRejectedValue(error);

const response = await getPricePrediction(mockAlloraClient, args);

expect(mockAlloraClient.getPricePrediction).toHaveBeenCalled();
expect(response).toBe(`Error getting price prediction: ${error}`);
});
});
Loading

0 comments on commit 3f9d81f

Please sign in to comment.