mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-24 23:26:44 +00:00
Merge branch 'main' into feat/squads_multisig
This commit is contained in:
71
src/actions/closeEmptyTokenAccounts.ts
Normal file
71
src/actions/closeEmptyTokenAccounts.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { closeEmptyTokenAccounts } from "../tools";
|
||||
|
||||
const closeEmptyTokenAccountsAction: Action = {
|
||||
name: "CLOSE_EMPTY_TOKEN_ACCOUNTS",
|
||||
similes: [
|
||||
"close token accounts",
|
||||
"remove empty accounts",
|
||||
"clean up token accounts",
|
||||
"close SPL token accounts",
|
||||
"clean wallet",
|
||||
],
|
||||
description: `Close empty SPL Token accounts associated with your wallet to reclaim rent.
|
||||
This action will close both regular SPL Token accounts and Token-2022 accounts that have zero balance. `,
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {},
|
||||
output: {
|
||||
status: "success",
|
||||
signature:
|
||||
"3KmPyiZvJQk8CfBVVaz8nf3c2crb6iqjQVDqNxknnusyb1FTFpXqD8zVSCBAd1X3rUcD8WiG1bdSjFbeHsmcYGXY",
|
||||
accountsClosed: 10,
|
||||
},
|
||||
explanation: "Closed 10 empty token accounts successfully.",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {},
|
||||
output: {
|
||||
status: "success",
|
||||
signature: "",
|
||||
accountsClosed: 0,
|
||||
},
|
||||
explanation: "No empty token accounts were found to close.",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({}),
|
||||
handler: async (agent: SolanaAgentKit) => {
|
||||
try {
|
||||
const result = await closeEmptyTokenAccounts(agent);
|
||||
|
||||
if (result.size === 0) {
|
||||
return {
|
||||
status: "success",
|
||||
signature: "",
|
||||
accountsClosed: 0,
|
||||
message: "No empty token accounts found to close",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
signature: result.signature,
|
||||
accountsClosed: result.size,
|
||||
message: `Successfully closed ${result.size} empty token accounts`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to close empty token accounts: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default closeEmptyTokenAccountsAction;
|
||||
68
src/actions/flashCloseTrade.ts
Normal file
68
src/actions/flashCloseTrade.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { flashCloseTrade } from "../tools";
|
||||
|
||||
const flashCloseTradeAction: Action = {
|
||||
name: "FLASH_CLOSE_TRADE",
|
||||
similes: [
|
||||
"close trade",
|
||||
"close leveraged trade",
|
||||
"exit position",
|
||||
"close position",
|
||||
"exit trade",
|
||||
"close long",
|
||||
"close short",
|
||||
"take profit",
|
||||
"stop loss",
|
||||
],
|
||||
description:
|
||||
"Close an existing leveraged trading position on Flash.Trade protocol",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
token: "SOL",
|
||||
side: "long",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
signature: "4xKpN2...",
|
||||
message: "Successfully closed long position on SOL",
|
||||
},
|
||||
explanation: "Close an existing long position on SOL",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
token: z
|
||||
.string()
|
||||
.describe("Token symbol of the position to close (e.g. SOL, ETH)"),
|
||||
side: z
|
||||
.enum(["long", "short"])
|
||||
.describe("Position side to close - long or short"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const params = {
|
||||
token: input.token as string,
|
||||
side: input.side as "long" | "short",
|
||||
};
|
||||
|
||||
const response = await flashCloseTrade(agent, params);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
signature: response,
|
||||
message: `Successfully closed ${params.side} position on ${params.token}`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Flash trade close failed: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default flashCloseTradeAction;
|
||||
78
src/actions/flashOpenTrade.ts
Normal file
78
src/actions/flashOpenTrade.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { flashOpenTrade } from "../tools";
|
||||
|
||||
const flashOpenTradeAction: Action = {
|
||||
name: "FLASH_OPEN_TRADE",
|
||||
similes: [
|
||||
"open trade",
|
||||
"open leveraged trade",
|
||||
"start trading position",
|
||||
"open position",
|
||||
"long position",
|
||||
"short position",
|
||||
"leverage trade",
|
||||
"margin trade",
|
||||
],
|
||||
description: "Open a leveraged trading position on Flash.Trade protocol",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
token: "SOL",
|
||||
side: "long",
|
||||
collateralUsd: 100,
|
||||
leverage: 5,
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
signature: "4xKpN2...",
|
||||
message:
|
||||
"Successfully opened 5x long position on SOL with $100 collateral",
|
||||
},
|
||||
explanation:
|
||||
"Open a 5x leveraged long position on SOL using $100 as collateral",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
token: z.string().describe("Token symbol to trade (e.g. SOL, ETH)"),
|
||||
side: z
|
||||
.enum(["long", "short"])
|
||||
.describe("Trading direction - long or short"),
|
||||
collateralUsd: z
|
||||
.number()
|
||||
.positive()
|
||||
.describe("Amount of collateral in USD"),
|
||||
leverage: z
|
||||
.number()
|
||||
.positive()
|
||||
.describe("Leverage multiplier for the trade"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const params = {
|
||||
token: input.token as string,
|
||||
side: input.side as "long" | "short",
|
||||
collateralUsd: input.collateralUsd as number,
|
||||
leverage: input.leverage as number,
|
||||
};
|
||||
|
||||
const response = await flashOpenTrade(agent, params);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
signature: response,
|
||||
message: `Successfully opened ${params.leverage}x ${params.side} position on ${params.token} with $${params.collateralUsd} collateral`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Flash trade failed: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default flashOpenTradeAction;
|
||||
@@ -28,6 +28,8 @@ import raydiumCreateAmmV4Action from "./raydiumCreateAmmV4";
|
||||
import createOrcaSingleSidedWhirlpoolAction from "./createOrcaSingleSidedWhirlpool";
|
||||
import launchPumpfunTokenAction from "./launchPumpfunToken";
|
||||
import getWalletAddressAction from "./getWalletAddress";
|
||||
import flashOpenTradeAction from "./flashOpenTrade";
|
||||
import flashCloseTradeAction from "./flashCloseTrade";
|
||||
|
||||
export const ACTIONS = {
|
||||
WALLET_ADDRESS_ACTION: getWalletAddressAction,
|
||||
@@ -61,6 +63,8 @@ export const ACTIONS = {
|
||||
CREATE_ORCA_SINGLE_SIDED_WHIRLPOOL_ACTION:
|
||||
createOrcaSingleSidedWhirlpoolAction,
|
||||
LAUNCH_PUMPFUN_TOKEN_ACTION: launchPumpfunTokenAction,
|
||||
FLASH_OPEN_TRADE_ACTION: flashOpenTradeAction,
|
||||
FLASH_CLOSE_TRADE_ACTION: flashCloseTradeAction,
|
||||
};
|
||||
|
||||
export type { Action, ActionExample, Handler } from "../types/action";
|
||||
|
||||
@@ -56,10 +56,13 @@ import {
|
||||
create_TipLink,
|
||||
listNFTForSale,
|
||||
cancelListing,
|
||||
closeEmptyTokenAccounts,
|
||||
fetchTokenReportSummary,
|
||||
fetchTokenDetailedReport,
|
||||
fetchPythPrice,
|
||||
fetchPythPriceFeedID,
|
||||
flashOpenTrade,
|
||||
flashCloseTrade,
|
||||
} from "../tools";
|
||||
import {
|
||||
CollectionDeployment,
|
||||
@@ -70,7 +73,18 @@ import {
|
||||
PumpfunLaunchResponse,
|
||||
PumpFunTokenOptions,
|
||||
OrderParams,
|
||||
FlashTradeParams,
|
||||
FlashCloseTradeParams,
|
||||
} from "../types";
|
||||
import {
|
||||
createCollection,
|
||||
createSingle,
|
||||
} from "../tools/create_3land_collectible";
|
||||
import {
|
||||
CreateCollectionOptions,
|
||||
CreateSingleOptions,
|
||||
StoreInitOptions,
|
||||
} from "@3land/listings-sdk/dist/types/implementation/implementationTypes";
|
||||
import { create_squads_multisig } from "../tools/squads_multisig/create_multisig";
|
||||
import { deposit_to_multisig } from "../tools/squads_multisig/deposit_to_multisig";
|
||||
import { transfer_from_multisig } from "../tools/squads_multisig/transfer_from_multisig";
|
||||
@@ -541,6 +555,13 @@ export class SolanaAgentKit {
|
||||
return cancelListing(this, nftMint);
|
||||
}
|
||||
|
||||
async closeEmptyTokenAccounts(): Promise<{
|
||||
signature: string;
|
||||
size: number;
|
||||
}> {
|
||||
return closeEmptyTokenAccounts(this);
|
||||
}
|
||||
|
||||
async fetchTokenReportSummary(mint: string): Promise<TokenCheck> {
|
||||
return fetchTokenReportSummary(mint);
|
||||
}
|
||||
@@ -549,6 +570,47 @@ export class SolanaAgentKit {
|
||||
return fetchTokenDetailedReport(mint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new trading position on Flash.Trade
|
||||
* @param params Flash trade parameters including market, side, collateral, leverage, and pool name
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
async flashOpenTrade(params: FlashTradeParams): Promise<string> {
|
||||
return flashOpenTrade(this, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes an existing trading position on Flash.Trade
|
||||
* @param params Flash trade close parameters
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
async flashCloseTrade(params: FlashCloseTradeParams): Promise<string> {
|
||||
return flashCloseTrade(this, params);
|
||||
}
|
||||
|
||||
async create3LandCollection(
|
||||
optionsWithBase58: StoreInitOptions,
|
||||
collectionOpts: CreateCollectionOptions,
|
||||
): Promise<string> {
|
||||
const tx = await createCollection(optionsWithBase58, collectionOpts);
|
||||
return `Transaction: ${tx}`;
|
||||
}
|
||||
|
||||
async create3LandNft(
|
||||
optionsWithBase58: StoreInitOptions,
|
||||
collectionAccount: string,
|
||||
createItemOptions: CreateSingleOptions,
|
||||
isMainnet: boolean,
|
||||
): Promise<string> {
|
||||
const tx = await createSingle(
|
||||
optionsWithBase58,
|
||||
collectionAccount,
|
||||
createItemOptions,
|
||||
isMainnet,
|
||||
);
|
||||
return `Transaction: ${tx}`;
|
||||
}
|
||||
|
||||
async createSquadsMultisig(creator: PublicKey): Promise<string> {
|
||||
return create_squads_multisig(this, creator);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,12 @@ import {
|
||||
SolanaAgentKit,
|
||||
} from "../index";
|
||||
import { create_image, FEE_TIERS, generateOrdersfromPattern } from "../tools";
|
||||
import { marketTokenMap } from "../utils/flashUtils";
|
||||
import {
|
||||
CreateCollectionOptions,
|
||||
CreateSingleOptions,
|
||||
StoreInitOptions,
|
||||
} from "@3land/listings-sdk/dist/types/implementation/implementationTypes";
|
||||
|
||||
export class SolanaBalanceTool extends Tool {
|
||||
name = "solana_balance";
|
||||
@@ -774,6 +780,135 @@ export class SolanaGetWalletAddressTool extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaFlashOpenTrade extends Tool {
|
||||
name = "solana_flash_open_trade";
|
||||
description = `This tool can be used to open a new leveraged trading position on Flash.Trade exchange.
|
||||
|
||||
Inputs ( input is a JSON string ):
|
||||
token: string, eg "SOL", "BTC", "ETH" (required)
|
||||
type: string, eg "long", "short" (required)
|
||||
collateral: number, eg 10, 100, 1000 (required)
|
||||
leverage: number, eg 5, 10, 20 (required)
|
||||
|
||||
Example prompt is Open a 20x leveraged trade for SOL on long side using flash trade with 500 USD as collateral`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = JSON.parse(input);
|
||||
|
||||
// Validate input parameters
|
||||
if (!parsedInput.token) {
|
||||
throw new Error("Token is required, received: " + parsedInput.token);
|
||||
}
|
||||
if (!Object.keys(marketTokenMap).includes(parsedInput.token)) {
|
||||
throw new Error(
|
||||
"Token must be one of " +
|
||||
Object.keys(marketTokenMap).join(", ") +
|
||||
", received: " +
|
||||
parsedInput.token +
|
||||
"\n" +
|
||||
"Please check https://beast.flash.trade/ for the list of supported tokens",
|
||||
);
|
||||
}
|
||||
if (!["long", "short"].includes(parsedInput.type)) {
|
||||
throw new Error(
|
||||
'Type must be either "long" or "short", received: ' +
|
||||
parsedInput.type,
|
||||
);
|
||||
}
|
||||
if (!parsedInput.collateral || parsedInput.collateral <= 0) {
|
||||
throw new Error(
|
||||
"Collateral amount must be positive, received: " +
|
||||
parsedInput.collateral,
|
||||
);
|
||||
}
|
||||
if (!parsedInput.leverage || parsedInput.leverage <= 0) {
|
||||
throw new Error(
|
||||
"Leverage must be positive, received: " + parsedInput.leverage,
|
||||
);
|
||||
}
|
||||
|
||||
const tx = await this.solanaKit.flashOpenTrade({
|
||||
token: parsedInput.token,
|
||||
side: parsedInput.type,
|
||||
collateralUsd: parsedInput.collateral,
|
||||
leverage: parsedInput.leverage,
|
||||
});
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Flash trade position opened successfully",
|
||||
transaction: tx,
|
||||
token: parsedInput.token,
|
||||
side: parsedInput.type,
|
||||
collateral: parsedInput.collateral,
|
||||
leverage: parsedInput.leverage,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaFlashCloseTrade extends Tool {
|
||||
name = "solana_flash_close_trade";
|
||||
description = `Close an existing leveraged trading position on Flash.Trade exchange.
|
||||
|
||||
Inputs ( input is a JSON string ):
|
||||
token: string, eg "SOL", "BTC", "ETH" (required)
|
||||
side: string, eg "long", "short" (required)
|
||||
|
||||
Example prompt is Close a 20x leveraged trade for SOL on long side`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = JSON.parse(input);
|
||||
|
||||
// Validate input parameters
|
||||
if (!parsedInput.token) {
|
||||
throw new Error("Token is required");
|
||||
}
|
||||
if (!["SOL", "BTC", "ETH"].includes(parsedInput.token)) {
|
||||
throw new Error('Token must be one of ["SOL", "BTC", "ETH"]');
|
||||
}
|
||||
if (!["long", "short"].includes(parsedInput.side)) {
|
||||
throw new Error('Side must be either "long" or "short"');
|
||||
}
|
||||
|
||||
const tx = await this.solanaKit.flashCloseTrade({
|
||||
token: parsedInput.token,
|
||||
side: parsedInput.side,
|
||||
});
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Flash trade position closed successfully",
|
||||
transaction: tx,
|
||||
token: parsedInput.token,
|
||||
side: parsedInput.side,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaPumpfunTokenLaunchTool extends Tool {
|
||||
name = "solana_launch_pumpfun_token";
|
||||
|
||||
@@ -2125,6 +2260,181 @@ export class SolanaFetchTokenDetailedReportTool extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
export class Solana3LandCreateSingle extends Tool {
|
||||
name = "3land_minting_tool";
|
||||
description = `Creates an NFT and lists it on 3.land's website
|
||||
|
||||
Inputs:
|
||||
privateKey (required): represents the privateKey of the wallet - can be an array of numbers, Uint8Array or base58 string
|
||||
collectionAccount (optional): represents the account for the nft collection
|
||||
itemName (required): the name of the NFT
|
||||
sellerFee (required): the fee of the seller
|
||||
itemAmount (required): the amount of the NFTs that can be minted
|
||||
itemDescription (required): the description of the NFT
|
||||
traits (required): the traits of the NFT [{trait_type: string, value: string}]
|
||||
price (required): the price of the item, if is 0 the listing will be free
|
||||
mainImageUrl (required): the main image of the NFT
|
||||
coverImageUrl (optional): the cover image of the NFT
|
||||
splHash (optional): the hash of the spl token, if not provided listing will be in $SOL
|
||||
isMainnet (required): defines is the tx takes places in mainnet
|
||||
`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const privateKey = inputFormat.privateKey;
|
||||
const isMainnet = inputFormat.isMainnet;
|
||||
|
||||
const optionsWithBase58: StoreInitOptions = {
|
||||
...(privateKey && { privateKey }),
|
||||
...(isMainnet && { isMainnet }),
|
||||
};
|
||||
|
||||
const collectionAccount = inputFormat.collectionAccount;
|
||||
|
||||
const itemName = inputFormat?.itemName;
|
||||
const sellerFee = inputFormat?.sellerFee;
|
||||
const itemAmount = inputFormat?.itemAmount;
|
||||
const itemSymbol = inputFormat?.itemSymbol;
|
||||
const itemDescription = inputFormat?.itemDescription;
|
||||
const traits = inputFormat?.traits;
|
||||
const price = inputFormat?.price;
|
||||
const mainImageUrl = inputFormat?.mainImageUrl;
|
||||
const coverImageUrl = inputFormat?.coverImageUrl;
|
||||
const splHash = inputFormat?.splHash;
|
||||
|
||||
const createItemOptions: CreateSingleOptions = {
|
||||
...(itemName && { itemName }),
|
||||
...(sellerFee && { sellerFee }),
|
||||
...(itemAmount && { itemAmount }),
|
||||
...(itemSymbol && { itemSymbol }),
|
||||
...(itemDescription && { itemDescription }),
|
||||
...(traits && { traits }),
|
||||
...(price && { price }),
|
||||
...(mainImageUrl && { mainImageUrl }),
|
||||
...(coverImageUrl && { coverImageUrl }),
|
||||
...(splHash && { splHash }),
|
||||
};
|
||||
|
||||
if (!collectionAccount) {
|
||||
throw new Error("Collection account is required");
|
||||
}
|
||||
|
||||
const tx = await this.solanaKit.create3LandNft(
|
||||
optionsWithBase58,
|
||||
collectionAccount,
|
||||
createItemOptions,
|
||||
isMainnet,
|
||||
);
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: `Created listing successfully ${tx}`,
|
||||
transaction: tx,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Solana3LandCreateCollection extends Tool {
|
||||
name = "3land_minting_tool";
|
||||
description = `Creates an NFT Collection that you can visit on 3.land's website (3.land/collection/{collectionAccount})
|
||||
|
||||
Inputs:
|
||||
privateKey (required): represents the privateKey of the wallet - can be an array of numbers, Uint8Array or base58 string
|
||||
isMainnet (required): defines is the tx takes places in mainnet
|
||||
collectionSymbol (required): the symbol of the collection
|
||||
collectionName (required): the name of the collection
|
||||
collectionDescription (required): the description of the collection
|
||||
mainImageUrl (required): the image of the collection
|
||||
coverImageUrl (optional): the cover image of the collection
|
||||
`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const privateKey = inputFormat.privateKey;
|
||||
const isMainnet = inputFormat.isMainnet;
|
||||
|
||||
const optionsWithBase58: StoreInitOptions = {
|
||||
...(privateKey && { privateKey }),
|
||||
...(isMainnet && { isMainnet }),
|
||||
};
|
||||
|
||||
const collectionSymbol = inputFormat?.collectionSymbol;
|
||||
const collectionName = inputFormat?.collectionName;
|
||||
const collectionDescription = inputFormat?.collectionDescription;
|
||||
const mainImageUrl = inputFormat?.mainImageUrl;
|
||||
const coverImageUrl = inputFormat?.coverImageUrl;
|
||||
|
||||
const collectionOpts: CreateCollectionOptions = {
|
||||
...(collectionSymbol && { collectionSymbol }),
|
||||
...(collectionName && { collectionName }),
|
||||
...(collectionDescription && { collectionDescription }),
|
||||
...(mainImageUrl && { mainImageUrl }),
|
||||
...(coverImageUrl && { coverImageUrl }),
|
||||
};
|
||||
|
||||
const tx = await this.solanaKit.create3LandCollection(
|
||||
optionsWithBase58,
|
||||
collectionOpts,
|
||||
);
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: `Created Collection successfully ${tx}`,
|
||||
transaction: tx,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaCloseEmptyTokenAccounts extends Tool {
|
||||
name = "close_empty_token_accounts";
|
||||
description = `Close all empty spl-token accounts and reclaim the rent`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(): Promise<string> {
|
||||
try {
|
||||
const { signature, size } =
|
||||
await this.solanaKit.closeEmptyTokenAccounts();
|
||||
|
||||
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,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaCreate2by2Multisig extends Tool {
|
||||
name = "create_2by2_multisig";
|
||||
description = `Create a 2-of-2 multisig account on Solana with the user and the agent, where both approvals will be required to run the transactions.
|
||||
@@ -2428,10 +2738,16 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
new SolanaTipLinkTool(solanaKit),
|
||||
new SolanaListNFTForSaleTool(solanaKit),
|
||||
new SolanaCancelNFTListingTool(solanaKit),
|
||||
new SolanaCloseEmptyTokenAccounts(solanaKit),
|
||||
new SolanaFetchTokenReportSummaryTool(solanaKit),
|
||||
new SolanaFetchTokenDetailedReportTool(solanaKit),
|
||||
new Solana3LandCreateSingle(solanaKit),
|
||||
new Solana3LandCreateCollection(solanaKit),
|
||||
new SolanaPerpOpenTradeTool(solanaKit),
|
||||
new SolanaPerpCloseTradeTool(solanaKit),
|
||||
new SolanaFlashOpenTrade(solanaKit),
|
||||
new SolanaFlashCloseTrade(solanaKit),
|
||||
new Solana3LandCreateSingle(solanaKit),
|
||||
new SolanaCreate2by2Multisig(solanaKit),
|
||||
new SolanaDepositTo2by2Multisig(solanaKit),
|
||||
new SolanaTransferFrom2by2Multisig(solanaKit),
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import {
|
||||
PublicKey,
|
||||
sendAndConfirmTransaction,
|
||||
Transaction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { ManifestClient } from "@cks-systems/manifest-sdk";
|
||||
|
||||
/**
|
||||
* Cancels all orders from Manifest
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param marketId Public key for the manifest market
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function cancelAllOrders(
|
||||
agent: SolanaAgentKit,
|
||||
marketId: PublicKey,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const mfxClient = await ManifestClient.getClientForMarket(
|
||||
agent.connection,
|
||||
marketId,
|
||||
agent.wallet,
|
||||
);
|
||||
|
||||
const cancelAllOrdersIx = await mfxClient.cancelAllIx();
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
agent.connection,
|
||||
new Transaction().add(cancelAllOrdersIx),
|
||||
[agent.wallet],
|
||||
);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Cancel all orders failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
103
src/tools/close_empty_token_accounts.ts
Normal file
103
src/tools/close_empty_token_accounts.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
PublicKey,
|
||||
Transaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import {
|
||||
AccountLayout,
|
||||
createCloseAccountInstruction,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
|
||||
/**
|
||||
* Close Empty SPL Token accounts of the agent
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @returns transaction signature and total number of accounts closed
|
||||
*/
|
||||
export async function closeEmptyTokenAccounts(
|
||||
agent: SolanaAgentKit,
|
||||
): Promise<{ signature: string; size: number }> {
|
||||
try {
|
||||
const spl_token = await create_close_instruction(agent, TOKEN_PROGRAM_ID);
|
||||
const token_2022 = await create_close_instruction(
|
||||
agent,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
);
|
||||
const transaction = new Transaction();
|
||||
|
||||
const MAX_INSTRUCTIONS = 40; // 40 instructions can be processed in a single transaction without failing
|
||||
|
||||
spl_token
|
||||
.slice(0, Math.min(MAX_INSTRUCTIONS, spl_token.length))
|
||||
.forEach((instruction) => transaction.add(instruction));
|
||||
|
||||
token_2022
|
||||
.slice(0, Math.max(0, MAX_INSTRUCTIONS - spl_token.length))
|
||||
.forEach((instruction) => transaction.add(instruction));
|
||||
|
||||
const size = spl_token.length + token_2022.length;
|
||||
|
||||
if (size === 0) {
|
||||
return {
|
||||
signature: "",
|
||||
size: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const signature = await agent.connection.sendTransaction(transaction, [
|
||||
agent.wallet,
|
||||
]);
|
||||
|
||||
return { signature, size };
|
||||
} catch (error) {
|
||||
throw new Error(`Error closing empty token accounts: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* creates the close instuctions of a spl token account
|
||||
* @param agnet SolanaAgentKit instance
|
||||
* @param token_program Token Program Id
|
||||
* @returns close instuction array
|
||||
*/
|
||||
|
||||
async function create_close_instruction(
|
||||
agent: SolanaAgentKit,
|
||||
token_program: PublicKey,
|
||||
): Promise<TransactionInstruction[]> {
|
||||
const instructions = [];
|
||||
|
||||
const ata_accounts = await agent.connection.getTokenAccountsByOwner(
|
||||
agent.wallet_address,
|
||||
{ programId: token_program },
|
||||
"confirmed",
|
||||
);
|
||||
|
||||
const tokens = ata_accounts.value;
|
||||
|
||||
const accountExceptions = [
|
||||
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
|
||||
];
|
||||
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const token_data = AccountLayout.decode(tokens[i].account.data);
|
||||
if (
|
||||
token_data.amount === BigInt(0) &&
|
||||
!accountExceptions.includes(token_data.mint.toString())
|
||||
) {
|
||||
const closeInstruction = createCloseAccountInstruction(
|
||||
ata_accounts.value[i].pubkey,
|
||||
agent.wallet_address,
|
||||
agent.wallet_address,
|
||||
[],
|
||||
token_program,
|
||||
);
|
||||
|
||||
instructions.push(closeInstruction);
|
||||
}
|
||||
}
|
||||
|
||||
return instructions;
|
||||
}
|
||||
69
src/tools/create_3land_collectible.ts
Normal file
69
src/tools/create_3land_collectible.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { createCollectionImp, createSingleImp } from "@3land/listings-sdk";
|
||||
import {
|
||||
StoreInitOptions,
|
||||
CreateCollectionOptions,
|
||||
CreateSingleOptions,
|
||||
} from "@3land/listings-sdk/dist/types/implementation/implementationTypes";
|
||||
|
||||
/**
|
||||
* Create a collection on 3Land
|
||||
* @param optionsWithBase58 represents the privateKey of the wallet - can be an array of numbers, Uint8Array or base58 string
|
||||
* @param collectionOpts represents the options for the collection creation
|
||||
* @returns
|
||||
*/
|
||||
export async function createCollection(
|
||||
optionsWithBase58: StoreInitOptions,
|
||||
collectionOpts: CreateCollectionOptions,
|
||||
) {
|
||||
try {
|
||||
const collection = await createCollectionImp(
|
||||
optionsWithBase58,
|
||||
collectionOpts,
|
||||
);
|
||||
return collection;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Collection creation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single edition on 3Land
|
||||
* @param optionsWithBase58 represents the privateKey of the wallet - can be an array of numbers, Uint8Array or base58 string
|
||||
* @param collectionAccount represents the account for the nft collection
|
||||
* @param createItemOptions the options for the creation of the single NFT listing
|
||||
* @returns
|
||||
*/
|
||||
export async function createSingle(
|
||||
optionsWithBase58: StoreInitOptions,
|
||||
collectionAccount: string,
|
||||
createItemOptions: CreateSingleOptions,
|
||||
isMainnet: boolean,
|
||||
) {
|
||||
try {
|
||||
const landStore = isMainnet
|
||||
? "AmQNs2kgw4LvS9sm6yE9JJ4Hs3JpVu65eyx9pxMG2xA"
|
||||
: "GyPCu89S63P9NcCQAtuSJesiefhhgpGWrNVJs4bF2cSK";
|
||||
|
||||
const singleEditionTx = await createSingleImp(
|
||||
optionsWithBase58,
|
||||
landStore,
|
||||
collectionAccount,
|
||||
createItemOptions,
|
||||
);
|
||||
return singleEditionTx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Single edition creation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Buy a single edition on 3Land
|
||||
* @param
|
||||
* @returns
|
||||
*/
|
||||
// export async function buySingle() {
|
||||
// try {
|
||||
// } catch (error: any) {
|
||||
// throw new Error(`Buying single edition failed: ${error.message}`);
|
||||
// }
|
||||
// }
|
||||
118
src/tools/flash_close_trade.ts
Normal file
118
src/tools/flash_close_trade.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { ComputeBudgetProgram } from "@solana/web3.js";
|
||||
import { PoolConfig, Side } from "flash-sdk";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import {
|
||||
CLOSE_POSITION_CU,
|
||||
marketSdkInfo,
|
||||
marketTokenMap,
|
||||
getNftTradingAccountInfo,
|
||||
fetchOraclePrice,
|
||||
createPerpClient,
|
||||
get_flash_privilege,
|
||||
} from "../utils/flashUtils";
|
||||
import { FlashCloseTradeParams } from "../types";
|
||||
|
||||
/**
|
||||
* Closes an existing position on Flash.Trade
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param params Trade parameters
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function flashCloseTrade(
|
||||
agent: SolanaAgentKit,
|
||||
params: FlashCloseTradeParams,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const { token, side } = params;
|
||||
|
||||
// Get market ID from token and side using marketTokenMap
|
||||
const tokenMarkets = marketTokenMap[token];
|
||||
if (!tokenMarkets) {
|
||||
throw new Error(`Token ${token} not supported for trading`);
|
||||
}
|
||||
|
||||
const sideEntry = tokenMarkets[side];
|
||||
if (!sideEntry) {
|
||||
throw new Error(`${side} side not available for ${token}`);
|
||||
}
|
||||
|
||||
const market = sideEntry.marketID;
|
||||
|
||||
// Validate market data using marketSdkInfo
|
||||
const marketData = marketSdkInfo[market];
|
||||
if (!marketData) {
|
||||
throw new Error(`Invalid market configuration for ${token}/${side}`);
|
||||
}
|
||||
|
||||
// Get token information
|
||||
const [targetSymbol, collateralSymbol] = marketData.tokenPair.split("/");
|
||||
|
||||
// Fetch oracle prices
|
||||
const [targetPrice] = await Promise.all([
|
||||
fetchOraclePrice(targetSymbol),
|
||||
fetchOraclePrice(collateralSymbol),
|
||||
]);
|
||||
|
||||
// Initialize pool configuration and perpClient
|
||||
const poolConfig = PoolConfig.fromIdsByName(
|
||||
marketData.pool,
|
||||
"mainnet-beta",
|
||||
);
|
||||
const perpClient = createPerpClient(agent.connection, agent.wallet);
|
||||
|
||||
// Calculate price after slippage
|
||||
const slippageBpsBN = new BN(100); // 1% slippage
|
||||
const sideEnum = side === "long" ? Side.Long : Side.Short;
|
||||
const priceWithSlippage = perpClient.getPriceAfterSlippage(
|
||||
false, // isEntry = false for closing position
|
||||
slippageBpsBN,
|
||||
targetPrice.price,
|
||||
sideEnum,
|
||||
);
|
||||
|
||||
// Get NFT trading account info
|
||||
const tradingAccounts = await getNftTradingAccountInfo(
|
||||
agent.wallet_address,
|
||||
perpClient,
|
||||
poolConfig,
|
||||
collateralSymbol,
|
||||
);
|
||||
|
||||
if (
|
||||
!tradingAccounts.nftTradingAccountPk ||
|
||||
!tradingAccounts.nftReferralAccountPK ||
|
||||
!tradingAccounts.nftOwnerRebateTokenAccountPk
|
||||
) {
|
||||
throw new Error("Required NFT trading accounts not found");
|
||||
}
|
||||
|
||||
// Build and send transaction
|
||||
const { instructions, additionalSigners } = await perpClient.closePosition(
|
||||
targetSymbol,
|
||||
collateralSymbol,
|
||||
priceWithSlippage,
|
||||
sideEnum,
|
||||
poolConfig,
|
||||
get_flash_privilege(agent),
|
||||
tradingAccounts.nftTradingAccountPk,
|
||||
tradingAccounts.nftReferralAccountPK,
|
||||
tradingAccounts.nftOwnerRebateTokenAccountPk,
|
||||
);
|
||||
|
||||
const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({
|
||||
units: CLOSE_POSITION_CU,
|
||||
});
|
||||
|
||||
return await perpClient.sendTransaction(
|
||||
[computeBudgetIx, ...instructions],
|
||||
{
|
||||
additionalSigners: additionalSigners,
|
||||
alts: perpClient.addressLookupTables,
|
||||
prioritizationFee: 5000000,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(`Flash trade close failed: ${error}`);
|
||||
}
|
||||
}
|
||||
251
src/tools/flash_open_trade.ts
Normal file
251
src/tools/flash_open_trade.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
import { ComputeBudgetProgram } from "@solana/web3.js";
|
||||
import {
|
||||
PerpetualsClient,
|
||||
OraclePrice,
|
||||
PoolConfig,
|
||||
Side,
|
||||
CustodyAccount,
|
||||
Custody,
|
||||
} from "flash-sdk";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import {
|
||||
ALL_TOKENS,
|
||||
marketSdkInfo,
|
||||
marketTokenMap,
|
||||
getNftTradingAccountInfo,
|
||||
OPEN_POSITION_CU,
|
||||
fetchOraclePrice,
|
||||
createPerpClient,
|
||||
get_flash_privilege,
|
||||
} from "../utils/flashUtils";
|
||||
import { FlashTradeParams } from "../types";
|
||||
|
||||
/**
|
||||
* Opens a new position on Flash.Trade
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param params Trade parameters
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function flashOpenTrade(
|
||||
agent: SolanaAgentKit,
|
||||
params: FlashTradeParams,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const { token, side, collateralUsd, leverage } = params;
|
||||
|
||||
// Get market ID from token and side using marketTokenMap
|
||||
const tokenMarkets = marketTokenMap[token];
|
||||
if (!tokenMarkets) {
|
||||
throw new Error(`Token ${token} not supported for trading`);
|
||||
}
|
||||
|
||||
const sideEntry = tokenMarkets[side];
|
||||
if (!sideEntry) {
|
||||
throw new Error(`${side} side not available for ${token}`);
|
||||
}
|
||||
|
||||
const market = sideEntry.marketID;
|
||||
|
||||
// Validate market data using marketSdkInfo
|
||||
const marketData = marketSdkInfo[market];
|
||||
if (!marketData) {
|
||||
throw new Error(`Invalid market configuration for ${token}/${side}`);
|
||||
}
|
||||
|
||||
// Get token information
|
||||
const [targetSymbol, collateralSymbol] = marketData.tokenPair.split("/");
|
||||
const targetToken = ALL_TOKENS.find((t) => t.symbol === targetSymbol);
|
||||
const collateralToken = ALL_TOKENS.find(
|
||||
(t) => t.symbol === collateralSymbol,
|
||||
);
|
||||
|
||||
if (!targetToken || !collateralToken) {
|
||||
throw new Error(`Token not found for pair ${marketData.tokenPair}`);
|
||||
}
|
||||
|
||||
// Fetch oracle prices
|
||||
const [targetPrice, collateralPrice] = await Promise.all([
|
||||
fetchOraclePrice(targetSymbol),
|
||||
fetchOraclePrice(collateralSymbol),
|
||||
]);
|
||||
|
||||
// Initialize pool configuration and perpClient
|
||||
const poolConfig = PoolConfig.fromIdsByName(
|
||||
marketData.pool,
|
||||
"mainnet-beta",
|
||||
);
|
||||
const perpClient = createPerpClient(agent.connection, agent.wallet);
|
||||
|
||||
// Calculate position parameters
|
||||
const leverageBN = new BN(leverage);
|
||||
const collateralTokenPrice = convertPriceToNumber(collateralPrice.price);
|
||||
const collateralAmount = calculateCollateralAmount(
|
||||
collateralUsd,
|
||||
collateralTokenPrice,
|
||||
collateralToken.decimals,
|
||||
);
|
||||
|
||||
// Get custody accounts
|
||||
const { targetCustody, collateralCustody } = await fetchCustodyAccounts(
|
||||
perpClient,
|
||||
poolConfig,
|
||||
targetSymbol,
|
||||
collateralSymbol,
|
||||
);
|
||||
|
||||
// Calculate position size
|
||||
const positionSize = calculatePositionSize(
|
||||
perpClient,
|
||||
collateralAmount,
|
||||
leverageBN,
|
||||
targetToken,
|
||||
collateralToken,
|
||||
side,
|
||||
targetPrice.price,
|
||||
collateralPrice.price,
|
||||
targetCustody,
|
||||
collateralCustody,
|
||||
);
|
||||
|
||||
// Get NFT trading account info
|
||||
const tradingAccounts = await getNftTradingAccountInfo(
|
||||
agent.wallet_address,
|
||||
perpClient,
|
||||
poolConfig,
|
||||
collateralSymbol,
|
||||
);
|
||||
|
||||
if (
|
||||
!tradingAccounts.nftTradingAccountPk ||
|
||||
!tradingAccounts.nftReferralAccountPK
|
||||
) {
|
||||
throw new Error("Required NFT trading accounts not found");
|
||||
}
|
||||
|
||||
// Prepare transaction
|
||||
const slippageBps = new BN(1000);
|
||||
const priceWithSlippage = perpClient.getPriceAfterSlippage(
|
||||
true,
|
||||
slippageBps,
|
||||
targetPrice.price,
|
||||
side === "long" ? Side.Long : Side.Short,
|
||||
);
|
||||
|
||||
// Build and send transaction
|
||||
const { instructions, additionalSigners } = await perpClient.openPosition(
|
||||
targetSymbol,
|
||||
collateralSymbol,
|
||||
priceWithSlippage,
|
||||
collateralAmount,
|
||||
positionSize,
|
||||
side === "long" ? Side.Long : Side.Short,
|
||||
poolConfig,
|
||||
get_flash_privilege(agent),
|
||||
tradingAccounts.nftTradingAccountPk,
|
||||
tradingAccounts.nftReferralAccountPK,
|
||||
tradingAccounts.nftOwnerRebateTokenAccountPk!,
|
||||
false,
|
||||
);
|
||||
|
||||
const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({
|
||||
units: OPEN_POSITION_CU,
|
||||
});
|
||||
|
||||
return await perpClient.sendTransaction(
|
||||
[computeBudgetIx, ...instructions],
|
||||
{
|
||||
additionalSigners: additionalSigners,
|
||||
alts: perpClient.addressLookupTables,
|
||||
prioritizationFee: 5000000,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(`Flash trade failed: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
function convertPriceToNumber(oraclePrice: OraclePrice): number {
|
||||
const price = parseInt(oraclePrice.price.toString("hex"), 16);
|
||||
const exponent = parseInt(oraclePrice.exponent.toString("hex"), 16);
|
||||
return price * Math.pow(10, exponent);
|
||||
}
|
||||
|
||||
function calculateCollateralAmount(
|
||||
usdAmount: number,
|
||||
tokenPrice: number,
|
||||
decimals: number,
|
||||
): BN {
|
||||
return new BN((usdAmount / tokenPrice) * Math.pow(10, decimals));
|
||||
}
|
||||
|
||||
async function fetchCustodyAccounts(
|
||||
perpClient: PerpetualsClient,
|
||||
poolConfig: PoolConfig,
|
||||
targetSymbol: string,
|
||||
collateralSymbol: string,
|
||||
) {
|
||||
const targetConfig = poolConfig.custodies.find(
|
||||
(c) => c.symbol === targetSymbol,
|
||||
);
|
||||
const collateralConfig = poolConfig.custodies.find(
|
||||
(c) => c.symbol === collateralSymbol,
|
||||
);
|
||||
|
||||
if (!targetConfig || !collateralConfig) {
|
||||
throw new Error("Custody configuration not found");
|
||||
}
|
||||
|
||||
const accounts = await perpClient.provider.connection.getMultipleAccountsInfo(
|
||||
[targetConfig.custodyAccount, collateralConfig.custodyAccount],
|
||||
);
|
||||
|
||||
if (!accounts[0] || !accounts[1]) {
|
||||
throw new Error("Failed to fetch custody accounts");
|
||||
}
|
||||
|
||||
return {
|
||||
targetCustody: CustodyAccount.from(
|
||||
targetConfig.custodyAccount,
|
||||
perpClient.program.coder.accounts.decode<Custody>(
|
||||
"custody",
|
||||
accounts[0].data,
|
||||
),
|
||||
),
|
||||
collateralCustody: CustodyAccount.from(
|
||||
collateralConfig.custodyAccount,
|
||||
perpClient.program.coder.accounts.decode<Custody>(
|
||||
"custody",
|
||||
accounts[1].data,
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function calculatePositionSize(
|
||||
perpClient: PerpetualsClient,
|
||||
collateralAmount: BN,
|
||||
leverage: BN,
|
||||
targetToken: any,
|
||||
collateralToken: any,
|
||||
side: "long" | "short",
|
||||
targetPrice: OraclePrice,
|
||||
collateralPrice: OraclePrice,
|
||||
targetCustody: CustodyAccount,
|
||||
collateralCustody: CustodyAccount,
|
||||
): BN {
|
||||
return perpClient.getSizeAmountFromLeverageAndCollateral(
|
||||
collateralAmount,
|
||||
leverage.toString(),
|
||||
targetToken,
|
||||
collateralToken,
|
||||
side === "long" ? Side.Long : Side.Short,
|
||||
targetPrice,
|
||||
targetPrice,
|
||||
targetCustody,
|
||||
collateralPrice,
|
||||
collateralPrice,
|
||||
collateralCustody,
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
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";
|
||||
@@ -20,8 +18,7 @@ export * from "./get_tps";
|
||||
export * from "./get_wallet_address";
|
||||
export * from "./launch_pumpfun_token";
|
||||
export * from "./lend";
|
||||
export * from "./limit_order";
|
||||
export * from "./manifest_create_market";
|
||||
export * from "./manifest_trade";
|
||||
export * from "./mint_nft";
|
||||
export * from "./openbook_create_market";
|
||||
export * from "./orca_close_position";
|
||||
@@ -44,6 +41,12 @@ 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";
|
||||
export * from "./flash_open_trade";
|
||||
export * from "./flash_close_trade";
|
||||
|
||||
export * from "./create_3land_collectible";
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import {
|
||||
PublicKey,
|
||||
Transaction,
|
||||
sendAndConfirmTransaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import {
|
||||
ManifestClient,
|
||||
WrapperPlaceOrderParamsExternal,
|
||||
OrderType,
|
||||
} from "@cks-systems/manifest-sdk";
|
||||
|
||||
/**
|
||||
* Place limit orders using Manifest
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param marketId Public key for the manifest market
|
||||
* @param quantity Amount to trade in tokens
|
||||
* @param side Buy or Sell
|
||||
* @param price Price in tokens ie. SOL/USDC
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function limitOrder(
|
||||
agent: SolanaAgentKit,
|
||||
marketId: PublicKey,
|
||||
quantity: number,
|
||||
side: string,
|
||||
price: number,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const mfxClient = await ManifestClient.getClientForMarket(
|
||||
agent.connection,
|
||||
marketId,
|
||||
agent.wallet,
|
||||
);
|
||||
|
||||
const orderParams: WrapperPlaceOrderParamsExternal = {
|
||||
numBaseTokens: quantity,
|
||||
tokenPrice: price,
|
||||
isBid: side === "Buy",
|
||||
lastValidSlot: 0,
|
||||
orderType: OrderType.Limit,
|
||||
clientOrderId: Number(Math.random() * 1000),
|
||||
};
|
||||
|
||||
const depositPlaceOrderIx: TransactionInstruction[] =
|
||||
await mfxClient.placeOrderWithRequiredDepositIx(
|
||||
agent.wallet.publicKey,
|
||||
orderParams,
|
||||
);
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
agent.connection,
|
||||
new Transaction().add(...depositPlaceOrderIx),
|
||||
[agent.wallet],
|
||||
);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Limit Order failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { ManifestClient } from "@cks-systems/manifest-sdk";
|
||||
import {
|
||||
Keypair,
|
||||
PublicKey,
|
||||
sendAndConfirmTransaction,
|
||||
SystemProgram,
|
||||
Transaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
|
||||
export async function manifestCreateMarket(
|
||||
agent: SolanaAgentKit,
|
||||
baseMint: PublicKey,
|
||||
quoteMint: PublicKey,
|
||||
): Promise<string[]> {
|
||||
const marketKeypair: Keypair = Keypair.generate();
|
||||
const FIXED_MANIFEST_HEADER_SIZE: number = 256;
|
||||
const createAccountIx: TransactionInstruction = SystemProgram.createAccount({
|
||||
fromPubkey: agent.wallet.publicKey,
|
||||
newAccountPubkey: marketKeypair.publicKey,
|
||||
space: FIXED_MANIFEST_HEADER_SIZE,
|
||||
lamports: await agent.connection.getMinimumBalanceForRentExemption(
|
||||
FIXED_MANIFEST_HEADER_SIZE,
|
||||
),
|
||||
programId: new PublicKey("MNFSTqtC93rEfYHB6hF82sKdZpUDFWkViLByLd1k1Ms"),
|
||||
});
|
||||
const createMarketIx = ManifestClient["createMarketIx"](
|
||||
agent.wallet.publicKey,
|
||||
baseMint,
|
||||
quoteMint,
|
||||
marketKeypair.publicKey,
|
||||
);
|
||||
|
||||
const tx: Transaction = new Transaction();
|
||||
tx.add(createAccountIx);
|
||||
tx.add(createMarketIx);
|
||||
const signature = await sendAndConfirmTransaction(agent.connection, tx, [
|
||||
agent.wallet,
|
||||
marketKeypair,
|
||||
]);
|
||||
return [signature, marketKeypair.publicKey.toBase58()];
|
||||
}
|
||||
@@ -1,16 +1,159 @@
|
||||
import {
|
||||
PublicKey,
|
||||
Transaction,
|
||||
sendAndConfirmTransaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import {
|
||||
ManifestClient,
|
||||
WrapperPlaceOrderParamsExternal,
|
||||
OrderType,
|
||||
WrapperPlaceOrderParamsExternal,
|
||||
} from "@cks-systems/manifest-sdk";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { BatchOrderPattern, OrderParams } from "../types";
|
||||
import {
|
||||
Keypair,
|
||||
PublicKey,
|
||||
sendAndConfirmTransaction,
|
||||
SystemProgram,
|
||||
Transaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { BatchOrderPattern, OrderParams, SolanaAgentKit } from "../index";
|
||||
|
||||
export async function manifestCreateMarket(
|
||||
agent: SolanaAgentKit,
|
||||
baseMint: PublicKey,
|
||||
quoteMint: PublicKey,
|
||||
): Promise<string[]> {
|
||||
const marketKeypair: Keypair = Keypair.generate();
|
||||
const FIXED_MANIFEST_HEADER_SIZE: number = 256;
|
||||
const createAccountIx: TransactionInstruction = SystemProgram.createAccount({
|
||||
fromPubkey: agent.wallet.publicKey,
|
||||
newAccountPubkey: marketKeypair.publicKey,
|
||||
space: FIXED_MANIFEST_HEADER_SIZE,
|
||||
lamports: await agent.connection.getMinimumBalanceForRentExemption(
|
||||
FIXED_MANIFEST_HEADER_SIZE,
|
||||
),
|
||||
programId: new PublicKey("MNFSTqtC93rEfYHB6hF82sKdZpUDFWkViLByLd1k1Ms"),
|
||||
});
|
||||
const createMarketIx = ManifestClient["createMarketIx"](
|
||||
agent.wallet.publicKey,
|
||||
baseMint,
|
||||
quoteMint,
|
||||
marketKeypair.publicKey,
|
||||
);
|
||||
|
||||
const tx: Transaction = new Transaction();
|
||||
tx.add(createAccountIx);
|
||||
tx.add(createMarketIx);
|
||||
const signature = await sendAndConfirmTransaction(agent.connection, tx, [
|
||||
agent.wallet,
|
||||
marketKeypair,
|
||||
]);
|
||||
return [signature, marketKeypair.publicKey.toBase58()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Place limit orders using Manifest
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param marketId Public key for the manifest market
|
||||
* @param quantity Amount to trade in tokens
|
||||
* @param side Buy or Sell
|
||||
* @param price Price in tokens ie. SOL/USDC
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function limitOrder(
|
||||
agent: SolanaAgentKit,
|
||||
marketId: PublicKey,
|
||||
quantity: number,
|
||||
side: string,
|
||||
price: number,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const mfxClient = await ManifestClient.getClientForMarket(
|
||||
agent.connection,
|
||||
marketId,
|
||||
agent.wallet,
|
||||
);
|
||||
|
||||
const orderParams: WrapperPlaceOrderParamsExternal = {
|
||||
numBaseTokens: quantity,
|
||||
tokenPrice: price,
|
||||
isBid: side === "Buy",
|
||||
lastValidSlot: 0,
|
||||
orderType: OrderType.Limit,
|
||||
clientOrderId: Number(Math.random() * 1000),
|
||||
};
|
||||
|
||||
const depositPlaceOrderIx: TransactionInstruction[] =
|
||||
await mfxClient.placeOrderWithRequiredDepositIx(
|
||||
agent.wallet.publicKey,
|
||||
orderParams,
|
||||
);
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
agent.connection,
|
||||
new Transaction().add(...depositPlaceOrderIx),
|
||||
[agent.wallet],
|
||||
);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Limit Order failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all orders from Manifest
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param marketId Public key for the manifest market
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function cancelAllOrders(
|
||||
agent: SolanaAgentKit,
|
||||
marketId: PublicKey,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const mfxClient = await ManifestClient.getClientForMarket(
|
||||
agent.connection,
|
||||
marketId,
|
||||
agent.wallet,
|
||||
);
|
||||
|
||||
const cancelAllOrdersIx = await mfxClient.cancelAllIx();
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
agent.connection,
|
||||
new Transaction().add(cancelAllOrdersIx),
|
||||
[agent.wallet],
|
||||
);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Cancel all orders failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Withdraws all funds from Manifest
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param marketId Public key for the manifest market
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function withdrawAll(
|
||||
agent: SolanaAgentKit,
|
||||
marketId: PublicKey,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const mfxClient = await ManifestClient.getClientForMarket(
|
||||
agent.connection,
|
||||
marketId,
|
||||
agent.wallet,
|
||||
);
|
||||
|
||||
const withdrawAllIx = await mfxClient.withdrawAllIx();
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
agent.connection,
|
||||
new Transaction().add(...withdrawAllIx),
|
||||
[agent.wallet],
|
||||
);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Withdraw all failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of orders based on the specified pattern
|
||||
@@ -1,37 +0,0 @@
|
||||
import {
|
||||
PublicKey,
|
||||
sendAndConfirmTransaction,
|
||||
Transaction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { ManifestClient } from "@cks-systems/manifest-sdk";
|
||||
|
||||
/**
|
||||
* Withdraws all funds from Manifest
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param marketId Public key for the manifest market
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function withdrawAll(
|
||||
agent: SolanaAgentKit,
|
||||
marketId: PublicKey,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const mfxClient = await ManifestClient.getClientForMarket(
|
||||
agent.connection,
|
||||
marketId,
|
||||
agent.wallet,
|
||||
);
|
||||
|
||||
const withdrawAllIx = await mfxClient.withdrawAllIx();
|
||||
const signature = await sendAndConfirmTransaction(
|
||||
agent.connection,
|
||||
new Transaction().add(...withdrawAllIx),
|
||||
[agent.wallet],
|
||||
);
|
||||
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Withdraw all failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export interface Config {
|
||||
OPENAI_API_KEY?: string;
|
||||
JUPITER_REFERRAL_ACCOUNT?: string;
|
||||
JUPITER_FEE_BPS?: number;
|
||||
FLASH_PRIVILEGE?: string;
|
||||
}
|
||||
|
||||
export interface Creator {
|
||||
@@ -224,3 +225,15 @@ export interface BatchOrderPattern {
|
||||
numberOfOrders?: number;
|
||||
individualQuantity?: number;
|
||||
}
|
||||
|
||||
export interface FlashTradeParams {
|
||||
token: string;
|
||||
side: "long" | "short";
|
||||
collateralUsd: number;
|
||||
leverage: number;
|
||||
}
|
||||
|
||||
export interface FlashCloseTradeParams {
|
||||
token: string;
|
||||
side: "long" | "short";
|
||||
}
|
||||
|
||||
300
src/utils/flashUtils.ts
Normal file
300
src/utils/flashUtils.ts
Normal file
@@ -0,0 +1,300 @@
|
||||
import { HermesClient } from "@pythnetwork/hermes-client";
|
||||
import { OraclePrice } from "flash-sdk";
|
||||
import { AnchorProvider, BN, Wallet } from "@coral-xyz/anchor";
|
||||
import {
|
||||
PoolConfig,
|
||||
Token,
|
||||
Referral,
|
||||
PerpetualsClient,
|
||||
Privilege,
|
||||
} from "flash-sdk";
|
||||
import { Cluster, PublicKey, Connection, Keypair } from "@solana/web3.js";
|
||||
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
|
||||
const POOL_NAMES = [
|
||||
"Crypto.1",
|
||||
"Virtual.1",
|
||||
"Governance.1",
|
||||
"Community.1",
|
||||
"Community.2",
|
||||
"Community.3",
|
||||
];
|
||||
|
||||
const DEFAULT_CLUSTER: Cluster = "mainnet-beta";
|
||||
export const POOL_CONFIGS = POOL_NAMES.map((f) =>
|
||||
PoolConfig.fromIdsByName(f, DEFAULT_CLUSTER),
|
||||
);
|
||||
|
||||
const DUPLICATE_TOKENS = POOL_CONFIGS.map((f) => f.tokens).flat();
|
||||
const tokenMap = new Map();
|
||||
for (const token of DUPLICATE_TOKENS) {
|
||||
tokenMap.set(token.symbol, token);
|
||||
}
|
||||
export const ALL_TOKENS: Token[] = Array.from(tokenMap.values());
|
||||
export const ALL_CUSTODIES = POOL_CONFIGS.map((f) => f.custodies).flat();
|
||||
const PROGRAM_ID = POOL_CONFIGS[0].programId;
|
||||
|
||||
// CU for trade instructions
|
||||
export const OPEN_POSITION_CU = 150_000;
|
||||
export const CLOSE_POSITION_CU = 180_000;
|
||||
|
||||
const HERMES_URL = "https://hermes.pyth.network"; // Replace with the actual Hermes URL if different
|
||||
|
||||
// Create a map of symbol to Pyth price ID
|
||||
const PRICE_FEED_IDS = ALL_TOKENS.reduce(
|
||||
(acc, token) => {
|
||||
acc[token.symbol] = token.pythPriceId;
|
||||
return acc;
|
||||
},
|
||||
{} as { [key: string]: string },
|
||||
);
|
||||
|
||||
const hermesClient = new HermesClient(HERMES_URL, {});
|
||||
|
||||
export interface PythPriceEntry {
|
||||
price: OraclePrice;
|
||||
emaPrice: OraclePrice;
|
||||
isStale: boolean;
|
||||
status: PriceStatus;
|
||||
}
|
||||
|
||||
export enum PriceStatus {
|
||||
Trading,
|
||||
Unknown,
|
||||
Halted,
|
||||
Auction,
|
||||
}
|
||||
|
||||
export const fetchOraclePrice = async (
|
||||
symbol: string,
|
||||
): Promise<PythPriceEntry> => {
|
||||
const priceFeedId = PRICE_FEED_IDS[symbol];
|
||||
if (!priceFeedId) {
|
||||
throw new Error(`Price feed ID not found for symbol: ${symbol}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const hermesPriceFeed = await hermesClient.getPriceFeeds({
|
||||
query: symbol,
|
||||
filter: "crypto",
|
||||
});
|
||||
|
||||
if (!hermesPriceFeed || hermesPriceFeed.length === 0) {
|
||||
throw new Error(`No price feed received for ${symbol}`);
|
||||
}
|
||||
|
||||
const hemrmesPriceUdpate = await hermesClient.getLatestPriceUpdates(
|
||||
[priceFeedId],
|
||||
{
|
||||
encoding: "hex",
|
||||
parsed: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (!hemrmesPriceUdpate.parsed) {
|
||||
throw new Error(`No price feed received for ${symbol}`);
|
||||
}
|
||||
const hermesEma = hemrmesPriceUdpate.parsed[0].ema_price;
|
||||
const hermesPrice = hemrmesPriceUdpate.parsed[0].price;
|
||||
|
||||
const hermesPriceOracle = new OraclePrice({
|
||||
price: new BN(hermesPrice.price),
|
||||
exponent: new BN(hermesPrice.expo),
|
||||
confidence: new BN(hermesPrice.conf),
|
||||
timestamp: new BN(hermesPrice.publish_time),
|
||||
});
|
||||
|
||||
const hermesEmaOracle = new OraclePrice({
|
||||
price: new BN(hermesEma.price),
|
||||
exponent: new BN(hermesEma.expo),
|
||||
confidence: new BN(hermesEma.conf),
|
||||
timestamp: new BN(hermesEma.publish_time),
|
||||
});
|
||||
|
||||
const token = ALL_TOKENS.find((t) => t.pythPriceId === priceFeedId);
|
||||
if (!token) {
|
||||
throw new Error(`Token not found for price feed ID: ${priceFeedId}`);
|
||||
}
|
||||
|
||||
const status = !token.isVirtual ? PriceStatus.Trading : PriceStatus.Unknown;
|
||||
|
||||
const pythPriceEntry: PythPriceEntry = {
|
||||
price: hermesPriceOracle,
|
||||
emaPrice: hermesEmaOracle,
|
||||
isStale: false,
|
||||
status: status,
|
||||
};
|
||||
|
||||
return pythPriceEntry;
|
||||
} catch (error) {
|
||||
console.error(`Error in fetchOraclePrice for ${symbol}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export interface MarketInfo {
|
||||
[key: string]: {
|
||||
tokenPair: string;
|
||||
token: string;
|
||||
side: string;
|
||||
pool: string;
|
||||
};
|
||||
}
|
||||
|
||||
const marketSdkInfo: MarketInfo = {};
|
||||
|
||||
// Loop through POOL_CONFIGS to process each market
|
||||
POOL_CONFIGS.forEach((poolConfig) => {
|
||||
poolConfig.markets.forEach((market) => {
|
||||
const targetToken = ALL_TOKENS.find(
|
||||
(token) => token.mintKey.toString() === market.targetMint.toString(),
|
||||
);
|
||||
|
||||
// Find collateral token by matching mintKey
|
||||
const collateralToken = ALL_TOKENS.find(
|
||||
(token) => token.mintKey.toString() === market.collateralMint.toString(),
|
||||
);
|
||||
|
||||
if (targetToken?.symbol && collateralToken?.symbol) {
|
||||
marketSdkInfo[market.marketAccount.toString()] = {
|
||||
tokenPair: `${targetToken.symbol}/${collateralToken.symbol}`,
|
||||
token: targetToken.symbol,
|
||||
side: Object.keys(market.side)[0],
|
||||
pool: poolConfig.poolName,
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export { marketSdkInfo };
|
||||
|
||||
export interface MarketTokenSides {
|
||||
[token: string]: {
|
||||
long?: { marketID: string };
|
||||
short?: { marketID: string };
|
||||
};
|
||||
}
|
||||
|
||||
const marketTokenMap: MarketTokenSides = {};
|
||||
|
||||
// Convert marketSdkInfo into marketTokenMap
|
||||
Object.entries(marketSdkInfo).forEach(([marketID, info]) => {
|
||||
if (!marketTokenMap[info.token]) {
|
||||
marketTokenMap[info.token] = {};
|
||||
}
|
||||
|
||||
marketTokenMap[info.token][info.side.toLowerCase() as "long" | "short"] = {
|
||||
marketID,
|
||||
};
|
||||
});
|
||||
|
||||
export { marketTokenMap };
|
||||
|
||||
interface TradingAccountResult {
|
||||
nftReferralAccountPK: PublicKey | null;
|
||||
nftTradingAccountPk: PublicKey | null;
|
||||
nftOwnerRebateTokenAccountPk: PublicKey | null;
|
||||
}
|
||||
|
||||
export async function getNftTradingAccountInfo(
|
||||
userPublicKey: PublicKey,
|
||||
perpClient: PerpetualsClient,
|
||||
poolConfig: PoolConfig,
|
||||
collateralCustodySymbol: string,
|
||||
): Promise<TradingAccountResult> {
|
||||
const getNFTReferralAccountPK = (publicKey: PublicKey) => {
|
||||
return PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("referral"), publicKey.toBuffer()],
|
||||
PROGRAM_ID,
|
||||
)[0];
|
||||
};
|
||||
const nftReferralAccountPK = getNFTReferralAccountPK(userPublicKey);
|
||||
const nftReferralAccountInfo =
|
||||
await perpClient.provider.connection.getAccountInfo(nftReferralAccountPK);
|
||||
|
||||
let nftTradingAccountPk: PublicKey | null = null;
|
||||
let nftOwnerRebateTokenAccountPk: PublicKey | null = null;
|
||||
|
||||
if (nftReferralAccountInfo) {
|
||||
const nftReferralAccountData = perpClient.program.coder.accounts.decode(
|
||||
"referral",
|
||||
nftReferralAccountInfo.data,
|
||||
) as Referral;
|
||||
|
||||
nftTradingAccountPk = nftReferralAccountData.refererTradingAccount;
|
||||
|
||||
if (nftTradingAccountPk) {
|
||||
const nftTradingAccountInfo =
|
||||
await perpClient.provider.connection.getAccountInfo(
|
||||
nftTradingAccountPk,
|
||||
);
|
||||
if (nftTradingAccountInfo) {
|
||||
const nftTradingAccount = perpClient.program.coder.accounts.decode(
|
||||
"trading",
|
||||
nftTradingAccountInfo.data,
|
||||
) as { owner: PublicKey };
|
||||
|
||||
nftOwnerRebateTokenAccountPk = getAssociatedTokenAddressSync(
|
||||
poolConfig.getTokenFromSymbol(collateralCustodySymbol).mintKey,
|
||||
nftTradingAccount.owner,
|
||||
);
|
||||
// Check if the account exists
|
||||
const accountExists =
|
||||
await perpClient.provider.connection.getAccountInfo(
|
||||
nftOwnerRebateTokenAccountPk,
|
||||
);
|
||||
if (!accountExists) {
|
||||
console.error(
|
||||
"NFT owner rebate token account does not exist and may need to be created",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
nftReferralAccountPK,
|
||||
nftTradingAccountPk,
|
||||
nftOwnerRebateTokenAccountPk,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new PerpetualsClient instance with the given connection and wallet
|
||||
* @param connection Solana connection
|
||||
* @param wallet Solana wallet
|
||||
* @returns PerpetualsClient instance
|
||||
*/
|
||||
export function createPerpClient(
|
||||
connection: Connection,
|
||||
wallet: Keypair,
|
||||
): PerpetualsClient {
|
||||
const provider = new AnchorProvider(connection, new Wallet(wallet), {
|
||||
commitment: "confirmed",
|
||||
preflightCommitment: "confirmed",
|
||||
skipPreflight: true,
|
||||
});
|
||||
|
||||
return new PerpetualsClient(
|
||||
provider,
|
||||
POOL_CONFIGS[0].programId,
|
||||
POOL_CONFIGS[0].perpComposibilityProgramId,
|
||||
POOL_CONFIGS[0].fbNftRewardProgramId,
|
||||
POOL_CONFIGS[0].rewardDistributionProgram.programId,
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
export function get_flash_privilege(agent: SolanaAgentKit): Privilege {
|
||||
const FLASH_PRIVILEGE = agent.config.FLASH_PRIVILEGE || "None";
|
||||
|
||||
switch (FLASH_PRIVILEGE.toLowerCase()) {
|
||||
case "referral":
|
||||
return Privilege.Referral;
|
||||
case "nft":
|
||||
return Privilege.NFT;
|
||||
default:
|
||||
return Privilege.None;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user