mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-24 15:10:46 +00:00
Merge branch 'main' into feat/squads_multisig
This commit is contained in:
62
src/actions/balance.ts
Normal file
62
src/actions/balance.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { get_balance } from "../tools";
|
||||
|
||||
const balanceAction: Action = {
|
||||
name: "BALANCE_ACTION",
|
||||
similes: [
|
||||
"check balance",
|
||||
"get wallet balance",
|
||||
"view balance",
|
||||
"show balance",
|
||||
"check token balance",
|
||||
],
|
||||
description: `Get the balance of a Solana wallet or token account.
|
||||
If you want to get the balance of your wallet, you don't need to provide the tokenAddress.
|
||||
If no tokenAddress is provided, the balance will be in SOL.`,
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {},
|
||||
output: {
|
||||
status: "success",
|
||||
balance: "100",
|
||||
token: "SOL",
|
||||
},
|
||||
explanation: "Get SOL balance of the wallet",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {
|
||||
tokenAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
balance: "1000",
|
||||
token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
},
|
||||
explanation: "Get USDC token balance",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
tokenAddress: z.string().optional(),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
const balance = await get_balance(
|
||||
agent,
|
||||
input.tokenAddress && new PublicKey(input.tokenAddress),
|
||||
);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
balance: balance,
|
||||
token: input.tokenAddress || "SOL",
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default balanceAction;
|
||||
104
src/actions/compressedAirdrop.ts
Normal file
104
src/actions/compressedAirdrop.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { sendCompressedAirdrop } from "../tools";
|
||||
|
||||
const compressedAirdropAction: Action = {
|
||||
name: "COMPRESSED_AIRDROP",
|
||||
similes: [
|
||||
"ZK Compressed airdrop",
|
||||
"Airdrop tokens with compression",
|
||||
"Send compressed SPL airdrop",
|
||||
"Airdrop to multiple recipients",
|
||||
],
|
||||
description:
|
||||
"Airdrop SPL tokens with ZK Compression (also known as airdropping tokens) to multiple recipients",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
mintAddress: "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
|
||||
amount: 42,
|
||||
decimals: 6,
|
||||
recipients: [
|
||||
"1nc1nerator11111111111111111111111111111111",
|
||||
"BrFndAe111111111111111111111111111111111",
|
||||
],
|
||||
priorityFeeInLamports: 30000,
|
||||
shouldLog: true,
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Airdropped 42 tokens to 2 recipients.",
|
||||
transactionHashes: ["4uyfBN...", "9XsF2N..."],
|
||||
},
|
||||
explanation:
|
||||
"Airdrops 42 tokens (with 6 decimals) to 2 recipients, optionally logging progress to stdout.",
|
||||
},
|
||||
],
|
||||
],
|
||||
// Validate inputs with zod
|
||||
schema: z.object({
|
||||
mintAddress: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe("Mint address of the token, e.g., 'JUPy...'"),
|
||||
amount: z
|
||||
.number()
|
||||
.positive()
|
||||
.describe("Number of tokens to airdrop per recipient, e.g., 42"),
|
||||
decimals: z
|
||||
.number()
|
||||
.nonnegative()
|
||||
.int()
|
||||
.describe("Decimals of the token, e.g., 6"),
|
||||
recipients: z
|
||||
.array(z.string())
|
||||
.nonempty()
|
||||
.describe("Array of recipient addresses, e.g., ['1nc1n...']"),
|
||||
priorityFeeInLamports: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Priority fee in lamports (default is 30_000)"),
|
||||
shouldLog: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Whether to log progress to stdout (default is false)"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const {
|
||||
mintAddress,
|
||||
amount,
|
||||
decimals,
|
||||
recipients,
|
||||
priorityFeeInLamports,
|
||||
shouldLog,
|
||||
} = input;
|
||||
|
||||
// Call your airdrop method on the SolanaAgentKit
|
||||
const txs = await sendCompressedAirdrop(
|
||||
mintAddress,
|
||||
amount,
|
||||
decimals,
|
||||
recipients,
|
||||
priorityFeeInLamports || 30_000,
|
||||
shouldLog || false,
|
||||
);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
message: `Airdropped ${amount} tokens to ${recipients.length} recipients.`,
|
||||
transactionHashes: txs,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to airdrop tokens: ${error.message}`,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default compressedAirdropAction;
|
||||
86
src/actions/createGibworkTask.ts
Normal file
86
src/actions/createGibworkTask.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { create_gibwork_task } from "../tools";
|
||||
|
||||
const createGibworkTaskAction: Action = {
|
||||
name: "CREATE_GIBWORK_TASK",
|
||||
similes: [
|
||||
"create task",
|
||||
"post job",
|
||||
"create gig",
|
||||
"post task",
|
||||
"create work",
|
||||
"new task on gibwork",
|
||||
],
|
||||
description:
|
||||
"Create a new task on the Gibwork platform with payment in SPL tokens",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
title: "Build a Solana dApp",
|
||||
content: "Create a simple Solana dApp with React frontend",
|
||||
requirements: "Experience with Rust and React",
|
||||
tags: ["solana", "rust", "react"],
|
||||
tokenMintAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
tokenAmount: 100,
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
taskId: "task_123",
|
||||
signature: "3YKpM1...",
|
||||
message: "Successfully created task: Build a Solana dApp",
|
||||
},
|
||||
explanation: "Create a new task on Gibwork with 100 USDC payment",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
title: z.string().min(1).describe("Title of the task"),
|
||||
content: z.string().min(1).describe("Description of the task"),
|
||||
requirements: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe("Requirements to complete the task"),
|
||||
tags: z
|
||||
.array(z.string())
|
||||
.min(1)
|
||||
.describe("List of tags associated with the task"),
|
||||
tokenMintAddress: z.string().describe("Token mint address for payment"),
|
||||
tokenAmount: z.number().positive().describe("Payment amount for the task"),
|
||||
payer: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Optional payer address (defaults to wallet address)"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const responseData = await create_gibwork_task(
|
||||
agent,
|
||||
input.title,
|
||||
input.content,
|
||||
input.requirements,
|
||||
input.tags,
|
||||
new PublicKey(input.tokenMintAddress),
|
||||
input.tokenAmount,
|
||||
input.payer ? new PublicKey(input.payer) : undefined,
|
||||
);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
taskId: responseData.taskId,
|
||||
signature: responseData.signature,
|
||||
message: `Successfully created task: ${input.title}`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to create task: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default createGibworkTaskAction;
|
||||
101
src/actions/createImage.ts
Normal file
101
src/actions/createImage.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { create_image } from "../tools/create_image";
|
||||
|
||||
const createImageAction: Action = {
|
||||
name: "CREATE_IMAGE",
|
||||
similes: [
|
||||
"generate image",
|
||||
"create artwork",
|
||||
"make image",
|
||||
"generate artwork",
|
||||
"create picture",
|
||||
"generate picture",
|
||||
],
|
||||
description:
|
||||
"Create an AI-generated image based on a text prompt using OpenAI's DALL-E models",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
prompt: "A beautiful sunset over a mountain landscape",
|
||||
model: "dall-e-3",
|
||||
size: "1024x1024",
|
||||
quality: "standard",
|
||||
style: "natural",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
imageUrl: "https://example.com/image.png",
|
||||
message: "Successfully generated image",
|
||||
},
|
||||
explanation: "Generate an image of a sunset landscape using DALL-E 3",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
prompt: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(1000)
|
||||
.describe("The text description of the image to generate"),
|
||||
model: z
|
||||
.enum(["dall-e-3"])
|
||||
.default("dall-e-3")
|
||||
.describe("The AI model to use for generation"),
|
||||
size: z
|
||||
.enum(["256x256", "512x512", "1024x1024", "1792x1024", "1024x1792"])
|
||||
.default("1024x1024")
|
||||
.describe("The size of the generated image"),
|
||||
quality: z
|
||||
.enum(["standard", "hd"])
|
||||
.default("standard")
|
||||
.describe("The quality level of the generated image"),
|
||||
style: z
|
||||
.enum(["natural", "vivid"])
|
||||
.default("natural")
|
||||
.describe("The style of the generated image"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
if (!agent.config.OPENAI_API_KEY) {
|
||||
return {
|
||||
status: "error",
|
||||
message: "OpenAI API key not found in agent configuration",
|
||||
};
|
||||
}
|
||||
|
||||
const { prompt, model, size } = input;
|
||||
const response = await create_image(agent, prompt, model, size);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
imageUrl: response.images[0].url,
|
||||
message: "Successfully generated image",
|
||||
};
|
||||
} catch (error: any) {
|
||||
// Handle specific OpenAI error types
|
||||
if (error.response) {
|
||||
const { status, data } = error.response;
|
||||
if (status === 429) {
|
||||
return {
|
||||
status: "error",
|
||||
message: "Rate limit exceeded. Please try again later.",
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: "error",
|
||||
message: `OpenAI API error: ${data.error?.message || error.message}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to generate image: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default createImageAction;
|
||||
80
src/actions/createOpenbookMarket.ts
Normal file
80
src/actions/createOpenbookMarket.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { openbookCreateMarket } from "../tools";
|
||||
|
||||
const createOpenbookMarketAction: Action = {
|
||||
name: "CREATE_OPENBOOK_MARKET",
|
||||
similes: [
|
||||
"create openbook market",
|
||||
"setup trading market",
|
||||
"new openbook market",
|
||||
"create trading pair",
|
||||
"setup dex market",
|
||||
"new trading market",
|
||||
],
|
||||
description: "Create a new trading market on Openbook DEX",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
baseMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
|
||||
quoteMint: "So11111111111111111111111111111111111111112", // SOL
|
||||
lotSize: 1,
|
||||
tickSize: 0.01,
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
signatures: ["2ZE7Rz...", "3YKpM1..."],
|
||||
message: "Successfully created Openbook market",
|
||||
},
|
||||
explanation:
|
||||
"Create a new USDC/SOL market on Openbook with default lot and tick sizes",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
baseMint: z.string().min(1).describe("The base token's mint address"),
|
||||
quoteMint: z.string().min(1).describe("The quote token's mint address"),
|
||||
lotSize: z
|
||||
.number()
|
||||
.positive()
|
||||
.default(1)
|
||||
.describe("The minimum order size (lot size)"),
|
||||
tickSize: z
|
||||
.number()
|
||||
.positive()
|
||||
.default(0.01)
|
||||
.describe("The minimum price increment (tick size)"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const baseMint = new PublicKey(input.baseMint);
|
||||
const quoteMint = new PublicKey(input.quoteMint);
|
||||
const lotSize = input.lotSize || 1;
|
||||
const tickSize = input.tickSize || 0.01;
|
||||
|
||||
const signatures = await openbookCreateMarket(
|
||||
agent,
|
||||
baseMint,
|
||||
quoteMint,
|
||||
lotSize,
|
||||
tickSize,
|
||||
);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
signatures,
|
||||
message: "Successfully created Openbook market",
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to create Openbook market: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default createOpenbookMarketAction;
|
||||
116
src/actions/createOrcaSingleSidedWhirlpool.ts
Normal file
116
src/actions/createOrcaSingleSidedWhirlpool.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { orcaCreateSingleSidedLiquidityPool } from "../tools";
|
||||
|
||||
// Fee tiers mapping from the original tool
|
||||
const FEE_TIERS = {
|
||||
0.01: 1,
|
||||
0.02: 2,
|
||||
0.04: 4,
|
||||
0.05: 8,
|
||||
0.16: 16,
|
||||
0.3: 64,
|
||||
0.65: 96,
|
||||
1.0: 128,
|
||||
2.0: 256,
|
||||
} as const;
|
||||
|
||||
const createOrcaSingleSidedWhirlpoolAction: Action = {
|
||||
name: "CREATE_ORCA_SINGLE_SIDED_WHIRLPOOL",
|
||||
similes: [
|
||||
"create orca whirlpool",
|
||||
"setup orca single sided pool",
|
||||
"initialize orca whirlpool",
|
||||
"create orca concentrated pool",
|
||||
"setup orca concentrated liquidity",
|
||||
"create orca trading pair",
|
||||
],
|
||||
description:
|
||||
"Create a new single-sided whirlpool on Orca with concentrated liquidity",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
depositTokenAmount: "1000000000000", // 1 million tokens with 6 decimals
|
||||
depositTokenMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
|
||||
otherTokenMint: "So11111111111111111111111111111111111111112", // SOL
|
||||
initialPrice: "0.001",
|
||||
maxPrice: "5.0",
|
||||
feeTier: 0.3,
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
signature: "2ZE7Rz...",
|
||||
message: "Successfully created Orca single-sided whirlpool",
|
||||
},
|
||||
explanation:
|
||||
"Create a USDC/SOL whirlpool with 1M USDC initial liquidity",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
depositTokenAmount: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe(
|
||||
"The amount of deposit token to provide as liquidity (including decimals)",
|
||||
),
|
||||
depositTokenMint: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe("The mint address of the token being deposited"),
|
||||
otherTokenMint: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe("The mint address of the other token in the pool"),
|
||||
initialPrice: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe("Initial price of deposit token in terms of the other token"),
|
||||
maxPrice: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe("Maximum price at which liquidity is added"),
|
||||
feeTier: z
|
||||
.number()
|
||||
.refine((val) => val in FEE_TIERS, "Invalid fee tier")
|
||||
.describe("Fee tier percentage for the pool (e.g., 0.3 for 0.3%)"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const depositTokenAmount = Number(input.depositTokenAmount);
|
||||
const depositTokenMint = new PublicKey(input.depositTokenMint);
|
||||
const otherTokenMint = new PublicKey(input.otherTokenMint);
|
||||
const initialPrice = new Decimal(input.initialPrice);
|
||||
const maxPrice = new Decimal(input.maxPrice);
|
||||
const feeTier = input.feeTier;
|
||||
|
||||
// Create the whirlpool
|
||||
const signature = await orcaCreateSingleSidedLiquidityPool(
|
||||
agent,
|
||||
depositTokenAmount,
|
||||
depositTokenMint,
|
||||
otherTokenMint,
|
||||
initialPrice,
|
||||
maxPrice,
|
||||
feeTier,
|
||||
);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
signature,
|
||||
message: "Successfully created Orca single-sided whirlpool",
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to create whirlpool: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default createOrcaSingleSidedWhirlpoolAction;
|
||||
78
src/actions/deployCollection.ts
Normal file
78
src/actions/deployCollection.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { deploy_collection } from "../tools";
|
||||
|
||||
interface CollectionOptions {
|
||||
name: string;
|
||||
uri: string;
|
||||
royaltyBasisPoints?: number;
|
||||
}
|
||||
|
||||
const deployCollectionAction: Action = {
|
||||
name: "DEPLOY_COLLECTION",
|
||||
similes: [
|
||||
"create collection",
|
||||
"launch collection",
|
||||
"deploy nft collection",
|
||||
"create nft collection",
|
||||
"mint collection",
|
||||
],
|
||||
description: `Deploy a new NFT collection on Solana blockchain.`,
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
name: "My Collection",
|
||||
uri: "https://example.com/collection.json",
|
||||
royaltyBasisPoints: 500,
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Collection deployed successfully",
|
||||
collectionAddress: "7nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkN",
|
||||
name: "My Collection",
|
||||
},
|
||||
explanation: "Deploy an NFT collection with 5% royalty",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {
|
||||
name: "Basic Collection",
|
||||
uri: "https://example.com/basic.json",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Collection deployed successfully",
|
||||
collectionAddress: "8nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkM",
|
||||
name: "Basic Collection",
|
||||
},
|
||||
explanation: "Deploy a basic NFT collection without royalties",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
uri: z.string().url("URI must be a valid URL"),
|
||||
royaltyBasisPoints: z.number().min(0).max(10000).optional(),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
const options: CollectionOptions = {
|
||||
name: input.name,
|
||||
uri: input.uri,
|
||||
royaltyBasisPoints: input.royaltyBasisPoints,
|
||||
};
|
||||
|
||||
const result = await deploy_collection(agent, options);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
message: "Collection deployed successfully",
|
||||
collectionAddress: result.collectionAddress.toString(),
|
||||
name: input.name,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default deployCollectionAction;
|
||||
83
src/actions/deployToken.ts
Normal file
83
src/actions/deployToken.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { deploy_token } from "../tools";
|
||||
|
||||
const deployTokenAction: Action = {
|
||||
name: "DEPLOY_TOKEN",
|
||||
similes: [
|
||||
"create token",
|
||||
"launch token",
|
||||
"deploy new token",
|
||||
"create new token",
|
||||
"mint token",
|
||||
],
|
||||
description:
|
||||
"Deploy a new SPL token on the Solana blockchain with specified parameters",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
name: "My Token",
|
||||
uri: "https://example.com/token.json",
|
||||
symbol: "MTK",
|
||||
decimals: 9,
|
||||
initialSupply: 1000000,
|
||||
},
|
||||
output: {
|
||||
mint: "7nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkN",
|
||||
status: "success",
|
||||
message: "Token deployed successfully",
|
||||
},
|
||||
explanation: "Deploy a token with initial supply and metadata",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {
|
||||
name: "Basic Token",
|
||||
uri: "https://example.com/basic.json",
|
||||
symbol: "BASIC",
|
||||
},
|
||||
output: {
|
||||
mint: "8nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkM",
|
||||
status: "success",
|
||||
message: "Token deployed successfully",
|
||||
},
|
||||
explanation: "Deploy a basic token with minimal parameters",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
uri: z.string().url("URI must be a valid URL"),
|
||||
symbol: z.string().min(1, "Symbol is required"),
|
||||
decimals: z.number().optional(),
|
||||
initialSupply: z.number().optional(),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const result = await deploy_token(
|
||||
agent,
|
||||
input.name,
|
||||
input.uri,
|
||||
input.symbol,
|
||||
input.decimals,
|
||||
input.initialSupply,
|
||||
);
|
||||
|
||||
return {
|
||||
mint: result.mint.toString(),
|
||||
status: "success",
|
||||
message: "Token deployed successfully",
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Token deployment failed: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default deployTokenAction;
|
||||
57
src/actions/fetchPrice.ts
Normal file
57
src/actions/fetchPrice.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { fetchPrice } from "../tools";
|
||||
|
||||
const fetchPriceAction: Action = {
|
||||
name: "FETCH_PRICE",
|
||||
similes: [
|
||||
"get token price",
|
||||
"check price",
|
||||
"token value",
|
||||
"price check",
|
||||
"get price in usd",
|
||||
],
|
||||
description:
|
||||
"Fetch the current price of a Solana token in USDC using Jupiter API",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
tokenAddress: "So11111111111111111111111111111111111111112",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
price: "23.45",
|
||||
message: "Current price: $23.45 USDC",
|
||||
},
|
||||
explanation: "Get the current price of SOL token in USDC",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
tokenAddress: z
|
||||
.string()
|
||||
.describe("The mint address of the token to fetch the price for"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const tokenId = new PublicKey(input.tokenAddress);
|
||||
const price = await fetchPrice(tokenId);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
price,
|
||||
message: `Current price: $${price} USDC`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to fetch price: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default fetchPriceAction;
|
||||
52
src/actions/getAllDomainsTLDs.ts
Normal file
52
src/actions/getAllDomainsTLDs.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { getAllDomainsTLDs } from "../tools";
|
||||
|
||||
const getAllDomainsTLDsAction: Action = {
|
||||
name: "GET_ALL_TLDS",
|
||||
similes: [
|
||||
"list domain tlds",
|
||||
"get domain extensions",
|
||||
"fetch domain tlds",
|
||||
"get top level domains",
|
||||
"list available tlds",
|
||||
"get domain suffixes",
|
||||
],
|
||||
description:
|
||||
"Get a list of all available top-level domains (TLDs) for Solana domains",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {},
|
||||
output: {
|
||||
status: "success",
|
||||
tlds: [".sol", ".abc", ".backpack", ".bonk"],
|
||||
message: "Successfully retrieved all domain TLDs",
|
||||
},
|
||||
explanation:
|
||||
"Get a list of all available TLDs that can be used for Solana domains",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({}),
|
||||
handler: async (agent: SolanaAgentKit) => {
|
||||
try {
|
||||
// Get all domain TLDs
|
||||
const tlds = await getAllDomainsTLDs(agent);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
tlds,
|
||||
message: "Successfully retrieved all domain TLDs",
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to get domain TLDs: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default getAllDomainsTLDsAction;
|
||||
70
src/actions/getAllRegisteredAllDomains.ts
Normal file
70
src/actions/getAllRegisteredAllDomains.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { getAllRegisteredAllDomains } from "../tools";
|
||||
|
||||
const getAllRegisteredAllDomainsAction: Action = {
|
||||
name: "GET_ALL_REGISTERED_ALL_DOMAINS",
|
||||
similes: [
|
||||
"list registered domains",
|
||||
"get all domains",
|
||||
"fetch registered domains",
|
||||
"get domain list",
|
||||
"list active domains",
|
||||
"get registered names",
|
||||
],
|
||||
description: "Get a list of all registered domains across all TLDs",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
limit: 100,
|
||||
offset: 0,
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
domains: ["solana.sol", "bonk.abc", "wallet.backpack"],
|
||||
total: 3,
|
||||
message: "Successfully retrieved registered domains",
|
||||
},
|
||||
explanation: "Get the first 100 registered domains across all TLDs",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
limit: z
|
||||
.number()
|
||||
.positive()
|
||||
.max(1000)
|
||||
.default(100)
|
||||
.describe("Maximum number of domains to return"),
|
||||
offset: z
|
||||
.number()
|
||||
.nonnegative()
|
||||
.default(0)
|
||||
.describe("Number of domains to skip"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const limit = input.limit || 100;
|
||||
const offset = input.offset || 0;
|
||||
|
||||
// Get all registered domains
|
||||
const domains = await getAllRegisteredAllDomains(agent);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
domains: domains.slice(offset, offset + limit),
|
||||
total: domains.length,
|
||||
message: "Successfully retrieved registered domains",
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to get registered domains: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default getAllRegisteredAllDomainsAction;
|
||||
67
src/actions/getMainAllDomainsDomain.ts
Normal file
67
src/actions/getMainAllDomainsDomain.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { getMainAllDomainsDomain } from "../tools";
|
||||
|
||||
const getMainAllDomainsDomainAction: Action = {
|
||||
name: "GET_MAIN_ALL_DOMAINS_DOMAIN",
|
||||
similes: [
|
||||
"get main domain",
|
||||
"fetch primary domain",
|
||||
"get default domain",
|
||||
"get main address name",
|
||||
"get primary name",
|
||||
"get main domain name",
|
||||
],
|
||||
description: "Get the main domain associated with a wallet address",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
address: "7nxQB...",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
domain: "solana.sol",
|
||||
message: "Successfully retrieved main domain",
|
||||
},
|
||||
explanation: "Get the main domain name for a given wallet address",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
address: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe("The wallet address to get the main domain for"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const mainDomain = await getMainAllDomainsDomain(
|
||||
agent,
|
||||
new PublicKey(input.address),
|
||||
);
|
||||
|
||||
if (!mainDomain) {
|
||||
return {
|
||||
status: "error",
|
||||
message: "No main domain found for this address",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
domain: mainDomain,
|
||||
message: "Successfully retrieved main domain",
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to get main domain: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default getMainAllDomainsDomainAction;
|
||||
63
src/actions/getOwnedAllDomains.ts
Normal file
63
src/actions/getOwnedAllDomains.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { getOwnedAllDomains } from "../tools";
|
||||
|
||||
const getOwnedAllDomainsAction: Action = {
|
||||
name: "GET_OWNED_ALL_DOMAINS",
|
||||
similes: [
|
||||
"list owned domains",
|
||||
"get my domains",
|
||||
"fetch wallet domains",
|
||||
"get owned names",
|
||||
"list my domains",
|
||||
"get address domains",
|
||||
],
|
||||
description:
|
||||
"Get all domains owned by a specific wallet address across all TLDs",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
address: "7nxQB...",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
domains: ["solana.sol", "wallet.abc", "user.backpack"],
|
||||
total: 3,
|
||||
message: "Successfully retrieved owned domains",
|
||||
},
|
||||
explanation: "Get all domain names owned by a specific wallet address",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
address: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe("The wallet address to get owned domains for"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const address = new PublicKey(input.address);
|
||||
|
||||
// Get owned domains
|
||||
const domains = await getOwnedAllDomains(agent, address);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
domains,
|
||||
total: domains.length,
|
||||
message: `Successfully retrieved ${domains.length} owned domain${domains.length === 1 ? "" : "s"}`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to get owned domains: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default getOwnedAllDomainsAction;
|
||||
63
src/actions/getOwnedDomainsForTLD.ts
Normal file
63
src/actions/getOwnedDomainsForTLD.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { getOwnedDomainsForTLD } from "../tools";
|
||||
|
||||
const getOwnedDomainsForTLDAction: Action = {
|
||||
name: "GET_OWNED_DOMAINS_FOR_TLD",
|
||||
similes: [
|
||||
"list owned domains for tld",
|
||||
"get my domains for extension",
|
||||
"fetch wallet domains by tld",
|
||||
"get owned names by extension",
|
||||
"list my domains by tld",
|
||||
"get address domains for tld",
|
||||
],
|
||||
description:
|
||||
"Get all domains owned by a specific wallet address for a given top-level domain (TLD)",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
tld: "sol",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
domains: ["solana.sol", "wallet.sol", "user.sol"],
|
||||
total: 3,
|
||||
message: "Successfully retrieved owned domains for .sol",
|
||||
},
|
||||
explanation:
|
||||
"Get all .sol domain names owned by a specific wallet address",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
tld: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe("The top-level domain to filter by (e.g., 'sol', 'abc')"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const tld = input.tld.toLowerCase();
|
||||
|
||||
// Get owned domains for TLD
|
||||
const domains = await getOwnedDomainsForTLD(agent, tld);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
domains,
|
||||
total: domains.length,
|
||||
message: `Successfully retrieved ${domains.length} owned domain${domains.length === 1 ? "" : "s"} for .${tld}`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to get owned domains: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default getOwnedDomainsForTLDAction;
|
||||
57
src/actions/getPrimaryDomain.ts
Normal file
57
src/actions/getPrimaryDomain.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { getPrimaryDomain } from "../tools";
|
||||
|
||||
const getPrimaryDomainAction: Action = {
|
||||
name: "GET_PRIMARY_DOMAIN",
|
||||
similes: [
|
||||
"get primary domain",
|
||||
"lookup primary domain",
|
||||
"check primary domain",
|
||||
"find primary domain",
|
||||
"get main domain",
|
||||
"primary sol domain",
|
||||
],
|
||||
description:
|
||||
"Get the primary .sol domain associated with a Solana wallet address",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
account: "7nxQB...",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
domain: "vitalik.sol",
|
||||
message: "Primary domain: vitalik.sol",
|
||||
},
|
||||
explanation: "Get the primary .sol domain for a wallet address",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
account: z.string().min(1).describe("The Solana wallet address to lookup"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const account = new PublicKey(input.account);
|
||||
|
||||
const response = await getPrimaryDomain(agent, account);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
domain: response,
|
||||
message: `Primary domain: ${response}`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to get primary domain: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default getPrimaryDomainAction;
|
||||
48
src/actions/getTPS.ts
Normal file
48
src/actions/getTPS.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { getTPS } from "../tools";
|
||||
|
||||
const getTPSAction: Action = {
|
||||
name: "GET_TPS",
|
||||
similes: [
|
||||
"get transactions per second",
|
||||
"check network speed",
|
||||
"network performance",
|
||||
"transaction throughput",
|
||||
"network tps",
|
||||
],
|
||||
description:
|
||||
"Get the current transactions per second (TPS) of the Solana network",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {},
|
||||
output: {
|
||||
status: "success",
|
||||
tps: 3500,
|
||||
message: "Current network TPS: 3500",
|
||||
},
|
||||
explanation: "Get the current TPS of the Solana network",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({}), // No input parameters required
|
||||
handler: async (agent: SolanaAgentKit, _input: Record<string, any>) => {
|
||||
try {
|
||||
const response = await getTPS(agent);
|
||||
return {
|
||||
status: "success",
|
||||
response,
|
||||
message: `Current network TPS: ${response}`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to get TPS: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default getTPSAction;
|
||||
98
src/actions/getTokenData.ts
Normal file
98
src/actions/getTokenData.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { JupiterTokenData } from "../types";
|
||||
import { getTokenAddressFromTicker, getTokenDataByAddress } from "../tools";
|
||||
|
||||
const getTokenDataAction: Action = {
|
||||
name: "GET_TOKEN_DATA",
|
||||
similes: [
|
||||
"get token info",
|
||||
"token details",
|
||||
"lookup token",
|
||||
"find token",
|
||||
"token data",
|
||||
],
|
||||
description: "Get token data from either a token address or ticker symbol",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
token: {
|
||||
name: "USD Coin",
|
||||
symbol: "USDC",
|
||||
address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
decimals: 6,
|
||||
},
|
||||
},
|
||||
explanation: "Get token data using the token's address",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {
|
||||
ticker: "SOL",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
token: {
|
||||
name: "Wrapped SOL",
|
||||
symbol: "SOL",
|
||||
address: "So11111111111111111111111111111111111111112",
|
||||
decimals: 9,
|
||||
},
|
||||
},
|
||||
explanation: "Get token data using the token's ticker symbol",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z
|
||||
.object({
|
||||
address: z.string().optional().describe("The token's mint address"),
|
||||
ticker: z.string().optional().describe("The token's ticker symbol"),
|
||||
})
|
||||
.refine((data) => data.address || data.ticker, {
|
||||
message: "Either address or ticker must be provided",
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
let tokenData: JupiterTokenData | undefined;
|
||||
if (input.address) {
|
||||
tokenData = await getTokenDataByAddress(new PublicKey(input.address));
|
||||
} else if (input.ticker) {
|
||||
const address = await getTokenAddressFromTicker(input.ticker);
|
||||
if (address) {
|
||||
tokenData = await getTokenDataByAddress(new PublicKey(address));
|
||||
}
|
||||
}
|
||||
if (!tokenData) {
|
||||
return {
|
||||
status: "error",
|
||||
message: "Token not found or not verified",
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: "success",
|
||||
token: {
|
||||
name: tokenData.name,
|
||||
symbol: tokenData.symbol,
|
||||
address: tokenData.address,
|
||||
decimals: tokenData.decimals,
|
||||
logoURI: tokenData.logoURI,
|
||||
},
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to get token data: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default getTokenDataAction;
|
||||
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;
|
||||
66
src/actions/index.ts
Normal file
66
src/actions/index.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import deployTokenAction from "./deployToken";
|
||||
import balanceAction from "./balance";
|
||||
import transferAction from "./transfer";
|
||||
import deployCollectionAction from "./deployCollection";
|
||||
import mintNFTAction from "./mintNFT";
|
||||
import tradeAction from "./trade";
|
||||
import requestFundsAction from "./requestFunds";
|
||||
import resolveDomainAction from "./resolveDomain";
|
||||
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";
|
||||
import resolveSolDomainAction from "./resolveSolDomain";
|
||||
import pythFetchPriceAction from "./pythFetchPrice";
|
||||
import getOwnedDomainsForTLDAction from "./getOwnedDomainsForTLD";
|
||||
import getPrimaryDomainAction from "./getPrimaryDomain";
|
||||
import getAllDomainsTLDsAction from "./getAllDomainsTLDs";
|
||||
import getOwnedAllDomainsAction from "./getOwnedAllDomains";
|
||||
import createImageAction from "./createImage";
|
||||
import getMainAllDomainsDomainAction from "./getMainAllDomainsDomain";
|
||||
import getAllRegisteredAllDomainsAction from "./getAllRegisteredAllDomains";
|
||||
import raydiumCreateCpmmAction from "./raydiumCreateCpmm";
|
||||
import raydiumCreateAmmV4Action from "./raydiumCreateAmmV4";
|
||||
import createOrcaSingleSidedWhirlpoolAction from "./createOrcaSingleSidedWhirlpool";
|
||||
import launchPumpfunTokenAction from "./launchPumpfunToken";
|
||||
import getWalletAddressAction from "./getWalletAddress";
|
||||
|
||||
export const ACTIONS = {
|
||||
WALLET_ADDRESS_ACTION: getWalletAddressAction,
|
||||
DEPLOY_TOKEN_ACTION: deployTokenAction,
|
||||
BALANCE_ACTION: balanceAction,
|
||||
TRANSFER_ACTION: transferAction,
|
||||
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";
|
||||
106
src/actions/launchPumpfunToken.ts
Normal file
106
src/actions/launchPumpfunToken.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { launchPumpFunToken } from "../tools";
|
||||
|
||||
const launchPumpfunTokenAction: Action = {
|
||||
name: "LAUNCH_PUMPFUN_TOKEN",
|
||||
similes: [
|
||||
"create pumpfun token",
|
||||
"launch token on pumpfun",
|
||||
"deploy pumpfun token",
|
||||
"create meme token",
|
||||
"launch memecoin",
|
||||
"create pump token",
|
||||
],
|
||||
description:
|
||||
"Launch a new token on Pump.fun with customizable metadata and initial liquidity",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
tokenName: "Sample Token",
|
||||
tokenTicker: "SMPL",
|
||||
description: "A sample token for demonstration",
|
||||
imageUrl: "https://example.com/token.png",
|
||||
twitter: "@sampletoken",
|
||||
telegram: "t.me/sampletoken",
|
||||
website: "https://sampletoken.com",
|
||||
initialLiquiditySOL: 0.1,
|
||||
slippageBps: 10,
|
||||
priorityFee: 0.0001,
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
signature: "2ZE7Rz...",
|
||||
mint: "7nxQB...",
|
||||
metadataUri: "https://arweave.net/...",
|
||||
message: "Successfully launched token on Pump.fun",
|
||||
},
|
||||
explanation:
|
||||
"Launch a new token with custom metadata and 0.1 SOL initial liquidity",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
tokenName: z.string().min(1).max(32).describe("Name of the token"),
|
||||
tokenTicker: z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(10)
|
||||
.describe("Ticker symbol of the token"),
|
||||
description: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(1000)
|
||||
.describe("Description of the token"),
|
||||
imageUrl: z.string().url().describe("URL of the token image"),
|
||||
twitter: z.string().optional().describe("Twitter handle (optional)"),
|
||||
telegram: z.string().optional().describe("Telegram group link (optional)"),
|
||||
website: z.string().url().optional().describe("Website URL (optional)"),
|
||||
initialLiquiditySOL: z
|
||||
.number()
|
||||
.min(0.0001)
|
||||
.default(0.0001)
|
||||
.describe("Initial liquidity in SOL"),
|
||||
slippageBps: z
|
||||
.number()
|
||||
.min(1)
|
||||
.max(1000)
|
||||
.default(5)
|
||||
.describe("Slippage tolerance in basis points"),
|
||||
priorityFee: z
|
||||
.number()
|
||||
.min(0.00001)
|
||||
.default(0.00005)
|
||||
.describe("Priority fee in SOL"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const { tokenName, tokenTicker, description, imageUrl } = input;
|
||||
const result = await launchPumpFunToken(
|
||||
agent,
|
||||
tokenName,
|
||||
tokenTicker,
|
||||
description,
|
||||
imageUrl,
|
||||
input,
|
||||
);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
signature: result.signature,
|
||||
mint: result.mint,
|
||||
metadataUri: result.metadataUri,
|
||||
message: "Successfully launched token on Pump.fun",
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to launch token: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default launchPumpfunTokenAction;
|
||||
55
src/actions/lendAsset.ts
Normal file
55
src/actions/lendAsset.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { lendAsset } from "../tools";
|
||||
|
||||
const lendAssetAction: Action = {
|
||||
name: "LEND_ASSET",
|
||||
similes: [
|
||||
"lend usdc",
|
||||
"deposit for yield",
|
||||
"earn yield",
|
||||
"lend with lulo",
|
||||
"deposit usdc",
|
||||
"lending",
|
||||
],
|
||||
description: "Lend USDC tokens to earn yield using Lulo protocol",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
amount: 100,
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
signature: "4xKpN2...",
|
||||
message: "Successfully lent 100 USDC",
|
||||
},
|
||||
explanation: "Lend 100 USDC to earn yield on Lulo",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
amount: z.number().positive().describe("Amount of USDC to lend"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const amount = input.amount as number;
|
||||
|
||||
const response = await lendAsset(agent, amount);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
signature: response,
|
||||
message: `Successfully lent ${amount} USDC`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Lending failed: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default lendAssetAction;
|
||||
90
src/actions/mintNFT.ts
Normal file
90
src/actions/mintNFT.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { mintCollectionNFT } from "../tools";
|
||||
|
||||
const mintNFTAction: Action = {
|
||||
name: "MINT_NFT",
|
||||
similes: [
|
||||
"mint nft",
|
||||
"create nft",
|
||||
"mint token",
|
||||
"create token",
|
||||
"add nft to collection",
|
||||
],
|
||||
description: `Mint a new NFT in a collection on Solana blockchain.`,
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
collectionMint: "J1S9H3QjnRtBbbuD4HjPV6RpRhwuk4zKbxsnCHuTgh9w",
|
||||
name: "My NFT",
|
||||
uri: "https://example.com/nft.json",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "NFT minted successfully",
|
||||
mintAddress: "7nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkN",
|
||||
metadata: {
|
||||
name: "My NFT",
|
||||
uri: "https://example.com/nft.json",
|
||||
},
|
||||
recipient: "7nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkN",
|
||||
},
|
||||
explanation: "Mint an NFT to the default wallet",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {
|
||||
collectionMint: "J1S9H3QjnRtBbbuD4HjPV6RpRhwuk4zKbxsnCHuTgh9w",
|
||||
name: "Gift NFT",
|
||||
uri: "https://example.com/gift.json",
|
||||
recipient: "9aUn5swQzUTRanaaTwmszxiv89cvFwUCjEBv1vZCoT1u",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "NFT minted successfully",
|
||||
mintAddress: "8nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkM",
|
||||
metadata: {
|
||||
name: "Gift NFT",
|
||||
uri: "https://example.com/gift.json",
|
||||
},
|
||||
recipient: "9aUn5swQzUTRanaaTwmszxiv89cvFwUCjEBv1vZCoT1u",
|
||||
},
|
||||
explanation: "Mint an NFT to a specific recipient",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
collectionMint: z.string().min(32, "Invalid collection mint address"),
|
||||
name: z.string().min(1, "Name is required"),
|
||||
uri: z.string().url("URI must be a valid URL"),
|
||||
recipient: z.string().min(32, "Invalid recipient address"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
const result = await mintCollectionNFT(
|
||||
agent,
|
||||
new PublicKey(input.collectionMint),
|
||||
{
|
||||
name: input.name,
|
||||
uri: input.uri,
|
||||
},
|
||||
input.recipient ? new PublicKey(input.recipient) : undefined,
|
||||
);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
message: "NFT minted successfully",
|
||||
mintAddress: result.mint.toString(),
|
||||
metadata: {
|
||||
name: input.name,
|
||||
uri: input.uri,
|
||||
},
|
||||
recipient: input.recipient || result.mint.toString(),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default mintNFTAction;
|
||||
60
src/actions/pythFetchPrice.ts
Normal file
60
src/actions/pythFetchPrice.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { fetchPythPrice, fetchPythPriceFeedID } from "../tools";
|
||||
|
||||
const pythFetchPriceAction: Action = {
|
||||
name: "PYTH_FETCH_PRICE",
|
||||
similes: [
|
||||
"get pyth price",
|
||||
"check pyth price",
|
||||
"pyth oracle price",
|
||||
"fetch from pyth",
|
||||
"pyth price feed",
|
||||
"oracle price",
|
||||
],
|
||||
description: "Fetch the current price from a Pyth oracle price feed",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
tokenSymbol: "SOL", // SOL/USD price feed
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
price: "23.45",
|
||||
message: "Current price: $23.45",
|
||||
},
|
||||
explanation: "Get the current SOL/USD price from Pyth oracle",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
tokenSymbol: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe("The token symbol to fetch the price for"),
|
||||
}),
|
||||
handler: async (_agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const priceFeedId = await fetchPythPriceFeedID(
|
||||
input.tokenSymbol as string,
|
||||
);
|
||||
|
||||
const priceStr = await fetchPythPrice(priceFeedId);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
price: priceStr,
|
||||
message: `Current price: $${priceStr}`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to fetch price from Pyth: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default pythFetchPriceAction;
|
||||
91
src/actions/raydiumCreateAmmV4.ts
Normal file
91
src/actions/raydiumCreateAmmV4.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import { raydiumCreateAmmV4 } from "../tools";
|
||||
|
||||
const raydiumCreateAmmV4Action: Action = {
|
||||
name: "RAYDIUM_CREATE_AMM_V4",
|
||||
similes: [
|
||||
"create raydium v4 pool",
|
||||
"setup raydium v4 liquidity pool",
|
||||
"initialize raydium v4 amm",
|
||||
"create raydium v4 market maker",
|
||||
"setup raydium v4 pool",
|
||||
"create raydium v4 trading pair",
|
||||
],
|
||||
description:
|
||||
"Create a new AMM V4 pool on Raydium with advanced features and improved efficiency",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
baseMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
|
||||
quoteMint: "So11111111111111111111111111111111111111112", // SOL
|
||||
baseAmount: 1000,
|
||||
quoteAmount: 10,
|
||||
startPrice: 100, // 1 SOL = 100 USDC
|
||||
openTime: 1672531200, // Unix timestamp
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
signature: "2ZE7Rz...",
|
||||
poolId: "7nxQB...",
|
||||
message: "Successfully created Raydium AMM V4 pool",
|
||||
},
|
||||
explanation:
|
||||
"Create a USDC-SOL V4 pool with initial liquidity and price",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
baseMint: z.string().min(1).describe("The base token mint address"),
|
||||
quoteMint: z.string().min(1).describe("The quote token mint address"),
|
||||
baseAmount: z
|
||||
.number()
|
||||
.positive()
|
||||
.describe("Initial base token amount to provide as liquidity"),
|
||||
quoteAmount: z
|
||||
.number()
|
||||
.positive()
|
||||
.describe("Initial quote token amount to provide as liquidity"),
|
||||
startPrice: z
|
||||
.number()
|
||||
.positive()
|
||||
.describe("Initial price of quote token in base token units"),
|
||||
openTime: z
|
||||
.number()
|
||||
.positive()
|
||||
.describe("Unix timestamp when trading should start"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const marketId = new PublicKey(input.marketId);
|
||||
const baseAmount = new BN(input.baseAmount);
|
||||
const quoteAmount = new BN(input.quoteAmount);
|
||||
const startTime = new BN(input.startTime);
|
||||
|
||||
const txId = await raydiumCreateAmmV4(
|
||||
agent,
|
||||
marketId,
|
||||
baseAmount,
|
||||
quoteAmount,
|
||||
startTime,
|
||||
);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
signature: txId,
|
||||
message: "Successfully created Raydium AMM V4 pool",
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to create AMM V4 pool: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default raydiumCreateAmmV4Action;
|
||||
76
src/actions/raydiumCreateClmm.ts
Normal file
76
src/actions/raydiumCreateClmm.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
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 { raydiumCreateClmm } from "../tools";
|
||||
|
||||
const raydiumCreateClmmAction: Action = {
|
||||
name: "RAYDIUM_CREATE_CLMM",
|
||||
similes: [
|
||||
"create clmm pool",
|
||||
"create concentrated liquidity pool",
|
||||
"raydium clmm setup",
|
||||
"launch concentrated liquidity market maker",
|
||||
],
|
||||
description: `Create a Raydium Concentrated Liquidity Market Maker (CLMM) pool with custom ranges, providing increased capital efficiency`,
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
mint1: "9xU1vzz456... (PublicKey)",
|
||||
mint2: "EfrsBcG98... (PublicKey)",
|
||||
configId: "D6yTTr... (Config PublicKey)",
|
||||
initialPrice: 123.12,
|
||||
startTime: 0, // or current UNIX timestamp
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Create raydium clmm pool successfully",
|
||||
transaction: "3skCN8... (transaction signature)",
|
||||
},
|
||||
explanation:
|
||||
"Creates a CLMM pool between mint1 and mint2 at an initial price of 123.12 and start time of 0.",
|
||||
},
|
||||
],
|
||||
],
|
||||
// Validate tool inputs using zod
|
||||
schema: z.object({
|
||||
mint1: z.string().min(1).describe("First token mint address (public key)"),
|
||||
mint2: z.string().min(1).describe("Second token mint address (public key)"),
|
||||
configId: z.string().min(1).describe("Raydium configId (public key)"),
|
||||
initialPrice: z.number().describe("Initial price for the CLMM pool"),
|
||||
startTime: z
|
||||
.number()
|
||||
.describe("Start time in seconds (UNIX timestamp or zero)"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const { mint1, mint2, configId, initialPrice, startTime } = input;
|
||||
|
||||
const tx = await raydiumCreateClmm(
|
||||
agent,
|
||||
new PublicKey(mint1),
|
||||
new PublicKey(mint2),
|
||||
new PublicKey(configId),
|
||||
new Decimal(initialPrice),
|
||||
new BN(startTime),
|
||||
);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
message: "Create raydium clmm pool successfully",
|
||||
transaction: tx,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to create CLMM pool: ${error.message}`,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default raydiumCreateClmmAction;
|
||||
90
src/actions/raydiumCreateCpmm.ts
Normal file
90
src/actions/raydiumCreateCpmm.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import BN from "bn.js";
|
||||
import { raydiumCreateCpmm } from "../tools";
|
||||
|
||||
const raydiumCreateCpmmAction: Action = {
|
||||
name: "RAYDIUM_CREATE_CPMM",
|
||||
similes: [
|
||||
"create raydium pool",
|
||||
"setup raydium liquidity pool",
|
||||
"initialize raydium amm",
|
||||
"create constant product market maker",
|
||||
"setup raydium cpmm",
|
||||
"create raydium trading pair",
|
||||
],
|
||||
description:
|
||||
"Create a new Constant Product Market Maker (CPMM) pool on Raydium",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
baseMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
|
||||
quoteMint: "So11111111111111111111111111111111111111112", // SOL
|
||||
baseAmount: 1000,
|
||||
quoteAmount: 10,
|
||||
startTime: 1672531200, // Unix timestamp
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
signature: "2ZE7Rz...",
|
||||
poolId: "7nxQB...",
|
||||
message: "Successfully created Raydium CPMM pool",
|
||||
},
|
||||
explanation:
|
||||
"Create a USDC-SOL pool with initial liquidity of 1000 USDC and 10 SOL",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
baseMint: z.string().min(1).describe("The base token mint address"),
|
||||
quoteMint: z.string().min(1).describe("The quote token mint address"),
|
||||
baseAmount: z
|
||||
.number()
|
||||
.positive()
|
||||
.describe("Initial base token amount to provide as liquidity"),
|
||||
quoteAmount: z
|
||||
.number()
|
||||
.positive()
|
||||
.describe("Initial quote token amount to provide as liquidity"),
|
||||
startTime: z
|
||||
.number()
|
||||
.positive()
|
||||
.describe("Unix timestamp when trading should start"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const mintA = new PublicKey(input.baseMint);
|
||||
const mintB = new PublicKey(input.quoteMint);
|
||||
const configId = new PublicKey(input.configId);
|
||||
const mintAAmount = new BN(input.baseAmount);
|
||||
const mintBAmount = new BN(input.quoteAmount);
|
||||
const startTime = new BN(input.startTime);
|
||||
|
||||
const txId = await raydiumCreateCpmm(
|
||||
agent,
|
||||
mintA,
|
||||
mintB,
|
||||
configId,
|
||||
mintAAmount,
|
||||
mintBAmount,
|
||||
startTime,
|
||||
);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
signature: txId,
|
||||
message: "Successfully created Raydium CPMM pool",
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to create CPMM pool: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default raydiumCreateCpmmAction;
|
||||
63
src/actions/registerDomain.ts
Normal file
63
src/actions/registerDomain.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { registerDomain } from "../tools";
|
||||
|
||||
const registerDomainAction: Action = {
|
||||
name: "REGISTER_DOMAIN",
|
||||
similes: [
|
||||
"register domain",
|
||||
"buy domain",
|
||||
"get domain name",
|
||||
"register .sol",
|
||||
"purchase domain",
|
||||
"domain registration",
|
||||
],
|
||||
description: "Register a .sol domain name using Bonfida Name Service",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
name: "mydomain",
|
||||
spaceKB: 1,
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
signature: "2ZE7Rz...",
|
||||
message: "Successfully registered mydomain.sol",
|
||||
},
|
||||
explanation: "Register a new .sol domain with 1KB storage space",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
name: z.string().min(1).describe("Domain name to register (without .sol)"),
|
||||
spaceKB: z
|
||||
.number()
|
||||
.min(1)
|
||||
.max(10)
|
||||
.default(1)
|
||||
.describe("Space allocation in KB (max 10KB)"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const name = input.name as string;
|
||||
const spaceKB = (input.spaceKB as number) || 1;
|
||||
|
||||
const signature = await registerDomain(agent, name, spaceKB);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
signature,
|
||||
message: `Successfully registered ${name}.sol`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Domain registration failed: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default registerDomainAction;
|
||||
41
src/actions/requestFunds.ts
Normal file
41
src/actions/requestFunds.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { request_faucet_funds } from "../tools";
|
||||
|
||||
const requestFundsAction: Action = {
|
||||
name: "REQUEST_FUNDS",
|
||||
similes: [
|
||||
"request sol",
|
||||
"get test sol",
|
||||
"use faucet",
|
||||
"request test tokens",
|
||||
"get devnet sol",
|
||||
],
|
||||
description: "Request SOL from Solana faucet (devnet/testnet only)",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Successfully requested faucet funds",
|
||||
network: "devnet.solana.com",
|
||||
},
|
||||
explanation: "Request SOL from the devnet faucet",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({}), // No input parameters required
|
||||
handler: async (agent: SolanaAgentKit, _input: Record<string, any>) => {
|
||||
await request_faucet_funds(agent);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
message: "Successfully requested faucet funds",
|
||||
network: agent.connection.rpcEndpoint.split("/")[2],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default requestFundsAction;
|
||||
51
src/actions/resolveDomain.ts
Normal file
51
src/actions/resolveDomain.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { resolveAllDomains } from "../tools";
|
||||
|
||||
const resolveDomainAction: Action = {
|
||||
name: "RESOLVE_ALL_DOMAINS",
|
||||
similes: [
|
||||
"resolve domain",
|
||||
"lookup domain",
|
||||
"get domain owner",
|
||||
"check domain",
|
||||
"find domain owner",
|
||||
],
|
||||
description: "Resolve a Solana domain name to get its owner's public key",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
domain: "example.sol",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
owner: "7nxQB...",
|
||||
},
|
||||
explanation: "Resolve a .sol domain name to get the owner's public key",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
domain: z.string().min(1).describe("The domain name to resolve"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const domain = input.domain as string;
|
||||
const tld = await resolveAllDomains(agent, domain);
|
||||
return {
|
||||
status: "success",
|
||||
owner: tld,
|
||||
message: `Successfully resolved domain ${domain}`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to resolve domain: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default resolveDomainAction;
|
||||
59
src/actions/resolveSolDomain.ts
Normal file
59
src/actions/resolveSolDomain.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { resolveSolDomain } from "../tools";
|
||||
|
||||
const resolveSolDomainAction: Action = {
|
||||
name: "RESOLVE_SOL_DOMAIN",
|
||||
similes: [
|
||||
"resolve sol domain",
|
||||
"lookup sol domain",
|
||||
"get sol domain owner",
|
||||
"check sol domain",
|
||||
"find sol domain owner",
|
||||
"resolve .sol",
|
||||
],
|
||||
description:
|
||||
"Resolve a .sol domain to its corresponding Solana wallet address using Bonfida Name Service",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
domain: "vitalik.sol",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
owner: "7nxQB...",
|
||||
message: "Successfully resolved vitalik.sol",
|
||||
},
|
||||
explanation: "Resolve a .sol domain to get the owner's wallet address",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
domain: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe("The .sol domain to resolve (with or without .sol suffix)"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const domain = input.domain as string;
|
||||
|
||||
const res = await resolveSolDomain(agent, domain);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
owner: res.toString(),
|
||||
message: `Successfully resolved ${res}`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to resolve domain: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default resolveSolDomainAction;
|
||||
55
src/actions/stakeWithJup.ts
Normal file
55
src/actions/stakeWithJup.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { stakeWithJup } from "../tools";
|
||||
|
||||
const stakeWithJupAction: Action = {
|
||||
name: "STAKE_WITH_JUPITER",
|
||||
similes: [
|
||||
"stake sol",
|
||||
"stake with jupiter",
|
||||
"jup staking",
|
||||
"stake with jup",
|
||||
"liquid staking",
|
||||
"get jupsol",
|
||||
],
|
||||
description:
|
||||
"Stake SOL tokens with Jupiter's liquid staking protocol to receive jupSOL",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
amount: 1.5,
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
signature: "5KtPn3...",
|
||||
message: "Successfully staked 1.5 SOL for jupSOL",
|
||||
},
|
||||
explanation: "Stake 1.5 SOL to receive jupSOL tokens",
|
||||
},
|
||||
],
|
||||
],
|
||||
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 stakeWithJup(agent, amount);
|
||||
return {
|
||||
status: "success",
|
||||
res,
|
||||
message: `Successfully staked ${amount} SOL for jupSOL`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `jupSOL staking failed: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default stakeWithJupAction;
|
||||
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;
|
||||
60
src/actions/tokenDataByTicker.ts
Normal file
60
src/actions/tokenDataByTicker.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { getTokenDataByTicker } from "../tools";
|
||||
|
||||
const tokenDataByTickerAction: Action = {
|
||||
name: "GET_TOKEN_DATA_BY_TICKER",
|
||||
similes: [
|
||||
"token data by ticker",
|
||||
"fetch token info by ticker",
|
||||
"lookup token ticker info",
|
||||
"get token info by ticker",
|
||||
],
|
||||
description: "Get the token data for a given token ticker",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
ticker: "USDC",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
tokenData: {
|
||||
// Some placeholder example data
|
||||
symbol: "USDC",
|
||||
name: "USD Coin",
|
||||
decimals: 6,
|
||||
mintAddress: "FhRg...",
|
||||
},
|
||||
},
|
||||
explanation: "Fetches metadata for the USDC token by its ticker.",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
ticker: z.string().min(1).describe("Ticker of the token, e.g. 'USDC'"),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
try {
|
||||
const ticker = input.ticker as string;
|
||||
|
||||
// Use agent’s method to get token data by ticker
|
||||
const tokenData = await getTokenDataByTicker(ticker);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
tokenData: tokenData,
|
||||
message: `Successfully fetched token data for ticker: ${ticker}`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to fetch token data for ticker: ${input.ticker || ""}. ${error.message}`,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default tokenDataByTickerAction;
|
||||
85
src/actions/trade.ts
Normal file
85
src/actions/trade.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { trade } from "../tools";
|
||||
|
||||
const tradeAction: Action = {
|
||||
name: "TRADE",
|
||||
similes: [
|
||||
"swap tokens",
|
||||
"exchange tokens",
|
||||
"trade tokens",
|
||||
"convert tokens",
|
||||
"swap sol",
|
||||
],
|
||||
description: `This tool can be used to swap tokens to another token (It uses Jupiter Exchange).`,
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
inputAmount: 1,
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Trade executed successfully",
|
||||
transaction:
|
||||
"5UfgJ5vVZxUxefDGqzqkVLHzHxVTyYH9StYyHKgvHYmXJgqJKxEqy9k4Rz9LpXrHF9kUZB7",
|
||||
inputAmount: 1,
|
||||
inputToken: "SOL",
|
||||
outputToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
},
|
||||
explanation: "Swap 1 SOL for USDC",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {
|
||||
outputMint: "So11111111111111111111111111111111111111112",
|
||||
inputAmount: 100,
|
||||
inputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
slippageBps: 100,
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Trade executed successfully",
|
||||
transaction:
|
||||
"4VfgJ5vVZxUxefDGqzqkVLHzHxVTyYH9StYyHKgvHYmXJgqJKxEqy9k4Rz9LpXrHF9kUZB7",
|
||||
inputAmount: 100,
|
||||
inputToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
outputToken: "So11111111111111111111111111111111111111112",
|
||||
},
|
||||
explanation: "Swap 100 USDC for SOL with 1% slippage",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
outputMint: z.string().min(32, "Invalid output mint address"),
|
||||
inputAmount: z.number().positive("Input amount must be positive"),
|
||||
inputMint: z.string().min(32, "Invalid input mint address").optional(),
|
||||
slippageBps: z.number().min(0).max(10000).optional(),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
const tx = await trade(
|
||||
agent,
|
||||
new PublicKey(input.outputMint),
|
||||
input.inputAmount,
|
||||
input.inputMint
|
||||
? new PublicKey(input.inputMint)
|
||||
: new PublicKey("So11111111111111111111111111111111111111112"),
|
||||
input.slippageBps,
|
||||
);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
message: "Trade executed successfully",
|
||||
transaction: tx,
|
||||
inputAmount: input.inputAmount,
|
||||
inputToken: input.inputMint || "SOL",
|
||||
outputToken: input.outputMint,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default tradeAction;
|
||||
78
src/actions/transfer.ts
Normal file
78
src/actions/transfer.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { transfer } from "../tools";
|
||||
|
||||
const transferAction: Action = {
|
||||
name: "TRANSFER",
|
||||
similes: [
|
||||
"send tokens",
|
||||
"transfer funds",
|
||||
"send money",
|
||||
"send sol",
|
||||
"transfer tokens",
|
||||
],
|
||||
description: `Transfer tokens or SOL to another address (also called as wallet address).`,
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
to: "8x2dR8Mpzuz2YqyZyZjUbYWKSWesBo5jMx2Q9Y86udVk",
|
||||
amount: 1,
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Transfer completed successfully",
|
||||
amount: 1,
|
||||
recipient: "8x2dR8Mpzuz2YqyZyZjUbYWKSWesBo5jMx2Q9Y86udVk",
|
||||
token: "SOL",
|
||||
transaction:
|
||||
"5UfgJ5vVZxUxefDGqzqkVLHzHxVTyYH9StYyHKgvHYmXJgqJKxEqy9k4Rz9LpXrHF9kUZB7",
|
||||
},
|
||||
explanation: "Transfer 1 SOL to the recipient address",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {
|
||||
to: "8x2dR8Mpzuz2YqyZyZjUbYWKSWesBo5jMx2Q9Y86udVk",
|
||||
amount: 100,
|
||||
mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Transfer completed successfully",
|
||||
amount: 100,
|
||||
recipient: "8x2dR8Mpzuz2YqyZyZjUbYWKSWesBo5jMx2Q9Y86udVk",
|
||||
token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
transaction:
|
||||
"4VfgJ5vVZxUxefDGqzqkVLHzHxVTyYH9StYyHKgvHYmXJgqJKxEqy9k4Rz9LpXrHF9kUZB7",
|
||||
},
|
||||
explanation: "Transfer 100 USDC tokens to the recipient address",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({
|
||||
to: z.string().min(32, "Invalid Solana address"),
|
||||
amount: z.number().positive("Amount must be positive"),
|
||||
mint: z.string().optional(),
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
const recipient = new PublicKey(input.to);
|
||||
const mintAddress = input.mint ? new PublicKey(input.mint) : undefined;
|
||||
|
||||
const tx = await transfer(agent, recipient, input.amount, mintAddress);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
message: "Transfer completed successfully",
|
||||
amount: input.amount,
|
||||
recipient: input.to,
|
||||
token: input.mint || "SOL",
|
||||
transaction: tx,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default transferAction;
|
||||
@@ -1,7 +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, TokenCheck } from "../types";
|
||||
import {
|
||||
deploy_collection,
|
||||
deploy_token,
|
||||
@@ -14,21 +16,33 @@ import {
|
||||
lendAsset,
|
||||
mintCollectionNFT,
|
||||
openbookCreateMarket,
|
||||
manifestCreateMarket,
|
||||
raydiumCreateAmmV4,
|
||||
raydiumCreateClmm,
|
||||
raydiumCreateCpmm,
|
||||
registerDomain,
|
||||
request_faucet_funds,
|
||||
trade,
|
||||
limitOrder,
|
||||
batchOrder,
|
||||
cancelAllOrders,
|
||||
withdrawAll,
|
||||
closePerpTradeShort,
|
||||
closePerpTradeLong,
|
||||
openPerpTradeShort,
|
||||
openPerpTradeLong,
|
||||
transfer,
|
||||
getTokenDataByAddress,
|
||||
getTokenDataByTicker,
|
||||
stakeWithJup,
|
||||
stakeWithSolayer,
|
||||
sendCompressedAirdrop,
|
||||
createOrcaSingleSidedWhirlpool,
|
||||
fetchPrice,
|
||||
pythFetchPrice,
|
||||
orcaCreateSingleSidedLiquidityPool,
|
||||
orcaCreateCLMM,
|
||||
orcaOpenCenteredPositionWithLiquidity,
|
||||
orcaOpenSingleSidedPosition,
|
||||
FEE_TIERS,
|
||||
fetchPrice,
|
||||
getAllDomainsTLDs,
|
||||
getAllRegisteredAllDomains,
|
||||
getOwnedDomainsForTLD,
|
||||
@@ -36,8 +50,16 @@ import {
|
||||
getOwnedAllDomains,
|
||||
resolveAllDomains,
|
||||
create_gibwork_task,
|
||||
orcaClosePosition,
|
||||
orcaFetchPositions,
|
||||
rock_paper_scissor,
|
||||
create_TipLink,
|
||||
listNFTForSale,
|
||||
cancelListing,
|
||||
fetchTokenReportSummary,
|
||||
fetchTokenDetailedReport,
|
||||
fetchPythPrice,
|
||||
fetchPythPriceFeedID,
|
||||
} from "../tools";
|
||||
import {
|
||||
CollectionDeployment,
|
||||
@@ -47,8 +69,8 @@ import {
|
||||
MintCollectionNFTResponse,
|
||||
PumpfunLaunchResponse,
|
||||
PumpFunTokenOptions,
|
||||
OrderParams,
|
||||
} from "../types";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
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";
|
||||
@@ -65,22 +87,45 @@ import { reject_proposal } from "../tools/squads_multisig/reject_proposal";
|
||||
* @property {Connection} connection - Solana RPC connection
|
||||
* @property {Keypair} wallet - Wallet keypair for signing transactions
|
||||
* @property {PublicKey} wallet_address - Public key of the wallet
|
||||
* @property {Config} config - Configuration object
|
||||
*/
|
||||
export class SolanaAgentKit {
|
||||
public connection: Connection;
|
||||
public wallet: Keypair;
|
||||
public wallet_address: PublicKey;
|
||||
public openai_api_key: string | null;
|
||||
public config: Config;
|
||||
|
||||
/**
|
||||
* @deprecated Using openai_api_key directly in constructor is deprecated.
|
||||
* Please use the new constructor with Config object instead:
|
||||
* @example
|
||||
* const agent = new SolanaAgentKit(privateKey, rpcUrl, {
|
||||
* OPENAI_API_KEY: 'your-key'
|
||||
* });
|
||||
*/
|
||||
constructor(
|
||||
private_key: string,
|
||||
rpc_url = "https://api.mainnet-beta.solana.com",
|
||||
openai_api_key: string | null = null,
|
||||
rpc_url: string,
|
||||
openai_api_key: string | null,
|
||||
);
|
||||
constructor(private_key: string, rpc_url: string, config: Config);
|
||||
constructor(
|
||||
private_key: string,
|
||||
rpc_url: string,
|
||||
configOrKey: Config | string | null,
|
||||
) {
|
||||
this.connection = new Connection(rpc_url);
|
||||
this.connection = new Connection(
|
||||
rpc_url || "https://api.mainnet-beta.solana.com",
|
||||
);
|
||||
this.wallet = Keypair.fromSecretKey(bs58.decode(private_key));
|
||||
this.wallet_address = this.wallet.publicKey;
|
||||
this.openai_api_key = openai_api_key;
|
||||
|
||||
// Handle both old and new patterns
|
||||
if (typeof configOrKey === "string" || configOrKey === null) {
|
||||
this.config = { OPENAI_API_KEY: configOrKey || "" };
|
||||
} else {
|
||||
this.config = configOrKey;
|
||||
}
|
||||
}
|
||||
|
||||
// Tool methods
|
||||
@@ -152,6 +197,66 @@ export class SolanaAgentKit {
|
||||
return trade(this, outputMint, inputAmount, inputMint, slippageBps);
|
||||
}
|
||||
|
||||
async limitOrder(
|
||||
marketId: PublicKey,
|
||||
quantity: number,
|
||||
side: string,
|
||||
price: number,
|
||||
): Promise<string> {
|
||||
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);
|
||||
}
|
||||
|
||||
async withdrawAll(marketId: PublicKey): Promise<string> {
|
||||
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);
|
||||
}
|
||||
@@ -197,6 +302,10 @@ export class SolanaAgentKit {
|
||||
return stakeWithJup(this, amount);
|
||||
}
|
||||
|
||||
async restake(amount: number): Promise<string> {
|
||||
return stakeWithSolayer(this, amount);
|
||||
}
|
||||
|
||||
async sendCompressedAirdrop(
|
||||
mintAddress: string,
|
||||
amount: number,
|
||||
@@ -216,15 +325,28 @@ export class SolanaAgentKit {
|
||||
);
|
||||
}
|
||||
|
||||
async createOrcaSingleSidedWhirlpool(
|
||||
depositTokenAmount: BN,
|
||||
async orcaClosePosition(positionMintAddress: PublicKey) {
|
||||
return orcaClosePosition(this, positionMintAddress);
|
||||
}
|
||||
|
||||
async orcaCreateCLMM(
|
||||
mintDeploy: PublicKey,
|
||||
mintPair: PublicKey,
|
||||
initialPrice: Decimal,
|
||||
feeTier: keyof typeof FEE_TIERS,
|
||||
) {
|
||||
return orcaCreateCLMM(this, mintDeploy, mintPair, initialPrice, feeTier);
|
||||
}
|
||||
|
||||
async orcaCreateSingleSidedLiquidityPool(
|
||||
depositTokenAmount: number,
|
||||
depositTokenMint: PublicKey,
|
||||
otherTokenMint: PublicKey,
|
||||
initialPrice: Decimal,
|
||||
maxPrice: Decimal,
|
||||
feeTier: keyof typeof FEE_TIERS,
|
||||
) {
|
||||
return createOrcaSingleSidedWhirlpool(
|
||||
return orcaCreateSingleSidedLiquidityPool(
|
||||
this,
|
||||
depositTokenAmount,
|
||||
depositTokenMint,
|
||||
@@ -235,6 +357,42 @@ export class SolanaAgentKit {
|
||||
);
|
||||
}
|
||||
|
||||
async orcaFetchPositions() {
|
||||
return orcaFetchPositions(this);
|
||||
}
|
||||
|
||||
async orcaOpenCenteredPositionWithLiquidity(
|
||||
whirlpoolAddress: PublicKey,
|
||||
priceOffsetBps: number,
|
||||
inputTokenMint: PublicKey,
|
||||
inputAmount: Decimal,
|
||||
) {
|
||||
return orcaOpenCenteredPositionWithLiquidity(
|
||||
this,
|
||||
whirlpoolAddress,
|
||||
priceOffsetBps,
|
||||
inputTokenMint,
|
||||
inputAmount,
|
||||
);
|
||||
}
|
||||
|
||||
async orcaOpenSingleSidedPosition(
|
||||
whirlpoolAddress: PublicKey,
|
||||
distanceFromCurrentPriceBps: number,
|
||||
widthBps: number,
|
||||
inputTokenMint: PublicKey,
|
||||
inputAmount: Decimal,
|
||||
): Promise<string> {
|
||||
return orcaOpenSingleSidedPosition(
|
||||
this,
|
||||
whirlpoolAddress,
|
||||
distanceFromCurrentPriceBps,
|
||||
widthBps,
|
||||
inputTokenMint,
|
||||
inputAmount,
|
||||
);
|
||||
}
|
||||
|
||||
async resolveAllDomains(domain: string): Promise<PublicKey | undefined> {
|
||||
return resolveAllDomains(this, domain);
|
||||
}
|
||||
@@ -247,8 +405,7 @@ export class SolanaAgentKit {
|
||||
return getOwnedDomainsForTLD(this, tld);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
async getAllDomainsTLDs(): Promise<String[]> {
|
||||
async getAllDomainsTLDs(): Promise<string[]> {
|
||||
return getAllDomainsTLDs(this);
|
||||
}
|
||||
|
||||
@@ -330,8 +487,19 @@ export class SolanaAgentKit {
|
||||
);
|
||||
}
|
||||
|
||||
async pythFetchPrice(priceFeedID: string): Promise<string> {
|
||||
return pythFetchPrice(priceFeedID);
|
||||
async manifestCreateMarket(
|
||||
baseMint: PublicKey,
|
||||
quoteMint: PublicKey,
|
||||
): Promise<string[]> {
|
||||
return manifestCreateMarket(this, baseMint, quoteMint);
|
||||
}
|
||||
|
||||
async getPythPriceFeedID(tokenSymbol: string): Promise<string> {
|
||||
return fetchPythPriceFeedID(tokenSymbol);
|
||||
}
|
||||
|
||||
async getPythPrice(priceFeedID: string): Promise<string> {
|
||||
return fetchPythPrice(priceFeedID);
|
||||
}
|
||||
|
||||
async createGibworkTask(
|
||||
@@ -365,6 +533,22 @@ export class SolanaAgentKit {
|
||||
return create_TipLink(this, amount, splmintAddress);
|
||||
}
|
||||
|
||||
async tensorListNFT(nftMint: PublicKey, price: number): Promise<string> {
|
||||
return listNFTForSale(this, nftMint, price);
|
||||
}
|
||||
|
||||
async tensorCancelListing(nftMint: PublicKey): Promise<string> {
|
||||
return cancelListing(this, nftMint);
|
||||
}
|
||||
|
||||
async fetchTokenReportSummary(mint: string): Promise<TokenCheck> {
|
||||
return fetchTokenReportSummary(mint);
|
||||
}
|
||||
|
||||
async fetchTokenDetailedReport(mint: string): Promise<TokenCheck> {
|
||||
return fetchTokenDetailedReport(mint);
|
||||
}
|
||||
|
||||
async createSquadsMultisig(creator: PublicKey): Promise<string> {
|
||||
return create_squads_multisig(this, creator);
|
||||
}
|
||||
|
||||
@@ -18,13 +18,18 @@ 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;
|
||||
|
||||
/**
|
||||
* Jupiter API URL
|
||||
*/
|
||||
export const JUP_API = "https://quote-api.jup.ag/v6";
|
||||
export const JUP_REFERRAL_ADDRESS =
|
||||
"REFER4ZgmyYx9c6He5XfaTMiGfdLwRnkV4RPp9t9iF3";
|
||||
|
||||
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,12 @@
|
||||
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";
|
||||
|
||||
// Export action system
|
||||
export { ACTIONS } from "./actions";
|
||||
export * from "./utils/actionExecutor";
|
||||
|
||||
@@ -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";
|
||||
@@ -46,7 +46,7 @@ export class SolanaBalanceTool extends Tool {
|
||||
|
||||
export class SolanaBalanceOtherTool extends Tool {
|
||||
name = "solana_balance_other";
|
||||
description = `Get the balance of a Solana wallet or token account different from the agent's wallet.
|
||||
description = `Get the balance of a Solana wallet or token account which is different from the agent's wallet.
|
||||
|
||||
If no tokenAddress is provided, the SOL balance of the wallet will be returned.
|
||||
|
||||
@@ -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 ).
|
||||
@@ -306,6 +414,208 @@ export class SolanaTradeTool extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
side: string, eg "Buy" or "Sell" (required)
|
||||
price: number, in tokens eg 200 for SOL/USDC (required)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = JSON.parse(input);
|
||||
|
||||
const tx = await this.solanaKit.limitOrder(
|
||||
new PublicKey(parsedInput.marketId),
|
||||
parsedInput.quantity,
|
||||
parsedInput.side,
|
||||
parsedInput.price,
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Trade executed successfully",
|
||||
transaction: tx,
|
||||
marketId: parsedInput.marketId,
|
||||
quantity: parsedInput.quantity,
|
||||
side: parsedInput.side,
|
||||
price: parsedInput.price,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Input ( input is a JSON string ):
|
||||
marketId: string, eg "ENhU8LsaR7vDD2G1CsWcsuSGNrih9Cv5WZEk7q9kPapQ" for SOL/USDC (required)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const marketId = new PublicKey(input.trim());
|
||||
const tx = await this.solanaKit.cancelAllOrders(marketId);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Cancel orders successfully",
|
||||
transaction: tx,
|
||||
marketId,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaWithdrawAllTool extends Tool {
|
||||
name = "solana_withdraw_all";
|
||||
description = `This tool can be used to withdraw all funds from a Manifest market.
|
||||
|
||||
Input ( input is a JSON string ):
|
||||
marketId: string, eg "ENhU8LsaR7vDD2G1CsWcsuSGNrih9Cv5WZEk7q9kPapQ" for SOL/USDC (required)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const marketId = new PublicKey(input.trim());
|
||||
const tx = await this.solanaKit.withdrawAll(marketId);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Withdrew successfully",
|
||||
transaction: tx,
|
||||
marketId,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaRequestFundsTool extends Tool {
|
||||
name = "solana_request_funds";
|
||||
description = "Request SOL from Solana faucet (devnet/testnet only)";
|
||||
@@ -660,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
|
||||
*/
|
||||
@@ -796,17 +1139,13 @@ export class SolanaCompressedAirdropTool extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaCreateSingleSidedWhirlpoolTool extends Tool {
|
||||
name = "create_orca_single_sided_whirlpool";
|
||||
description = `Create a single-sided Whirlpool with liquidity.
|
||||
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 (input is a JSON string):
|
||||
- depositTokenAmount: number, eg: 1000000000 (required, in units of deposit token including decimals)
|
||||
- depositTokenMint: string, eg: "DepositTokenMintAddress" (required, mint address of deposit token)
|
||||
- otherTokenMint: string, eg: "OtherTokenMintAddress" (required, mint address of other token)
|
||||
- initialPrice: number, eg: 0.001 (required, initial price of deposit token in terms of other token)
|
||||
- maxPrice: number, eg: 5.0 (required, maximum price at which liquidity is added)
|
||||
- feeTier: number, eg: 0.30 (required, fee tier for the pool)`;
|
||||
Inputs (JSON string):
|
||||
- positionMintAddress: string, the address of the position mint that represents the liquidity position.`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
@@ -815,7 +1154,102 @@ export class SolanaCreateSingleSidedWhirlpoolTool extends Tool {
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const depositTokenAmount = new BN(inputFormat.depositTokenAmount);
|
||||
const positionMintAddress = new PublicKey(
|
||||
inputFormat.positionMintAddress,
|
||||
);
|
||||
|
||||
const txId = await this.solanaKit.orcaClosePosition(positionMintAddress);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Liquidity position closed successfully.",
|
||||
transaction: txId,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaOrcaCreateCLMM extends Tool {
|
||||
name = "orca_create_clmm";
|
||||
description = `Create a Concentrated Liquidity Market Maker (CLMM) pool on Orca, the most efficient and capital-optimized CLMM on Solana. This function initializes a CLMM pool but does not add liquidity. You can add liquidity later using a centered position or a single-sided position.
|
||||
|
||||
Inputs (JSON string):
|
||||
- mintDeploy: string, the mint of the token you want to deploy (required).
|
||||
- mintPair: string, The mint of the token you want to pair the deployed mint with (required).
|
||||
- initialPrice: number, initial price of mintA in terms of mintB, e.g., 0.001 (required).
|
||||
- feeTier: number, fee tier in bps. Options: 1, 2, 4, 5, 16, 30, 65, 100, 200 (required).`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const mintA = new PublicKey(inputFormat.mintDeploy);
|
||||
const mintB = new PublicKey(inputFormat.mintPair);
|
||||
const initialPrice = new Decimal(inputFormat.initialPrice);
|
||||
const feeTier = inputFormat.feeTier;
|
||||
|
||||
if (!feeTier || !(feeTier in FEE_TIERS)) {
|
||||
throw new Error(
|
||||
`Invalid feeTier. Available options: ${Object.keys(FEE_TIERS).join(
|
||||
", ",
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const txId = await this.solanaKit.orcaCreateCLMM(
|
||||
mintA,
|
||||
mintB,
|
||||
initialPrice,
|
||||
feeTier,
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message:
|
||||
"CLMM pool created successfully. Note: No liquidity was added.",
|
||||
transaction: txId,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
- depositTokenMint: string, mint address of the deposit token, e.g., "DepositTokenMintAddress" (required).
|
||||
- otherTokenMint: string, mint address of the other token, e.g., "OtherTokenMintAddress" (required).
|
||||
- initialPrice: number, initial price of the deposit token in terms of the other token, e.g., 0.001 (required).
|
||||
- maxPrice: number, maximum price at which liquidity is added, e.g., 5.0 (required).
|
||||
- feeTier: number, fee tier for the pool in bps. Options: 1, 2, 4, 5, 16, 30, 65, 100, 200 (required).`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const depositTokenAmount = inputFormat.depositTokenAmount;
|
||||
const depositTokenMint = new PublicKey(inputFormat.depositTokenMint);
|
||||
const otherTokenMint = new PublicKey(inputFormat.otherTokenMint);
|
||||
const initialPrice = new Decimal(inputFormat.initialPrice);
|
||||
@@ -830,7 +1264,7 @@ export class SolanaCreateSingleSidedWhirlpoolTool extends Tool {
|
||||
);
|
||||
}
|
||||
|
||||
const txId = await this.solanaKit.createOrcaSingleSidedWhirlpool(
|
||||
const txId = await this.solanaKit.orcaCreateSingleSidedLiquidityPool(
|
||||
depositTokenAmount,
|
||||
depositTokenMint,
|
||||
otherTokenMint,
|
||||
@@ -854,9 +1288,140 @@ export class SolanaCreateSingleSidedWhirlpoolTool extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaOrcaFetchPositions extends Tool {
|
||||
name = "orca_fetch_positions";
|
||||
description = `Fetch all the liquidity positions in an Orca Whirlpool by owner. Returns an object with positiont mint addresses as keys and position status details as values.`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
async _call(): Promise<string> {
|
||||
try {
|
||||
const txId = await this.solanaKit.orcaFetchPositions();
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Liquidity positions fetched.",
|
||||
transaction: txId,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaOrcaOpenCenteredPosition extends Tool {
|
||||
name = "orca_open_centered_position_with_liquidity";
|
||||
description = `Add liquidity to a CLMM by opening a centered position in an Orca Whirlpool, the most efficient liquidity pool on Solana.
|
||||
|
||||
Inputs (JSON string):
|
||||
- whirlpoolAddress: string, address of the Orca Whirlpool (required).
|
||||
- priceOffsetBps: number, bps offset (one side) from the current pool price, e.g., 500 for 5% (required).
|
||||
- inputTokenMint: string, mint address of the deposit token (required).
|
||||
- inputAmount: number, amount of the deposit token, e.g., 100.0 (required).`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const whirlpoolAddress = new PublicKey(inputFormat.whirlpoolAddress);
|
||||
const priceOffsetBps = parseInt(inputFormat.priceOffsetBps, 10);
|
||||
const inputTokenMint = new PublicKey(inputFormat.inputTokenMint);
|
||||
const inputAmount = new Decimal(inputFormat.inputAmount);
|
||||
|
||||
if (priceOffsetBps < 0) {
|
||||
throw new Error(
|
||||
"Invalid distanceFromCurrentPriceBps. It must be equal or greater than 0.",
|
||||
);
|
||||
}
|
||||
|
||||
const txId = await this.solanaKit.orcaOpenCenteredPositionWithLiquidity(
|
||||
whirlpoolAddress,
|
||||
priceOffsetBps,
|
||||
inputTokenMint,
|
||||
inputAmount,
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Centered liquidity position opened successfully.",
|
||||
transaction: txId,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaOrcaOpenSingleSidedPosition extends Tool {
|
||||
name = "orca_open_single_sided_position";
|
||||
description = `Add liquidity to a CLMM by opening a single-sided position in an Orca Whirlpool, the most efficient liquidity pool on Solana.
|
||||
|
||||
Inputs (JSON string):
|
||||
- whirlpoolAddress: string, address of the Orca Whirlpool (required).
|
||||
- distanceFromCurrentPriceBps: number, distance in basis points from the current price for the position (required).
|
||||
- widthBps: number, width of the position in basis points (required).
|
||||
- inputTokenMint: string, mint address of the deposit token (required).
|
||||
- inputAmount: number, amount of the deposit token, e.g., 100.0 (required).`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const whirlpoolAddress = new PublicKey(inputFormat.whirlpoolAddress);
|
||||
const distanceFromCurrentPriceBps =
|
||||
inputFormat.distanceFromCurrentPriceBps;
|
||||
const widthBps = inputFormat.widthBps;
|
||||
const inputTokenMint = new PublicKey(inputFormat.inputTokenMint);
|
||||
const inputAmount = new Decimal(inputFormat.inputAmount);
|
||||
|
||||
if (distanceFromCurrentPriceBps < 0 || widthBps < 0) {
|
||||
throw new Error(
|
||||
"Invalid distanceFromCurrentPriceBps or width. It must be equal or greater than 0.",
|
||||
);
|
||||
}
|
||||
|
||||
const txId = await this.solanaKit.orcaOpenSingleSidedPosition(
|
||||
whirlpoolAddress,
|
||||
distanceFromCurrentPriceBps,
|
||||
widthBps,
|
||||
inputTokenMint,
|
||||
inputAmount,
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Single-sided liquidity position opened successfully.",
|
||||
transaction: txId,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaRaydiumCreateAmmV4 extends Tool {
|
||||
name = "raydium_create_ammV4";
|
||||
description = `Raydium's Legacy AMM that requiers an OpenBook marketID
|
||||
description = `Raydium's Legacy AMM that requires an OpenBook marketID
|
||||
|
||||
Inputs (input is a json string):
|
||||
marketId: string (required)
|
||||
@@ -882,7 +1447,7 @@ export class SolanaRaydiumCreateAmmV4 extends Tool {
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Create raydium amm v4 pool successfully",
|
||||
message: "Raydium amm v4 pool created successfully",
|
||||
transaction: tx,
|
||||
});
|
||||
} catch (error: any) {
|
||||
@@ -927,7 +1492,7 @@ export class SolanaRaydiumCreateClmm extends Tool {
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Create raydium clmm pool successfully",
|
||||
message: "Raydium clmm pool created successfully",
|
||||
transaction: tx,
|
||||
});
|
||||
} catch (error: any) {
|
||||
@@ -975,7 +1540,7 @@ export class SolanaRaydiumCreateCpmm extends Tool {
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Create raydium cpmm pool successfully",
|
||||
message: "Raydium cpmm pool created successfully",
|
||||
transaction: tx,
|
||||
});
|
||||
} catch (error: any) {
|
||||
@@ -1017,7 +1582,7 @@ export class SolanaOpenbookCreateMarket extends Tool {
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Create openbook market successfully",
|
||||
message: "Openbook market created successfully",
|
||||
transaction: tx,
|
||||
});
|
||||
} catch (error: any) {
|
||||
@@ -1030,12 +1595,50 @@ export class SolanaOpenbookCreateMarket extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaManifestCreateMarket extends Tool {
|
||||
name = "solana_manifest_create_market";
|
||||
description = `Manifest market
|
||||
|
||||
Inputs (input is a json string):
|
||||
baseMint: string (required)
|
||||
quoteMint: string (required)
|
||||
`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
|
||||
const tx = await this.solanaKit.manifestCreateMarket(
|
||||
new PublicKey(inputFormat.baseMint),
|
||||
new PublicKey(inputFormat.quoteMint),
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Create manifest market successfully",
|
||||
transaction: tx[0],
|
||||
marketId: tx[1],
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaPythFetchPrice extends Tool {
|
||||
name = "solana_pyth_fetch_price";
|
||||
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();
|
||||
@@ -1043,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",
|
||||
};
|
||||
@@ -1302,9 +1909,9 @@ export class SolanaRockPaperScissorsTool extends Tool {
|
||||
const result = await this.solanaKit.rockPaperScissors(
|
||||
Number(parsedInput['"amount"']),
|
||||
parsedInput['"choice"'].replace(/^"|"$/g, "") as
|
||||
| "rock"
|
||||
| "paper"
|
||||
| "scissors",
|
||||
| "rock"
|
||||
| "paper"
|
||||
| "scissors",
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
@@ -1368,6 +1975,156 @@ export class SolanaTipLinkTool extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaListNFTForSaleTool extends Tool {
|
||||
name = "solana_list_nft_for_sale";
|
||||
description = `List an NFT for sale on Tensor Trade.
|
||||
|
||||
Inputs (input is a JSON string):
|
||||
nftMint: string, the mint address of the NFT (required)
|
||||
price: number, price in SOL (required)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = JSON.parse(input);
|
||||
|
||||
// Validate NFT ownership first
|
||||
const nftAccount =
|
||||
await this.solanaKit.connection.getTokenAccountsByOwner(
|
||||
this.solanaKit.wallet_address,
|
||||
{ mint: new PublicKey(parsedInput.nftMint) },
|
||||
);
|
||||
|
||||
if (nftAccount.value.length === 0) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message:
|
||||
"NFT not found in wallet. Please make sure you own this NFT.",
|
||||
code: "NFT_NOT_FOUND",
|
||||
});
|
||||
}
|
||||
|
||||
const tx = await this.solanaKit.tensorListNFT(
|
||||
new PublicKey(parsedInput.nftMint),
|
||||
parsedInput.price,
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "NFT listed for sale successfully",
|
||||
transaction: tx,
|
||||
price: parsedInput.price,
|
||||
nftMint: parsedInput.nftMint,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaCancelNFTListingTool extends Tool {
|
||||
name = "solana_cancel_nft_listing";
|
||||
description = `Cancel an NFT listing on Tensor Trade.
|
||||
|
||||
Inputs (input is a JSON string):
|
||||
nftMint: string, the mint address of the NFT (required)`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const parsedInput = JSON.parse(input);
|
||||
|
||||
const tx = await this.solanaKit.tensorCancelListing(
|
||||
new PublicKey(parsedInput.nftMint),
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "NFT listing cancelled successfully",
|
||||
transaction: tx,
|
||||
nftMint: parsedInput.nftMint,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(input: string): Promise<string> {
|
||||
try {
|
||||
const mint = input.trim();
|
||||
const report = await this.solanaKit.fetchTokenReportSummary(mint);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Token report summary fetched successfully",
|
||||
report,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
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",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
return [
|
||||
new SolanaBalanceTool(solanaKit),
|
||||
@@ -1385,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),
|
||||
@@ -1394,7 +2152,17 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
new SolanaRaydiumCreateClmm(solanaKit),
|
||||
new SolanaRaydiumCreateCpmm(solanaKit),
|
||||
new SolanaOpenbookCreateMarket(solanaKit),
|
||||
new SolanaCreateSingleSidedWhirlpoolTool(solanaKit),
|
||||
new SolanaManifestCreateMarket(solanaKit),
|
||||
new SolanaLimitOrderTool(solanaKit),
|
||||
new SolanaBatchOrderTool(solanaKit),
|
||||
new SolanaCancelAllOrdersTool(solanaKit),
|
||||
new SolanaWithdrawAllTool(solanaKit),
|
||||
new SolanaClosePosition(solanaKit),
|
||||
new SolanaOrcaCreateCLMM(solanaKit),
|
||||
new SolanaOrcaCreateSingleSideLiquidityPool(solanaKit),
|
||||
new SolanaOrcaFetchPositions(solanaKit),
|
||||
new SolanaOrcaOpenCenteredPosition(solanaKit),
|
||||
new SolanaOrcaOpenSingleSidedPosition(solanaKit),
|
||||
new SolanaPythFetchPrice(solanaKit),
|
||||
new SolanaResolveDomainTool(solanaKit),
|
||||
new SolanaGetOwnedDomains(solanaKit),
|
||||
@@ -1405,5 +2173,11 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
new SolanaCreateGibworkTask(solanaKit),
|
||||
new SolanaRockPaperScissorsTool(solanaKit),
|
||||
new SolanaTipLinkTool(solanaKit),
|
||||
new SolanaListNFTForSaleTool(solanaKit),
|
||||
new SolanaCancelNFTListingTool(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}`);
|
||||
}
|
||||
}
|
||||
37
src/tools/cancel_all_orders.ts
Normal file
37
src/tools/cancel_all_orders.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,12 @@ export async function create_image(
|
||||
n: number = 1,
|
||||
) {
|
||||
try {
|
||||
if (!agent.openai_api_key) {
|
||||
if (!agent.config.OPENAI_API_KEY) {
|
||||
throw new Error("OpenAI API key not found in agent configuration");
|
||||
}
|
||||
|
||||
const openai = new OpenAI({
|
||||
apiKey: agent.openai_api_key,
|
||||
apiKey: agent.config.OPENAI_API_KEY,
|
||||
});
|
||||
|
||||
const response = await openai.images.generate({
|
||||
|
||||
@@ -1,431 +0,0 @@
|
||||
import { Keypair, PublicKey, Transaction } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { BN, Wallet } from "@coral-xyz/anchor";
|
||||
import { Decimal } from "decimal.js";
|
||||
import {
|
||||
PDAUtil,
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
ORCA_WHIRLPOOLS_CONFIG,
|
||||
WhirlpoolContext,
|
||||
TickUtil,
|
||||
PriceMath,
|
||||
PoolUtil,
|
||||
TokenExtensionContextForPool,
|
||||
NO_TOKEN_EXTENSION_CONTEXT,
|
||||
TokenExtensionUtil,
|
||||
WhirlpoolIx,
|
||||
IncreaseLiquidityQuoteParam,
|
||||
increaseLiquidityQuoteByInputTokenWithParams,
|
||||
} from "@orca-so/whirlpools-sdk";
|
||||
import {
|
||||
Percentage,
|
||||
resolveOrCreateATAs,
|
||||
TransactionBuilder,
|
||||
} from "@orca-so/common-sdk";
|
||||
import {
|
||||
increaseLiquidityIx,
|
||||
increaseLiquidityV2Ix,
|
||||
initTickArrayIx,
|
||||
openPositionWithTokenExtensionsIx,
|
||||
} from "@orca-so/whirlpools-sdk/dist/instructions";
|
||||
import {
|
||||
getAssociatedTokenAddressSync,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import { sendTx } from "../utils/send_tx";
|
||||
|
||||
/**
|
||||
* Maps fee tier percentages to their corresponding tick spacing values in the Orca Whirlpool protocol.
|
||||
*
|
||||
* @remarks
|
||||
* Fee tiers determine the percentage of fees collected on swaps, while tick spacing affects
|
||||
* the granularity of price ranges for liquidity positions.
|
||||
*
|
||||
* For more details, refer to:
|
||||
* - [Whirlpool Fees](https://orca-so.github.io/whirlpools/Architecture%20Overview/Whirlpool%20Fees)
|
||||
* - [Whirlpool Parameters](https://orca-so.github.io/whirlpools/Architecture%20Overview/Whirlpool%20Parameters)
|
||||
*
|
||||
* @example
|
||||
* const tickSpacing = FEE_TIERS[0.30]; // Returns 64
|
||||
*/
|
||||
export const FEE_TIERS = {
|
||||
0.01: 1,
|
||||
0.02: 2,
|
||||
0.04: 4,
|
||||
0.05: 8,
|
||||
0.16: 16,
|
||||
0.3: 64,
|
||||
0.65: 96,
|
||||
1.0: 128,
|
||||
2.0: 256,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* # Creates a single-sided Whirlpool.
|
||||
*
|
||||
* This function initializes a new Whirlpool (liquidity pool) on Orca and seeds it with liquidity from a single token.
|
||||
*
|
||||
* ## Example Usage:
|
||||
* You created a new token called SHARK, and you want to set the initial price to 0.001 USDC.
|
||||
* You set `depositTokenMint` to SHARK's mint address and `otherTokenMint` to USDC's mint address.
|
||||
* You can minimize price impact for buyers in a few ways:
|
||||
* 1. Increase the amount of tokens you deposit
|
||||
* 2. Set the initial price very low
|
||||
* 3. Set the maximum price closer to the initial price
|
||||
*
|
||||
* ### Note for experts:
|
||||
* The Wrhirlpool program initializes the Whirlpool with the in a specific order. This might not be
|
||||
* the order you expect, so the function checks the order and adjusts the inverts the prices. This means that
|
||||
* on-chain the Whirlpool might be configured as USDC/SHARK instead of SHARK/USDC, and the on-chain price will
|
||||
* be 1/`initialPrice`. This will not affect the price of the token as you intended it to be.
|
||||
*
|
||||
* @param agent - The `SolanaAgentKit` instance representing the wallet and connection details.
|
||||
* @param depositTokenAmount - The amount of the deposit token (including the decimals) to contribute to the pool.
|
||||
* @param depositTokenMint - The mint address of the token being deposited into the pool, eg. SHARK.
|
||||
* @param otherTokenMint - The mint address of the other token in the pool, eg. USDC.
|
||||
* @param initialPrice - The initial price of the deposit token in terms of the other token.
|
||||
* @param maxPrice - The maximum price at which liquidity is added.
|
||||
* @param feeTier - The fee tier percentage for the pool, determining tick spacing and fee collection rates.
|
||||
*
|
||||
* @returns A promise that resolves to a transaction ID (`string`) of the transaction creating the pool.
|
||||
*
|
||||
* @throws Will throw an error if:
|
||||
* - Mint accounts for the tokens cannot be fetched.
|
||||
* - Prices are out of bounds.
|
||||
*
|
||||
* @remarks
|
||||
* This function is designed for single-sided deposits where users only contribute one type of token,
|
||||
* and the function manages mint order and necessary calculations.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { SolanaAgentKit } from "your-sdk";
|
||||
* import { PublicKey } from "@solana/web3.js";
|
||||
* import { BN } from "@coral-xyz/anchor";
|
||||
* import Decimal from "decimal.js";
|
||||
*
|
||||
* const agent = new SolanaAgentKit(wallet, connection);
|
||||
* const depositAmount = new BN(1_000_000_000_000); // 1 million SHARK if SHARK has 6 decimals
|
||||
* const depositTokenMint = new PublicKey("DEPOSTI_TOKEN_ADDRESS");
|
||||
* const otherTokenMint = new PublicKey("OTHER_TOKEN_ADDRESS");
|
||||
* const initialPrice = new Decimal(0.001);
|
||||
* const maxPrice = new Decimal(5.0);
|
||||
* const feeTier = 0.30;
|
||||
*
|
||||
* const txId = await createOrcaSingleSidedWhirlpool(
|
||||
* agent,
|
||||
* depositAmount,
|
||||
* depositTokenMint,
|
||||
* otherTokenMint,
|
||||
* initialPrice,
|
||||
* maxPrice,
|
||||
* feeTier,
|
||||
* );
|
||||
* console.log(`Single sided whirlpool created in transaction: ${txId}`);
|
||||
* ```
|
||||
*/
|
||||
export async function createOrcaSingleSidedWhirlpool(
|
||||
agent: SolanaAgentKit,
|
||||
depositTokenAmount: BN,
|
||||
depositTokenMint: PublicKey,
|
||||
otherTokenMint: PublicKey,
|
||||
initialPrice: Decimal,
|
||||
maxPrice: Decimal,
|
||||
feeTier: keyof typeof FEE_TIERS,
|
||||
): Promise<string> {
|
||||
const wallet = new Wallet(agent.wallet);
|
||||
const ctx = WhirlpoolContext.from(
|
||||
agent.connection,
|
||||
wallet,
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
);
|
||||
const fetcher = ctx.fetcher;
|
||||
|
||||
const correctTokenOrder = PoolUtil.orderMints(
|
||||
otherTokenMint,
|
||||
depositTokenMint,
|
||||
).map((addr) => addr.toString());
|
||||
const isCorrectMintOrder =
|
||||
correctTokenOrder[0] === depositTokenMint.toString();
|
||||
let mintA, mintB;
|
||||
if (isCorrectMintOrder) {
|
||||
[mintA, mintB] = [depositTokenMint, otherTokenMint];
|
||||
} else {
|
||||
[mintA, mintB] = [otherTokenMint, depositTokenMint];
|
||||
initialPrice = new Decimal(1 / initialPrice.toNumber());
|
||||
maxPrice = new Decimal(1 / maxPrice.toNumber());
|
||||
}
|
||||
const mintAAccount = await fetcher.getMintInfo(mintA);
|
||||
const mintBAccount = await fetcher.getMintInfo(mintB);
|
||||
if (mintAAccount === null || mintBAccount === null) {
|
||||
throw Error("Mint account not found");
|
||||
}
|
||||
const tickSpacing = FEE_TIERS[feeTier];
|
||||
const tickIndex = PriceMath.priceToTickIndex(
|
||||
initialPrice,
|
||||
mintAAccount.decimals,
|
||||
mintBAccount.decimals,
|
||||
);
|
||||
const initialTick = TickUtil.getInitializableTickIndex(
|
||||
tickIndex,
|
||||
tickSpacing,
|
||||
);
|
||||
|
||||
const tokenExtensionCtx: TokenExtensionContextForPool = {
|
||||
...NO_TOKEN_EXTENSION_CONTEXT,
|
||||
tokenMintWithProgramA: mintAAccount,
|
||||
tokenMintWithProgramB: mintBAccount,
|
||||
};
|
||||
const feeTierKey = PDAUtil.getFeeTier(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
ORCA_WHIRLPOOLS_CONFIG,
|
||||
tickSpacing,
|
||||
).publicKey;
|
||||
const initSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(initialTick);
|
||||
const tokenVaultAKeypair = Keypair.generate();
|
||||
const tokenVaultBKeypair = Keypair.generate();
|
||||
const whirlpoolPda = PDAUtil.getWhirlpool(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
ORCA_WHIRLPOOLS_CONFIG,
|
||||
mintA,
|
||||
mintB,
|
||||
FEE_TIERS[feeTier],
|
||||
);
|
||||
const tokenBadgeA = PDAUtil.getTokenBadge(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
ORCA_WHIRLPOOLS_CONFIG,
|
||||
mintA,
|
||||
).publicKey;
|
||||
const tokenBadgeB = PDAUtil.getTokenBadge(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
ORCA_WHIRLPOOLS_CONFIG,
|
||||
mintB,
|
||||
).publicKey;
|
||||
const baseParamsPool = {
|
||||
initSqrtPrice,
|
||||
whirlpoolsConfig: ORCA_WHIRLPOOLS_CONFIG,
|
||||
whirlpoolPda,
|
||||
tokenMintA: mintA,
|
||||
tokenMintB: mintB,
|
||||
tokenVaultAKeypair,
|
||||
tokenVaultBKeypair,
|
||||
feeTierKey,
|
||||
tickSpacing: tickSpacing,
|
||||
funder: wallet.publicKey,
|
||||
};
|
||||
const initPoolIx = !TokenExtensionUtil.isV2IxRequiredPool(tokenExtensionCtx)
|
||||
? WhirlpoolIx.initializePoolIx(ctx.program, baseParamsPool)
|
||||
: WhirlpoolIx.initializePoolV2Ix(ctx.program, {
|
||||
...baseParamsPool,
|
||||
tokenProgramA: tokenExtensionCtx.tokenMintWithProgramA.tokenProgram,
|
||||
tokenProgramB: tokenExtensionCtx.tokenMintWithProgramB.tokenProgram,
|
||||
tokenBadgeA,
|
||||
tokenBadgeB,
|
||||
});
|
||||
const initialTickArrayStartTick = TickUtil.getStartTickIndex(
|
||||
initialTick,
|
||||
tickSpacing,
|
||||
);
|
||||
const initialTickArrayPda = PDAUtil.getTickArray(
|
||||
ctx.program.programId,
|
||||
whirlpoolPda.publicKey,
|
||||
initialTickArrayStartTick,
|
||||
);
|
||||
|
||||
const txBuilder = new TransactionBuilder(
|
||||
ctx.provider.connection,
|
||||
ctx.provider.wallet,
|
||||
ctx.txBuilderOpts,
|
||||
);
|
||||
txBuilder.addInstruction(initPoolIx);
|
||||
txBuilder.addInstruction(
|
||||
initTickArrayIx(ctx.program, {
|
||||
startTick: initialTickArrayStartTick,
|
||||
tickArrayPda: initialTickArrayPda,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
funder: wallet.publicKey,
|
||||
}),
|
||||
);
|
||||
|
||||
let tickLowerIndex, tickUpperIndex;
|
||||
if (isCorrectMintOrder) {
|
||||
tickLowerIndex = initialTick;
|
||||
tickUpperIndex = PriceMath.priceToTickIndex(
|
||||
maxPrice,
|
||||
mintAAccount.decimals,
|
||||
mintBAccount.decimals,
|
||||
);
|
||||
} else {
|
||||
tickLowerIndex = PriceMath.priceToTickIndex(
|
||||
maxPrice,
|
||||
mintAAccount.decimals,
|
||||
mintBAccount.decimals,
|
||||
);
|
||||
tickUpperIndex = initialTick;
|
||||
}
|
||||
const tickLowerInitializableIndex = TickUtil.getInitializableTickIndex(
|
||||
tickLowerIndex,
|
||||
tickSpacing,
|
||||
);
|
||||
const tickUpperInitializableIndex = TickUtil.getInitializableTickIndex(
|
||||
tickUpperIndex,
|
||||
tickSpacing,
|
||||
);
|
||||
if (
|
||||
!TickUtil.checkTickInBounds(tickLowerInitializableIndex) ||
|
||||
!TickUtil.checkTickInBounds(tickUpperInitializableIndex)
|
||||
) {
|
||||
throw Error("Prices out of bounds");
|
||||
}
|
||||
const increasLiquidityQuoteParam: IncreaseLiquidityQuoteParam = {
|
||||
inputTokenAmount: new BN(depositTokenAmount),
|
||||
inputTokenMint: depositTokenMint,
|
||||
tokenMintA: mintA,
|
||||
tokenMintB: mintB,
|
||||
tickCurrentIndex: initialTick,
|
||||
sqrtPrice: initSqrtPrice,
|
||||
tickLowerIndex: tickLowerInitializableIndex,
|
||||
tickUpperIndex: tickUpperInitializableIndex,
|
||||
tokenExtensionCtx: tokenExtensionCtx,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100),
|
||||
};
|
||||
const liquidityInput = increaseLiquidityQuoteByInputTokenWithParams(
|
||||
increasLiquidityQuoteParam,
|
||||
);
|
||||
const { liquidityAmount: liquidity, tokenMaxA, tokenMaxB } = liquidityInput;
|
||||
|
||||
const positionMintKeypair = Keypair.generate();
|
||||
const positionMintPubkey = positionMintKeypair.publicKey;
|
||||
const positionPda = PDAUtil.getPosition(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
positionMintPubkey,
|
||||
);
|
||||
const positionTokenAccountAddress = getAssociatedTokenAddressSync(
|
||||
positionMintPubkey,
|
||||
wallet.publicKey,
|
||||
ctx.accountResolverOpts.allowPDAOwnerAddress,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
);
|
||||
const params = {
|
||||
funder: wallet.publicKey,
|
||||
owner: wallet.publicKey,
|
||||
positionPda,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
tickLowerIndex: tickLowerInitializableIndex,
|
||||
tickUpperIndex: tickUpperInitializableIndex,
|
||||
};
|
||||
const positionIx = openPositionWithTokenExtensionsIx(ctx.program, {
|
||||
...params,
|
||||
positionMint: positionMintPubkey,
|
||||
withTokenMetadataExtension: true,
|
||||
});
|
||||
|
||||
txBuilder.addInstruction(positionIx);
|
||||
txBuilder.addSigner(positionMintKeypair);
|
||||
|
||||
const [ataA, ataB] = await resolveOrCreateATAs(
|
||||
ctx.connection,
|
||||
wallet.publicKey,
|
||||
[
|
||||
{ tokenMint: mintA, wrappedSolAmountIn: tokenMaxA },
|
||||
{ tokenMint: mintB, wrappedSolAmountIn: tokenMaxB },
|
||||
],
|
||||
() => ctx.fetcher.getAccountRentExempt(),
|
||||
wallet.publicKey,
|
||||
undefined,
|
||||
ctx.accountResolverOpts.allowPDAOwnerAddress,
|
||||
ctx.accountResolverOpts.createWrappedSolAccountMethod,
|
||||
);
|
||||
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = ataA;
|
||||
const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = ataB;
|
||||
|
||||
txBuilder.addInstruction(tokenOwnerAccountAIx);
|
||||
txBuilder.addInstruction(tokenOwnerAccountBIx);
|
||||
|
||||
const tickArrayLowerStartIndex = TickUtil.getStartTickIndex(
|
||||
tickLowerInitializableIndex,
|
||||
tickSpacing,
|
||||
);
|
||||
const tickArrayUpperStartIndex = TickUtil.getStartTickIndex(
|
||||
tickUpperInitializableIndex,
|
||||
tickSpacing,
|
||||
);
|
||||
const tickArrayLowerPda = PDAUtil.getTickArray(
|
||||
ctx.program.programId,
|
||||
whirlpoolPda.publicKey,
|
||||
tickArrayLowerStartIndex,
|
||||
);
|
||||
const tickArrayUpperPda = PDAUtil.getTickArray(
|
||||
ctx.program.programId,
|
||||
whirlpoolPda.publicKey,
|
||||
tickArrayUpperStartIndex,
|
||||
);
|
||||
if (tickArrayUpperStartIndex !== tickArrayLowerStartIndex) {
|
||||
if (isCorrectMintOrder) {
|
||||
txBuilder.addInstruction(
|
||||
initTickArrayIx(ctx.program, {
|
||||
startTick: tickArrayUpperStartIndex,
|
||||
tickArrayPda: tickArrayUpperPda,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
funder: wallet.publicKey,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
txBuilder.addInstruction(
|
||||
initTickArrayIx(ctx.program, {
|
||||
startTick: tickArrayLowerStartIndex,
|
||||
tickArrayPda: tickArrayLowerPda,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
funder: wallet.publicKey,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const baseParamsLiquidity = {
|
||||
liquidityAmount: liquidity,
|
||||
tokenMaxA,
|
||||
tokenMaxB,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: wallet.publicKey,
|
||||
position: positionPda.publicKey,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
tokenOwnerAccountA,
|
||||
tokenOwnerAccountB,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
tickArrayLower: tickArrayLowerPda.publicKey,
|
||||
tickArrayUpper: tickArrayUpperPda.publicKey,
|
||||
};
|
||||
|
||||
const liquidityIx = !TokenExtensionUtil.isV2IxRequiredPool(tokenExtensionCtx)
|
||||
? increaseLiquidityIx(ctx.program, baseParamsLiquidity)
|
||||
: increaseLiquidityV2Ix(ctx.program, {
|
||||
...baseParamsLiquidity,
|
||||
tokenMintA: mintA,
|
||||
tokenMintB: mintB,
|
||||
tokenProgramA: tokenExtensionCtx.tokenMintWithProgramA.tokenProgram,
|
||||
tokenProgramB: tokenExtensionCtx.tokenMintWithProgramB.tokenProgram,
|
||||
});
|
||||
txBuilder.addInstruction(liquidityIx);
|
||||
|
||||
const txPayload = await txBuilder.build({
|
||||
maxSupportedTransactionVersion: "legacy",
|
||||
});
|
||||
|
||||
if (txPayload.transaction instanceof Transaction) {
|
||||
try {
|
||||
const txId = await sendTx(agent, txPayload.transaction, [
|
||||
positionMintKeypair,
|
||||
tokenVaultAKeypair,
|
||||
tokenVaultBKeypair,
|
||||
]);
|
||||
return txId;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to create pool: ${JSON.stringify(error)}`);
|
||||
}
|
||||
} else {
|
||||
throw new Error("Failed to create pool: Transaction not created");
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ export async function deploy_token(
|
||||
mint: mint.publicKey,
|
||||
tokenStandard: TokenStandard.Fungible,
|
||||
tokenOwner: fromWeb3JsPublicKey(agent.wallet_address),
|
||||
amount: initialSupply,
|
||||
amount: initialSupply * Math.pow(10, decimals),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,11 +8,10 @@ import { getAllTld } from "@onsol/tldparser";
|
||||
*/
|
||||
export async function getAllDomainsTLDs(
|
||||
agent: SolanaAgentKit,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
): Promise<String[]> {
|
||||
): Promise<string[]> {
|
||||
try {
|
||||
const tlds = await getAllTld(agent.connection);
|
||||
return tlds.map((tld) => tld.tld);
|
||||
return tlds.map((tld) => String(tld.tld));
|
||||
} catch (error: any) {
|
||||
throw new Error(`Failed to fetch TLDs: ${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,43 +1,49 @@
|
||||
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 "./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 "./create_orca_single_sided_whirlpool";
|
||||
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 "./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 "./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 "./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 "./trade";
|
||||
export * from "./transfer";
|
||||
export * from "./withdraw_all";
|
||||
|
||||
61
src/tools/limit_order.ts
Normal file
61
src/tools/limit_order.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
43
src/tools/manifest_create_market.ts
Normal file
43
src/tools/manifest_create_market.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
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()];
|
||||
}
|
||||
82
src/tools/orca_close_position.ts
Normal file
82
src/tools/orca_close_position.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import {
|
||||
Keypair,
|
||||
PublicKey,
|
||||
TransactionMessage,
|
||||
VersionedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { Wallet } from "@coral-xyz/anchor";
|
||||
import {
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
WhirlpoolContext,
|
||||
buildWhirlpoolClient,
|
||||
PDAUtil,
|
||||
} from "@orca-so/whirlpools-sdk";
|
||||
import { sendTx } from "../utils/send_tx";
|
||||
import { Percentage } from "@orca-so/common-sdk";
|
||||
|
||||
/**
|
||||
* # Closes a Liquidity Position in an Orca Whirlpool
|
||||
*
|
||||
* This function closes an existing liquidity position in a specified Orca Whirlpool. The user provides
|
||||
* the position's mint address.
|
||||
*
|
||||
* ## Parameters
|
||||
* - `agent`: The `SolanaAgentKit` instance representing the wallet and connection details.
|
||||
* - `positionMintAddress`: The mint address of the liquidity position to close.
|
||||
*
|
||||
* ## Returns
|
||||
* A `Promise` that resolves to a `string` containing the transaction ID of the transaction
|
||||
*
|
||||
* ## Notes
|
||||
* - The function uses Orca’s SDK to interact with the specified Whirlpool and close the liquidity position.
|
||||
* - A maximum slippage of 1% is assumed for liquidity provision during the position closing.
|
||||
* - The function automatically fetches the associated Whirlpool address and position details using the provided mint address.
|
||||
*
|
||||
* ## Throws
|
||||
* An error will be thrown if:
|
||||
* - The specified position mint address is invalid or inaccessible.
|
||||
* - The transaction fails to send.
|
||||
* - Any required position or Whirlpool data cannot be fetched.
|
||||
*
|
||||
* @param agent - The `SolanaAgentKit` instance representing the wallet and connection.
|
||||
* @param positionMintAddress - The mint address of the liquidity position to close.
|
||||
* @returns A promise resolving to the transaction ID (`string`).
|
||||
*/
|
||||
export async function orcaClosePosition(
|
||||
agent: SolanaAgentKit,
|
||||
positionMintAddress: PublicKey,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const wallet = new Wallet(agent.wallet);
|
||||
const ctx = WhirlpoolContext.from(
|
||||
agent.connection,
|
||||
wallet,
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
);
|
||||
const client = buildWhirlpoolClient(ctx);
|
||||
|
||||
const positionAddress = PDAUtil.getPosition(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
positionMintAddress,
|
||||
);
|
||||
const position = await client.getPosition(positionAddress.publicKey);
|
||||
const whirlpoolAddress = position.getData().whirlpool;
|
||||
const whirlpool = await client.getPool(whirlpoolAddress);
|
||||
const txBuilder = await whirlpool.closePosition(
|
||||
positionAddress.publicKey,
|
||||
Percentage.fromFraction(1, 100),
|
||||
);
|
||||
const txPayload = await txBuilder[0].build();
|
||||
const txPayloadDecompiled = TransactionMessage.decompile(
|
||||
(txPayload.transaction as VersionedTransaction).message,
|
||||
);
|
||||
const instructions = txPayloadDecompiled.instructions;
|
||||
const signers = txPayload.signers as Keypair[];
|
||||
|
||||
const txId = await sendTx(agent, instructions, signers);
|
||||
return txId;
|
||||
} catch (error) {
|
||||
throw new Error(`${error}`);
|
||||
}
|
||||
}
|
||||
132
src/tools/orca_create_clmm.ts
Normal file
132
src/tools/orca_create_clmm.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import {
|
||||
Keypair,
|
||||
PublicKey,
|
||||
TransactionMessage,
|
||||
VersionedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { Wallet } from "@coral-xyz/anchor";
|
||||
import { Decimal } from "decimal.js";
|
||||
import {
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
WhirlpoolContext,
|
||||
PriceMath,
|
||||
PoolUtil,
|
||||
buildWhirlpoolClient,
|
||||
} from "@orca-so/whirlpools-sdk";
|
||||
import { sendTx } from "../utils/send_tx";
|
||||
import { FEE_TIERS } from "./orca_create_single_sided_liquidity_pool";
|
||||
|
||||
/**
|
||||
* # Creates a CLMM Pool (Concentrated Liquidity Market Maker Pool).
|
||||
*
|
||||
* This function initializes a new Whirlpool (CLMM Pool) on Orca. It only sets up the pool and does not seed it with liquidity.
|
||||
*
|
||||
* ## Example Usage:
|
||||
* Suppose you want to create a CLMM pool with two tokens, SHARK and USDC, and set the initial price of SHARK to 0.001 USDC.
|
||||
* You would call this function with `mintA` as SHARK's mint address and `mintB` as USDC's mint address. The pool is created
|
||||
* with the specified fee tier and tick spacing associated with that fee tier.
|
||||
*
|
||||
* ### Note for Experts:
|
||||
* The Whirlpool program determines the token mint order, which might not match your expectation. This function
|
||||
* adjusts the input order as needed and inverts the initial price accordingly.
|
||||
*
|
||||
* @param agent - The `SolanaAgentKit` instance representing the wallet and connection details.
|
||||
* @param mintDeploy - The mint of the token you want to deploy (e.g., SHARK).
|
||||
* @param mintPair - The mint of the token you want to pair the deployed mint with (e.g., USDC).
|
||||
* @param initialPrice - The initial price of `mintDeploy` in terms of `mintPair`.
|
||||
* @param feeTier - The fee tier bps for the pool, determining tick spacing and fee collection rates.
|
||||
*
|
||||
* @returns A promise that resolves to a transaction ID (`string`) of the transaction creating the pool.
|
||||
*
|
||||
* @throws Will throw an error if:
|
||||
* - Mint accounts for the tokens cannot be fetched.
|
||||
* - The network is unsupported.
|
||||
*
|
||||
* @remarks
|
||||
* This function only initializes the CLMM pool and does not add liquidity. For adding liquidity, you can use
|
||||
* a separate function after the pool is successfully created.
|
||||
* ```
|
||||
*/
|
||||
export async function orcaCreateCLMM(
|
||||
agent: SolanaAgentKit,
|
||||
mintDeploy: PublicKey,
|
||||
mintPair: PublicKey,
|
||||
initialPrice: Decimal,
|
||||
feeTier: keyof typeof FEE_TIERS,
|
||||
): Promise<string> {
|
||||
try {
|
||||
let whirlpoolsConfigAddress: PublicKey;
|
||||
if (agent.connection.rpcEndpoint.includes("mainnet")) {
|
||||
whirlpoolsConfigAddress = new PublicKey(
|
||||
"2LecshUwdy9xi7meFgHtFJQNSKk4KdTrcpvaB56dP2NQ",
|
||||
);
|
||||
} else if (agent.connection.rpcEndpoint.includes("devnet")) {
|
||||
whirlpoolsConfigAddress = new PublicKey(
|
||||
"FcrweFY1G9HJAHG5inkGB6pKg1HZ6x9UC2WioAfWrGkR",
|
||||
);
|
||||
} else {
|
||||
throw new Error("Unsupported network");
|
||||
}
|
||||
const wallet = new Wallet(agent.wallet);
|
||||
const ctx = WhirlpoolContext.from(
|
||||
agent.connection,
|
||||
wallet,
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
);
|
||||
const fetcher = ctx.fetcher;
|
||||
const client = buildWhirlpoolClient(ctx);
|
||||
|
||||
const correctTokenOrder = PoolUtil.orderMints(mintDeploy, mintPair).map(
|
||||
(addr) => addr.toString(),
|
||||
);
|
||||
const isCorrectMintOrder = correctTokenOrder[0] === mintDeploy.toString();
|
||||
let mintA;
|
||||
let mintB;
|
||||
if (!isCorrectMintOrder) {
|
||||
[mintA, mintB] = [mintPair, mintDeploy];
|
||||
initialPrice = new Decimal(1 / initialPrice.toNumber());
|
||||
} else {
|
||||
[mintA, mintB] = [mintDeploy, mintPair];
|
||||
}
|
||||
const mintAAccount = await fetcher.getMintInfo(mintA);
|
||||
const mintBAccount = await fetcher.getMintInfo(mintB);
|
||||
if (mintAAccount === null || mintBAccount === null) {
|
||||
throw Error("Mint account not found");
|
||||
}
|
||||
|
||||
const tickSpacing = FEE_TIERS[feeTier];
|
||||
const initialTick = PriceMath.priceToInitializableTickIndex(
|
||||
initialPrice,
|
||||
mintAAccount.decimals,
|
||||
mintBAccount.decimals,
|
||||
tickSpacing,
|
||||
);
|
||||
const { poolKey, tx: txBuilder } = await client.createPool(
|
||||
whirlpoolsConfigAddress,
|
||||
mintA,
|
||||
mintB,
|
||||
tickSpacing,
|
||||
initialTick,
|
||||
wallet.publicKey,
|
||||
);
|
||||
|
||||
const txPayload = await txBuilder.build();
|
||||
const txPayloadDecompiled = TransactionMessage.decompile(
|
||||
(txPayload.transaction as VersionedTransaction).message,
|
||||
);
|
||||
const instructions = txPayloadDecompiled.instructions;
|
||||
|
||||
const txId = await sendTx(
|
||||
agent,
|
||||
instructions,
|
||||
txPayload.signers as Keypair[],
|
||||
);
|
||||
return JSON.stringify({
|
||||
transactionId: txId,
|
||||
whirlpoolAddress: poolKey.toString(),
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`${error}`);
|
||||
}
|
||||
}
|
||||
422
src/tools/orca_create_single_sided_liquidity_pool.ts
Normal file
422
src/tools/orca_create_single_sided_liquidity_pool.ts
Normal file
@@ -0,0 +1,422 @@
|
||||
import {
|
||||
Keypair,
|
||||
PublicKey,
|
||||
TransactionMessage,
|
||||
VersionedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { BN, Wallet } from "@coral-xyz/anchor";
|
||||
import { Decimal } from "decimal.js";
|
||||
import {
|
||||
PDAUtil,
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
WhirlpoolContext,
|
||||
TickUtil,
|
||||
PriceMath,
|
||||
PoolUtil,
|
||||
TokenExtensionContextForPool,
|
||||
NO_TOKEN_EXTENSION_CONTEXT,
|
||||
TokenExtensionUtil,
|
||||
WhirlpoolIx,
|
||||
IncreaseLiquidityQuoteParam,
|
||||
increaseLiquidityQuoteByInputTokenWithParams,
|
||||
} from "@orca-so/whirlpools-sdk";
|
||||
import {
|
||||
Percentage,
|
||||
resolveOrCreateATAs,
|
||||
TransactionBuilder,
|
||||
} from "@orca-so/common-sdk";
|
||||
import {
|
||||
increaseLiquidityIx,
|
||||
increaseLiquidityV2Ix,
|
||||
initTickArrayIx,
|
||||
openPositionWithTokenExtensionsIx,
|
||||
} from "@orca-so/whirlpools-sdk/dist/instructions";
|
||||
import {
|
||||
getAssociatedTokenAddressSync,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import { sendTx } from "../utils/send_tx";
|
||||
|
||||
/**
|
||||
* Maps fee tier bps to their corresponding tick spacing values in the Orca Whirlpool protocol.
|
||||
*
|
||||
* @remarks
|
||||
* Fee tiers determine the percentage of fees collected on swaps, while tick spacing affects
|
||||
* the granularity of price ranges for liquidity positions.
|
||||
*
|
||||
* For more details, refer to:
|
||||
* - [Whirlpool Fees](https://orca-so.github.io/whirlpools/Architecture%20Overview/Whirlpool%20Fees)
|
||||
* - [Whirlpool Parameters](https://orca-so.github.io/whirlpools/Architecture%20Overview/Whirlpool%20Parameters)
|
||||
*
|
||||
* @example
|
||||
* const tickSpacing = FEE_TIERS[1]; // returns 1
|
||||
*/
|
||||
export const FEE_TIERS = {
|
||||
1: 1,
|
||||
2: 2,
|
||||
4: 4,
|
||||
5: 8,
|
||||
16: 16,
|
||||
30: 64,
|
||||
65: 96,
|
||||
100: 128,
|
||||
200: 256,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* # Creates a single-sided liquidity pool.
|
||||
*
|
||||
* This function initializes a new Whirlpool (liquidity pool) on Orca and seeds it with liquidity from a single token.
|
||||
*
|
||||
* ## Example Usage:
|
||||
* You created a new token called SHARK, and you want to set the initial price to 0.001 USDC.
|
||||
* You set `depositTokenMint` to SHARK's mint address and `otherTokenMint` to USDC's mint address.
|
||||
* You can minimize price impact for buyers in a few ways:
|
||||
* 1. Increase the amount of tokens you deposit
|
||||
* 2. Set the initial price very low
|
||||
* 3. Set the maximum price closer to the initial price
|
||||
*
|
||||
* ### Note for experts:
|
||||
* The Wrhirlpool program initializes the Whirlpool with the in a specific order. This might not be
|
||||
* the order you expect, so the function checks the order and adjusts the inverts the prices. This means that
|
||||
* on-chain the Whirlpool might be configured as USDC/SHARK instead of SHARK/USDC, and the on-chain price will
|
||||
* be 1/`initialPrice`. This will not affect the price of the token as you intended it to be.
|
||||
*
|
||||
* @param agent - The `SolanaAgentKit` instance representing the wallet and connection details.
|
||||
* @param depositTokenAmount - The amount of the deposit token to deposit in the pool.
|
||||
* @param depositTokenMint - The mint address of the token being deposited into the pool, eg. SHARK.
|
||||
* @param otherTokenMint - The mint address of the other token in the pool, eg. USDC.
|
||||
* @param initialPrice - The initial price of the deposit token in terms of the other token.
|
||||
* @param maxPrice - The maximum price at which liquidity is added.
|
||||
* @param feeTier - The fee tier bps for the pool, determining tick spacing and fee collection rates.
|
||||
*
|
||||
* @returns A promise that resolves to a transaction ID (`string`) of the transaction creating the pool.
|
||||
*
|
||||
* @throws Will throw an error if:
|
||||
* - Mint accounts for the tokens cannot be fetched.
|
||||
* - Prices are out of bounds.
|
||||
*
|
||||
* @remarks
|
||||
* This function is designed for single-sided deposits where users only contribute one type of token,
|
||||
* and the function manages mint order and necessary calculations.
|
||||
*/
|
||||
export async function orcaCreateSingleSidedLiquidityPool(
|
||||
agent: SolanaAgentKit,
|
||||
depositTokenAmount: number,
|
||||
depositTokenMint: PublicKey,
|
||||
otherTokenMint: PublicKey,
|
||||
initialPrice: Decimal,
|
||||
maxPrice: Decimal,
|
||||
feeTierBps: keyof typeof FEE_TIERS,
|
||||
): Promise<string> {
|
||||
try {
|
||||
let whirlpoolsConfigAddress: PublicKey;
|
||||
if (agent.connection.rpcEndpoint.includes("mainnet")) {
|
||||
whirlpoolsConfigAddress = new PublicKey(
|
||||
"2LecshUwdy9xi7meFgHtFJQNSKk4KdTrcpvaB56dP2NQ",
|
||||
);
|
||||
} else if (agent.connection.rpcEndpoint.includes("devnet")) {
|
||||
whirlpoolsConfigAddress = new PublicKey(
|
||||
"FcrweFY1G9HJAHG5inkGB6pKg1HZ6x9UC2WioAfWrGkR",
|
||||
);
|
||||
} else {
|
||||
throw new Error("Unsupported network");
|
||||
}
|
||||
const wallet = new Wallet(agent.wallet);
|
||||
const ctx = WhirlpoolContext.from(
|
||||
agent.connection,
|
||||
wallet,
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
);
|
||||
const fetcher = ctx.fetcher;
|
||||
|
||||
const correctTokenOrder = PoolUtil.orderMints(
|
||||
otherTokenMint,
|
||||
depositTokenMint,
|
||||
).map((addr) => addr.toString());
|
||||
const isCorrectMintOrder =
|
||||
correctTokenOrder[0] === depositTokenMint.toString();
|
||||
let mintA, mintB;
|
||||
if (isCorrectMintOrder) {
|
||||
[mintA, mintB] = [depositTokenMint, otherTokenMint];
|
||||
} else {
|
||||
[mintA, mintB] = [otherTokenMint, depositTokenMint];
|
||||
initialPrice = new Decimal(1 / initialPrice.toNumber());
|
||||
maxPrice = new Decimal(1 / maxPrice.toNumber());
|
||||
}
|
||||
const mintAAccount = await fetcher.getMintInfo(mintA);
|
||||
const mintBAccount = await fetcher.getMintInfo(mintB);
|
||||
if (mintAAccount === null || mintBAccount === null) {
|
||||
throw Error("Mint account not found");
|
||||
}
|
||||
const tickSpacing = FEE_TIERS[feeTierBps];
|
||||
const tickIndex = PriceMath.priceToTickIndex(
|
||||
initialPrice,
|
||||
mintAAccount.decimals,
|
||||
mintBAccount.decimals,
|
||||
);
|
||||
const initialTick = TickUtil.getInitializableTickIndex(
|
||||
tickIndex,
|
||||
tickSpacing,
|
||||
);
|
||||
|
||||
const tokenExtensionCtx: TokenExtensionContextForPool = {
|
||||
...NO_TOKEN_EXTENSION_CONTEXT,
|
||||
tokenMintWithProgramA: mintAAccount,
|
||||
tokenMintWithProgramB: mintBAccount,
|
||||
};
|
||||
const feeTierKey = PDAUtil.getFeeTier(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
whirlpoolsConfigAddress,
|
||||
tickSpacing,
|
||||
).publicKey;
|
||||
const initSqrtPrice = PriceMath.tickIndexToSqrtPriceX64(initialTick);
|
||||
const tokenVaultAKeypair = Keypair.generate();
|
||||
const tokenVaultBKeypair = Keypair.generate();
|
||||
const whirlpoolPda = PDAUtil.getWhirlpool(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
whirlpoolsConfigAddress,
|
||||
mintA,
|
||||
mintB,
|
||||
FEE_TIERS[feeTierBps],
|
||||
);
|
||||
const tokenBadgeA = PDAUtil.getTokenBadge(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
whirlpoolsConfigAddress,
|
||||
mintA,
|
||||
).publicKey;
|
||||
const tokenBadgeB = PDAUtil.getTokenBadge(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
whirlpoolsConfigAddress,
|
||||
mintB,
|
||||
).publicKey;
|
||||
const baseParamsPool = {
|
||||
initSqrtPrice,
|
||||
whirlpoolsConfig: whirlpoolsConfigAddress,
|
||||
whirlpoolPda,
|
||||
tokenMintA: mintA,
|
||||
tokenMintB: mintB,
|
||||
tokenVaultAKeypair,
|
||||
tokenVaultBKeypair,
|
||||
feeTierKey,
|
||||
tickSpacing: tickSpacing,
|
||||
funder: wallet.publicKey,
|
||||
};
|
||||
const initPoolIx = !TokenExtensionUtil.isV2IxRequiredPool(tokenExtensionCtx)
|
||||
? WhirlpoolIx.initializePoolIx(ctx.program, baseParamsPool)
|
||||
: WhirlpoolIx.initializePoolV2Ix(ctx.program, {
|
||||
...baseParamsPool,
|
||||
tokenProgramA: tokenExtensionCtx.tokenMintWithProgramA.tokenProgram,
|
||||
tokenProgramB: tokenExtensionCtx.tokenMintWithProgramB.tokenProgram,
|
||||
tokenBadgeA,
|
||||
tokenBadgeB,
|
||||
});
|
||||
const initialTickArrayStartTick = TickUtil.getStartTickIndex(
|
||||
initialTick,
|
||||
tickSpacing,
|
||||
);
|
||||
const initialTickArrayPda = PDAUtil.getTickArray(
|
||||
ctx.program.programId,
|
||||
whirlpoolPda.publicKey,
|
||||
initialTickArrayStartTick,
|
||||
);
|
||||
|
||||
const txBuilder = new TransactionBuilder(
|
||||
ctx.provider.connection,
|
||||
ctx.provider.wallet,
|
||||
ctx.txBuilderOpts,
|
||||
);
|
||||
txBuilder.addInstruction(initPoolIx);
|
||||
txBuilder.addInstruction(
|
||||
initTickArrayIx(ctx.program, {
|
||||
startTick: initialTickArrayStartTick,
|
||||
tickArrayPda: initialTickArrayPda,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
funder: wallet.publicKey,
|
||||
}),
|
||||
);
|
||||
|
||||
let tickLowerIndex, tickUpperIndex;
|
||||
if (isCorrectMintOrder) {
|
||||
tickLowerIndex = initialTick;
|
||||
tickUpperIndex = PriceMath.priceToTickIndex(
|
||||
maxPrice,
|
||||
mintAAccount.decimals,
|
||||
mintBAccount.decimals,
|
||||
);
|
||||
} else {
|
||||
tickLowerIndex = PriceMath.priceToTickIndex(
|
||||
maxPrice,
|
||||
mintAAccount.decimals,
|
||||
mintBAccount.decimals,
|
||||
);
|
||||
tickUpperIndex = initialTick;
|
||||
}
|
||||
const tickLowerInitializableIndex = TickUtil.getInitializableTickIndex(
|
||||
tickLowerIndex,
|
||||
tickSpacing,
|
||||
);
|
||||
const tickUpperInitializableIndex = TickUtil.getInitializableTickIndex(
|
||||
tickUpperIndex,
|
||||
tickSpacing,
|
||||
);
|
||||
if (
|
||||
!TickUtil.checkTickInBounds(tickLowerInitializableIndex) ||
|
||||
!TickUtil.checkTickInBounds(tickUpperInitializableIndex)
|
||||
) {
|
||||
throw Error("Prices out of bounds");
|
||||
}
|
||||
depositTokenAmount = isCorrectMintOrder
|
||||
? depositTokenAmount * Math.pow(10, mintAAccount.decimals)
|
||||
: depositTokenAmount * Math.pow(10, mintBAccount.decimals);
|
||||
const increasLiquidityQuoteParam: IncreaseLiquidityQuoteParam = {
|
||||
inputTokenAmount: new BN(depositTokenAmount),
|
||||
inputTokenMint: depositTokenMint,
|
||||
tokenMintA: mintA,
|
||||
tokenMintB: mintB,
|
||||
tickCurrentIndex: initialTick,
|
||||
sqrtPrice: initSqrtPrice,
|
||||
tickLowerIndex: tickLowerInitializableIndex,
|
||||
tickUpperIndex: tickUpperInitializableIndex,
|
||||
tokenExtensionCtx: tokenExtensionCtx,
|
||||
slippageTolerance: Percentage.fromFraction(0, 100),
|
||||
};
|
||||
const liquidityInput = increaseLiquidityQuoteByInputTokenWithParams(
|
||||
increasLiquidityQuoteParam,
|
||||
);
|
||||
const { liquidityAmount: liquidity, tokenMaxA, tokenMaxB } = liquidityInput;
|
||||
|
||||
const positionMintKeypair = Keypair.generate();
|
||||
const positionMintPubkey = positionMintKeypair.publicKey;
|
||||
const positionPda = PDAUtil.getPosition(
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
positionMintPubkey,
|
||||
);
|
||||
const positionTokenAccountAddress = getAssociatedTokenAddressSync(
|
||||
positionMintPubkey,
|
||||
wallet.publicKey,
|
||||
ctx.accountResolverOpts.allowPDAOwnerAddress,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
);
|
||||
const params = {
|
||||
funder: wallet.publicKey,
|
||||
owner: wallet.publicKey,
|
||||
positionPda,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
tickLowerIndex: tickLowerInitializableIndex,
|
||||
tickUpperIndex: tickUpperInitializableIndex,
|
||||
};
|
||||
const positionIx = openPositionWithTokenExtensionsIx(ctx.program, {
|
||||
...params,
|
||||
positionMint: positionMintPubkey,
|
||||
withTokenMetadataExtension: true,
|
||||
});
|
||||
|
||||
txBuilder.addInstruction(positionIx);
|
||||
txBuilder.addSigner(positionMintKeypair);
|
||||
|
||||
const [ataA, ataB] = await resolveOrCreateATAs(
|
||||
ctx.connection,
|
||||
wallet.publicKey,
|
||||
[
|
||||
{ tokenMint: mintA, wrappedSolAmountIn: tokenMaxA },
|
||||
{ tokenMint: mintB, wrappedSolAmountIn: tokenMaxB },
|
||||
],
|
||||
() => ctx.fetcher.getAccountRentExempt(),
|
||||
wallet.publicKey,
|
||||
undefined,
|
||||
ctx.accountResolverOpts.allowPDAOwnerAddress,
|
||||
"ata",
|
||||
);
|
||||
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = ataA;
|
||||
const { address: tokenOwnerAccountB, ...tokenOwnerAccountBIx } = ataB;
|
||||
|
||||
txBuilder.addInstruction(tokenOwnerAccountAIx);
|
||||
txBuilder.addInstruction(tokenOwnerAccountBIx);
|
||||
|
||||
const tickArrayLowerStartIndex = TickUtil.getStartTickIndex(
|
||||
tickLowerInitializableIndex,
|
||||
tickSpacing,
|
||||
);
|
||||
const tickArrayUpperStartIndex = TickUtil.getStartTickIndex(
|
||||
tickUpperInitializableIndex,
|
||||
tickSpacing,
|
||||
);
|
||||
const tickArrayLowerPda = PDAUtil.getTickArray(
|
||||
ctx.program.programId,
|
||||
whirlpoolPda.publicKey,
|
||||
tickArrayLowerStartIndex,
|
||||
);
|
||||
const tickArrayUpperPda = PDAUtil.getTickArray(
|
||||
ctx.program.programId,
|
||||
whirlpoolPda.publicKey,
|
||||
tickArrayUpperStartIndex,
|
||||
);
|
||||
if (tickArrayUpperStartIndex !== tickArrayLowerStartIndex) {
|
||||
if (isCorrectMintOrder) {
|
||||
txBuilder.addInstruction(
|
||||
initTickArrayIx(ctx.program, {
|
||||
startTick: tickArrayUpperStartIndex,
|
||||
tickArrayPda: tickArrayUpperPda,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
funder: wallet.publicKey,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
txBuilder.addInstruction(
|
||||
initTickArrayIx(ctx.program, {
|
||||
startTick: tickArrayLowerStartIndex,
|
||||
tickArrayPda: tickArrayLowerPda,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
funder: wallet.publicKey,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const baseParamsLiquidity = {
|
||||
liquidityAmount: liquidity,
|
||||
tokenMaxA,
|
||||
tokenMaxB,
|
||||
whirlpool: whirlpoolPda.publicKey,
|
||||
positionAuthority: wallet.publicKey,
|
||||
position: positionPda.publicKey,
|
||||
positionTokenAccount: positionTokenAccountAddress,
|
||||
tokenOwnerAccountA,
|
||||
tokenOwnerAccountB,
|
||||
tokenVaultA: tokenVaultAKeypair.publicKey,
|
||||
tokenVaultB: tokenVaultBKeypair.publicKey,
|
||||
tickArrayLower: tickArrayLowerPda.publicKey,
|
||||
tickArrayUpper: tickArrayUpperPda.publicKey,
|
||||
};
|
||||
|
||||
const liquidityIx = !TokenExtensionUtil.isV2IxRequiredPool(
|
||||
tokenExtensionCtx,
|
||||
)
|
||||
? increaseLiquidityIx(ctx.program, baseParamsLiquidity)
|
||||
: increaseLiquidityV2Ix(ctx.program, {
|
||||
...baseParamsLiquidity,
|
||||
tokenMintA: mintA,
|
||||
tokenMintB: mintB,
|
||||
tokenProgramA: tokenExtensionCtx.tokenMintWithProgramA.tokenProgram,
|
||||
tokenProgramB: tokenExtensionCtx.tokenMintWithProgramB.tokenProgram,
|
||||
});
|
||||
txBuilder.addInstruction(liquidityIx);
|
||||
|
||||
const txPayload = await txBuilder.build();
|
||||
const instructions = TransactionMessage.decompile(
|
||||
(txPayload.transaction as VersionedTransaction).message,
|
||||
).instructions;
|
||||
|
||||
const txId = await sendTx(agent, instructions, [
|
||||
positionMintKeypair,
|
||||
tokenVaultAKeypair,
|
||||
tokenVaultBKeypair,
|
||||
]);
|
||||
return txId;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to send transaction: ${JSON.stringify(error)}`);
|
||||
}
|
||||
}
|
||||
121
src/tools/orca_fetch_positions.ts
Normal file
121
src/tools/orca_fetch_positions.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { Wallet } from "@coral-xyz/anchor";
|
||||
import {
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
WhirlpoolContext,
|
||||
buildWhirlpoolClient,
|
||||
getAllPositionAccountsByOwner,
|
||||
PriceMath,
|
||||
} from "@orca-so/whirlpools-sdk";
|
||||
|
||||
interface PositionInfo {
|
||||
whirlpoolAddress: string;
|
||||
positionInRange: boolean;
|
||||
distanceFromCenterBps: number;
|
||||
}
|
||||
|
||||
type PositionDataMap = {
|
||||
[positionMintAddress: string]: PositionInfo;
|
||||
};
|
||||
|
||||
/**
|
||||
* # Fetches Liquidity Position Data in Orca Whirlpools
|
||||
*
|
||||
* Fetches data for all liquidity positions owned by the provided wallet, including:
|
||||
* - Whirlpool address.
|
||||
* - Whether the position is in range.
|
||||
* - Distance from the center price to the current price in basis points.
|
||||
*
|
||||
* ## Parameters
|
||||
* - `agent`: The `SolanaAgentKit` instance representing the wallet and connection.
|
||||
*
|
||||
* ## Returns
|
||||
* A JSON string with an object mapping position mint addresses to position details:
|
||||
* ```json
|
||||
* {
|
||||
* "positionMintAddress1": {
|
||||
* "whirlpoolAddress": "whirlpoolAddress1",
|
||||
* "positionInRange": true,
|
||||
* "distanceFromCenterBps": 250
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* ## Throws
|
||||
* - If positions cannot be fetched or processed.
|
||||
* - If the position mint address is invalid.
|
||||
*
|
||||
* @param agent - The `SolanaAgentKit` instance.
|
||||
* @returns A JSON string with position data.
|
||||
*/
|
||||
export async function orcaFetchPositions(
|
||||
agent: SolanaAgentKit,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const wallet = new Wallet(agent.wallet);
|
||||
const ctx = WhirlpoolContext.from(
|
||||
agent.connection,
|
||||
wallet,
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
);
|
||||
const client = buildWhirlpoolClient(ctx);
|
||||
|
||||
const positions = await getAllPositionAccountsByOwner({
|
||||
ctx,
|
||||
owner: agent.wallet.publicKey,
|
||||
});
|
||||
const positionDatas = [
|
||||
...positions.positions.entries(),
|
||||
...positions.positionsWithTokenExtensions.entries(),
|
||||
];
|
||||
const result: PositionDataMap = {};
|
||||
for (const [, positionData] of positionDatas) {
|
||||
const positionMintAddress = positionData.positionMint;
|
||||
const whirlpoolAddress = positionData.whirlpool;
|
||||
const whirlpool = await client.getPool(whirlpoolAddress);
|
||||
const whirlpoolData = whirlpool.getData();
|
||||
const sqrtPrice = whirlpoolData.sqrtPrice;
|
||||
const currentTick = whirlpoolData.tickCurrentIndex;
|
||||
const mintA = whirlpool.getTokenAInfo();
|
||||
const mintB = whirlpool.getTokenBInfo();
|
||||
const currentPrice = PriceMath.sqrtPriceX64ToPrice(
|
||||
sqrtPrice,
|
||||
mintA.decimals,
|
||||
mintB.decimals,
|
||||
);
|
||||
const lowerTick = positionData.tickLowerIndex;
|
||||
const upperTick = positionData.tickUpperIndex;
|
||||
const lowerPrice = PriceMath.tickIndexToPrice(
|
||||
lowerTick,
|
||||
mintA.decimals,
|
||||
mintB.decimals,
|
||||
);
|
||||
const upperPrice = PriceMath.tickIndexToPrice(
|
||||
upperTick,
|
||||
mintA.decimals,
|
||||
mintB.decimals,
|
||||
);
|
||||
const centerPosition = lowerPrice.add(upperPrice).div(2);
|
||||
|
||||
const positionInRange =
|
||||
currentTick > lowerTick && currentTick < upperTick ? true : false;
|
||||
const distanceFromCenterBps = Math.ceil(
|
||||
currentPrice
|
||||
.sub(centerPosition)
|
||||
.abs()
|
||||
.div(centerPosition)
|
||||
.mul(10000)
|
||||
.toNumber(),
|
||||
);
|
||||
|
||||
result[positionMintAddress.toString()] = {
|
||||
whirlpoolAddress: whirlpoolAddress.toString(),
|
||||
positionInRange,
|
||||
distanceFromCenterBps,
|
||||
};
|
||||
}
|
||||
return JSON.stringify(result);
|
||||
} catch (error) {
|
||||
throw new Error(`${error}`);
|
||||
}
|
||||
}
|
||||
161
src/tools/orca_open_centered_position_with_liquidity.ts
Normal file
161
src/tools/orca_open_centered_position_with_liquidity.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import {
|
||||
Keypair,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
TransactionMessage,
|
||||
VersionedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { Wallet } from "@coral-xyz/anchor";
|
||||
import { Decimal } from "decimal.js";
|
||||
import {
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
WhirlpoolContext,
|
||||
PriceMath,
|
||||
buildWhirlpoolClient,
|
||||
increaseLiquidityQuoteByInputToken,
|
||||
TokenExtensionContextForPool,
|
||||
NO_TOKEN_EXTENSION_CONTEXT,
|
||||
} from "@orca-so/whirlpools-sdk";
|
||||
|
||||
import { sendTx } from "../utils/send_tx";
|
||||
import { Percentage } from "@orca-so/common-sdk";
|
||||
import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
|
||||
|
||||
/**
|
||||
* # Opens a Centered Liquidity Position in an Orca Whirlpool
|
||||
*
|
||||
* This function opens a centered liquidity position in a specified Orca Whirlpool. The user defines
|
||||
* a basis point (bps) offset from the current price of the pool to set the lower and upper bounds of the position.
|
||||
* The user also specifies the token mint and the amount to deposit. The required amount of the other token
|
||||
* is calculated automatically.
|
||||
*
|
||||
* ## Parameters
|
||||
* - `agent`: The `SolanaAgentKit` instance representing the wallet and connection details.
|
||||
* - `whirlpoolAddress`: The address of the Orca Whirlpool where the position will be opened.
|
||||
* - `priceOffsetBps`: The basis point (bps) offset (on one side) from the current price fo the pool. For example,
|
||||
* 500 bps (5%) creates a range from 95% to 105% of the current pool price.
|
||||
* - `inputTokenMint`: The mint address of the token being deposited (e.g., USDC or another token).
|
||||
* - `inputAmount`: The amount of the input token to deposit, specified as a `Decimal` value.
|
||||
*
|
||||
* ## Returns
|
||||
* A `Promise` that resolves to the transaction ID (`string`) of the transaction that opens the position.
|
||||
*
|
||||
* ## Notes
|
||||
* - The `priceOffsetBps` specifies the range symmetrically around the current price.
|
||||
* - The specified `inputTokenMint` determines which token is deposited directly. The function calculates
|
||||
* the required amount of the other token based on the specified price range.
|
||||
* - This function supports Orca's token extensions for managing tokens with special behaviors.
|
||||
* - The function assumes a maximum slippage of 1% for liquidity provision.
|
||||
*
|
||||
* ## Throws
|
||||
* An error will be thrown if:
|
||||
* - The specified Whirlpool address is invalid or inaccessible.
|
||||
* - The transaction fails to send.
|
||||
* - Any required mint information cannot be fetched.
|
||||
*
|
||||
* @param agent - The `SolanaAgentKit` instance representing the wallet and connection.
|
||||
* @param whirlpoolAddress - The address of the Orca Whirlpool.
|
||||
* @param priceOffsetBps - The basis point offset (one side) from the current pool price.
|
||||
* @param inputTokenMint - The mint address of the token to deposit.
|
||||
* @param inputAmount - The amount of the input token to deposit.
|
||||
* @returns A promise resolving to the transaction ID (`string`).
|
||||
*/
|
||||
export async function orcaOpenCenteredPositionWithLiquidity(
|
||||
agent: SolanaAgentKit,
|
||||
whirlpoolAddress: PublicKey,
|
||||
priceOffsetBps: number,
|
||||
inputTokenMint: PublicKey,
|
||||
inputAmount: Decimal,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const wallet = new Wallet(agent.wallet);
|
||||
const ctx = WhirlpoolContext.from(
|
||||
agent.connection,
|
||||
wallet,
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
);
|
||||
const client = buildWhirlpoolClient(ctx);
|
||||
|
||||
const whirlpool = await client.getPool(whirlpoolAddress);
|
||||
const whirlpoolData = whirlpool.getData();
|
||||
const mintInfoA = whirlpool.getTokenAInfo();
|
||||
const mintInfoB = whirlpool.getTokenBInfo();
|
||||
const price = PriceMath.sqrtPriceX64ToPrice(
|
||||
whirlpoolData.sqrtPrice,
|
||||
mintInfoA.decimals,
|
||||
mintInfoB.decimals,
|
||||
);
|
||||
|
||||
const lowerPrice = price.mul(1 - priceOffsetBps / 10000);
|
||||
const upperPrice = price.mul(1 + priceOffsetBps / 10000);
|
||||
const lowerTick = PriceMath.priceToInitializableTickIndex(
|
||||
lowerPrice,
|
||||
mintInfoA.decimals,
|
||||
mintInfoB.decimals,
|
||||
whirlpoolData.tickSpacing,
|
||||
);
|
||||
const upperTick = PriceMath.priceToInitializableTickIndex(
|
||||
upperPrice,
|
||||
mintInfoA.decimals,
|
||||
mintInfoB.decimals,
|
||||
whirlpoolData.tickSpacing,
|
||||
);
|
||||
|
||||
const txBuilderTickArrays = await whirlpool.initTickArrayForTicks([
|
||||
lowerTick,
|
||||
upperTick,
|
||||
]);
|
||||
let instructions: TransactionInstruction[] = [];
|
||||
let signers: Keypair[] = [];
|
||||
if (txBuilderTickArrays !== null) {
|
||||
const txPayloadTickArrays = await txBuilderTickArrays.build();
|
||||
const txPayloadTickArraysDecompiled = TransactionMessage.decompile(
|
||||
(txPayloadTickArrays.transaction as VersionedTransaction).message,
|
||||
);
|
||||
const instructionsTickArrays = txPayloadTickArraysDecompiled.instructions;
|
||||
instructions = instructions.concat(instructionsTickArrays);
|
||||
signers = signers.concat(txPayloadTickArrays.signers as Keypair[]);
|
||||
}
|
||||
|
||||
const tokenExtensionCtx: TokenExtensionContextForPool = {
|
||||
...NO_TOKEN_EXTENSION_CONTEXT,
|
||||
tokenMintWithProgramA: mintInfoA,
|
||||
tokenMintWithProgramB: mintInfoB,
|
||||
};
|
||||
const increaseLiquiditQuote = increaseLiquidityQuoteByInputToken(
|
||||
inputTokenMint,
|
||||
inputAmount,
|
||||
lowerTick,
|
||||
upperTick,
|
||||
Percentage.fromFraction(1, 100),
|
||||
whirlpool,
|
||||
tokenExtensionCtx,
|
||||
);
|
||||
const { positionMint, tx: txBuilder } =
|
||||
await whirlpool.openPositionWithMetadata(
|
||||
lowerTick,
|
||||
upperTick,
|
||||
increaseLiquiditQuote,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
);
|
||||
|
||||
const txPayload = await txBuilder.build();
|
||||
const txPayloadDecompiled = TransactionMessage.decompile(
|
||||
(txPayload.transaction as VersionedTransaction).message,
|
||||
);
|
||||
instructions = instructions.concat(txPayloadDecompiled.instructions);
|
||||
signers = signers.concat(txPayload.signers as Keypair[]);
|
||||
|
||||
const txId = await sendTx(agent, instructions, signers);
|
||||
return JSON.stringify({
|
||||
transactionId: txId,
|
||||
positionMint: positionMint.toString(),
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`${error}`);
|
||||
}
|
||||
}
|
||||
177
src/tools/orca_open_single_sided_position.ts
Normal file
177
src/tools/orca_open_single_sided_position.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import {
|
||||
Keypair,
|
||||
PublicKey,
|
||||
TransactionMessage,
|
||||
VersionedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { Wallet } from "@coral-xyz/anchor";
|
||||
import { Decimal } from "decimal.js";
|
||||
import {
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
WhirlpoolContext,
|
||||
PriceMath,
|
||||
buildWhirlpoolClient,
|
||||
increaseLiquidityQuoteByInputToken,
|
||||
TokenExtensionContextForPool,
|
||||
NO_TOKEN_EXTENSION_CONTEXT,
|
||||
} from "@orca-so/whirlpools-sdk";
|
||||
import { sendTx } from "../utils/send_tx";
|
||||
import { Percentage } from "@orca-so/common-sdk";
|
||||
import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
|
||||
|
||||
/**
|
||||
* # Opens a Single-Sided Liquidity Position in an Orca Whirlpool
|
||||
*
|
||||
* This function opens a single-sided liquidity position in a specified Orca Whirlpool. The user specifies
|
||||
* a basis point (bps) offset from the current price for the lower bound and a width (bps) for the range width.
|
||||
* The required amount of the other token is calculated automatically.
|
||||
*
|
||||
* ## Parameters
|
||||
* - `agent`: The `SolanaAgentKit` instance representing the wallet and connection details.
|
||||
* - `whirlpoolAddress`: The address of the Orca Whirlpool where the position will be opened.
|
||||
* - `distanceFromCurrentPriceBps`: The basis point offset from the current price for the lower bound.
|
||||
* - `widthBps`: The width of the range as a percentage increment from the lower bound.
|
||||
* - `inputTokenMint`: The mint address of the token being deposited (e.g., USDC or another token).
|
||||
* - `inputAmount`: The amount of the input token to deposit, specified as a `Decimal` value.
|
||||
*
|
||||
* ## Returns
|
||||
* A `Promise` that resolves to the transaction ID (`string`) of the transaction that opens the position.
|
||||
*
|
||||
* ## Notes
|
||||
* - The `distanceFromCurrentPriceBps` specifies the starting point of the range.
|
||||
* - The `widthBps` determines the range size from the lower bound.
|
||||
* - The specified `inputTokenMint` determines which token is deposited directly.
|
||||
*
|
||||
* @param agent - The `SolanaAgentKit` instance representing the wallet and connection.
|
||||
* @param whirlpoolAddress - The address of the Orca Whirlpool.
|
||||
* @param distanceFromCurrentPriceBps - The basis point offset from the current price for the lower bound.
|
||||
* @param widthBps - The width of the range as a percentage increment from the lower bound.
|
||||
* @param inputTokenMint - The mint address of the token to deposit.
|
||||
* @param inputAmount - The amount of the input token to deposit.
|
||||
* @returns A promise resolving to the transaction ID (`string`).
|
||||
*/
|
||||
export async function orcaOpenSingleSidedPosition(
|
||||
agent: SolanaAgentKit,
|
||||
whirlpoolAddress: PublicKey,
|
||||
distanceFromCurrentPriceBps: number,
|
||||
widthBps: number,
|
||||
inputTokenMint: PublicKey,
|
||||
inputAmount: Decimal,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const wallet = new Wallet(agent.wallet);
|
||||
const ctx = WhirlpoolContext.from(
|
||||
agent.connection,
|
||||
wallet,
|
||||
ORCA_WHIRLPOOL_PROGRAM_ID,
|
||||
);
|
||||
const client = buildWhirlpoolClient(ctx);
|
||||
|
||||
const whirlpool = await client.getPool(whirlpoolAddress);
|
||||
const whirlpoolData = whirlpool.getData();
|
||||
const mintInfoA = whirlpool.getTokenAInfo();
|
||||
const mintInfoB = whirlpool.getTokenBInfo();
|
||||
const price = PriceMath.sqrtPriceX64ToPrice(
|
||||
whirlpoolData.sqrtPrice,
|
||||
mintInfoA.decimals,
|
||||
mintInfoB.decimals,
|
||||
);
|
||||
|
||||
const isTokenA = inputTokenMint.equals(mintInfoA.mint);
|
||||
let lowerBoundPrice;
|
||||
let upperBoundPrice;
|
||||
let lowerTick;
|
||||
let upperTick;
|
||||
if (isTokenA) {
|
||||
lowerBoundPrice = price.mul(1 + distanceFromCurrentPriceBps / 10000);
|
||||
upperBoundPrice = lowerBoundPrice.mul(1 + widthBps / 10000);
|
||||
upperTick = PriceMath.priceToInitializableTickIndex(
|
||||
upperBoundPrice,
|
||||
mintInfoA.decimals,
|
||||
mintInfoB.decimals,
|
||||
whirlpoolData.tickSpacing,
|
||||
);
|
||||
lowerTick = PriceMath.priceToInitializableTickIndex(
|
||||
lowerBoundPrice,
|
||||
mintInfoA.decimals,
|
||||
mintInfoB.decimals,
|
||||
whirlpoolData.tickSpacing,
|
||||
);
|
||||
} else {
|
||||
lowerBoundPrice = price.mul(1 - distanceFromCurrentPriceBps / 10000);
|
||||
upperBoundPrice = lowerBoundPrice.mul(1 - widthBps / 10000);
|
||||
lowerTick = PriceMath.priceToInitializableTickIndex(
|
||||
upperBoundPrice,
|
||||
mintInfoA.decimals,
|
||||
mintInfoB.decimals,
|
||||
whirlpoolData.tickSpacing,
|
||||
);
|
||||
upperTick = PriceMath.priceToInitializableTickIndex(
|
||||
lowerBoundPrice,
|
||||
mintInfoA.decimals,
|
||||
mintInfoB.decimals,
|
||||
whirlpoolData.tickSpacing,
|
||||
);
|
||||
}
|
||||
|
||||
const txBuilderTickArrays = await whirlpool.initTickArrayForTicks([
|
||||
lowerTick,
|
||||
upperTick,
|
||||
]);
|
||||
let txIds: string = "";
|
||||
if (txBuilderTickArrays !== null) {
|
||||
const txPayloadTickArrays = await txBuilderTickArrays.build();
|
||||
const txPayloadTickArraysDecompiled = TransactionMessage.decompile(
|
||||
(txPayloadTickArrays.transaction as VersionedTransaction).message,
|
||||
);
|
||||
const instructions = txPayloadTickArraysDecompiled.instructions;
|
||||
const signers = txPayloadTickArrays.signers as Keypair[];
|
||||
|
||||
const tickArrayTxId = await sendTx(agent, instructions, signers);
|
||||
txIds += tickArrayTxId + ",";
|
||||
}
|
||||
|
||||
const tokenExtensionCtx: TokenExtensionContextForPool = {
|
||||
...NO_TOKEN_EXTENSION_CONTEXT,
|
||||
tokenMintWithProgramA: mintInfoA,
|
||||
tokenMintWithProgramB: mintInfoB,
|
||||
};
|
||||
const increaseLiquiditQuote = increaseLiquidityQuoteByInputToken(
|
||||
inputTokenMint,
|
||||
inputAmount,
|
||||
lowerTick,
|
||||
upperTick,
|
||||
Percentage.fromFraction(1, 100),
|
||||
whirlpool,
|
||||
tokenExtensionCtx,
|
||||
);
|
||||
const { positionMint, tx: txBuilder } =
|
||||
await whirlpool.openPositionWithMetadata(
|
||||
lowerTick,
|
||||
upperTick,
|
||||
increaseLiquiditQuote,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
);
|
||||
|
||||
const txPayload = await txBuilder.build();
|
||||
const txPayloadDecompiled = TransactionMessage.decompile(
|
||||
(txPayload.transaction as VersionedTransaction).message,
|
||||
);
|
||||
const instructions = txPayloadDecompiled.instructions;
|
||||
const signers = txPayload.signers as Keypair[];
|
||||
|
||||
const positionTxId = await sendTx(agent, instructions, signers);
|
||||
txIds += positionTxId;
|
||||
|
||||
return JSON.stringify({
|
||||
transactionIds: txIds,
|
||||
positionMint: positionMint.toString(),
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`${error}`);
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
109
src/tools/tensor_trade.ts
Normal file
109
src/tools/tensor_trade.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { TensorSwapSDK } from "@tensor-oss/tensorswap-sdk";
|
||||
import { PublicKey, Transaction } from "@solana/web3.js";
|
||||
import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
|
||||
import { BN } from "bn.js";
|
||||
import {
|
||||
getAssociatedTokenAddress,
|
||||
TOKEN_PROGRAM_ID,
|
||||
getAccount,
|
||||
} from "@solana/spl-token";
|
||||
|
||||
export async function listNFTForSale(
|
||||
agent: SolanaAgentKit,
|
||||
nftMint: PublicKey,
|
||||
price: number,
|
||||
): Promise<string> {
|
||||
try {
|
||||
if (!PublicKey.isOnCurve(nftMint)) {
|
||||
throw new Error("Invalid NFT mint address");
|
||||
}
|
||||
|
||||
const mintInfo = await agent.connection.getAccountInfo(nftMint);
|
||||
if (!mintInfo) {
|
||||
throw new Error(`NFT mint ${nftMint.toString()} does not exist`);
|
||||
}
|
||||
|
||||
const ata = await getAssociatedTokenAddress(nftMint, agent.wallet_address);
|
||||
|
||||
try {
|
||||
const tokenAccount = await getAccount(agent.connection, ata);
|
||||
|
||||
if (!tokenAccount || tokenAccount.amount <= 0) {
|
||||
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.`,
|
||||
);
|
||||
}
|
||||
|
||||
const provider = new AnchorProvider(
|
||||
agent.connection,
|
||||
new Wallet(agent.wallet),
|
||||
AnchorProvider.defaultOptions(),
|
||||
);
|
||||
|
||||
const tensorSwapSdk = new TensorSwapSDK({ provider });
|
||||
const priceInLamports = new BN(price * 1e9);
|
||||
const nftSource = await getAssociatedTokenAddress(
|
||||
nftMint,
|
||||
agent.wallet_address,
|
||||
);
|
||||
|
||||
const { tx } = await tensorSwapSdk.list({
|
||||
nftMint,
|
||||
nftSource,
|
||||
owner: agent.wallet_address,
|
||||
price: priceInLamports,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
payer: agent.wallet_address,
|
||||
});
|
||||
|
||||
const transaction = new Transaction();
|
||||
transaction.add(...tx.ixs);
|
||||
return await agent.connection.sendTransaction(transaction, [
|
||||
agent.wallet,
|
||||
...tx.extraSigners,
|
||||
]);
|
||||
} catch (error: any) {
|
||||
console.error("Full error details:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function cancelListing(
|
||||
agent: SolanaAgentKit,
|
||||
nftMint: PublicKey,
|
||||
): Promise<string> {
|
||||
const provider = new AnchorProvider(
|
||||
agent.connection,
|
||||
new Wallet(agent.wallet),
|
||||
AnchorProvider.defaultOptions(),
|
||||
);
|
||||
|
||||
const tensorSwapSdk = new TensorSwapSDK({ provider });
|
||||
const nftDest = await getAssociatedTokenAddress(
|
||||
nftMint,
|
||||
agent.wallet_address,
|
||||
false,
|
||||
TOKEN_PROGRAM_ID,
|
||||
);
|
||||
|
||||
const { tx } = await tensorSwapSdk.delist({
|
||||
nftMint,
|
||||
nftDest,
|
||||
owner: agent.wallet_address,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
payer: agent.wallet_address,
|
||||
authData: null,
|
||||
});
|
||||
|
||||
const transaction = new Transaction();
|
||||
transaction.add(...tx.ixs);
|
||||
return await agent.connection.sendTransaction(transaction, [
|
||||
agent.wallet,
|
||||
...tx.extraSigners,
|
||||
]);
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
import { VersionedTransaction, PublicKey } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import { TOKENS, DEFAULT_OPTIONS, JUP_API } from "../constants";
|
||||
import {
|
||||
TOKENS,
|
||||
DEFAULT_OPTIONS,
|
||||
JUP_API,
|
||||
JUP_REFERRAL_ADDRESS,
|
||||
} from "../constants";
|
||||
import { getMint } from "@solana/spl-token";
|
||||
/**
|
||||
* Swap tokens using Jupiter Exchange
|
||||
@@ -11,6 +16,7 @@ import { getMint } from "@solana/spl-token";
|
||||
* @param slippageBps Slippage tolerance in basis points (default: 300 = 3%)
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
|
||||
export async function trade(
|
||||
agent: SolanaAgentKit,
|
||||
outputMint: PublicKey,
|
||||
@@ -38,11 +44,24 @@ export async function trade(
|
||||
`&amount=${scaledAmount}` +
|
||||
`&slippageBps=${slippageBps}` +
|
||||
`&onlyDirectRoutes=true` +
|
||||
`&maxAccounts=20`,
|
||||
`&maxAccounts=20` +
|
||||
`${agent.config.JUPITER_FEE_BPS ? `&platformFeeBps=${agent.config.JUPITER_FEE_BPS}` : ""}`,
|
||||
)
|
||||
).json();
|
||||
|
||||
// Get serialized transaction
|
||||
let feeAccount;
|
||||
if (agent.config.JUPITER_REFERRAL_ACCOUNT) {
|
||||
[feeAccount] = PublicKey.findProgramAddressSync(
|
||||
[
|
||||
Buffer.from("referral_ata"),
|
||||
new PublicKey(agent.config.JUPITER_REFERRAL_ACCOUNT).toBuffer(),
|
||||
TOKENS.SOL.toBuffer(),
|
||||
],
|
||||
new PublicKey(JUP_REFERRAL_ADDRESS),
|
||||
);
|
||||
}
|
||||
|
||||
const { swapTransaction } = await (
|
||||
await fetch("https://quote-api.jup.ag/v6/swap", {
|
||||
method: "POST",
|
||||
@@ -55,6 +74,7 @@ export async function trade(
|
||||
wrapAndUnwrapSol: true,
|
||||
dynamicComputeUnitLimit: true,
|
||||
prioritizationFeeLamports: "auto",
|
||||
feeAccount: feeAccount ? feeAccount.toString() : null,
|
||||
}),
|
||||
})
|
||||
).json();
|
||||
|
||||
37
src/tools/withdraw_all.ts
Normal file
37
src/tools/withdraw_all.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
56
src/types/action.ts
Normal file
56
src/types/action.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Example of an action with input and output
|
||||
*/
|
||||
export interface ActionExample {
|
||||
input: Record<string, any>;
|
||||
output: Record<string, any>;
|
||||
explanation: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function type for executing the action
|
||||
*/
|
||||
export type Handler = (
|
||||
agent: SolanaAgentKit,
|
||||
input: Record<string, any>,
|
||||
) => Promise<Record<string, any>>;
|
||||
|
||||
/**
|
||||
* Main Action interface inspired by ELIZA
|
||||
* This interface makes it easier to implement actions across different frameworks
|
||||
*/
|
||||
export interface Action {
|
||||
/**
|
||||
* Unique name of the action
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Alternative names/phrases that can trigger this action
|
||||
*/
|
||||
similes: string[];
|
||||
|
||||
/**
|
||||
* Detailed description of what the action does
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* Array of example inputs and outputs for the action
|
||||
* Each inner array represents a group of related examples
|
||||
*/
|
||||
examples: ActionExample[][];
|
||||
|
||||
/**
|
||||
* Zod schema for input validation
|
||||
*/
|
||||
schema: z.ZodType<any>;
|
||||
|
||||
/**
|
||||
* Function that executes the action
|
||||
*/
|
||||
handler: Handler;
|
||||
}
|
||||
@@ -1,4 +1,12 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
|
||||
export interface Config {
|
||||
OPENAI_API_KEY?: string;
|
||||
JUPITER_REFERRAL_ACCOUNT?: string;
|
||||
JUPITER_FEE_BPS?: number;
|
||||
}
|
||||
|
||||
export interface Creator {
|
||||
address: string;
|
||||
@@ -80,7 +88,8 @@ export interface FetchPriceResponse {
|
||||
|
||||
export interface PythFetchPriceResponse {
|
||||
status: "success" | "error";
|
||||
priceFeedID: string;
|
||||
tokenSymbol: string;
|
||||
priceFeedID?: string;
|
||||
price?: string;
|
||||
message?: string;
|
||||
code?: string;
|
||||
@@ -91,3 +100,127 @@ export interface GibworkCreateTaskReponse {
|
||||
taskId?: string | undefined;
|
||||
signature?: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Example of an action with input and output
|
||||
*/
|
||||
export interface ActionExample {
|
||||
input: Record<string, any>;
|
||||
output: Record<string, any>;
|
||||
explanation: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function type for executing the action
|
||||
*/
|
||||
export type Handler = (
|
||||
agent: SolanaAgentKit,
|
||||
input: Record<string, any>,
|
||||
) => Promise<Record<string, any>>;
|
||||
|
||||
/**
|
||||
* Main Action interface inspired by ELIZA
|
||||
* This interface makes it easier to implement actions across different frameworks
|
||||
*/
|
||||
export interface Action {
|
||||
/**
|
||||
* Unique name of the action
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Alternative names/phrases that can trigger this action
|
||||
*/
|
||||
similes: string[];
|
||||
|
||||
/**
|
||||
* Detailed description of what the action does
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* Array of example inputs and outputs for the action
|
||||
* Each inner array represents a group of related examples
|
||||
*/
|
||||
examples: ActionExample[][];
|
||||
|
||||
/**
|
||||
* Zod schema for input validation
|
||||
*/
|
||||
schema: z.ZodType<any>;
|
||||
|
||||
/**
|
||||
* Function that executes the 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
68
src/utils/actionExecutor.ts
Normal file
68
src/utils/actionExecutor.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { ACTIONS } from "../actions";
|
||||
|
||||
/**
|
||||
* Find an action by its name or one of its similes
|
||||
*/
|
||||
export function findAction(query: string): Action | undefined {
|
||||
const normalizedQuery = query.toLowerCase().trim();
|
||||
return Object.values(ACTIONS).find(
|
||||
(action) =>
|
||||
action.name.toLowerCase() === normalizedQuery ||
|
||||
action.similes.some((simile) => simile.toLowerCase() === normalizedQuery),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an action with the given input
|
||||
*/
|
||||
export async function executeAction(
|
||||
action: Action,
|
||||
agent: SolanaAgentKit,
|
||||
input: Record<string, any>,
|
||||
): Promise<Record<string, any>> {
|
||||
try {
|
||||
// Validate input using Zod schema
|
||||
const validatedInput = action.schema.parse(input);
|
||||
|
||||
// Execute the action with validated input
|
||||
const result = await action.handler(agent, validatedInput);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
...result,
|
||||
};
|
||||
} catch (error: any) {
|
||||
// Handle Zod validation errors specially
|
||||
if (error.errors) {
|
||||
return {
|
||||
status: "error",
|
||||
message: "Validation error",
|
||||
details: error.errors,
|
||||
code: "VALIDATION_ERROR",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "EXECUTION_ERROR",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get examples for an action
|
||||
*/
|
||||
export function getActionExamples(action: Action): string {
|
||||
return action.examples
|
||||
.flat()
|
||||
.map((example) => {
|
||||
return `Input: ${JSON.stringify(example.input, null, 2)}
|
||||
Output: ${JSON.stringify(example.output, null, 2)}
|
||||
Explanation: ${example.explanation}
|
||||
---`;
|
||||
})
|
||||
.join("\n");
|
||||
}
|
||||
@@ -1,70 +1,71 @@
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { Transaction, Keypair, TransactionInstruction } from "@solana/web3.js";
|
||||
import { Connection, ComputeBudgetProgram } from "@solana/web3.js";
|
||||
import {
|
||||
Keypair,
|
||||
Signer,
|
||||
TransactionInstruction,
|
||||
TransactionMessage,
|
||||
VersionedTransaction,
|
||||
} from "@solana/web3.js";
|
||||
import { ComputeBudgetProgram } from "@solana/web3.js";
|
||||
|
||||
const feeTiers = {
|
||||
min: 0.01,
|
||||
mid: 0.5,
|
||||
max: 0.95,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get priority fees for the current block
|
||||
* @param connection - Solana RPC connection
|
||||
* @returns Priority fees statistics and instructions for different fee levels
|
||||
*/
|
||||
export async function getPriorityFees(connection: Connection): Promise<{
|
||||
min: number;
|
||||
median: number;
|
||||
max: number;
|
||||
instructions?: {
|
||||
low: TransactionInstruction;
|
||||
medium: TransactionInstruction;
|
||||
high: TransactionInstruction;
|
||||
};
|
||||
export async function getComputeBudgetInstructions(
|
||||
agent: SolanaAgentKit,
|
||||
instructions: TransactionInstruction[],
|
||||
feeTier: keyof typeof feeTiers,
|
||||
): Promise<{
|
||||
blockhash: string;
|
||||
computeBudgetLimitInstruction: TransactionInstruction;
|
||||
computeBudgetPriorityFeeInstructions: TransactionInstruction;
|
||||
}> {
|
||||
try {
|
||||
// Get recent prioritization fees
|
||||
const priorityFees = await connection.getRecentPrioritizationFees();
|
||||
const blockhash = (await agent.connection.getLatestBlockhash()).blockhash;
|
||||
const messageV0 = new TransactionMessage({
|
||||
payerKey: agent.wallet_address,
|
||||
recentBlockhash: blockhash,
|
||||
instructions: instructions,
|
||||
}).compileToV0Message();
|
||||
const transaction = new VersionedTransaction(messageV0);
|
||||
const simulatedTx = agent.connection.simulateTransaction(transaction);
|
||||
const estimatedComputeUnits = (await simulatedTx).value.unitsConsumed;
|
||||
const safeComputeUnits = Math.ceil(
|
||||
estimatedComputeUnits
|
||||
? Math.max(estimatedComputeUnits + 100000, estimatedComputeUnits * 1.2)
|
||||
: 200000,
|
||||
);
|
||||
const computeBudgetLimitInstruction =
|
||||
ComputeBudgetProgram.setComputeUnitLimit({
|
||||
units: safeComputeUnits,
|
||||
});
|
||||
|
||||
if (!priorityFees.length) {
|
||||
return {
|
||||
min: 0,
|
||||
median: 0,
|
||||
max: 0,
|
||||
};
|
||||
}
|
||||
const priorityFee = await agent.connection
|
||||
.getRecentPrioritizationFees()
|
||||
.then(
|
||||
(fees) =>
|
||||
fees.sort((a, b) => a.prioritizationFee - b.prioritizationFee)[
|
||||
Math.floor(fees.length * feeTiers[feeTier])
|
||||
].prioritizationFee,
|
||||
);
|
||||
|
||||
// Sort fees by value
|
||||
const sortedFees = priorityFees
|
||||
.map((x) => x.prioritizationFee)
|
||||
.sort((a, b) => a - b);
|
||||
const computeBudgetPriorityFeeInstructions =
|
||||
ComputeBudgetProgram.setComputeUnitPrice({
|
||||
microLamports: priorityFee,
|
||||
});
|
||||
|
||||
// Calculate statistics
|
||||
const min = sortedFees[0] ?? 0;
|
||||
const max = sortedFees[sortedFees.length - 1] ?? 0;
|
||||
const mid = Math.floor(sortedFees.length / 2);
|
||||
const median =
|
||||
sortedFees.length % 2 === 0
|
||||
? ((sortedFees[mid - 1] ?? 0) + (sortedFees[mid] ?? 0)) / 2
|
||||
: (sortedFees[mid] ?? 0);
|
||||
|
||||
// Helper to create priority fee IX based on chosen strategy
|
||||
const createPriorityFeeIx = (fee: number) => {
|
||||
return ComputeBudgetProgram.setComputeUnitPrice({
|
||||
microLamports: fee,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
min,
|
||||
median,
|
||||
max,
|
||||
// Return instructions for different fee levels
|
||||
instructions: {
|
||||
low: createPriorityFeeIx(min),
|
||||
medium: createPriorityFeeIx(median),
|
||||
high: createPriorityFeeIx(max),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error getting priority fees:", error);
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
blockhash,
|
||||
computeBudgetLimitInstruction,
|
||||
computeBudgetPriorityFeeInstructions,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,23 +76,53 @@ export async function getPriorityFees(connection: Connection): Promise<{
|
||||
*/
|
||||
export async function sendTx(
|
||||
agent: SolanaAgentKit,
|
||||
tx: Transaction,
|
||||
instructions: TransactionInstruction[],
|
||||
otherKeypairs?: Keypair[],
|
||||
) {
|
||||
tx.recentBlockhash = (await agent.connection.getLatestBlockhash()).blockhash;
|
||||
tx.feePayer = agent.wallet_address;
|
||||
const fees = await getPriorityFees(agent.connection);
|
||||
if (fees.instructions) {
|
||||
tx.add(fees.instructions.medium!);
|
||||
}
|
||||
const ixComputeBudget = await getComputeBudgetInstructions(
|
||||
agent,
|
||||
instructions,
|
||||
"mid",
|
||||
);
|
||||
const allInstructions = [
|
||||
ixComputeBudget.computeBudgetLimitInstruction,
|
||||
ixComputeBudget.computeBudgetPriorityFeeInstructions,
|
||||
...instructions,
|
||||
];
|
||||
const messageV0 = new TransactionMessage({
|
||||
payerKey: agent.wallet_address,
|
||||
recentBlockhash: ixComputeBudget.blockhash,
|
||||
instructions: allInstructions,
|
||||
}).compileToV0Message();
|
||||
const transaction = new VersionedTransaction(messageV0);
|
||||
transaction.sign([agent.wallet, ...(otherKeypairs ?? [])] as Signer[]);
|
||||
|
||||
tx.sign(agent.wallet, ...(otherKeypairs ?? []));
|
||||
const txid = await agent.connection.sendRawTransaction(tx.serialize());
|
||||
await agent.connection.confirmTransaction({
|
||||
signature: txid,
|
||||
blockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
lastValidBlockHeight: (await agent.connection.getLatestBlockhash())
|
||||
.lastValidBlockHeight,
|
||||
});
|
||||
return txid;
|
||||
const timeoutMs = 90000;
|
||||
const startTime = Date.now();
|
||||
while (Date.now() - startTime < timeoutMs) {
|
||||
const transactionStartTime = Date.now();
|
||||
|
||||
const signature = await agent.connection.sendTransaction(transaction, {
|
||||
maxRetries: 0,
|
||||
skipPreflight: false,
|
||||
});
|
||||
|
||||
const statuses = await agent.connection.getSignatureStatuses([signature]);
|
||||
if (statuses.value[0]) {
|
||||
if (!statuses.value[0].err) {
|
||||
return signature;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Transaction failed: ${statuses.value[0].err.toString()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const elapsedTime = Date.now() - transactionStartTime;
|
||||
const remainingTime = Math.max(0, 1000 - elapsedTime);
|
||||
if (remainingTime > 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
||||
}
|
||||
}
|
||||
throw new Error("Transaction timeout");
|
||||
}
|
||||
|
||||
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