From 8ad32e1f0848f4d13ae7d50106df1b0bada51490 Mon Sep 17 00:00:00 2001 From: Fernando Campos Date: Wed, 15 Jan 2025 13:33:31 -0300 Subject: [PATCH] Adds Allora Network price prediction to the CDP AgentKit (typescript) --- cdp-agentkit-core/typescript/package.json | 1 + .../src/actions/cdp/allora/allora_action.ts | 32 ++++++++ .../src/actions/cdp/allora/get_all_topics.ts | 64 +++++++++++++++ .../cdp/allora/get_price_prediction.ts | 63 +++++++++++++++ .../src/actions/cdp/allora/index.ts | 25 ++++++ .../typescript/src/allora_agentkit.ts | 44 +++++++++++ cdp-agentkit-core/typescript/src/index.ts | 3 + .../src/tests/allora_agentkit_test.ts | 67 ++++++++++++++++ .../src/tests/allora_get_all_topics_test.ts | 58 ++++++++++++++ .../tests/allora_get_price_prediction_test.ts | 79 +++++++++++++++++++ package-lock.json | 55 ++++++++++++- 11 files changed, 488 insertions(+), 3 deletions(-) create mode 100644 cdp-agentkit-core/typescript/src/actions/cdp/allora/allora_action.ts create mode 100644 cdp-agentkit-core/typescript/src/actions/cdp/allora/get_all_topics.ts create mode 100644 cdp-agentkit-core/typescript/src/actions/cdp/allora/get_price_prediction.ts create mode 100644 cdp-agentkit-core/typescript/src/actions/cdp/allora/index.ts create mode 100644 cdp-agentkit-core/typescript/src/allora_agentkit.ts create mode 100644 cdp-agentkit-core/typescript/src/tests/allora_agentkit_test.ts create mode 100644 cdp-agentkit-core/typescript/src/tests/allora_get_all_topics_test.ts create mode 100644 cdp-agentkit-core/typescript/src/tests/allora_get_price_prediction_test.ts diff --git a/cdp-agentkit-core/typescript/package.json b/cdp-agentkit-core/typescript/package.json index 9e75e0ee9..300fecaab 100644 --- a/cdp-agentkit-core/typescript/package.json +++ b/cdp-agentkit-core/typescript/package.json @@ -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", diff --git a/cdp-agentkit-core/typescript/src/actions/cdp/allora/allora_action.ts b/cdp-agentkit-core/typescript/src/actions/cdp/allora/allora_action.ts new file mode 100644 index 000000000..2251e26f8 --- /dev/null +++ b/cdp-agentkit-core/typescript/src/actions/cdp/allora/allora_action.ts @@ -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; + +/** + * Represents the base structure for Allora Actions. + */ +export interface AlloraAction { + /** + * 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) => Promise) + | ((args: z.infer) => Promise); +} diff --git a/cdp-agentkit-core/typescript/src/actions/cdp/allora/get_all_topics.ts b/cdp-agentkit-core/typescript/src/actions/cdp/allora/get_all_topics.ts new file mode 100644 index 000000000..26b77796f --- /dev/null +++ b/cdp-agentkit-core/typescript/src/actions/cdp/allora/get_all_topics.ts @@ -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, +): Promise { + 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 { + public name = "get_all_topics"; + public description = GET_ALL_TOPICS_PROMPT; + public argsSchema = GetAllTopicsInput; + public func = getAllTopics; +} diff --git a/cdp-agentkit-core/typescript/src/actions/cdp/allora/get_price_prediction.ts b/cdp-agentkit-core/typescript/src/actions/cdp/allora/get_price_prediction.ts new file mode 100644 index 000000000..eec1c30b7 --- /dev/null +++ b/cdp-agentkit-core/typescript/src/actions/cdp/allora/get_price_prediction.ts @@ -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, +): Promise { + 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 { + public name = "get_price_prediction"; + public description = GET_PRICE_PREDICTION_PROMPT; + public argsSchema = GetPricePredictionInput; + public func = getPricePrediction; +} diff --git a/cdp-agentkit-core/typescript/src/actions/cdp/allora/index.ts b/cdp-agentkit-core/typescript/src/actions/cdp/allora/index.ts new file mode 100644 index 000000000..b04e92a65 --- /dev/null +++ b/cdp-agentkit-core/typescript/src/actions/cdp/allora/index.ts @@ -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[]} An array of Allora action instances. + */ +export function getAllAlloraActions(): AlloraAction[] { + return [new GetPricePredictionAction(), new GetAllTopicsAction()]; +} + +/** + * All available Allora actions. + */ +export const ALLORA_ACTIONS = getAllAlloraActions(); + +/** + * All Allora action types. + */ +export { AlloraAction, AlloraActionSchemaAny, GetPricePredictionAction, GetAllTopicsAction }; diff --git a/cdp-agentkit-core/typescript/src/allora_agentkit.ts b/cdp-agentkit-core/typescript/src/allora_agentkit.ts new file mode 100644 index 000000000..799fbfffe --- /dev/null +++ b/cdp-agentkit-core/typescript/src/allora_agentkit.ts @@ -0,0 +1,44 @@ +import { AlloraAPIClient, ChainSlug } from "@alloralabs/allora-sdk"; +import { AlloraAction, AlloraActionSchemaAny } from "./actions/cdp/allora"; + +interface AlloraAgentkitOptions { + apiKey?: string; + baseAPIUrl?: string; + chainSlug?: string; +} + +export class AlloraAgentkit { + private client: AlloraAPIClient; + + constructor(config: AlloraAgentkitOptions = {}) { + const apiKey = config.apiKey || process.env.ALLORA_API_KEY || "UP-4151d0cc489a44a7aa5cd7ef"; + const baseAPIUrl = config.baseAPIUrl || process.env.ALLORA_BASE_API_URL; + const chainSlug = config.chainSlug || process.env.ALLORA_CHAIN_SLUG || ChainSlug.TESTNET; + + if (!Object.values(ChainSlug).includes(chainSlug as ChainSlug)) { + throw new Error( + `Invalid chainSlug: ${chainSlug}. Valid options are: ${Object.values(ChainSlug).join(", ")}`, + ); + } + + this.client = new AlloraAPIClient({ + apiKey, + baseAPIUrl, + chainSlug: chainSlug as ChainSlug, + }); + } + + /** + * Executes a Allora action. + * + * @param action - The Allora action to execute. + * @param args - The arguments for the action. + * @returns The result of the execution. + */ + async run( + action: AlloraAction, + args: TActionSchema, + ): Promise { + return await action.func(this.client!, args); + } +} diff --git a/cdp-agentkit-core/typescript/src/index.ts b/cdp-agentkit-core/typescript/src/index.ts index 99708a2d5..20f765ca1 100644 --- a/cdp-agentkit-core/typescript/src/index.ts +++ b/cdp-agentkit-core/typescript/src/index.ts @@ -15,3 +15,6 @@ export { CdpAgentkit } from "./cdp_agentkit"; // Export Twitter AgentKit export { TwitterAgentkit } from "./twitter_agentkit"; + +// Export Allora AgentKit +export { AlloraAgentkit } from "./allora_agentkit"; diff --git a/cdp-agentkit-core/typescript/src/tests/allora_agentkit_test.ts b/cdp-agentkit-core/typescript/src/tests/allora_agentkit_test.ts new file mode 100644 index 000000000..1ed094f58 --- /dev/null +++ b/cdp-agentkit-core/typescript/src/tests/allora_agentkit_test.ts @@ -0,0 +1,67 @@ +import { AlloraAPIClient, ChainSlug } from "@alloralabs/allora-sdk"; +import { AlloraAgentkit } from "../allora_agentkit"; +import { AlloraAction, GetPricePredictionAction, AlloraActionSchemaAny } from "../actions/cdp/allora"; +import { GetPricePredictionInput } from "../actions/cdp/allora/get_price_prediction"; +import { z, ZodString } from "zod"; + +jest.mock("@alloralabs/allora-sdk"); + +describe("AlloraAgentkit", () => { + describe("initialization", () => { + beforeEach(() => { + process.env.ALLORA_API_KEY = "test-api-key"; + process.env.ALLORA_BASE_API_URL = "https://test.api.url"; + process.env.ALLORA_CHAIN_SLUG = ChainSlug.TESTNET; + }); + + afterEach(() => { + jest.resetAllMocks(); + process.env.ALLORA_API_KEY = ""; + process.env.ALLORA_BASE_API_URL = ""; + process.env.ALLORA_CHAIN_SLUG = ""; + }); + + it("should successfully init with env variables", () => { + const agentkit = new AlloraAgentkit(); + expect(agentkit).toBeDefined(); + expect(AlloraAPIClient).toHaveBeenCalledWith({ + apiKey: "test-api-key", + baseAPIUrl: "https://test.api.url", + chainSlug: ChainSlug.TESTNET, + }); + }); + + it("should successfully init with options overriding env", () => { + const options = { + apiKey: "custom-api-key", + baseAPIUrl: "https://custom.api.url", + chainSlug: ChainSlug.MAINNET, + }; + + const agentkit = new AlloraAgentkit(options); + expect(agentkit).toBeDefined(); + expect(AlloraAPIClient).toHaveBeenCalledWith(options); + }); + + it("should use default values when no options or env provided", () => { + process.env.ALLORA_API_KEY = ""; + process.env.ALLORA_BASE_API_URL = ""; + process.env.ALLORA_CHAIN_SLUG = ""; + + const agentkit = new AlloraAgentkit(); + expect(agentkit).toBeDefined(); + expect(AlloraAPIClient).toHaveBeenCalledWith({ + apiKey: "UP-4151d0cc489a44a7aa5cd7ef", + baseAPIUrl: "", + chainSlug: ChainSlug.TESTNET, + }); + }); + + it("should throw error for invalid chain slug", () => { + const invalidChainSlug = "INVALID_CHAIN" as unknown as ChainSlug; + expect(() => { + new AlloraAgentkit({ chainSlug: invalidChainSlug }); + }).toThrow(/Invalid chainSlug/); + }); + }); +}); diff --git a/cdp-agentkit-core/typescript/src/tests/allora_get_all_topics_test.ts b/cdp-agentkit-core/typescript/src/tests/allora_get_all_topics_test.ts new file mode 100644 index 000000000..b6b102af8 --- /dev/null +++ b/cdp-agentkit-core/typescript/src/tests/allora_get_all_topics_test.ts @@ -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; + + 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; + + const result = await getAllTopics(mockClient, {}); + + expect(mockClient.getAllTopics).toHaveBeenCalledTimes(1); + expect(result).toContain("Error getting all topics:"); + expect(result).toContain(mockError.toString()); + }); + }); +}); diff --git a/cdp-agentkit-core/typescript/src/tests/allora_get_price_prediction_test.ts b/cdp-agentkit-core/typescript/src/tests/allora_get_price_prediction_test.ts new file mode 100644 index 000000000..2ff88176e --- /dev/null +++ b/cdp-agentkit-core/typescript/src/tests/allora_get_price_prediction_test.ts @@ -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; + + beforeEach(() => { + mockAlloraClient = { + getPricePrediction: jest.fn(), + } as unknown as jest.Mocked; + }); + + 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}`); + }); +}); diff --git a/package-lock.json b/package-lock.json index 273152136..c10b9855c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "version": "0.0.11", "license": "Apache-2.0", "dependencies": { + "@alloralabs/allora-sdk": "^0.0.4", "@coinbase/coinbase-sdk": "^0.13.0", "twitter-api-v2": "^1.18.2", "viem": "^2.21.51", @@ -68,6 +69,31 @@ "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" }, + "node_modules/@alloralabs/allora-sdk": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@alloralabs/allora-sdk/-/allora-sdk-0.0.4.tgz", + "integrity": "sha512-QlpXJAnN5I6QHnNP+j96Cim05ztBfsKV/Ecn79+2KE2Wt71PJQj3ZGJ5SmbICJdQe5FrkpgthBkK0xOoYMYhWQ==", + "dependencies": { + "@types/node": "^22.10.5", + "typescript": "^5.7.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alloralabs/allora-sdk/node_modules/@types/node": { + "version": "22.10.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.6.tgz", + "integrity": "sha512-qNiuwC4ZDAUNcY47xgaSuS92cjf8JbSUoaKS77bmLG1rU7MlATVSiw/IlrjtIyyskXBZ8KkNfjK/P5na7rgXbQ==", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@alloralabs/allora-sdk/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -7137,7 +7163,6 @@ "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", - "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7610,6 +7635,30 @@ "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" }, + "@alloralabs/allora-sdk": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@alloralabs/allora-sdk/-/allora-sdk-0.0.4.tgz", + "integrity": "sha512-QlpXJAnN5I6QHnNP+j96Cim05ztBfsKV/Ecn79+2KE2Wt71PJQj3ZGJ5SmbICJdQe5FrkpgthBkK0xOoYMYhWQ==", + "requires": { + "@types/node": "^22.10.5", + "typescript": "^5.7.2" + }, + "dependencies": { + "@types/node": { + "version": "22.10.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.6.tgz", + "integrity": "sha512-qNiuwC4ZDAUNcY47xgaSuS92cjf8JbSUoaKS77bmLG1rU7MlATVSiw/IlrjtIyyskXBZ8KkNfjK/P5na7rgXbQ==", + "requires": { + "undici-types": "~6.20.0" + } + }, + "undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + } + } + }, "@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -7977,6 +8026,7 @@ "@coinbase/cdp-agentkit-core": { "version": "file:cdp-agentkit-core/typescript", "requires": { + "@alloralabs/allora-sdk": "^0.0.4", "@coinbase/coinbase-sdk": "^0.13.0", "@types/jest": "^29.5.14", "@types/secp256k1": "^4.0.6", @@ -12779,8 +12829,7 @@ "typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", - "devOptional": true + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==" }, "uc.micro": { "version": "2.1.0",