mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-26 23:26:45 +00:00
Merge branch 'main' into feature/reclaim_rent
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -87,7 +86,7 @@ const createOrcaSingleSidedWhirlpoolAction: Action = {
|
||||
const otherTokenMint = new PublicKey(input.otherTokenMint);
|
||||
const initialPrice = new Decimal(input.initialPrice);
|
||||
const maxPrice = new Decimal(input.maxPrice);
|
||||
const feeTier = input.feeTier
|
||||
const feeTier = input.feeTier;
|
||||
|
||||
// Create the whirlpool
|
||||
const signature = await orcaCreateSingleSidedLiquidityPool(
|
||||
|
||||
29
src/actions/getWalletAddress.ts
Normal file
29
src/actions/getWalletAddress.ts
Normal 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;
|
||||
@@ -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,36 +27,40 @@ import raydiumCreateCpmmAction from "./raydiumCreateCpmm";
|
||||
import raydiumCreateAmmV4Action from "./raydiumCreateAmmV4";
|
||||
import createOrcaSingleSidedWhirlpoolAction from "./createOrcaSingleSidedWhirlpool";
|
||||
import launchPumpfunTokenAction from "./launchPumpfunToken";
|
||||
import getWalletAddressAction from "./getWalletAddress";
|
||||
|
||||
export const ACTIONS = {
|
||||
"DEPLOY_TOKEN_ACTION" : deployTokenAction,
|
||||
"BALANCE_ACTION" : balanceAction,
|
||||
"TRANSFER_ACTION" : transferAction,
|
||||
"DEPLOY_COLLECTION_ACTION" : deployCollectionAction,
|
||||
"MINT_NFT_ACTION" : mintNFTAction,
|
||||
"TRADE_ACTION" : tradeAction,
|
||||
"REQUEST_FUNDS_ACTION" : requestFundsAction,
|
||||
"RESOLVE_DOMAIN_ACTION" : resolveDomainAction,
|
||||
"GET_TOKEN_DATA_ACTION" : getTokenDataAction,
|
||||
"GET_TPS_ACTION" : getTPSAction,
|
||||
"FETCH_PRICE_ACTION" : fetchPriceAction,
|
||||
"STAKE_WITH_JUP_ACTION" : stakeWithJupAction,
|
||||
"REGISTER_DOMAIN_ACTION" : registerDomainAction,
|
||||
"LEND_ASSET_ACTION" : lendAssetAction,
|
||||
"CREATE_GIBWORK_TASK_ACTION" : createGibworkTaskAction,
|
||||
"RESOLVE_SOL_DOMAIN_ACTION" : resolveSolDomainAction,
|
||||
"PYTH_FETCH_PRICE_ACTION" : pythFetchPriceAction,
|
||||
"GET_OWNED_DOMAINS_FOR_TLD_ACTION" : getOwnedDomainsForTLDAction,
|
||||
"GET_PRIMARY_DOMAIN_ACTION" : getPrimaryDomainAction,
|
||||
"GET_ALL_DOMAINS_TLDS_ACTION" : getAllDomainsTLDsAction,
|
||||
"GET_OWNED_ALL_DOMAINS_ACTION" : getOwnedAllDomainsAction,
|
||||
"CREATE_IMAGE_ACTION" : createImageAction,
|
||||
"GET_MAIN_ALL_DOMAINS_DOMAIN_ACTION" : getMainAllDomainsDomainAction,
|
||||
"GET_ALL_REGISTERED_ALL_DOMAINS_ACTION" : getAllRegisteredAllDomainsAction,
|
||||
"RAYDIUM_CREATE_CPMM_ACTION" : raydiumCreateCpmmAction,
|
||||
"RAYDIUM_CREATE_AMM_V4_ACTION" : raydiumCreateAmmV4Action,
|
||||
"CREATE_ORCA_SINGLE_SIDED_WHIRLPOOL_ACTION" : createOrcaSingleSidedWhirlpoolAction,
|
||||
"LAUNCH_PUMPFUN_TOKEN_ACTION" : launchPumpfunTokenAction,
|
||||
WALLET_ADDRESS_ACTION: getWalletAddressAction,
|
||||
DEPLOY_TOKEN_ACTION: deployTokenAction,
|
||||
BALANCE_ACTION: balanceAction,
|
||||
TRANSFER_ACTION: transferAction,
|
||||
DEPLOY_COLLECTION_ACTION: deployCollectionAction,
|
||||
MINT_NFT_ACTION: mintNFTAction,
|
||||
TRADE_ACTION: tradeAction,
|
||||
REQUEST_FUNDS_ACTION: requestFundsAction,
|
||||
RESOLVE_DOMAIN_ACTION: resolveDomainAction,
|
||||
GET_TOKEN_DATA_ACTION: getTokenDataAction,
|
||||
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,
|
||||
RESOLVE_SOL_DOMAIN_ACTION: resolveSolDomainAction,
|
||||
PYTH_FETCH_PRICE_ACTION: pythFetchPriceAction,
|
||||
GET_OWNED_DOMAINS_FOR_TLD_ACTION: getOwnedDomainsForTLDAction,
|
||||
GET_PRIMARY_DOMAIN_ACTION: getPrimaryDomainAction,
|
||||
GET_ALL_DOMAINS_TLDS_ACTION: getAllDomainsTLDsAction,
|
||||
GET_OWNED_ALL_DOMAINS_ACTION: getOwnedAllDomainsAction,
|
||||
CREATE_IMAGE_ACTION: createImageAction,
|
||||
GET_MAIN_ALL_DOMAINS_DOMAIN_ACTION: getMainAllDomainsDomainAction,
|
||||
GET_ALL_REGISTERED_ALL_DOMAINS_ACTION: getAllRegisteredAllDomainsAction,
|
||||
RAYDIUM_CREATE_CPMM_ACTION: raydiumCreateCpmmAction,
|
||||
RAYDIUM_CREATE_AMM_V4_ACTION: raydiumCreateAmmV4Action,
|
||||
CREATE_ORCA_SINGLE_SIDED_WHIRLPOOL_ACTION:
|
||||
createOrcaSingleSidedWhirlpoolAction,
|
||||
LAUNCH_PUMPFUN_TOKEN_ACTION: launchPumpfunTokenAction,
|
||||
};
|
||||
|
||||
export type { Action, ActionExample, Handler } from "../types/action";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { pythFetchPrice } from "../tools";
|
||||
import { fetchPythPrice, fetchPythPriceFeedID } from "../tools";
|
||||
|
||||
const pythFetchPriceAction: Action = {
|
||||
name: "PYTH_FETCH_PRICE",
|
||||
@@ -18,7 +18,7 @@ const pythFetchPriceAction: Action = {
|
||||
[
|
||||
{
|
||||
input: {
|
||||
priceFeedId: "Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD", // SOL/USD price feed
|
||||
tokenSymbol: "SOL", // SOL/USD price feed
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
@@ -30,15 +30,17 @@ const pythFetchPriceAction: Action = {
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
priceFeedId: z
|
||||
tokenSymbol: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe("The Pyth price feed ID to fetch the price from"),
|
||||
.describe("The token symbol to fetch the price for"),
|
||||
}),
|
||||
handler: async (_agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const priceFeedId = input.tokenId as string;
|
||||
const priceStr = await pythFetchPrice(priceFeedId);
|
||||
const priceFeedId = await fetchPythPriceFeedID(input.tokenSymbol);
|
||||
|
||||
const priceStr = await fetchPythPrice(priceFeedId);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
price: priceStr,
|
||||
|
||||
60
src/actions/stakeWithSolayer.ts
Normal file
60
src/actions/stakeWithSolayer.ts
Normal 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;
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
import bs58 from "bs58";
|
||||
import Decimal from "decimal.js";
|
||||
import { DEFAULT_OPTIONS } from "../constants";
|
||||
import { Config } from "../types";
|
||||
import { Config, TokenCheck } from "../types";
|
||||
import {
|
||||
deploy_collection,
|
||||
deploy_token,
|
||||
@@ -23,12 +24,18 @@ import {
|
||||
request_faucet_funds,
|
||||
trade,
|
||||
limitOrder,
|
||||
batchOrder,
|
||||
cancelAllOrders,
|
||||
withdrawAll,
|
||||
closePerpTradeShort,
|
||||
closePerpTradeLong,
|
||||
openPerpTradeShort,
|
||||
openPerpTradeLong,
|
||||
transfer,
|
||||
getTokenDataByAddress,
|
||||
getTokenDataByTicker,
|
||||
stakeWithJup,
|
||||
stakeWithSolayer,
|
||||
sendCompressedAirdrop,
|
||||
orcaCreateSingleSidedLiquidityPool,
|
||||
orcaCreateCLMM,
|
||||
@@ -36,7 +43,6 @@ import {
|
||||
orcaOpenSingleSidedPosition,
|
||||
FEE_TIERS,
|
||||
fetchPrice,
|
||||
pythFetchPrice,
|
||||
getAllDomainsTLDs,
|
||||
getAllRegisteredAllDomains,
|
||||
getOwnedDomainsForTLD,
|
||||
@@ -51,8 +57,11 @@ import {
|
||||
listNFTForSale,
|
||||
cancelListing,
|
||||
closeEmptyTokenAccounts,
|
||||
fetchTokenReportSummary,
|
||||
fetchTokenDetailedReport,
|
||||
fetchPythPrice,
|
||||
fetchPythPriceFeedID,
|
||||
} from "../tools";
|
||||
|
||||
import {
|
||||
CollectionDeployment,
|
||||
CollectionOptions,
|
||||
@@ -61,8 +70,8 @@ import {
|
||||
MintCollectionNFTResponse,
|
||||
PumpfunLaunchResponse,
|
||||
PumpFunTokenOptions,
|
||||
OrderParams,
|
||||
} from "../types";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
|
||||
/**
|
||||
* Main class for interacting with Solana blockchain
|
||||
@@ -191,6 +200,13 @@ export class SolanaAgentKit {
|
||||
return limitOrder(this, marketId, quantity, side, price);
|
||||
}
|
||||
|
||||
async batchOrder(
|
||||
marketId: PublicKey,
|
||||
orders: OrderParams[],
|
||||
): Promise<string> {
|
||||
return batchOrder(this, marketId, orders);
|
||||
}
|
||||
|
||||
async cancelAllOrders(marketId: PublicKey): Promise<string> {
|
||||
return cancelAllOrders(this, marketId);
|
||||
}
|
||||
@@ -199,6 +215,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);
|
||||
}
|
||||
@@ -244,6 +296,10 @@ export class SolanaAgentKit {
|
||||
return stakeWithJup(this, amount);
|
||||
}
|
||||
|
||||
async restake(amount: number): Promise<string> {
|
||||
return stakeWithSolayer(this, amount);
|
||||
}
|
||||
|
||||
async sendCompressedAirdrop(
|
||||
mintAddress: string,
|
||||
amount: number,
|
||||
@@ -432,8 +488,12 @@ export class SolanaAgentKit {
|
||||
return manifestCreateMarket(this, baseMint, quoteMint);
|
||||
}
|
||||
|
||||
async pythFetchPrice(priceFeedID: string): Promise<string> {
|
||||
return pythFetchPrice(priceFeedID);
|
||||
async getPythPriceFeedID(tokenSymbol: string): Promise<string> {
|
||||
return fetchPythPriceFeedID(tokenSymbol);
|
||||
}
|
||||
|
||||
async getPythPrice(priceFeedID: string): Promise<string> {
|
||||
return fetchPythPrice(priceFeedID);
|
||||
}
|
||||
|
||||
async createGibworkTask(
|
||||
@@ -480,5 +540,12 @@ export class SolanaAgentKit {
|
||||
size: number;
|
||||
}> {
|
||||
return closeEmptyTokenAccounts(this);
|
||||
|
||||
async fetchTokenReportSummary(mint: string): Promise<TokenCheck> {
|
||||
return fetchTokenReportSummary(mint);
|
||||
}
|
||||
|
||||
async fetchTokenDetailedReport(mint: string): Promise<TokenCheck> {
|
||||
return fetchTokenDetailedReport(mint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
20671
src/idls/adrena.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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";
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
import Decimal from "decimal.js";
|
||||
import { Tool } from "langchain/tools";
|
||||
import {
|
||||
GibworkCreateTaskReponse,
|
||||
OrderParams,
|
||||
PythFetchPriceResponse,
|
||||
SolanaAgentKit,
|
||||
} from "../index";
|
||||
import { create_image } from "../tools/create_image";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
import { FEE_TIERS } from "../tools";
|
||||
import { create_image, FEE_TIERS, generateOrdersfromPattern } from "../tools";
|
||||
|
||||
export class SolanaBalanceTool extends Tool {
|
||||
name = "solana_balance";
|
||||
@@ -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 ).
|
||||
@@ -310,6 +418,8 @@ export class SolanaLimitOrderTool extends Tool {
|
||||
name = "solana_limit_order";
|
||||
description = `This tool can be used to place limit orders using Manifest.
|
||||
|
||||
Do not allow users to place multiple orders with this instruction, use solana_batch_order instead.
|
||||
|
||||
Inputs ( input is a JSON string ):
|
||||
marketId: PublicKey, eg "ENhU8LsaR7vDD2G1CsWcsuSGNrih9Cv5WZEk7q9kPapQ" for SOL/USDC (required)
|
||||
quantity: number, eg 1 or 0.01 (required)
|
||||
@@ -350,6 +460,98 @@ export class SolanaLimitOrderTool extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaBatchOrderTool extends Tool {
|
||||
name = "solana_batch_order";
|
||||
description = `Places multiple limit orders in one transaction using Manifest. Submit orders either as a list or pattern:
|
||||
|
||||
1. List format:
|
||||
{
|
||||
"marketId": "ENhU8LsaR7vDD2G1CsWcsuSGNrih9Cv5WZEk7q9kPapQ",
|
||||
"orders": [
|
||||
{ "quantity": 1, "side": "Buy", "price": 200 },
|
||||
{ "quantity": 0.5, "side": "Sell", "price": 205 }
|
||||
]
|
||||
}
|
||||
|
||||
2. Pattern format:
|
||||
{
|
||||
"marketId": "ENhU8LsaR7vDD2G1CsWcsuSGNrih9Cv5WZEk7q9kPapQ",
|
||||
"pattern": {
|
||||
"side": "Buy",
|
||||
"totalQuantity": 100,
|
||||
"priceRange": { "max": 1.0 },
|
||||
"spacing": { "type": "percentage", "value": 1 },
|
||||
"numberOfOrders": 5
|
||||
}
|
||||
}
|
||||
|
||||
Examples:
|
||||
- "Place 5 buy orders totaling 100 tokens, 1% apart below $1"
|
||||
- "Create 3 sell orders of 10 tokens each between $50-$55"
|
||||
- "Place buy orders worth 50 tokens, $0.10 spacing from $0.80"
|
||||
|
||||
Important: All orders must be in one transaction. Combine buy and sell orders into a single pattern or list. Never break the orders down to individual buy or sell orders.`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = JSON.parse(input);
|
||||
let ordersToPlace: OrderParams[] = [];
|
||||
|
||||
if (!parsedInput.marketId) {
|
||||
throw new Error("Market ID is required");
|
||||
}
|
||||
|
||||
if (parsedInput.pattern) {
|
||||
ordersToPlace = generateOrdersfromPattern(parsedInput.pattern);
|
||||
} else if (Array.isArray(parsedInput.orders)) {
|
||||
ordersToPlace = parsedInput.orders;
|
||||
} else {
|
||||
throw new Error("Either pattern or orders array is required");
|
||||
}
|
||||
|
||||
if (ordersToPlace.length === 0) {
|
||||
throw new Error("No orders generated or provided");
|
||||
}
|
||||
|
||||
ordersToPlace.forEach((order: OrderParams, index: number) => {
|
||||
if (!order.quantity || !order.side || !order.price) {
|
||||
throw new Error(
|
||||
`Invalid order at index ${index}: quantity, side, and price are required`,
|
||||
);
|
||||
}
|
||||
if (order.side !== "Buy" && order.side !== "Sell") {
|
||||
throw new Error(
|
||||
`Invalid side at index ${index}: must be "Buy" or "Sell"`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const tx = await this.solanaKit.batchOrder(
|
||||
new PublicKey(parsedInput.marketId),
|
||||
parsedInput.orders,
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Batch order executed successfully",
|
||||
transaction: tx,
|
||||
marketId: parsedInput.marketId,
|
||||
orders: parsedInput.orders,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaCancelAllOrdersTool extends Tool {
|
||||
name = "solana_cancel_all_orders";
|
||||
description = `This tool can be used to cancel all orders from a Manifest market.
|
||||
@@ -768,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
|
||||
*/
|
||||
@@ -908,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.`;
|
||||
|
||||
@@ -995,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).
|
||||
@@ -1403,7 +1638,7 @@ export class SolanaPythFetchPrice extends Tool {
|
||||
description = `Fetch the price of a given price feed from Pyth's Hermes service
|
||||
|
||||
Inputs:
|
||||
priceFeedID: string, the price feed ID, e.g., "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43" for BTC/USD`;
|
||||
tokenSymbol: string, e.g., BTC for bitcoin`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
@@ -1411,17 +1646,21 @@ export class SolanaPythFetchPrice extends Tool {
|
||||
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const price = await this.solanaKit.pythFetchPrice(input);
|
||||
const priceFeedID = await this.solanaKit.getPythPriceFeedID(input);
|
||||
const price = await this.solanaKit.getPythPrice(priceFeedID);
|
||||
|
||||
const response: PythFetchPriceResponse = {
|
||||
status: "success",
|
||||
priceFeedID: input,
|
||||
tokenSymbol: input,
|
||||
priceFeedID,
|
||||
price,
|
||||
};
|
||||
|
||||
return JSON.stringify(response);
|
||||
} catch (error: any) {
|
||||
const response: PythFetchPriceResponse = {
|
||||
status: "error",
|
||||
priceFeedID: input,
|
||||
tokenSymbol: input,
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
};
|
||||
@@ -1825,29 +2064,62 @@ export class SolanaCancelNFTListingTool extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
export class CloseEmptyTokenAccounts extends Tool {
|
||||
name = "close_empty_token_accounts";
|
||||
description = `Close all empty spl-token accounts and reclaim the rent`;
|
||||
export class SolanaFetchTokenReportSummaryTool extends Tool {
|
||||
name = "solana_fetch_token_report_summary";
|
||||
description = `Fetches a summary report for a specific token from RugCheck.
|
||||
Inputs:
|
||||
- mint: string, the mint address of the token, e.g., "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" (required).`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(): Promise<string> {
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const { signature, size } =
|
||||
await this.solanaKit.closeEmptyTokenAccounts();
|
||||
const mint = input.trim();
|
||||
const report = await this.solanaKit.fetchTokenReportSummary(mint);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: `${size} accounts closed successfully. ${size === 48 ? "48 accounts can be closed in a single transaction try again to close more accounts" : ""}`,
|
||||
signature,
|
||||
message: "Token report summary fetched successfully",
|
||||
report,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
code: error.code || "FETCH_TOKEN_REPORT_SUMMARY_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaFetchTokenDetailedReportTool extends Tool {
|
||||
name = "solana_fetch_token_detailed_report";
|
||||
description = `Fetches a detailed report for a specific token from RugCheck.
|
||||
Inputs:
|
||||
- mint: string, the mint address of the token, e.g., "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" (required).`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const mint = input.trim();
|
||||
const detailedReport =
|
||||
await this.solanaKit.fetchTokenDetailedReport(mint);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Detailed token report fetched successfully",
|
||||
report: detailedReport,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "FETCH_TOKEN_DETAILED_REPORT_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1870,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),
|
||||
@@ -1881,6 +2154,7 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
new SolanaOpenbookCreateMarket(solanaKit),
|
||||
new SolanaManifestCreateMarket(solanaKit),
|
||||
new SolanaLimitOrderTool(solanaKit),
|
||||
new SolanaBatchOrderTool(solanaKit),
|
||||
new SolanaCancelAllOrdersTool(solanaKit),
|
||||
new SolanaWithdrawAllTool(solanaKit),
|
||||
new SolanaClosePosition(solanaKit),
|
||||
@@ -1901,6 +2175,10 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
new SolanaTipLinkTool(solanaKit),
|
||||
new SolanaListNFTForSaleTool(solanaKit),
|
||||
new SolanaCancelNFTListingTool(solanaKit),
|
||||
new CloseEmptyTokenAccounts(solanaKit),
|
||||
new SolanaCloseEmptyTokenAccounts(solanaKit),
|
||||
new SolanaFetchTokenReportSummaryTool(solanaKit),
|
||||
new SolanaFetchTokenDetailedReportTool(solanaKit),
|
||||
new SolanaPerpOpenTradeTool(solanaKit),
|
||||
new SolanaPerpCloseTradeTool(solanaKit),
|
||||
];
|
||||
}
|
||||
|
||||
506
src/tools/adrena_perp_trading.ts
Normal file
506
src/tools/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]);
|
||||
}
|
||||
152
src/tools/batch_order.ts
Normal file
152
src/tools/batch_order.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import {
|
||||
PublicKey,
|
||||
Transaction,
|
||||
sendAndConfirmTransaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import {
|
||||
ManifestClient,
|
||||
WrapperPlaceOrderParamsExternal,
|
||||
OrderType,
|
||||
} from "@cks-systems/manifest-sdk";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { BatchOrderPattern, OrderParams } from "../types";
|
||||
|
||||
/**
|
||||
* Generates an array of orders based on the specified pattern
|
||||
*/
|
||||
export function generateOrdersfromPattern(
|
||||
pattern: BatchOrderPattern,
|
||||
): OrderParams[] {
|
||||
const orders: OrderParams[] = [];
|
||||
|
||||
// Random number of orders if not specified, max of 8
|
||||
const numOrders = pattern.numberOfOrders || Math.ceil(Math.random() * 8);
|
||||
|
||||
// Calculate price points
|
||||
const prices: number[] = [];
|
||||
if (pattern.priceRange) {
|
||||
const { min, max } = pattern.priceRange;
|
||||
if (min && max) {
|
||||
// Generate evenly spaced prices
|
||||
for (let i = 0; i < numOrders; i++) {
|
||||
if (pattern.spacing?.type === "percentage") {
|
||||
const factor = 1 + pattern.spacing.value / 100;
|
||||
prices.push(min * Math.pow(factor, i));
|
||||
} else {
|
||||
const step = (max - min) / (numOrders - 1);
|
||||
prices.push(min + step * i);
|
||||
}
|
||||
}
|
||||
} else if (min) {
|
||||
// Generate prices starting from min with specified spacing
|
||||
for (let i = 0; i < numOrders; i++) {
|
||||
if (pattern.spacing?.type === "percentage") {
|
||||
const factor = 1 + pattern.spacing.value / 100;
|
||||
prices.push(min * Math.pow(factor, i));
|
||||
} else {
|
||||
prices.push(min + (pattern.spacing?.value || 0.01) * i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate quantities
|
||||
let quantities: number[] = [];
|
||||
if (pattern.totalQuantity) {
|
||||
const individualQty = pattern.totalQuantity / numOrders;
|
||||
quantities = Array(numOrders).fill(individualQty);
|
||||
} else if (pattern.individualQuantity) {
|
||||
quantities = Array(numOrders).fill(pattern.individualQuantity);
|
||||
}
|
||||
|
||||
// Generate orders
|
||||
for (let i = 0; i < numOrders; i++) {
|
||||
orders.push({
|
||||
side: pattern.side,
|
||||
price: prices[i],
|
||||
quantity: quantities[i],
|
||||
});
|
||||
}
|
||||
|
||||
return orders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that sell orders are not priced below buy orders
|
||||
* @param orders Array of order parameters to validate
|
||||
* @throws Error if orders are crossed
|
||||
*/
|
||||
function validateNoCrossedOrders(orders: OrderParams[]): void {
|
||||
// Find lowest sell and highest buy prices
|
||||
let lowestSell = Number.MAX_SAFE_INTEGER;
|
||||
let highestBuy = 0;
|
||||
|
||||
orders.forEach((order) => {
|
||||
if (order.side === "Sell" && order.price < lowestSell) {
|
||||
lowestSell = order.price;
|
||||
}
|
||||
if (order.side === "Buy" && order.price > highestBuy) {
|
||||
highestBuy = order.price;
|
||||
}
|
||||
});
|
||||
|
||||
// Check if orders cross
|
||||
if (lowestSell <= highestBuy) {
|
||||
throw new Error(
|
||||
`Invalid order prices: Sell order at ${lowestSell} is lower than or equal to Buy order at ${highestBuy}. Orders cannot cross.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Place batch 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 batchOrder(
|
||||
agent: SolanaAgentKit,
|
||||
marketId: PublicKey,
|
||||
orders: OrderParams[],
|
||||
): Promise<string> {
|
||||
try {
|
||||
validateNoCrossedOrders(orders);
|
||||
|
||||
const mfxClient = await ManifestClient.getClientForMarket(
|
||||
agent.connection,
|
||||
marketId,
|
||||
agent.wallet,
|
||||
);
|
||||
|
||||
const placeParams: WrapperPlaceOrderParamsExternal[] = orders.map(
|
||||
(order) => ({
|
||||
numBaseTokens: order.quantity,
|
||||
tokenPrice: order.price,
|
||||
isBid: order.side === "Buy",
|
||||
lastValidSlot: 0,
|
||||
orderType: OrderType.Limit,
|
||||
clientOrderId: Number(Math.random() * 10000),
|
||||
}),
|
||||
);
|
||||
|
||||
const batchOrderIx: TransactionInstruction = await mfxClient.batchUpdateIx(
|
||||
placeParams,
|
||||
[],
|
||||
true,
|
||||
);
|
||||
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
agent.connection,
|
||||
new Transaction().add(batchOrderIx),
|
||||
[agent.wallet],
|
||||
);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Batch Order failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()}`,
|
||||
);
|
||||
|
||||
10
src/tools/get_wallet_address.ts
Normal file
10
src/tools/get_wallet_address.ts
Normal 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();
|
||||
}
|
||||
@@ -1,55 +1,52 @@
|
||||
export * from "./request_faucet_funds";
|
||||
export * from "./deploy_token";
|
||||
export * from "./adrena_perp_trading";
|
||||
export * from "./batch_order";
|
||||
export * from "./cancel_all_orders";
|
||||
export * from "./create_gibwork_task";
|
||||
export * from "./create_image";
|
||||
export * from "./create_tiplinks";
|
||||
export * from "./deploy_collection";
|
||||
export * from "./deploy_token";
|
||||
export * from "./fetch_price";
|
||||
export * from "./get_all_domains_tlds";
|
||||
export * from "./get_all_registered_all_domains";
|
||||
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 "./cancel_all_orders";
|
||||
export * from "./withdraw_all";
|
||||
export * from "./register_domain";
|
||||
export * from "./resolve_sol_domain";
|
||||
export * from "./get_main_all_domains_domain";
|
||||
export * from "./get_owned_all_domains";
|
||||
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 "./get_tps";
|
||||
export * from "./get_token_data";
|
||||
export * from "./stake_with_jup";
|
||||
export * from "./fetch_price";
|
||||
export * from "./send_compressed_airdrop";
|
||||
export * from "./limit_order";
|
||||
export * from "./manifest_create_market";
|
||||
export * from "./mint_nft";
|
||||
export * from "./openbook_create_market";
|
||||
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 "./pyth_fetch_price";
|
||||
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 "./register_domain";
|
||||
export * from "./request_faucet_funds";
|
||||
export * from "./resolve_domain";
|
||||
export * from "./resolve_sol_domain";
|
||||
export * from "./rock_paper_scissor";
|
||||
export * from "./create_tiplinks";
|
||||
|
||||
export * from "./rugcheck";
|
||||
export * from "./send_compressed_airdrop";
|
||||
export * from "./stake_with_jup";
|
||||
export * from "./stake_with_solayer";
|
||||
export * from "./tensor_trade";
|
||||
|
||||
export * from "./close_empty_token_accounts";
|
||||
|
||||
export * from "./trade";
|
||||
export * from "./transfer";
|
||||
export * from "./withdraw_all";
|
||||
|
||||
@@ -8,8 +8,8 @@ import { SolanaAgentKit } from "../index";
|
||||
import {
|
||||
ManifestClient,
|
||||
WrapperPlaceOrderParamsExternal,
|
||||
OrderType,
|
||||
} from "@cks-systems/manifest-sdk";
|
||||
import { OrderType } from "@cks-systems/manifest-sdk/client/ts/src/wrapper/types/OrderType";
|
||||
|
||||
/**
|
||||
* Place limit orders using Manifest
|
||||
|
||||
@@ -1,38 +1,91 @@
|
||||
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
|
||||
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 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];
|
||||
|
||||
export async function fetchPythPrice(feedID: string): Promise<string> {
|
||||
try {
|
||||
const currentPrice = await connection.getLatestPriceFeeds(feeds);
|
||||
const stableHermesServiceUrl: string = "https://hermes.pyth.network";
|
||||
|
||||
if (currentPrice === undefined) {
|
||||
throw new Error("Price data not available for the given token.");
|
||||
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}`);
|
||||
}
|
||||
|
||||
if (currentPrice.length === 0) {
|
||||
throw new Error("Price data not available for the given token.");
|
||||
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;
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
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}`);
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
53
src/tools/rugcheck.ts
Normal file
53
src/tools/rugcheck.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { TokenCheck } from "../types";
|
||||
|
||||
const BASE_URL = "https://api.rugcheck.xyz/v1";
|
||||
|
||||
/**
|
||||
* Fetches a summary report for a specific token.
|
||||
* @async
|
||||
* @param {string} mint - The mint address of the token.
|
||||
* @returns {Promise<TokenCheck>} The token summary report.
|
||||
* @throws {Error} If the API call fails.
|
||||
*/
|
||||
export async function fetchTokenReportSummary(
|
||||
mint: string,
|
||||
): Promise<TokenCheck> {
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/tokens/${mint}/report/summary`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
`Error fetching report summary for token ${mint}:`,
|
||||
error.message,
|
||||
);
|
||||
throw new Error(`Failed to fetch report summary for token ${mint}.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a detailed report for a specific token.
|
||||
* @async
|
||||
* @param {string} mint - The mint address of the token.
|
||||
* @returns {Promise<TokenCheck>} The detailed token report.
|
||||
* @throws {Error} If the API call fails.
|
||||
*/
|
||||
export async function fetchTokenDetailedReport(
|
||||
mint: string,
|
||||
): Promise<TokenCheck> {
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/tokens/${mint}/report`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
`Error fetching detailed report for token ${mint}:`,
|
||||
error.message,
|
||||
);
|
||||
throw new Error(`Failed to fetch detailed report for token ${mint}.`);
|
||||
}
|
||||
}
|
||||
@@ -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.",
|
||||
);
|
||||
|
||||
64
src/tools/stake_with_solayer.ts
Normal file
64
src/tools/stake_with_solayer.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
@@ -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.`,
|
||||
);
|
||||
|
||||
@@ -88,7 +88,8 @@ export interface FetchPriceResponse {
|
||||
|
||||
export interface PythFetchPriceResponse {
|
||||
status: "success" | "error";
|
||||
priceFeedID: string;
|
||||
tokenSymbol: string;
|
||||
priceFeedID?: string;
|
||||
price?: string;
|
||||
message?: string;
|
||||
code?: string;
|
||||
@@ -153,3 +154,73 @@ export interface Action {
|
||||
*/
|
||||
handler: Handler;
|
||||
}
|
||||
|
||||
export interface TokenCheck {
|
||||
tokenProgram: string;
|
||||
tokenType: string;
|
||||
risks: Array<{
|
||||
name: string;
|
||||
level: string;
|
||||
description: string;
|
||||
score: number;
|
||||
}>;
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface PythPriceFeedIDItem {
|
||||
id: string;
|
||||
attributes: {
|
||||
asset_type: string;
|
||||
base: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PythPriceItem {
|
||||
binary: {
|
||||
data: string[];
|
||||
encoding: string;
|
||||
};
|
||||
parsed: [
|
||||
Array<{
|
||||
id: string;
|
||||
price: {
|
||||
price: string;
|
||||
conf: string;
|
||||
expo: number;
|
||||
publish_time: number;
|
||||
};
|
||||
ema_price: {
|
||||
price: string;
|
||||
conf: string;
|
||||
expo: number;
|
||||
publish_time: number;
|
||||
};
|
||||
metadata: {
|
||||
slot: number;
|
||||
proof_available_time: number;
|
||||
prev_publish_time: number;
|
||||
};
|
||||
}>,
|
||||
];
|
||||
}
|
||||
|
||||
export interface OrderParams {
|
||||
quantity: number;
|
||||
side: string;
|
||||
price: number;
|
||||
}
|
||||
|
||||
export interface BatchOrderPattern {
|
||||
side: string;
|
||||
totalQuantity?: number;
|
||||
priceRange?: {
|
||||
min?: number;
|
||||
max?: number;
|
||||
};
|
||||
spacing?: {
|
||||
type: "percentage" | "fixed";
|
||||
value: number;
|
||||
};
|
||||
numberOfOrders?: number;
|
||||
individualQuantity?: number;
|
||||
}
|
||||
|
||||
220
src/utils/AdrenaClient.ts
Normal file
220
src/utils/AdrenaClient.ts
Normal 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
25
src/vercel-ai/index.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user