From 315ce64eedeb4e0b5d60fbcca728f83754954c60 Mon Sep 17 00:00:00 2001 From: michaelessiet Date: Fri, 10 Jan 2025 19:46:42 +0100 Subject: [PATCH] feat: drift vault actions --- src/actions/drift/createVault.ts | 65 ++++++++++ src/actions/drift/depositIntoVault.ts | 54 +++++++++ .../drift/requestWithdrawalFromVault.ts | 54 +++++++++ src/actions/drift/tradeDelegatedDriftVault.ts | 114 ++++++++++++++++++ src/actions/drift/updateVault.ts | 76 ++++++++++++ src/actions/drift/withdrawFromVault.ts | 52 ++++++++ src/actions/index.ts | 12 ++ src/actions/mintNFT.ts | 2 +- src/tools/drift_vault.ts | 21 ++-- src/tools/index.ts | 2 + 10 files changed, 444 insertions(+), 8 deletions(-) create mode 100644 src/actions/drift/createVault.ts create mode 100644 src/actions/drift/depositIntoVault.ts create mode 100644 src/actions/drift/requestWithdrawalFromVault.ts create mode 100644 src/actions/drift/tradeDelegatedDriftVault.ts create mode 100644 src/actions/drift/updateVault.ts create mode 100644 src/actions/drift/withdrawFromVault.ts diff --git a/src/actions/drift/createVault.ts b/src/actions/drift/createVault.ts new file mode 100644 index 0000000..8262f0a --- /dev/null +++ b/src/actions/drift/createVault.ts @@ -0,0 +1,65 @@ +import { z } from "zod"; +import type { Action } from "../../types"; +import type { SolanaAgentKit } from "../.."; +import { createVault } from "../../tools/drift_vault"; + +const createDriftVaultAction: Action = { + name: "CREATE_DRIFT_VAULT", + similes: ["create a drift vault", "open a drift vault", "create vault"], + description: + "Create a new drift vault delegating the agents address as the owner.", + examples: [ + [ + { + input: {}, + output: { + status: "success", + message: "Drift vault created successfully", + signature: + "2nFeP7taii3wGVgrWk4YiLMPmhtu3Zg9iXCUu4zGBDadwunHw8reXFxRWT7khbFsQ9JT3zK4RYDLNDFDRYvM3wJk", + }, + explanation: "Create a drift vault", + }, + ], + ], + schema: z.object({ + name: z.string().min(5, "Name must be at least 5 characters"), + // regex matches SOL-SPOT + marketName: z.string().regex(/^([A-Za-z0-9]{2,7})-SPOT$/), + redeemPeriod: z.number().int().min(1, "Redeem period must be at least 1"), + maxTokens: z.number().int().min(100, "Max tokens must be at least 100"), + minDepositAmount: z.number().positive(), + managementFee: z.number().positive().max(20), + profitShare: z.number().positive().max(90).optional().default(5), + handleRate: z.number().optional(), + permissioned: z + .boolean() + .optional() + .describe("Should the vault have a whitelist of not"), + }), + handler: async (agent: SolanaAgentKit, input) => { + try { + const tx = await createVault( + agent, + // @ts-expect-error - zod schema validation + { + ...input, + }, + ); + + return { + status: "success", + message: "Drift vault created successfully", + signature: tx, + }; + } catch (e) { + return { + status: "error", + // @ts-expect-error - e is not a string + message: `Failed to create drift vault: ${e.message}`, + }; + } + }, +}; + +export default createDriftVaultAction; diff --git a/src/actions/drift/depositIntoVault.ts b/src/actions/drift/depositIntoVault.ts new file mode 100644 index 0000000..3c44bb3 --- /dev/null +++ b/src/actions/drift/depositIntoVault.ts @@ -0,0 +1,54 @@ +import { z } from "zod"; +import type { Action } from "../../types"; +import type { SolanaAgentKit } from "../../agent"; +import { depositIntoVault } from "../../tools/drift_vault"; + +const depositIntoDriftVaultAction: Action = { + name: "DEPOSIT_INTO_DRIFT_VAULT", + description: "Deposit funds into an existing drift vault", + similes: ["deposit into drift vault", "add funds to drift vault"], + examples: [ + [ + { + input: { + amount: 100, + vaultAddress: "2nFeP7taii3wGVgrWk4YiLMPmhtu3Zg9iXCUu4zGBD", + }, + output: { + status: "success", + message: "Funds deposited successfully", + signature: + "2nFeP7taii3wGVgrWk4YiLMPmhtu3Zg9iXCUu4zGBDadwunHw8reXFxRWT7khbFsQ9JT3zK4RYDLNDFDRYvM3wJk", + }, + explanation: "Deposit 100 USDC into a drift vault", + }, + ], + ], + schema: z.object({ + vaultAddress: z.string(), + amount: z.number().positive(), + }), + handler: async (agent: SolanaAgentKit, input) => { + try { + const tx = await depositIntoVault( + agent, + input.amount as number, + input.vaultAddress as string, + ); + + return { + status: "success", + message: "Funds deposited successfully", + signature: tx, + }; + } catch (e) { + return { + status: "error", + // @ts-expect-error - error message + message: `Failed to deposit funds: ${e.message}`, + }; + } + }, +}; + +export default depositIntoDriftVaultAction; diff --git a/src/actions/drift/requestWithdrawalFromVault.ts b/src/actions/drift/requestWithdrawalFromVault.ts new file mode 100644 index 0000000..789c58d --- /dev/null +++ b/src/actions/drift/requestWithdrawalFromVault.ts @@ -0,0 +1,54 @@ +import { z } from "zod"; +import type { Action } from "../../types"; +import type { SolanaAgentKit } from "../../agent"; +import { requestWithdrawalFromVault } from "../../tools/drift_vault"; + +const requestWithdrawalFromVaultAction: Action = { + name: "REQUEST_WITHDRAWAL_FROM_DRIFT_VAULT", + description: "Request a withdrawal from an existing drift vault", + similes: ["withdraw from drift vault", "request withdrawal from vault"], + examples: [ + [ + { + input: { + amount: 100, + vaultAddress: "2nFeP7taii", + }, + output: { + status: "success", + message: "Withdrawal request successful", + signature: + "2nFeP7taii3wGVgrWk4YiLMPmhtu3Zg9iXCUu4zGBDadwunHw8reXFxRWT7khbFsQ9JT3zK4RYDLNDFDRYvM3wJk", + }, + explanation: "Request a withdrawal of 100 USDC from a drift vault", + }, + ], + ], + schema: z.object({ + vaultAddress: z.string(), + amount: z.number().positive(), + }), + handler: async (agent: SolanaAgentKit, input) => { + try { + const tx = await requestWithdrawalFromVault( + agent, + input.amount as number, + input.vaultAddress as string, + ); + + return { + status: "success", + message: "Withdrawal request successful", + signature: tx, + }; + } catch (e) { + return { + status: "error", + // @ts-expect-error - error message + message: `Failed to request withdrawal: ${e.message}`, + }; + } + }, +}; + +export default requestWithdrawalFromVaultAction; diff --git a/src/actions/drift/tradeDelegatedDriftVault.ts b/src/actions/drift/tradeDelegatedDriftVault.ts new file mode 100644 index 0000000..a51e143 --- /dev/null +++ b/src/actions/drift/tradeDelegatedDriftVault.ts @@ -0,0 +1,114 @@ +import { z } from "zod"; +import type { Action } from "../../types"; +import type { SolanaAgentKit } from "../../agent"; +import { tradeDriftVault } from "../../tools"; + +const tradeDelegatedDriftVaultAction: Action = { + name: "TRADE_DELEGATED_DRIFT_VAULT", + similes: [ + "trade delegated drift vault", + "trade delegated vault", + "trade vault", + "trade drift vault", + "trade delegated vault", + "trade vault", + "trade drift vault", + "open drift vault trade", + ], + description: "Carry out trades in a Drift vault.", + examples: [ + [ + { + input: { + vaultAddress: "J1S9H3QjnRtBbbuD4HjPV6RpRhwuk4zKbxsnCHuTgh9w", + amount: 100, + symbol: "SOL", + action: "buy", + type: "market", + }, + output: { + status: "success", + message: "Trade successful", + transactionId: "7nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkN", + amount: 100, + symbol: "SOL", + action: "buy", + type: "market", + }, + explanation: "Buy 100 SOL in the vault", + }, + ], + [ + { + input: { + vaultAddress: "J1S9H3QjnRtBbbuD4HjPV6RpRhwuk4zKbxsnCHuTgh9w", + amount: 50, + symbol: "SOL", + action: "sell", + type: "limit", + price: 200, + }, + output: { + status: "success", + message: "Order placed successful", + transactionId: "8nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkM", + amount: 50, + symbol: "SOL", + action: "sell", + type: "limit", + price: 200, + }, + explanation: "Sell 50 SOL in the vault at $200", + }, + ], + ], + schema: z.object({ + vaultAddress: z.string().describe("Address of the Drift vault to trade in"), + amount: z.number().positive().describe("Amount to trade"), + symbol: z.string().describe("Symbol of the token to trade"), + action: z.enum(["buy", "sell"]).describe("Trade action - buy or sell"), + type: z.enum(["market", "limit"]).describe("Trade type - market or limit"), + price: z.number().positive().optional().describe("Price for limit order"), + }), + handler: async (agent: SolanaAgentKit, input) => { + try { + const params = { + vaultAddress: input.vaultAddress as string, + amount: input.amount as number, + symbol: input.symbol as string, + action: input.action as "buy" | "sell", + type: input.type as "market" | "limit", + price: input.price as number | undefined, + }; + + // Carry out the trade + const transactionId = await tradeDriftVault( + agent, + params.vaultAddress, + params.amount, + params.symbol, + params.action, + params.type, + params.price, + ); + + return { + status: "success", + message: + params.type === "limit" + ? "Order placed successfully" + : "Trade successful", + transactionId, + ...params, + }; + } catch (error) { + return { + status: "error", + // @ts-expect-error error is not a string + message: error.message, + }; + } + }, +}; + +export default tradeDelegatedDriftVaultAction; diff --git a/src/actions/drift/updateVault.ts b/src/actions/drift/updateVault.ts new file mode 100644 index 0000000..6aa82f2 --- /dev/null +++ b/src/actions/drift/updateVault.ts @@ -0,0 +1,76 @@ +import { z } from "zod"; +import type { Action } from "../../types"; +import type { SolanaAgentKit } from "../../agent"; +import { updateVault } from "../../tools/drift_vault"; + +const updateDriftVaultAction: Action = { + name: "UPDATE_DRIFT_VAULT", + similes: ["update a drift vault", "modify a drift vault", "update vault"], + description: "Update an existing drift vault with new settings.", + examples: [ + [ + { + input: { + redeemPeriod: 30, + maxTokens: 10000, + minDepositAmount: 10, + managementFee: 5, + profitShare: 10, + handleRate: 0.1, + permissioned: false, + vaultAddress: "2nFeP7taii3wGVgrWk4YiLMPmhtu3Zg9iXCUu4zGBD", + }, + output: { + status: "success", + message: "Drift vault updated successfully", + signature: + "2nFeP7taii3wGVgrWk4YiLMPmhtu3Zg9iXCUu4zGBDadwunHw8reXFxRWT7khbFsQ9JT3zK4RYDLNDFDRYvM3wJk", + }, + explanation: "Update a drift vault", + }, + ], + ], + schema: z.object({ + vaultAddress: z.string(), + name: z.string().min(5, "Name must be at least 5 characters"), + // regex matches SOL-SPOT + marketName: z.string().regex(/^([A-Za-z0-9]{2,7})-SPOT$/), + redeemPeriod: z.number().int().min(1, "Redeem period must be at least 1"), + maxTokens: z.number().int().min(100, "Max tokens must be at least 100"), + minDepositAmount: z.number().positive(), + managementFee: z.number().positive().max(20), + profitShare: z.number().positive().max(90).optional().default(5), + handleRate: z.number().optional(), + permissioned: z + .boolean() + .optional() + .describe("Should the vault have a whitelist of not"), + }), + handler: async (agent: SolanaAgentKit, input) => { + try { + const tx = await updateVault(agent, input.vaultAddress, { + hurdleRate: input.hurdleRate, + maxTokens: input.maxTokens, + minDepositAmount: input.minDepositAmount, + profitShare: input.profitShare, + managementFee: input.managementFee, + permissioned: input.permissioned, + redeemPeriod: input.redeemPeriod, + }); + + return { + status: "success", + message: "Drift vault parameters updated successfully", + signature: tx, + }; + } catch (e) { + return { + status: "error", + // @ts-expect-error - error message + message: `Failed to update drift vault: ${e.message}`, + }; + } + }, +}; + +export default updateDriftVaultAction; diff --git a/src/actions/drift/withdrawFromVault.ts b/src/actions/drift/withdrawFromVault.ts new file mode 100644 index 0000000..b6007f2 --- /dev/null +++ b/src/actions/drift/withdrawFromVault.ts @@ -0,0 +1,52 @@ +import { z } from "zod"; +import type { Action } from "../../types"; +import type { SolanaAgentKit } from "../../agent"; +import { withdrawFromDriftVault } from "../../tools"; + +const withdrawFromVaultAction: Action = { + name: "WITHDRAW_FROM_DRIFT_VAULT", + description: + "Withdraw funds from a vault given the redemption time has elapsed.", + similes: ["withdraw from drift vault", "redeem funds from vault"], + examples: [ + [ + { + input: { + vaultAddress: "2nFeP7taii", + }, + output: { + status: "success", + message: "Withdrawal successful", + signature: + "2nFeP7taii3wGVgrWk4YiLMPmhtu3Zg9iXCUu4zGBDadwunHw8reXFxRWT7khbFsQ9JT3zK4RYDLNDFDRYvM3wJk", + }, + explanation: "Withdraw funds from a drift vault", + }, + ], + ], + schema: z.object({ + vaultAddress: z.string(), + }), + handler: async (agent: SolanaAgentKit, input) => { + try { + const tx = await withdrawFromDriftVault( + agent, + input.vaultAddress as string, + ); + + return { + status: "success", + message: "Withdrawal successful", + signature: tx, + }; + } catch (e) { + return { + status: "error", + // @ts-expect-error - error message + message: `Failed to withdraw funds: ${e.message}`, + }; + } + }, +}; + +export default withdrawFromVaultAction; diff --git a/src/actions/index.ts b/src/actions/index.ts index c974209..217d547 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -30,6 +30,12 @@ import launchPumpfunTokenAction from "./launchPumpfunToken"; import getWalletAddressAction from "./getWalletAddress"; import flashOpenTradeAction from "./flashOpenTrade"; import flashCloseTradeAction from "./flashCloseTrade"; +import createDriftVaultAction from "./drift/createVault"; +import updateDriftVaultAction from "./drift/updateVault"; +import depositIntoDriftVaultAction from "./drift/depositIntoVault"; +import requestWithdrawalFromVaultAction from "./drift/requestWithdrawalFromVault"; +import withdrawFromVaultAction from "./drift/withdrawFromVault"; +import tradeDelegatedDriftVaultAction from "./drift/tradeDelegatedDriftVault"; export const ACTIONS = { WALLET_ADDRESS_ACTION: getWalletAddressAction, @@ -65,6 +71,12 @@ export const ACTIONS = { LAUNCH_PUMPFUN_TOKEN_ACTION: launchPumpfunTokenAction, FLASH_OPEN_TRADE_ACTION: flashOpenTradeAction, FLASH_CLOSE_TRADE_ACTION: flashCloseTradeAction, + CREATE_DRIFT_VAULT_ACTION: createDriftVaultAction, + UPDATE_DRIFT_VAULT_ACTION: updateDriftVaultAction, + DEPOSIT_INTO_DRIFT_VAULT_ACTION: depositIntoDriftVaultAction, + REQUEST_WITHDRAWAL_FROM_DRIFT_VAULT_ACTION: requestWithdrawalFromVaultAction, + WITHDRAW_FROM_DRIFT_VAULT_ACTION: withdrawFromVaultAction, + TRADE_DELEGATED_DRIFT_VAULT_ACTION: tradeDelegatedDriftVaultAction, }; export type { Action, ActionExample, Handler } from "../types/action"; diff --git a/src/actions/mintNFT.ts b/src/actions/mintNFT.ts index f04cfb7..acee758 100644 --- a/src/actions/mintNFT.ts +++ b/src/actions/mintNFT.ts @@ -13,7 +13,7 @@ const mintNFTAction: Action = { "create token", "add nft to collection", ], - description: `Mint a new NFT in a collection on Solana blockchain.`, + description: "Mint a new NFT in a collection on Solana blockchain.", examples: [ [ { diff --git a/src/tools/drift_vault.ts b/src/tools/drift_vault.ts index 6d70bf3..dda6542 100644 --- a/src/tools/drift_vault.ts +++ b/src/tools/drift_vault.ts @@ -165,8 +165,8 @@ export async function createVault( .mul(PERCENTAGE_PRECISION) .div(new BN(100)) .toNumber(), - minDepositAmount: new BN(params.minDepositAmount).mul(spotPrecision), - redeemPeriod: new BN(params.redeemPeriod), + minDepositAmount: numberToSafeBN(params.minDepositAmount, spotPrecision), + redeemPeriod: new BN(params.redeemPeriod * 86400), maxTokens: new BN(params.maxTokens).mul(spotPrecision), managementFee: new BN(params.managementFee) .mul(PERCENTAGE_PRECISION) @@ -214,7 +214,11 @@ export async function updateVault( const vaultDetails = await vaultClient.getVault(vaultPublicKey); const tx = await vaultClient.managerUpdateVault(vaultPublicKey, { - redeemPeriod: new BN(params.redeemPeriod ?? vaultDetails.redeemPeriod), + redeemPeriod: new BN( + params.redeemPeriod + ? params.redeemPeriod * 86400 + : vaultDetails.redeemPeriod, + ), maxTokens: new BN(params.maxTokens ?? vaultDetails.maxTokens), minDepositAmount: new BN( params.minDepositAmount ?? vaultDetails.minDepositAmount, @@ -326,7 +330,10 @@ export async function requestWithdrawalFromVault( @param vault Vault address @returns Promise - The transaction signature of the redemption */ -export async function withdraw(agent: SolanaAgentKit, vault: string) { +export async function withdrawFromDriftVault( + agent: SolanaAgentKit, + vault: string, +) { try { const { vaultClient } = initClients(agent); const vaultPublicKey = new PublicKey(vault); @@ -353,7 +360,7 @@ export async function withdraw(agent: SolanaAgentKit, vault: string) { @param vault Vault address @returns Promise - Whether the vault is owned by the user */ -export async function getIsOwned(agent: SolanaAgentKit, vault: string) { +async function getIsOwned(agent: SolanaAgentKit, vault: string) { try { const { vaultClient } = initClients(agent); const vaultPublicKey = new PublicKey(vault); @@ -375,13 +382,13 @@ export async function getIsOwned(agent: SolanaAgentKit, vault: string) { @param type Type of trade (e.g. "market" or "limit") @param vault Vault address */ -export async function trade( +export async function tradeDriftVault( agent: SolanaAgentKit, + vault: string, amount: number, symbol: string, action: "buy" | "sell", type: "market" | "limit", - vault: string, price?: number, ) { try { diff --git a/src/tools/index.ts b/src/tools/index.ts index 2363e3a..f466f59 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -50,3 +50,5 @@ export * from "./flash_open_trade"; export * from "./flash_close_trade"; export * from "./create_3land_collectible"; + +export * from "./drift_vault";