Merge branch 'main' into deposit_and_withdraw_with_lulo

This commit is contained in:
aryan
2025-01-18 01:19:10 +05:30
committed by GitHub
155 changed files with 17138 additions and 627 deletions

View File

@@ -37,7 +37,8 @@ export async function createSingle(
optionsWithBase58: StoreInitOptions,
collectionAccount: string,
createItemOptions: CreateSingleOptions,
isMainnet: boolean,
isMainnet: boolean = false,
withPool: boolean = false,
) {
try {
const landStore = isMainnet
@@ -49,21 +50,11 @@ export async function createSingle(
landStore,
collectionAccount,
createItemOptions,
true, //isAI
withPool,
);
return singleEditionTx;
} catch (error: any) {
throw new Error(`Single edition creation failed: ${error.message}`);
}
}
/**
* Buy a single edition on 3Land
* @param
* @returns
*/
// export async function buySingle() {
// try {
// } catch (error: any) {
// throw new Error(`Buying single edition failed: ${error.message}`);
// }
// }

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,641 @@
import {
BASE_PRECISION,
convertToNumber,
getLimitOrderParams,
getMarketOrderParams,
getOrderParams,
MainnetPerpMarkets,
MainnetSpotMarkets,
MarketType,
numberToSafeBN,
PERCENTAGE_PRECISION,
PositionDirection,
PostOnlyParams,
PRICE_PRECISION,
QUOTE_PRECISION,
TEN,
} from "@drift-labs/sdk";
import {
WithdrawUnit,
decodeName,
encodeName,
getVaultAddressSync,
getVaultDepositorAddressSync,
} from "@drift-labs/vaults-sdk";
import {
ComputeBudgetProgram,
PublicKey,
type TransactionInstruction,
} from "@solana/web3.js";
import type { SolanaAgentKit } from "../../agent";
import { BN } from "bn.js";
import { initClients } from "./drift";
export function getMarketIndexAndType(name: `${string}-${string}`) {
const [symbol, type] = name.toUpperCase().split("-");
if (type === "PERP") {
const token = MainnetPerpMarkets.find((v) => v.baseAssetSymbol === symbol);
if (!token) {
throw new Error("Drift doesn't have that market");
}
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

@@ -0,0 +1,57 @@
import { SolanaAgentKit } from "../../index";
import { PublicKey } from "@solana/web3.js";
/**
* Fetch assets by owner using the Helius Digital Asset Standard (DAS) API
* @param agent SolanaAgentKit instance
* @param ownerPublicKey Owner's Solana wallet PublicKey
* @param limit Number of assets to retrieve per request
* @returns Assets owned by the specified address
*/
export async function getAssetsByOwner(
agent: SolanaAgentKit,
ownerPublicKey: PublicKey,
limit: number,
): Promise<any> {
try {
const apiKey = agent.config.HELIUS_API_KEY;
if (!apiKey) {
throw new Error("HELIUS_API_KEY not found in environment variables");
}
const url = `https://mainnet.helius-rpc.com/?api-key=${apiKey}`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
jsonrpc: "2.0",
id: "get-assets",
method: "getAssetsByOwner",
params: {
ownerAddress: ownerPublicKey.toString(),
page: 3,
limit: limit,
displayOptions: {
showFungible: true,
},
},
}),
});
if (!response.ok) {
throw new Error(
`Failed to fetch: ${response.status} - ${response.statusText}`,
);
}
const data = await response.json();
return data.result.items;
} catch (error: any) {
console.error("Error retrieving assets: ", error.message);
throw new Error(`Assets retrieval failed: ${error.message}`);
}
}

View File

@@ -0,0 +1,44 @@
import { SolanaAgentKit } from "../../index";
/**
* Parse a Solana transaction using the Helius Enhanced Transactions API
* @param agent SolanaAgentKit instance
* @param transactionId The transaction ID to parse
* @returns Parsed transaction data
*/
export async function parseTransaction(
agent: SolanaAgentKit,
transactionId: string,
): Promise<any> {
try {
const apiKey = agent.config.HELIUS_API_KEY;
if (!apiKey) {
throw new Error("HELIUS_API_KEY not found in environment variables");
}
const url = `https://api.helius.xyz/v0/transactions/?api-key=${apiKey}`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
transactions: [transactionId],
}),
});
if (!response.ok) {
throw new Error(
`Failed to fetch: ${response.status} - ${response.statusText}`,
);
}
const data = await response.json();
return data;
} catch (error: any) {
console.error("Error parsing transaction: ", error.message);
throw new Error(`Transaction parsing failed: ${error.message}`);
}
}

View File

@@ -0,0 +1,132 @@
import { SolanaAgentKit } from "../../index";
import { HeliusWebhookResponse, HeliusWebhookIdResponse } from "../../index";
export async function create_HeliusWebhook(
agent: SolanaAgentKit,
accountAddresses: string[],
webhookURL: string,
): Promise<HeliusWebhookResponse> {
try {
const response = await fetch(
`https://api.helius.xyz/v0/webhooks?api-key=${agent.config.HELIUS_API_KEY}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
webhookURL,
transactionTypes: ["Any"],
accountAddresses,
webhookType: "enhanced",
txnStatus: "all",
}),
},
);
const data = await response.json();
return {
webhookURL: data.webhookURL,
webhookID: data.webhookID,
};
} catch (error: any) {
throw new Error(`Failed to create Webhook: ${error.message}`);
}
}
/**
* Retrieves a Helius Webhook by ID, returning only the specified fields.
*
* @param agent - An instance of SolanaAgentKit (with .config.HELIUS_API_KEY)
* @param webhookID - The unique ID of the webhook to retrieve
*
* @returns A HeliusWebhook object containing { wallet, webhookURL, transactionTypes, accountAddresses, webhookType }
*/
export async function getHeliusWebhook(
agent: SolanaAgentKit,
webhookID: string,
): Promise<HeliusWebhookIdResponse> {
try {
const apiKey = agent.config.HELIUS_API_KEY;
if (!apiKey) {
throw new Error("HELIUS_API_KEY is missing in agent.config");
}
const response = await fetch(
`https://api.helius.xyz/v0/webhooks/${webhookID}?api-key=${apiKey}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
);
if (!response.ok) {
throw new Error(
`Failed to fetch webhook with ID ${webhookID}. ` +
`Status Code: ${response.status}`,
);
}
const data = await response.json();
return {
wallet: data.wallet,
webhookURL: data.webhookURL,
transactionTypes: data.transactionTypes,
accountAddresses: data.accountAddresses,
webhookType: data.webhookType,
};
} catch (error: any) {
throw new Error(`Failed to get webhook by ID: ${error.message}`);
}
}
/**
* Deletes a Helius Webhook by its ID.
*
* @param agent - An instance of SolanaAgentKit (with .config.HELIUS_API_KEY)
* @param webhookID - The unique ID of the webhook to delete
*
* @returns The response body from the Helius API (which may contain status or other info)
*/
export async function deleteHeliusWebhook(
agent: SolanaAgentKit,
webhookID: string,
): Promise<any> {
try {
const apiKey = agent.config.HELIUS_API_KEY;
if (!apiKey) {
throw new Error("Missing Helius API key in agent.config.HELIUS_API_KEY");
}
const url = `https://api.helius.xyz/v0/webhooks/${webhookID}?api-key=${apiKey}`;
const response = await fetch(url, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(
`Failed to delete webhook: ${response.status} ${response.statusText}`,
);
}
if (response.status === 204) {
return { message: "Webhook deleted successfully (no content returned)" };
}
const contentLength = response.headers.get("Content-Length");
if (contentLength === "0" || !contentLength) {
return { message: "Webhook deleted successfully (empty body)" };
}
// Otherwise, parse as JSON
const data = await response.json();
return data;
} catch (error: any) {
console.error("Error deleting Helius Webhook:", error.message);
throw new Error(`Failed to delete Helius Webhook: ${error.message}`);
}
}

View File

@@ -0,0 +1,4 @@
export * from "./get_assets_by_owner";
export * from "./helius_transaction_parsing";
export * from "./helius_webhooks";
export * from "./send_transaction_with_priority";

View File

@@ -0,0 +1,174 @@
import { SolanaAgentKit, PriorityFeeResponse } from "../../index";
import {
SystemProgram,
Transaction,
sendAndConfirmTransaction,
ComputeBudgetProgram,
PublicKey,
LAMPORTS_PER_SOL,
} from "@solana/web3.js";
import {
getAssociatedTokenAddress,
createTransferInstruction,
getMint,
createAssociatedTokenAccountInstruction,
} from "@solana/spl-token";
import bs58 from "bs58";
/**
* Sends a transaction with an estimated priority fee using the provided SolanaAgentKit.
*
* @param agent An instance of SolanaAgentKit containing connection, wallet, etc.
* @param priorityLevel The priority level (e.g., "Min", "Low", "Medium", "High", "VeryHigh", or "UnsafeMax").
* @param amount The amount of SOL to send (in SOL, not lamports).
* @param to The recipient's PublicKey.
* @returns The transaction signature (string) once confirmed along with the fee used.
*/
export async function sendTransactionWithPriorityFee(
agent: SolanaAgentKit,
priorityLevel: string,
amount: number,
to: PublicKey,
splmintAddress?: PublicKey,
): Promise<{ transactionId: string; fee: number }> {
try {
if (!splmintAddress) {
const transaction = new Transaction();
const { blockhash, lastValidBlockHeight } =
await agent.connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
transaction.lastValidBlockHeight = lastValidBlockHeight;
transaction.feePayer = agent.wallet_address;
const transferIx = SystemProgram.transfer({
fromPubkey: agent.wallet_address,
toPubkey: to,
lamports: amount * LAMPORTS_PER_SOL,
});
transaction.add(transferIx);
transaction.sign(agent.wallet);
const response = await fetch(
`https://mainnet.helius-rpc.com/?api-key=${agent.config.HELIUS_API_KEY}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: "1",
method: "getPriorityFeeEstimate",
params: [
{
transaction: bs58.encode(transaction.serialize()),
options: { priorityLevel: priorityLevel },
},
],
} as PriorityFeeResponse),
},
);
const data = await response.json();
if (data.error) {
throw new Error("Error fetching priority fee:");
}
const feeEstimate: number = data.result.priorityFeeEstimate;
// Set the priority fee if applicable
const computePriceIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: feeEstimate,
});
transaction.add(computePriceIx);
// Send the transaction and confirm
const txSignature = await sendAndConfirmTransaction(
agent.connection,
transaction,
[agent.wallet],
);
return {
transactionId: txSignature,
fee: feeEstimate,
};
} else {
const fromAta = await getAssociatedTokenAddress(
splmintAddress,
agent.wallet_address,
);
const toAta = await getAssociatedTokenAddress(splmintAddress, to);
const mintInfo = await getMint(agent.connection, splmintAddress);
const adjustedAmount = amount * Math.pow(10, mintInfo.decimals);
const transaction = new Transaction();
const { blockhash, lastValidBlockHeight } =
await agent.connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
transaction.lastValidBlockHeight = lastValidBlockHeight;
transaction.feePayer = agent.wallet_address;
const response = await fetch(
`https://mainnet.helius-rpc.com/?api-key=${agent.config.HELIUS_API_KEY}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: "1",
method: "getPriorityFeeEstimate",
params: [
{
transaction: bs58.encode(transaction.serialize()),
options: { priorityLevel: priorityLevel },
},
],
} as PriorityFeeResponse),
},
);
const data = await response.json();
if (data.error) {
throw new Error("Error fetching priority fee:");
}
const feeEstimate: number = data.result.priorityFeeEstimate;
transaction.add(
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: feeEstimate,
}),
);
transaction.add(
createAssociatedTokenAccountInstruction(
agent.wallet_address,
toAta,
to,
splmintAddress,
),
);
transaction.add(
createTransferInstruction(
fromAta,
toAta,
agent.wallet_address,
adjustedAmount,
),
);
const txSignature = await sendAndConfirmTransaction(
agent.connection,
transaction,
[agent.wallet],
);
return {
transactionId: txSignature,
fee: feeEstimate,
};
}
} catch (error: any) {
throw new Error(`Failed to process transaction: ${error.message}`);
}
}

View File

@@ -16,10 +16,13 @@ export * from "./pumpfun";
export * from "./pyth";
export * from "./raydium";
export * from "./rugcheck";
export * from "./drift";
export * from "./sendarcade";
export * from "./solayer";
export * from "./tensor";
export * from "./3land";
export * from "./tiplink";
export * from "./lightprotocol";
export * from "./squads_multisig";
export * from "./squads";
export * from "./helius";
export * from "./voltr";

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ const { Multisig } = multisig.accounts;
* @returns {Promise<string>} - A promise that resolves to the transaction ID of the approved proposal.
* @throws {Error} - Throws an error if the approval process fails.
*/
export async function approve_proposal(
export async function multisig_approve_proposal(
agent: SolanaAgentKit,
transactionIndex?: number | bigint,
): Promise<string> {

View File

@@ -10,7 +10,7 @@ const { Multisig } = multisig.accounts;
* @returns {Promise<string>} - The transaction ID of the created proposal.
* @throws {Error} - Throws an error if the proposal creation fails.
*/
export async function create_proposal(
export async function multisig_create_proposal(
agent: SolanaAgentKit,
transactionIndex?: number | bigint,
): Promise<string> {

View File

@@ -17,7 +17,7 @@ import * as multisig from "@sqds/multisig";
* @param mint Optional mint address for SPL tokens
* @returns Transaction signature
*/
export async function deposit_to_multisig(
export async function multisig_deposit_to_treasury(
agent: SolanaAgentKit,
amount: number,
vaultIndex?: number,

View File

@@ -10,7 +10,7 @@ const { Multisig } = multisig.accounts;
* @returns {Promise<string>} - A promise that resolves to the transaction signature string.
* @throws {Error} - Throws an error if the transaction execution fails.
*/
export async function execute_transaction(
export async function multisig_execute_proposal(
agent: SolanaAgentKit,
transactionIndex?: number | bigint,
): Promise<string> {

View File

@@ -1,7 +1,7 @@
export * from "./approve_proposal";
export * from "./create_multisig";
export * from "./create_proposal";
export * from "./deposit_to_multisig";
export * from "./approve_proposal";
export * from "./deposit_to_treasury";
export * from "./execute_proposal";
export * from "./reject_proposal";
export * from "./transfer_from_multisig";
export * from "./transfer_from_treasury";

View File

@@ -10,7 +10,7 @@ const { Multisig } = multisig.accounts;
* @returns A promise that resolves to the transaction ID of the rejection transaction.
* @throws Will throw an error if the transaction fails.
*/
export async function reject_proposal(
export async function multisig_reject_proposal(
agent: SolanaAgentKit,
transactionIndex?: number | bigint,
): Promise<string> {

View File

@@ -23,7 +23,7 @@ const { Multisig } = multisig.accounts;
* @param mint - Optional mint address for SPL tokens.
* @returns Transaction signature.
*/
export async function transfer_from_multisig(
export async function multisig_transfer_from_treasury(
agent: SolanaAgentKit,
amount: number,
to: PublicKey,

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

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

View File

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

View File

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

View File

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