From 8bef717eb9a50e4843f8ff78e2a788d90531c2c5 Mon Sep 17 00:00:00 2001 From: michaelessiet Date: Fri, 10 Jan 2025 23:26:14 +0100 Subject: [PATCH] feat: drift vault info action --- .eslintrc | 3 +- src/actions/drift/createVault.ts | 1 - src/actions/drift/vaultInfo.ts | 78 +++++++++++++++++++++++++++ src/actions/index.ts | 2 + src/tools/drift_vault.ts | 92 ++++++++++++++++++++++++++------ 5 files changed, 158 insertions(+), 18 deletions(-) create mode 100644 src/actions/drift/vaultInfo.ts diff --git a/.eslintrc b/.eslintrc index e619c84..3b8e4c6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -13,6 +13,7 @@ "no-constant-condition": "off", "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-empty-object-type": "off", "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], "no-console": ["warn", { "allow": ["warn", "error"] }], "curly": ["error", "all"], @@ -30,4 +31,4 @@ "ecmaVersion": 2020, "sourceType": "module" } -} \ No newline at end of file +} diff --git a/src/actions/drift/createVault.ts b/src/actions/drift/createVault.ts index c0702c3..2296196 100644 --- a/src/actions/drift/createVault.ts +++ b/src/actions/drift/createVault.ts @@ -40,7 +40,6 @@ const createDriftVaultAction: Action = { // regex matches SOL-SPOT marketName: z .string() - .regex(/^([A-Za-z0-9]{2,7})-SPOT$/) .describe('Market name must be in the format "TOKEN-SPOT"'), redeemPeriod: z .number() diff --git a/src/actions/drift/vaultInfo.ts b/src/actions/drift/vaultInfo.ts new file mode 100644 index 0000000..c354ae5 --- /dev/null +++ b/src/actions/drift/vaultInfo.ts @@ -0,0 +1,78 @@ +import { z } from "zod"; +import type { Action } from "../../types"; +import { getVaultInfo } from "../../tools"; +import type { SolanaAgentKit } from "../../agent"; +import { decodeName } from "@drift-labs/vaults-sdk"; +import { MainnetSpotMarkets, PERCENTAGE_PRECISION } from "@drift-labs/sdk"; + +const vaultInfoAction: Action = { + name: "DRIFT_VAULT_INFO", + similes: ["get drift vault info", "vault info", "vault information"], + description: "Get information about a drift vault", + examples: [ + [ + { + input: { + vaultAddress: "2nFeP7taii", + }, + output: { + status: "success", + message: "Vault info retrieved successfully", + data: { + name: "My Drift Vault", + marketName: "SOL-SPOT", + redeemPeriod: 30, + maxTokens: 1000, + minDepositAmount: 100, + managementFee: 10, + profitShare: 5, + hurdleRate: 0.1, + permissioned: false, + }, + }, + explanation: "Get information about a drift vault", + }, + ], + ], + schema: z.object({ + vaultAddress: z.string(), + }), + handler: async (agent: SolanaAgentKit, input) => { + try { + const vaultInfo = await getVaultInfo(agent, input.vaultAddress as string); + const spotToken = MainnetSpotMarkets[vaultInfo.spotMarketIndex]; + const data = { + name: decodeName(vaultInfo.name), + marketName: `${spotToken.symbol}-SPOT`, + redeemPeriod: vaultInfo.redeemPeriod.toNumber(), + maxTokens: vaultInfo.maxTokens.div(spotToken.precision).toNumber(), + minDepositAmount: vaultInfo.minDepositAmount + .div(spotToken.precision) + .toNumber(), + managementFee: + (vaultInfo.managementFee.toNumber() / + PERCENTAGE_PRECISION.toNumber()) * + 100, + profitShare: + (vaultInfo.profitShare / PERCENTAGE_PRECISION.toNumber()) * 100, + hurdleRate: + (vaultInfo.hurdleRate / PERCENTAGE_PRECISION.toNumber()) * 100, + permissioned: vaultInfo.permissioned, + }; + + return { + status: "success", + message: "Vault info retrieved successfully", + data, + }; + } catch (e) { + return { + status: "error", + // @ts-expect-error - error message + message: `Failed to retrieve vault info: ${e.message}`, + }; + } + }, +}; + +export default vaultInfoAction; diff --git a/src/actions/index.ts b/src/actions/index.ts index 217d547..7baf94e 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -36,6 +36,7 @@ import depositIntoDriftVaultAction from "./drift/depositIntoVault"; import requestWithdrawalFromVaultAction from "./drift/requestWithdrawalFromVault"; import withdrawFromVaultAction from "./drift/withdrawFromVault"; import tradeDelegatedDriftVaultAction from "./drift/tradeDelegatedDriftVault"; +import vaultInfoAction from "./drift/vaultInfo"; export const ACTIONS = { WALLET_ADDRESS_ACTION: getWalletAddressAction, @@ -77,6 +78,7 @@ export const ACTIONS = { REQUEST_WITHDRAWAL_FROM_DRIFT_VAULT_ACTION: requestWithdrawalFromVaultAction, WITHDRAW_FROM_DRIFT_VAULT_ACTION: withdrawFromVaultAction, TRADE_DELEGATED_DRIFT_VAULT_ACTION: tradeDelegatedDriftVaultAction, + DRIFT_VAULT_INFO_ACTION: vaultInfoAction, }; export type { Action, ActionExample, Handler } from "../types/action"; diff --git a/src/tools/drift_vault.ts b/src/tools/drift_vault.ts index dda6542..54d56d7 100644 --- a/src/tools/drift_vault.ts +++ b/src/tools/drift_vault.ts @@ -6,6 +6,8 @@ import { getLimitOrderParams, getMarketOrderParams, getOrderParams, + MainnetPerpMarkets, + MainnetSpotMarkets, MarketType, numberToSafeBN, PERCENTAGE_PRECISION, @@ -33,7 +35,25 @@ import { import type { SolanaAgentKit } from "../agent"; import { BN } from "bn.js"; -function initClients(agent: SolanaAgentKit) { +export function getMarketIndexAndType(name: `${string}-${string}`) { + const [symbol, type] = name.toUpperCase().split("-"); + + if (type === "PERP") { + const token = MainnetPerpMarkets.find((v) => v.symbol === symbol); + if (!token) { + throw new Error("Drift doesn't have that market"); + } + return { marketIndex: token.marketIndex, marketType: MarketType.PERP }; + } + + const token = MainnetSpotMarkets.find((v) => v.symbol === symbol); + if (!token) { + throw new Error("Drift doesn't have that market"); + } + return { marketIndex: token.marketIndex, marketType: MarketType.SPOT }; +} + +async function initClients(agent: SolanaAgentKit) { const wallet: IWallet = { publicKey: agent.wallet.publicKey, payer: agent.wallet, @@ -76,12 +96,17 @@ function initClients(agent: SolanaAgentKit) { program: vaultProgram, cliMode: false, }); + await driftClient.subscribe(); - return { driftClient, vaultClient }; + async function cleanUp() { + await driftClient.unsubscribe(); + } + + return { driftClient, vaultClient, cleanUp }; } async function getOrCreateVaultDepositor(agent: SolanaAgentKit, vault: string) { - const { vaultClient } = initClients(agent); + const { vaultClient, cleanUp } = await initClients(agent); const vaultPublicKey = new PublicKey(vault); const vaultDepositor = getVaultDepositorAddressSync( vaultClient.program.programId, @@ -91,12 +116,14 @@ async function getOrCreateVaultDepositor(agent: SolanaAgentKit, vault: string) { try { await vaultClient.getVaultDepositor(vaultDepositor); + await cleanUp(); return vaultDepositor; } catch (e) { // @ts-expect-error - error message is a string if (e.message === "Account not found") { await vaultClient.initializeVaultDepositor(vaultDepositor); } + await cleanUp(); return vaultDepositor; } } @@ -131,10 +158,8 @@ export async function createVault( }, ) { try { - const { vaultClient, driftClient } = initClients(agent); - const marketIndexAndType = driftClient.getMarketIndexAndType( - params.marketName, - ); + const { vaultClient, driftClient, cleanUp } = await initClients(agent); + const marketIndexAndType = getMarketIndexAndType(params.marketName); if (!marketIndexAndType) { throw new Error("Invalid market name"); @@ -174,6 +199,8 @@ export async function createVault( permissioned: params.permissioned ?? false, }); + await cleanUp(); + return tx; } catch (e) { // @ts-expect-error - error message is a string @@ -209,7 +236,7 @@ export async function updateVault( }, ) { try { - const { vaultClient } = initClients(agent); + const { vaultClient, cleanUp } = await initClients(agent); const vaultPublicKey = new PublicKey(vault); const vaultDetails = await vaultClient.getVault(vaultPublicKey); @@ -233,6 +260,8 @@ export async function updateVault( permissioned: params.permissioned ?? vaultDetails.permissioned, }); + await cleanUp(); + return tx; } catch (e) { // @ts-expect-error - error message is a string @@ -240,6 +269,24 @@ export async function updateVault( } } +export async function getVaultInfo( + agent: SolanaAgentKit, + vaultAddress: string, +) { + try { + const { vaultClient, cleanUp } = await initClients(agent); + const vaultPublicKey = new PublicKey(vaultAddress); + const vaultDetails = await vaultClient.getVault(vaultPublicKey); + + await cleanUp(); + + return vaultDetails; + } catch (e) { + // @ts-expect-error - error message is a string + throw new Error(`Failed to get vault info: ${e.message}`); + } +} + /** Deposit tokens into a vault @param agent SolanaAgentKit instance @@ -252,8 +299,9 @@ export async function depositIntoVault( amount: number, vault: string, ) { + const { vaultClient, driftClient, cleanUp } = await initClients(agent); + try { - const { vaultClient, driftClient } = initClients(agent); const vaultPublicKey = new PublicKey(vault); const [isOwned, vaultDetails] = await Promise.all([ getIsOwned(agent, vault), @@ -275,8 +323,11 @@ export async function depositIntoVault( } const vaultDepositor = await getOrCreateVaultDepositor(agent, vault); + const tx = await vaultClient.deposit(vaultDepositor, amountBN); - return await vaultClient.deposit(vaultDepositor, amountBN); + await cleanUp(); + + return tx; } catch (e) { // @ts-expect-error - error message is a string throw new Error(`Failed to deposit into Drift vault: ${e.message}`); @@ -295,7 +346,7 @@ export async function requestWithdrawalFromVault( vault: string, ) { try { - const { vaultClient } = initClients(agent); + const { vaultClient, cleanUp } = await initClients(agent); const vaultPublicKey = new PublicKey(vault); const isOwned = await getIsOwned(agent, vault); @@ -315,6 +366,8 @@ export async function requestWithdrawalFromVault( WithdrawUnit.SHARES, ); + await cleanUp(); + return tx; } catch (e) { throw new Error( @@ -335,7 +388,7 @@ export async function withdrawFromDriftVault( vault: string, ) { try { - const { vaultClient } = initClients(agent); + const { vaultClient, cleanUp } = await initClients(agent); const vaultPublicKey = new PublicKey(vault); const isOwned = await getIsOwned(agent, vault); @@ -347,6 +400,8 @@ export async function withdrawFromDriftVault( const tx = await vaultClient.withdraw(vaultDepositor); + await cleanUp(); + return tx; } catch (e) { // @ts-expect-error - error message is a string @@ -362,11 +417,14 @@ export async function withdrawFromDriftVault( */ async function getIsOwned(agent: SolanaAgentKit, vault: string) { try { - const { vaultClient } = initClients(agent); + const { vaultClient, cleanUp } = await initClients(agent); const vaultPublicKey = new PublicKey(vault); const vaultDetails = await vaultClient.getVault(vaultPublicKey); + const isOwned = vaultDetails.delegate.equals(agent.wallet.publicKey); - return vaultDetails.delegate.equals(agent.wallet.publicKey); + await cleanUp(); + + return isOwned; } catch (e) { // @ts-expect-error - error message is a string throw new Error(`Failed to check if vault is owned: ${e.message}`); @@ -392,7 +450,7 @@ export async function tradeDriftVault( price?: number, ) { try { - const { driftClient, vaultClient } = initClients(agent); + const { driftClient, vaultClient, cleanUp } = await initClients(agent); const [isOwned, vaultDetails, driftLookupTableAccount] = await Promise.all([ getIsOwned(agent, vault), vaultClient.getVault(new PublicKey(vault)), @@ -433,7 +491,7 @@ export async function tradeDriftVault( ); } - const perpMarketIndexAndType = driftClient.getMarketIndexAndType( + const perpMarketIndexAndType = getMarketIndexAndType( `${symbol.toUpperCase()}-PERP`, ); @@ -506,6 +564,8 @@ export async function tradeDriftVault( ), ); + await cleanUp(); + return tx; } catch (e) { // @ts-expect-error - error message is a string