mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-24 07:36:45 +00:00
Merge branch 'main' into deposit_and_withdraw_with_lulo
This commit is contained in:
@@ -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
460
src/tools/drift/drift.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
641
src/tools/drift/drift_vault.ts
Normal file
641
src/tools/drift/drift_vault.ts
Normal 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
2
src/tools/drift/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./drift";
|
||||
export * from "./drift_vault";
|
||||
57
src/tools/helius/get_assets_by_owner.ts
Normal file
57
src/tools/helius/get_assets_by_owner.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
44
src/tools/helius/helius_transaction_parsing.ts
Normal file
44
src/tools/helius/helius_transaction_parsing.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
132
src/tools/helius/helius_webhooks.ts
Normal file
132
src/tools/helius/helius_webhooks.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
4
src/tools/helius/index.ts
Normal file
4
src/tools/helius/index.ts
Normal 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";
|
||||
174
src/tools/helius/send_transaction_with_priority.ts
Normal file
174
src/tools/helius/send_transaction_with_priority.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
59
src/tools/solana/get_token_balances.ts
Normal file
59
src/tools/solana/get_token_balances.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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> {
|
||||
@@ -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> {
|
||||
@@ -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,
|
||||
@@ -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> {
|
||||
@@ -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";
|
||||
@@ -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> {
|
||||
@@ -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
3
src/tools/voltr/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./voltr_deposit_strategy";
|
||||
export * from "./voltr_withdraw_strategy";
|
||||
export * from "./voltr_get_position_values";
|
||||
99
src/tools/voltr/voltr_deposit_strategy.ts
Normal file
99
src/tools/voltr/voltr_deposit_strategy.ts
Normal 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;
|
||||
}
|
||||
20
src/tools/voltr/voltr_get_position_values.ts
Normal file
20
src/tools/voltr/voltr_get_position_values.ts
Normal 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);
|
||||
}
|
||||
99
src/tools/voltr/voltr_withdraw_strategy.ts
Normal file
99
src/tools/voltr/voltr_withdraw_strategy.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user