From 284338af5432170a679d6639c7dae03e2873f922 Mon Sep 17 00:00:00 2001 From: DonDuala Date: Sun, 29 Dec 2024 21:57:51 -0400 Subject: [PATCH] Add limit order support on Manifest --- src/agent/index.ts | 10 +++++ src/langchain/index.ts | 46 +++++++++++++++++++++- src/tools/index.ts | 1 + src/tools/limit_order.ts | 83 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 src/tools/limit_order.ts diff --git a/src/agent/index.ts b/src/agent/index.ts index b46b415..20dd44a 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -20,6 +20,7 @@ import { registerDomain, request_faucet_funds, trade, + limitOrder, transfer, getTokenDataByAddress, getTokenDataByTicker, @@ -138,6 +139,15 @@ export class SolanaAgentKit { return trade(this, outputMint, inputAmount, inputMint, slippageBps); } + async limitOrder( + marketId: PublicKey, + quantity: number, + side: string, + price: number, + ): Promise { + return limitOrder(this, marketId, quantity, side, price); + } + async lendAssets(amount: number): Promise { return lendAsset(this, amount); } diff --git a/src/langchain/index.ts b/src/langchain/index.ts index 9c74a84..859c26e 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -264,6 +264,50 @@ export class SolanaTradeTool extends Tool { } } +export class SolanaLimitOrderTool extends Tool { + name = "solana_limit_order"; + description = `This tool can be used to place limit orders using Manifest. + + Inputs ( input is a JSON string ): + marketId: PublicKey, eg "ENhU8LsaR7vDD2G1CsWcsuSGNrih9Cv5WZEk7q9kPapQ" for SOL/USDC (required) + quantity: number, eg 1 or 0.01 (required) + side: string, eg "Buy" or "Sell" (required) + price: number, in tokens eg 200 for SOL/USDC (required)`; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const parsedInput = JSON.parse(input); + + const tx = await this.solanaKit.limitOrder( + parsedInput.marketId, + parsedInput.quantity, + parsedInput.side, + parsedInput.price, + ); + + return JSON.stringify({ + status: "success", + message: "Trade executed successfully", + transaction: tx, + marketId: parsedInput.marketId, + quantity: parsedInput.quantity, + side: parsedInput.side, + price: parsedInput.price, + }); + } catch (error: any) { + return JSON.stringify({ + status: "error", + message: error.message, + code: error.code || "UNKNOWN_ERROR", + }); + } + } +} + export class SolanaRequestFundsTool extends Tool { name = "solana_request_funds"; description = "Request SOL from Solana faucet (devnet/testnet only)"; @@ -1026,8 +1070,6 @@ export class SolanaManifestCreateMarket extends Tool { } } -// TODO: Add limit order support - export class SolanaPythFetchPrice extends Tool { name = "solana_pyth_fetch_price"; description = `Fetch the price of a given price feed from Pyth's Hermes service diff --git a/src/tools/index.ts b/src/tools/index.ts index 7289ca9..fa43a9e 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -5,6 +5,7 @@ export * from "./get_balance"; export * from "./mint_nft"; export * from "./transfer"; export * from "./trade"; +export * from "./limit_order"; export * from "./register_domain"; export * from "./resolve_sol_domain"; export * from "./get_primary_domain"; diff --git a/src/tools/limit_order.ts b/src/tools/limit_order.ts new file mode 100644 index 0000000..e2c7b9d --- /dev/null +++ b/src/tools/limit_order.ts @@ -0,0 +1,83 @@ +import { + PublicKey, + Transaction, + sendAndConfirmTransaction, + TransactionInstruction, +} from "@solana/web3.js"; +import { SolanaAgentKit } from "../index"; +import { + ManifestClient, + WrapperPlaceOrderParamsExternal, +} from "@cks-systems/manifest-sdk"; +import { sleep } from "openai/core"; +import { OrderType } from "@cks-systems/manifest-sdk/dist/types/src/manifest"; + +/** + * Place limit orders using Manifest + * @param agent SolanaAgentKit instance + * @param marketId Public key for the manifest market + * @param quantity Amount to trade in tokens + * @param side Buy or Sell + * @param price Price in tokens ie. SOL/USDC + * @returns Transaction signature + */ +export async function limitOrder( + agent: SolanaAgentKit, + marketId: PublicKey, + quantity: number, + side: string, + price: number, +): Promise { + try { + const { setupNeeded, instructions, wrapperKeypair } = + await ManifestClient.getSetupIxs( + agent.connection, + marketId, + agent.wallet.publicKey, + ); + + if (setupNeeded) { + const tx = new Transaction().add(...instructions); + const { blockhash } = await agent.connection.getLatestBlockhash(); + tx.recentBlockhash = blockhash; + tx.feePayer = agent.wallet.publicKey!; + if (wrapperKeypair) { + tx.sign(wrapperKeypair); + } + + await sendAndConfirmTransaction(agent.connection, tx, [agent.wallet]); + + await sleep(5_000); + } + + const mfxClient = await ManifestClient.getClientForMarket( + agent.connection, + marketId, + agent.wallet, + ); + + const orderParams: WrapperPlaceOrderParamsExternal = { + numBaseTokens: quantity, + tokenPrice: price, + isBid: side === "buy", + lastValidSlot: 0, + orderType: OrderType.Limit, + clientOrderId: Number(Math.random() * 1000), + }; + + const depositPlaceOrderIx: TransactionInstruction[] = + await mfxClient.placeOrderWithRequiredDepositIx( + agent.wallet.publicKey, + orderParams, + ); + const signature = await sendAndConfirmTransaction( + agent.connection, + new Transaction().add(...depositPlaceOrderIx), + [agent.wallet], + ); + + return signature; + } catch (error: any) { + throw new Error(`Limit Order failed: ${error.message}`); + } +}