Merge branch 'main' into quangkeu95/main

This commit is contained in:
aryan
2025-01-18 01:49:33 +05:30
130 changed files with 13458 additions and 4458 deletions

View File

@@ -0,0 +1,55 @@
import { MainnetSpotMarkets } from "@drift-labs/sdk";
import type { Action } from "../../types";
import { z } from "zod";
import {
getAvailableDriftPerpMarkets,
getAvailableDriftSpotMarkets,
} from "../../tools";
const availableDriftMarketsAction: Action = {
name: "AVAILABLE_DRIFT_MARKETS",
description: "Get a list of available drift markets",
similes: [
"get drift markets",
"drift markets",
"available drift markets",
"get available drift perp markets",
"get available spot markets on drift",
],
examples: [
[
{
input: {
marketType: "spot",
},
output: {
status: "success",
message: `The list of available spot markets are ${MainnetSpotMarkets.map((v) => v.symbol).join(", ")}`,
data: MainnetSpotMarkets,
},
explanation: "Get the list of available spot markets/tokens on drift",
},
],
],
schema: z.object({
marketType: z
.enum(["spot", "perp"])
.describe("Type of market to get")
.optional(),
}),
handler: async (agent, input) => {
switch (input.marketType) {
case "perp":
return getAvailableDriftPerpMarkets();
case "spot":
return getAvailableDriftSpotMarkets();
default:
return {
spot: getAvailableDriftSpotMarkets(),
perp: getAvailableDriftPerpMarkets(),
};
}
},
};
export default availableDriftMarketsAction;

View File

@@ -0,0 +1,64 @@
import { z } from "zod";
import type { Action } from "../../types";
import { createDriftUserAccount } from "../../tools";
const createDriftUserAccountAction: Action = {
name: "CREATE_DRIFT_USER_ACCOUNT",
similes: [
"create drift account",
"create drift user account",
"create user account on drift",
],
description: "Create a new user account on Drift protocol",
examples: [
[
{
input: {
amount: 100,
symbol: "SOL",
},
output: {
status: "success",
message: "User account created with 100 SOL successfully deposited",
account: "4xKpN2...",
},
explanation: "Create a new user account with 100 SOL",
},
],
],
schema: z.object({
amount: z
.number()
.positive()
.describe(
"Amount of the token to deposit. In normal token amounts e.g 50 SOL, 100 USDC, etc",
),
symbol: z.string().describe("Symbol of the token to deposit"),
}),
handler: async (agent, input) => {
try {
const res = await createDriftUserAccount(
agent,
input.amount,
input.symbol,
);
return {
status: "success",
message:
res.message ??
`User account created with ${input.amount} ${input.symobl} successfully deposited.`,
account: res.account,
signature: res.txSignature,
};
} catch (e) {
return {
status: "error",
// @ts-expect-error - error message is a string
message: `Failed to create user account: ${e.message}`,
};
}
},
};
export default createDriftUserAccountAction;

View File

@@ -0,0 +1,113 @@
import { z } from "zod";
import type { Action } from "../../types";
import type { SolanaAgentKit } from "../..";
import { createVault } from "../../tools";
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: {
name: "My Drift Vault",
marketName: "SOL-SPOT",
redeemPeriod: 30,
maxTokens: 1000,
minDepositAmount: 100,
managementFee: 10,
profitShare: 5,
hurdleRate: 0.1,
permissioned: false,
},
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")
.describe("Has to be unique. 2 Vaults can not have the same name."),
// regex matches SOL-SPOT
marketName: z
.string()
.describe('Market name must be in the format "TOKEN-SPOT"'),
redeemPeriod: z
.number()
.int()
.min(1, "Redeem period must be at least 1")
.describe(
"Number of days to wait before funds deposited in a vault can be redeemed ",
),
maxTokens: z
.number()
.int()
.min(100, "Max tokens must be at least 100")
.describe(
"The maximum amount of tokens the vault will be accomodating. For example some vaults have a cap at 10 million USDC. This amount should be normal token amounts e.g 50 SOL, 100 USDC, etc",
),
minDepositAmount: z
.number()
.positive()
.describe(
"Minimum deposit amount in normal token values e.g 50 SOL, 100 USDC, etc",
),
managementFee: z
.number()
.positive()
.max(20)
.describe(
"How much of a fee you'll be taking to manage depositors funds. This is in percentage e.g 2 for 2%",
),
profitShare: z
.number()
.positive()
.max(90)
.optional()
.default(5)
.describe(
"How much of the profit you'll be sharing with depositors. This is in percentage e.g 2 for 2%. Defaults to 5%",
),
hurdleRate: 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. Please note down the name of your vault as it is unique and was used to derive your vault address",
vaultName: input.name,
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;

View File

@@ -0,0 +1,58 @@
import { z } from "zod";
import type { Action } from "../../types";
import { depositIntoVault } from "../../tools";
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()
.describe(
"The amount in tokens you'd like to deposit into the vault in normal token amounts e.g 50 SOL, 100 USDC, etc",
),
}),
handler: async (agent, 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;

View File

@@ -0,0 +1,73 @@
import { z } from "zod";
import type { SolanaAgentKit } from "../../agent";
import type { Action } from "../../types";
import { depositToDriftUserAccount } from "../../tools";
const depositToDriftUserAccountAction: Action = {
name: "DEPOSIT_TO_DRIFT_USER_ACCOUNT",
description: "Deposit funds into your drift user account",
similes: [
"deposit into drift user account",
"add funds to drift user account",
"add funds to my drift account",
"deposit collateral into drift account",
],
examples: [
[
{
input: {
amount: 100,
symbol: "usdc",
},
output: {
status: "success",
message: "Funds deposited successfully",
signature:
"2nFeP7taii3wGVgrWk4YiLMPmhtu3Zg9iXCUu4zGBDadwunHw8reXFxRWT7khbFsQ9JT3zK4RYDLNDFDRYvM3wJk",
},
explanation: "Deposit 100 USDC into your drift user account",
},
],
],
schema: z.object({
amount: z
.number()
.positive()
.describe(
"The amount in tokens you'd like to deposit into your drift user account in normal token amounts e.g 50 SOL, 100 USDC, etc",
),
symbol: z
.string()
.toUpperCase()
.describe("The symbol of the token you'd like to deposit"),
repay: z
.boolean()
.optional()
.default(false)
.describe("Whether or not to repay the borrowed funds in the account"),
}),
handler: async (agent: SolanaAgentKit, input) => {
try {
const tx = await depositToDriftUserAccount(
agent,
input.amount as number,
input.symbol as string,
input.repay as boolean,
);
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 depositToDriftUserAccountAction;

View File

@@ -0,0 +1,46 @@
import { z } from "zod";
import type { Action } from "../../types";
import { getVaultAddress } from "../../tools";
const deriveDriftVaultAddressAction: Action = {
name: "DERIVE_DRIFT_VAULT_ADDRESS_ACTION",
similes: ["derive drift vault address", "get drift vault address"],
description: "Derive a drift vault address from the vaults name",
examples: [
[
{
input: {
name: "My Drift Vault",
},
output: {
status: "success",
message: "Vault address derived successfully",
address: "2nFeP7taii3wGVgrWk4YiLMPmhtu3Zg9iXCUu4zGBD",
},
explanation: "Derive a drift vault address",
},
],
],
schema: z.object({
name: z.string().describe("The name of the vault to derive the address of"),
}),
handler: async (agent, input) => {
try {
const address = await getVaultAddress(agent, input.name as string);
return {
status: "success",
message: "Vault address derived successfully",
address,
};
} catch (e) {
return {
status: "error",
// @ts-expect-error - error message
message: `Failed to derive vault address: ${e.message}`,
};
}
},
};
export default deriveDriftVaultAddressAction;

View File

@@ -0,0 +1,53 @@
import { z } from "zod";
import { doesUserHaveDriftAccount } from "../../tools";
import type { Action } from "../../types";
export const doesUserHaveDriftAccountAction: Action = {
name: "DOES_USER_HAVE_DRIFT_ACCOUNT",
description: "Check if a user has a Drift account",
similes: [
"check if user has drift account",
"check if user has account on drift",
"do I have an account on drift",
],
examples: [
[
{
input: {},
output: {
status: "success",
message: "Nice! You have a Drift account",
account: "4xKpN2...",
},
explanation: "Check if a user has a Drift account",
},
],
],
schema: z.object({}),
handler: async (agent) => {
try {
const res = await doesUserHaveDriftAccount(agent);
if (!res.hasAccount) {
return {
status: "error",
message: "You do not have a Drift account",
};
}
return {
status: "success",
message: "Nice! You have a Drift account",
account: res.account,
};
} catch (e) {
return {
status: "error",
// @ts-expect-error - error message is a string
message: `Failed to check if you have a Drift account: ${e.message}`,
};
}
},
};
export default doesUserHaveDriftAccountAction;

View File

@@ -0,0 +1,39 @@
import { z } from "zod";
import type { Action } from "../../types";
import { driftUserAccountInfo } from "../../tools";
const driftUserAccountInfoAction: Action = {
name: "DRIFT_USER_ACCOUNT_INFO",
similes: ["get drift user account info", "get drift account info"],
description: "Get information about your drift account",
examples: [
[
{
input: {},
explanation: "Get information about your drift account",
output: {
status: "success",
data: {},
},
},
],
],
schema: z.object({}),
handler: async (agent) => {
try {
const accountInfo = await driftUserAccountInfo(agent);
return {
status: "success",
data: accountInfo,
};
} catch (e) {
return {
status: "error",
// @ts-expect-error - error message is a string
message: `Failed to get drift account info: ${e.message}`,
};
}
},
};
export default driftUserAccountInfoAction;

View File

@@ -0,0 +1,65 @@
import { z } from "zod";
import type { Action } from "../../types";
import { getEntryQuoteOfPerpTrade } from "../../tools";
const entryQuoteOfPerpTradeAction: Action = {
name: "DRIFT_GET_ENTRY_QUOTE_OF_PERP_TRADE_ACTION",
description: "Get the entry quote of a perpetual trade on Drift",
similes: [
"get the entry quote of a perpetual trade on drift",
"get the entry quote of a perp trade on drift",
"get the entry quote of the BTC-PERP trade on drift",
"get the entry quote of the SOL-PERP trade on drift",
"get the entry quote of a 1000 USDC long on the SOL-PERP market",
"get the entry quote of a 1000 USDC short on the SOL-PERP market",
"quote for a $1000 long on the BTC-PERP market",
],
examples: [
[
{
input: {
marketSymbol: "BTC-PERP",
type: "long",
amount: 1000,
},
output: {
status: "success",
data: {
entryPrice: 100000,
priceImpact: 0.0001,
bestPrice: 100001,
worstPrice: 99999,
baseFilled: 1000,
quoteFilled: 1000,
},
},
explanation:
"Get the entry quote of a $1000 long on the BTC-PERP market",
},
],
],
schema: z.object({
marketSymbol: z.string().describe("Symbol of the perpetual market"),
type: z.enum(["long", "short"]).describe("Type of trade"),
amount: z.number().positive().describe("Amount to trade"),
}),
handler: async (agent, input) => {
try {
const data = await getEntryQuoteOfPerpTrade(
input.marketSymbol,
input.amount,
input.type,
);
return data;
} catch (e) {
return {
status: "error",
// @ts-expect-error error is not a string
message: e.message,
};
}
},
};
export default entryQuoteOfPerpTradeAction;

View File

@@ -0,0 +1,52 @@
import { z } from "zod";
import type { Action } from "../../types";
import { getLendingAndBorrowAPY } from "../../tools";
const lendAndBorrowAPYAction: Action = {
name: "DRIFT_GET_LEND_AND_BORROW_APY_ACTION",
description: "Get the lending and borrowing APY (in %) of a token on Drift",
similes: [
"get the lending and borrowing APY of a token on drift",
"get the lending and borrowing APY of a token on drift",
"get the lending and borrowing APY of the USDC token on drift",
"get the lending and borrowing APY of the SOL token on drift",
],
examples: [
[
{
input: {
symbol: "USDC",
},
output: {
status: "success",
data: {
lendingAPY: 10,
borrowingAPY: 12.1,
},
},
explanation: "Get the lending and borrowing APY of the USDC token",
},
],
],
schema: z.object({
symbol: z.string().describe("Symbol of the token"),
}),
handler: async (agent, input) => {
try {
const data = await getLendingAndBorrowAPY(agent, input.symbol);
return {
status: "success",
data,
};
} catch (e) {
return {
status: "error",
// @ts-expect-error error is not a string
message: e.message,
};
}
},
};
export default lendAndBorrowAPYAction;

View File

@@ -0,0 +1,61 @@
import { z } from "zod";
import type { Action } from "../../types";
import { calculatePerpMarketFundingRate } from "../../tools";
const perpMarktetFundingRateAction: Action = {
name: "DRIFT_PERP_MARKET_FUNDING_RATE_ACTION",
description: "Get the funding rate of a perpetual market on Drift",
similes: [
"get the yearly funding rate of a perpetual market on drift",
"get the funding rate of a perp market on drift",
"get the hourly funding rate of a perpetual market on drift",
"get the funding rate of the BTC-PERP market on drift",
"get the funding rate of the SOL-PERP market on drift",
],
examples: [
[
{
input: {
marketSymbol: "BTC-PERP",
},
output: {
status: "success",
data: {
longRate: 0.0001,
shortRate: 0.0002,
},
},
explanation: "Get the funding rate of the BTC-PERP market",
},
],
],
schema: z.object({
marketSymbol: z
.string()
.toUpperCase()
.describe("Symbol of the perpetual market"),
period: z.enum(["year", "hour"]).default("hour").optional(),
}),
handler: async (agent, input) => {
try {
const data = await calculatePerpMarketFundingRate(
agent,
input.marketSymbol,
input.period,
);
return {
status: "success",
data,
};
} catch (e) {
return {
status: "error",
// @ts-expect-error error is not a string
message: e.message,
};
}
},
};
export default perpMarktetFundingRateAction;

View File

@@ -0,0 +1,61 @@
import { z } from "zod";
import type { Action } from "../../types";
import { requestUnstakeFromDriftInsuranceFund } from "../../tools";
const requestUnstakeFromDriftInsuranceFundAction: Action = {
name: "REQUEST_UNSTAKE_FROM_DRIFT_INSURANCE_FUND_ACTION",
description:
"Request to unstake a certain amount of a token from the Drift Insurance Fund",
similes: [
"request an unstake from the drift insurance fund",
"request to unstake an amount from the drift insurance fund",
],
examples: [
[
{
input: {
amount: 100,
symbol: "SOL",
},
output: {
status: "success",
message: "Requested to unstake 100 SOL from the Drift Insurance Fund",
signature: "4FdasklhiIHyOI",
},
explanation: "Request to unstake 100 SOL from the Drift Insurance Fund",
},
],
],
schema: z.object({
amount: z
.number()
.positive()
.describe("Amount to unstake in normal units e.g 50 === 50 SOL"),
symbol: z.string().describe("Symbol of the token to unstake"),
}),
handler: async (agent, input) => {
try {
const tx = await requestUnstakeFromDriftInsuranceFund(
agent,
input.amount,
input.symbol,
);
return {
status: "success",
message: `Requested to unstake ${input.amount} ${input.symbol} from the Drift Insurance Fund`,
data: {
signature: tx,
},
};
} catch (e) {
return {
status: "error",
// @ts-expect-error error is not a string
message: e.message,
};
}
},
};
export default requestUnstakeFromDriftInsuranceFundAction;

View File

@@ -0,0 +1,59 @@
import { z } from "zod";
import type { Action } from "../../types";
import type { SolanaAgentKit } from "../../agent";
import { requestWithdrawalFromVault } from "../../tools";
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()
.describe(
"Amount of shares you would like to withdraw from the vault in normal token amounts e.g 50 SOL, 100 USDC, etc",
),
}),
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;

View File

@@ -0,0 +1,59 @@
import { z } from "zod";
import type { Action } from "../../types";
import { stakeToDriftInsuranceFund } from "../../tools";
const stakeToDriftInsuranceFundAction: Action = {
name: "STAKE_TO_DRIFT_INSURANCE_FUND_ACTION",
description: "Stake a token to Drift Insurance Fund",
similes: ["Stake a token to Drift Insurance Fund"],
examples: [
[
{
input: {
amount: 100,
symbol: "SOL",
},
output: {
status: "success",
message: "Staked 100 SOL to the Drift Insurance Fund",
data: {
signature: "signature",
},
},
explanation: "Stake 100 SOL to the Drift Insurance Fund",
},
],
],
schema: z.object({
amount: z
.number()
.positive()
.describe("Amount to stake in normal units e.g 50 === 50 SOL"),
symbol: z.string().describe("Symbol of the token stake"),
}),
handler: async (agent, input) => {
try {
const tx = await stakeToDriftInsuranceFund(
agent,
input.amount,
input.symbol,
);
return {
status: "sucess",
message: `Staked ${input.amount} ${input.symbol} to the Drift Insurance Fund`,
data: {
signature: tx,
},
};
} catch (error) {
return {
status: "error",
// @ts-expect-error error is not a string
message: error.message,
};
}
},
};
export default stakeToDriftInsuranceFundAction;

View File

@@ -0,0 +1,78 @@
import { z } from "zod";
import type { Action } from "../../types";
import { swapSpotToken } from "../../tools";
const driftSpotTokenSwapAction: Action = {
name: "DRIFT_SPOT_TOKEN_SWAP_ACTION",
description: "Swap a token for another token on Drift",
similes: [
"swap a token for another token on drift",
"exchange a token for another token on drift",
"trade a token for another token on drift",
"swap usdc to 5 sol on drift (in this case 5 sol is the toAmount)",
"swap 5 usdt to DRIFT on drift (in this case 5 usdt is the fromAmount)",
],
examples: [
[
{
input: {
fromSymbol: "SOL",
toSymbol: "USDC",
fromAmount: 100,
},
output: {
status: "success",
message: "Swapped 100 SOL for USDC on Drift",
signature: "4FdasklhiIHyOI",
},
explanation: "Swap 100 SOL for USDC on Drift",
},
],
],
schema: z.object({
fromSymbol: z.string().describe("Symbol of the token to swap from"),
toSymbol: z.string().describe("Symbol of the token to swap to"),
fromAmount: z
.number()
.positive()
.describe("Amount to swap from e.g 50 === 50 SOL")
.optional(),
toAmount: z
.number()
.positive()
.describe("Amount to swap to e.g 5000 === 5000 USDC")
.optional(),
slippage: z
.number()
.positive()
.describe("Slippage tolerance in percentage e.g 0.5 === 0.5%")
.default(0.5),
}),
handler: async (agent, input) => {
try {
const tx = await swapSpotToken(agent, {
fromSymbol: input.fromSymbol,
toSymbol: input.toSymbol,
fromAmount: input.fromAmount,
toAmount: input.toAmount,
slippage: input.slippage,
});
return {
status: "success",
message: `Swapped ${input.fromAmount} ${input.fromSymbol} for ${input.toAmount} ${input.toSymbol} on Drift`,
data: {
signature: tx,
},
};
} catch (e) {
return {
status: "error",
// @ts-expect-error error is not a string
message: e.message,
};
}
},
};
export default driftSpotTokenSwapAction;

View File

@@ -0,0 +1,123 @@
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 in normal token amounts e.g 50 SOL, 100 USDC, etc",
),
symbol: z.string().describe("Symbol of the token to trade"),
action: z.enum(["long", "short"]).describe("Trade action - long or short"),
type: z.enum(["market", "limit"]).describe("Trade type - market or limit"),
price: z
.number()
.positive()
.optional()
.describe("USD 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 "long" | "short",
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;

View File

@@ -0,0 +1,95 @@
import { z } from "zod";
import type { Action } from "../../types";
import { driftPerpTrade } from "../../tools";
export const tradeDriftPerpAccountAction: Action = {
name: "TRADE_DRIFT_PERP_ACCOUNT",
similes: [
"trade drift perp account",
"trade drift perp",
"trade drift perpetual account",
"trade perp account",
"trade account",
],
description: "Trade a perpetual account on Drift protocol",
examples: [
[
{
input: {
amount: 100,
symbol: "SOL",
action: "long",
type: "market",
},
output: {
status: "success",
message: "Trade successful",
},
explanation: "Open a $100 long position on SOL.",
},
],
[
{
input: {
amount: 50,
symbol: "BTC",
action: "short",
type: "limit",
price: 50000,
},
output: {
status: "success",
message: "Trade successful",
},
explanation: "$50 short position on BTC at $50,000.",
},
],
],
schema: z.object({
amount: z
.number()
.positive()
.describe(
"The amount of the token to trade in normal token amounts e.g 50 SOL, 100 USDC",
),
symbol: z
.string()
.toUpperCase()
.describe("Symbol of the token to open a position on "),
action: z
.enum(["long", "short"])
.describe(
"The action you would like to carry out whether it be a long or a short",
),
type: z
.enum(["market", "limit"])
.describe(
"The type of trade you would like to open, market or limit order",
),
price: z.number().positive().optional().describe("USD price of the token"),
}),
handler: async (agent, input) => {
try {
const signature = await driftPerpTrade(agent, {
action: input.action,
amount: input.amount,
symbol: input.symbol,
type: input.type,
price: input.price,
});
return {
status: "success",
signature: signature,
};
} catch (e) {
return {
status: "error",
// @ts-expect-error - error message is a string
message: `Failed to trade perp account: ${e.message}`,
};
}
},
};
export default tradeDriftPerpAccountAction;

View File

@@ -0,0 +1,51 @@
import { z } from "zod";
import type { Action } from "../../types";
import { unstakeFromDriftInsuranceFund } from "../../tools";
const unstakeFromDriftInsuranceFundAction: Action = {
name: "UNSTAKE_FROM_DRIFT_INSURANCE_FUND_ACTION",
description:
"Unstake requested unstake token from the Drift Insurance fund once the cool period has elapsed",
similes: [
"unstake from the drift insurance fund",
"withdraw from the drift insurance fund",
"take out funds from the drift insurance fund",
],
examples: [
[
{
input: {
symbol: "SOL",
},
output: {
status: "success",
message: "Unstaked your SOL from the Drift Insurance Fund",
signature: "4FdasklhiIHyOI",
},
explanation: "Unstake SOL from the Drift Insurance Fund",
},
],
],
schema: z.object({
symbol: z.string().describe("Symbol of the token to unstake"),
}),
handler: async (agent, input) => {
try {
const tx = await unstakeFromDriftInsuranceFund(agent, input.symbol);
return {
status: "success",
message: `Unstaked your ${input.symbol} from the Drift Insurance Fund`,
signature: tx,
};
} catch (e) {
return {
status: "error",
// @ts-expect-error error is not a string
message: e.message,
};
}
},
};
export default unstakeFromDriftInsuranceFundAction;

View File

@@ -0,0 +1,53 @@
import { z } from "zod";
import type { Action } from "../../types";
import { updateVaultDelegate } from "../../tools";
const updateDriftVaultDelegateAction: Action = {
name: "UPDATE_DRIFT_VAULT_DELEGATE_ACTION",
similes: ["update drift vault delegate", "change drift vault delegate"],
description: "Update the delegate of a drift vault",
examples: [
[
{
input: {
vaultAddress: "2nFeP7taii3wGVgrWk4YiLMPmhtu3Zg9iXCUu4zGBD",
newDelegate: "2nFeP7tai",
},
output: {
status: "success",
message: "Vault delegate updated successfully",
signature:
"2nFeP7taii3wGVgrWk4YiLMPmhtu3Zg9iXCUu4zGBDadwunHw8reXFxRWT7khbFsQ9JT3zK4RYDLNDFDRYvM3wJk",
},
explanation: "Update the delegate of a drift vault to another address",
},
],
],
schema: z.object({
vaultAddress: z.string().describe("vault's address"),
newDelegate: z.string().describe("new address to delegate the vault to"),
}),
handler: async (agent, input) => {
try {
const tx = await updateVaultDelegate(
agent,
input.vaultAddress as string,
input.newDelegate as string,
);
return {
status: "success",
message: "Vault delegate updated successfully",
signature: tx,
};
} catch (e) {
return {
status: "error",
// @ts-expect-error - error message
message: `Failed to update vault delegate: ${e.message}`,
};
}
},
};
export default updateDriftVaultDelegateAction;

View File

@@ -0,0 +1,106 @@
import { z } from "zod";
import type { Action } from "../../types";
import type { SolanaAgentKit } from "../../agent";
import { updateVault } from "../../tools";
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").optional(),
// regex matches SOL-SPOT
marketName: z
.string()
.regex(/^([A-Za-z0-9]{2,7})-SPOT$/)
.optional(),
redeemPeriod: z
.number()
.int()
.min(1, "Redeem period must be at least 1 day")
.optional(),
maxTokens: z
.number()
.int()
.min(100, "Max tokens must be at least be 100 units")
.optional()
.describe(
"The maximum number of tokens the vault is willing to accept and manage",
),
minDepositAmount: z
.number()
.positive()
.optional()
.describe(
"The minimum amount that is allowed to be deposited into the vault in normal token amounts e.g 10 USDC",
),
managementFee: z
.number()
.positive()
.max(20)
.optional()
.describe("The percentage fee the vault takes for asset management"),
profitShare: z
.number()
.positive()
.max(90)
.optional()
.describe("Profit share in percentage e.g 2 === 2%"),
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;

View File

@@ -0,0 +1,57 @@
import { z } from "zod";
import type { Action } from "../../types";
import { getVaultInfo } from "../../tools";
import type { SolanaAgentKit } from "../../agent";
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: {
vaultNameOrAddress: "test-vault",
},
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({
vaultNameOrAddress: z.string().describe("Name or address of the vault"),
}),
handler: async (agent: SolanaAgentKit, input) => {
try {
const vaultInfo = await getVaultInfo(agent, input.vaultNameOrAddress);
return {
status: "success",
message: "Vault info retrieved successfully",
data: vaultInfo,
};
} catch (e) {
return {
status: "error",
// @ts-expect-error - error message
message: `Failed to retrieve vault info: ${e.message}`,
};
}
},
};
export default vaultInfoAction;

View File

@@ -0,0 +1,77 @@
import { z } from "zod";
import type { Action } from "../../types";
import { withdrawFromDriftUserAccount } from "../../tools";
const withdrawFromDriftAccountAction: Action = {
name: "WITHDRAW_OR_BORROW_FROM_DRIFT_ACCOUNT",
description: "Withdraw funds from your drift account",
similes: [
"withdraw from drift account",
"withdraw funds from drift account",
"withdraw funds from my drift account",
"borrow from drift account",
"borrow funds from my drift account",
"borrow from drift",
"withdraw from drift",
],
examples: [
[
{
input: {
amount: 100,
symbol: "usdc",
},
output: {
status: "success",
message: "Funds withdrawn successfully",
signature:
"2nFeP7taii3wGVgrWk4YiLMPmhtu3Zg9iXCUu4zGBDadwunHw8reXFxRWT7khbFsQ9JT3zK4RYDLNDFDRYvM3wJk",
},
explanation: "Withdraw 100 USDC from your drift account",
},
],
],
schema: z.object({
amount: z
.number()
.positive()
.describe(
"The amount in tokens you'd like to withdraw from your drift account in normal token amounts, e.g 50 SOL, 100 USDC, etc",
),
symbol: z
.string()
.toUpperCase()
.describe("The symbol of the token you'd like to withdraw"),
isBorrow: z
.boolean()
.optional()
.default(false)
.describe(
"Whether or not to borrow funds based on collateral provided instead of withdrawing",
),
}),
handler: async (agent, input) => {
try {
const tx = await withdrawFromDriftUserAccount(
agent,
input.amount,
input.symbol,
input.isBorrow,
);
return {
status: "success",
message: "Funds withdrawn successfully",
signature: tx,
};
} catch (e) {
return {
status: "error",
// @ts-expect-error - error message is a string
message: `Failed to withdraw funds: ${e.message}`,
};
}
},
};
export default withdrawFromDriftAccountAction;

View File

@@ -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().describe("Vault's address"),
}),
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;

View File

@@ -1,3 +1,4 @@
import tokenBalancesAction from "./tokenBalances";
import deployTokenAction from "./metaplex/deployToken";
import balanceAction from "./solana/balance";
import transferAction from "./solana/transfer";
@@ -13,6 +14,8 @@ import stakeWithJupAction from "./jupiter/stakeWithJup";
import stakeWithSolayerAction from "./solayer/stakeWithSolayer";
import registerDomainAction from "./sns/registerDomain";
import lendAssetAction from "./lulo/lendAsset";
import luloLendAction from "./lulo/luloLend";
import luloWithdrawAction from "./lulo/luloWithdraw";
import createGibworkTaskAction from "./gibwork/createGibworkTask";
import resolveSolDomainAction from "./sns/resolveSolDomain";
import pythFetchPriceAction from "./pyth/pythFetchPrice";
@@ -43,9 +46,36 @@ import getAssetsByOwnerAction from "./helius/getAssetsbyOwner";
import getWebhookAction from "./helius/getWebhook";
import parseSolanaTransactionAction from "./helius/parseTransaction";
import sendTransactionWithPriorityFeeAction from "./helius/sendTransactionWithPriority";
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";
import vaultInfoAction from "./drift/vaultInfo";
import createDriftUserAccountAction from "./drift/createDriftUserAccount";
import tradeDriftPerpAccountAction from "./drift/tradePerpAccount";
import doesUserHaveDriftAccountAction from "./drift/doesUserHaveDriftAccount";
import depositToDriftUserAccountAction from "./drift/depositToDriftUserAccount";
import withdrawFromDriftAccountAction from "./drift/withdrawFromDriftAccount";
import driftUserAccountInfoAction from "./drift/driftUserAccountInfo";
import deriveDriftVaultAddressAction from "./drift/deriveVaultAddress";
import updateDriftVaultDelegateAction from "./drift/updateDriftVaultDelegate";
import availableDriftMarketsAction from "./drift/availableMarkets";
import stakeToDriftInsuranceFundAction from "./drift/stakeToDriftInsuranceFund";
import requestUnstakeFromDriftInsuranceFundAction from "./drift/requestUnstakeFromDriftInsuranceFund";
import unstakeFromDriftInsuranceFundAction from "./drift/unstakeFromDriftInsuranceFund";
import driftSpotTokenSwapAction from "./drift/swapSpotToken";
import perpMarktetFundingRateAction from "./drift/perpMarketFundingRate";
import entryQuoteOfPerpTradeAction from "./drift/entryQuoteOfPerpTrade";
import lendAndBorrowAPYAction from "./drift/getLendAndBorrowAPY";
import getVoltrPositionValuesAction from "./voltr/getPositionValues";
import depositVoltrStrategyAction from "./voltr/depositStrategy";
import withdrawVoltrStrategyAction from "./voltr/withdrawStrategy";
export const ACTIONS = {
WALLET_ADDRESS_ACTION: getWalletAddressAction,
TOKEN_BALANCES_ACTION: tokenBalancesAction,
DEPLOY_TOKEN_ACTION: deployTokenAction,
BALANCE_ACTION: balanceAction,
TRANSFER_ACTION: transferAction,
@@ -61,6 +91,8 @@ export const ACTIONS = {
STAKE_WITH_SOLAYER_ACTION: stakeWithSolayerAction,
REGISTER_DOMAIN_ACTION: registerDomainAction,
LEND_ASSET_ACTION: lendAssetAction,
LULO_LEND_ACTION: luloLendAction,
LULO_WITHDRAW_ACTION: luloWithdrawAction,
CREATE_GIBWORK_TASK_ACTION: createGibworkTaskAction,
RESOLVE_SOL_DOMAIN_ACTION: resolveSolDomainAction,
PYTH_FETCH_PRICE_ACTION: pythFetchPriceAction,
@@ -91,6 +123,33 @@ export const ACTIONS = {
GET_WEBHOOK_ACTION: getWebhookAction,
PARSE_TRANSACTION_ACTION: parseSolanaTransactionAction,
SEND_TRANSACTION_WITH_PRIORITY_ACTION: sendTransactionWithPriorityFeeAction,
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,
DRIFT_VAULT_INFO_ACTION: vaultInfoAction,
CREATE_DRIFT_USER_ACCOUNT_ACTION: createDriftUserAccountAction,
TRADE_DRIFT_PERP_ACCOUNT_ACTION: tradeDriftPerpAccountAction,
DOES_USER_HAVE_DRIFT_ACCOUNT_ACTION: doesUserHaveDriftAccountAction,
DEPOSIT_TO_DRIFT_USER_ACCOUNT_ACTION: depositToDriftUserAccountAction,
WITHDRAW_OR_BORROW_FROM_DRIFT_ACCOUNT_ACTION: withdrawFromDriftAccountAction,
DRIFT_USER_ACCOUNT_INFO_ACTION: driftUserAccountInfoAction,
DERIVE_DRIFT_VAULT_ADDRESS_ACTION: deriveDriftVaultAddressAction,
UPDATE_DRIFT_VAULT_DELEGATE_ACTION: updateDriftVaultDelegateAction,
AVAILABLE_DRIFT_MARKETS_ACTION: availableDriftMarketsAction,
STAKE_TO_DRIFT_INSURANCE_FUND_ACTION: stakeToDriftInsuranceFundAction,
REQUEST_UNSTAKE_FROM_DRIFT_INSURANCE_FUND_ACTION:
requestUnstakeFromDriftInsuranceFundAction,
UNSTAKE_FROM_DRIFT_INSURANCE_FUND_ACTION: unstakeFromDriftInsuranceFundAction,
DRIFT_SPOT_TOKEN_SWAP_ACTION: driftSpotTokenSwapAction,
DRIFT_PERP_MARKET_FUNDING_RATE_ACTION: perpMarktetFundingRateAction,
DRIFT_GET_ENTRY_QUOTE_OF_PERP_TRADE_ACTION: entryQuoteOfPerpTradeAction,
DRIFT_GET_LEND_AND_BORROW_APY_ACTION: lendAndBorrowAPYAction,
GET_VOLTR_POSITION_VALUES_ACTION: getVoltrPositionValuesAction,
DEPOSIT_VOLTR_STRATEGY_ACTION: depositVoltrStrategyAction,
WITHDRAW_VOLTR_STRATEGY_ACTION: withdrawVoltrStrategyAction,
};
export type { Action, ActionExample, Handler } from "../types/action";

View File

@@ -0,0 +1,62 @@
import { Action } from "../../types/action";
import { SolanaAgentKit } from "../../agent";
import { z } from "zod";
import { luloLend } from "../../tools/lulo";
const luloLendAction: Action = {
name: "LULO_LEND",
similes: [
"lend USDC with lulo",
"lend PYUSD with lulo",
"lend USDS with lulo",
"lend USDT with lulo",
"lend SQL with lulo",
"lend jitoSQL with lulo",
"lend bSQL with lulo",
"lend mSQL with lulo",
"lend BONK with lulo",
"lend JUP with lulo",
],
description: "Lend SPL tokens using Lulo protocol",
examples: [
[
{
input: {
mintAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
amount: 100,
},
output: {
status: "success",
signature: "4xKpN2...",
message: "Successfully lend 100 USDC",
},
explanation: "Lend 100 USDC on Lulo",
},
],
],
schema: z.object({
mintAddress: z.string().describe("SPL Mint address"),
amount: z.number().positive().describe("Amount to lend"),
}),
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
try {
const mintAddress = input.mintAddress as string;
const amount = input.amount as number;
const response = await luloLend(agent, mintAddress, amount);
return {
status: "success",
signature: response,
message: `Successfully lend ${amount} of token ${mintAddress}`,
};
} catch (error: any) {
return {
status: "error",
message: `Lend failed: ${error.message}`,
};
}
},
};
export default luloLendAction;

View File

@@ -0,0 +1,62 @@
import { Action } from "../../types/action";
import { SolanaAgentKit } from "../../agent";
import { z } from "zod";
import { luloWithdraw } from "../../tools/lulo";
const luloWithdrawAction: Action = {
name: "LULO_WITHDRAW",
similes: [
"withdraw USDC with lulo",
"withdraw PYUSD with lulo",
"withdraw USDS with lulo",
"withdraw USDT with lulo",
"withdraw SQL with lulo",
"withdraw jitoSQL with lulo",
"withdraw bSQL with lulo",
"withdraw mSQL with lulo",
"withdraw BONK with lulo",
"withdraw JUP with lulo",
],
description: "Withdraw SPL tokens using Lulo protocol",
examples: [
[
{
input: {
mintAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
amount: 100,
},
output: {
status: "success",
signature: "4xKpN2...",
message: "Successfully withdraw 100 USDC",
},
explanation: "Withdraw 100 USDC on Lulo",
},
],
],
schema: z.object({
mintAddress: z.string().describe("SPL Mint address"),
amount: z.number().positive().describe("Amount to lend"),
}),
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
try {
const mintAddress = input.mintAddress as string;
const amount = input.amount as number;
const response = await luloWithdraw(agent, mintAddress, amount);
return {
status: "success",
signature: response,
message: `Successfully withdraw ${amount} of token ${mintAddress}`,
};
} catch (error: any) {
return {
status: "error",
message: `Withdraw failed: ${error.message}`,
};
}
},
};
export default luloWithdrawAction;

View File

@@ -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: [
[
{

View File

@@ -1,6 +1,6 @@
import { PublicKey } from "@solana/web3.js";
import { Action } from "../../types/action";
import { SolanaAgentKit } from "../../agent";
import type { Action } from "../../types/action";
import type { SolanaAgentKit } from "../../agent";
import { z } from "zod";
import { get_balance } from "../../tools";

View File

@@ -0,0 +1,80 @@
import { PublicKey } from "@solana/web3.js";
import type { Action } from "../types/action";
import type { SolanaAgentKit } from "../agent";
import { z } from "zod";
import { get_token_balance } from "../tools";
const tokenBalancesAction: Action = {
name: "TOKEN_BALANCE_ACTION",
similes: [
"check token balances",
"get wallet token balances",
"view token balances",
"show token balances",
"check token balance",
],
description: `Get the token balances of a Solana wallet.
If you want to get the balance of your wallet, you don't need to provide the wallet address.`,
examples: [
[
{
input: {},
output: {
status: "success",
balance: {
sol: 100,
tokens: [
{
tokenAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
name: "USD Coin",
symbol: "USDC",
balance: 100,
decimals: 9,
},
],
},
},
explanation: "Get token balances of the wallet",
},
],
[
{
input: {
walletAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
},
output: {
status: "success",
balance: {
sol: 100,
tokens: [
{
tokenAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
name: "USD Coin",
symbol: "USDC",
balance: 100,
decimals: 9,
},
],
},
},
explanation: "Get address token balance",
},
],
],
schema: z.object({
walletAddress: z.string().optional(),
}),
handler: async (agent: SolanaAgentKit, input) => {
const balance = await get_token_balance(
agent,
input.tokenAddress && new PublicKey(input.tokenAddress),
);
return {
status: "success",
balance: balance,
};
},
};
export default tokenBalancesAction;

View File

@@ -0,0 +1,83 @@
import { Action } from "../../types/action";
import { SolanaAgentKit } from "../../agent";
import { z } from "zod";
import { PublicKey } from "@solana/web3.js";
import { BN } from "bn.js";
const depositVoltrStrategyAction: Action = {
name: "DEPOSIT_VOLTR_STRATEGY",
similes: [
"deposit to voltr strategy",
"add funds to voltr vault strategy",
"invest in voltr strategy",
"deposit assets to voltr",
"contribute to voltr vault",
"fund voltr strategy",
],
description: "Deposit assets into a specific strategy within a Voltr vault",
examples: [
[
{
input: {
depositAmount: "1000000000", // 1 USDC with 6 decimals
vault: "7opUkqYtxmQRriZvwZkPcg6LqmGjAh1RSEsVrdsGDx5K",
strategy: "9ZQQYvr4x7AMqd6abVa1f5duGjti5wk1MHsX6hogPsLk",
},
output: {
status: "success",
vault: "7opUkqYtxmQRriZvwZkPcg6LqmGjAh1RSEsVrdsGDx5K",
strategy: "9ZQQYvr4x7AMqd6abVa1f5duGjti5wk1MHsX6hogPsLk",
signature: "2ZE7Rz...",
message: "Successfully deposited 1000000000 into strategy",
},
explanation: "Deposit 1 USDC into a Voltr vault strategy",
},
],
],
schema: z.object({
depositAmount: z
.string()
.min(1)
.describe("The amount to deposit (in base units including decimals)"),
vault: z
.string()
.min(1)
.describe(
"The public key of the Voltr source vault to take assets from, e.g., 'Ga27...'",
),
strategy: z
.string()
.min(1)
.describe(
"The public key of the initialized target strategy to deposit into, e.g., 'Jheh...'",
),
}),
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
try {
const depositAmount = new BN(input.depositAmount);
const vault = new PublicKey(input.vault);
const strategy = new PublicKey(input.strategy);
const signature = await agent.voltrDepositStrategy(
depositAmount,
vault,
strategy,
);
return {
status: "success",
vault: vault.toBase58(),
strategy: strategy.toBase58(),
signature,
message: `Successfully deposited ${input.depositAmount} into strategy`,
};
} catch (error: any) {
return {
status: "error",
message: `Failed to deposit into strategy: ${error.message}`,
};
}
},
};
export default depositVoltrStrategyAction;

View File

@@ -0,0 +1,74 @@
import { Action } from "../../types/action";
import { SolanaAgentKit } from "../../agent";
import { z } from "zod";
import { PublicKey } from "@solana/web3.js";
const getVoltrPositionValuesAction: Action = {
name: "GET_VOLTR_POSITION_VALUES",
similes: [
"get voltr vault value",
"check voltr position",
"get voltr vault assets",
"view voltr holdings",
"check voltr portfolio",
"get voltr vault breakdown",
],
description:
"Get the current position values and total assets for a Voltr vault",
examples: [
[
{
input: {
vault: "7opUkqYtxmQRriZvwZkPcg6LqmGjAh1RSEsVrdsGDx5K",
},
output: {
status: "success",
data: {
totalValue: 1000000,
positions: [
{
strategy: "4JHtgXyMb9gFJ1hGd2sh645jrZcxurSG3QP7Le3aTMTx",
value: 600000,
},
{
strategy: "4i9kzGr1UkxBCCUkQUQ4vsF51fjdt2knKxrwM1h1NW4g",
value: 400000,
},
],
},
message: "Successfully retrieved Voltr vault position values",
},
explanation:
"Get position values for a Voltr vault showing total value and value per strategy",
},
],
],
schema: z.object({
vault: z
.string()
.min(1)
.describe("The public key of the Voltr vault to query, e.g., 'Ga27...'"),
}),
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
try {
const vault = new PublicKey(input.vault);
const result = await agent.voltrGetPositionValues(vault);
const positionData = JSON.parse(result);
return {
status: "success",
vault: vault.toBase58(),
data: positionData,
message: "Successfully retrieved Voltr vault position values",
};
} catch (error: any) {
return {
status: "error",
message: `Failed to get vault position values: ${error.message}`,
};
}
},
};
export default getVoltrPositionValuesAction;

View File

@@ -0,0 +1,83 @@
import { Action } from "../../types/action";
import { SolanaAgentKit } from "../../agent";
import { z } from "zod";
import { PublicKey } from "@solana/web3.js";
import { BN } from "bn.js";
const withdrawVoltrStrategyAction: Action = {
name: "WITHDRAW_VOLTR_STRATEGY",
similes: [
"withdraw from voltr strategy",
"remove funds from voltr vault strategy",
"take out from voltr strategy",
"withdraw assets from voltr",
"pull from voltr vault",
"redeem from voltr strategy",
],
description: "Withdraw assets from a specific strategy within a Voltr vault",
examples: [
[
{
input: {
withdrawAmount: "1000000000", // 1 USDC with 6 decimals
vault: "7opUkqYtxmQRriZvwZkPcg6LqmGjAh1RSEsVrdsGDx5K",
strategy: "9ZQQYvr4x7AMqd6abVa1f5duGjti5wk1MHsX6hogPsLk",
},
output: {
status: "success",
vault: "7opUkqYtxmQRriZvwZkPcg6LqmGjAh1RSEsVrdsGDx5K",
strategy: "9ZQQYvr4x7AMqd6abVa1f5duGjti5wk1MHsX6hogPsLk",
signature: "2ZE7Rz...",
message: "Successfully withdrew 1000000000 from strategy",
},
explanation: "Withdraw 1 USDC from a Voltr vault strategy",
},
],
],
schema: z.object({
withdrawAmount: z
.string()
.min(1)
.describe("The amount to withdraw (in base units including decimals)"),
vault: z
.string()
.min(1)
.describe(
"The public key of the Voltr source vault to deposit assets into, e.g., 'Ga27...'",
),
strategy: z
.string()
.min(1)
.describe(
"The public key of the initialized target strategy to withdraw from, e.g., 'Jheh...'",
),
}),
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
try {
const withdrawAmount = new BN(input.withdrawAmount);
const vault = new PublicKey(input.vault);
const strategy = new PublicKey(input.strategy);
const signature = await agent.voltrWithdrawStrategy(
withdrawAmount,
vault,
strategy,
);
return {
status: "success",
vault: vault.toBase58(),
strategy: strategy.toBase58(),
signature,
message: `Successfully withdrew ${input.withdrawAmount} from strategy`,
};
} catch (error: any) {
return {
status: "error",
message: `Failed to withdraw from strategy: ${error.message}`,
};
}
},
};
export default withdrawVoltrStrategyAction;

View File

@@ -18,6 +18,8 @@ import {
getPrimaryDomain,
launchPumpFunToken,
lendAsset,
luloLend,
luloWithdraw,
mintCollectionNFT,
openbookCreateMarket,
manifestCreateMarket,
@@ -84,6 +86,34 @@ import {
getHeliusWebhook,
create_HeliusWebhook,
deleteHeliusWebhook,
createDriftUserAccount,
createVault,
depositIntoVault,
depositToDriftUserAccount,
getVaultAddress,
doesUserHaveDriftAccount,
driftUserAccountInfo,
requestWithdrawalFromVault,
tradeDriftVault,
driftPerpTrade,
updateVault,
getVaultInfo,
withdrawFromDriftUserAccount,
withdrawFromDriftVault,
updateVaultDelegate,
get_token_balance,
getAvailableDriftSpotMarkets,
getAvailableDriftPerpMarkets,
stakeToDriftInsuranceFund,
requestUnstakeFromDriftInsuranceFund,
unstakeFromDriftInsuranceFund,
swapSpotToken,
calculatePerpMarketFundingRate,
getEntryQuoteOfPerpTrade,
getLendingAndBorrowAPY,
voltrGetPositionValues,
voltrDepositStrategy,
voltrWithdrawStrategy,
} from "../tools";
import {
Config,
@@ -176,6 +206,19 @@ export class SolanaAgentKit {
return get_balance(this, token_address);
}
async getTokenBalances(wallet_address?: PublicKey): Promise<{
sol: number;
tokens: Array<{
tokenAddress: string;
name: string;
symbol: string;
balance: number;
decimals: number;
}>;
}> {
return get_token_balance(this, wallet_address);
}
async getBalanceOther(
walletAddress: PublicKey,
tokenAddress?: PublicKey,
@@ -284,6 +327,14 @@ export class SolanaAgentKit {
return lendAsset(this, amount);
}
async luloLend(mintAddress: string, amount: number): Promise<string> {
return luloLend(this, mintAddress, amount);
}
async luloWithdraw(mintAddress: string, amount: number): Promise<string> {
return luloWithdraw(this, mintAddress, amount);
}
async getTPS(): Promise<number> {
return getTPS(this);
}
@@ -655,24 +706,43 @@ export class SolanaAgentKit {
}
async create3LandCollection(
optionsWithBase58: StoreInitOptions,
collectionOpts: CreateCollectionOptions,
isDevnet: boolean = false,
): Promise<string> {
const optionsWithBase58: StoreInitOptions = {
privateKey: this.wallet.secretKey,
};
if (isDevnet) {
optionsWithBase58.isMainnet = false;
} else {
optionsWithBase58.isMainnet = true;
}
const tx = await createCollection(optionsWithBase58, collectionOpts);
return `Transaction: ${tx}`;
}
async create3LandNft(
optionsWithBase58: StoreInitOptions,
collectionAccount: string,
createItemOptions: CreateSingleOptions,
isMainnet: boolean,
isDevnet: boolean = false,
withPool: boolean = false,
): Promise<string> {
const optionsWithBase58: StoreInitOptions = {
privateKey: this.wallet.secretKey,
};
if (isDevnet) {
optionsWithBase58.isMainnet = false;
} else {
optionsWithBase58.isMainnet = true;
}
const tx = await createSingle(
optionsWithBase58,
collectionAccount,
createItemOptions,
isMainnet,
!isDevnet,
withPool,
);
return `Transaction: ${tx}`;
}
@@ -747,4 +817,185 @@ export class SolanaAgentKit {
async deleteWebhook(webhookID: string): Promise<any> {
return deleteHeliusWebhook(this, webhookID);
}
async createDriftUserAccount(depositAmount: number, depositSymbol: string) {
return await createDriftUserAccount(this, depositAmount, depositSymbol);
}
async createDriftVault(params: {
name: string;
marketName: `${string}-${string}`;
redeemPeriod: number;
maxTokens: number;
minDepositAmount: number;
managementFee: number;
profitShare: number;
hurdleRate?: number;
permissioned?: boolean;
}) {
return await createVault(this, params);
}
async depositIntoDriftVault(amount: number, vault: string) {
return await depositIntoVault(this, amount, vault);
}
async depositToDriftUserAccount(
amount: number,
symbol: string,
isRepayment?: boolean,
) {
return await depositToDriftUserAccount(this, amount, symbol, isRepayment);
}
async deriveDriftVaultAddress(name: string) {
return await getVaultAddress(this, name);
}
async doesUserHaveDriftAccount() {
return await doesUserHaveDriftAccount(this);
}
async driftUserAccountInfo() {
return await driftUserAccountInfo(this);
}
async requestWithdrawalFromDriftVault(amount: number, vault: string) {
return await requestWithdrawalFromVault(this, amount, vault);
}
async tradeUsingDelegatedDriftVault(
vault: string,
amount: number,
symbol: string,
action: "long" | "short",
type: "market" | "limit",
price?: number,
) {
return await tradeDriftVault(
this,
vault,
amount,
symbol,
action,
type,
price,
);
}
async tradeUsingDriftPerpAccount(
amount: number,
symbol: string,
action: "long" | "short",
type: "market" | "limit",
price?: number,
) {
return await driftPerpTrade(this, { action, amount, symbol, type, price });
}
async updateDriftVault(
vaultAddress: string,
params: {
name: string;
marketName: `${string}-${string}`;
redeemPeriod: number;
maxTokens: number;
minDepositAmount: number;
managementFee: number;
profitShare: number;
hurdleRate?: number;
permissioned?: boolean;
},
) {
return await updateVault(this, vaultAddress, params);
}
async getDriftVaultInfo(vaultName: string) {
return await getVaultInfo(this, vaultName);
}
async withdrawFromDriftAccount(
amount: number,
symbol: string,
isBorrow?: boolean,
) {
return await withdrawFromDriftUserAccount(this, amount, symbol, isBorrow);
}
async withdrawFromDriftVault(vault: string) {
return await withdrawFromDriftVault(this, vault);
}
async updateDriftVaultDelegate(vaultAddress: string, delegate: string) {
return await updateVaultDelegate(this, vaultAddress, delegate);
}
getAvailableDriftMarkets(type?: "spot" | "perp") {
switch (type) {
case "spot":
return getAvailableDriftSpotMarkets();
case "perp":
return getAvailableDriftPerpMarkets();
default:
return {
spot: getAvailableDriftSpotMarkets(),
perp: getAvailableDriftPerpMarkets(),
};
}
}
async stakeToDriftInsuranceFund(amount: number, symbol: string) {
return await stakeToDriftInsuranceFund(this, amount, symbol);
}
async requestUnstakeFromDriftInsuranceFund(amount: number, symbol: string) {
return await requestUnstakeFromDriftInsuranceFund(this, amount, symbol);
}
async unstakeFromDriftInsuranceFund(symbol: string) {
return await unstakeFromDriftInsuranceFund(this, symbol);
}
async driftSpotTokenSwap(
params: {
fromSymbol: string;
toSymbol: string;
slippage?: number;
} & (
| {
toAmount: number;
}
| { fromAmount: number }
),
) {
return await swapSpotToken(this, {
fromSymbol: params.fromSymbol,
toSymbol: params.toSymbol,
// @ts-expect-error - fromAmount and toAmount are mutually exclusive
fromAmount: params.fromAmount,
// @ts-expect-error - fromAmount and toAmount are mutually exclusive
toAmount: params.toAmount,
slippage: params.slippage,
});
}
async getPerpMarketFundingRate(
symbol: `${string}-PERP`,
period: "year" | "hour" = "year",
) {
return calculatePerpMarketFundingRate(this, symbol, period);
}
async getEntryQuoteOfPerpTrade(
amount: number,
symbol: `${string}-PERP`,
action: "short" | "long",
) {
return getEntryQuoteOfPerpTrade(symbol, amount, action);
}
async getLendAndBorrowAPY(symbol: string) {
return getLendingAndBorrowAPY(this, symbol);
}
async voltrDepositStrategy(
depositAmount: BN,
vault: PublicKey,
strategy: PublicKey,
): Promise<string> {
return voltrDepositStrategy(this, depositAmount, vault, strategy);
}
async voltrWithdrawStrategy(
withdrawAmount: BN,
vault: PublicKey,
strategy: PublicKey,
): Promise<string> {
return voltrWithdrawStrategy(this, withdrawAmount, vault, strategy);
}
async voltrGetPositionValues(vault: PublicKey): Promise<string> {
return voltrGetPositionValues(this, vault);
}
}

View File

@@ -42,3 +42,8 @@ export const METEORA_DYNAMIC_AMM_PROGRAM_ID = new PublicKey(
export const METEORA_DLMM_PROGRAM_ID = new PublicKey(
"LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ",
);
/**
* Minimum compute price required to carry out complex transactions on the Drift protocol
*/
export const MINIMUM_COMPUTE_PRICE_FOR_COMPLEX_ACTIONS =
0.000003 * 1000000 * 1000000;

View File

@@ -10,7 +10,6 @@ export class Solana3LandCreateCollection extends Tool {
description = `Creates an NFT Collection that you can visit on 3.land's website (3.land/collection/{collectionAccount})
Inputs:
privateKey (required): represents the privateKey of the wallet - can be an array of numbers, Uint8Array or base58 string
isMainnet (required): defines is the tx takes places in mainnet
collectionSymbol (required): the symbol of the collection
collectionName (required): the name of the collection
@@ -26,14 +25,8 @@ export class Solana3LandCreateCollection extends Tool {
protected async _call(input: string): Promise<string> {
try {
const inputFormat = JSON.parse(input);
const privateKey = inputFormat.privateKey;
const isMainnet = inputFormat.isMainnet;
const optionsWithBase58: StoreInitOptions = {
...(privateKey && { privateKey }),
...(isMainnet && { isMainnet }),
};
const collectionSymbol = inputFormat?.collectionSymbol;
const collectionName = inputFormat?.collectionName;
const collectionDescription = inputFormat?.collectionDescription;
@@ -49,8 +42,8 @@ export class Solana3LandCreateCollection extends Tool {
};
const tx = await this.solanaKit.create3LandCollection(
optionsWithBase58,
collectionOpts,
!isMainnet,
);
return JSON.stringify({
status: "success",

View File

@@ -10,7 +10,6 @@ export class Solana3LandCreateSingle extends Tool {
description = `Creates an NFT and lists it on 3.land's website
Inputs:
privateKey (required): represents the privateKey of the wallet - can be an array of numbers, Uint8Array or base58 string
collectionAccount (optional): represents the account for the nft collection
itemName (required): the name of the NFT
sellerFee (required): the fee of the seller
@@ -21,7 +20,9 @@ export class Solana3LandCreateSingle extends Tool {
mainImageUrl (required): the main image of the NFT
coverImageUrl (optional): the cover image of the NFT
splHash (optional): the hash of the spl token, if not provided listing will be in $SOL
isMainnet (required): defines is the tx takes places in mainnet
poolName (optional): the name of the pool
isMainnet (required): defines if the tx takes places in mainnet
withPool (optional): defines if minted edition will be tied to a liquidity pool
`;
constructor(private solanaKit: SolanaAgentKit) {
@@ -31,13 +32,9 @@ export class Solana3LandCreateSingle extends Tool {
protected async _call(input: string): Promise<string> {
try {
const inputFormat = JSON.parse(input);
const privateKey = inputFormat.privateKey;
const isMainnet = inputFormat.isMainnet;
const optionsWithBase58: StoreInitOptions = {
...(privateKey && { privateKey }),
...(isMainnet && { isMainnet }),
};
const withPool = inputFormat.withPool;
const poolName = inputFormat.poolName;
const collectionAccount = inputFormat.collectionAccount;
@@ -52,6 +49,15 @@ export class Solana3LandCreateSingle extends Tool {
const coverImageUrl = inputFormat?.coverImageUrl;
const splHash = inputFormat?.splHash;
if (withPool) {
if (!poolName) {
throw new Error("poolName is required when withPool is true");
}
if (!splHash) {
throw new Error("splHash is required when withPool is true");
}
}
const createItemOptions: CreateSingleOptions = {
...(itemName && { itemName }),
...(sellerFee && { sellerFee }),
@@ -63,6 +69,7 @@ export class Solana3LandCreateSingle extends Tool {
...(mainImageUrl && { mainImageUrl }),
...(coverImageUrl && { coverImageUrl }),
...(splHash && { splHash }),
...(poolName && { poolName }),
};
if (!collectionAccount) {
@@ -70,10 +77,10 @@ export class Solana3LandCreateSingle extends Tool {
}
const tx = await this.solanaKit.create3LandNft(
optionsWithBase58,
collectionAccount,
createItemOptions,
isMainnet,
!isMainnet,
withPool,
);
return JSON.stringify({
status: "success",

View File

@@ -0,0 +1,38 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaCreateDriftUserAccountTool extends Tool {
name = "create_drift_user_account";
description = `Create a new user account with a deposit on Drift protocol.
Inputs (JSON string):
- amount: number, amount of the token to deposit (required)
- symbol: string, symbol of the token to deposit (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const res = await this.solanaKit.createDriftUserAccount(
parsedInput.amount,
parsedInput.symbol,
);
return JSON.stringify({
status: "success",
message: `User account created with ${parsedInput.amount} ${parsedInput.symbol} successfully deposited`,
account: res.account,
signature: res.txSignature,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "CREATE_DRIFT_USER_ACCOUNT_ERROR",
});
}
}
}

View File

@@ -0,0 +1,42 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaCreateDriftVaultTool extends Tool {
name = "create_drift_vault";
description = `Create a new drift vault delegating the agents address as the owner.
Inputs (JSON string):
- name: string, unique vault name (min 5 chars)
- marketName: string, market name in TOKEN-SPOT format
- redeemPeriod: number, days to wait before funds can be redeemed (min 1)
- maxTokens: number, maximum tokens vault can accommodate (min 100)
- minDepositAmount: number, minimum deposit amount
- managementFee: number, fee percentage for managing funds (max 20)
- profitShare: number, profit sharing percentage (max 90, default 5)
- hurdleRate: number, optional hurdle rate
- permissioned: boolean, whether vault has whitelist`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx = await this.solanaKit.createDriftVault(parsedInput);
return JSON.stringify({
status: "success",
message: "Drift vault created successfully",
vaultName: parsedInput.name,
signature: tx,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "CREATE_DRIFT_VAULT_ERROR",
});
}
}
}

View File

@@ -0,0 +1,37 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaDepositIntoDriftVaultTool extends Tool {
name = "deposit_into_drift_vault";
description = `Deposit funds into an existing drift vault.
Inputs (JSON string):
- vaultAddress: string, address of the vault (required)
- amount: number, amount to deposit (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx = await this.solanaKit.depositIntoDriftVault(
parsedInput.amount,
parsedInput.vaultAddress,
);
return JSON.stringify({
status: "success",
message: "Funds deposited successfully",
signature: tx,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "DEPOSIT_INTO_VAULT_ERROR",
});
}
}
}

View File

@@ -0,0 +1,39 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaDepositToDriftUserAccountTool extends Tool {
name = "deposit_to_drift_user_account";
description = `Deposit funds into your drift user account.
Inputs (JSON string):
- amount: number, amount to deposit (required)
- symbol: string, token symbol (required)
- repay: boolean, whether to repay borrowed funds (optional, default: false)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx = await this.solanaKit.depositToDriftUserAccount(
parsedInput.amount,
parsedInput.symbol,
parsedInput.repay,
);
return JSON.stringify({
status: "success",
message: "Funds deposited successfully",
signature: tx,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "DEPOSIT_TO_DRIFT_ACCOUNT_ERROR",
});
}
}
}

View File

@@ -0,0 +1,32 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaDeriveVaultAddressTool extends Tool {
name = "derive_drift_vault_address";
description = `Derive a drift vault address from the vault's name.
Inputs (JSON string):
- name: string, name of the vault to derive the address of (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const address = await this.solanaKit.deriveDriftVaultAddress(input);
return JSON.stringify({
status: "success",
message: "Vault address derived successfully",
address,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "DERIVE_VAULT_ADDRESS_ERROR",
});
}
}
}

View File

@@ -0,0 +1,38 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaCheckDriftAccountTool extends Tool {
name = "does_user_have_drift_account";
description = `Check if a user has a Drift account.
Inputs: No inputs required - checks the current user's account`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(_input: string): Promise<string> {
try {
const res = await this.solanaKit.doesUserHaveDriftAccount();
if (!res.hasAccount) {
return JSON.stringify({
status: "error",
message: "You do not have a Drift account",
});
}
return JSON.stringify({
status: "success",
message: "Nice! You have a Drift account",
account: res.account,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "CHECK_DRIFT_ACCOUNT_ERROR",
});
}
}
}

View File

@@ -0,0 +1,29 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaDriftUserAccountInfoTool extends Tool {
name = "drift_user_account_info";
description = `Get information about your drift account.
Inputs: No inputs required - retrieves current user's account info`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(_input: string): Promise<string> {
try {
const accountInfo = await this.solanaKit.driftUserAccountInfo();
return JSON.stringify({
status: "success",
data: accountInfo,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "DRIFT_ACCOUNT_INFO_ERROR",
});
}
}
}

View File

@@ -0,0 +1,39 @@
import { Tool } from "langchain/tools";
import type { SolanaAgentKit } from "../../agent";
export class SolanaDriftEntryQuoteOfPerpTradeTool extends Tool {
name = "drift_entry_quote_of_perp_trade";
description = `Get an entry quote for a perpetual trade on Drift protocol.
Inputs (JSON string):
- amount: number, amount to trade (required)
- symbol: string, market symbol (required)
- action: "long" | "short", trade direction (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const quote = await this.solanaKit.getEntryQuoteOfPerpTrade(
parsedInput.amount,
parsedInput.symbol,
parsedInput.action,
);
return JSON.stringify({
status: "success",
message: `Entry quote retrieved for ${parsedInput.action} ${parsedInput.amount} ${parsedInput.symbol}`,
data: quote,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "ENTRY_QUOTE_OF_PERP_TRADE_ERROR",
});
}
}
}

View File

@@ -0,0 +1,22 @@
export * from "./create_user_account";
export * from "./create_vault";
export * from "./deposit_into_vault";
export * from "./deposit_to_user_account";
export * from "./derive_vault_address";
export * from "./does_user_have_drift_account";
export * from "./drift_user_account_info";
export * from "./request_withdrawal";
export * from "./trade_delegated_vault";
export * from "./trade_perp_account";
export * from "./update_drift_vault_delegate";
export * from "./update_vault";
export * from "./vault_info";
export * from "./withdraw_from_account";
export * from "./withdraw_from_vault";
export * from "./perp_market_funding_rate";
export * from "./entry_quote_of_perp_trade";
export * from "./lend_and_borrow_apy";
export * from "./stake_to_insurance_fund";
export * from "./swap_spot_token";
export * from "./unstake_from_insurance_fund";
export * from "./request_unstake_from_insurance_fund";

View File

@@ -0,0 +1,32 @@
import { Tool } from "langchain/tools";
import type { SolanaAgentKit } from "../../agent";
export class SolanaDriftLendAndBorrowAPYTool extends Tool {
name = "drift_lend_and_borrow_apy";
description = `Get lending and borrowing APY for a token on Drift protocol.
Inputs (JSON string):
- symbol: string, token symbol (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const apyInfo = await this.solanaKit.getLendAndBorrowAPY(input);
return JSON.stringify({
status: "success",
message: `APY information retrieved for ${input}`,
data: apyInfo,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "LEND_AND_BORROW_APY_ERROR",
});
}
}
}

View File

@@ -0,0 +1,36 @@
import { Tool } from "langchain/tools";
import type { SolanaAgentKit } from "../../agent";
export class SolanaDriftPerpMarketFundingRateTool extends Tool {
name = "drift_perp_market_funding_rate";
description = `Get the funding rate for a perpetual market on Drift protocol.
Inputs (JSON string):
- symbol: string, market symbol (required)
- period: year or hour (default: hour)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const fundingRate = await this.solanaKit.getPerpMarketFundingRate(
parsedInput.symbol,
parsedInput.period,
);
return JSON.stringify({
status: "success",
message: `Funding rate retrieved for ${parsedInput.symbol}`,
data: fundingRate,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
});
}
}
}

View File

@@ -0,0 +1,37 @@
import { Tool } from "langchain/tools";
import type { SolanaAgentKit } from "../../agent";
export class SolanaRequestUnstakeFromDriftInsuranceFundTool extends Tool {
name = "request_unstake_from_drift_insurance_fund";
description = `Request to unstake tokens from Drift Insurance Fund.
Inputs (JSON string):
- amount: number, amount to unstake (required)
- symbol: string, token symbol (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx = await this.solanaKit.requestUnstakeFromDriftInsuranceFund(
parsedInput.amount,
parsedInput.symbol,
);
return JSON.stringify({
status: "success",
message: `Requested unstake of ${parsedInput.amount} ${parsedInput.symbol} from the Drift Insurance Fund`,
signature: tx,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "REQUEST_UNSTAKE_FROM_DRIFT_INSURANCE_FUND_ERROR",
});
}
}
}

View File

@@ -0,0 +1,37 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaRequestDriftWithdrawalTool extends Tool {
name = "request_withdrawal_from_drift_vault";
description = `Request a withdrawal from an existing drift vault.
Inputs (JSON string):
- vaultAddress: string, vault address (required)
- amount: number, amount of shares to withdraw (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx = await this.solanaKit.requestWithdrawalFromDriftVault(
parsedInput.amount,
parsedInput.vaultAddress,
);
return JSON.stringify({
status: "success",
message: "Withdrawal request successful",
signature: tx,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "REQUEST_DRIFT_WITHDRAWAL_ERROR",
});
}
}
}

View File

@@ -0,0 +1,37 @@
import { Tool } from "langchain/tools";
import type { SolanaAgentKit } from "../../agent";
export class SolanaStakeToDriftInsuranceFundTool extends Tool {
name = "stake_to_drift_insurance_fund";
description = `Stake a token to Drift Insurance Fund.
Inputs (JSON string):
- amount: number, amount to stake (required)
- symbol: string, token symbol (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx = await this.solanaKit.stakeToDriftInsuranceFund(
parsedInput.amount,
parsedInput.symbol,
);
return JSON.stringify({
status: "success",
message: `Staked ${parsedInput.amount} ${parsedInput.symbol} to the Drift Insurance Fund`,
signature: tx,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "STAKE_TO_DRIFT_INSURANCE_FUND_ERROR",
});
}
}
}

View File

@@ -0,0 +1,37 @@
import { Tool } from "langchain/tools";
import type { SolanaAgentKit } from "../../agent";
export class SolanaDriftSpotTokenSwapTool extends Tool {
name = "drift_spot_token_swap";
description = `Swap spot tokens on Drift protocol.
Inputs (JSON string):
- fromSymbol: string, symbol of token to swap from (required)
- toSymbol: string, symbol of token to swap to (required)
- fromAmount: number, amount to swap from (optional) required if toAmount is not provided
- toAmount: number, amount to swap to (optional) required if fromAmount is not provided
- slippage: number, slippage tolerance in percentage (optional)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx = await this.solanaKit.driftSpotTokenSwap(parsedInput);
return JSON.stringify({
status: "success",
message: `Swapped ${parsedInput.fromAmount} ${parsedInput.fromSymbol} for ${parsedInput.toSymbol}`,
signature: tx,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "DRIFT_SPOT_TOKEN_SWAP_ERROR",
});
}
}
}

View File

@@ -0,0 +1,49 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaTradeDelegatedDriftVaultTool extends Tool {
name = "trade_delegated_drift_vault";
description = `Carry out trades in a Drift vault.
Inputs (JSON string):
- vaultAddress: string, address of the Drift vault
- amount: number, amount to trade
- symbol: string, symbol of the token to trade
- action: "long" | "short", trade direction
- type: "market" | "limit", order type
- price: number, optional limit price`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx = await this.solanaKit.tradeUsingDelegatedDriftVault(
parsedInput.vaultAddress,
parsedInput.amount,
parsedInput.symbol,
parsedInput.action,
parsedInput.type,
parsedInput.price,
);
return JSON.stringify({
status: "success",
message:
parsedInput.type === "limit"
? "Order placed successfully"
: "Trade successful",
transactionId: tx,
...parsedInput,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "TRADE_DRIFT_VAULT_ERROR",
});
}
}
}

View File

@@ -0,0 +1,42 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaTradeDriftPerpAccountTool extends Tool {
name = "trade_drift_perp_account";
description = `Trade a perpetual account on Drift protocol.
Inputs (JSON string):
- amount: number, amount to trade (required)
- symbol: string, token symbol (required)
- action: "long" | "short", trade direction (required)
- type: "market" | "limit", order type (required)
- price: number, required for limit orders`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const signature = await this.solanaKit.tradeUsingDriftPerpAccount(
parsedInput.amount,
parsedInput.symbol,
parsedInput.action,
parsedInput.type,
parsedInput.price,
);
return JSON.stringify({
status: "success",
signature,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "TRADE_PERP_ACCOUNT_ERROR",
});
}
}
}

View File

@@ -0,0 +1,32 @@
import { Tool } from "langchain/tools";
import type { SolanaAgentKit } from "../../agent";
export class SolanaUnstakeFromDriftInsuranceFundTool extends Tool {
name = "unstake_from_drift_insurance_fund";
description = `Unstake tokens from Drift Insurance Fund after request period has elapsed.
Inputs (JSON string):
- symbol: string, token symbol (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const tx = await this.solanaKit.unstakeFromDriftInsuranceFund(input);
return JSON.stringify({
status: "success",
message: `Unstaked ${input} from the Drift Insurance Fund`,
signature: tx,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNSTAKE_FROM_DRIFT_INSURANCE_FUND_ERROR",
});
}
}
}

View File

@@ -0,0 +1,37 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaUpdateDriftVaultDelegateTool extends Tool {
name = "update_drift_vault_delegate";
description = `Update the delegate of a drift vault.
Inputs (JSON string):
- vaultAddress: string, address of the vault (required)
- newDelegate: string, address of the new delegate (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx = await this.solanaKit.updateDriftVaultDelegate(
parsedInput.vaultAddress,
parsedInput.newDelegate,
);
return JSON.stringify({
status: "success",
message: "Vault delegate updated successfully",
signature: tx,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UPDATE_DRIFT_VAULT_DELEGATE_ERROR",
});
}
}
}

View File

@@ -0,0 +1,52 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaUpdateDriftVaultTool extends Tool {
name = "update_drift_vault";
description = `Update an existing drift vault with new settings.
Inputs (JSON string):
- vaultAddress: string, vault address (required)
- redeemPeriod: number, days until redemption (optional)
- maxTokens: number, maximum tokens allowed (optional)
- minDepositAmount: number, minimum deposit amount (optional)
- managementFee: number, management fee percentage (optional)
- profitShare: number, profit sharing percentage (optional)
- hurdleRate: number, hurdle rate (optional)
- permissioned: boolean, whitelist requirement (optional)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx = await this.solanaKit.updateDriftVault(
parsedInput.vaultAddress,
// @ts-expect-error - type mismatch
{
hurdleRate: parsedInput.hurdleRate,
maxTokens: parsedInput.maxTokens,
minDepositAmount: parsedInput.minDepositAmount,
profitShare: parsedInput.profitShare,
managementFee: parsedInput.managementFee,
permissioned: parsedInput.permissioned,
redeemPeriod: parsedInput.redeemPeriod,
},
);
return JSON.stringify({
status: "success",
message: "Drift vault parameters updated successfully",
signature: tx,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UPDATE_DRIFT_VAULT_ERROR",
});
}
}
}

View File

@@ -0,0 +1,32 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaDriftVaultInfoTool extends Tool {
name = "drift_vault_info";
description = `Get information about a drift vault.
Inputs (JSON string):
- vaultNameOrAddress: string, name or address of the vault (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const vaultInfo = await this.solanaKit.getDriftVaultInfo(input);
return JSON.stringify({
status: "success",
message: "Vault info retrieved successfully",
data: vaultInfo,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "DRIFT_VAULT_INFO_ERROR",
});
}
}
}

View File

@@ -0,0 +1,39 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaWithdrawFromDriftAccountTool extends Tool {
name = "withdraw_from_drift_account";
description = `Withdraw or borrow funds from your drift account.
Inputs (JSON string):
- amount: number, amount to withdraw (required)
- symbol: string, token symbol (required)
- isBorrow: boolean, whether to borrow funds instead of withdrawing (optional, default: false)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx = await this.solanaKit.withdrawFromDriftAccount(
parsedInput.amount,
parsedInput.symbol,
parsedInput.isBorrow,
);
return JSON.stringify({
status: "success",
message: "Funds withdrawn successfully",
signature: tx,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "WITHDRAW_FROM_DRIFT_ACCOUNT_ERROR",
});
}
}
}

View File

@@ -0,0 +1,32 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaWithdrawFromDriftVaultTool extends Tool {
name = "withdraw_from_drift_vault";
description = `Withdraw funds from a vault given the redemption time has elapsed.
Inputs (JSON string):
- vaultAddress: string, vault address (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const tx = await this.solanaKit.withdrawFromDriftVault(input);
return JSON.stringify({
status: "success",
message: "Withdrawal successful",
signature: tx,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "WITHDRAW_FROM_DRIFT_VAULT_ERROR",
});
}
}
}

View File

@@ -26,8 +26,10 @@ export * from "./lightprotocol";
export * from "./squads";
export * from "./meteora";
export * from "./helius";
export * from "./drift";
export * from "./voltr";
import { SolanaAgentKit } from "../agent";
import type { SolanaAgentKit } from "../agent";
import {
SolanaBalanceTool,
SolanaBalanceOtherTool,
@@ -42,6 +44,8 @@ import {
SolanaPumpfunTokenLaunchTool,
SolanaCreateImageTool,
SolanaLendAssetTool,
SolanaLuloLendTool,
SolanaLuloWithdrawTool,
SolanaTPSCalculatorTool,
SolanaStakeTool,
SolanaRestakeTool,
@@ -101,6 +105,31 @@ import {
SolanaDeleteHeliusWebhookTool,
SolanaParseTransactionHeliusTool,
SolanaGetAllAssetsByOwner,
SolanaCheckDriftAccountTool,
SolanaCreateDriftUserAccountTool,
SolanaCreateDriftVaultTool,
SolanaDepositIntoDriftVaultTool,
SolanaDepositToDriftUserAccountTool,
SolanaDeriveVaultAddressTool,
SolanaDriftUserAccountInfoTool,
SolanaDriftVaultInfoTool,
SolanaRequestDriftWithdrawalTool,
SolanaTradeDelegatedDriftVaultTool,
SolanaTradeDriftPerpAccountTool,
SolanaUpdateDriftVaultDelegateTool,
SolanaUpdateDriftVaultTool,
SolanaWithdrawFromDriftAccountTool,
SolanaWithdrawFromDriftVaultTool,
SolanaDriftLendAndBorrowAPYTool,
SolanaDriftEntryQuoteOfPerpTradeTool,
SolanaDriftPerpMarketFundingRateTool,
SolanaDriftSpotTokenSwapTool,
SolanaRequestUnstakeFromDriftInsuranceFundTool,
SolanaStakeToDriftInsuranceFundTool,
SolanaUnstakeFromDriftInsuranceFundTool,
SolanaVoltrGetPositionValues,
SolanaVoltrDepositStrategy,
SolanaVoltrWithdrawStrategy,
} from "./index";
export function createSolanaTools(solanaKit: SolanaAgentKit) {
@@ -118,6 +147,8 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
new SolanaPumpfunTokenLaunchTool(solanaKit),
new SolanaCreateImageTool(solanaKit),
new SolanaLendAssetTool(solanaKit),
new SolanaLuloLendTool(solanaKit),
new SolanaLuloWithdrawTool(solanaKit),
new SolanaTPSCalculatorTool(solanaKit),
new SolanaStakeTool(solanaKit),
new SolanaRestakeTool(solanaKit),
@@ -182,5 +213,30 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
new SolanaHeliusWebhookTool(solanaKit),
new SolanaGetHeliusWebhookTool(solanaKit),
new SolanaDeleteHeliusWebhookTool(solanaKit),
new SolanaCreateDriftUserAccountTool(solanaKit),
new SolanaCreateDriftVaultTool(solanaKit),
new SolanaDepositIntoDriftVaultTool(solanaKit),
new SolanaDepositToDriftUserAccountTool(solanaKit),
new SolanaDeriveVaultAddressTool(solanaKit),
new SolanaCheckDriftAccountTool(solanaKit),
new SolanaDriftUserAccountInfoTool(solanaKit),
new SolanaRequestDriftWithdrawalTool(solanaKit),
new SolanaTradeDelegatedDriftVaultTool(solanaKit),
new SolanaTradeDriftPerpAccountTool(solanaKit),
new SolanaUpdateDriftVaultDelegateTool(solanaKit),
new SolanaUpdateDriftVaultTool(solanaKit),
new SolanaDriftVaultInfoTool(solanaKit),
new SolanaWithdrawFromDriftAccountTool(solanaKit),
new SolanaWithdrawFromDriftVaultTool(solanaKit),
new SolanaDriftSpotTokenSwapTool(solanaKit),
new SolanaStakeToDriftInsuranceFundTool(solanaKit),
new SolanaRequestUnstakeFromDriftInsuranceFundTool(solanaKit),
new SolanaUnstakeFromDriftInsuranceFundTool(solanaKit),
new SolanaDriftLendAndBorrowAPYTool(solanaKit),
new SolanaDriftEntryQuoteOfPerpTradeTool(solanaKit),
new SolanaDriftPerpMarketFundingRateTool(solanaKit),
new SolanaVoltrGetPositionValues(solanaKit),
new SolanaVoltrDepositStrategy(solanaKit),
new SolanaVoltrWithdrawStrategy(solanaKit),
];
}

View File

@@ -1 +1,3 @@
export * from "./lend_asset";
export * from "./lulo_lend";
export * from "./lulo_withdraw";

View File

@@ -0,0 +1,37 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaLuloLendTool extends Tool {
name = "solana_lulo_lend";
description = `Lend token for yield using Lulo. (support USDC/PYUSD/USDS/USDT/SOL/jitoSOL/bSOL/mSOL/BONK/JUP)
Inputs:
mintAddress: string, eg "So11111111111111111111111111111111111111112" (required)
amount: number, eg 1, 0.01 (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const mintAddress = parsedInput.mintAddress;
const amount = parsedInput.amount;
const tx = await this.solanaKit.luloLend(mintAddress, amount);
return JSON.stringify({
status: "success",
message: "Asset lent successfully",
transaction: tx,
amount,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}

View File

@@ -0,0 +1,37 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
export class SolanaLuloWithdrawTool extends Tool {
name = "solana_lulo_withdraw";
description = `Withdraw token USDC using Lulo. (support USDC/PYUSD/USDS/USDT/SOL/jitoSOL/bSOL/mSOL/BONK/JUP)
Inputs (input is a json string):
mintAddress: string, eg "So11111111111111111111111111111111111111112" (required)
amount: number, eg 1, 0.01 (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const mintAddress = parsedInput.mintAddress;
const amount = parsedInput.amount;
const tx = await this.solanaKit.luloWithdraw(mintAddress, amount);
return JSON.stringify({
status: "success",
message: "Asset withdraw successfully",
transaction: tx,
amount,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}

View File

@@ -0,0 +1,39 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
import { PublicKey } from "@solana/web3.js";
import { BN } from "bn.js";
export class SolanaVoltrDepositStrategy extends Tool {
name = "solana_voltr_deposit_strategy";
description = `Deposit amount into a strategy for Voltr's vaults
Inputs (input is a json string):
depositAmount: number (required)
vault: string (required)
strategy: string (required)
`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
async _call(input: string): Promise<string> {
try {
const inputFormat = JSON.parse(input);
const tx = await this.solanaKit.voltrDepositStrategy(
new BN(inputFormat.depositAmount),
new PublicKey(inputFormat.vault),
new PublicKey(inputFormat.strategy),
);
return JSON.stringify({
status: "success",
message: `Deposited ${inputFormat.depositAmount} into strategy ${inputFormat.strategy} of vault ${inputFormat.vault} successfully`,
transaction: tx,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}

View File

@@ -0,0 +1,18 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
import { PublicKey } from "@solana/web3.js";
export class SolanaVoltrGetPositionValues extends Tool {
name = "solana_voltr_get_position_values";
description = `Get the total asset value and current value for each strategy of a given Voltr vault
Inputs:
vault: string (required)
`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
async _call(input: string): Promise<string> {
return this.solanaKit.voltrGetPositionValues(new PublicKey(input));
}
}

View File

@@ -0,0 +1,3 @@
export * from "./deposit_strategy";
export * from "./withdraw_strategy";
export * from "./get_position_values";

View File

@@ -0,0 +1,39 @@
import { Tool } from "langchain/tools";
import { SolanaAgentKit } from "../../agent";
import { PublicKey } from "@solana/web3.js";
import { BN } from "bn.js";
export class SolanaVoltrWithdrawStrategy extends Tool {
name = "solana_voltr_withdraw_strategy";
description = `Withdraw amount from a strategy for Voltr's vaults
Inputs (input is a json string):
withdrawAmount: number (required)
vault: string (required)
strategy: string (required)
`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
async _call(input: string): Promise<string> {
try {
const inputFormat = JSON.parse(input);
const tx = await this.solanaKit.voltrWithdrawStrategy(
new BN(inputFormat.withdrawAmount),
new PublicKey(inputFormat.vault),
new PublicKey(inputFormat.strategy),
);
return JSON.stringify({
status: "success",
message: `Withdrew ${inputFormat.withdrawAmount} from strategy ${inputFormat.strategy} of vault ${inputFormat.vault} successfully`,
transaction: tx,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}

View File

@@ -37,7 +37,8 @@ export async function createSingle(
optionsWithBase58: StoreInitOptions,
collectionAccount: string,
createItemOptions: CreateSingleOptions,
isMainnet: boolean,
isMainnet: boolean = false,
withPool: boolean = false,
) {
try {
const landStore = isMainnet
@@ -49,6 +50,8 @@ export async function createSingle(
landStore,
collectionAccount,
createItemOptions,
true, //isAI
withPool,
);
return singleEditionTx;
} catch (error: any) {

1011
src/tools/drift/drift.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,649 @@
import {
BASE_PRECISION,
convertToNumber,
getLimitOrderParams,
getMarketOrderParams,
getOrderParams,
MainnetPerpMarkets,
MainnetSpotMarkets,
MarketType,
numberToSafeBN,
PERCENTAGE_PRECISION,
PositionDirection,
PostOnlyParams,
PRICE_PRECISION,
QUOTE_PRECISION,
TEN,
} from "@drift-labs/sdk";
import {
WithdrawUnit,
decodeName,
encodeName,
getVaultAddressSync,
getVaultDepositorAddressSync,
} from "@drift-labs/vaults-sdk";
import {
ComputeBudgetProgram,
PublicKey,
type TransactionInstruction,
} from "@solana/web3.js";
import type { SolanaAgentKit } from "../../agent";
import { BN } from "bn.js";
import { initClients } from "./drift";
export function getMarketIndexAndType(name: `${string}-${string}`) {
const [symbol, type] = name.toUpperCase().split("-");
if (type === "PERP") {
const token = MainnetPerpMarkets.find((v) => v.baseAssetSymbol === symbol);
if (!token) {
throw new Error(
`Drift doesn't have that market. Here's a list of available perp markets: ${MainnetPerpMarkets.map((v) => v.baseAssetSymbol).join(", ")}`,
);
}
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. Here's a list of available spot markets: ${MainnetSpotMarkets.map((v) => v.symbol).join(", ")}`,
);
}
return { marketIndex: token.marketIndex, marketType: MarketType.SPOT };
}
async function getOrCreateVaultDepositor(agent: SolanaAgentKit, vault: string) {
const { vaultClient, cleanUp } = await initClients(agent);
const vaultPublicKey = new PublicKey(vault);
const vaultDepositor = getVaultDepositorAddressSync(
vaultClient.program.programId,
vaultPublicKey,
agent.wallet.publicKey,
);
try {
await vaultClient.getVaultDepositor(vaultDepositor);
await cleanUp();
return vaultDepositor;
} catch (e) {
// @ts-expect-error - error message is a string
if (e.message.includes("Account does not exist")) {
await vaultClient.initializeVaultDepositor(
vaultPublicKey,
agent.wallet.publicKey,
);
}
await new Promise((resolve) => setTimeout(resolve, 2000));
await cleanUp();
return vaultDepositor;
}
}
async function getVaultAvailableBalance(agent: SolanaAgentKit, vault: string) {
try {
const { cleanUp, vaultClient } = await initClients(agent);
const vaultDetails = await vaultClient.getVault(new PublicKey(vault));
const currentVaultBalance = convertToNumber(
vaultDetails.netDeposits,
QUOTE_PRECISION,
);
const vaultWithdrawalsRequested = convertToNumber(
vaultDetails.totalWithdrawRequested,
QUOTE_PRECISION,
);
const availableBalanceInUSD =
currentVaultBalance - vaultWithdrawalsRequested;
await cleanUp();
return availableBalanceInUSD;
} catch (e) {
// @ts-expect-error - error message is a string
throw new Error(`Failed to get vault available balance: ${e.message}`);
}
}
/**
Create a vault
@param agent SolanaAgentKit instance
@param params Vault creation parameters
@param params.name Name of the vault (must be unique)
@param params.marketName Market name of the vault (e.g. "USDC-SPOT")
@param params.redeemPeriod Redeem period in seconds
@param params.maxTokens Maximum amount that can be deposited into the vault (in tokens)
@param params.minDepositAmount Minimum amount that can be deposited into the vault (in tokens)
@param params.managementFee Management fee percentage (e.g 2 == 2%)
@param params.profitShare Profit share percentage (e.g 20 == 20%)
@param params.hurdleRate Hurdle rate percentage
@param params.permissioned Whether the vault uses a whitelist
@returns Promise<anchor.Web3.TransactionSignature> - The transaction signature of the vault creation
*/
export async function createVault(
agent: SolanaAgentKit,
params: {
name: string;
marketName: `${string}-${string}`;
redeemPeriod: number;
maxTokens: number;
minDepositAmount: number;
managementFee: number;
profitShare: number;
hurdleRate?: number;
permissioned?: boolean;
},
) {
try {
const { vaultClient, driftClient, cleanUp } = await initClients(agent);
const marketIndexAndType = getMarketIndexAndType(params.marketName);
const spotMarket = driftClient.getSpotMarketAccount(
marketIndexAndType.marketIndex,
);
if (!spotMarket) {
throw new Error(
`Market not found. Here's a list of available spot markets: ${MainnetSpotMarkets.map((v) => `${v.symbol}-SPOT`).join(", ")}`,
);
}
const spotPrecision = TEN.pow(new BN(spotMarket.decimals));
if (marketIndexAndType.marketType === MarketType.PERP) {
throw new Error(
`Only SPOT market names are supported. Such as ${MainnetSpotMarkets.map((v) => `${v.symbol}-SPOT`).join(", ")}`,
);
}
const tx = await vaultClient.initializeVault({
name: encodeName(params.name),
spotMarketIndex: marketIndexAndType.marketIndex,
hurdleRate: new BN(params.hurdleRate ?? 0)
.mul(PERCENTAGE_PRECISION)
.div(new BN(100))
.toNumber(),
profitShare: new BN(params.profitShare)
.mul(PERCENTAGE_PRECISION)
.div(new BN(100))
.toNumber(),
minDepositAmount: numberToSafeBN(params.minDepositAmount, spotPrecision),
redeemPeriod: new BN(params.redeemPeriod * 86400),
maxTokens: numberToSafeBN(params.maxTokens, spotPrecision),
managementFee: new BN(params.managementFee)
.mul(PERCENTAGE_PRECISION)
.div(new BN(100)),
permissioned: params.permissioned ?? false,
});
await cleanUp();
return tx;
} catch (e) {
// @ts-expect-error - error message is a string
throw new Error(`Failed to create Drift vault: ${e.message}`);
}
}
export async function updateVaultDelegate(
agent: SolanaAgentKit,
vault: string,
delegateAddress: string,
) {
try {
const { vaultClient, cleanUp } = await initClients(agent);
const signature = await vaultClient.updateDelegate(
new PublicKey(vault),
new PublicKey(delegateAddress),
);
await cleanUp();
return signature;
} catch (e) {
throw new Error(
// @ts-expect-error - error message is a string
`Failed to update vault delegate: ${e.message}`,
);
}
}
/**
Update the vault's info
@param agent SolanaAgentKit instance
@param vault Vault address
@param params Vault update parameters
@param params.redeemPeriod Redeem period in seconds
@param params.maxTokens Maximum amount that can be deposited into the vault (in tokens)
@param params.minDepositAmount Minimum amount that can be deposited into the vault (in tokens)
@param params.managementFee Management fee percentage (e.g 2 == 2%)
@param params.profitShare Profit share percentage (e.g 20 == 20%)
@param params.hurdleRate Hurdle rate percentage
@param params.permissioned Whether the vault uses a whitelist
@returns Promise<anchor.Web3.TransactionSignature> - The transaction signature of the vault update
*/
export async function updateVault(
agent: SolanaAgentKit,
vault: string,
params: {
redeemPeriod?: number;
maxTokens?: number;
minDepositAmount?: number;
managementFee?: number;
profitShare?: number;
hurdleRate?: number;
permissioned?: boolean;
},
) {
try {
const { vaultClient, cleanUp, driftClient } = await initClients(agent);
const vaultPublicKey = new PublicKey(vault);
const vaultDetails = await vaultClient.getVault(vaultPublicKey);
const spotMarket = driftClient.getSpotMarketAccount(
vaultDetails.spotMarketIndex,
);
if (!spotMarket) {
throw new Error(
"Market not found. This vault's market is no longer supported",
);
}
const spotPrecision = TEN.pow(new BN(spotMarket.decimals));
const tx = await vaultClient.managerUpdateVault(vaultPublicKey, {
redeemPeriod: params.redeemPeriod
? new BN(params.redeemPeriod * 86400)
: null,
maxTokens: params.maxTokens
? numberToSafeBN(params.maxTokens, spotPrecision)
: null,
minDepositAmount: params.minDepositAmount
? numberToSafeBN(params.minDepositAmount, spotPrecision)
: null,
managementFee: params.managementFee
? new BN(params.managementFee)
.mul(PERCENTAGE_PRECISION)
.div(new BN(100))
: null,
profitShare: params.profitShare
? new BN(params.profitShare)
.mul(PERCENTAGE_PRECISION)
.div(new BN(100))
.toNumber()
: null,
hurdleRate: params.hurdleRate
? new BN(params.hurdleRate)
.mul(PERCENTAGE_PRECISION)
.div(new BN(100))
.toNumber()
: null,
permissioned: params.permissioned ?? vaultDetails.permissioned,
});
await cleanUp();
return tx;
} catch (e) {
// @ts-expect-error - error message is a string
throw new Error(`Failed to update Drift vault: ${e.message}`);
}
}
export const validateAndEncodeAddress = (input: string, programId: string) => {
try {
return new PublicKey(input);
} catch {
return getVaultAddressSync(new PublicKey(programId), encodeName(input));
}
};
/**
* Get information on a particular vault given its name
* @param agent
* @param vaultNameOrAddress
* @returns
*/
export async function getVaultInfo(
agent: SolanaAgentKit,
vaultNameOrAddress: string,
) {
try {
const { vaultClient, cleanUp } = await initClients(agent);
const vaultPublicKey = validateAndEncodeAddress(
vaultNameOrAddress,
vaultClient.program.programId.toBase58(),
);
const [vaultDetails, vaultBalance] = await Promise.all([
vaultClient.getVault(vaultPublicKey),
getVaultAvailableBalance(agent, vaultPublicKey.toBase58()),
]);
await cleanUp();
const spotToken = MainnetSpotMarkets[vaultDetails.spotMarketIndex];
const data = {
name: decodeName(vaultDetails.name),
delegate: vaultDetails.delegate.toBase58(),
address: vaultPublicKey.toBase58(),
marketName: `${spotToken.symbol}-SPOT`,
balance: `${vaultBalance} ${spotToken.symbol}`,
redeemPeriod: vaultDetails.redeemPeriod.toNumber(),
maxTokens: vaultDetails.maxTokens.div(spotToken.precision).toNumber(),
minDepositAmount: vaultDetails.minDepositAmount
.div(spotToken.precision)
.toNumber(),
managementFee:
(vaultDetails.managementFee.toNumber() /
PERCENTAGE_PRECISION.toNumber()) *
100,
profitShare:
(vaultDetails.profitShare / PERCENTAGE_PRECISION.toNumber()) * 100,
hurdleRate:
(vaultDetails.hurdleRate / PERCENTAGE_PRECISION.toNumber()) * 100,
permissioned: vaultDetails.permissioned,
};
return data;
} 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
@param amount Amount to deposit into the vault (in tokens)
@param vault Vault address
@returns Promise<anchor.Web3.TransactionSignature> - The transaction signature of the deposit
*/
export async function depositIntoVault(
agent: SolanaAgentKit,
amount: number,
vault: string,
) {
const { vaultClient, driftClient, cleanUp } = await initClients(agent);
try {
const vaultPublicKey = new PublicKey(vault);
const [isOwned, vaultDetails, vaultDepositor] = await Promise.all([
getIsOwned(agent, vault),
vaultClient.getVault(vaultPublicKey),
getOrCreateVaultDepositor(agent, vault),
]);
const spotMarket = driftClient.getSpotMarketAccount(
vaultDetails.spotMarketIndex,
);
if (!spotMarket) {
throw new Error(
"Market not found. This vaults market is no longer supported",
);
}
const spotPrecision = TEN.pow(new BN(spotMarket.decimals));
const amountBN = numberToSafeBN(amount, spotPrecision);
if (isOwned) {
return await vaultClient.managerDeposit(vaultPublicKey, amountBN);
}
const tx = 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}`);
}
}
/**
Request a withdrawal from a vault. If successful redemption period starts and the user can redeem the tokens after the period ends
@param agent SolanaAgentKit instance
@param amount Amount to withdraw from the vault (in shares)
@param vault Vault address
*/
export async function requestWithdrawalFromVault(
agent: SolanaAgentKit,
amount: number,
vault: string,
) {
try {
const { vaultClient, cleanUp } = await initClients(agent);
const vaultPublicKey = new PublicKey(vault);
const isOwned = await getIsOwned(agent, vault);
if (isOwned) {
return await vaultClient.managerRequestWithdraw(
vaultPublicKey,
numberToSafeBN(amount, QUOTE_PRECISION),
WithdrawUnit.TOKEN,
);
}
const vaultDepositor = await getOrCreateVaultDepositor(agent, vault);
const tx = await vaultClient.requestWithdraw(
vaultDepositor,
numberToSafeBN(amount, QUOTE_PRECISION),
WithdrawUnit.TOKEN,
);
await cleanUp();
return tx;
} catch (e) {
throw new Error(
// @ts-expect-error - error message is a string
`Failed to request withdrawal from Drift vault: ${e.message}`,
);
}
}
/**
Withdraw tokens once the redemption period has elapsed.
@param agent SolanaAgentKit instance
@param vault Vault address
@returns Promise<anchor.Web3.TransactionSignature> - The transaction signature of the redemption
*/
export async function withdrawFromDriftVault(
agent: SolanaAgentKit,
vault: string,
) {
try {
const { vaultClient, cleanUp } = await initClients(agent);
const vaultPublicKey = new PublicKey(vault);
const isOwned = await getIsOwned(agent, vault);
if (isOwned) {
return await vaultClient.managerWithdraw(vaultPublicKey);
}
const vaultDepositor = await getOrCreateVaultDepositor(agent, vault);
const tx = await vaultClient.withdraw(vaultDepositor);
await cleanUp();
return tx;
} catch (e) {
// @ts-expect-error - error message is a string
throw new Error(`Failed to redeem tokens from Drift vault: ${e.message}`);
}
}
/**
Get if vault is owned by the user
@param agent SolanaAgentKit instance
@param vault Vault address
@returns Promise<boolean> - Whether the vault is owned by the user
*/
async function getIsOwned(agent: SolanaAgentKit, vault: string) {
try {
const { vaultClient, cleanUp } = await initClients(agent);
const vaultPublicKey = new PublicKey(vault);
const vaultDetails = await vaultClient.getVault(vaultPublicKey);
const isOwned = vaultDetails.manager.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}`);
}
}
/**
* Get a vaults address using the vault's name
* @param agent
* @param name
*/
export async function getVaultAddress(agent: SolanaAgentKit, name: string) {
const encodedName = encodeName(name);
try {
const { vaultClient, cleanUp } = await initClients(agent);
const vaultAddress = getVaultAddressSync(
vaultClient.program.programId,
encodedName,
);
await cleanUp();
return vaultAddress;
} catch (e) {
throw new Error(
// @ts-expect-error - error message is a string
`Failed to get vault address: ${e.message}`,
);
}
}
/**
Carry out a trade with a delegated vault
@param agent SolanaAgentKit instance
@param amount Amount to trade (in tokens)
@param symbol Symbol of the token to trade
@param action Action to take (e.g. "buy" or "sell")
@param type Type of trade (e.g. "market" or "limit")
@param vault Vault address
*/
export async function tradeDriftVault(
agent: SolanaAgentKit,
vault: string,
amount: number,
symbol: string,
action: "long" | "short",
type: "market" | "limit",
price?: number,
) {
try {
const { driftClient, cleanUp } = await initClients(agent, {
authority: new PublicKey(vault),
activeSubAccountId: 0,
subAccountIds: [0],
});
const [isOwned, driftLookupTableAccount] = await Promise.all([
getIsOwned(agent, vault),
driftClient.fetchMarketLookupTableAccount(),
]);
if (!isOwned) {
throw new Error(
"This vault is owned/delegated to someone else, you can't trade with it",
);
}
const usdcSpotMarket = driftClient.getSpotMarketAccount(0);
if (!usdcSpotMarket) {
throw new Error("USDC-SPOT market not found");
}
const perpMarketIndexAndType = getMarketIndexAndType(
`${symbol.toUpperCase()}-PERP`,
);
const perpMarketAccount = driftClient.getPerpMarketAccount(
perpMarketIndexAndType.marketIndex,
);
if (!perpMarketIndexAndType || !perpMarketAccount) {
throw new Error(
"Invalid symbol: Drift doesn't have a market for this token",
);
}
const perpOracle = driftClient.getOracleDataForPerpMarket(
perpMarketAccount.marketIndex,
);
const oraclePriceNumber = convertToNumber(
perpOracle.price,
PRICE_PRECISION,
);
const baseAmount = amount / oraclePriceNumber;
const instructions: TransactionInstruction[] = [];
instructions.push(
ComputeBudgetProgram.setComputeUnitLimit({ units: 1400000 }),
);
if (type === "limit" || price) {
if (!price) {
throw new Error("Price is required for limit orders");
}
const instruction = await driftClient.getPlaceOrdersIx([
getOrderParams(
getLimitOrderParams({
price: numberToSafeBN(price, PRICE_PRECISION),
marketType: MarketType.PERP,
baseAssetAmount: numberToSafeBN(baseAmount, BASE_PRECISION),
direction:
action === "long"
? PositionDirection.LONG
: PositionDirection.SHORT,
marketIndex: perpMarketAccount.marketIndex,
postOnly: PostOnlyParams.SLIDE,
}),
),
]);
instructions.push(instruction);
} else {
// defaults to market order if type is not limit and price is not provided
const instruction = await driftClient.getPlaceOrdersIx([
getOrderParams(
getMarketOrderParams({
marketType: MarketType.PERP,
baseAssetAmount: numberToSafeBN(baseAmount, BASE_PRECISION),
direction:
action === "long"
? PositionDirection.LONG
: PositionDirection.SHORT,
marketIndex: perpMarketAccount.marketIndex,
}),
),
]);
instructions.push(instruction);
}
const latestBlockhash = await driftClient.connection.getLatestBlockhash();
const tx = await driftClient.txSender.sendVersionedTransaction(
await driftClient.txSender.getVersionedTransaction(
instructions,
[driftLookupTableAccount],
[],
driftClient.opts,
latestBlockhash,
),
);
await cleanUp();
return tx;
} catch (e) {
// @ts-expect-error - error message is a string
throw new Error(`Failed to trade with Drift vault: ${e.message}`);
}
}

2
src/tools/drift/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from "./drift";
export * from "./drift_vault";

33
src/tools/drift/types.ts Normal file
View File

@@ -0,0 +1,33 @@
import type { L2OrderBook, MarketType, OraclePriceData } from "@drift-labs/sdk";
export type L2WithOracle = L2OrderBook & { oracleData: OraclePriceData };
export type RawL2Output = {
marketIndex: number;
marketType: MarketType;
marketName: string;
asks: {
price: string;
size: string;
sources: {
[key: string]: string;
};
}[];
bids: {
price: string;
size: string;
sources: {
[key: string]: string;
};
}[];
oracleData: {
price: string;
slot: string;
confidence: string;
hasSufficientNumberOfDataPoints: boolean;
twap?: string;
twapConfidence?: string;
maxPrice?: string;
};
slot?: number;
};

View File

@@ -16,6 +16,7 @@ export * from "./pumpfun";
export * from "./pyth";
export * from "./raydium";
export * from "./rugcheck";
export * from "./drift";
export * from "./sendarcade";
export * from "./solayer";
export * from "./tensor";
@@ -25,3 +26,4 @@ export * from "./lightprotocol";
export * from "./squads";
export * from "./meteora";
export * from "./helius";
export * from "./voltr";

View File

@@ -1 +1,3 @@
export * from "./lend";
export * from "./lulo_lend";
export * from "./lulo_withdraw";

View File

@@ -0,0 +1,66 @@
import { VersionedTransaction } from "@solana/web3.js";
import { SolanaAgentKit } from "../../index";
/**
* Lend tokens for yields using Lulo
* @param agent SolanaAgentKit instance
* @param mintAddress SPL Mint address
* @param amount Amount to lend
* @returns Transaction signature
*/
export async function luloLend(
agent: SolanaAgentKit,
mintAddress: string,
amount: number,
): Promise<string> {
try {
const response = await fetch(
`https://api.flexlend.fi/generate/account/deposit?priorityFee=50000`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-wallet-pubkey": agent.wallet.publicKey.toBase58(),
"x-api-key": process.env.FLEXLEND_API_KEY!,
},
body: JSON.stringify({
owner: agent.wallet.publicKey.toBase58(),
mintAddress: mintAddress,
depositAmount: amount.toString(),
}),
},
);
const {
data: { transactionMeta },
} = await response.json();
// Deserialize the transaction
const luloTxn = VersionedTransaction.deserialize(
Buffer.from(transactionMeta[0].transaction, "base64"),
);
// Get a recent blockhash and set it
const { blockhash } = await agent.connection.getLatestBlockhash();
luloTxn.message.recentBlockhash = blockhash;
// Sign and send transaction
luloTxn.sign([agent.wallet]);
const signature = await agent.connection.sendTransaction(luloTxn, {
preflightCommitment: "confirmed",
maxRetries: 3,
});
// Wait for confirmation using the latest strategy
const latestBlockhash = await agent.connection.getLatestBlockhash();
await agent.connection.confirmTransaction({
signature,
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
});
return signature;
} catch (error: any) {
throw new Error(`Lending failed: ${error.message}`);
}
}

View File

@@ -0,0 +1,71 @@
import { VersionedTransaction } from "@solana/web3.js";
import { SolanaAgentKit } from "../../index";
/**
* Withdraw tokens for yields using Lulo
* @param agent SolanaAgentKit instance
* @param mintAddress SPL Mint address
* @param amount Amount to withdraw
* @returns Transaction signature
*/
export async function luloWithdraw(
agent: SolanaAgentKit,
mintAddress: string,
amount: number,
): Promise<string> {
try {
if (!agent.config.FLEXLEND_API_KEY) {
throw new Error("Lulo API key not found in agent configuration");
}
const response = await fetch(
`https://api.flexlend.fi/generate/account/withdraw?priorityFee=50000`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-wallet-pubkey": agent.wallet.publicKey.toBase58(),
"x-api-key": agent.config.FLEXLEND_API_KEY,
},
body: JSON.stringify({
owner: agent.wallet.publicKey.toBase58(),
mintAddress: mintAddress,
depositAmount: amount,
}),
},
);
const {
data: { transactionMeta },
} = await response.json();
// Deserialize the transaction
const luloTxn = VersionedTransaction.deserialize(
Buffer.from(transactionMeta[0].transaction, "base64"),
);
// Get a recent blockhash and set it
const { blockhash } = await agent.connection.getLatestBlockhash();
luloTxn.message.recentBlockhash = blockhash;
// Sign and send transaction
luloTxn.sign([agent.wallet]);
const signature = await agent.connection.sendTransaction(luloTxn, {
preflightCommitment: "confirmed",
maxRetries: 3,
});
// Wait for confirmation using the latest strategy
const latestBlockhash = await agent.connection.getLatestBlockhash();
await agent.connection.confirmTransaction({
signature,
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
});
return signature;
} catch (error: any) {
throw new Error(`Lending failed: ${error.message}`);
}
}

View File

@@ -0,0 +1,59 @@
import { LAMPORTS_PER_SOL, type PublicKey } from "@solana/web3.js";
import type { SolanaAgentKit } from "../../index";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { getTokenMetadata } from "../../utils/tokenMetadata";
/**
* Get the token balances of a Solana wallet
* @param agent - SolanaAgentKit instance
* @param token_address - Optional SPL token mint address. If not provided, returns SOL balance
* @returns Promise resolving to the balance as an object containing sol balance and token balances with their respective mints, symbols, names and decimals
*/
export async function get_token_balance(
agent: SolanaAgentKit,
walletAddress?: PublicKey,
): Promise<{
sol: number;
tokens: Array<{
tokenAddress: string;
name: string;
symbol: string;
balance: number;
decimals: number;
}>;
}> {
const [lamportsBalance, tokenAccountData] = await Promise.all([
agent.connection.getBalance(walletAddress ?? agent.wallet_address),
agent.connection.getParsedTokenAccountsByOwner(
walletAddress ?? agent.wallet_address,
{
programId: TOKEN_PROGRAM_ID,
},
),
]);
const removedZeroBalance = tokenAccountData.value.filter(
(v) => v.account.data.parsed.info.tokenAmount.uiAmount !== 0,
);
const tokenBalances = await Promise.all(
removedZeroBalance.map(async (v) => {
const mint = v.account.data.parsed.info.mint;
const mintInfo = await getTokenMetadata(agent.connection, mint);
return {
tokenAddress: mint,
name: mintInfo.name ?? "",
symbol: mintInfo.symbol ?? "",
balance: v.account.data.parsed.info.tokenAmount.uiAmount as number,
decimals: v.account.data.parsed.info.tokenAmount.decimals as number,
};
}),
);
const solBalance = lamportsBalance / LAMPORTS_PER_SOL;
return {
sol: solBalance,
tokens: tokenBalances,
};
}

View File

@@ -4,3 +4,4 @@ export * from "./close_empty_token_accounts";
export * from "./transfer";
export * from "./get_balance";
export * from "./get_balance_other";
export * from "./get_token_balances";

3
src/tools/voltr/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export * from "./voltr_deposit_strategy";
export * from "./voltr_withdraw_strategy";
export * from "./voltr_get_position_values";

View File

@@ -0,0 +1,99 @@
import { TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
import { SolanaAgentKit } from "../../agent";
import {
PublicKey,
sendAndConfirmTransaction,
Transaction,
} from "@solana/web3.js";
import { VoltrClient } from "@voltr/vault-sdk";
import BN from "bn.js";
/**
* Deposits assets into a Voltr strategy
* @param agent SolanaAgentKit instance
* @param depositAmount Amount to deposit in base units (BN)
* @param vault Public key of the target vault
* @param strategy Public key of the target strategy
* @returns Transaction signature for the deposit
*/
export async function voltrDepositStrategy(
agent: SolanaAgentKit,
depositAmount: BN,
vault: PublicKey,
strategy: PublicKey,
): Promise<string> {
const vc = new VoltrClient(agent.connection, agent.wallet);
const vaultAccount = await vc.fetchVaultAccount(vault);
const vaultAssetMint = vaultAccount.asset.mint;
const assetTokenProgram = await agent.connection
.getAccountInfo(new PublicKey(vaultAssetMint))
.then((account) => account?.owner);
if (
!assetTokenProgram ||
!(
assetTokenProgram.equals(TOKEN_PROGRAM_ID) ||
assetTokenProgram.equals(TOKEN_2022_PROGRAM_ID)
)
) {
throw new Error("Invalid asset token program");
}
const response = await fetch(
`https://voltr.xyz/api/remaining-accounts/deposit-strategy?vault=${vault.toBase58()}&strategy=${strategy.toBase58()}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
);
const data = (await response.json()).data as {
instructionDiscriminator: number[] | null;
additionalArgs: number[] | null;
remainingAccounts:
| {
pubkey: string;
isSigner: boolean;
isWritable: boolean;
}[]
| null;
};
const additionalArgs = data.additionalArgs
? Buffer.from(data.additionalArgs)
: null;
const instructionDiscriminator = data.instructionDiscriminator
? Buffer.from(data.instructionDiscriminator)
: null;
const remainingAccounts =
data.remainingAccounts?.map((account) => ({
pubkey: new PublicKey(account.pubkey),
isSigner: account.isSigner,
isWritable: account.isWritable,
})) ?? [];
const depositIx = await vc.createDepositStrategyIx(
{
depositAmount,
additionalArgs,
instructionDiscriminator,
},
{
vault,
vaultAssetMint,
strategy: strategy,
assetTokenProgram,
remainingAccounts,
},
);
const transaction = new Transaction();
transaction.add(depositIx);
const txSig = await sendAndConfirmTransaction(agent.connection, transaction, [
agent.wallet,
]);
return txSig;
}

View File

@@ -0,0 +1,20 @@
import { SolanaAgentKit } from "../../agent";
import { PublicKey } from "@solana/web3.js";
import { VoltrClient } from "@voltr/vault-sdk";
/**
* Gets the value of assets in a Voltr vault
* @param agent SolanaAgentKit instance
* @param vault Public key of the target vault
* @returns Position and total values for the vault
*/
export async function voltrGetPositionValues(
agent: SolanaAgentKit,
vault: PublicKey,
): Promise<string> {
const vc = new VoltrClient(agent.connection, agent.wallet);
const positionAndTotalValues =
await vc.getPositionAndTotalValuesForVault(vault);
return JSON.stringify(positionAndTotalValues);
}

View File

@@ -0,0 +1,99 @@
import { SolanaAgentKit } from "../../agent";
import {
PublicKey,
sendAndConfirmTransaction,
Transaction,
} from "@solana/web3.js";
import BN from "bn.js";
import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { VoltrClient } from "@voltr/vault-sdk";
/**
* Withdraws assets from a Voltr strategy
* @param agent SolanaAgentKit instance
* @param withdrawAmount Amount to withdraw in base units (BN)
* @param vault Public key of the target vault
* @param strategy Public key of the target strategy
* @returns Transaction signature for the deposit
*/
export async function voltrWithdrawStrategy(
agent: SolanaAgentKit,
withdrawAmount: BN,
vault: PublicKey,
strategy: PublicKey,
): Promise<string> {
const vc = new VoltrClient(agent.connection, agent.wallet);
const vaultAccount = await vc.fetchVaultAccount(vault);
const vaultAssetMint = vaultAccount.asset.mint;
const assetTokenProgram = await agent.connection
.getAccountInfo(new PublicKey(vaultAssetMint))
.then((account) => account?.owner);
if (
!assetTokenProgram ||
!(
assetTokenProgram.equals(TOKEN_PROGRAM_ID) ||
assetTokenProgram.equals(TOKEN_2022_PROGRAM_ID)
)
) {
throw new Error("Invalid asset token program");
}
const response = await fetch(
`https://voltr.xyz/api/remaining-accounts/deposit-strategy?vault=${vault.toBase58()}&strategy=${strategy.toBase58()}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
);
const data = (await response.json()).data as {
instructionDiscriminator: number[] | null;
additionalArgs: number[] | null;
remainingAccounts:
| {
pubkey: string;
isSigner: boolean;
isWritable: boolean;
}[]
| null;
};
const additionalArgs = data.additionalArgs
? Buffer.from(data.additionalArgs)
: null;
const instructionDiscriminator = data.instructionDiscriminator
? Buffer.from(data.instructionDiscriminator)
: null;
const remainingAccounts =
data.remainingAccounts?.map((account) => ({
pubkey: new PublicKey(account.pubkey),
isSigner: account.isSigner,
isWritable: account.isWritable,
})) ?? [];
const withdrawIx = await vc.createWithdrawStrategyIx(
{
withdrawAmount,
additionalArgs,
instructionDiscriminator,
},
{
vault,
vaultAssetMint,
strategy,
assetTokenProgram,
remainingAccounts,
},
);
const transaction = new Transaction();
transaction.add(withdrawIx);
const txSig = await sendAndConfirmTransaction(agent.connection, transaction, [
agent.wallet,
]);
return txSig;
}

View File

@@ -7,6 +7,7 @@ export interface Config {
JUPITER_REFERRAL_ACCOUNT?: string;
JUPITER_FEE_BPS?: number;
FLASH_PRIVILEGE?: string;
FLEXLEND_API_KEY?: string;
HELIUS_API_KEY?: string;
}

View File

@@ -0,0 +1,83 @@
import { Connection, PublicKey } from "@solana/web3.js";
export async function getTokenMetadata(
connection: Connection,
tokenMint: string,
) {
const METADATA_PROGRAM_ID = new PublicKey(
"metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s",
);
const [metadataPDA] = PublicKey.findProgramAddressSync(
[
Buffer.from("metadata"),
METADATA_PROGRAM_ID.toBuffer(),
new PublicKey(tokenMint).toBuffer(),
],
METADATA_PROGRAM_ID,
);
const metadata = await connection.getAccountInfo(metadataPDA);
if (!metadata?.data) {
throw new Error("Metadata not found");
}
let offset = 1 + 32 + 32; // key + update auth + mint
const data = metadata.data;
const decoder = new TextDecoder();
// Read variable length strings
const readString = () => {
let nameLength = data[offset];
while (nameLength === 0) {
offset++;
nameLength = data[offset];
if (offset >= data.length) {
return null;
}
}
offset++;
const name = decoder
.decode(data.slice(offset, offset + nameLength))
// @eslint-disable-next-line no-control-regex
.replace(new RegExp(String.fromCharCode(0), "g"), "");
offset += nameLength;
return name;
};
const name = readString();
const symbol = readString();
const uri = readString();
// Read remaining data
const sellerFeeBasisPoints = data.readUInt16LE(offset);
offset += 2;
let creators:
| { address: PublicKey; verified: boolean; share: number }[]
| null = null;
if (data[offset] === 1) {
offset++;
const numCreators = data[offset];
offset++;
creators = [...Array(numCreators)].map(() => {
const creator = {
address: new PublicKey(data.slice(offset, offset + 32)),
verified: data[offset + 32] === 1,
share: data[offset + 33],
};
offset += 34;
return creator;
});
}
return {
name,
symbol,
uri,
sellerFeeBasisPoints,
creators,
};
}

View File

@@ -14,7 +14,15 @@ export function createSolanaTools(
tools[key] = tool({
// @ts-expect-error Value matches type however TS still shows error
id: action.name,
description: action.description,
description: `
${action.description}
Similes: ${action.similes.map(
(simile) => `
${simile}
`,
)}
`.slice(0, 1023),
parameters: action.schema,
execute: async (params) =>
await executeAction(action, solanaAgentKit, params),