mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-22 23:26:45 +00:00
fix: Drift protocol team review implementation (#210)
# Pull Request Description This PR fixes an issue with validating if a user has a drift account or not. I noticed this while preparing a demo video to showcase how it works. It also fixes the gas fee required to borrow funds from drift. The PR also contains changes requested by the Drift Protocol team. The changes include: 1. Addition of actions to stake and unstake from their insurance fund 2. Addition of actions to swap spot tokens 3. Addition of actions to get information such as the lend and borrow apy, quote trade price impact and prices, and funding rates. 4. Addition of an action that gets available spot and perp markets. # Tests and prompts used        
This commit is contained in:
55
src/actions/drift/availableMarkets.ts
Normal file
55
src/actions/drift/availableMarkets.ts
Normal 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;
|
||||
@@ -27,7 +27,12 @@ const createDriftUserAccountAction: Action = {
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
amount: z.number().positive().describe("Amount of the token to deposit"),
|
||||
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) => {
|
||||
|
||||
@@ -53,9 +53,14 @@ const createDriftVaultAction: Action = {
|
||||
.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",
|
||||
"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",
|
||||
),
|
||||
minDepositAmount: z.number().positive().describe("Minimum deposit amount"),
|
||||
managementFee: z
|
||||
.number()
|
||||
.positive()
|
||||
|
||||
@@ -28,7 +28,9 @@ const depositIntoDriftVaultAction: Action = {
|
||||
amount: z
|
||||
.number()
|
||||
.positive()
|
||||
.describe("The amount in tokens you'd like to deposit into the vault"),
|
||||
.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 {
|
||||
|
||||
@@ -34,7 +34,7 @@ const depositToDriftUserAccountAction: Action = {
|
||||
.number()
|
||||
.positive()
|
||||
.describe(
|
||||
"The amount in tokens you'd like to deposit into your drift user account",
|
||||
"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()
|
||||
|
||||
65
src/actions/drift/entryQuoteOfPerpTrade.ts
Normal file
65
src/actions/drift/entryQuoteOfPerpTrade.ts
Normal 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;
|
||||
52
src/actions/drift/getLendAndBorrowAPY.ts
Normal file
52
src/actions/drift/getLendAndBorrowAPY.ts
Normal 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;
|
||||
61
src/actions/drift/perpMarketFundingRate.ts
Normal file
61
src/actions/drift/perpMarketFundingRate.ts
Normal 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;
|
||||
61
src/actions/drift/requestUnstakeFromDriftInsuranceFund.ts
Normal file
61
src/actions/drift/requestUnstakeFromDriftInsuranceFund.ts
Normal 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;
|
||||
@@ -29,7 +29,9 @@ const requestWithdrawalFromVaultAction: Action = {
|
||||
amount: z
|
||||
.number()
|
||||
.positive()
|
||||
.describe("Amount of shares you would like to withdraw from the vault"),
|
||||
.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 {
|
||||
|
||||
59
src/actions/drift/stakeToDriftInsuranceFund.ts
Normal file
59
src/actions/drift/stakeToDriftInsuranceFund.ts
Normal 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;
|
||||
78
src/actions/drift/swapSpotToken.ts
Normal file
78
src/actions/drift/swapSpotToken.ts
Normal 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;
|
||||
@@ -64,11 +64,20 @@ const tradeDelegatedDriftVaultAction: Action = {
|
||||
],
|
||||
schema: z.object({
|
||||
vaultAddress: z.string().describe("Address of the Drift vault to trade in"),
|
||||
amount: z.number().positive().describe("Amount to trade"),
|
||||
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("Price for limit order"),
|
||||
price: z
|
||||
.number()
|
||||
.positive()
|
||||
.optional()
|
||||
.describe("USD price for limit order"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input) => {
|
||||
try {
|
||||
|
||||
@@ -46,14 +46,27 @@ export const tradeDriftPerpAccountAction: Action = {
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
amount: z.number().positive(),
|
||||
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"]),
|
||||
type: z.enum(["market", "limit"]),
|
||||
price: z.number().positive().optional(),
|
||||
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 {
|
||||
|
||||
51
src/actions/drift/unstakeFromDriftInsuranceFund.ts
Normal file
51
src/actions/drift/unstakeFromDriftInsuranceFund.ts
Normal 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;
|
||||
@@ -24,8 +24,8 @@ const updateDriftVaultDelegateAction: Action = {
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
vaultAddress: z.string(),
|
||||
newDelegate: z.string(),
|
||||
vaultAddress: z.string().describe("vault's address"),
|
||||
newDelegate: z.string().describe("new address to delegate the vault to"),
|
||||
}),
|
||||
handler: async (agent, input) => {
|
||||
try {
|
||||
|
||||
@@ -41,16 +41,35 @@ const updateDriftVaultAction: Action = {
|
||||
redeemPeriod: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1, "Redeem period must be at least 1")
|
||||
.min(1, "Redeem period must be at least 1 day")
|
||||
.optional(),
|
||||
maxTokens: z
|
||||
.number()
|
||||
.int()
|
||||
.min(100, "Max tokens must be at least 100")
|
||||
.optional(),
|
||||
minDepositAmount: z.number().positive().optional(),
|
||||
managementFee: z.number().positive().max(20).optional(),
|
||||
profitShare: z.number().positive().max(90).optional(),
|
||||
.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()
|
||||
|
||||
@@ -36,7 +36,7 @@ const withdrawFromDriftAccountAction: Action = {
|
||||
.number()
|
||||
.positive()
|
||||
.describe(
|
||||
"The amount in tokens you'd like to withdraw from your drift account",
|
||||
"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()
|
||||
|
||||
@@ -25,7 +25,7 @@ const withdrawFromVaultAction: Action = {
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
vaultAddress: z.string(),
|
||||
vaultAddress: z.string().describe("Vault's address"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input) => {
|
||||
try {
|
||||
|
||||
@@ -61,6 +61,14 @@ 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";
|
||||
@@ -130,6 +138,15 @@ export const ACTIONS = {
|
||||
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,
|
||||
|
||||
@@ -100,6 +100,15 @@ import {
|
||||
withdrawFromDriftVault,
|
||||
updateVaultDelegate,
|
||||
get_token_balance,
|
||||
getAvailableDriftSpotMarkets,
|
||||
getAvailableDriftPerpMarkets,
|
||||
stakeToDriftInsuranceFund,
|
||||
requestUnstakeFromDriftInsuranceFund,
|
||||
unstakeFromDriftInsuranceFund,
|
||||
swapSpotToken,
|
||||
calculatePerpMarketFundingRate,
|
||||
getEntryQuoteOfPerpTrade,
|
||||
getLendingAndBorrowAPY,
|
||||
voltrGetPositionValues,
|
||||
voltrDepositStrategy,
|
||||
voltrWithdrawStrategy,
|
||||
@@ -759,6 +768,7 @@ export class SolanaAgentKit {
|
||||
async createDriftUserAccount(depositAmount: number, depositSymbol: string) {
|
||||
return await createDriftUserAccount(this, depositAmount, depositSymbol);
|
||||
}
|
||||
|
||||
async createDriftVault(params: {
|
||||
name: string;
|
||||
marketName: `${string}-${string}`;
|
||||
@@ -772,6 +782,7 @@ export class SolanaAgentKit {
|
||||
}) {
|
||||
return await createVault(this, params);
|
||||
}
|
||||
|
||||
async depositIntoDriftVault(amount: number, vault: string) {
|
||||
return await depositIntoVault(this, amount, vault);
|
||||
}
|
||||
@@ -854,6 +865,66 @@ export class SolanaAgentKit {
|
||||
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,
|
||||
|
||||
@@ -33,3 +33,9 @@ export const DEFAULT_OPTIONS = {
|
||||
export const JUP_API = "https://quote-api.jup.ag/v6";
|
||||
export const JUP_REFERRAL_ADDRESS =
|
||||
"REFER4ZgmyYx9c6He5XfaTMiGfdLwRnkV4RPp9t9iF3";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
39
src/langchain/drift/entry_quote_of_perp_trade.ts
Normal file
39
src/langchain/drift/entry_quote_of_perp_trade.ts
Normal 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",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,3 +13,10 @@ 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";
|
||||
|
||||
32
src/langchain/drift/lend_and_borrow_apy.ts
Normal file
32
src/langchain/drift/lend_and_borrow_apy.ts
Normal 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",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/langchain/drift/perp_market_funding_rate.ts
Normal file
36
src/langchain/drift/perp_market_funding_rate.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/langchain/drift/request_unstake_from_insurance_fund.ts
Normal file
37
src/langchain/drift/request_unstake_from_insurance_fund.ts
Normal 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",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/langchain/drift/stake_to_insurance_fund.ts
Normal file
37
src/langchain/drift/stake_to_insurance_fund.ts
Normal 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",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/langchain/drift/swap_spot_token.ts
Normal file
37
src/langchain/drift/swap_spot_token.ts
Normal 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",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/langchain/drift/unstake_from_insurance_fund.ts
Normal file
32
src/langchain/drift/unstake_from_insurance_fund.ts
Normal 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",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ export * from "./helius";
|
||||
export * from "./drift";
|
||||
export * from "./voltr";
|
||||
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import type { SolanaAgentKit } from "../agent";
|
||||
import {
|
||||
SolanaBalanceTool,
|
||||
SolanaBalanceOtherTool,
|
||||
@@ -117,6 +117,13 @@ import {
|
||||
SolanaUpdateDriftVaultTool,
|
||||
SolanaWithdrawFromDriftAccountTool,
|
||||
SolanaWithdrawFromDriftVaultTool,
|
||||
SolanaDriftLendAndBorrowAPYTool,
|
||||
SolanaDriftEntryQuoteOfPerpTradeTool,
|
||||
SolanaDriftPerpMarketFundingRateTool,
|
||||
SolanaDriftSpotTokenSwapTool,
|
||||
SolanaRequestUnstakeFromDriftInsuranceFundTool,
|
||||
SolanaStakeToDriftInsuranceFundTool,
|
||||
SolanaUnstakeFromDriftInsuranceFundTool,
|
||||
SolanaVoltrGetPositionValues,
|
||||
SolanaVoltrDepositStrategy,
|
||||
SolanaVoltrWithdrawStrategy,
|
||||
@@ -216,6 +223,13 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
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),
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
import {
|
||||
BASE_PRECISION,
|
||||
BigNum,
|
||||
calculateDepositRate,
|
||||
calculateEstimatedEntryPriceWithL2,
|
||||
calculateInterestRate,
|
||||
calculateLongShortFundingRateAndLiveTwaps,
|
||||
convertToNumber,
|
||||
DRIFT_PROGRAM_ID,
|
||||
DriftClient,
|
||||
FastSingleTxSender,
|
||||
FUNDING_RATE_BUFFER_PRECISION,
|
||||
FUNDING_RATE_PRECISION_EXP,
|
||||
getInsuranceFundStakeAccountPublicKey,
|
||||
getLimitOrderParams,
|
||||
getMarketOrderParams,
|
||||
getUserAccountPublicKeySync,
|
||||
JupiterClient,
|
||||
MainnetPerpMarkets,
|
||||
MainnetSpotMarkets,
|
||||
numberToSafeBN,
|
||||
PERCENTAGE_PRECISION,
|
||||
PositionDirection,
|
||||
PostOnlyParams,
|
||||
PRICE_PRECISION,
|
||||
@@ -23,6 +34,8 @@ import { getAssociatedTokenAddressSync } from "@solana/spl-token";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Transaction } from "@solana/web3.js";
|
||||
import { ComputeBudgetProgram } from "@solana/web3.js";
|
||||
import type { RawL2Output } from "./types";
|
||||
import { MINIMUM_COMPUTE_PRICE_FOR_COMPLEX_ACTIONS } from "../../constants";
|
||||
|
||||
export async function initClients(
|
||||
agent: SolanaAgentKit,
|
||||
@@ -56,7 +69,7 @@ export async function initClients(
|
||||
activeSubAccountId: params?.activeSubAccountId,
|
||||
subAccountIds: params?.subAccountIds,
|
||||
txParams: {
|
||||
computeUnitsPrice: 0.000001 * 1000000 * 1000000,
|
||||
computeUnitsPrice: MINIMUM_COMPUTE_PRICE_FOR_COMPLEX_ACTIONS,
|
||||
},
|
||||
txSender: new FastSingleTxSender({
|
||||
connection: agent.connection,
|
||||
@@ -115,7 +128,10 @@ export async function createDriftUserAccount(
|
||||
);
|
||||
|
||||
if (!token) {
|
||||
throw new Error(`Token with symbol ${symbol} not found`);
|
||||
throw new Error(`Token with symbol ${symbol} not found. Here's a list of available spot markets: ${MainnetSpotMarkets.map(
|
||||
(v) => v.symbol,
|
||||
).join(", ")}
|
||||
`);
|
||||
}
|
||||
|
||||
if (!userAccountExists) {
|
||||
@@ -171,7 +187,11 @@ export async function depositToDriftUserAccount(
|
||||
);
|
||||
|
||||
if (!token) {
|
||||
throw new Error(`Token with symbol ${symbol} not found`);
|
||||
throw new Error(
|
||||
`Token with symbol ${symbol} not found. Here's a list of available spot markets: ${MainnetSpotMarkets.map(
|
||||
(v) => v.symbol,
|
||||
).join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!userAccountExists) {
|
||||
@@ -193,7 +213,7 @@ export async function depositToDriftUserAccount(
|
||||
|
||||
const tx = new Transaction().add(...depInstruction).add(
|
||||
ComputeBudgetProgram.setComputeUnitPrice({
|
||||
microLamports: 0.000001 * 1000000 * 1000000,
|
||||
microLamports: MINIMUM_COMPUTE_PRICE_FOR_COMPLEX_ACTIONS,
|
||||
}),
|
||||
);
|
||||
tx.recentBlockhash = latestBlockhash.blockhash;
|
||||
@@ -237,7 +257,11 @@ export async function withdrawFromDriftUserAccount(
|
||||
);
|
||||
|
||||
if (!token) {
|
||||
throw new Error(`Token with symbol ${symbol} not found`);
|
||||
throw new Error(
|
||||
`Token with symbol ${symbol} not found. Here's a list of available spot markets: ${MainnetSpotMarkets.map(
|
||||
(v) => v.symbol,
|
||||
).join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const withdrawAmount = numberToSafeBN(amount, token.precision);
|
||||
@@ -254,7 +278,7 @@ export async function withdrawFromDriftUserAccount(
|
||||
|
||||
const tx = new Transaction().add(...withdrawInstruction).add(
|
||||
ComputeBudgetProgram.setComputeUnitPrice({
|
||||
microLamports: 0.000001 * 1000000 * 1000000,
|
||||
microLamports: MINIMUM_COMPUTE_PRICE_FOR_COMPLEX_ACTIONS,
|
||||
}),
|
||||
);
|
||||
tx.recentBlockhash = latestBlockhash.blockhash;
|
||||
@@ -313,7 +337,11 @@ export async function driftPerpTrade(
|
||||
);
|
||||
|
||||
if (!market) {
|
||||
throw new Error(`Token with symbol ${params.symbol} not found`);
|
||||
throw new Error(
|
||||
`Token with symbol ${params.symbol} not found. Here's a list of available perp markets: ${MainnetPerpMarkets.map(
|
||||
(v) => v.symbol,
|
||||
).join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const baseAssetPrice = driftClient.getOracleDataForPerpMarket(
|
||||
@@ -357,7 +385,7 @@ export async function driftPerpTrade(
|
||||
marketIndex: market.marketIndex,
|
||||
}),
|
||||
{
|
||||
computeUnitsPrice: 0.000001 * 1000000 * 1000000,
|
||||
computeUnitsPrice: MINIMUM_COMPUTE_PRICE_FOR_COMPLEX_ACTIONS,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -388,9 +416,11 @@ export async function doesUserHaveDriftAccount(agent: SolanaAgentKit) {
|
||||
agent.wallet.publicKey,
|
||||
),
|
||||
});
|
||||
await user.subscribe();
|
||||
user.getActivePerpPositions();
|
||||
const userAccountExists = await user.exists();
|
||||
await cleanUp();
|
||||
await user.unsubscribe();
|
||||
return {
|
||||
hasAccount: userAccountExists,
|
||||
account: user.userAccountPublicKey,
|
||||
@@ -433,10 +463,9 @@ export async function driftUserAccountInfo(agent: SolanaAgentKit) {
|
||||
}));
|
||||
const spotPositions = account.spotPositions.map((pos) => ({
|
||||
...pos,
|
||||
scaledBalance: convertToNumber(pos.scaledBalance, BASE_PRECISION),
|
||||
cumulativeDeposits: convertToNumber(
|
||||
pos.cumulativeDeposits,
|
||||
BASE_PRECISION,
|
||||
availableBalance: convertToNumber(
|
||||
pos.scaledBalance,
|
||||
MainnetSpotMarkets[pos.marketIndex].precision,
|
||||
),
|
||||
symbol: MainnetSpotMarkets.find((v) => v.marketIndex === pos.marketIndex)
|
||||
?.symbol,
|
||||
@@ -446,8 +475,6 @@ export async function driftUserAccountInfo(agent: SolanaAgentKit) {
|
||||
...account,
|
||||
name: account.name,
|
||||
authority: account.authority,
|
||||
totalDeposits: `$${convertToNumber(account.totalDeposits, QUOTE_PRECISION)}`,
|
||||
totalWithdraws: `$${convertToNumber(account.totalWithdraws, QUOTE_PRECISION)}`,
|
||||
settledPerpPnl: `$${convertToNumber(account.settledPerpPnl, QUOTE_PRECISION)}`,
|
||||
lastActiveSlot: account.lastActiveSlot.toNumber(),
|
||||
perpPositions,
|
||||
@@ -458,3 +485,527 @@ export async function driftUserAccountInfo(agent: SolanaAgentKit) {
|
||||
throw new Error(`Failed to check user account: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available spot markets on drift protocol
|
||||
*/
|
||||
export function getAvailableDriftSpotMarkets() {
|
||||
return MainnetSpotMarkets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available perp markets on drift protocol
|
||||
*/
|
||||
export function getAvailableDriftPerpMarkets() {
|
||||
return MainnetPerpMarkets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stake a token to the drift insurance fund
|
||||
* @param agent
|
||||
* @param amount
|
||||
* @param symbol
|
||||
*/
|
||||
export async function stakeToDriftInsuranceFund(
|
||||
agent: SolanaAgentKit,
|
||||
amount: number,
|
||||
symbol: string,
|
||||
) {
|
||||
try {
|
||||
const { cleanUp, driftClient } = await initClients(agent);
|
||||
const token = MainnetSpotMarkets.find(
|
||||
(v) => v.symbol === symbol.toUpperCase(),
|
||||
);
|
||||
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
`Token with symbol ${symbol} not found. Here's a list of available spot markets: ${MainnetSpotMarkets.map(
|
||||
(v) => v.symbol,
|
||||
).join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const deriveInsuranceFundStakeAccount =
|
||||
getInsuranceFundStakeAccountPublicKey(
|
||||
driftClient.program.programId,
|
||||
agent.wallet.publicKey,
|
||||
token.marketIndex,
|
||||
);
|
||||
let shouldCreateAccount = false;
|
||||
|
||||
try {
|
||||
await driftClient.connection.getAccountInfo(
|
||||
deriveInsuranceFundStakeAccount,
|
||||
);
|
||||
} catch (e) {
|
||||
// @ts-expect-error - error message is a string
|
||||
if (e.message.includes("Account not found")) {
|
||||
shouldCreateAccount = true;
|
||||
}
|
||||
}
|
||||
|
||||
const signature = await driftClient.addInsuranceFundStake({
|
||||
amount: numberToSafeBN(amount, token.precision),
|
||||
marketIndex: token.marketIndex,
|
||||
collateralAccountPublicKey: getAssociatedTokenAddressSync(
|
||||
token.mint,
|
||||
agent.wallet.publicKey,
|
||||
),
|
||||
initializeStakeAccount: shouldCreateAccount,
|
||||
txParams: {
|
||||
computeUnitsPrice: MINIMUM_COMPUTE_PRICE_FOR_COMPLEX_ACTIONS,
|
||||
},
|
||||
});
|
||||
|
||||
await cleanUp();
|
||||
return signature;
|
||||
} catch (e) {
|
||||
// @ts-expect-error - error message is a string
|
||||
throw new Error(`Failed to get APYs: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request an unstake from the drift insurance fund
|
||||
* @param agent
|
||||
* @param amount
|
||||
* @param symbol
|
||||
*/
|
||||
export async function requestUnstakeFromDriftInsuranceFund(
|
||||
agent: SolanaAgentKit,
|
||||
amount: number,
|
||||
symbol: string,
|
||||
) {
|
||||
try {
|
||||
const { driftClient, cleanUp } = await initClients(agent);
|
||||
const token = MainnetSpotMarkets.find(
|
||||
(v) => v.symbol === symbol.toUpperCase(),
|
||||
);
|
||||
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
`Token with symbol ${symbol} not found. Here's a list of available spot markets: ${MainnetSpotMarkets.map(
|
||||
(v) => v.symbol,
|
||||
).join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const signature = await driftClient.requestRemoveInsuranceFundStake(
|
||||
token.marketIndex,
|
||||
numberToSafeBN(amount, token.precision),
|
||||
{ computeUnitsPrice: MINIMUM_COMPUTE_PRICE_FOR_COMPLEX_ACTIONS },
|
||||
);
|
||||
|
||||
await cleanUp();
|
||||
return signature;
|
||||
} catch (e) {
|
||||
// @ts-expect-error error message is a string
|
||||
throw new Error(`Failed to unstake from insurance fund: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unstake requested funds from the drift insurance fund once cool down period is elapsed
|
||||
* @param agent
|
||||
* @param symbol
|
||||
*/
|
||||
export async function unstakeFromDriftInsuranceFund(
|
||||
agent: SolanaAgentKit,
|
||||
symbol: string,
|
||||
) {
|
||||
try {
|
||||
const { driftClient, cleanUp } = await initClients(agent);
|
||||
const token = MainnetSpotMarkets.find(
|
||||
(v) => v.symbol === symbol.toUpperCase(),
|
||||
);
|
||||
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
`Token with symbol ${symbol} not found. Here's a list of available spot markets: ${MainnetSpotMarkets.map(
|
||||
(v) => v.symbol,
|
||||
).join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const signature = await driftClient.removeInsuranceFundStake(
|
||||
token.marketIndex,
|
||||
getAssociatedTokenAddressSync(token.mint, agent.wallet.publicKey),
|
||||
{
|
||||
computeUnitsPrice: MINIMUM_COMPUTE_PRICE_FOR_COMPLEX_ACTIONS,
|
||||
},
|
||||
);
|
||||
|
||||
await cleanUp();
|
||||
return signature;
|
||||
} catch (e) {
|
||||
// @ts-expect-error error message is a string
|
||||
throw new Error(`Failed to unstake from insurance fund: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap a spot token for another on drift
|
||||
* @param agent
|
||||
* @param params
|
||||
* @param params.fromSymbol symbol of the token to deposit
|
||||
* @param params.toSymbol symbol of the token to receive
|
||||
* @param params.fromAmount amount of the token to deposit
|
||||
* @param params.toAmount amount of the token to receive
|
||||
* @param params.slippage slippage tolerance in percentage
|
||||
*/
|
||||
export async function swapSpotToken(
|
||||
agent: SolanaAgentKit,
|
||||
params: {
|
||||
fromSymbol: string;
|
||||
toSymbol: string;
|
||||
slippage?: number | undefined;
|
||||
} & (
|
||||
| {
|
||||
fromAmount: number;
|
||||
}
|
||||
| {
|
||||
toAmount: number;
|
||||
}
|
||||
),
|
||||
) {
|
||||
try {
|
||||
const { driftClient, cleanUp } = await initClients(agent);
|
||||
const fromToken = MainnetSpotMarkets.find(
|
||||
(v) => v.symbol === params.fromSymbol.toUpperCase(),
|
||||
);
|
||||
const toToken = MainnetSpotMarkets.find(
|
||||
(v) => v.symbol === params.toSymbol.toUpperCase(),
|
||||
);
|
||||
|
||||
if (!fromToken) {
|
||||
throw new Error(
|
||||
`Token with symbol ${params.fromSymbol} not found. Here's a list of available spot markets: ${MainnetSpotMarkets.map(
|
||||
(v) => v.symbol,
|
||||
).join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!toToken) {
|
||||
throw new Error(
|
||||
`Token with symbol ${params.toSymbol} not found. Here's a list of available spot markets: ${MainnetSpotMarkets.map(
|
||||
(v) => v.symbol,
|
||||
).join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
let txSig: string;
|
||||
|
||||
// @ts-expect-error - false undefined type conflict
|
||||
if (params.fromAmount) {
|
||||
const jupiterClient = new JupiterClient({ connection: agent.connection });
|
||||
// @ts-expect-error - false undefined type conflict
|
||||
const fromAmount = numberToSafeBN(params.fromAmount, fromToken.precision);
|
||||
const res = await (
|
||||
await fetch(
|
||||
`https://quote-api.jup.ag/v6/quote?inputMint=${fromToken.mint}&outputMint=${toToken.mint}&amount=${fromAmount.toNumber()}&slippageBps=${(params.slippage ?? 0.5) * 100}&swapMode=ExactIn`,
|
||||
)
|
||||
).json();
|
||||
const signature = await driftClient.swap({
|
||||
amount: fromAmount,
|
||||
inMarketIndex: fromToken.marketIndex,
|
||||
outMarketIndex: toToken.marketIndex,
|
||||
jupiterClient: jupiterClient,
|
||||
v6: {
|
||||
quote: res,
|
||||
},
|
||||
slippageBps: (params.slippage ?? 0.5) * 100,
|
||||
swapMode: "ExactIn",
|
||||
});
|
||||
|
||||
txSig = signature;
|
||||
}
|
||||
|
||||
// @ts-expect-error - false undefined type conflict
|
||||
if (params.toAmount) {
|
||||
const jupiterClient = new JupiterClient({ connection: agent.connection });
|
||||
// @ts-expect-error - false undefined type conflict
|
||||
const toAmount = numberToSafeBN(params.toAmount, toToken.precision);
|
||||
const res = await (
|
||||
await fetch(
|
||||
`https://quote-api.jup.ag/v6/quote?inputMint=${fromToken.mint}&outputMint=${toToken.mint}&amount=${toAmount.toNumber()}&slippageBps=${(params.slippage ?? 0.5) * 100}&swapMode=ExactOut`,
|
||||
)
|
||||
).json();
|
||||
const signature = await driftClient.swap({
|
||||
amount: toAmount,
|
||||
inMarketIndex: toToken.marketIndex,
|
||||
outMarketIndex: fromToken.marketIndex,
|
||||
jupiterClient: jupiterClient,
|
||||
v6: {
|
||||
quote: res,
|
||||
},
|
||||
slippageBps: (params.slippage ?? 0.5) * 100,
|
||||
swapMode: "ExactOut",
|
||||
});
|
||||
|
||||
txSig = signature;
|
||||
}
|
||||
|
||||
await cleanUp();
|
||||
|
||||
// @ts-expect-error - false use before assignment
|
||||
if (txSig) {
|
||||
return txSig;
|
||||
}
|
||||
|
||||
throw new Error("Either fromAmount or toAmount must be provided");
|
||||
} catch (e) {
|
||||
// @ts-expect-error error message is a string
|
||||
throw new Error(`Failed to swap token: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To get funding rate as a percentage, you need to multiply by the funding rate buffer precision
|
||||
* @param rawFundingRate
|
||||
*/
|
||||
export function getFundingRateAsPercentage(rawFundingRate: anchor.BN) {
|
||||
return BigNum.from(
|
||||
rawFundingRate.mul(FUNDING_RATE_BUFFER_PRECISION),
|
||||
FUNDING_RATE_PRECISION_EXP,
|
||||
).toNum();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the funding rate for a perpetual market
|
||||
* @param agent
|
||||
* @param marketSymbol
|
||||
*/
|
||||
export async function calculatePerpMarketFundingRate(
|
||||
agent: SolanaAgentKit,
|
||||
marketSymbol: `${string}-PERP`,
|
||||
period: "year" | "hour",
|
||||
) {
|
||||
try {
|
||||
const { driftClient, cleanUp } = await initClients(agent);
|
||||
const market = driftClient.getMarketIndexAndType(
|
||||
`${marketSymbol.toUpperCase()}`,
|
||||
);
|
||||
|
||||
if (!market) {
|
||||
throw new Error(
|
||||
`This market isn't available on the Drift Protocol. Here's a list of markets that are: ${MainnetPerpMarkets.map(
|
||||
(v) => v.symbol,
|
||||
).join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const marketAccount = driftClient.getPerpMarketAccount(market.marketIndex);
|
||||
|
||||
if (!marketAccount) {
|
||||
throw new Error("Market account not found");
|
||||
}
|
||||
|
||||
const [
|
||||
_marketTwapLive,
|
||||
_oracleTwapLive,
|
||||
longFundingRate,
|
||||
shortFundingRate,
|
||||
] = await calculateLongShortFundingRateAndLiveTwaps(
|
||||
marketAccount,
|
||||
driftClient.getOracleDataForPerpMarket(market.marketIndex),
|
||||
undefined,
|
||||
new anchor.BN(Date.now()),
|
||||
);
|
||||
|
||||
await cleanUp();
|
||||
|
||||
let longFundingRateNum = getFundingRateAsPercentage(longFundingRate);
|
||||
let shortFundingRateNum = getFundingRateAsPercentage(shortFundingRate);
|
||||
|
||||
if (period === "year") {
|
||||
const paymentsPerYear = 24 * 365.25;
|
||||
|
||||
longFundingRateNum *= paymentsPerYear;
|
||||
shortFundingRateNum *= paymentsPerYear;
|
||||
}
|
||||
|
||||
const longsArePaying = longFundingRateNum > 0;
|
||||
const shortsArePaying = !(shortFundingRateNum > 0);
|
||||
|
||||
const longsAreString = longsArePaying ? "pay" : "receive";
|
||||
const shortsAreString = !shortsArePaying ? "receive" : "pay";
|
||||
|
||||
const absoluteLongFundingRateNum = Math.abs(longFundingRateNum);
|
||||
const absoluteShortFundingRateNum = Math.abs(shortFundingRateNum);
|
||||
|
||||
const formattedLongRatePct = absoluteLongFundingRateNum.toFixed(
|
||||
period === "hour" ? 5 : 2,
|
||||
);
|
||||
const formattedShortRatePct = absoluteShortFundingRateNum.toFixed(
|
||||
period === "hour" ? 5 : 2,
|
||||
);
|
||||
|
||||
const paymentUnit = period === "year" ? "% APR" : "%";
|
||||
|
||||
const friendlyString = `At this rate, longs would ${longsAreString} ${formattedLongRatePct} ${paymentUnit} and shorts would ${shortsAreString} ${formattedShortRatePct} ${paymentUnit} at the end of the hour.`;
|
||||
|
||||
return {
|
||||
longRate: longsArePaying
|
||||
? -absoluteLongFundingRateNum
|
||||
: absoluteLongFundingRateNum,
|
||||
shortRate: shortsArePaying
|
||||
? -absoluteShortFundingRateNum
|
||||
: absoluteShortFundingRateNum,
|
||||
friendlyString,
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
// @ts-expect-error e.message is a string
|
||||
`Something went wrong while trying to get the market's funding rate. Here's some more context: ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getL2OrderBook(marketSymbol: `${string}-PERP`) {
|
||||
try {
|
||||
const serializedOrderbook: RawL2Output = await (
|
||||
await fetch(
|
||||
`https://dlob.drift.trade/l2?marketName=${marketSymbol.toUpperCase()}&includeOracle=true`,
|
||||
)
|
||||
).json();
|
||||
|
||||
return {
|
||||
asks: serializedOrderbook.asks.map((ask) => ({
|
||||
price: new anchor.BN(ask.price),
|
||||
size: new anchor.BN(ask.size),
|
||||
sources: Object.entries(ask.sources).reduce((previous, [key, val]) => {
|
||||
return {
|
||||
...(previous ?? {}),
|
||||
[key]: new anchor.BN(val),
|
||||
};
|
||||
}, {}),
|
||||
})),
|
||||
bids: serializedOrderbook.bids.map((bid) => ({
|
||||
price: new anchor.BN(bid.price),
|
||||
size: new anchor.BN(bid.size),
|
||||
sources: Object.entries(bid.sources).reduce((previous, [key, val]) => {
|
||||
return {
|
||||
...(previous ?? {}),
|
||||
[key]: new anchor.BN(val),
|
||||
};
|
||||
}, {}),
|
||||
})),
|
||||
oracleData: {
|
||||
price: serializedOrderbook.oracleData.price
|
||||
? new anchor.BN(serializedOrderbook.oracleData.price)
|
||||
: undefined,
|
||||
slot: serializedOrderbook.oracleData.slot
|
||||
? new anchor.BN(serializedOrderbook.oracleData.slot)
|
||||
: undefined,
|
||||
confidence: serializedOrderbook.oracleData.confidence
|
||||
? new anchor.BN(serializedOrderbook.oracleData.confidence)
|
||||
: undefined,
|
||||
hasSufficientNumberOfDataPoints:
|
||||
serializedOrderbook.oracleData.hasSufficientNumberOfDataPoints,
|
||||
twap: serializedOrderbook.oracleData.twap
|
||||
? new anchor.BN(serializedOrderbook.oracleData.twap)
|
||||
: undefined,
|
||||
twapConfidence: serializedOrderbook.oracleData.twapConfidence
|
||||
? new anchor.BN(serializedOrderbook.oracleData.twapConfidence)
|
||||
: undefined,
|
||||
maxPrice: serializedOrderbook.oracleData.maxPrice
|
||||
? new anchor.BN(serializedOrderbook.oracleData.maxPrice)
|
||||
: undefined,
|
||||
},
|
||||
slot: serializedOrderbook.slot,
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the estimated entry quote of a perp trade
|
||||
* @param agent
|
||||
* @param marketSymbol
|
||||
* @param amount
|
||||
* @param type
|
||||
*/
|
||||
export async function getEntryQuoteOfPerpTrade(
|
||||
marketSymbol: `${string}-PERP`,
|
||||
amount: number,
|
||||
type: "long" | "short",
|
||||
) {
|
||||
try {
|
||||
const l2OrderBookData = await getL2OrderBook(marketSymbol);
|
||||
const estimatedEntryPriceData = calculateEstimatedEntryPriceWithL2(
|
||||
"quote",
|
||||
numberToSafeBN(amount, BASE_PRECISION),
|
||||
type === "long" ? PositionDirection.LONG : PositionDirection.SHORT,
|
||||
BASE_PRECISION,
|
||||
// @ts-expect-error - false type conflict
|
||||
l2OrderBookData,
|
||||
);
|
||||
|
||||
return {
|
||||
entryPrice: convertToNumber(
|
||||
estimatedEntryPriceData.entryPrice,
|
||||
QUOTE_PRECISION,
|
||||
),
|
||||
priceImpact: convertToNumber(
|
||||
estimatedEntryPriceData.priceImpact,
|
||||
QUOTE_PRECISION,
|
||||
),
|
||||
bestPrice: convertToNumber(
|
||||
estimatedEntryPriceData.bestPrice,
|
||||
QUOTE_PRECISION,
|
||||
),
|
||||
worstPrice: convertToNumber(
|
||||
estimatedEntryPriceData.worstPrice,
|
||||
QUOTE_PRECISION,
|
||||
),
|
||||
};
|
||||
} catch (e) {
|
||||
// @ts-expect-error - error message is a string
|
||||
throw new Error(`Failed to get entry quote: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the APY for lending and borrowing a specific token on drift protocol
|
||||
* @param agent
|
||||
* @param symbol
|
||||
*/
|
||||
export async function getLendingAndBorrowAPY(
|
||||
agent: SolanaAgentKit,
|
||||
symbol: string,
|
||||
) {
|
||||
try {
|
||||
const { driftClient, cleanUp } = await initClients(agent);
|
||||
const token = MainnetSpotMarkets.find(
|
||||
(v) => v.symbol === symbol.toUpperCase(),
|
||||
);
|
||||
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
`Token with symbol ${symbol} not found. Here's a list of available spot markets: ${MainnetSpotMarkets.map(
|
||||
(v) => v.symbol,
|
||||
).join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const marketAccount = driftClient.getSpotMarketAccount(token.marketIndex);
|
||||
|
||||
if (!marketAccount) {
|
||||
throw new Error("Market account not found");
|
||||
}
|
||||
|
||||
const lendAPY = calculateDepositRate(marketAccount);
|
||||
const borrowAPY = calculateInterestRate(marketAccount);
|
||||
|
||||
await cleanUp();
|
||||
|
||||
return {
|
||||
lendingAPY: convertToNumber(lendAPY, PERCENTAGE_PRECISION) * 100, // convert to percentage
|
||||
borrowAPY: convertToNumber(borrowAPY, PERCENTAGE_PRECISION) * 100, // convert to percentage
|
||||
};
|
||||
} catch (e) {
|
||||
// @ts-expect-error - error message is a string
|
||||
throw new Error(`Failed to get APYs: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,14 +37,18 @@ export function getMarketIndexAndType(name: `${string}-${string}`) {
|
||||
if (type === "PERP") {
|
||||
const token = MainnetPerpMarkets.find((v) => v.baseAssetSymbol === symbol);
|
||||
if (!token) {
|
||||
throw new Error("Drift doesn't have that market");
|
||||
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");
|
||||
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 };
|
||||
}
|
||||
@@ -134,22 +138,22 @@ export async function createVault(
|
||||
const { vaultClient, driftClient, cleanUp } = await initClients(agent);
|
||||
const marketIndexAndType = getMarketIndexAndType(params.marketName);
|
||||
|
||||
if (!marketIndexAndType) {
|
||||
throw new Error("Invalid market name");
|
||||
}
|
||||
|
||||
const spotMarket = driftClient.getSpotMarketAccount(
|
||||
marketIndexAndType.marketIndex,
|
||||
);
|
||||
|
||||
if (!spotMarket) {
|
||||
throw new Error("Market not found");
|
||||
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");
|
||||
throw new Error(
|
||||
`Only SPOT market names are supported. Such as ${MainnetSpotMarkets.map((v) => `${v.symbol}-SPOT`).join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const tx = await vaultClient.initializeVault({
|
||||
@@ -239,7 +243,9 @@ export async function updateVault(
|
||||
);
|
||||
|
||||
if (!spotMarket) {
|
||||
throw new Error("Market not found");
|
||||
throw new Error(
|
||||
"Market not found. This vault's market is no longer supported",
|
||||
);
|
||||
}
|
||||
|
||||
const spotPrecision = TEN.pow(new BN(spotMarket.decimals));
|
||||
@@ -370,7 +376,9 @@ export async function depositIntoVault(
|
||||
);
|
||||
|
||||
if (!spotMarket) {
|
||||
throw new Error("Market not found");
|
||||
throw new Error(
|
||||
"Market not found. This vaults market is no longer supported",
|
||||
);
|
||||
}
|
||||
|
||||
const spotPrecision = TEN.pow(new BN(spotMarket.decimals));
|
||||
@@ -544,7 +552,7 @@ export async function tradeDriftVault(
|
||||
|
||||
if (!isOwned) {
|
||||
throw new Error(
|
||||
"This vault is owned by someone else, so you can't trade with it",
|
||||
"This vault is owned/delegated to someone else, you can't trade with it",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
33
src/tools/drift/types.ts
Normal file
33
src/tools/drift/types.ts
Normal 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;
|
||||
};
|
||||
@@ -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),
|
||||
|
||||
@@ -194,4 +194,4 @@ if (require.main === module) {
|
||||
console.error("Fatal error:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user