Feat: Drift integration (#207)

# Pull Request Description

This PR adds [Drift protocol](https://drift.trade) to SolanaAgentKit.
Functionality implemented includes:
1. Vault management: creation, depositing, trading, and withdrawal
2. Perpetual trading: trade opening, closing, and order creation
3. Account management: create, deposit, withdraw, lend and borrow

## Changes Made
This PR adds the following changes:
<!-- List the key changes made in this PR -->
- This PR adds drift protocol to the SDK
  
## Implementation Details
<!-- Provide technical details about the implementation -->
- This integration required the following SDKs to be installed
`@drift-labs/sdk`, `@drift-labs/vaults`.
- These SDKs were required in order to interface with the Drift programs
- These SDKs also provided utility functions and constants that made
integration a whole lot easier

## Transaction executed by agent and prompts used
<!-- If applicable, provide example usage, transactions, or screenshots
-->
Example transaction: 
<img width="771" alt="Screenshot 2025-01-14 at 21 20 42"
src="https://github.com/user-attachments/assets/da4639c6-ffd7-461e-bb36-a31435dbbb52"
/>
<img width="771" alt="Screenshot 2025-01-14 at 21 21 56"
src="https://github.com/user-attachments/assets/90652b78-9c6e-4e16-8cd0-13d276ac5747"
/>
<img width="771" alt="Screenshot 2025-01-14 at 21 23 28"
src="https://github.com/user-attachments/assets/40a63cd5-5a7f-491b-80c0-905335713bc8"
/>
<img width="771" alt="Screenshot 2025-01-14 at 21 25 40"
src="https://github.com/user-attachments/assets/ec380ded-486d-4403-88c4-207948f3dbc3"
/>
<img width="771" alt="Screenshot 2025-01-14 at 21 27 30"
src="https://github.com/user-attachments/assets/0a4d80a3-f352-497e-9030-462856b9a783"
/>

**Transaction Links**:
-
https://solscan.io/tx/4BondSu3JArkMiHYwFbaExweu3DMcBKiks4uP7oKdmj7Tb3NzdvX5foPdFhbXiSgfmG3t8MtHchkPHshN1d1i5qx
-
https://solscan.io/tx/3qEr1hPCmcmn4nmf31RJAP7ZjkWLm5ehmYfxEUvK8S1jLAUAiVwy35P7oVv5xTEoPr168oRCXZP2zcaq6K6V2bdD

-https://solscan.io/tx/22zDZ8CUn11yRWR8Guj5p7X2r5XSUyFo12FxHReAdauuzwaM9Pg3VsP5uJf8LopzAm4yYtgBMzd59m96KtZf8TPf

## Prompt Used
<!-- If relevant, include the prompt or configuration used -->
```
Create a drift account and deposit 2 usdc into my drift account
```

## Additional Notes
<!-- Any additional information that reviewers should know -->

## Checklist
- [x] I have tested these changes locally
- [x] I have updated the documentation
- [x] I have added a transaction link
- [x] I have added the prompt used to test it
This commit is contained in:
aryan
2025-01-15 02:44:45 +05:30
committed by GitHub
27 changed files with 4680 additions and 476 deletions

View File

@@ -13,6 +13,7 @@
"no-constant-condition": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-object-type": "off",
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
"no-console": ["warn", { "allow": ["warn", "error"] }],
"curly": ["error", "all"],
@@ -30,4 +31,4 @@
"ecmaVersion": 2020,
"sourceType": "module"
}
}
}

166
README.md
View File

@@ -56,6 +56,7 @@ Anyone - whether an SF-based AI researcher or a crypto-native builder - can brin
- Pyth Price feeds for fetching Asset Prices
- Register/resolve Alldomains
- Perpetuals Trading with Adrena Protocol
- Drift Vaults, Perps, Lending and Borrowing
- **Solana Blinks**
- Lending by Lulo (Best APR for USDC)
@@ -309,6 +310,167 @@ const signature = await agent.closePerpTradeLong({
const { signature } = await agent.closeEmptyTokenAccounts();
```
### Create a Drift account
Create a drift account with an initial token deposit.
```typescript
const result = await agent.createDriftUserAccount()
```
### Create a Drift Vault
Create a drift vault.
```typescript
const signature = await agent.createDriftVault({
name: "my-drift-vault",
marketName: "USDC-SPOT",
redeemPeriod: 1, // in days
maxTokens: 100000, // in token units e.g 100000 USDC
minDepositAmount: 5, // in token units e.g 5 USDC
managementFee: 1, // 1%
profitShare: 10, // 10%
hurdleRate: 5, // 5%
permissioned: false, // public vault or whitelist
})
```
### Deposit into a Drift Vault
Deposit tokens into a drift vault.
```typescript
const signature = await agent.depositIntoDriftVault(100, "41Y8C4oxk4zgJT1KXyQr35UhZcfsp5mP86Z2G7UUzojU")
```
### Deposit into your Drift account
Deposit tokens into your drift account.
```typescript
const {txSig} = await agent.depositToDriftUserAccount(100, "USDC")
```
### Derive a Drift Vault address
Derive a drift vault address.
```typescript
const vaultPublicKey = await agent.deriveDriftVaultAddress("my-drift-vault")
```
### Do you have a Drift account
Check if agent has a drift account.
```typescript
const {hasAccount, account} = await agent.doesUserHaveDriftAccount()
```
### Get Drift account information
Get drift account information.
```typescript
const accountInfo = await agent.driftUserAccountInfo()
```
### Request withdrawal from Drift vault
Request withdrawal from drift vault.
```typescript
const signature = await agent.requestWithdrawalFromDriftVault(100, "41Y8C4oxk4zgJT1KXyQr35UhZcfsp5mP86Z2G7UUzojU")
```
### Carry out a perpetual trade using a Drift vault
Open a perpertual trade using a drift vault that is delegated to you.
```typescript
const signature = await agent.tradeUsingDelegatedDriftVault({
vault: "41Y8C4oxk4zgJT1KXyQr35UhZcfsp5mP86Z2G7UUzojU",
amount: 500,
symbol: "SOL",
action: "long",
type: "limit",
price: 180 // Please long limit order at $180/SOL
})
```
### Carry out a perpetual trade using your Drift account
Open a perpertual trade using your drift account.
```typescript
const signature = await agent.tradeUsingDriftPerpAccount({
amount: 500,
symbol: "SOL",
action: "long",
type: "limit",
price: 180 // Please long limit order at $180/SOL
})
```
### Update Drift vault parameters
Update drift vault parameters.
```typescript
const signature = await agent.updateDriftVault({
name: "my-drift-vault",
marketName: "USDC-SPOT",
redeemPeriod: 1, // in days
maxTokens: 100000, // in token units e.g 100000 USDC
minDepositAmount: 5, // in token units e.g 5 USDC
managementFee: 1, // 1%
profitShare: 10, // 10%
hurdleRate: 5, // 5%
permissioned: false, // public vault or whitelist
})
```
### Withdraw from Drift account
Withdraw tokens from your drift account.
```typescript
const {txSig} = await agent.withdrawFromDriftAccount(100, "USDC")
```
### Borrow from Drift
Borrow tokens from drift.
```typescript
const {txSig} = await agent.withdrawFromDriftAccount(1, "SOL", true)
```
### Repay Drift loan
Repay a loan from drift.
```typescript
const {txSig} = await agent.depositToDriftUserAccount(1, "SOL", true)
```
### Withdraw from Drift vault
Withdraw tokens from a drift vault after the redemption period has elapsed.
```typescript
const signature = await agent.withdrawFromDriftVault( "41Y8C4oxk4zgJT1KXyQr35UhZcfsp5mP86Z2G7UUzojU")
```
### Update the address a Drift vault is delegated to
Update the address a drift vault is delegated to.
```typescript
const signature = await agent.updateDriftVaultDelegate("41Y8C4oxk4zgJT1KXyQr35UhZcfsp5mP86Z2G7UUzojU", "new-address")
```
## Examples
### LangGraph Multi-Agent System
@@ -357,7 +519,7 @@ Refer to [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines on how to co
Apache-2 License
## Funding
## Funding
If you wanna give back any tokens or donations to the OSS community -- The Public Solana Agent Kit Treasury Address:
@@ -365,4 +527,4 @@ Solana Network : EKHTbXpsm6YDgJzMkFxNU1LNXeWcUW7Ezf8mjUNQQ4Pa
## Security
This toolkit handles private keys and transactions. Always ensure you're using it in a secure environment and never share your private keys.
This toolkit handles private keys and transactions. Always ensure you're using it in a secure environment and never share your private keys.

View File

@@ -1,123 +0,0 @@
module.exports = {
env: {
es2021: true,
node: true,
},
root: true,
settings: {
'import/resolver': {
typescript: {},
},
},
ignorePatterns: [
'.eslintrc.js',
'webpack.config.js',
'dist/*',
'**/*.js',
'node_modules/*',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 12,
project: 'tsconfig.json',
tsconfigRootDir: '.',
sourceType: 'module',
},
extends: [
'airbnb-base',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:prettier/recommended',
'plugin:sonarjs/recommended',
'plugin:security/recommended',
'plugin:promise/recommended',
'prettier',
],
plugins: [
'@typescript-eslint/eslint-plugin',
'sonarjs',
'security',
'promise',
'prettier',
],
rules: {
semi: [2, 'always'],
quotes: [1, 'single', { allowTemplateLiterals: true }],
curly: [2, 'all'],
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-misused-promises': [
'error',
{ checksVoidReturn: false },
],
'security/detect-non-literal-regexp': 0,
'security/detect-object-injection': 0,
'promise/always-return': 0,
'promise/no-callback-in-promise': 0,
'sonarjs/cognitive-complexity': [2, 50],
'sonarjs/no-duplicate-string': 0,
'sonarjs/no-useless-catch': 1,
'sonarjs/no-nested-template-literals': 0,
'sonarjs/prefer-single-boolean-return': 1,
'sonarjs/no-small-switch': 'off',
'@typescript-eslint/no-unused-vars': [
1,
{ argsIgnorePattern: '^_|^returns$|^of$|^type$' },
],
'import/extensions': 'off',
'import/no-import-module-exports': 'off',
'import/prefer-default-export': 'off',
'import/no-extraneous-dependencies': 'off',
'import/no-dynamic-require': 'off',
'prettier/prettier': [
'error',
{
useTabs: false,
arrowParens: 'always',
printWidth: 80,
singleQuote: true,
trailingComma: 'all',
endOfLine: 'auto',
bracketSpacing: true,
},
{
usePrettierrc: false,
},
],
'no-restricted-imports': [
'error',
{
patterns: ['**/dist/**'],
},
],
'no-use-before-define': 'off',
'no-console': 'off',
'no-return-await': 'off',
'consistent-return': 'off',
'default-case': 'off',
'no-fallthrough': 'off',
'no-plusplus': 'off',
'no-await-in-loop': 'off',
'no-restricted-syntax': 'off',
'no-continue': 'off',
'no-nested-ternary': 'off',
'no-void': 'off',
'no-param-reassign': 'off',
'class-methods-use-this': 'off',
'no-return-assign': 'off',
'no-case-declarations': 'off',
'global-require': 'off',
'security/detect-non-literal-require': 'off',
'global-require': 'off',
},
};

View File

@@ -28,6 +28,8 @@
"@bonfida/spl-name-service": "^3.0.7",
"@cks-systems/manifest-sdk": "0.1.59",
"@coral-xyz/anchor": "0.29",
"@drift-labs/sdk": "2.107.0-beta.3",
"@drift-labs/vaults-sdk": "^0.2.49",
"@langchain/core": "^0.3.26",
"@langchain/groq": "^0.1.2",
"@langchain/langgraph": "^0.2.36",
@@ -77,5 +79,6 @@
"prettier": "^3.4.2",
"tsx": "^4.19.2",
"typescript": "^5.7.2"
}
},
"packageManager": "pnpm@9.15.3"
}

2595
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
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"),
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,108 @@
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",
),
minDepositAmount: z.number().positive().describe("Minimum deposit amount"),
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,56 @@
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"),
}),
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",
),
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,57 @@
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"),
}),
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,114 @@
import { z } from "zod";
import type { Action } from "../../types";
import type { SolanaAgentKit } from "../../agent";
import { tradeDriftVault } from "../../tools";
const tradeDelegatedDriftVaultAction: Action = {
name: "TRADE_DELEGATED_DRIFT_VAULT",
similes: [
"trade delegated drift vault",
"trade delegated vault",
"trade vault",
"trade drift vault",
"trade delegated vault",
"trade vault",
"trade drift vault",
"open drift vault trade",
],
description: "Carry out trades in a Drift vault.",
examples: [
[
{
input: {
vaultAddress: "J1S9H3QjnRtBbbuD4HjPV6RpRhwuk4zKbxsnCHuTgh9w",
amount: 100,
symbol: "SOL",
action: "buy",
type: "market",
},
output: {
status: "success",
message: "Trade successful",
transactionId: "7nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkN",
amount: 100,
symbol: "SOL",
action: "buy",
type: "market",
},
explanation: "Buy 100 SOL in the vault",
},
],
[
{
input: {
vaultAddress: "J1S9H3QjnRtBbbuD4HjPV6RpRhwuk4zKbxsnCHuTgh9w",
amount: 50,
symbol: "SOL",
action: "sell",
type: "limit",
price: 200,
},
output: {
status: "success",
message: "Order placed successful",
transactionId: "8nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkM",
amount: 50,
symbol: "SOL",
action: "sell",
type: "limit",
price: 200,
},
explanation: "Sell 50 SOL in the vault at $200",
},
],
],
schema: z.object({
vaultAddress: z.string().describe("Address of the Drift vault to trade in"),
amount: z.number().positive().describe("Amount to trade"),
symbol: z.string().describe("Symbol of the token to trade"),
action: z.enum(["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"),
}),
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,82 @@
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(),
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(),
}),
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,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(),
newDelegate: z.string(),
}),
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,87 @@
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")
.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(),
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",
),
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(),
}),
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

@@ -43,6 +43,21 @@ 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";
export const ACTIONS = {
WALLET_ADDRESS_ACTION: getWalletAddressAction,
@@ -91,6 +106,21 @@ 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,
};
export type { Action, ActionExample, Handler } from "../types/action";

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

@@ -82,6 +82,21 @@ import {
getHeliusWebhook,
create_HeliusWebhook,
deleteHeliusWebhook,
createDriftUserAccount,
createVault,
depositIntoVault,
depositToDriftUserAccount,
getVaultAddress,
doesUserHaveDriftAccount,
driftUserAccountInfo,
requestWithdrawalFromVault,
tradeDriftVault,
driftPerpTrade,
updateVault,
getVaultInfo,
withdrawFromDriftUserAccount,
withdrawFromDriftVault,
updateVaultDelegate,
} from "../tools";
import {
Config,
@@ -694,4 +709,102 @@ 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);
}
}

460
src/tools/drift/drift.ts Normal file
View File

@@ -0,0 +1,460 @@
import {
BASE_PRECISION,
convertToNumber,
DRIFT_PROGRAM_ID,
DriftClient,
FastSingleTxSender,
getLimitOrderParams,
getMarketOrderParams,
getUserAccountPublicKeySync,
MainnetSpotMarkets,
numberToSafeBN,
PositionDirection,
PostOnlyParams,
PRICE_PRECISION,
QUOTE_PRECISION,
User,
type IWallet,
} from "@drift-labs/sdk";
import type { SolanaAgentKit } from "../../agent";
import * as anchor from "@coral-xyz/anchor";
import { IDL, VAULT_PROGRAM_ID, VaultClient } from "@drift-labs/vaults-sdk";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";
import { Transaction } from "@solana/web3.js";
import { ComputeBudgetProgram } from "@solana/web3.js";
export async function initClients(
agent: SolanaAgentKit,
params?: {
authority: PublicKey;
activeSubAccountId: number;
subAccountIds: number[];
},
) {
const wallet: IWallet = {
publicKey: agent.wallet.publicKey,
payer: agent.wallet,
signAllTransactions: async (txs) => {
for (const tx of txs) {
tx.sign(agent.wallet);
}
return txs;
},
signTransaction: async (tx) => {
tx.sign(agent.wallet);
return tx;
},
};
// @ts-expect-error - false undefined type conflict
const driftClient = new DriftClient({
connection: agent.connection,
wallet,
env: "mainnet-beta",
authority: params?.authority,
activeSubAccountId: params?.activeSubAccountId,
subAccountIds: params?.subAccountIds,
txParams: {
computeUnitsPrice: 0.000001 * 1000000 * 1000000,
},
txSender: new FastSingleTxSender({
connection: agent.connection,
wallet,
timeout: 30000,
blockhashRefreshInterval: 1000,
opts: {
commitment: agent.connection.commitment ?? "confirmed",
skipPreflight: false,
preflightCommitment: agent.connection.commitment ?? "confirmed",
},
}),
});
const vaultProgram = new anchor.Program(
IDL,
VAULT_PROGRAM_ID,
driftClient.provider,
);
const vaultClient = new VaultClient({
driftClient,
// @ts-expect-error - type mismatch due to different dep versions
program: vaultProgram,
cliMode: false,
});
await driftClient.subscribe();
async function cleanUp() {
await driftClient.unsubscribe();
}
return { driftClient, vaultClient, cleanUp };
}
/**
* Create a drift user account provided an amount
* @param amount amount of the token to deposit
* @param symbol symbol of the token to deposit
*/
export async function createDriftUserAccount(
agent: SolanaAgentKit,
amount: number,
symbol: string,
) {
try {
const { driftClient, cleanUp } = await initClients(agent);
const user = new User({
driftClient,
userAccountPublicKey: getUserAccountPublicKeySync(
new PublicKey(DRIFT_PROGRAM_ID),
agent.wallet.publicKey,
),
});
const userAccountExists = await user.exists();
const token = MainnetSpotMarkets.find(
(v) => v.symbol === symbol.toUpperCase(),
);
if (!token) {
throw new Error(`Token with symbol ${symbol} not found`);
}
if (!userAccountExists) {
const depositAmount = numberToSafeBN(amount, token.precision);
const [txSignature, account] =
await driftClient.initializeUserAccountAndDepositCollateral(
depositAmount,
getAssociatedTokenAddressSync(token.mint, agent.wallet.publicKey),
);
await cleanUp();
return { txSignature, account };
}
await cleanUp();
return {
message: "User account already exists",
account: user.userAccountPublicKey,
};
} catch (e) {
// @ts-expect-error - error message is a string
throw new Error(`Failed to create user account: ${e.message}`);
}
}
/**
* Deposit to your drift user account
* @param agent
* @param amount
* @param symbol
* @param isRepay
* @returns
*/
export async function depositToDriftUserAccount(
agent: SolanaAgentKit,
amount: number,
symbol: string,
isRepay = false,
) {
try {
const { driftClient, cleanUp } = await initClients(agent);
const publicKey = agent.wallet.publicKey;
const user = new User({
driftClient,
userAccountPublicKey: getUserAccountPublicKeySync(
new PublicKey(DRIFT_PROGRAM_ID),
publicKey,
),
});
const userAccountExists = await user.exists();
const token = MainnetSpotMarkets.find(
(v) => v.symbol === symbol.toUpperCase(),
);
if (!token) {
throw new Error(`Token with symbol ${symbol} not found`);
}
if (!userAccountExists) {
throw new Error("You need to create a Drift user account first.");
}
const depositAmount = numberToSafeBN(amount, token.precision);
const [depInstruction, latestBlockhash] = await Promise.all([
driftClient.getDepositTxnIx(
depositAmount,
token.marketIndex,
getAssociatedTokenAddressSync(token.mint, publicKey),
undefined,
isRepay,
),
driftClient.connection.getLatestBlockhash(),
]);
const tx = new Transaction().add(...depInstruction).add(
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: 0.000001 * 1000000 * 1000000,
}),
);
tx.recentBlockhash = latestBlockhash.blockhash;
tx.sign(agent.wallet);
const txSignature = await driftClient.txSender.sendRawTransaction(
tx.serialize(),
{ ...driftClient.opts },
);
await cleanUp();
return txSignature;
} catch (e) {
// @ts-expect-error - error message is a string
throw new Error(`Failed to deposit to user account: ${e.message}`);
}
}
export async function withdrawFromDriftUserAccount(
agent: SolanaAgentKit,
amount: number,
symbol: string,
isBorrow = false,
) {
try {
const { driftClient, cleanUp } = await initClients(agent);
const user = new User({
driftClient,
userAccountPublicKey: getUserAccountPublicKeySync(
new PublicKey(DRIFT_PROGRAM_ID),
agent.wallet.publicKey,
),
});
const userAccountExists = await user.exists();
if (!userAccountExists) {
throw new Error("You need to create a Drift user account first.");
}
const token = MainnetSpotMarkets.find(
(v) => v.symbol === symbol.toUpperCase(),
);
if (!token) {
throw new Error(`Token with symbol ${symbol} not found`);
}
const withdrawAmount = numberToSafeBN(amount, token.precision);
const [withdrawInstruction, latestBlockhash] = await Promise.all([
driftClient.getWithdrawalIxs(
withdrawAmount,
token.marketIndex,
getAssociatedTokenAddressSync(token.mint, agent.wallet.publicKey),
!isBorrow,
),
driftClient.connection.getLatestBlockhash(),
]);
const tx = new Transaction().add(...withdrawInstruction).add(
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: 0.000001 * 1000000 * 1000000,
}),
);
tx.recentBlockhash = latestBlockhash.blockhash;
tx.sign(agent.wallet);
const txSignature = await driftClient.txSender.sendRawTransaction(
tx.serialize(),
{ ...driftClient.opts },
);
await cleanUp();
return txSignature;
} catch (e) {
// @ts-expect-error - error message is a string
throw new Error(`Failed to withdraw from user account: ${e.message}`);
}
}
/**
* Open a perpetual trade on drift
* @param agent
* @param params.amount
* @param params.symbol
* @param params.action
* @param params.type
* @param params.price this should only be supplied if type is limit
* @param params.reduceOnly
*/
export async function driftPerpTrade(
agent: SolanaAgentKit,
params: {
amount: number;
symbol: string;
action: "long" | "short";
type: "market" | "limit";
price?: number | undefined;
},
) {
try {
const { driftClient, cleanUp } = await initClients(agent);
const user = new User({
driftClient,
userAccountPublicKey: getUserAccountPublicKeySync(
new PublicKey(DRIFT_PROGRAM_ID),
agent.wallet.publicKey,
),
});
const userAccountExists = await user.exists();
if (!userAccountExists) {
throw new Error("You need to create a Drift user account first.");
}
const market = driftClient.getMarketIndexAndType(
`${params.symbol.toUpperCase()}-PERP`,
);
if (!market) {
throw new Error(`Token with symbol ${params.symbol} not found`);
}
const baseAssetPrice = driftClient.getOracleDataForPerpMarket(
market.marketIndex,
);
const convertedAmount =
params.amount / convertToNumber(baseAssetPrice.price, PRICE_PRECISION);
let signature: anchor.web3.TransactionSignature;
if (params.type === "limit") {
if (!params.price) {
throw new Error("Price is required for limit orders");
}
signature = await driftClient.placePerpOrder(
getLimitOrderParams({
baseAssetAmount: numberToSafeBN(convertedAmount, BASE_PRECISION),
reduceOnly: false,
direction:
params.action === "long"
? PositionDirection.LONG
: PositionDirection.SHORT,
marketIndex: market.marketIndex,
price: numberToSafeBN(params.price, PRICE_PRECISION),
postOnly: PostOnlyParams.SLIDE,
}),
{
computeUnitsPrice: 0.000001 * 1000000 * 1000000,
},
);
} else {
signature = await driftClient.placePerpOrder(
getMarketOrderParams({
baseAssetAmount: numberToSafeBN(convertedAmount, BASE_PRECISION),
reduceOnly: false,
direction:
params.action === "long"
? PositionDirection.LONG
: PositionDirection.SHORT,
marketIndex: market.marketIndex,
}),
{
computeUnitsPrice: 0.000001 * 1000000 * 1000000,
},
);
}
if (!signature) {
throw new Error("Failed to place order. Please make sure ");
}
await cleanUp();
return signature;
} catch (e) {
// @ts-expect-error - error message is a string
throw new Error(`Failed to place order: ${e.message}`);
}
}
/**
* Check if a user has a drift account
* @param agent
*/
export async function doesUserHaveDriftAccount(agent: SolanaAgentKit) {
try {
const { driftClient, cleanUp } = await initClients(agent);
const user = new User({
driftClient,
userAccountPublicKey: getUserAccountPublicKeySync(
new PublicKey(DRIFT_PROGRAM_ID),
agent.wallet.publicKey,
),
});
user.getActivePerpPositions();
const userAccountExists = await user.exists();
await cleanUp();
return {
hasAccount: userAccountExists,
account: user.userAccountPublicKey,
};
} catch (e) {
// @ts-expect-error - error message is a string
throw new Error(`Failed to check user account: ${e.message}`);
}
}
/**
* Get account info for a drift User
* @param agent
* @returns
*/
export async function driftUserAccountInfo(agent: SolanaAgentKit) {
try {
const { driftClient, cleanUp } = await initClients(agent);
const user = new User({
driftClient,
userAccountPublicKey: getUserAccountPublicKeySync(
new PublicKey(DRIFT_PROGRAM_ID),
agent.wallet.publicKey,
),
});
const userAccountExists = await user.exists();
if (!userAccountExists) {
throw new Error("User account does not exist");
}
await user.subscribe();
const account = user.getUserAccount();
await user.unsubscribe();
await cleanUp();
const perpPositions = account.perpPositions.map((pos) => ({
...pos,
baseAssetAmount: convertToNumber(pos.baseAssetAmount, BASE_PRECISION),
settledPnl: convertToNumber(pos.settledPnl, QUOTE_PRECISION),
}));
const spotPositions = account.spotPositions.map((pos) => ({
...pos,
scaledBalance: convertToNumber(pos.scaledBalance, BASE_PRECISION),
cumulativeDeposits: convertToNumber(
pos.cumulativeDeposits,
BASE_PRECISION,
),
symbol: MainnetSpotMarkets.find((v) => v.marketIndex === pos.marketIndex)
?.symbol,
}));
return {
...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,
spotPositions,
};
} catch (e) {
// @ts-expect-error - error message is a string
throw new Error(`Failed to check user account: ${e.message}`);
}
}

View File

@@ -0,0 +1,643 @@
import {
BASE_PRECISION,
convertToNumber,
getLimitOrderParams,
getMarketOrderParams,
getOrderParams,
MainnetPerpMarkets,
MainnetSpotMarkets,
MarketType,
numberToSafeBN,
PERCENTAGE_PRECISION,
PositionDirection,
PostOnlyParams,
PRICE_PRECISION,
QUOTE_PRECISION,
TEN,
User,
} from "@drift-labs/sdk";
import {
VaultAccount,
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");
}
return { marketIndex: token.marketIndex, marketType: MarketType.PERP };
}
const token = MainnetSpotMarkets.find((v) => v.symbol === symbol);
if (!token) {
throw new Error("Drift doesn't have that market");
}
return { marketIndex: token.marketIndex, marketType: MarketType.SPOT };
}
async function 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);
if (!marketIndexAndType) {
throw new Error("Invalid market name");
}
const spotMarket = driftClient.getSpotMarketAccount(
marketIndexAndType.marketIndex,
);
if (!spotMarket) {
throw new Error("Market not found");
}
const spotPrecision = TEN.pow(new BN(spotMarket.decimals));
if (marketIndexAndType.marketType === MarketType.PERP) {
throw new Error("Only SPOT market names are supported");
}
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");
}
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");
}
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 by someone else, so 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";

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";