diff --git a/src/agent/index.ts b/src/agent/index.ts index 5886fb4..0fe422a 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -96,6 +96,7 @@ import { OrderParams, FlashTradeParams, FlashCloseTradeParams, + PriorityFeeTransaction, HeliusWebhookIdResponse, HeliusWebhookResponse, } from "../types"; @@ -687,24 +688,4 @@ export class SolanaAgentKit { async deleteWebhook(webhookID: string): Promise { return deleteHeliusWebhook(this, webhookID); } - async heliusParseTransactions(transactionId: string): Promise { - return parseTransaction(this, transactionId); - } - async getAllAssetsbyOwner(owner: PublicKey, limit: number): Promise { - return getAssetsByOwner(this, owner, limit); - } - async sendTranctionWithPriority( - priorityLevel: string, - amount: number, - to: PublicKey, - splmintAddress?: PublicKey, - ): Promise<{ transactionId: string; fee: number }> { - return sendTransactionWithPriorityFee( - this, - priorityLevel, - amount, - to, - splmintAddress, - ); - } } diff --git a/src/langchain/index.ts b/src/langchain/index.ts index 524b3d0..8809e7b 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -100,6 +100,150 @@ import { SolanaGetAllAssetsByOwner, } from "./index"; +export class SolanaHeliusWebhookTool extends Tool { + name = "create_helius_webhook"; + description = `Creates a Helius Webhook that listens to specified account addresses. + + Inputs (input is a JSON string): + accountAddresses: string[] | string, + e.g. ["BVdNLvyG2DNiWAXBE9qAmc4MTQXymd5Bzfo9xrQSUzVP","Eo2ciguhMLmcTWXELuEQPdu7DWZt67LHXb2rdHZUbot7"] + or "BVdNLvyG2DNiWAXBE9qAmc4MTQXymd5Bzfo9xrQSUzVP,Eo2ciguhMLmcTWXELuEQPdu7DWZt67LHXb2rdHZUbot7" + webhookURL: string, e.g. "https://TestServer.test.repl.co/webhooks"`; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const parsedInput = JSON.parse(input); + let accountAddresses: string[] = []; + + if (!parsedInput.accountAddresses) { + throw new Error('Missing "accountAddresses" property in input JSON.'); + } + if (Array.isArray(parsedInput.accountAddresses)) { + accountAddresses = parsedInput.accountAddresses.map((addr: string) => + addr.trim(), + ); + } else if (typeof parsedInput.accountAddresses === "string") { + accountAddresses = parsedInput.accountAddresses + .split(",") + .map((addr: string) => addr.trim()); + } else { + throw new Error( + 'Invalid type for "accountAddresses". Expected array or comma-separated string.', + ); + } + + const webhookURL = parsedInput.webhookURL; + if (!webhookURL) { + throw new Error( + 'Invalid input. Expected a "webhookURL" property in the JSON.', + ); + } + const result = await this.solanaKit.CreateWebhook( + accountAddresses, + webhookURL, + ); + + // Return success in JSON + return JSON.stringify({ + status: "success", + message: "Helius Webhook created successfully", + webhookURL: result.webhookURL, + webhookID: result.webhookID, + }); + } catch (error: any) { + return JSON.stringify({ + status: "error", + message: error.message, + code: error.code || "UNKNOWN_ERROR", + }); + } + } +} + +export class SolanaGetHeliusWebhookTool extends Tool { + name = "get_helius_webhook"; + description = `Retrieves a Helius Webhook by its ID. + +Inputs (input is a JSON string): + webhookID: string, e.g. "1ed4244d-a591-4854-ac31-cc28d40b8255"`; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const parsedInput = JSON.parse(input); + + const webhookID = parsedInput.webhookID; + if (!webhookID || typeof webhookID !== "string") { + throw new Error( + 'Invalid input. Expected a "webhookID" property in the JSON.', + ); + } + + const result = await this.solanaKit.getWebhook(webhookID); + return JSON.stringify({ + status: "success", + message: "Helius Webhook retrieved successfully", + wallet: result.wallet, + webhookURL: result.webhookURL, + transactionTypes: result.transactionTypes, + accountAddresses: result.accountAddresses, + webhookType: result.webhookType, + }); + } catch (error: any) { + return JSON.stringify({ + status: "error", + message: error.message, + code: error.code || "UNKNOWN_ERROR", + }); + } + } +} + +export class SolanaDeleteHeliusWebhookTool extends Tool { + name = "delete_helius_webhook"; + description = `Deletes a Helius Webhook by its ID. + +Inputs (input is a JSON string): + webhookID: string, e.g. "1ed4244d-a591-4854-ac31-cc28d40b8255"`; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const parsedInput = JSON.parse(input); + + const webhookID = parsedInput.webhookID; + if (!webhookID || typeof webhookID !== "string") { + throw new Error( + 'Invalid input. Expected a "webhookID" property in the JSON.', + ); + } + const result = await this.solanaKit.deleteWebhook(webhookID); + + return JSON.stringify({ + status: "success", + message: "Helius Webhook deleted successfully", + data: result, + }); + } catch (error: any) { + return JSON.stringify({ + status: "error", + message: error.message, + code: error.code || "UNKNOWN_ERROR", + }); + } + } +} + export function createSolanaTools(solanaKit: SolanaAgentKit) { return [ new SolanaBalanceTool(solanaKit), @@ -172,5 +316,10 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) { new SolanaDeleteHeliusWebhookTool(solanaKit), new SolanaParseTransactionHeliusTool(solanaKit), new SolanaGetAllAssetsByOwner(solanaKit), + new Solana3LandCreateSingle(solanaKit), + new SolanaSendTransactionWithPriorityFee(solanaKit), + new SolanaHeliusWebhookTool(solanaKit), + new SolanaGetHeliusWebhookTool(solanaKit), + new SolanaDeleteHeliusWebhookTool(solanaKit), ]; } diff --git a/src/tools/helius_webhook.ts b/src/tools/helius_webhook.ts new file mode 100644 index 0000000..98b083f --- /dev/null +++ b/src/tools/helius_webhook.ts @@ -0,0 +1,132 @@ +import { SolanaAgentKit } from "../index"; +import { HeliusWebhookResponse, HeliusWebhookIdResponse } from "../index"; + +export async function create_HeliusWebhook( + agent: SolanaAgentKit, + accountAddresses: string[], + webhookURL: string, +): Promise { + try { + const response = await fetch( + `https://api.helius.xyz/v0/webhooks?api-key=${agent.config.HELIUS_API_KEY}`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + webhookURL, + transactionTypes: ["Any"], + accountAddresses, + webhookType: "enhanced", + txnStatus: "all", + }), + }, + ); + + const data = await response.json(); + return { + webhookURL: data.webhookURL, + webhookID: data.webhookID, + }; + } catch (error: any) { + throw new Error(`Failed to create Webhook: ${error.message}`); + } +} + +/** + * Retrieves a Helius Webhook by ID, returning only the specified fields. + * + * @param agent - An instance of SolanaAgentKit (with .config.HELIUS_API_KEY) + * @param webhookID - The unique ID of the webhook to retrieve + * + * @returns A HeliusWebhook object containing { wallet, webhookURL, transactionTypes, accountAddresses, webhookType } + */ +export async function getHeliusWebhook( + agent: SolanaAgentKit, + webhookID: string, +): Promise { + try { + const apiKey = agent.config.HELIUS_API_KEY; + if (!apiKey) { + throw new Error("HELIUS_API_KEY is missing in agent.config"); + } + + const response = await fetch( + `https://api.helius.xyz/v0/webhooks/${webhookID}?api-key=${apiKey}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }, + ); + + if (!response.ok) { + throw new Error( + `Failed to fetch webhook with ID ${webhookID}. ` + + `Status Code: ${response.status}`, + ); + } + + const data = await response.json(); + + return { + wallet: data.wallet, + webhookURL: data.webhookURL, + transactionTypes: data.transactionTypes, + accountAddresses: data.accountAddresses, + webhookType: data.webhookType, + }; + } catch (error: any) { + throw new Error(`Failed to get webhook by ID: ${error.message}`); + } +} + +/** + * Deletes a Helius Webhook by its ID. + * + * @param agent - An instance of SolanaAgentKit (with .config.HELIUS_API_KEY) + * @param webhookID - The unique ID of the webhook to delete + * + * @returns The response body from the Helius API (which may contain status or other info) + */ +export async function deleteHeliusWebhook( + agent: SolanaAgentKit, + webhookID: string, +): Promise { + try { + const apiKey = agent.config.HELIUS_API_KEY; + if (!apiKey) { + throw new Error("Missing Helius API key in agent.config.HELIUS_API_KEY"); + } + + const url = `https://api.helius.xyz/v0/webhooks/${webhookID}?api-key=${apiKey}`; + const response = await fetch(url, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error( + `Failed to delete webhook: ${response.status} ${response.statusText}`, + ); + } + if (response.status === 204) { + return { message: "Webhook deleted successfully (no content returned)" }; + } + const contentLength = response.headers.get("Content-Length"); + if (contentLength === "0" || !contentLength) { + return { message: "Webhook deleted successfully (empty body)" }; + } + + // Otherwise, parse as JSON + const data = await response.json(); + return data; + } catch (error: any) { + console.error("Error deleting Helius Webhook:", error.message); + throw new Error(`Failed to delete Helius Webhook: ${error.message}`); + } +} diff --git a/src/types/index.ts b/src/types/index.ts index 2d733b7..0fedb5e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -238,3 +238,20 @@ export interface FlashCloseTradeParams { token: string; side: "long" | "short"; } + +export interface PriorityFeeTransaction { + transactionId: string; + fee: number; +} + +export interface HeliusWebhookResponse { + webhookURL: string; + webhookID: string; +} +export interface HeliusWebhookIdResponse { + wallet: string; + webhookURL: string; + transactionTypes: string[]; + accountAddresses: string[]; + webhookType: string; +}