mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-24 07:36:45 +00:00
chore: merge Voltr changes into remote branch
This commit is contained in:
57
src/tools/3land/create_3land_collectible.ts
Normal file
57
src/tools/3land/create_3land_collectible.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { createCollectionImp, createSingleImp } from "@3land/listings-sdk";
|
||||
import {
|
||||
StoreInitOptions,
|
||||
CreateCollectionOptions,
|
||||
CreateSingleOptions,
|
||||
} from "@3land/listings-sdk/dist/types/implementation/implementationTypes";
|
||||
|
||||
/**
|
||||
* Create a collection on 3Land
|
||||
* @param optionsWithBase58 represents the privateKey of the wallet - can be an array of numbers, Uint8Array or base58 string
|
||||
* @param collectionOpts represents the options for the collection creation
|
||||
* @returns
|
||||
*/
|
||||
export async function createCollection(
|
||||
optionsWithBase58: StoreInitOptions,
|
||||
collectionOpts: CreateCollectionOptions,
|
||||
) {
|
||||
try {
|
||||
const collection = await createCollectionImp(
|
||||
optionsWithBase58,
|
||||
collectionOpts,
|
||||
);
|
||||
return collection;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Collection creation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single edition on 3Land
|
||||
* @param optionsWithBase58 represents the privateKey of the wallet - can be an array of numbers, Uint8Array or base58 string
|
||||
* @param collectionAccount represents the account for the nft collection
|
||||
* @param createItemOptions the options for the creation of the single NFT listing
|
||||
* @returns
|
||||
*/
|
||||
export async function createSingle(
|
||||
optionsWithBase58: StoreInitOptions,
|
||||
collectionAccount: string,
|
||||
createItemOptions: CreateSingleOptions,
|
||||
isMainnet: boolean,
|
||||
) {
|
||||
try {
|
||||
const landStore = isMainnet
|
||||
? "AmQNs2kgw4LvS9sm6yE9JJ4Hs3JpVu65eyx9pxMG2xA"
|
||||
: "GyPCu89S63P9NcCQAtuSJesiefhhgpGWrNVJs4bF2cSK";
|
||||
|
||||
const singleEditionTx = await createSingleImp(
|
||||
optionsWithBase58,
|
||||
landStore,
|
||||
collectionAccount,
|
||||
createItemOptions,
|
||||
);
|
||||
return singleEditionTx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Single edition creation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
1
src/tools/3land/index.ts
Normal file
1
src/tools/3land/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./create_3land_collectible";
|
||||
506
src/tools/adrena/adrena_perp_trading.ts
Normal file
506
src/tools/adrena/adrena_perp_trading.ts
Normal file
@@ -0,0 +1,506 @@
|
||||
import {
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import { TOKENS, DEFAULT_OPTIONS } from "../../constants";
|
||||
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
|
||||
import AdrenaClient from "../../utils/AdrenaClient";
|
||||
import { sendTx } from "../../utils/send_tx";
|
||||
|
||||
const PRICE_DECIMALS = 10;
|
||||
const ADRENA_PROGRAM_ID = new PublicKey(
|
||||
"13gDzEXCdocbj8iAiqrScGo47NiSuYENGsRqi3SEAwet",
|
||||
);
|
||||
|
||||
// i.e percentage = -2 (for -2%)
|
||||
// i.e percentage = 5 (for 5%)
|
||||
function applySlippage(nb: BN, percentage: number): BN {
|
||||
const negative = percentage < 0 ? true : false;
|
||||
|
||||
// Do x10_000 so percentage can be up to 4 decimals
|
||||
const percentageBN = new BN(
|
||||
(negative ? percentage * -1 : percentage) * 10_000,
|
||||
);
|
||||
|
||||
const delta = nb.mul(percentageBN).divRound(new BN(10_000 * 100));
|
||||
|
||||
return negative ? nb.sub(delta) : nb.add(delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close short trade on Adrena
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function closePerpTradeShort({
|
||||
agent,
|
||||
price,
|
||||
tradeMint,
|
||||
}: {
|
||||
agent: SolanaAgentKit;
|
||||
price: number;
|
||||
tradeMint: PublicKey;
|
||||
}) {
|
||||
const client = await AdrenaClient.load(agent);
|
||||
|
||||
const owner = agent.wallet.publicKey;
|
||||
|
||||
const custody = client.getCustodyByMint(tradeMint);
|
||||
const collateralCustody = client.getCustodyByMint(TOKENS.USDC);
|
||||
|
||||
const stakingRewardTokenCustodyAccount = client.getCustodyByMint(
|
||||
AdrenaClient.stakingRewardTokenMint,
|
||||
);
|
||||
|
||||
const stakingRewardTokenCustodyTokenAccount =
|
||||
AdrenaClient.findCustodyTokenAccountAddress(
|
||||
AdrenaClient.stakingRewardTokenMint,
|
||||
);
|
||||
|
||||
const position = AdrenaClient.findPositionAddress(
|
||||
owner,
|
||||
custody.pubkey,
|
||||
"long",
|
||||
);
|
||||
|
||||
const userProfilePda = AdrenaClient.getUserProfilePda(owner);
|
||||
|
||||
const userProfile =
|
||||
await client.program.account.userProfile.fetchNullable(userProfilePda);
|
||||
|
||||
const receivingAccount = AdrenaClient.findATAAddressSync(
|
||||
owner,
|
||||
collateralCustody.mint,
|
||||
);
|
||||
|
||||
const preInstructions: TransactionInstruction[] = [];
|
||||
|
||||
const collateralCustodyOracle = collateralCustody.oracle;
|
||||
const collateralCustodyTokenAccount =
|
||||
AdrenaClient.findCustodyTokenAccountAddress(collateralCustody.mint);
|
||||
|
||||
if (
|
||||
!(await AdrenaClient.isAccountInitialized(
|
||||
agent.connection,
|
||||
receivingAccount,
|
||||
))
|
||||
) {
|
||||
preInstructions.push(
|
||||
AdrenaClient.createATAInstruction({
|
||||
ataAddress: receivingAccount,
|
||||
mint: collateralCustody.mint,
|
||||
owner,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const instruction = await client.program.methods
|
||||
.closePositionShort({
|
||||
price: new BN(price * 10 ** PRICE_DECIMALS),
|
||||
})
|
||||
.accountsStrict({
|
||||
owner,
|
||||
receivingAccount,
|
||||
transferAuthority: AdrenaClient.transferAuthority,
|
||||
pool: AdrenaClient.mainPool,
|
||||
position: position,
|
||||
custody: custody.pubkey,
|
||||
custodyTradeOracle: custody.tradeOracle,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
lmStaking: AdrenaClient.lmStaking,
|
||||
lpStaking: AdrenaClient.lpStaking,
|
||||
cortex: AdrenaClient.cortex,
|
||||
stakingRewardTokenCustody: stakingRewardTokenCustodyAccount.pubkey,
|
||||
stakingRewardTokenCustodyOracle: stakingRewardTokenCustodyAccount.oracle,
|
||||
stakingRewardTokenCustodyTokenAccount,
|
||||
lmStakingRewardTokenVault: AdrenaClient.lmStakingRewardTokenVault,
|
||||
lpStakingRewardTokenVault: AdrenaClient.lpStakingRewardTokenVault,
|
||||
lpTokenMint: AdrenaClient.lpTokenMint,
|
||||
protocolFeeRecipient: client.cortex.protocolFeeRecipient,
|
||||
adrenaProgram: AdrenaClient.programId,
|
||||
userProfile: userProfile ? userProfilePda : null,
|
||||
caller: owner,
|
||||
collateralCustody: collateralCustody.pubkey,
|
||||
collateralCustodyOracle,
|
||||
collateralCustodyTokenAccount,
|
||||
})
|
||||
.instruction();
|
||||
|
||||
return sendTx(agent, [...preInstructions, instruction]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close long trade on Adrena
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function closePerpTradeLong({
|
||||
agent,
|
||||
price,
|
||||
tradeMint,
|
||||
}: {
|
||||
agent: SolanaAgentKit;
|
||||
price: number;
|
||||
tradeMint: PublicKey;
|
||||
}) {
|
||||
const client = await AdrenaClient.load(agent);
|
||||
|
||||
const owner = agent.wallet.publicKey;
|
||||
|
||||
const custody = client.getCustodyByMint(tradeMint);
|
||||
|
||||
const custodyTokenAccount =
|
||||
AdrenaClient.findCustodyTokenAccountAddress(tradeMint);
|
||||
|
||||
const stakingRewardTokenCustodyAccount = client.getCustodyByMint(
|
||||
AdrenaClient.stakingRewardTokenMint,
|
||||
);
|
||||
|
||||
const stakingRewardTokenCustodyTokenAccount =
|
||||
AdrenaClient.findCustodyTokenAccountAddress(
|
||||
AdrenaClient.stakingRewardTokenMint,
|
||||
);
|
||||
|
||||
const position = AdrenaClient.findPositionAddress(
|
||||
owner,
|
||||
custody.pubkey,
|
||||
"long",
|
||||
);
|
||||
|
||||
const userProfilePda = AdrenaClient.getUserProfilePda(owner);
|
||||
|
||||
const userProfile =
|
||||
await client.program.account.userProfile.fetchNullable(userProfilePda);
|
||||
|
||||
const receivingAccount = AdrenaClient.findATAAddressSync(owner, custody.mint);
|
||||
|
||||
const preInstructions: TransactionInstruction[] = [];
|
||||
|
||||
if (
|
||||
!(await AdrenaClient.isAccountInitialized(
|
||||
agent.connection,
|
||||
receivingAccount,
|
||||
))
|
||||
) {
|
||||
preInstructions.push(
|
||||
AdrenaClient.createATAInstruction({
|
||||
ataAddress: receivingAccount,
|
||||
mint: custody.mint,
|
||||
owner,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const instruction = await client.program.methods
|
||||
.closePositionLong({
|
||||
price: new BN(price * 10 ** PRICE_DECIMALS),
|
||||
})
|
||||
.accountsStrict({
|
||||
owner,
|
||||
receivingAccount,
|
||||
transferAuthority: AdrenaClient.transferAuthority,
|
||||
pool: AdrenaClient.mainPool,
|
||||
position: position,
|
||||
custody: custody.pubkey,
|
||||
custodyTokenAccount,
|
||||
custodyOracle: custody.oracle,
|
||||
custodyTradeOracle: custody.tradeOracle,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
lmStaking: AdrenaClient.lmStaking,
|
||||
lpStaking: AdrenaClient.lpStaking,
|
||||
cortex: AdrenaClient.cortex,
|
||||
stakingRewardTokenCustody: stakingRewardTokenCustodyAccount.pubkey,
|
||||
stakingRewardTokenCustodyOracle: stakingRewardTokenCustodyAccount.oracle,
|
||||
stakingRewardTokenCustodyTokenAccount,
|
||||
lmStakingRewardTokenVault: AdrenaClient.lmStakingRewardTokenVault,
|
||||
lpStakingRewardTokenVault: AdrenaClient.lpStakingRewardTokenVault,
|
||||
lpTokenMint: AdrenaClient.lpTokenMint,
|
||||
protocolFeeRecipient: client.cortex.protocolFeeRecipient,
|
||||
adrenaProgram: AdrenaClient.programId,
|
||||
userProfile: userProfile ? userProfilePda : null,
|
||||
caller: owner,
|
||||
})
|
||||
.instruction();
|
||||
|
||||
return sendTx(agent, [...preInstructions, instruction]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open long trade on Adrena
|
||||
*
|
||||
* Note: provide the same token as collateralMint and as tradeMint to avoid swap
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function openPerpTradeLong({
|
||||
agent,
|
||||
price,
|
||||
collateralAmount,
|
||||
collateralMint = TOKENS.jitoSOL,
|
||||
leverage = DEFAULT_OPTIONS.LEVERAGE_BPS,
|
||||
tradeMint = TOKENS.jitoSOL,
|
||||
slippage = 0.3,
|
||||
}: {
|
||||
agent: SolanaAgentKit;
|
||||
price: number;
|
||||
collateralAmount: number;
|
||||
collateralMint?: PublicKey;
|
||||
leverage?: number;
|
||||
tradeMint?: PublicKey;
|
||||
slippage?: number;
|
||||
}): Promise<string> {
|
||||
const client = await AdrenaClient.load(agent);
|
||||
|
||||
const owner = agent.wallet.publicKey;
|
||||
|
||||
const collateralAccount = AdrenaClient.findATAAddressSync(owner, tradeMint);
|
||||
const fundingAccount = AdrenaClient.findATAAddressSync(owner, collateralMint);
|
||||
|
||||
const receivingCustody = AdrenaClient.findCustodyAddress(collateralMint);
|
||||
const receivingCustodyOracle = client.getCustodyByMint(collateralMint).oracle;
|
||||
const receivingCustodyTokenAccount =
|
||||
AdrenaClient.findCustodyTokenAccountAddress(collateralMint);
|
||||
|
||||
// Principal custody is the custody of the targeted token
|
||||
// i.e open a 1 ETH long position, principal custody is ETH
|
||||
const principalCustody = AdrenaClient.findCustodyAddress(tradeMint);
|
||||
const principalCustodyAccount = client.getCustodyByMint(tradeMint);
|
||||
const principalCustodyOracle = principalCustodyAccount.oracle;
|
||||
const principalCustodyTradeOracle = principalCustodyAccount.tradeOracle;
|
||||
const principalCustodyTokenAccount =
|
||||
AdrenaClient.findCustodyTokenAccountAddress(tradeMint);
|
||||
|
||||
const stakingRewardTokenCustodyAccount = client.getCustodyByMint(
|
||||
AdrenaClient.stakingRewardTokenMint,
|
||||
);
|
||||
|
||||
const stakingRewardTokenCustodyTokenAccount =
|
||||
AdrenaClient.findCustodyTokenAccountAddress(
|
||||
AdrenaClient.stakingRewardTokenMint,
|
||||
);
|
||||
|
||||
const position = AdrenaClient.findPositionAddress(
|
||||
owner,
|
||||
principalCustody,
|
||||
"long",
|
||||
);
|
||||
|
||||
const userProfilePda = AdrenaClient.getUserProfilePda(owner);
|
||||
|
||||
const userProfile =
|
||||
await client.program.account.userProfile.fetchNullable(userProfilePda);
|
||||
|
||||
const priceWithSlippage = applySlippage(
|
||||
new BN(price * 10 ** PRICE_DECIMALS),
|
||||
slippage,
|
||||
);
|
||||
|
||||
const scaledCollateralAmount = new BN(
|
||||
collateralAmount *
|
||||
Math.pow(10, client.getCustodyByMint(collateralMint).decimals),
|
||||
);
|
||||
|
||||
const preInstructions: TransactionInstruction[] = [];
|
||||
|
||||
if (
|
||||
!(await AdrenaClient.isAccountInitialized(
|
||||
agent.connection,
|
||||
collateralAccount,
|
||||
))
|
||||
) {
|
||||
preInstructions.push(
|
||||
AdrenaClient.createATAInstruction({
|
||||
ataAddress: collateralAccount,
|
||||
mint: tradeMint,
|
||||
owner,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const instruction = await client.program.methods
|
||||
.openOrIncreasePositionWithSwapLong({
|
||||
price: priceWithSlippage,
|
||||
collateral: scaledCollateralAmount,
|
||||
leverage,
|
||||
referrer: null,
|
||||
})
|
||||
.accountsStrict({
|
||||
owner,
|
||||
payer: owner,
|
||||
fundingAccount,
|
||||
collateralAccount,
|
||||
receivingCustody,
|
||||
receivingCustodyOracle,
|
||||
receivingCustodyTokenAccount,
|
||||
principalCustody,
|
||||
principalCustodyOracle,
|
||||
principalCustodyTradeOracle,
|
||||
principalCustodyTokenAccount,
|
||||
transferAuthority: AdrenaClient.transferAuthority,
|
||||
cortex: AdrenaClient.cortex,
|
||||
lmStaking: AdrenaClient.lmStaking,
|
||||
lpStaking: AdrenaClient.lpStaking,
|
||||
pool: AdrenaClient.mainPool,
|
||||
position,
|
||||
stakingRewardTokenCustody: stakingRewardTokenCustodyAccount.pubkey,
|
||||
stakingRewardTokenCustodyOracle: stakingRewardTokenCustodyAccount.oracle,
|
||||
stakingRewardTokenCustodyTokenAccount,
|
||||
lmStakingRewardTokenVault: AdrenaClient.lmStakingRewardTokenVault,
|
||||
lpStakingRewardTokenVault: AdrenaClient.lpStakingRewardTokenVault,
|
||||
lpTokenMint: AdrenaClient.lpTokenMint,
|
||||
userProfile: userProfile ? userProfilePda : null,
|
||||
protocolFeeRecipient: client.cortex.protocolFeeRecipient,
|
||||
systemProgram: SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
adrenaProgram: ADRENA_PROGRAM_ID,
|
||||
})
|
||||
.instruction();
|
||||
|
||||
return sendTx(agent, [...preInstructions, instruction]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open short trade on Adrena
|
||||
*
|
||||
* Note: provide USDC as collateralMint to avoid swap
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function openPerpTradeShort({
|
||||
agent,
|
||||
price,
|
||||
collateralAmount,
|
||||
collateralMint = TOKENS.USDC,
|
||||
leverage = DEFAULT_OPTIONS.LEVERAGE_BPS,
|
||||
tradeMint = TOKENS.jitoSOL,
|
||||
slippage = 0.3,
|
||||
}: {
|
||||
agent: SolanaAgentKit;
|
||||
price: number;
|
||||
collateralAmount: number;
|
||||
collateralMint?: PublicKey;
|
||||
leverage?: number;
|
||||
tradeMint?: PublicKey;
|
||||
slippage?: number;
|
||||
}): Promise<string> {
|
||||
const client = await AdrenaClient.load(agent);
|
||||
|
||||
const owner = agent.wallet.publicKey;
|
||||
|
||||
const collateralAccount = AdrenaClient.findATAAddressSync(owner, tradeMint);
|
||||
const fundingAccount = AdrenaClient.findATAAddressSync(owner, collateralMint);
|
||||
|
||||
const receivingCustody = AdrenaClient.findCustodyAddress(collateralMint);
|
||||
const receivingCustodyOracle = client.getCustodyByMint(collateralMint).oracle;
|
||||
const receivingCustodyTokenAccount =
|
||||
AdrenaClient.findCustodyTokenAccountAddress(collateralMint);
|
||||
|
||||
// Principal custody is the custody of the targeted token
|
||||
// i.e open a 1 BTC short position, principal custody is BTC
|
||||
const principalCustody = AdrenaClient.findCustodyAddress(tradeMint);
|
||||
const principalCustodyAccount = client.getCustodyByMint(tradeMint);
|
||||
const principalCustodyTradeOracle = principalCustodyAccount.tradeOracle;
|
||||
const principalCustodyTokenAccount =
|
||||
AdrenaClient.findCustodyTokenAccountAddress(tradeMint);
|
||||
|
||||
const usdcAta = AdrenaClient.findATAAddressSync(owner, TOKENS.USDC);
|
||||
|
||||
const preInstructions: TransactionInstruction[] = [];
|
||||
|
||||
if (!(await AdrenaClient.isAccountInitialized(agent.connection, usdcAta))) {
|
||||
preInstructions.push(
|
||||
AdrenaClient.createATAInstruction({
|
||||
ataAddress: usdcAta,
|
||||
mint: TOKENS.USDC,
|
||||
owner,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Custody used to provide collateral when opening the position
|
||||
// Should be a stable token, by default, use USDC
|
||||
const instructionCollateralMint = TOKENS.USDC;
|
||||
|
||||
const collateralCustody = AdrenaClient.findCustodyAddress(
|
||||
instructionCollateralMint,
|
||||
);
|
||||
const collateralCustodyOracle = client.getCustodyByMint(
|
||||
instructionCollateralMint,
|
||||
).oracle;
|
||||
|
||||
const collateralCustodyTokenAccount =
|
||||
AdrenaClient.findCustodyTokenAccountAddress(instructionCollateralMint);
|
||||
|
||||
const stakingRewardTokenCustodyAccount = client.getCustodyByMint(
|
||||
AdrenaClient.stakingRewardTokenMint,
|
||||
);
|
||||
|
||||
const stakingRewardTokenCustodyTokenAccount =
|
||||
AdrenaClient.findCustodyTokenAccountAddress(
|
||||
AdrenaClient.stakingRewardTokenMint,
|
||||
);
|
||||
|
||||
const position = AdrenaClient.findPositionAddress(
|
||||
owner,
|
||||
principalCustody,
|
||||
"long",
|
||||
);
|
||||
|
||||
const userProfilePda = AdrenaClient.getUserProfilePda(owner);
|
||||
|
||||
const userProfile =
|
||||
await client.program.account.userProfile.fetchNullable(userProfilePda);
|
||||
|
||||
const priceWithSlippage = applySlippage(
|
||||
new BN(price * 10 ** PRICE_DECIMALS),
|
||||
slippage,
|
||||
);
|
||||
|
||||
const scaledCollateralAmount = new BN(
|
||||
collateralAmount *
|
||||
Math.pow(10, client.getCustodyByMint(collateralMint).decimals),
|
||||
);
|
||||
|
||||
const instruction = await client.program.methods
|
||||
.openOrIncreasePositionWithSwapShort({
|
||||
price: priceWithSlippage,
|
||||
collateral: scaledCollateralAmount,
|
||||
leverage,
|
||||
referrer: null,
|
||||
})
|
||||
.accountsStrict({
|
||||
owner,
|
||||
payer: owner,
|
||||
fundingAccount,
|
||||
collateralAccount,
|
||||
receivingCustody,
|
||||
receivingCustodyOracle,
|
||||
receivingCustodyTokenAccount,
|
||||
principalCustody,
|
||||
principalCustodyTradeOracle,
|
||||
principalCustodyTokenAccount,
|
||||
collateralCustody,
|
||||
collateralCustodyOracle,
|
||||
collateralCustodyTokenAccount,
|
||||
transferAuthority: AdrenaClient.transferAuthority,
|
||||
cortex: AdrenaClient.cortex,
|
||||
lmStaking: AdrenaClient.lmStaking,
|
||||
lpStaking: AdrenaClient.lpStaking,
|
||||
pool: AdrenaClient.mainPool,
|
||||
position,
|
||||
stakingRewardTokenCustody: stakingRewardTokenCustodyAccount.pubkey,
|
||||
stakingRewardTokenCustodyOracle: stakingRewardTokenCustodyAccount.oracle,
|
||||
stakingRewardTokenCustodyTokenAccount,
|
||||
lmStakingRewardTokenVault: AdrenaClient.lmStakingRewardTokenVault,
|
||||
lpStakingRewardTokenVault: AdrenaClient.lpStakingRewardTokenVault,
|
||||
lpTokenMint: AdrenaClient.lpTokenMint,
|
||||
userProfile: userProfile ? userProfilePda : null,
|
||||
protocolFeeRecipient: client.cortex.protocolFeeRecipient,
|
||||
systemProgram: SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
adrenaProgram: ADRENA_PROGRAM_ID,
|
||||
})
|
||||
.instruction();
|
||||
|
||||
return sendTx(agent, [...preInstructions, instruction]);
|
||||
}
|
||||
1
src/tools/adrena/index.ts
Normal file
1
src/tools/adrena/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./adrena_perp_trading";
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import OpenAI from "openai";
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SolanaAgentKit } from "..";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
|
||||
/**
|
||||
* Get the agents wallet address
|
||||
2
src/tools/agent/index.ts
Normal file
2
src/tools/agent/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./create_image";
|
||||
export * from "./get_wallet_address";
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import { getAllTld } from "@onsol/tldparser";
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { TldParser } from "@onsol/tldparser";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TldParser } from "@onsol/tldparser";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
/**
|
||||
* Get all domains owned by an address for a specific TLD
|
||||
* @param agent SolanaAgentKit instance
|
||||
4
src/tools/alldomains/index.ts
Normal file
4
src/tools/alldomains/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./get_all_domains_tlds";
|
||||
export * from "./get_owned_all_domains";
|
||||
export * from "./get_owned_domains_for_tld";
|
||||
export * from "./resolve_domain";
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TldParser } from "@onsol/tldparser";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
|
||||
/**
|
||||
@@ -1,37 +0,0 @@
|
||||
import {
|
||||
PublicKey,
|
||||
sendAndConfirmTransaction,
|
||||
Transaction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { ManifestClient } from "@cks-systems/manifest-sdk";
|
||||
|
||||
/**
|
||||
* Cancels all orders from Manifest
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param marketId Public key for the manifest market
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function cancelAllOrders(
|
||||
agent: SolanaAgentKit,
|
||||
marketId: PublicKey,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const mfxClient = await ManifestClient.getClientForMarket(
|
||||
agent.connection,
|
||||
marketId,
|
||||
agent.wallet,
|
||||
);
|
||||
|
||||
const cancelAllOrdersIx = await mfxClient.cancelAllIx();
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
agent.connection,
|
||||
new Transaction().add(cancelAllOrdersIx),
|
||||
[agent.wallet],
|
||||
);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Cancel all orders failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { JupiterTokenData } from "../types";
|
||||
import { JupiterTokenData } from "../../types";
|
||||
|
||||
export async function getTokenDataByAddress(
|
||||
mint: PublicKey,
|
||||
@@ -9,17 +9,14 @@ export async function getTokenDataByAddress(
|
||||
throw new Error("Mint address is required");
|
||||
}
|
||||
|
||||
const response = await fetch("https://tokens.jup.ag/tokens?tags=verified", {
|
||||
const response = await fetch(`https://tokens.jup.ag/token/${mint}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const data = (await response.json()) as JupiterTokenData[];
|
||||
const token = data.find((token: JupiterTokenData) => {
|
||||
return token.address === mint.toBase58();
|
||||
});
|
||||
const token = (await response.json()) as JupiterTokenData;
|
||||
return token;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error fetching token data: ${error.message}`);
|
||||
1
src/tools/dexscreener/index.ts
Normal file
1
src/tools/dexscreener/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./get_token_data";
|
||||
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";
|
||||
118
src/tools/flash/flash_close_trade.ts
Normal file
118
src/tools/flash/flash_close_trade.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { ComputeBudgetProgram } from "@solana/web3.js";
|
||||
import { PoolConfig, Side } from "flash-sdk";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import {
|
||||
CLOSE_POSITION_CU,
|
||||
marketSdkInfo,
|
||||
marketTokenMap,
|
||||
getNftTradingAccountInfo,
|
||||
fetchOraclePrice,
|
||||
createPerpClient,
|
||||
get_flash_privilege,
|
||||
} from "../../utils/flashUtils";
|
||||
import { FlashCloseTradeParams } from "../../types";
|
||||
|
||||
/**
|
||||
* Closes an existing position on Flash.Trade
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param params Trade parameters
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function flashCloseTrade(
|
||||
agent: SolanaAgentKit,
|
||||
params: FlashCloseTradeParams,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const { token, side } = params;
|
||||
|
||||
// Get market ID from token and side using marketTokenMap
|
||||
const tokenMarkets = marketTokenMap[token];
|
||||
if (!tokenMarkets) {
|
||||
throw new Error(`Token ${token} not supported for trading`);
|
||||
}
|
||||
|
||||
const sideEntry = tokenMarkets[side];
|
||||
if (!sideEntry) {
|
||||
throw new Error(`${side} side not available for ${token}`);
|
||||
}
|
||||
|
||||
const market = sideEntry.marketID;
|
||||
|
||||
// Validate market data using marketSdkInfo
|
||||
const marketData = marketSdkInfo[market];
|
||||
if (!marketData) {
|
||||
throw new Error(`Invalid market configuration for ${token}/${side}`);
|
||||
}
|
||||
|
||||
// Get token information
|
||||
const [targetSymbol, collateralSymbol] = marketData.tokenPair.split("/");
|
||||
|
||||
// Fetch oracle prices
|
||||
const [targetPrice] = await Promise.all([
|
||||
fetchOraclePrice(targetSymbol),
|
||||
fetchOraclePrice(collateralSymbol),
|
||||
]);
|
||||
|
||||
// Initialize pool configuration and perpClient
|
||||
const poolConfig = PoolConfig.fromIdsByName(
|
||||
marketData.pool,
|
||||
"mainnet-beta",
|
||||
);
|
||||
const perpClient = createPerpClient(agent.connection, agent.wallet);
|
||||
|
||||
// Calculate price after slippage
|
||||
const slippageBpsBN = new BN(100); // 1% slippage
|
||||
const sideEnum = side === "long" ? Side.Long : Side.Short;
|
||||
const priceWithSlippage = perpClient.getPriceAfterSlippage(
|
||||
false, // isEntry = false for closing position
|
||||
slippageBpsBN,
|
||||
targetPrice.price,
|
||||
sideEnum,
|
||||
);
|
||||
|
||||
// Get NFT trading account info
|
||||
const tradingAccounts = await getNftTradingAccountInfo(
|
||||
agent.wallet_address,
|
||||
perpClient,
|
||||
poolConfig,
|
||||
collateralSymbol,
|
||||
);
|
||||
|
||||
if (
|
||||
!tradingAccounts.nftTradingAccountPk ||
|
||||
!tradingAccounts.nftReferralAccountPK ||
|
||||
!tradingAccounts.nftOwnerRebateTokenAccountPk
|
||||
) {
|
||||
throw new Error("Required NFT trading accounts not found");
|
||||
}
|
||||
|
||||
// Build and send transaction
|
||||
const { instructions, additionalSigners } = await perpClient.closePosition(
|
||||
targetSymbol,
|
||||
collateralSymbol,
|
||||
priceWithSlippage,
|
||||
sideEnum,
|
||||
poolConfig,
|
||||
get_flash_privilege(agent),
|
||||
tradingAccounts.nftTradingAccountPk,
|
||||
tradingAccounts.nftReferralAccountPK,
|
||||
tradingAccounts.nftOwnerRebateTokenAccountPk,
|
||||
);
|
||||
|
||||
const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({
|
||||
units: CLOSE_POSITION_CU,
|
||||
});
|
||||
|
||||
return await perpClient.sendTransaction(
|
||||
[computeBudgetIx, ...instructions],
|
||||
{
|
||||
additionalSigners: additionalSigners,
|
||||
alts: perpClient.addressLookupTables,
|
||||
prioritizationFee: 5000000,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(`Flash trade close failed: ${error}`);
|
||||
}
|
||||
}
|
||||
251
src/tools/flash/flash_open_trade.ts
Normal file
251
src/tools/flash/flash_open_trade.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
import { ComputeBudgetProgram } from "@solana/web3.js";
|
||||
import {
|
||||
PerpetualsClient,
|
||||
OraclePrice,
|
||||
PoolConfig,
|
||||
Side,
|
||||
CustodyAccount,
|
||||
Custody,
|
||||
} from "flash-sdk";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import {
|
||||
ALL_TOKENS,
|
||||
marketSdkInfo,
|
||||
marketTokenMap,
|
||||
getNftTradingAccountInfo,
|
||||
OPEN_POSITION_CU,
|
||||
fetchOraclePrice,
|
||||
createPerpClient,
|
||||
get_flash_privilege,
|
||||
} from "../../utils/flashUtils";
|
||||
import { FlashTradeParams } from "../../types";
|
||||
|
||||
/**
|
||||
* Opens a new position on Flash.Trade
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param params Trade parameters
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function flashOpenTrade(
|
||||
agent: SolanaAgentKit,
|
||||
params: FlashTradeParams,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const { token, side, collateralUsd, leverage } = params;
|
||||
|
||||
// Get market ID from token and side using marketTokenMap
|
||||
const tokenMarkets = marketTokenMap[token];
|
||||
if (!tokenMarkets) {
|
||||
throw new Error(`Token ${token} not supported for trading`);
|
||||
}
|
||||
|
||||
const sideEntry = tokenMarkets[side];
|
||||
if (!sideEntry) {
|
||||
throw new Error(`${side} side not available for ${token}`);
|
||||
}
|
||||
|
||||
const market = sideEntry.marketID;
|
||||
|
||||
// Validate market data using marketSdkInfo
|
||||
const marketData = marketSdkInfo[market];
|
||||
if (!marketData) {
|
||||
throw new Error(`Invalid market configuration for ${token}/${side}`);
|
||||
}
|
||||
|
||||
// Get token information
|
||||
const [targetSymbol, collateralSymbol] = marketData.tokenPair.split("/");
|
||||
const targetToken = ALL_TOKENS.find((t) => t.symbol === targetSymbol);
|
||||
const collateralToken = ALL_TOKENS.find(
|
||||
(t) => t.symbol === collateralSymbol,
|
||||
);
|
||||
|
||||
if (!targetToken || !collateralToken) {
|
||||
throw new Error(`Token not found for pair ${marketData.tokenPair}`);
|
||||
}
|
||||
|
||||
// Fetch oracle prices
|
||||
const [targetPrice, collateralPrice] = await Promise.all([
|
||||
fetchOraclePrice(targetSymbol),
|
||||
fetchOraclePrice(collateralSymbol),
|
||||
]);
|
||||
|
||||
// Initialize pool configuration and perpClient
|
||||
const poolConfig = PoolConfig.fromIdsByName(
|
||||
marketData.pool,
|
||||
"mainnet-beta",
|
||||
);
|
||||
const perpClient = createPerpClient(agent.connection, agent.wallet);
|
||||
|
||||
// Calculate position parameters
|
||||
const leverageBN = new BN(leverage);
|
||||
const collateralTokenPrice = convertPriceToNumber(collateralPrice.price);
|
||||
const collateralAmount = calculateCollateralAmount(
|
||||
collateralUsd,
|
||||
collateralTokenPrice,
|
||||
collateralToken.decimals,
|
||||
);
|
||||
|
||||
// Get custody accounts
|
||||
const { targetCustody, collateralCustody } = await fetchCustodyAccounts(
|
||||
perpClient,
|
||||
poolConfig,
|
||||
targetSymbol,
|
||||
collateralSymbol,
|
||||
);
|
||||
|
||||
// Calculate position size
|
||||
const positionSize = calculatePositionSize(
|
||||
perpClient,
|
||||
collateralAmount,
|
||||
leverageBN,
|
||||
targetToken,
|
||||
collateralToken,
|
||||
side,
|
||||
targetPrice.price,
|
||||
collateralPrice.price,
|
||||
targetCustody,
|
||||
collateralCustody,
|
||||
);
|
||||
|
||||
// Get NFT trading account info
|
||||
const tradingAccounts = await getNftTradingAccountInfo(
|
||||
agent.wallet_address,
|
||||
perpClient,
|
||||
poolConfig,
|
||||
collateralSymbol,
|
||||
);
|
||||
|
||||
if (
|
||||
!tradingAccounts.nftTradingAccountPk ||
|
||||
!tradingAccounts.nftReferralAccountPK
|
||||
) {
|
||||
throw new Error("Required NFT trading accounts not found");
|
||||
}
|
||||
|
||||
// Prepare transaction
|
||||
const slippageBps = new BN(1000);
|
||||
const priceWithSlippage = perpClient.getPriceAfterSlippage(
|
||||
true,
|
||||
slippageBps,
|
||||
targetPrice.price,
|
||||
side === "long" ? Side.Long : Side.Short,
|
||||
);
|
||||
|
||||
// Build and send transaction
|
||||
const { instructions, additionalSigners } = await perpClient.openPosition(
|
||||
targetSymbol,
|
||||
collateralSymbol,
|
||||
priceWithSlippage,
|
||||
collateralAmount,
|
||||
positionSize,
|
||||
side === "long" ? Side.Long : Side.Short,
|
||||
poolConfig,
|
||||
get_flash_privilege(agent),
|
||||
tradingAccounts.nftTradingAccountPk,
|
||||
tradingAccounts.nftReferralAccountPK,
|
||||
tradingAccounts.nftOwnerRebateTokenAccountPk!,
|
||||
false,
|
||||
);
|
||||
|
||||
const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({
|
||||
units: OPEN_POSITION_CU,
|
||||
});
|
||||
|
||||
return await perpClient.sendTransaction(
|
||||
[computeBudgetIx, ...instructions],
|
||||
{
|
||||
additionalSigners: additionalSigners,
|
||||
alts: perpClient.addressLookupTables,
|
||||
prioritizationFee: 5000000,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(`Flash trade failed: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
function convertPriceToNumber(oraclePrice: OraclePrice): number {
|
||||
const price = parseInt(oraclePrice.price.toString("hex"), 16);
|
||||
const exponent = parseInt(oraclePrice.exponent.toString("hex"), 16);
|
||||
return price * Math.pow(10, exponent);
|
||||
}
|
||||
|
||||
function calculateCollateralAmount(
|
||||
usdAmount: number,
|
||||
tokenPrice: number,
|
||||
decimals: number,
|
||||
): BN {
|
||||
return new BN((usdAmount / tokenPrice) * Math.pow(10, decimals));
|
||||
}
|
||||
|
||||
async function fetchCustodyAccounts(
|
||||
perpClient: PerpetualsClient,
|
||||
poolConfig: PoolConfig,
|
||||
targetSymbol: string,
|
||||
collateralSymbol: string,
|
||||
) {
|
||||
const targetConfig = poolConfig.custodies.find(
|
||||
(c) => c.symbol === targetSymbol,
|
||||
);
|
||||
const collateralConfig = poolConfig.custodies.find(
|
||||
(c) => c.symbol === collateralSymbol,
|
||||
);
|
||||
|
||||
if (!targetConfig || !collateralConfig) {
|
||||
throw new Error("Custody configuration not found");
|
||||
}
|
||||
|
||||
const accounts = await perpClient.provider.connection.getMultipleAccountsInfo(
|
||||
[targetConfig.custodyAccount, collateralConfig.custodyAccount],
|
||||
);
|
||||
|
||||
if (!accounts[0] || !accounts[1]) {
|
||||
throw new Error("Failed to fetch custody accounts");
|
||||
}
|
||||
|
||||
return {
|
||||
targetCustody: CustodyAccount.from(
|
||||
targetConfig.custodyAccount,
|
||||
perpClient.program.coder.accounts.decode<Custody>(
|
||||
"custody",
|
||||
accounts[0].data,
|
||||
),
|
||||
),
|
||||
collateralCustody: CustodyAccount.from(
|
||||
collateralConfig.custodyAccount,
|
||||
perpClient.program.coder.accounts.decode<Custody>(
|
||||
"custody",
|
||||
accounts[1].data,
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function calculatePositionSize(
|
||||
perpClient: PerpetualsClient,
|
||||
collateralAmount: BN,
|
||||
leverage: BN,
|
||||
targetToken: any,
|
||||
collateralToken: any,
|
||||
side: "long" | "short",
|
||||
targetPrice: OraclePrice,
|
||||
collateralPrice: OraclePrice,
|
||||
targetCustody: CustodyAccount,
|
||||
collateralCustody: CustodyAccount,
|
||||
): BN {
|
||||
return perpClient.getSizeAmountFromLeverageAndCollateral(
|
||||
collateralAmount,
|
||||
leverage.toString(),
|
||||
targetToken,
|
||||
collateralToken,
|
||||
side === "long" ? Side.Long : Side.Short,
|
||||
targetPrice,
|
||||
targetPrice,
|
||||
targetCustody,
|
||||
collateralPrice,
|
||||
collateralPrice,
|
||||
collateralCustody,
|
||||
);
|
||||
}
|
||||
2
src/tools/flash/index.ts
Normal file
2
src/tools/flash/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./flash_open_trade";
|
||||
export * from "./flash_close_trade";
|
||||
@@ -1,6 +1,6 @@
|
||||
import { VersionedTransaction } from "@solana/web3.js";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { GibworkCreateTaskReponse, SolanaAgentKit } from "../index";
|
||||
import { GibworkCreateTaskReponse, SolanaAgentKit } from "../../index";
|
||||
|
||||
/**
|
||||
* Create an new task on Gibwork
|
||||
1
src/tools/gibwork/index.ts
Normal file
1
src/tools/gibwork/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./create_gibwork_task";
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
@@ -1,64 +1,30 @@
|
||||
export * from "./get_wallet_address";
|
||||
export * from "./request_faucet_funds";
|
||||
export * from "./get_wallet_address";
|
||||
export * from "./request_faucet_funds";
|
||||
export * from "./deploy_token";
|
||||
export * from "./deploy_collection";
|
||||
export * from "./get_balance";
|
||||
export * from "./get_balance_other";
|
||||
export * from "./mint_nft";
|
||||
export * from "./transfer";
|
||||
export * from "./trade";
|
||||
export * from "./limit_order";
|
||||
export * from "./batch_order";
|
||||
export * from "./cancel_all_orders";
|
||||
export * from "./withdraw_all";
|
||||
export * from "./register_domain";
|
||||
export * from "./resolve_sol_domain";
|
||||
export * from "./get_primary_domain";
|
||||
export * from "./launch_pumpfun_token";
|
||||
export * from "./lend";
|
||||
export * from "./get_tps";
|
||||
export * from "./get_token_data";
|
||||
export * from "./stake_with_jup";
|
||||
export * from "./stake_with_solayer";
|
||||
export * from "./fetch_price";
|
||||
export * from "./send_compressed_airdrop";
|
||||
export * from "./orca_close_position";
|
||||
export * from "./orca_create_clmm";
|
||||
export * from "./orca_create_single_sided_liquidity_pool";
|
||||
export * from "./orca_fetch_positions";
|
||||
export * from "./orca_open_centered_position_with_liquidity";
|
||||
export * from "./orca_open_single_sided_position";
|
||||
export * from "./get_all_domains_tlds";
|
||||
export * from "./get_all_registered_all_domains";
|
||||
export * from "./get_owned_domains_for_tld";
|
||||
export * from "./get_main_all_domains_domain";
|
||||
export * from "./get_owned_all_domains";
|
||||
export * from "./resolve_domain";
|
||||
|
||||
export * from "./get_all_domains_tlds";
|
||||
export * from "./get_all_registered_all_domains";
|
||||
export * from "./get_owned_domains_for_tld";
|
||||
export * from "./get_main_all_domains_domain";
|
||||
export * from "./get_owned_all_domains";
|
||||
export * from "./resolve_domain";
|
||||
|
||||
export * from "./raydium_create_ammV4";
|
||||
export * from "./raydium_create_clmm";
|
||||
export * from "./raydium_create_cpmm";
|
||||
export * from "./openbook_create_market";
|
||||
export * from "./manifest_create_market";
|
||||
export * from "./pyth_fetch_price";
|
||||
|
||||
export * from "./create_gibwork_task";
|
||||
|
||||
export * from "./rock_paper_scissor";
|
||||
export * from "./create_tiplinks";
|
||||
|
||||
export * from "./tensor_trade";
|
||||
export * from "./adrena";
|
||||
export * from "./sns";
|
||||
export * from "./dexscreener";
|
||||
export * from "./alldomains";
|
||||
export * from "./flash";
|
||||
export * from "./gibwork";
|
||||
export * from "./jupiter";
|
||||
export * from "./lulo";
|
||||
export * from "./manifest";
|
||||
export * from "./solana";
|
||||
export * from "./agent";
|
||||
export * from "./metaplex";
|
||||
export * from "./openbook";
|
||||
export * from "./orca";
|
||||
export * from "./pumpfun";
|
||||
export * from "./pyth";
|
||||
export * from "./raydium";
|
||||
export * from "./rugcheck";
|
||||
|
||||
export * from "./voltr_deposit_strategy";
|
||||
export * from "./voltr_withdraw_strategy";
|
||||
export * from "./voltr_get_position_values";
|
||||
export * from "./drift";
|
||||
export * from "./sendarcade";
|
||||
export * from "./solayer";
|
||||
export * from "./tensor";
|
||||
export * from "./3land";
|
||||
export * from "./tiplink";
|
||||
export * from "./lightprotocol";
|
||||
export * from "./squads";
|
||||
export * from "./helius";
|
||||
export * from "./voltr/voltr_deposit_strategy";
|
||||
export * from "./voltr/voltr_withdraw_strategy";
|
||||
export * from "./voltr/voltr_get_position_values";
|
||||
|
||||
3
src/tools/jupiter/index.ts
Normal file
3
src/tools/jupiter/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./fetch_price";
|
||||
export * from "./stake_with_jup";
|
||||
export * from "./trade";
|
||||
@@ -1,5 +1,5 @@
|
||||
import { VersionedTransaction } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
|
||||
/**
|
||||
* Stake SOL with Jup validator
|
||||
@@ -1,11 +1,11 @@
|
||||
import { VersionedTransaction, PublicKey } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import {
|
||||
TOKENS,
|
||||
DEFAULT_OPTIONS,
|
||||
JUP_API,
|
||||
JUP_REFERRAL_ADDRESS,
|
||||
} from "../constants";
|
||||
} from "../../constants";
|
||||
import { getMint } from "@solana/spl-token";
|
||||
/**
|
||||
* Swap tokens using Jupiter Exchange
|
||||
1
src/tools/lightprotocol/index.ts
Normal file
1
src/tools/lightprotocol/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./send_compressed_airdrop";
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import {
|
||||
buildAndSignTx,
|
||||
calculateComputeUnitPrice,
|
||||
@@ -1,61 +0,0 @@
|
||||
import {
|
||||
PublicKey,
|
||||
Transaction,
|
||||
sendAndConfirmTransaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import {
|
||||
ManifestClient,
|
||||
WrapperPlaceOrderParamsExternal,
|
||||
} from "@cks-systems/manifest-sdk";
|
||||
import { OrderType } from "@cks-systems/manifest-sdk/client/ts/src/wrapper/types/OrderType";
|
||||
|
||||
/**
|
||||
* Place limit orders using Manifest
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param marketId Public key for the manifest market
|
||||
* @param quantity Amount to trade in tokens
|
||||
* @param side Buy or Sell
|
||||
* @param price Price in tokens ie. SOL/USDC
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function limitOrder(
|
||||
agent: SolanaAgentKit,
|
||||
marketId: PublicKey,
|
||||
quantity: number,
|
||||
side: string,
|
||||
price: number,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const mfxClient = await ManifestClient.getClientForMarket(
|
||||
agent.connection,
|
||||
marketId,
|
||||
agent.wallet,
|
||||
);
|
||||
|
||||
const orderParams: WrapperPlaceOrderParamsExternal = {
|
||||
numBaseTokens: quantity,
|
||||
tokenPrice: price,
|
||||
isBid: side === "Buy",
|
||||
lastValidSlot: 0,
|
||||
orderType: OrderType.Limit,
|
||||
clientOrderId: Number(Math.random() * 1000),
|
||||
};
|
||||
|
||||
const depositPlaceOrderIx: TransactionInstruction[] =
|
||||
await mfxClient.placeOrderWithRequiredDepositIx(
|
||||
agent.wallet.publicKey,
|
||||
orderParams,
|
||||
);
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
agent.connection,
|
||||
new Transaction().add(...depositPlaceOrderIx),
|
||||
[agent.wallet],
|
||||
);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Limit Order failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
1
src/tools/lulo/index.ts
Normal file
1
src/tools/lulo/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./lend";
|
||||
@@ -1,5 +1,5 @@
|
||||
import { VersionedTransaction } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
|
||||
/**
|
||||
* Lend tokens for yields using Lulo
|
||||
1
src/tools/manifest/index.ts
Normal file
1
src/tools/manifest/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./manifest_trade";
|
||||
@@ -1,35 +1,158 @@
|
||||
import {
|
||||
PublicKey,
|
||||
Transaction,
|
||||
sendAndConfirmTransaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import {
|
||||
ManifestClient,
|
||||
OrderType,
|
||||
WrapperPlaceOrderParamsExternal,
|
||||
} from "@cks-systems/manifest-sdk";
|
||||
import { OrderType } from "@cks-systems/manifest-sdk/client/ts/src/wrapper/types/OrderType";
|
||||
import {
|
||||
Keypair,
|
||||
PublicKey,
|
||||
sendAndConfirmTransaction,
|
||||
SystemProgram,
|
||||
Transaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { BatchOrderPattern, OrderParams, SolanaAgentKit } from "../../index";
|
||||
|
||||
export interface OrderParams {
|
||||
quantity: number;
|
||||
side: string;
|
||||
price: number;
|
||||
export async function manifestCreateMarket(
|
||||
agent: SolanaAgentKit,
|
||||
baseMint: PublicKey,
|
||||
quoteMint: PublicKey,
|
||||
): Promise<string[]> {
|
||||
const marketKeypair: Keypair = Keypair.generate();
|
||||
const FIXED_MANIFEST_HEADER_SIZE: number = 256;
|
||||
const createAccountIx: TransactionInstruction = SystemProgram.createAccount({
|
||||
fromPubkey: agent.wallet.publicKey,
|
||||
newAccountPubkey: marketKeypair.publicKey,
|
||||
space: FIXED_MANIFEST_HEADER_SIZE,
|
||||
lamports: await agent.connection.getMinimumBalanceForRentExemption(
|
||||
FIXED_MANIFEST_HEADER_SIZE,
|
||||
),
|
||||
programId: new PublicKey("MNFSTqtC93rEfYHB6hF82sKdZpUDFWkViLByLd1k1Ms"),
|
||||
});
|
||||
const createMarketIx = ManifestClient["createMarketIx"](
|
||||
agent.wallet.publicKey,
|
||||
baseMint,
|
||||
quoteMint,
|
||||
marketKeypair.publicKey,
|
||||
);
|
||||
|
||||
const tx: Transaction = new Transaction();
|
||||
tx.add(createAccountIx);
|
||||
tx.add(createMarketIx);
|
||||
const signature = await sendAndConfirmTransaction(agent.connection, tx, [
|
||||
agent.wallet,
|
||||
marketKeypair,
|
||||
]);
|
||||
return [signature, marketKeypair.publicKey.toBase58()];
|
||||
}
|
||||
|
||||
interface BatchOrderPattern {
|
||||
side: string;
|
||||
totalQuantity?: number;
|
||||
priceRange?: {
|
||||
min?: number;
|
||||
max?: number;
|
||||
};
|
||||
spacing?: {
|
||||
type: "percentage" | "fixed";
|
||||
value: number;
|
||||
};
|
||||
numberOfOrders?: number;
|
||||
individualQuantity?: number;
|
||||
/**
|
||||
* Place limit orders using Manifest
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param marketId Public key for the manifest market
|
||||
* @param quantity Amount to trade in tokens
|
||||
* @param side Buy or Sell
|
||||
* @param price Price in tokens ie. SOL/USDC
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function limitOrder(
|
||||
agent: SolanaAgentKit,
|
||||
marketId: PublicKey,
|
||||
quantity: number,
|
||||
side: string,
|
||||
price: number,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const mfxClient = await ManifestClient.getClientForMarket(
|
||||
agent.connection,
|
||||
marketId,
|
||||
agent.wallet,
|
||||
);
|
||||
|
||||
const orderParams: WrapperPlaceOrderParamsExternal = {
|
||||
numBaseTokens: quantity,
|
||||
tokenPrice: price,
|
||||
isBid: side === "Buy",
|
||||
lastValidSlot: 0,
|
||||
orderType: OrderType.Limit,
|
||||
clientOrderId: Number(Math.random() * 1000),
|
||||
};
|
||||
|
||||
const depositPlaceOrderIx: TransactionInstruction[] =
|
||||
await mfxClient.placeOrderWithRequiredDepositIx(
|
||||
agent.wallet.publicKey,
|
||||
orderParams,
|
||||
);
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
agent.connection,
|
||||
new Transaction().add(...depositPlaceOrderIx),
|
||||
[agent.wallet],
|
||||
);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Limit Order failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all orders from Manifest
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param marketId Public key for the manifest market
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function cancelAllOrders(
|
||||
agent: SolanaAgentKit,
|
||||
marketId: PublicKey,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const mfxClient = await ManifestClient.getClientForMarket(
|
||||
agent.connection,
|
||||
marketId,
|
||||
agent.wallet,
|
||||
);
|
||||
|
||||
const cancelAllOrdersIx = await mfxClient.cancelAllIx();
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
agent.connection,
|
||||
new Transaction().add(cancelAllOrdersIx),
|
||||
[agent.wallet],
|
||||
);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Cancel all orders failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Withdraws all funds from Manifest
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param marketId Public key for the manifest market
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function withdrawAll(
|
||||
agent: SolanaAgentKit,
|
||||
marketId: PublicKey,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const mfxClient = await ManifestClient.getClientForMarket(
|
||||
agent.connection,
|
||||
marketId,
|
||||
agent.wallet,
|
||||
);
|
||||
|
||||
const withdrawAllIx = await mfxClient.withdrawAllIx();
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
agent.connection,
|
||||
new Transaction().add(...withdrawAllIx),
|
||||
[agent.wallet],
|
||||
);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Withdraw all failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,43 +0,0 @@
|
||||
import { ManifestClient } from "@cks-systems/manifest-sdk";
|
||||
import {
|
||||
Keypair,
|
||||
PublicKey,
|
||||
sendAndConfirmTransaction,
|
||||
SystemProgram,
|
||||
Transaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
|
||||
export async function manifestCreateMarket(
|
||||
agent: SolanaAgentKit,
|
||||
baseMint: PublicKey,
|
||||
quoteMint: PublicKey,
|
||||
): Promise<string[]> {
|
||||
const marketKeypair: Keypair = Keypair.generate();
|
||||
const FIXED_MANIFEST_HEADER_SIZE: number = 256;
|
||||
const createAccountIx: TransactionInstruction = SystemProgram.createAccount({
|
||||
fromPubkey: agent.wallet.publicKey,
|
||||
newAccountPubkey: marketKeypair.publicKey,
|
||||
space: FIXED_MANIFEST_HEADER_SIZE,
|
||||
lamports: await agent.connection.getMinimumBalanceForRentExemption(
|
||||
FIXED_MANIFEST_HEADER_SIZE,
|
||||
),
|
||||
programId: new PublicKey("MNFSTqtC93rEfYHB6hF82sKdZpUDFWkViLByLd1k1Ms"),
|
||||
});
|
||||
const createMarketIx = ManifestClient["createMarketIx"](
|
||||
agent.wallet.publicKey,
|
||||
baseMint,
|
||||
quoteMint,
|
||||
marketKeypair.publicKey,
|
||||
);
|
||||
|
||||
const tx: Transaction = new Transaction();
|
||||
tx.add(createAccountIx);
|
||||
tx.add(createMarketIx);
|
||||
const signature = await sendAndConfirmTransaction(agent.connection, tx, [
|
||||
agent.wallet,
|
||||
marketKeypair,
|
||||
]);
|
||||
return [signature, marketKeypair.publicKey.toBase58()];
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import {
|
||||
generateSigner,
|
||||
keypairIdentity,
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
mplCore,
|
||||
ruleSet,
|
||||
} from "@metaplex-foundation/mpl-core";
|
||||
import { CollectionOptions, CollectionDeployment } from "../types";
|
||||
import { CollectionOptions, CollectionDeployment } from "../../types";
|
||||
import {
|
||||
fromWeb3JsKeypair,
|
||||
toWeb3JsPublicKey,
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
|
||||
import { generateSigner, keypairIdentity } from "@metaplex-foundation/umi";
|
||||
3
src/tools/metaplex/index.ts
Normal file
3
src/tools/metaplex/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./deploy_collection";
|
||||
export * from "./mint_nft";
|
||||
export * from "./deploy_token";
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import { generateSigner, keypairIdentity } from "@metaplex-foundation/umi";
|
||||
import { create, mplCore } from "@metaplex-foundation/mpl-core";
|
||||
import { fetchCollection } from "@metaplex-foundation/mpl-core";
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
toWeb3JsPublicKey,
|
||||
} from "@metaplex-foundation/umi-web3js-adapters";
|
||||
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
|
||||
import { MintCollectionNFTResponse } from "../types";
|
||||
import { MintCollectionNFTResponse } from "../../types";
|
||||
|
||||
/**
|
||||
* Mint a new NFT as part of an existing collection
|
||||
1
src/tools/openbook/index.ts
Normal file
1
src/tools/openbook/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./openbook_create_market";
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
} from "@raydium-io/raydium-sdk-v2";
|
||||
import { MintLayout, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
|
||||
export async function openbookCreateMarket(
|
||||
agent: SolanaAgentKit,
|
||||
6
src/tools/orca/index.ts
Normal file
6
src/tools/orca/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from "./orca_close_position";
|
||||
export * from "./orca_create_clmm";
|
||||
export * from "./orca_create_single_sided_liquidity_pool";
|
||||
export * from "./orca_fetch_positions";
|
||||
export * from "./orca_open_centered_position_with_liquidity";
|
||||
export * from "./orca_open_single_sided_position";
|
||||
@@ -4,15 +4,15 @@ import {
|
||||
TransactionMessage,
|
||||
VersionedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { Wallet } from "@coral-xyz/anchor";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import { Wallet } from "../../utils/keypair";
|
||||
import {
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
WhirlpoolContext,
|
||||
buildWhirlpoolClient,
|
||||
PDAUtil,
|
||||
} from "@orca-so/whirlpools-sdk";
|
||||
import { sendTx } from "../utils/send_tx";
|
||||
import { sendTx } from "../../utils/send_tx";
|
||||
import { Percentage } from "@orca-so/common-sdk";
|
||||
|
||||
/**
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
TransactionMessage,
|
||||
VersionedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { Wallet } from "@coral-xyz/anchor";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import { Wallet } from "../../utils/keypair";
|
||||
import { Decimal } from "decimal.js";
|
||||
import {
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
PoolUtil,
|
||||
buildWhirlpoolClient,
|
||||
} from "@orca-so/whirlpools-sdk";
|
||||
import { sendTx } from "../utils/send_tx";
|
||||
import { sendTx } from "../../utils/send_tx";
|
||||
import { FEE_TIERS } from "./orca_create_single_sided_liquidity_pool";
|
||||
|
||||
/**
|
||||
@@ -4,8 +4,9 @@ import {
|
||||
TransactionMessage,
|
||||
VersionedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { BN, Wallet } from "@coral-xyz/anchor";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
import { Wallet } from "../../utils/keypair";
|
||||
import { Decimal } from "decimal.js";
|
||||
import {
|
||||
PDAUtil,
|
||||
@@ -36,7 +37,7 @@ import {
|
||||
getAssociatedTokenAddressSync,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import { sendTx } from "../utils/send_tx";
|
||||
import { sendTx } from "../../utils/send_tx";
|
||||
|
||||
/**
|
||||
* Maps fee tier bps to their corresponding tick spacing values in the Orca Whirlpool protocol.
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import { Wallet } from "@coral-xyz/anchor";
|
||||
import {
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
TransactionMessage,
|
||||
VersionedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { Wallet } from "@coral-xyz/anchor";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import { Wallet } from "../../utils/keypair";
|
||||
import { Decimal } from "decimal.js";
|
||||
import {
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
NO_TOKEN_EXTENSION_CONTEXT,
|
||||
} from "@orca-so/whirlpools-sdk";
|
||||
|
||||
import { sendTx } from "../utils/send_tx";
|
||||
import { sendTx } from "../../utils/send_tx";
|
||||
import { Percentage } from "@orca-so/common-sdk";
|
||||
import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
|
||||
|
||||
@@ -26,7 +26,7 @@ import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
|
||||
* # Opens a Centered Liquidity Position in an Orca Whirlpool
|
||||
*
|
||||
* This function opens a centered liquidity position in a specified Orca Whirlpool. The user defines
|
||||
* a basis point (bps) offset from the cuurent price of the pool to set the lower and upper bounds of the position.
|
||||
* a basis point (bps) offset from the current price of the pool to set the lower and upper bounds of the position.
|
||||
* The user also specifies the token mint and the amount to deposit. The required amount of the other token
|
||||
* is calculated automatically.
|
||||
*
|
||||
@@ -1,11 +1,12 @@
|
||||
import {
|
||||
Keypair,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
TransactionMessage,
|
||||
VersionedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { Wallet } from "@coral-xyz/anchor";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import { Wallet } from "../../utils/keypair";
|
||||
import { Decimal } from "decimal.js";
|
||||
import {
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
@@ -16,7 +17,7 @@ import {
|
||||
TokenExtensionContextForPool,
|
||||
NO_TOKEN_EXTENSION_CONTEXT,
|
||||
} from "@orca-so/whirlpools-sdk";
|
||||
import { sendTx } from "../utils/send_tx";
|
||||
import { sendTx } from "../../utils/send_tx";
|
||||
import { Percentage } from "@orca-so/common-sdk";
|
||||
import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
|
||||
|
||||
@@ -119,17 +120,17 @@ export async function orcaOpenSingleSidedPosition(
|
||||
lowerTick,
|
||||
upperTick,
|
||||
]);
|
||||
let txIds: string = "";
|
||||
let instructions: TransactionInstruction[] = [];
|
||||
let signers: Keypair[] = [];
|
||||
if (txBuilderTickArrays !== null) {
|
||||
const txPayloadTickArrays = await txBuilderTickArrays.build();
|
||||
const txPayloadTickArraysDecompiled = TransactionMessage.decompile(
|
||||
(txPayloadTickArrays.transaction as VersionedTransaction).message,
|
||||
);
|
||||
const instructions = txPayloadTickArraysDecompiled.instructions;
|
||||
const signers = txPayloadTickArrays.signers as Keypair[];
|
||||
|
||||
const tickArrayTxId = await sendTx(agent, instructions, signers);
|
||||
txIds += tickArrayTxId + ",";
|
||||
instructions = instructions.concat(
|
||||
txPayloadTickArraysDecompiled.instructions,
|
||||
);
|
||||
signers = signers.concat(txPayloadTickArrays.signers as Keypair[]);
|
||||
}
|
||||
|
||||
const tokenExtensionCtx: TokenExtensionContextForPool = {
|
||||
@@ -161,14 +162,13 @@ export async function orcaOpenSingleSidedPosition(
|
||||
const txPayloadDecompiled = TransactionMessage.decompile(
|
||||
(txPayload.transaction as VersionedTransaction).message,
|
||||
);
|
||||
const instructions = txPayloadDecompiled.instructions;
|
||||
const signers = txPayload.signers as Keypair[];
|
||||
instructions = instructions.concat(txPayloadDecompiled.instructions);
|
||||
signers = signers.concat(txPayload.signers as Keypair[]);
|
||||
|
||||
const positionTxId = await sendTx(agent, instructions, signers);
|
||||
txIds += positionTxId;
|
||||
const txId = await sendTx(agent, instructions, signers);
|
||||
|
||||
return JSON.stringify({
|
||||
transactionIds: txIds,
|
||||
transactionIds: txId,
|
||||
positionMint: positionMint.toString(),
|
||||
});
|
||||
} catch (error) {
|
||||
1
src/tools/pumpfun/index.ts
Normal file
1
src/tools/pumpfun/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./launch_pumpfun_token";
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
PumpfunLaunchResponse,
|
||||
PumpFunTokenOptions,
|
||||
SolanaAgentKit,
|
||||
} from "../index";
|
||||
} from "../../index";
|
||||
|
||||
async function uploadMetadata(
|
||||
tokenName: string,
|
||||
1
src/tools/pyth/index.ts
Normal file
1
src/tools/pyth/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./pyth_fetch_price";
|
||||
93
src/tools/pyth/pyth_fetch_price.ts
Normal file
93
src/tools/pyth/pyth_fetch_price.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import BN from "bn.js";
|
||||
import { PythPriceFeedIDItem } from "../../types";
|
||||
|
||||
/**
|
||||
* Fetch the price feed ID for a given token symbol from Pyth
|
||||
* @param tokenSymbol Token symbol
|
||||
* @returns Price feed ID
|
||||
*/
|
||||
export async function fetchPythPriceFeedID(
|
||||
tokenSymbol: string,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const stableHermesServiceUrl: string = "https://hermes.pyth.network";
|
||||
|
||||
const response = await fetch(
|
||||
`${stableHermesServiceUrl}/v2/price_feeds?query=${tokenSymbol}&asset_type=crypto`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.length === 0) {
|
||||
throw new Error(`No price feed found for ${tokenSymbol}`);
|
||||
}
|
||||
|
||||
if (data.length > 1) {
|
||||
const filteredData = data.filter(
|
||||
(item: PythPriceFeedIDItem) =>
|
||||
item.attributes.base.toLowerCase() === tokenSymbol.toLowerCase(),
|
||||
);
|
||||
|
||||
if (filteredData.length === 0) {
|
||||
throw new Error(`No price feed found for ${tokenSymbol}`);
|
||||
}
|
||||
|
||||
return filteredData[0].id;
|
||||
}
|
||||
|
||||
return data[0].id;
|
||||
} catch (error: any) {
|
||||
throw new Error(
|
||||
`Fetching price feed ID from Pyth failed: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the price of a given price feed from Pyth
|
||||
* @param priceFeedID Price feed ID
|
||||
* @returns Latest price value from feed
|
||||
*
|
||||
* You can find priceFeedIDs here: https://www.pyth.network/developers/price-feed-ids#stable
|
||||
*/
|
||||
export async function fetchPythPrice(feedID: string): Promise<string> {
|
||||
try {
|
||||
const stableHermesServiceUrl: string = "https://hermes.pyth.network";
|
||||
|
||||
const response = await fetch(
|
||||
`${stableHermesServiceUrl}/v2/updates/price/latest?ids[]=${feedID}`,
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const parsedData = data.parsed;
|
||||
|
||||
if (parsedData.length === 0) {
|
||||
throw new Error(`No price data found for ${feedID}`);
|
||||
}
|
||||
|
||||
const price = new BN(parsedData[0].price.price);
|
||||
const exponent = parsedData[0].price.expo;
|
||||
|
||||
if (exponent < 0) {
|
||||
const adjustedPrice = price.mul(new BN(100));
|
||||
const divisor = new BN(10).pow(new BN(-exponent));
|
||||
const scaledPrice = adjustedPrice.div(divisor);
|
||||
|
||||
const priceStr = scaledPrice.toString();
|
||||
const formattedPrice = `${priceStr.slice(0, -2)}.${priceStr.slice(-2)}`;
|
||||
return formattedPrice.startsWith(".")
|
||||
? `0${formattedPrice}`
|
||||
: formattedPrice;
|
||||
}
|
||||
|
||||
const scaledPrice = price.div(new BN(10).pow(new BN(exponent)));
|
||||
return scaledPrice.toString();
|
||||
} catch (error: any) {
|
||||
throw new Error(`Fetching price from Pyth failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
|
||||
import BN from "bn.js";
|
||||
|
||||
/**
|
||||
* Fetch the price of a given price feed from Pyth
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param priceFeedID Price feed ID
|
||||
* @returns Latest price value from feed
|
||||
*
|
||||
* You can find priceFeedIDs here: https://www.pyth.network/developers/price-feed-ids#stable
|
||||
*/
|
||||
export async function pythFetchPrice(priceFeedID: string): Promise<string> {
|
||||
// get Hermes service URL from https://docs.pyth.network/price-feeds/api-instances-and-providers/hermes
|
||||
const stableHermesServiceUrl: string = "https://hermes.pyth.network";
|
||||
const connection = new PriceServiceConnection(stableHermesServiceUrl);
|
||||
const feeds = [priceFeedID];
|
||||
|
||||
try {
|
||||
const currentPrice = await connection.getLatestPriceFeeds(feeds);
|
||||
|
||||
if (currentPrice === undefined) {
|
||||
throw new Error("Price data not available for the given token.");
|
||||
}
|
||||
|
||||
if (currentPrice.length === 0) {
|
||||
throw new Error("Price data not available for the given token.");
|
||||
}
|
||||
|
||||
// get price and exponent from price feed
|
||||
const price = new BN(currentPrice[0].getPriceUnchecked().price);
|
||||
const exponent = new BN(currentPrice[0].getPriceUnchecked().expo);
|
||||
|
||||
// convert to scaled price
|
||||
const scaledPrice = price.div(new BN(10).pow(exponent));
|
||||
|
||||
return scaledPrice.toString();
|
||||
} catch (error: any) {
|
||||
throw new Error(`Fetching price from Pyth failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
3
src/tools/raydium/index.ts
Normal file
3
src/tools/raydium/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./raydium_create_ammV4";
|
||||
export * from "./raydium_create_clmm";
|
||||
export * from "./raydium_create_cpmm";
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { MintLayout, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
|
||||
export async function raydiumCreateAmmV4(
|
||||
agent: SolanaAgentKit,
|
||||
@@ -7,7 +7,7 @@ import { MintLayout } from "@solana/spl-token";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import Decimal from "decimal.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
|
||||
export async function raydiumCreateClmm(
|
||||
agent: SolanaAgentKit,
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { MintLayout } from "@solana/spl-token";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
|
||||
export async function raydiumCreateCpmm(
|
||||
agent: SolanaAgentKit,
|
||||
1
src/tools/rugcheck/index.ts
Normal file
1
src/tools/rugcheck/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./rugcheck";
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TokenCheck } from "../types";
|
||||
import { TokenCheck } from "../../types";
|
||||
|
||||
const BASE_URL = "https://api.rugcheck.xyz/v1";
|
||||
|
||||
1
src/tools/sendarcade/index.ts
Normal file
1
src/tools/sendarcade/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./rock_paper_scissor";
|
||||
@@ -1,5 +1,5 @@
|
||||
import { sendAndConfirmTransaction, Transaction } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
|
||||
export async function rock_paper_scissor(
|
||||
agent: SolanaAgentKit,
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getAllDomains } from "@bonfida/spl-name-service";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { getAllDomainsTLDs } from "./get_all_domains_tlds";
|
||||
import { getAllDomainsTLDs } from "../alldomains/get_all_domains_tlds";
|
||||
|
||||
/**
|
||||
* Get all registered domains across all TLDs
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getPrimaryDomain as _getPrimaryDomain } from "@bonfida/spl-name-service";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
|
||||
/**
|
||||
* Retrieves the primary .sol domain associated with a given Solana public key.
|
||||
5
src/tools/sns/index.ts
Normal file
5
src/tools/sns/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from "./get_all_registered_all_domains";
|
||||
export * from "./get_main_all_domains_domain";
|
||||
export * from "./get_primary_domain";
|
||||
export * from "./register_domain";
|
||||
export * from "./resolve_sol_domain";
|
||||
@@ -1,8 +1,8 @@
|
||||
import { registerDomainNameV2 } from "@bonfida/spl-name-service";
|
||||
import { Transaction } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
|
||||
import { TOKENS } from "../constants";
|
||||
import { TOKENS } from "../../constants";
|
||||
|
||||
/**
|
||||
* Register a .sol domain name using Bonfida Name Service
|
||||
@@ -1,6 +1,6 @@
|
||||
import { resolve } from "@bonfida/spl-name-service";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
|
||||
/**
|
||||
* Resolves a .sol domain to a Solana PublicKey.
|
||||
103
src/tools/solana/close_empty_token_accounts.ts
Normal file
103
src/tools/solana/close_empty_token_accounts.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
PublicKey,
|
||||
Transaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import {
|
||||
AccountLayout,
|
||||
createCloseAccountInstruction,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
|
||||
/**
|
||||
* Close Empty SPL Token accounts of the agent
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @returns transaction signature and total number of accounts closed
|
||||
*/
|
||||
export async function closeEmptyTokenAccounts(
|
||||
agent: SolanaAgentKit,
|
||||
): Promise<{ signature: string; size: number }> {
|
||||
try {
|
||||
const spl_token = await create_close_instruction(agent, TOKEN_PROGRAM_ID);
|
||||
const token_2022 = await create_close_instruction(
|
||||
agent,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
);
|
||||
const transaction = new Transaction();
|
||||
|
||||
const MAX_INSTRUCTIONS = 40; // 40 instructions can be processed in a single transaction without failing
|
||||
|
||||
spl_token
|
||||
.slice(0, Math.min(MAX_INSTRUCTIONS, spl_token.length))
|
||||
.forEach((instruction) => transaction.add(instruction));
|
||||
|
||||
token_2022
|
||||
.slice(0, Math.max(0, MAX_INSTRUCTIONS - spl_token.length))
|
||||
.forEach((instruction) => transaction.add(instruction));
|
||||
|
||||
const size = spl_token.length + token_2022.length;
|
||||
|
||||
if (size === 0) {
|
||||
return {
|
||||
signature: "",
|
||||
size: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const signature = await agent.connection.sendTransaction(transaction, [
|
||||
agent.wallet,
|
||||
]);
|
||||
|
||||
return { signature, size };
|
||||
} catch (error) {
|
||||
throw new Error(`Error closing empty token accounts: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* creates the close instuctions of a spl token account
|
||||
* @param agnet SolanaAgentKit instance
|
||||
* @param token_program Token Program Id
|
||||
* @returns close instuction array
|
||||
*/
|
||||
|
||||
async function create_close_instruction(
|
||||
agent: SolanaAgentKit,
|
||||
token_program: PublicKey,
|
||||
): Promise<TransactionInstruction[]> {
|
||||
const instructions = [];
|
||||
|
||||
const ata_accounts = await agent.connection.getTokenAccountsByOwner(
|
||||
agent.wallet_address,
|
||||
{ programId: token_program },
|
||||
"confirmed",
|
||||
);
|
||||
|
||||
const tokens = ata_accounts.value;
|
||||
|
||||
const accountExceptions = [
|
||||
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
|
||||
];
|
||||
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const token_data = AccountLayout.decode(tokens[i].account.data);
|
||||
if (
|
||||
token_data.amount === BigInt(0) &&
|
||||
!accountExceptions.includes(token_data.mint.toString())
|
||||
) {
|
||||
const closeInstruction = createCloseAccountInstruction(
|
||||
ata_accounts.value[i].pubkey,
|
||||
agent.wallet_address,
|
||||
agent.wallet_address,
|
||||
[],
|
||||
token_program,
|
||||
);
|
||||
|
||||
instructions.push(closeInstruction);
|
||||
}
|
||||
}
|
||||
|
||||
return instructions;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
|
||||
/**
|
||||
* Get the balance of SOL or an SPL token for the agent's wallet
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
ParsedAccountData,
|
||||
PublicKey,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
|
||||
/**
|
||||
* Get the balance of SOL or an SPL token for the specified wallet address (other than the agent's wallet)
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
|
||||
export async function getTPS(agent: SolanaAgentKit): Promise<number> {
|
||||
const perfSamples = await agent.connection.getRecentPerformanceSamples();
|
||||
6
src/tools/solana/index.ts
Normal file
6
src/tools/solana/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from "./get_tps";
|
||||
export * from "./request_faucet_funds";
|
||||
export * from "./close_empty_token_accounts";
|
||||
export * from "./transfer";
|
||||
export * from "./get_balance";
|
||||
export * from "./get_balance_other";
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import { PublicKey, SystemProgram, Transaction } from "@solana/web3.js";
|
||||
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
|
||||
import {
|
||||
1
src/tools/solayer/index.ts
Normal file
1
src/tools/solayer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./stake_with_solayer";
|
||||
@@ -1,5 +1,5 @@
|
||||
import { VersionedTransaction } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
|
||||
/**
|
||||
* Stake SOL with Solayer
|
||||
@@ -27,7 +27,7 @@ export async function stakeWithSolayer(
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.message || 'Staking request failed');
|
||||
throw new Error(errorData.message || "Staking request failed");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
@@ -61,4 +61,4 @@ export async function stakeWithSolayer(
|
||||
console.error(error);
|
||||
throw new Error(`Solayer sSOL staking failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/tools/squads/approve_proposal.ts
Normal file
52
src/tools/squads/approve_proposal.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import * as multisig from "@sqds/multisig";
|
||||
const { Multisig } = multisig.accounts;
|
||||
|
||||
/**
|
||||
* Approves a proposal in a Solana multisig wallet.
|
||||
*
|
||||
* @param {SolanaAgentKit} agent - The Solana agent kit instance.
|
||||
* @param {number | bigint} [transactionIndex] - The index of the transaction to approve. If not provided, the current transaction index will be used.
|
||||
* @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 multisig_approve_proposal(
|
||||
agent: SolanaAgentKit,
|
||||
transactionIndex?: number | bigint,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const createKey = agent.wallet;
|
||||
const [multisigPda] = multisig.getMultisigPda({
|
||||
createKey: createKey.publicKey,
|
||||
});
|
||||
const multisigInfo = await Multisig.fromAccountAddress(
|
||||
agent.connection,
|
||||
multisigPda,
|
||||
);
|
||||
const currentTransactionIndex = Number(multisigInfo.transactionIndex);
|
||||
if (!transactionIndex) {
|
||||
transactionIndex = BigInt(currentTransactionIndex);
|
||||
} else if (typeof transactionIndex !== "bigint") {
|
||||
transactionIndex = BigInt(transactionIndex);
|
||||
}
|
||||
// const [proposalPda, proposalBump] = multisig.getProposalPda({
|
||||
// multisigPda,
|
||||
// transactionIndex,
|
||||
// });
|
||||
const multisigTx = multisig.transactions.proposalApprove({
|
||||
blockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
feePayer: agent.wallet.publicKey,
|
||||
multisigPda,
|
||||
transactionIndex: transactionIndex,
|
||||
member: agent.wallet.publicKey,
|
||||
});
|
||||
|
||||
multisigTx.sign([agent.wallet]);
|
||||
const tx = await agent.connection.sendRawTransaction(
|
||||
multisigTx.serialize(),
|
||||
);
|
||||
return tx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Transfer failed: ${error}`);
|
||||
}
|
||||
}
|
||||
61
src/tools/squads/create_multisig.ts
Normal file
61
src/tools/squads/create_multisig.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import * as multisig from "@sqds/multisig";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
|
||||
/**
|
||||
* Creates a new Squads multisig account.
|
||||
*
|
||||
* @param agent - The SolanaAgentKit instance containing the connection and wallet information.
|
||||
* @param creator - The public key of the creator who will be a member of the multisig.
|
||||
* @returns A promise that resolves to the transaction ID of the multisig creation transaction.
|
||||
*
|
||||
* @throws Will throw an error if the transaction fails.
|
||||
*/
|
||||
export async function create_squads_multisig(
|
||||
agent: SolanaAgentKit,
|
||||
creator: PublicKey,
|
||||
): Promise<string> {
|
||||
const connection = agent.connection;
|
||||
const createKey = agent.wallet; // can be any keypair, using the agent wallet as only one multisig is required
|
||||
|
||||
const [multisigPda] = multisig.getMultisigPda({
|
||||
createKey: createKey.publicKey,
|
||||
});
|
||||
|
||||
const programConfigPda = multisig.getProgramConfigPda({})[0];
|
||||
|
||||
const programConfig =
|
||||
await multisig.accounts.ProgramConfig.fromAccountAddress(
|
||||
connection,
|
||||
programConfigPda,
|
||||
);
|
||||
|
||||
const configTreasury = programConfig.treasury;
|
||||
const tx = multisig.transactions.multisigCreateV2({
|
||||
blockhash: (await connection.getLatestBlockhash()).blockhash,
|
||||
treasury: configTreasury,
|
||||
createKey: createKey.publicKey,
|
||||
creator: agent.wallet.publicKey,
|
||||
multisigPda,
|
||||
configAuthority: null,
|
||||
timeLock: 0,
|
||||
threshold: 2,
|
||||
rentCollector: null,
|
||||
members: [
|
||||
{
|
||||
key: agent.wallet.publicKey,
|
||||
permissions: multisig.types.Permissions.all(),
|
||||
},
|
||||
{
|
||||
key: creator,
|
||||
permissions: multisig.types.Permissions.all(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
tx.sign([agent.wallet, createKey]);
|
||||
|
||||
const txId = connection.sendRawTransaction(tx.serialize());
|
||||
|
||||
return txId;
|
||||
}
|
||||
48
src/tools/squads/create_proposal.ts
Normal file
48
src/tools/squads/create_proposal.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import * as multisig from "@sqds/multisig";
|
||||
const { Multisig } = multisig.accounts;
|
||||
|
||||
/**
|
||||
* Creates a proposal for a multisig transaction.
|
||||
*
|
||||
* @param {SolanaAgentKit} agent - The Solana agent kit instance.
|
||||
* @param {number | bigint} [transactionIndex] - Optional transaction index. If not provided, the current transaction index will be used.
|
||||
* @returns {Promise<string>} - The transaction ID of the created proposal.
|
||||
* @throws {Error} - Throws an error if the proposal creation fails.
|
||||
*/
|
||||
export async function multisig_create_proposal(
|
||||
agent: SolanaAgentKit,
|
||||
transactionIndex?: number | bigint,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const createKey = agent.wallet;
|
||||
const [multisigPda] = multisig.getMultisigPda({
|
||||
createKey: createKey.publicKey,
|
||||
});
|
||||
const multisigInfo = await Multisig.fromAccountAddress(
|
||||
agent.connection,
|
||||
multisigPda,
|
||||
);
|
||||
const currentTransactionIndex = Number(multisigInfo.transactionIndex);
|
||||
if (!transactionIndex) {
|
||||
transactionIndex = BigInt(currentTransactionIndex);
|
||||
} else if (typeof transactionIndex !== "bigint") {
|
||||
transactionIndex = BigInt(transactionIndex);
|
||||
}
|
||||
const multisigTx = multisig.transactions.proposalCreate({
|
||||
blockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
feePayer: agent.wallet_address,
|
||||
multisigPda,
|
||||
transactionIndex,
|
||||
creator: agent.wallet_address,
|
||||
});
|
||||
|
||||
multisigTx.sign([agent.wallet]);
|
||||
const tx = await agent.connection.sendRawTransaction(
|
||||
multisigTx.serialize(),
|
||||
);
|
||||
return tx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Transfer failed: ${error}`);
|
||||
}
|
||||
}
|
||||
91
src/tools/squads/deposit_to_treasury.ts
Normal file
91
src/tools/squads/deposit_to_treasury.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import { PublicKey, SystemProgram, Transaction } from "@solana/web3.js";
|
||||
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
|
||||
import {
|
||||
getAssociatedTokenAddress,
|
||||
createTransferInstruction,
|
||||
getMint,
|
||||
createAssociatedTokenAccountInstruction,
|
||||
} from "@solana/spl-token";
|
||||
import * as multisig from "@sqds/multisig";
|
||||
|
||||
/**
|
||||
* Transfer SOL or SPL tokens to a multisig vault.
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param amount Amount to transfer
|
||||
* @param vaultIndex Optional vault index, default is 0
|
||||
* @param mint Optional mint address for SPL tokens
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function multisig_deposit_to_treasury(
|
||||
agent: SolanaAgentKit,
|
||||
amount: number,
|
||||
vaultIndex?: number,
|
||||
mint?: PublicKey,
|
||||
): Promise<string> {
|
||||
try {
|
||||
let tx: string;
|
||||
if (!vaultIndex) {
|
||||
vaultIndex = 0;
|
||||
}
|
||||
const createKey = agent.wallet;
|
||||
const [multisigPda] = multisig.getMultisigPda({
|
||||
createKey: createKey.publicKey,
|
||||
});
|
||||
const [vaultPda] = multisig.getVaultPda({
|
||||
multisigPda,
|
||||
index: vaultIndex,
|
||||
});
|
||||
const to = vaultPda;
|
||||
if (!mint) {
|
||||
// Transfer native SOL
|
||||
const transaction = new Transaction().add(
|
||||
SystemProgram.transfer({
|
||||
fromPubkey: agent.wallet_address,
|
||||
toPubkey: to,
|
||||
lamports: amount * LAMPORTS_PER_SOL,
|
||||
}),
|
||||
);
|
||||
|
||||
tx = await agent.connection.sendTransaction(transaction, [agent.wallet]);
|
||||
} else {
|
||||
// Transfer SPL token
|
||||
const fromAta = await getAssociatedTokenAddress(
|
||||
mint,
|
||||
agent.wallet_address,
|
||||
);
|
||||
const transaction = new Transaction();
|
||||
const toAta = await getAssociatedTokenAddress(mint, to, true);
|
||||
const toTokenAccountInfo = await agent.connection.getAccountInfo(toAta);
|
||||
// Create associated token account if it doesn't exist
|
||||
if (!toTokenAccountInfo) {
|
||||
transaction.add(
|
||||
createAssociatedTokenAccountInstruction(
|
||||
agent.wallet_address,
|
||||
toAta,
|
||||
to,
|
||||
mint,
|
||||
),
|
||||
);
|
||||
}
|
||||
// Get mint info to determine decimals
|
||||
const mintInfo = await getMint(agent.connection, mint);
|
||||
const adjustedAmount = amount * Math.pow(10, mintInfo.decimals);
|
||||
|
||||
transaction.add(
|
||||
createTransferInstruction(
|
||||
fromAta,
|
||||
toAta,
|
||||
agent.wallet_address,
|
||||
adjustedAmount,
|
||||
),
|
||||
);
|
||||
|
||||
tx = await agent.connection.sendTransaction(transaction, [agent.wallet]);
|
||||
}
|
||||
|
||||
return tx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Transfer failed: ${error}`);
|
||||
}
|
||||
}
|
||||
49
src/tools/squads/execute_proposal.ts
Normal file
49
src/tools/squads/execute_proposal.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import * as multisig from "@sqds/multisig";
|
||||
const { Multisig } = multisig.accounts;
|
||||
|
||||
/**
|
||||
* Executes a transaction on the Solana blockchain using the provided agent.
|
||||
*
|
||||
* @param {SolanaAgentKit} agent - The Solana agent kit instance containing the wallet and connection.
|
||||
* @param {number | bigint} [transactionIndex] - Optional transaction index to execute. If not provided, the current transaction index from the multisig account will be used.
|
||||
* @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 multisig_execute_proposal(
|
||||
agent: SolanaAgentKit,
|
||||
transactionIndex?: number | bigint,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const createKey = agent.wallet;
|
||||
const [multisigPda] = multisig.getMultisigPda({
|
||||
createKey: createKey.publicKey,
|
||||
});
|
||||
const multisigInfo = await Multisig.fromAccountAddress(
|
||||
agent.connection,
|
||||
multisigPda,
|
||||
);
|
||||
const currentTransactionIndex = Number(multisigInfo.transactionIndex);
|
||||
if (!transactionIndex) {
|
||||
transactionIndex = BigInt(currentTransactionIndex);
|
||||
} else if (typeof transactionIndex !== "bigint") {
|
||||
transactionIndex = BigInt(transactionIndex);
|
||||
}
|
||||
const multisigTx = await multisig.transactions.vaultTransactionExecute({
|
||||
connection: agent.connection,
|
||||
blockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
feePayer: agent.wallet.publicKey,
|
||||
multisigPda,
|
||||
transactionIndex,
|
||||
member: agent.wallet.publicKey,
|
||||
});
|
||||
|
||||
multisigTx.sign([agent.wallet]);
|
||||
const tx = await agent.connection.sendRawTransaction(
|
||||
multisigTx.serialize(),
|
||||
);
|
||||
return tx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Transfer failed: ${error}`);
|
||||
}
|
||||
}
|
||||
7
src/tools/squads/index.ts
Normal file
7
src/tools/squads/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from "./create_multisig";
|
||||
export * from "./create_proposal";
|
||||
export * from "./approve_proposal";
|
||||
export * from "./deposit_to_treasury";
|
||||
export * from "./execute_proposal";
|
||||
export * from "./reject_proposal";
|
||||
export * from "./transfer_from_treasury";
|
||||
52
src/tools/squads/reject_proposal.ts
Normal file
52
src/tools/squads/reject_proposal.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import * as multisig from "@sqds/multisig";
|
||||
const { Multisig } = multisig.accounts;
|
||||
|
||||
/**
|
||||
* Rejects a proposal in a Solana multisig setup.
|
||||
*
|
||||
* @param agent - The SolanaAgentKit instance containing the wallet and connection.
|
||||
* @param transactionIndex - Optional. The index of the transaction to reject. If not provided, the current transaction index will be used.
|
||||
* @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 multisig_reject_proposal(
|
||||
agent: SolanaAgentKit,
|
||||
transactionIndex?: number | bigint,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const createKey = agent.wallet;
|
||||
const [multisigPda] = multisig.getMultisigPda({
|
||||
createKey: createKey.publicKey,
|
||||
});
|
||||
const multisigInfo = await Multisig.fromAccountAddress(
|
||||
agent.connection,
|
||||
multisigPda,
|
||||
);
|
||||
const currentTransactionIndex = Number(multisigInfo.transactionIndex);
|
||||
if (!transactionIndex) {
|
||||
transactionIndex = BigInt(currentTransactionIndex);
|
||||
} else if (typeof transactionIndex !== "bigint") {
|
||||
transactionIndex = BigInt(transactionIndex);
|
||||
}
|
||||
// const [proposalPda, proposalBump] = multisig.getProposalPda({
|
||||
// multisigPda,
|
||||
// transactionIndex,
|
||||
// });
|
||||
const multisigTx = multisig.transactions.proposalReject({
|
||||
blockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
feePayer: agent.wallet.publicKey,
|
||||
multisigPda,
|
||||
transactionIndex: transactionIndex,
|
||||
member: agent.wallet.publicKey,
|
||||
});
|
||||
|
||||
multisigTx.sign([agent.wallet]);
|
||||
const tx = await agent.connection.sendRawTransaction(
|
||||
multisigTx.serialize(),
|
||||
);
|
||||
return tx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Transfer failed: ${error}`);
|
||||
}
|
||||
}
|
||||
98
src/tools/squads/transfer_from_treasury.ts
Normal file
98
src/tools/squads/transfer_from_treasury.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import {
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
TransactionInstruction,
|
||||
TransactionMessage,
|
||||
} from "@solana/web3.js";
|
||||
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
|
||||
import {
|
||||
getAssociatedTokenAddress,
|
||||
createTransferInstruction,
|
||||
getMint,
|
||||
} from "@solana/spl-token";
|
||||
import * as multisig from "@sqds/multisig";
|
||||
const { Multisig } = multisig.accounts;
|
||||
|
||||
/**
|
||||
* Transfer SOL or SPL tokens to a recipient from a multisig vault.
|
||||
* @param agent - SolanaAgentKit instance.
|
||||
* @param amount - Amount to transfer.
|
||||
* @param to - Recipient's public key.
|
||||
* @param vaultIndex - Optional vault index, default is 0.
|
||||
* @param mint - Optional mint address for SPL tokens.
|
||||
* @returns Transaction signature.
|
||||
*/
|
||||
export async function multisig_transfer_from_treasury(
|
||||
agent: SolanaAgentKit,
|
||||
amount: number,
|
||||
to: PublicKey,
|
||||
vaultIndex: number = 0,
|
||||
mint?: PublicKey,
|
||||
): Promise<string> {
|
||||
try {
|
||||
let transferInstruction: TransactionInstruction;
|
||||
|
||||
const createKey = agent.wallet;
|
||||
const [multisigPda] = multisig.getMultisigPda({
|
||||
createKey: createKey.publicKey,
|
||||
});
|
||||
const multisigInfo = await Multisig.fromAccountAddress(
|
||||
agent.connection,
|
||||
multisigPda,
|
||||
);
|
||||
const currentTransactionIndex = Number(multisigInfo.transactionIndex);
|
||||
const transactionIndex = BigInt(currentTransactionIndex + 1);
|
||||
const [vaultPda] = multisig.getVaultPda({
|
||||
multisigPda,
|
||||
index: vaultIndex,
|
||||
});
|
||||
|
||||
if (!mint) {
|
||||
// Transfer native SOL
|
||||
transferInstruction = SystemProgram.transfer({
|
||||
fromPubkey: agent.wallet_address,
|
||||
toPubkey: to,
|
||||
lamports: amount * LAMPORTS_PER_SOL,
|
||||
});
|
||||
} else {
|
||||
// Transfer SPL token
|
||||
const fromAta = await getAssociatedTokenAddress(mint, vaultPda, true);
|
||||
const toAta = await getAssociatedTokenAddress(mint, to, true);
|
||||
const mintInfo = await getMint(agent.connection, mint);
|
||||
const adjustedAmount = amount * Math.pow(10, mintInfo.decimals);
|
||||
|
||||
transferInstruction = createTransferInstruction(
|
||||
fromAta,
|
||||
toAta,
|
||||
agent.wallet_address,
|
||||
adjustedAmount,
|
||||
);
|
||||
}
|
||||
|
||||
const transferMessage = new TransactionMessage({
|
||||
payerKey: vaultPda,
|
||||
recentBlockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
instructions: [transferInstruction],
|
||||
});
|
||||
|
||||
const multisigTx = multisig.transactions.vaultTransactionCreate({
|
||||
blockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
feePayer: agent.wallet_address,
|
||||
multisigPda,
|
||||
transactionIndex,
|
||||
creator: agent.wallet_address,
|
||||
vaultIndex: 0,
|
||||
ephemeralSigners: 0,
|
||||
transactionMessage: transferMessage,
|
||||
});
|
||||
|
||||
multisigTx.sign([agent.wallet]);
|
||||
const tx = await agent.connection.sendRawTransaction(
|
||||
multisigTx.serialize(),
|
||||
);
|
||||
return tx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Transfer failed: ${error}`);
|
||||
}
|
||||
}
|
||||
1
src/tools/tensor/index.ts
Normal file
1
src/tools/tensor/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./tensor_trade";
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import { TensorSwapSDK } from "@tensor-oss/tensorswap-sdk";
|
||||
import { PublicKey, Transaction } from "@solana/web3.js";
|
||||
import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
getMint,
|
||||
createAssociatedTokenAccountInstruction,
|
||||
} from "@solana/spl-token";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
|
||||
const MINIMUM_SOL_BALANCE = 0.003 * LAMPORTS_PER_SOL;
|
||||
|
||||
1
src/tools/tiplink/index.ts
Normal file
1
src/tools/tiplink/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./create_tiplinks";
|
||||
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";
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import {
|
||||
PublicKey,
|
||||
sendAndConfirmTransaction,
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { VoltrClient } from "@voltr/vault-sdk";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { SolanaAgentKit } from "../../agent";
|
||||
import {
|
||||
PublicKey,
|
||||
sendAndConfirmTransaction,
|
||||
@@ -1,37 +0,0 @@
|
||||
import {
|
||||
PublicKey,
|
||||
sendAndConfirmTransaction,
|
||||
Transaction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { ManifestClient } from "@cks-systems/manifest-sdk";
|
||||
|
||||
/**
|
||||
* Withdraws all funds from Manifest
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param marketId Public key for the manifest market
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function withdrawAll(
|
||||
agent: SolanaAgentKit,
|
||||
marketId: PublicKey,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const mfxClient = await ManifestClient.getClientForMarket(
|
||||
agent.connection,
|
||||
marketId,
|
||||
agent.wallet,
|
||||
);
|
||||
|
||||
const withdrawAllIx = await mfxClient.withdrawAllIx();
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
agent.connection,
|
||||
new Transaction().add(...withdrawAllIx),
|
||||
[agent.wallet],
|
||||
);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Withdraw all failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user