diff --git a/src/actions/createGibworkTask.ts b/src/actions/createGibworkTask.ts new file mode 100644 index 0000000..8ae28ce --- /dev/null +++ b/src/actions/createGibworkTask.ts @@ -0,0 +1,135 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { PublicKey, VersionedTransaction } from "@solana/web3.js"; + +const createGibworkTaskAction: Action = { + name: "solana_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) => { + try { + const tokenMintAddress = new PublicKey(input.tokenMintAddress); + const payer = input.payer ? new PublicKey(input.payer) : undefined; + + const apiResponse = await fetch( + "https://api2.gib.work/tasks/public/transaction", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + title: input.title, + content: input.content, + requirements: input.requirements, + tags: input.tags, + payer: payer?.toBase58() || agent.wallet.publicKey.toBase58(), + token: { + mintAddress: tokenMintAddress.toBase58(), + amount: input.tokenAmount, + }, + }), + } + ); + + if (!apiResponse.ok) { + return { + status: "error", + message: `Failed to create task: ${apiResponse.statusText}` + }; + } + + const responseData = await apiResponse.json(); + if (!responseData.taskId || !responseData.serializedTransaction) { + return { + status: "error", + message: responseData.message || "Invalid response from Gibwork API" + }; + } + + const serializedTransaction = Buffer.from( + responseData.serializedTransaction, + "base64" + ); + const tx = VersionedTransaction.deserialize(serializedTransaction); + + tx.sign([agent.wallet]); + const signature = await agent.connection.sendTransaction(tx, { + preflightCommitment: "confirmed", + maxRetries: 3, + }); + + const latestBlockhash = await agent.connection.getLatestBlockhash(); + await agent.connection.confirmTransaction({ + signature, + blockhash: latestBlockhash.blockhash, + lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, + }); + + return { + status: "success", + taskId: responseData.taskId, + signature, + message: `Successfully created task: ${input.title}` + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to create task: ${error.message}` + }; + } + } +}; + +export default createGibworkTaskAction; \ No newline at end of file diff --git a/src/actions/createImage.ts b/src/actions/createImage.ts new file mode 100644 index 0000000..4d16b9a --- /dev/null +++ b/src/actions/createImage.ts @@ -0,0 +1,114 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import OpenAI from "openai"; + +const createImageAction: Action = { + name: "solana_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) => { + try { + if (!agent.openai_api_key) { + return { + status: "error", + message: "OpenAI API key not found in agent configuration" + }; + } + + const { prompt, model, size, quality, style } = input; + + const openai = new OpenAI({ + apiKey: agent.openai_api_key + }); + + const response = await openai.images.generate({ + prompt, + model, + n: 1, + size, + quality, + style + }); + + if (!response.data || response.data.length === 0) { + return { + status: "error", + message: "No image was generated" + }; + } + + return { + status: "success", + imageUrl: response.data[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; \ No newline at end of file diff --git a/src/actions/createOpenbookMarket.ts b/src/actions/createOpenbookMarket.ts new file mode 100644 index 0000000..991ab97 --- /dev/null +++ b/src/actions/createOpenbookMarket.ts @@ -0,0 +1,119 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { OPEN_BOOK_PROGRAM, Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2"; +import { MintLayout, TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { PublicKey } from "@solana/web3.js"; + +const createOpenbookMarketAction: Action = { + name: "solana_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) => { + 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 raydium = await Raydium.load({ + owner: agent.wallet, + connection: agent.connection, + }); + + // Get mint info + const baseMintInfo = await agent.connection.getAccountInfo(baseMint); + const quoteMintInfo = await agent.connection.getAccountInfo(quoteMint); + + if (!baseMintInfo || !quoteMintInfo) { + return { + status: "error", + message: "Failed to fetch mint information" + }; + } + + // Verify token program + if ( + baseMintInfo.owner.toString() !== TOKEN_PROGRAM_ID.toBase58() || + quoteMintInfo.owner.toString() !== TOKEN_PROGRAM_ID.toBase58() + ) { + return { + status: "error", + message: "Openbook market only supports TOKEN_PROGRAM_ID mints. For token-2022, please use Raydium CPMM pool instead." + }; + } + + // Create market + const { execute } = await raydium.marketV2.create({ + baseInfo: { + mint: baseMint, + decimals: MintLayout.decode(baseMintInfo.data).decimals, + }, + quoteInfo: { + mint: quoteMint, + decimals: MintLayout.decode(quoteMintInfo.data).decimals, + }, + lotSize, + tickSize, + dexProgramId: OPEN_BOOK_PROGRAM, + txVersion: TxVersion.V0, + }); + + const { txIds } = await execute({ sequentially: true }); + + return { + status: "success", + signatures: txIds, + message: "Successfully created Openbook market" + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to create Openbook market: ${error.message}` + }; + } + } +}; + +export default createOpenbookMarketAction; \ No newline at end of file diff --git a/src/actions/createOrcaSingleSidedWhirlpool.ts b/src/actions/createOrcaSingleSidedWhirlpool.ts new file mode 100644 index 0000000..11c8289 --- /dev/null +++ b/src/actions/createOrcaSingleSidedWhirlpool.ts @@ -0,0 +1,105 @@ +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"; + +// 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: "solana_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) => { + try { + const depositTokenAmount = new BN(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 as keyof typeof FEE_TIERS; + + // Create the whirlpool + const signature = await agent.createOrcaSingleSidedWhirlpool( + 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; \ No newline at end of file diff --git a/src/actions/fetchPrice.ts b/src/actions/fetchPrice.ts new file mode 100644 index 0000000..40b9a1d --- /dev/null +++ b/src/actions/fetchPrice.ts @@ -0,0 +1,70 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { PublicKey } from "@solana/web3.js"; + +const fetchPriceAction: Action = { + name: "solana_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) => { + try { + const tokenId = new PublicKey(input.tokenAddress); + const response = await fetch(`https://api.jup.ag/price/v2?ids=${tokenId}`); + + if (!response.ok) { + return { + status: "error", + message: `Failed to fetch price: ${response.statusText}` + }; + } + + const data = await response.json(); + const price = data.data[tokenId.toBase58()]?.price; + + if (!price) { + return { + status: "error", + message: "Price data not available for the given token" + }; + } + + 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; \ No newline at end of file diff --git a/src/actions/getAllDomainsTLDs.ts b/src/actions/getAllDomainsTLDs.ts new file mode 100644 index 0000000..fbda0b8 --- /dev/null +++ b/src/actions/getAllDomainsTLDs.ts @@ -0,0 +1,49 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; + +const getAllDomainsTLDsAction: Action = { + name: "solana_get_all_domains_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, input: Record) => { + try { + // Get all domain TLDs + const tlds = await agent.getAllDomainsTLDs(); + + 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; \ No newline at end of file diff --git a/src/actions/getAllRegisteredAllDomains.ts b/src/actions/getAllRegisteredAllDomains.ts new file mode 100644 index 0000000..19a5542 --- /dev/null +++ b/src/actions/getAllRegisteredAllDomains.ts @@ -0,0 +1,70 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; + +const getAllRegisteredAllDomainsAction: Action = { + name: "solana_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) => { + try { + const limit = input.limit || 100; + const offset = input.offset || 0; + + // Get all registered domains + const domains = await agent.getAllRegisteredAllDomains(); + + // Apply pagination + const paginatedDomains = domains.slice(offset, offset + limit); + + return { + status: "success", + domains: paginatedDomains, + 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; \ No newline at end of file diff --git a/src/actions/getMainAllDomainsDomain.ts b/src/actions/getMainAllDomainsDomain.ts new file mode 100644 index 0000000..81f911d --- /dev/null +++ b/src/actions/getMainAllDomainsDomain.ts @@ -0,0 +1,67 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { PublicKey } from "@solana/web3.js"; +import { TldParser } from "@onsol/tldparser"; + +const getMainAllDomainsDomainAction: Action = { + name: "solana_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) => { + try { + const address = new PublicKey(input.address); + + // Get the main domain using TldParser + const parser = new TldParser(agent.connection); + const mainDomain = await parser.getMainDomain(address); + + if (!mainDomain) { + return { + status: "error", + message: "No main domain found for this address" + }; + } + + return { + status: "success", + domain: mainDomain.domain, + message: "Successfully retrieved main domain" + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to get main domain: ${error.message}` + }; + } + } +}; + +export default getMainAllDomainsDomainAction; \ No newline at end of file diff --git a/src/actions/getOwnedAllDomains.ts b/src/actions/getOwnedAllDomains.ts new file mode 100644 index 0000000..7c41760 --- /dev/null +++ b/src/actions/getOwnedAllDomains.ts @@ -0,0 +1,60 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { PublicKey } from "@solana/web3.js"; + +const getOwnedAllDomainsAction: Action = { + name: "solana_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) => { + try { + const address = new PublicKey(input.address); + + // Get owned domains + const domains = await agent.getOwnedAllDomains(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; \ No newline at end of file diff --git a/src/actions/getOwnedDomainsForTLD.ts b/src/actions/getOwnedDomainsForTLD.ts new file mode 100644 index 0000000..36924e4 --- /dev/null +++ b/src/actions/getOwnedDomainsForTLD.ts @@ -0,0 +1,60 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { PublicKey } from "@solana/web3.js"; + +const getOwnedDomainsForTLDAction: Action = { + name: "solana_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) => { + try { + const tld = input.tld.toLowerCase(); + + // Get owned domains for TLD + const domains = await agent.getOwnedDomainsForTLD(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; \ No newline at end of file diff --git a/src/actions/getPrimaryDomain.ts b/src/actions/getPrimaryDomain.ts new file mode 100644 index 0000000..778fadb --- /dev/null +++ b/src/actions/getPrimaryDomain.ts @@ -0,0 +1,68 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { getPrimaryDomain as _getPrimaryDomain } from "@bonfida/spl-name-service"; +import { PublicKey } from "@solana/web3.js"; + +const getPrimaryDomainAction: Action = { + name: "solana_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) => { + try { + const account = new PublicKey(input.account); + + const { reverse, stale } = await _getPrimaryDomain( + agent.connection, + account + ); + + if (stale) { + return { + status: "error", + message: `Primary domain is stale for account: ${account.toBase58()}` + }; + } + + return { + status: "success", + domain: reverse, + message: `Primary domain: ${reverse}` + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to get primary domain: ${error.message}` + }; + } + } +}; + +export default getPrimaryDomainAction; \ No newline at end of file diff --git a/src/actions/getTPS.ts b/src/actions/getTPS.ts new file mode 100644 index 0000000..8baadff --- /dev/null +++ b/src/actions/getTPS.ts @@ -0,0 +1,62 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; + +const getTPSAction: Action = { + name: "solana_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) => { + try { + const perfSamples = await agent.connection.getRecentPerformanceSamples(); + + if ( + !perfSamples.length || + !perfSamples[0]?.numTransactions || + !perfSamples[0]?.samplePeriodSecs + ) { + return { + status: "error", + message: "No performance samples available" + }; + } + + const tps = Math.round( + perfSamples[0].numTransactions / perfSamples[0].samplePeriodSecs + ); + + return { + status: "success", + tps, + message: `Current network TPS: ${tps}` + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to get TPS: ${error.message}` + }; + } + } +}; + +export default getTPSAction; \ No newline at end of file diff --git a/src/actions/getTokenData.ts b/src/actions/getTokenData.ts new file mode 100644 index 0000000..4b75cb9 --- /dev/null +++ b/src/actions/getTokenData.ts @@ -0,0 +1,134 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { PublicKey } from "@solana/web3.js"; +import { JupiterTokenData } from "../types"; + +const getTokenDataAction: Action = { + name: "solana_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) => { + try { + let tokenData: JupiterTokenData | undefined; + + if (input.address) { + const mint = new PublicKey(input.address); + const response = await fetch("https://tokens.jup.ag/tokens?tags=verified", { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + const data = (await response.json()) as JupiterTokenData[]; + tokenData = data.find((token: JupiterTokenData) => token.address === mint.toBase58()); + } else if (input.ticker) { + const response = await fetch( + `https://api.dexscreener.com/latest/dex/search?q=${input.ticker}` + ); + const data = await response.json(); + + if (!data.pairs || data.pairs.length === 0) { + return { + status: "error", + message: `No token found for ticker: ${input.ticker}` + }; + } + + let solanaPairs = data.pairs + .filter((pair: any) => pair.chainId === "solana") + .sort((a: any, b: any) => (b.fdv || 0) - (a.fdv || 0)) + .filter( + (pair: any) => + pair.baseToken.symbol.toLowerCase() === input.ticker.toLowerCase() + ); + + if (solanaPairs.length === 0) { + return { + status: "error", + message: `No Solana token found for ticker: ${input.ticker}` + }; + } + + const address = solanaPairs[0].baseToken.address; + const jupResponse = await fetch("https://tokens.jup.ag/tokens?tags=verified"); + const jupData = (await jupResponse.json()) as JupiterTokenData[]; + tokenData = jupData.find((token: JupiterTokenData) => token.address === 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; \ No newline at end of file diff --git a/src/actions/index.ts b/src/actions/index.ts index bf72aca..1e15f58 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -5,6 +5,30 @@ 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 registerDomainAction from "./registerDomain"; +import lendAssetAction from "./lendAsset"; +import createGibworkTaskAction from "./createGibworkTask"; +import resolveSolDomainAction from "./resolveSolDomain"; +import pythFetchPriceAction from "./pythFetchPrice"; +import getOwnedDomainsForTLDAction from "./getOwnedDomainsForTLD"; +import createRaydiumCLMMAction from "./createRaydiumCLMM"; +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 createRaydiumCPMMAction from "./createRaydiumCPMM"; +import sendCompressedAirdropAction from "./sendCompressedAirdrop"; +import raydiumCreateCpmmAction from "./raydiumCreateCpmm"; +import raydiumCreateAmmV4Action from "./raydiumCreateAmmV4"; +import createOrcaSingleSidedWhirlpoolAction from "./createOrcaSingleSidedWhirlpool"; +import launchPumpfunTokenAction from "./launchPumpfunToken"; export const actions = [ deployTokenAction, @@ -14,7 +38,30 @@ export const actions = [ mintNFTAction, tradeAction, requestFundsAction, - // Add more actions here as they are implemented + resolveDomainAction, + getTokenDataAction, + getTPSAction, + fetchPriceAction, + stakeWithJupAction, + registerDomainAction, + lendAssetAction, + createGibworkTaskAction, + resolveSolDomainAction, + pythFetchPriceAction, + getOwnedDomainsForTLDAction, + createRaydiumCLMMAction, + getPrimaryDomainAction, + getAllDomainsTLDsAction, + getOwnedAllDomainsAction, + createImageAction, + getMainAllDomainsDomainAction, + getAllRegisteredAllDomainsAction, + createRaydiumCPMMAction, + sendCompressedAirdropAction, + raydiumCreateCpmmAction, + raydiumCreateAmmV4Action, + createOrcaSingleSidedWhirlpoolAction, + launchPumpfunTokenAction, ]; export type { Action, ActionExample, Handler } from "../types/action"; \ No newline at end of file diff --git a/src/actions/launchPumpfunToken.ts b/src/actions/launchPumpfunToken.ts new file mode 100644 index 0000000..ea38076 --- /dev/null +++ b/src/actions/launchPumpfunToken.ts @@ -0,0 +1,203 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { VersionedTransaction, Keypair } from "@solana/web3.js"; + +const launchPumpfunTokenAction: Action = { + name: "solana_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) => { + try { + const mintKeypair = Keypair.generate(); + + // Upload metadata + const formData = new URLSearchParams(); + formData.append("name", input.tokenName); + formData.append("symbol", input.tokenTicker); + formData.append("description", input.description); + formData.append("showName", "true"); + + if (input.twitter) { + formData.append("twitter", input.twitter); + } + if (input.telegram) { + formData.append("telegram", input.telegram); + } + if (input.website) { + formData.append("website", input.website); + } + + // Fetch and process image + const imageResponse = await fetch(input.imageUrl); + const imageBlob = await imageResponse.blob(); + const imageFile = new File([imageBlob], "token_image.png", { type: "image/png" }); + + // Create final form data + const finalFormData = new FormData(); + for (const [key, value] of formData.entries()) { + finalFormData.append(key, value); + } + finalFormData.append("file", imageFile); + + // Upload metadata to IPFS + const metadataResponse = await fetch("https://pump.fun/api/ipfs", { + method: "POST", + body: finalFormData, + }); + + if (!metadataResponse.ok) { + throw new Error(`Metadata upload failed: ${metadataResponse.statusText}`); + } + + const metadataResult = await metadataResponse.json(); + + // Create token transaction + const payload = { + publicKey: agent.wallet_address.toBase58(), + action: "create", + tokenMetadata: { + name: metadataResult.metadata.name, + symbol: metadataResult.metadata.symbol, + uri: metadataResult.metadataUri, + }, + mint: mintKeypair.publicKey.toBase58(), + denominatedInSol: "true", + amount: input.initialLiquiditySOL || 0.0001, + slippage: input.slippageBps || 5, + priorityFee: input.priorityFee || 0.00005, + pool: "pump", + }; + + const txResponse = await fetch("https://pumpportal.fun/api/trade-local", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); + + if (!txResponse.ok) { + const errorText = await txResponse.text(); + throw new Error(`Transaction creation failed: ${txResponse.status} - ${errorText}`); + } + + // Process and sign transaction + const transactionData = await txResponse.arrayBuffer(); + const tx = VersionedTransaction.deserialize(new Uint8Array(transactionData)); + + // Get latest blockhash + const { blockhash, lastValidBlockHeight } = await agent.connection.getLatestBlockhash(); + tx.message.recentBlockhash = blockhash; + + // Sign transaction + tx.sign([mintKeypair, agent.wallet]); + + // Send transaction + const signature = await agent.connection.sendTransaction(tx, { + skipPreflight: false, + preflightCommitment: "confirmed", + maxRetries: 5, + }); + + // Wait for confirmation + const confirmation = await agent.connection.confirmTransaction({ + signature, + blockhash, + lastValidBlockHeight, + }); + + if (confirmation.value.err) { + throw new Error(`Transaction failed: ${confirmation.value.err}`); + } + + return { + status: "success", + signature, + mint: mintKeypair.publicKey.toBase58(), + metadataUri: metadataResult.metadataUri, + message: "Successfully launched token on Pump.fun" + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to launch token: ${error.message}` + }; + } + } +}; + +export default launchPumpfunTokenAction; \ No newline at end of file diff --git a/src/actions/lendAsset.ts b/src/actions/lendAsset.ts new file mode 100644 index 0000000..25e7258 --- /dev/null +++ b/src/actions/lendAsset.ts @@ -0,0 +1,101 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { VersionedTransaction } from "@solana/web3.js"; + +const lendAssetAction: Action = { + name: "solana_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) => { + try { + const amount = input.amount as number; + + const response = await fetch( + `https://blink.lulo.fi/actions?amount=${amount}&symbol=USDC`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + account: agent.wallet.publicKey.toBase58(), + }), + } + ); + + if (!response.ok) { + return { + status: "error", + message: `Failed to get lending transaction: ${response.statusText}` + }; + } + + const data = await response.json(); + + // Deserialize the transaction + const luloTxn = VersionedTransaction.deserialize( + Buffer.from(data.transaction, "base64") + ); + + // Get a recent blockhash and set it + const { blockhash } = await agent.connection.getLatestBlockhash(); + luloTxn.message.recentBlockhash = blockhash; + + // Sign and send transaction + luloTxn.sign([agent.wallet]); + const signature = await agent.connection.sendTransaction(luloTxn, { + 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 { + status: "success", + signature, + message: `Successfully lent ${amount} USDC` + }; + } catch (error: any) { + return { + status: "error", + message: `Lending failed: ${error.message}` + }; + } + } +}; + +export default lendAssetAction; \ No newline at end of file diff --git a/src/actions/pythFetchPrice.ts b/src/actions/pythFetchPrice.ts new file mode 100644 index 0000000..acafeb0 --- /dev/null +++ b/src/actions/pythFetchPrice.ts @@ -0,0 +1,78 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { PriceServiceConnection } from "@pythnetwork/price-service-client"; +import BN from "bn.js"; + +const pythFetchPriceAction: Action = { + name: "solana_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: { + priceFeedId: "Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD" // 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({ + priceFeedId: z.string() + .min(1) + .describe("The Pyth price feed ID to fetch the price from") + }), + handler: async (_agent: SolanaAgentKit, input: Record) => { + try { + const priceFeedId = input.priceFeedId as string; + + // Connect to Hermes service + const stableHermesServiceUrl = "https://hermes.pyth.network"; + const connection = new PriceServiceConnection(stableHermesServiceUrl); + const feeds = [priceFeedId]; + + const currentPrice = await connection.getLatestPriceFeeds(feeds); + + if (!currentPrice || currentPrice.length === 0) { + return { + status: "error", + message: "Price data not available for the given feed ID" + }; + } + + // 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 priceStr = scaledPrice.toString(); + + 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; \ No newline at end of file diff --git a/src/actions/raydiumCreateAmmV4.ts b/src/actions/raydiumCreateAmmV4.ts new file mode 100644 index 0000000..6c4c357 --- /dev/null +++ b/src/actions/raydiumCreateAmmV4.ts @@ -0,0 +1,154 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { + AMM_V4, + FEE_DESTINATION_ID, + MARKET_STATE_LAYOUT_V3, + OPEN_BOOK_PROGRAM, + Raydium, + TxVersion, +} from "@raydium-io/raydium-sdk-v2"; +import { MintLayout, TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { PublicKey } from "@solana/web3.js"; +import BN from "bn.js"; + +const raydiumCreateAmmV4Action: Action = { + name: "solana_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) => { + 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 raydium = await Raydium.load({ + owner: agent.wallet, + connection: agent.connection, + }); + + const marketBufferInfo = await agent.connection.getAccountInfo( + new PublicKey(marketId), + ); + const { baseMint, quoteMint } = MARKET_STATE_LAYOUT_V3.decode( + marketBufferInfo!.data, + ); + + const baseMintInfo = await agent.connection.getAccountInfo(baseMint); + const quoteMintInfo = await agent.connection.getAccountInfo(quoteMint); + + if ( + baseMintInfo?.owner.toString() !== TOKEN_PROGRAM_ID.toBase58() || + quoteMintInfo?.owner.toString() !== TOKEN_PROGRAM_ID.toBase58() + ) { + throw new Error( + "amm pools with openbook market only support TOKEN_PROGRAM_ID mints, if you want to create pool with token-2022, please create cpmm pool instead", + ); + } + + if ( + baseAmount + .mul(quoteAmount) + .lte( + new BN(1) + .mul(new BN(10 ** MintLayout.decode(baseMintInfo.data).decimals)) + .pow(new BN(2)), + ) + ) { + throw new Error( + "initial liquidity too low, try adding more baseAmount/quoteAmount", + ); + } + + const { execute } = await raydium.liquidity.createPoolV4({ + programId: AMM_V4, + marketInfo: { + marketId, + programId: OPEN_BOOK_PROGRAM, + }, + baseMintInfo: { + mint: baseMint, + decimals: MintLayout.decode(baseMintInfo.data).decimals, + }, + quoteMintInfo: { + mint: quoteMint, + decimals: MintLayout.decode(quoteMintInfo.data).decimals, + }, + baseAmount, + quoteAmount, + startTime, + ownerInfo: { + useSOLBalance: true, + }, + associatedOnly: false, + txVersion: TxVersion.V0, + feeDestinationId: FEE_DESTINATION_ID, + }); + + const { txId } = await execute({ sendAndConfirm: true }); + + 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; \ No newline at end of file diff --git a/src/actions/raydiumCreateCpmm.ts b/src/actions/raydiumCreateCpmm.ts new file mode 100644 index 0000000..faea973 --- /dev/null +++ b/src/actions/raydiumCreateCpmm.ts @@ -0,0 +1,142 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { + CREATE_CPMM_POOL_FEE_ACC, + CREATE_CPMM_POOL_PROGRAM, + Raydium, + TxVersion, +} from "@raydium-io/raydium-sdk-v2"; +import { MintLayout } from "@solana/spl-token"; +import { PublicKey } from "@solana/web3.js"; +import BN from "bn.js"; + +const raydiumCreateCpmmAction: Action = { + name: "solana_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) => { + 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 raydium = await Raydium.load({ + owner: agent.wallet, + connection: agent.connection, + }); + + const [mintInfoA, mintInfoB] = await agent.connection.getMultipleAccountsInfo( + [mintA, mintB], + ); + if (mintInfoA === null || mintInfoB === null) { + throw Error("fetch mint info error"); + } + + const mintDecodeInfoA = MintLayout.decode(mintInfoA.data); + const mintDecodeInfoB = MintLayout.decode(mintInfoB.data); + + const mintFormatInfoA = { + chainId: 101, + address: mintA.toString(), + programId: mintInfoA.owner.toString(), + logoURI: "", + symbol: "", + name: "", + decimals: mintDecodeInfoA.decimals, + tags: [], + extensions: {}, + }; + const mintFormatInfoB = { + chainId: 101, + address: mintB.toString(), + programId: mintInfoB.owner.toString(), + logoURI: "", + symbol: "", + name: "", + decimals: mintDecodeInfoB.decimals, + tags: [], + extensions: {}, + }; + + const { execute } = await raydium.cpmm.createPool({ + programId: CREATE_CPMM_POOL_PROGRAM, + poolFeeAccount: CREATE_CPMM_POOL_FEE_ACC, + mintA: mintFormatInfoA, + mintB: mintFormatInfoB, + mintAAmount, + mintBAmount, + startTime, + //@ts-expect-error sdk bug + feeConfig: { id: configId.toString() }, + associatedOnly: false, + ownerInfo: { + useSOLBalance: true, + }, + txVersion: TxVersion.V0, + }); + + const { txId } = await execute({ sendAndConfirm: true }); + + 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; \ No newline at end of file diff --git a/src/actions/registerDomain.ts b/src/actions/registerDomain.ts new file mode 100644 index 0000000..02b92d3 --- /dev/null +++ b/src/actions/registerDomain.ts @@ -0,0 +1,110 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { Transaction } from "@solana/web3.js"; +import { registerDomainNameV2 } from "@bonfida/spl-name-service"; +import { getAssociatedTokenAddressSync } from "@solana/spl-token"; +import { TOKENS } from "../constants"; + +const registerDomainAction: Action = { + name: "solana_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) => { + try { + const name = input.name as string; + const spaceKB = (input.spaceKB as number) || 1; + + // Validate space size + if (spaceKB > 10) { + return { + status: "error", + message: "Maximum domain size is 10KB" + }; + } + + // Convert KB to bytes + const space = spaceKB * 1_000; + + const buyerTokenAccount = await getAssociatedTokenAddressSync( + agent.wallet_address, + TOKENS.USDC + ); + + // Create registration instruction + const instruction = await registerDomainNameV2( + agent.connection, + name, + space, + agent.wallet_address, + buyerTokenAccount + ); + + // Create and sign transaction + const transaction = new Transaction().add(...instruction); + transaction.recentBlockhash = ( + await agent.connection.getLatestBlockhash() + ).blockhash; + transaction.feePayer = agent.wallet_address; + + // Sign and send transaction + const signature = await agent.connection.sendTransaction(transaction, [ + agent.wallet + ]); + + // Wait for confirmation + const latestBlockhash = await agent.connection.getLatestBlockhash(); + await agent.connection.confirmTransaction({ + signature, + blockhash: latestBlockhash.blockhash, + lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, + }); + + return { + status: "success", + signature, + message: `Successfully registered ${name}.sol` + }; + } catch (error: any) { + return { + status: "error", + message: `Domain registration failed: ${error.message}` + }; + } + } +}; + +export default registerDomainAction; \ No newline at end of file diff --git a/src/actions/resolveDomain.ts b/src/actions/resolveDomain.ts new file mode 100644 index 0000000..049c3df --- /dev/null +++ b/src/actions/resolveDomain.ts @@ -0,0 +1,61 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { TldParser } from "@onsol/tldparser"; + +const resolveDomainAction: Action = { + name: "solana_resolve_domain", + 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) => { + try { + const domain = input.domain as string; + const tld = await new TldParser(agent.connection).getOwnerFromDomainTld( + domain + ); + + if (!tld) { + return { + status: "error", + message: "Domain not found" + }; + } + + return { + status: "success", + owner: tld.toBase58(), + message: `Successfully resolved domain ${domain}` + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to resolve domain: ${error.message}` + }; + } + } +}; + +export default resolveDomainAction; \ No newline at end of file diff --git a/src/actions/resolveSolDomain.ts b/src/actions/resolveSolDomain.ts new file mode 100644 index 0000000..481acef --- /dev/null +++ b/src/actions/resolveSolDomain.ts @@ -0,0 +1,69 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { resolve } from "@bonfida/spl-name-service"; + +const resolveSolDomainAction: Action = { + name: "solana_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) => { + try { + const domain = input.domain as string; + + if (!domain || typeof domain !== "string") { + return { + status: "error", + message: "Invalid domain. Expected a non-empty string." + }; + } + + // Remove .sol suffix if present for consistent handling + const cleanDomain = domain.toLowerCase().endsWith(".sol") + ? domain.slice(0, -4) + : domain; + + const ownerAddress = await resolve(agent.connection, cleanDomain); + + return { + status: "success", + owner: ownerAddress.toBase58(), + message: `Successfully resolved ${cleanDomain}.sol` + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to resolve domain: ${error.message}` + }; + } + } +}; + +export default resolveSolDomainAction; \ No newline at end of file diff --git a/src/actions/stakeWithJup.ts b/src/actions/stakeWithJup.ts new file mode 100644 index 0000000..271f902 --- /dev/null +++ b/src/actions/stakeWithJup.ts @@ -0,0 +1,101 @@ +import { Action } from "../types/action"; +import { SolanaAgentKit } from "../agent"; +import { z } from "zod"; +import { VersionedTransaction } from "@solana/web3.js"; + +const stakeWithJupAction: Action = { + name: "solana_stake_with_jup", + 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) => { + try { + const amount = input.amount as number; + + // Get staking transaction from Jupiter + const res = await fetch( + `https://worker.jup.ag/blinks/swap/So11111111111111111111111111111111111111112/jupSoLaHXQiZZTSfEWMTRRgpnyFm8f6sZdosWBjx93v/${amount}`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + account: agent.wallet.publicKey.toBase58(), + }), + } + ); + + if (!res.ok) { + return { + status: "error", + message: `Failed to get staking transaction: ${res.statusText}` + }; + } + + const data = await res.json(); + + // Deserialize and prepare transaction + const txn = VersionedTransaction.deserialize( + Buffer.from(data.transaction, "base64") + ); + + 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, + }); + + // Confirm transaction + const latestBlockhash = await agent.connection.getLatestBlockhash(); + await agent.connection.confirmTransaction({ + signature, + blockhash: latestBlockhash.blockhash, + lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, + }); + + return { + status: "success", + signature, + message: `Successfully staked ${amount} SOL for jupSOL` + }; + } catch (error: any) { + return { + status: "error", + message: `jupSOL staking failed: ${error.message}` + }; + } + } +}; + +export default stakeWithJupAction; \ No newline at end of file