From 090b6734f16db0b020a0b8c76cebd614bd9f7a5c Mon Sep 17 00:00:00 2001 From: pann0x Date: Mon, 13 Jan 2025 12:05:47 -0800 Subject: [PATCH 1/3] feat: adrena-perp-trading --- .../src/actions/createToken.ts | 7 +- .../src/actions/openPerpTrade.ts | 226 ++++++++++++++++++ .../plugin-solana-agentkit/src/constants.ts | 35 +++ packages/plugin-solana-agentkit/src/index.ts | 6 +- packages/plugin-solana-agentkit/src/types.ts | 8 + 5 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 packages/plugin-solana-agentkit/src/actions/openPerpTrade.ts create mode 100644 packages/plugin-solana-agentkit/src/constants.ts create mode 100644 packages/plugin-solana-agentkit/src/types.ts diff --git a/packages/plugin-solana-agentkit/src/actions/createToken.ts b/packages/plugin-solana-agentkit/src/actions/createToken.ts index 50c0cbdf94..83b75c6aad 100644 --- a/packages/plugin-solana-agentkit/src/actions/createToken.ts +++ b/packages/plugin-solana-agentkit/src/actions/createToken.ts @@ -13,7 +13,12 @@ import { } from "@elizaos/core"; import { SolanaAgentKit } from "solana-agent-kit"; - +// agent: SolanaAgentKit, +// tokenName: string, +// tokenTicker: string, +// description: string, +// imageUrl: string, +// options?: PumpFunTokenOptions, export interface CreateTokenContent extends Content { name: string; uri: string; diff --git a/packages/plugin-solana-agentkit/src/actions/openPerpTrade.ts b/packages/plugin-solana-agentkit/src/actions/openPerpTrade.ts new file mode 100644 index 0000000000..6afea57e99 --- /dev/null +++ b/packages/plugin-solana-agentkit/src/actions/openPerpTrade.ts @@ -0,0 +1,226 @@ +import { + ActionExample, + composeContext, + Content, + elizaLogger, + generateObjectDeprecated, + HandlerCallback, + IAgentRuntime, + Memory, + ModelClass, + State, + type Action, +} from "@elizaos/core"; +import { + PerpetualsClient, + OraclePrice, + PoolConfig, + Privilege, + Side, + CustodyAccount, + Custody, +} from "flash-sdk"; +import { PublicKey, ComputeBudgetProgram } from "@solana/web3.js"; +import { BN } from "@coral-xyz/anchor"; +import { createRecoverNestedInstruction, TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { TOKENS, DEFAULT_OPTIONS } from "../constants"; +import { SolanaAgentKit, createSolanaTools } from "solana-agent-kit"; +import { publicKey, token } from "@coral-xyz/anchor/dist/cjs/utils"; +import { orcaFetchPositions } from "solana-agent-kit/dist/tools"; + +export interface perpTradeContent extends Content { + price?: number; + collateralAmount: number; + collateralMint?: string; + leverage?: number; + tradeMint?: string; + slippage?: number; +} + +function isOpenPerpTradeContent(content: any, reaps?: JSON): content is perpTradeContent{ + //corrections + //content.slippage = content.slippage * 10 // 0.3 = 0.3% slippage tolerance + content.leverage = content.leverage * 10000 + //jitosol address is used on + if (content.tradeMint == TOKENS.SOL.toString()){ + content.tradeMint = TOKENS.jitoSOL.toString() + } + elizaLogger.log("Content for openPerpTrade: ", content); + return( + typeof content.price === "number" && + typeof content.collateralAmount === "number" && + typeof content.collateralMint === "string" && + typeof content.leverage === "number" && + typeof content.tradeMint === "string" && + typeof content.slippage === "number" + ); +} + +const createTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. + +Example response: +\`\`\`json +{ + "price": 300, + "collateralAmount": 10, + "collateralMint": "So11111111111111111111111111111111111111112", + "leverage": 17.27, + "tradeMint": "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn", + "slippage": 0.3 +} +\`\`\` + +{{recentMessages}} + +Given the recent messages, extract the following information about the requested token transfer: +- price +- collateral amount +- collateral mint +- Leverage +- trade mint +- slippage + +Respond with a JSON markdown block containing only the extracted values.`; + +export default { + name: "OPEN_PERP_TRADE_LONG", + similes: ["OPEN_PERP_TRADE_LONG"], + validate: async( + runtime: IAgentRuntime, + message: Memory + ) => true, + description: "Open a leveraged trading position on Adrena protocol", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: { [key: string]: unknown }, + callback?: HandlerCallback + ) => { + elizaLogger.log("starting OPEN_PERP_TRADE_LONG handler...") + // Initialize or update state + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + //Compose trade context + const openTradeContext = composeContext({ + state, + template: createTemplate, + }) + + //generate open trade content + const content = await generateObjectDeprecated({ + runtime, + context: openTradeContext, + modelClass: ModelClass.LARGE, + }); + + //todo + //use jup api instead of pyth for simplification + + // validate open trade content + + + if (!isOpenPerpTradeContent(content)){ + elizaLogger.error("Invalid content for OPEN_PERP_TRADE action.") + + console.log(content); + if (callback) { + callback({ + text: "Unable to process OPEN_PERP_TRADE request. Invalid content provided.", + content: { error: "Invalid OPEN_PERP_TRADE content" }, + }); + } + return false; + } + + elizaLogger.log("Init solana agent kit..."); + const solanaPrivatekey = runtime.getSetting("SOLANA_PRIVATE_KEY"); + const rpc = runtime.getSetting("SOLANA_RPC_URL"); + const openAIKey = runtime.getSetting("OPENAI_API_KEY"); + const solanaAgentKit = new SolanaAgentKit( + solanaPrivatekey, + rpc, + openAIKey + ); + + try { + const responseJup = await fetch(`https://api.jup.ag/price/v2?ids=${content.tradeMint.toString()}`) + const jsonstr = JSON.stringify(responseJup) + const jsonObj = JSON.parse(jsonstr) + const jsonidk = responseJup.json(); + console.log(jsonidk); + console.log("jupiter api price says: ", jsonObj); + const priceFeedID = await solanaAgentKit.getPythPriceFeedID("SOL"); + const tokenPrice = await solanaAgentKit.getPythPrice(priceFeedID); + content.price = Number(tokenPrice); + console.log("fetched token price: ", content.price); + + // const getComputeUnitEstimate = getComputeUnitEstimateForTransactionMessageFactory({ rpc }); + + // ComputeBudgetProgram.setCopnpmmputeUnitLimit({ + // units: 1000000, + // }) + + const signature = await solanaAgentKit.openPerpTradeLong( + { + price: content.price,// content.price || Number(tokenPrice), + collateralAmount: content.collateralAmount, + collateralMint: new PublicKey(content.collateralMint),// content.collateralMint, + leverage: content.leverage || DEFAULT_OPTIONS.LEVERAGE_BPS, + tradeMint: new PublicKey(content.tradeMint), + slippage: content.slippage || DEFAULT_OPTIONS.SLIPPAGE_BPS + } + ); + + elizaLogger.log("initiated perp long: ", signature); + if (callback) { + callback({ + text: `Successfully opened position on ${content.tradeMint}\n ${signature}`, + content: { + success: true, + signature, + }, + }); + } + return true; + } catch (error) { + if (callback) { + elizaLogger.error("Error during opening long position: ", error); + callback({ + text: `Error opening position: ${error.message}`, + content: { error: error.message }, + }); + } + return false + } + + }, + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Long SOL with 1000 USDC at 10x leverage", + }, + }, + { + user: "{{user2}}", + content: { + text: "creating long position now...", + action: "OPEN_PERP_TRADE_LONG", + }, + }, + { + user: "{{user2}}", + content: { + text: "Successfully opened long position on SOL with 1000 USDC at 10x leverage. \n Your position size: 10,000USDC\nYour take profit price is $300\n5R3Td47N23N4u12z56dFMFVeDmw8TNvDvhLU4XaL8QybTbbdcMgKCFG8s71TiJJy3p5Mxw8Z82Bny6Uo8Ga3rJ5K", + }, + } + ], + ] as ActionExample[][], +} as Action; + diff --git a/packages/plugin-solana-agentkit/src/constants.ts b/packages/plugin-solana-agentkit/src/constants.ts new file mode 100644 index 0000000000..4626383dec --- /dev/null +++ b/packages/plugin-solana-agentkit/src/constants.ts @@ -0,0 +1,35 @@ +import { PublicKey } from "@solana/web3.js"; + +/** + * Common token addresses used across the toolkit + */ +export const TOKENS = { + USDC: new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), + USDT: new PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"), + USDS: new PublicKey("USDSwr9ApdHk5bvJKMjzff41FfuX8bSxdKcR81vTwcA"), + SOL: new PublicKey("So11111111111111111111111111111111111111112"), + jitoSOL: new PublicKey("J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn"), + bSOL: new PublicKey("bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1"), + mSOL: new PublicKey("mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So"), + BONK: new PublicKey("DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263"), +} as const; + +/** + * Default configuration options + * @property {number} SLIPPAGE_BPS - Default slippage tolerance in basis points (300 = 3%) + * @property {number} TOKEN_DECIMALS - Default number of decimals for new tokens + * @property {number} LEVERAGE_BPS - Default leverage for trading PERP + */ +export const DEFAULT_OPTIONS = { + SLIPPAGE_BPS: 300, //3% + TOKEN_DECIMALS: 9, + RERERRAL_FEE: 200, + LEVERAGE_BPS: 50000, // 10000 = x1, 50000 = x5, 100000 = x10, 1000000 = x100 +} as const; + +/** + * Jupiter API URL + */ +export const JUP_API = "https://quote-api.jup.ag/v6"; +export const JUP_REFERRAL_ADDRESS = + ""; \ No newline at end of file diff --git a/packages/plugin-solana-agentkit/src/index.ts b/packages/plugin-solana-agentkit/src/index.ts index ae80c66c74..783474506e 100644 --- a/packages/plugin-solana-agentkit/src/index.ts +++ b/packages/plugin-solana-agentkit/src/index.ts @@ -1,10 +1,14 @@ import { Plugin } from "@elizaos/core"; import createToken from "./actions/createToken.ts"; +import openPerpTrade from "./actions/openPerpTrade.ts"; export const solanaAgentkitPlguin: Plugin = { name: "solana", description: "Solana Plugin with solana agent kit for Eliza", - actions: [createToken], + actions: [ + createToken, + openPerpTrade + ], evaluators: [], providers: [], }; diff --git a/packages/plugin-solana-agentkit/src/types.ts b/packages/plugin-solana-agentkit/src/types.ts new file mode 100644 index 0000000000..ca1cd13c1e --- /dev/null +++ b/packages/plugin-solana-agentkit/src/types.ts @@ -0,0 +1,8 @@ +import { Content } from "@elizaos/core"; + +export interface flashTradeContent extends Content { + token: string; + side: "long" | "short"; + collateralUsd: number; + leverage: number; +} From d26e0c9805df94d45ce03bf7b752307143d11858 Mon Sep 17 00:00:00 2001 From: pann <143483662+pann0x@users.noreply.github.com> Date: Tue, 14 Jan 2025 23:01:14 -0800 Subject: [PATCH 2/3] Update packages/plugin-solana-agentkit/src/actions/openPerpTrade.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../src/actions/openPerpTrade.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/plugin-solana-agentkit/src/actions/openPerpTrade.ts b/packages/plugin-solana-agentkit/src/actions/openPerpTrade.ts index 6afea57e99..5d70397648 100644 --- a/packages/plugin-solana-agentkit/src/actions/openPerpTrade.ts +++ b/packages/plugin-solana-agentkit/src/actions/openPerpTrade.ts @@ -148,15 +148,14 @@ export default { ); try { - const responseJup = await fetch(`https://api.jup.ag/price/v2?ids=${content.tradeMint.toString()}`) - const jsonstr = JSON.stringify(responseJup) - const jsonObj = JSON.parse(jsonstr) - const jsonidk = responseJup.json(); - console.log(jsonidk); - console.log("jupiter api price says: ", jsonObj); - const priceFeedID = await solanaAgentKit.getPythPriceFeedID("SOL"); - const tokenPrice = await solanaAgentKit.getPythPrice(priceFeedID); - content.price = Number(tokenPrice); + const responseJup = await fetch(`https://quote-api.jup.ag/v4/price?ids=${content.tradeMint}`); + const json = await responseJup.json(); + if (json.data && json.data[content.tradeMint] && json.data[content.tradeMint].price) { + content.price = json.data[content.tradeMint].price; + elizaLogger.log("Jupiter API price: ", content.price); + } else { + throw new Error("Failed to fetch price from Jupiter API"); + } console.log("fetched token price: ", content.price); // const getComputeUnitEstimate = getComputeUnitEstimateForTransactionMessageFactory({ rpc }); From c5e25fe0eaced0af13b2d6137ed735f661f41c6e Mon Sep 17 00:00:00 2001 From: Sayo Date: Wed, 15 Jan 2025 14:14:41 +0530 Subject: [PATCH 3/3] Update createToken.ts --- packages/plugin-solana-agentkit/src/actions/createToken.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/plugin-solana-agentkit/src/actions/createToken.ts b/packages/plugin-solana-agentkit/src/actions/createToken.ts index 83b75c6aad..50c0cbdf94 100644 --- a/packages/plugin-solana-agentkit/src/actions/createToken.ts +++ b/packages/plugin-solana-agentkit/src/actions/createToken.ts @@ -13,12 +13,7 @@ import { } from "@elizaos/core"; import { SolanaAgentKit } from "solana-agent-kit"; -// agent: SolanaAgentKit, -// tokenName: string, -// tokenTicker: string, -// description: string, -// imageUrl: string, -// options?: PumpFunTokenOptions, + export interface CreateTokenContent extends Content { name: string; uri: string;