merge and docs

This commit is contained in:
Arihant Bansal
2025-01-04 23:41:47 +05:30
57 changed files with 22860 additions and 554 deletions

View File

@@ -2,7 +2,6 @@ import { Action } from "../types/action";
import { SolanaAgentKit } from "../agent";
import { z } from "zod";
import { PublicKey } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
import { Decimal } from "decimal.js";
import { orcaCreateSingleSidedLiquidityPool } from "../tools";

View File

@@ -0,0 +1,29 @@
import { z } from "zod";
import { SolanaAgentKit } from "..";
import { get_wallet_address } from "../tools";
import { Action } from "../types/action";
const getWalletAddressAction: Action = {
name: "GET_WALLET_ADDRESS",
similes: ["wallet address", "address", "wallet"],
description: "Get wallet address of the agent",
examples: [
[
{
input: {},
output: {
status: "success",
address: "0x1234567890abcdef",
},
explanation: "The agent's wallet address is 0x1234567890abcdef",
},
],
],
schema: z.object({}),
handler: async (agent: SolanaAgentKit) => ({
status: "success",
address: get_wallet_address(agent),
}),
};
export default getWalletAddressAction;

View File

@@ -10,6 +10,7 @@ import getTokenDataAction from "./getTokenData";
import getTPSAction from "./getTPS";
import fetchPriceAction from "./fetchPrice";
import stakeWithJupAction from "./stakeWithJup";
import stakeWithSolayerAction from "./stakeWithSolayer";
import registerDomainAction from "./registerDomain";
import lendAssetAction from "./lendAsset";
import createGibworkTaskAction from "./createGibworkTask";
@@ -26,8 +27,10 @@ import raydiumCreateCpmmAction from "./raydiumCreateCpmm";
import raydiumCreateAmmV4Action from "./raydiumCreateAmmV4";
import createOrcaSingleSidedWhirlpoolAction from "./createOrcaSingleSidedWhirlpool";
import launchPumpfunTokenAction from "./launchPumpfunToken";
import getWalletAddressAction from "./getWalletAddress";
export const ACTIONS = {
WALLET_ADDRESS_ACTION: getWalletAddressAction,
DEPLOY_TOKEN_ACTION: deployTokenAction,
BALANCE_ACTION: balanceAction,
TRANSFER_ACTION: transferAction,
@@ -40,6 +43,7 @@ export const ACTIONS = {
GET_TPS_ACTION: getTPSAction,
FETCH_PRICE_ACTION: fetchPriceAction,
STAKE_WITH_JUP_ACTION: stakeWithJupAction,
STAKE_WITH_SOLAYER_ACTION: stakeWithSolayerAction,
REGISTER_DOMAIN_ACTION: registerDomainAction,
LEND_ASSET_ACTION: lendAssetAction,
CREATE_GIBWORK_TASK_ACTION: createGibworkTaskAction,

View File

@@ -0,0 +1,60 @@
import { Action } from "../types/action";
import { SolanaAgentKit } from "../agent";
import { z } from "zod";
import { stakeWithSolayer } from "../tools";
const stakeWithSolayerAction: Action = {
name: "STAKE_WITH_SOLAYER",
similes: [
"stake sol",
"solayer sol",
"ssol",
"stake with solayer",
"solayer restaking",
"solayer staking",
"stake with sol",
"liquid staking solayer",
"get solayer sol",
"solayer sol restaking",
"solayer sol staking",
],
description:
"Stake native SOL with Solayer's restaking protocol to receive Solayer SOL (sSOL)",
examples: [
[
{
input: {
amount: 1.0,
},
output: {
status: "success",
signature: "3FgHn9...",
message: "Successfully staked 1.0 SOL for Solayer SOL (sSOL)",
},
explanation: "Stake 1.0 SOL to receive Solayer SOL (sSOL)",
},
],
],
schema: z.object({
amount: z.number().positive().describe("Amount of SOL to stake"),
}),
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
try {
const amount = input.amount as number;
const res = await stakeWithSolayer(agent, amount);
return {
status: "success",
res,
message: `Successfully staked ${amount} SOL for Solayer SOL (sSOL)`,
};
} catch (error: any) {
return {
status: "error",
message: `Solayer staking failed: ${error.message}`,
};
}
},
};
export default stakeWithSolayerAction;

View File

@@ -27,10 +27,15 @@ import {
batchOrder,
cancelAllOrders,
withdrawAll,
closePerpTradeShort,
closePerpTradeLong,
openPerpTradeShort,
openPerpTradeLong,
transfer,
getTokenDataByAddress,
getTokenDataByTicker,
stakeWithJup,
stakeWithSolayer,
sendCompressedAirdrop,
orcaCreateSingleSidedLiquidityPool,
orcaCreateCLMM,
@@ -209,6 +214,42 @@ export class SolanaAgentKit {
return withdrawAll(this, marketId);
}
async openPerpTradeLong(
args: Omit<Parameters<typeof openPerpTradeLong>[0], "agent">,
): Promise<string> {
return openPerpTradeLong({
agent: this,
...args,
});
}
async openPerpTradeShort(
args: Omit<Parameters<typeof openPerpTradeShort>[0], "agent">,
): Promise<string> {
return openPerpTradeShort({
agent: this,
...args,
});
}
async closePerpTradeShort(
args: Omit<Parameters<typeof closePerpTradeShort>[0], "agent">,
): Promise<string> {
return closePerpTradeShort({
agent: this,
...args,
});
}
async closePerpTradeLong(
args: Omit<Parameters<typeof closePerpTradeLong>[0], "agent">,
): Promise<string> {
return closePerpTradeLong({
agent: this,
...args,
});
}
async lendAssets(amount: number): Promise<string> {
return lendAsset(this, amount);
}
@@ -254,6 +295,10 @@ export class SolanaAgentKit {
return stakeWithJup(this, amount);
}
async restake(amount: number): Promise<string> {
return stakeWithSolayer(this, amount);
}
async sendCompressedAirdrop(
mintAddress: string,
amount: number,

View File

@@ -18,11 +18,13 @@ export const TOKENS = {
* Default configuration options
* @property {number} SLIPPAGE_BPS - Default slippage tolerance in basis points (300 = 3%)
* @property {number} TOKEN_DECIMALS - Default number of decimals for new tokens
* @property {number} LEVERAGE_BPS - Default leverage for trading PERP
*/
export const DEFAULT_OPTIONS = {
SLIPPAGE_BPS: 300,
TOKEN_DECIMALS: 9,
RERERRAL_FEE: 200,
LEVERAGE_BPS: 50000, // 10000 = x1, 50000 = x5, 100000 = x10, 1000000 = x100
} as const;
/**

20671
src/idls/adrena.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,8 @@
import { SolanaAgentKit } from "./agent";
import { createSolanaTools } from "./langchain";
import { createSolanaTools as createVercelAITools } from "./vercel-ai";
export { SolanaAgentKit, createSolanaTools };
export { SolanaAgentKit, createSolanaTools, createVercelAITools };
// Optional: Export types that users might need
export * from "./types";

View File

@@ -261,6 +261,114 @@ export class SolanaMintNFTTool extends Tool {
}
}
export class SolanaPerpCloseTradeTool extends Tool {
name = "solana_close_perp_trade";
description = `This tool can be used to close perpetuals trade ( It uses Adrena Protocol ).
Inputs ( input is a JSON string ):
tradeMint: string, eg "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn", "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263" etc. (optional)
price?: number, eg 100 (optional)
side: string, eg: "long" or "short"`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx =
parsedInput.side === "long"
? await this.solanaKit.closePerpTradeLong({
price: parsedInput.price,
tradeMint: new PublicKey(parsedInput.tradeMint),
})
: await this.solanaKit.closePerpTradeShort({
price: parsedInput.price,
tradeMint: new PublicKey(parsedInput.tradeMint),
});
return JSON.stringify({
status: "success",
message: "Perpetual trade closed successfully",
transaction: tx,
price: parsedInput.price,
tradeMint: new PublicKey(parsedInput.tradeMint),
side: parsedInput.side,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}
export class SolanaPerpOpenTradeTool extends Tool {
name = "solana_open_perp_trade";
description = `This tool can be used to open perpetuals trade ( It uses Adrena Protocol ).
Inputs ( input is a JSON string ):
collateralAmount: number, eg 1 or 0.01 (required)
collateralMint: string, eg "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn" or "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" etc. (optional)
tradeMint: string, eg "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn", "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263" etc. (optional)
leverage: number, eg 50000 = x5, 100000 = x10, 1000000 = x100 (optional)
price?: number, eg 100 (optional)
slippage?: number, eg 0.3 (optional)
side: string, eg: "long" or "short"`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const tx =
parsedInput.side === "long"
? await this.solanaKit.openPerpTradeLong({
price: parsedInput.price,
collateralAmount: parsedInput.collateralAmount,
collateralMint: new PublicKey(parsedInput.collateralMint),
leverage: parsedInput.leverage,
tradeMint: new PublicKey(parsedInput.tradeMint),
slippage: parsedInput.slippage,
})
: await this.solanaKit.openPerpTradeLong({
price: parsedInput.price,
collateralAmount: parsedInput.collateralAmount,
collateralMint: new PublicKey(parsedInput.collateralMint),
leverage: parsedInput.leverage,
tradeMint: new PublicKey(parsedInput.tradeMint),
slippage: parsedInput.slippage,
});
return JSON.stringify({
status: "success",
message: "Perpetual trade opened successfully",
transaction: tx,
price: parsedInput.price,
collateralAmount: parsedInput.collateralAmount,
collateralMint: new PublicKey(parsedInput.collateralMint),
leverage: parsedInput.leverage,
tradeMint: new PublicKey(parsedInput.tradeMint),
slippage: parsedInput.slippage,
side: parsedInput.side,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}
export class SolanaTradeTool extends Tool {
name = "solana_trade";
description = `This tool can be used to swap tokens to another token ( It uses Jupiter Exchange ).
@@ -862,6 +970,39 @@ export class SolanaStakeTool extends Tool {
}
}
export class SolanaRestakeTool extends Tool {
name = "solana_restake";
description = `This tool can be used to restake your SOL on Solayer to receive Solayer SOL (sSOL) as a Liquid Staking Token (LST).
Inputs:
amount: number, eg 1 or 0.01 (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input) || Number(input);
const tx = await this.solanaKit.restake(parsedInput.amount);
return JSON.stringify({
status: "success",
message: "Staked successfully",
transaction: tx,
amount: parsedInput.amount,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "UNKNOWN_ERROR",
});
}
}
}
/**
* Tool to fetch the price of a token in USDC
*/
@@ -1002,7 +1143,7 @@ export class SolanaClosePosition extends Tool {
name = "orca_close_position";
description = `Closes an existing liquidity position in an Orca Whirlpool. This function fetches the position
details using the provided mint address and closes the position with a 1% slippage.
Inputs (JSON string):
- positionMintAddress: string, the address of the position mint that represents the liquidity position.`;
@@ -1089,9 +1230,9 @@ export class SolanaOrcaCreateCLMM extends Tool {
export class SolanaOrcaCreateSingleSideLiquidityPool extends Tool {
name = "orca_create_single_sided_liquidity_pool";
description = `Create a single-sided liquidity pool on Orca, the most efficient and capital-optimized CLMM platform on Solana.
description = `Create a single-sided liquidity pool on Orca, the most efficient and capital-optimized CLMM platform on Solana.
This function initializes a single-sided liquidity pool, ideal for community driven project, fair launches, and fundraising. Minimize price impact by setting a narrow price range.
This function initializes a single-sided liquidity pool, ideal for community driven project, fair launches, and fundraising. Minimize price impact by setting a narrow price range.
Inputs (JSON string):
- depositTokenAmount: number, in units of the deposit token including decimals, e.g., 1000000000 (required).
@@ -2001,6 +2142,7 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
new SolanaLendAssetTool(solanaKit),
new SolanaTPSCalculatorTool(solanaKit),
new SolanaStakeTool(solanaKit),
new SolanaRestakeTool(solanaKit),
new SolanaFetchPriceTool(solanaKit),
new SolanaGetDomainTool(solanaKit),
new SolanaTokenDataTool(solanaKit),
@@ -2035,5 +2177,7 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
new SolanaCancelNFTListingTool(solanaKit),
new SolanaFetchTokenReportSummaryTool(solanaKit),
new SolanaFetchTokenDetailedReportTool(solanaKit),
new SolanaPerpOpenTradeTool(solanaKit),
new SolanaPerpCloseTradeTool(solanaKit),
];
}

View 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]);
}

View File

@@ -16,6 +16,7 @@ export async function getMainAllDomainsDomain(
mainDomain = await _getFavoriteDomain(agent.connection, owner);
return mainDomain.stale ? null : mainDomain.reverse;
} catch (error: any) {
console.error(error);
return null;
}
}

View File

@@ -29,7 +29,8 @@ export async function getPrimaryDomain(
);
}
return reverse;
} catch (error) {
} catch (error: any) {
console.error(error);
throw new Error(
`Failed to get primary domain for account: ${account.toBase58()}`,
);

View File

@@ -0,0 +1,10 @@
import { SolanaAgentKit } from "../agent";
/**
* Get the agents wallet address
* @param agent - SolanaAgentKit instance
* @returns string
*/
export function get_wallet_address(agent: SolanaAgentKit) {
return agent.wallet_address.toBase58();
}

View File

@@ -1,3 +1,4 @@
export * from "./adrena_perp_trading";
export * from "./batch_order";
export * from "./cancel_all_orders";
export * from "./create_gibwork_task";
@@ -16,6 +17,7 @@ export * from "./get_owned_domains_for_tld";
export * from "./get_primary_domain";
export * from "./get_token_data";
export * from "./get_tps";
export * from "./get_wallet_address";
export * from "./launch_pumpfun_token";
export * from "./lend";
export * from "./limit_order";
@@ -40,6 +42,7 @@ export * from "./rock_paper_scissor";
export * from "./rugcheck";
export * from "./send_compressed_airdrop";
export * from "./stake_with_jup";
export * from "./stake_with_solayer";
export * from "./tensor_trade";
export * from "./trade";
export * from "./transfer";

View File

@@ -24,7 +24,8 @@ export async function resolveSolDomain(
try {
return await resolve(agent.connection, domain);
} catch (error) {
} catch (error: any) {
console.error(error);
throw new Error(`Failed to resolve domain: ${domain}`);
}
}

View File

@@ -88,6 +88,7 @@ export async function sendCompressedAirdrop(
agent.wallet.publicKey,
);
} catch (error) {
console.error(error);
throw new Error(
"Source token account not found and failed to create it. Please add funds to your wallet and try again.",
);

View File

@@ -0,0 +1,64 @@
import { VersionedTransaction } from "@solana/web3.js";
import { SolanaAgentKit } from "../index";
/**
* Stake SOL with Solayer
* @param agent SolanaAgentKit instance
* @param amount Amount of SOL to stake
* @returns Transaction signature
*/
export async function stakeWithSolayer(
agent: SolanaAgentKit,
amount: number,
): Promise<string> {
try {
const response = await fetch(
`https://app.solayer.org/api/action/restake/ssol?amount=${amount}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
account: agent.wallet.publicKey.toBase58(),
}),
},
);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || "Staking request failed");
}
const data = await response.json();
// Deserialize and prepare transaction
const txn = VersionedTransaction.deserialize(
Buffer.from(data.transaction, "base64"),
);
// Update blockhash
const { blockhash } = await agent.connection.getLatestBlockhash();
txn.message.recentBlockhash = blockhash;
// Sign and send transaction
txn.sign([agent.wallet]);
const signature = await agent.connection.sendTransaction(txn, {
preflightCommitment: "confirmed",
maxRetries: 3,
});
// Wait for confirmation
const latestBlockhash = await agent.connection.getLatestBlockhash();
await agent.connection.confirmTransaction({
signature,
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
});
return signature;
} catch (error: any) {
console.error(error);
throw new Error(`Solayer sSOL staking failed: ${error.message}`);
}
}

View File

@@ -33,6 +33,7 @@ export async function listNFTForSale(
throw new Error(`You don't own this NFT (${nftMint.toString()})`);
}
} catch (error: any) {
console.error(error);
throw new Error(
`No token account found for mint ${nftMint.toString()}. Make sure you own this NFT.`,
);

220
src/utils/AdrenaClient.ts Normal file
View File

@@ -0,0 +1,220 @@
import { Connection, PublicKey } from "@solana/web3.js";
import { SolanaAgentKit } from "../index";
import { AnchorProvider, IdlAccounts, Program } from "@coral-xyz/anchor";
import { Adrena, IDL as ADRENA_IDL } from "../idls/adrena";
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
import {
ASSOCIATED_TOKEN_PROGRAM_ID,
createAssociatedTokenAccountInstruction,
TOKEN_PROGRAM_ID,
} from "@solana/spl-token";
import { TOKENS } from "../constants";
export type AdrenaProgram = Program<Adrena>;
type Accounts = IdlAccounts<Adrena>;
export type Cortex = Accounts["cortex"];
export type Custody = Accounts["custody"] & { pubkey: PublicKey };
export type Pool = Accounts["pool"];
export default class AdrenaClient {
public static programId = new PublicKey(
"13gDzEXCdocbj8iAiqrScGo47NiSuYENGsRqi3SEAwet",
);
constructor(
public program: AdrenaProgram,
public mainPool: Pool,
public cortex: Cortex,
public custodies: Custody[],
) {}
public static mainPool = new PublicKey(
"4bQRutgDJs6vuh6ZcWaPVXiQaBzbHketjbCDjL4oRN34",
);
public static async load(agent: SolanaAgentKit): Promise<AdrenaClient> {
const program = new Program<Adrena>(
ADRENA_IDL,
AdrenaClient.programId,
new AnchorProvider(agent.connection, new NodeWallet(agent.wallet), {
commitment: "processed",
skipPreflight: true,
}),
);
const [cortex, mainPool] = await Promise.all([
program.account.cortex.fetch(AdrenaClient.cortex),
program.account.pool.fetch(AdrenaClient.mainPool),
]);
const custodiesAddresses = mainPool.custodies.filter(
(custody) => !custody.equals(PublicKey.default),
);
const custodies =
await program.account.custody.fetchMultiple(custodiesAddresses);
if (!custodies.length || custodies.some((c) => c === null)) {
throw new Error("Custodies not found");
}
return new AdrenaClient(
program,
mainPool,
cortex,
(custodies as Custody[]).map((c, i) => ({
...c,
pubkey: custodiesAddresses[i],
})),
);
}
public static findCustodyAddress(mint: PublicKey): PublicKey {
return PublicKey.findProgramAddressSync(
[
Buffer.from("custody"),
AdrenaClient.mainPool.toBuffer(),
mint.toBuffer(),
],
AdrenaClient.programId,
)[0];
}
public static findCustodyTokenAccountAddress(mint: PublicKey) {
return PublicKey.findProgramAddressSync(
[
Buffer.from("custody_token_account"),
AdrenaClient.mainPool.toBuffer(),
mint.toBuffer(),
],
AdrenaClient.programId,
)[0];
}
public static findPositionAddress(
owner: PublicKey,
custody: PublicKey,
side: "long" | "short",
) {
return PublicKey.findProgramAddressSync(
[
Buffer.from("position"),
owner.toBuffer(),
AdrenaClient.mainPool.toBuffer(),
custody.toBuffer(),
Buffer.from([
{
long: 1,
short: 2,
}[side],
]),
],
AdrenaClient.programId,
)[0];
}
public static cortex = PublicKey.findProgramAddressSync(
[Buffer.from("cortex")],
AdrenaClient.programId,
)[0];
public static lpTokenMint = PublicKey.findProgramAddressSync(
[Buffer.from("lp_token_mint"), AdrenaClient.mainPool.toBuffer()],
AdrenaClient.programId,
)[0];
public static lmTokenMint = PublicKey.findProgramAddressSync(
[Buffer.from("lm_token_mint")],
AdrenaClient.programId,
)[0];
public static getStakingPda(stakedTokenMint: PublicKey) {
return PublicKey.findProgramAddressSync(
[Buffer.from("staking"), stakedTokenMint.toBuffer()],
AdrenaClient.programId,
)[0];
}
public static lmStaking = AdrenaClient.getStakingPda(
AdrenaClient.lmTokenMint,
);
public static lpStaking = AdrenaClient.getStakingPda(
AdrenaClient.lpTokenMint,
);
public static transferAuthority = PublicKey.findProgramAddressSync(
[Buffer.from("transfer_authority")],
AdrenaClient.programId,
)[0];
public static findATAAddressSync(
wallet: PublicKey,
mint: PublicKey,
): PublicKey {
return PublicKey.findProgramAddressSync(
[wallet.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer()],
ASSOCIATED_TOKEN_PROGRAM_ID,
)[0];
}
public getCustodyByMint(mint: PublicKey): Custody {
const custody = this.custodies.find((custody) => custody.mint.equals(mint));
if (!custody) {
throw new Error(`Cannot find custody for mint ${mint.toBase58()}`);
}
return custody;
}
public static getUserProfilePda(wallet: PublicKey) {
return PublicKey.findProgramAddressSync(
[Buffer.from("user_profile"), wallet.toBuffer()],
AdrenaClient.programId,
)[0];
}
public static stakingRewardTokenMint = TOKENS.USDC;
public static getStakingRewardTokenVaultPda(stakingPda: PublicKey) {
return PublicKey.findProgramAddressSync(
[Buffer.from("staking_reward_token_vault"), stakingPda.toBuffer()],
AdrenaClient.programId,
)[0];
}
public static lmStakingRewardTokenVault =
AdrenaClient.getStakingRewardTokenVaultPda(AdrenaClient.lmStaking);
public static lpStakingRewardTokenVault =
AdrenaClient.getStakingRewardTokenVaultPda(AdrenaClient.lpStaking);
public static async isAccountInitialized(
connection: Connection,
address: PublicKey,
): Promise<boolean> {
return !!(await connection.getAccountInfo(address));
}
public static createATAInstruction({
ataAddress,
mint,
owner,
payer = owner,
}: {
ataAddress: PublicKey;
mint: PublicKey;
owner: PublicKey;
payer?: PublicKey;
}) {
return createAssociatedTokenAccountInstruction(
payer,
ataAddress,
owner,
mint,
);
}
}

25
src/vercel-ai/index.ts Normal file
View File

@@ -0,0 +1,25 @@
import { tool, type CoreTool } from "ai";
import { SolanaAgentKit } from "../agent";
import { executeAction } from "../utils/actionExecutor";
import { ACTIONS } from "../actions";
export function createSolanaTools(
solanaAgentKit: SolanaAgentKit,
): Record<string, CoreTool> {
const tools: Record<string, CoreTool> = {};
const actionKeys = Object.keys(ACTIONS);
for (const key of actionKeys) {
const action = ACTIONS[key as keyof typeof ACTIONS];
tools[key] = tool({
// @ts-expect-error Value matches type however TS still shows error
id: action.name,
description: action.description,
parameters: action.schema,
execute: async (params) =>
await executeAction(action, solanaAgentKit, params),
});
}
return tools;
}