diff --git a/.env.example b/.env.example index 52fdf99..bd420ff 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,5 @@ RPC_URL= SOLANA_PRIVATE_KEY= JUPITER_REFERRAL_ACCOUNT= JUPITER_FEE_BPS= -FLASH_PRIVILEGE= referral | nft | none \ No newline at end of file +FLASH_PRIVILEGE= referral | nft | none +HELIUS_API_KEY= \ No newline at end of file diff --git a/src/actions/helius/createWebhook.ts b/src/actions/helius/createWebhook.ts new file mode 100644 index 0000000..4cb183a --- /dev/null +++ b/src/actions/helius/createWebhook.ts @@ -0,0 +1,57 @@ +import { Action } from "../../types/action"; +import { SolanaAgentKit } from "../../agent"; +import { z } from "zod"; +import { create_HeliusWebhook } from "../../tools/helius"; + +const createWebhookAction: Action = { + name: "CREATE_HELIOUS_WEBHOOK", + similes: ["setup webhook", "register webhook", "initiate webhook"], + description: + "Creates a new webhook in the Helius system to monitor transactions for specified account addresses", + examples: [ + [ + { + input: { + accountAddresses: [ + "BVdNLvyG2DNiWAXBE9qAmc4MTQXymd5Bzfo9xrQSUzVP", + "Eo2ciguhMLmcTWXELuEQPdu7DWZt67LHXb2rdHZUbot7", + ], + webhookURL: "https://yourdomain.com/webhook", + }, + output: { + status: "success", + webhookURL: "https://yourdomain.com/webhook", + webhookID: "webhook_123", + message: "Webhook created successfully.", + }, + explanation: + "Creates a Webhook to send live notifications on the given Url with the wallet Addresses.", + }, + ], + ], + schema: z.object({ + accountAddresses: z + .array(z.string()) + .min(1) + .describe("List of Solana account public keys to monitor"), + webhookURL: z + .string() + .url() + .describe("The URL where Helius will send webhook notifications"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + const response = await create_HeliusWebhook( + agent, + input.accountAddresses, + input.webhookURL, + ); + + return { + status: "success", + ...response, + message: "Webhook created successfully.", + }; + }, +}; + +export default createWebhookAction; diff --git a/src/actions/helius/deleteWebhook.ts b/src/actions/helius/deleteWebhook.ts new file mode 100644 index 0000000..35500f8 --- /dev/null +++ b/src/actions/helius/deleteWebhook.ts @@ -0,0 +1,40 @@ +import { Action } from "../../types/action"; +import { SolanaAgentKit } from "../../agent"; +import { z } from "zod"; +import { deleteHeliusWebhook } from "../../tools/helius"; + +const deleteWebhookAction: Action = { + name: "DELETE_HELIOUS_WEBHOOK", + similes: ["remove webhook", "unregister webhook", "delete webhook"], + description: "Deletes a Helius webhook by its unique ID", + examples: [ + [ + { + input: { + webhookID: "webhook_123", + }, + output: { + status: "success", + message: "Webhook deleted successfully.", + }, + explanation: "Permanently removes a Helius webhook.", + }, + ], + ], + schema: z.object({ + webhookID: z + .string() + .min(1) + .describe("The unique identifier of the Helius webhook to delete"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + const result = await deleteHeliusWebhook(agent, input.webhookID); + + return { + status: "success", + message: result.message || "Webhook deleted successfully.", + }; + }, +}; + +export default deleteWebhookAction; diff --git a/src/actions/helius/getAssetsbyOwner.ts b/src/actions/helius/getAssetsbyOwner.ts new file mode 100644 index 0000000..e71fdbc --- /dev/null +++ b/src/actions/helius/getAssetsbyOwner.ts @@ -0,0 +1,75 @@ +import { Action } from "../../types/action"; +import { PublicKey } from "@solana/web3.js"; +import { SolanaAgentKit } from "../../agent"; +import { z } from "zod"; +import { getAssetsByOwner } from "../../tools/helius"; + +const getAssetsByOwnerAction: Action = { + name: "FETCH_ASSETS_BY_OWNER", + similes: [ + "fetch assets", + "get assets", + "retrieve assets", + "list assets", + "assets by owner", + ], + description: + "Fetch assets owned by a specific Solana wallet address using the Helius Digital Asset Standard API", + examples: [ + [ + { + input: { + ownerPublicKey: "4Pf8q3mHGLdkoc1M8xWZwW5q32gYmdhwC2gJ8K9EAGDX", + limit: 10, + }, + output: { + status: "success", + assets: [ + { + name: "Helius NFT #1", + type: "NFT", + owner: "4Pf8q3mHGLdkoc1M8xWZwW5q32gYmdhwC2gJ8K9EAGDX", + }, + { + name: "Helius Token #10", + type: "Token", + owner: "4Pf8q3mHGLdkoc1M8xWZwW5q32gYmdhwC2gJ8K9EAGDX", + }, + ], + message: "Successfully fetched assets for the wallet address", + }, + explanation: + "Fetches a list of assets from the for the given wallet address with a limit of 10 items.", + }, + ], + ], + schema: z.object({ + ownerPublicKey: z.string().describe("Owner's Solana wallet PublicKey"), + limit: z + .number() + .positive() + .describe("Number of assets to retrieve per request"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const assets = await getAssetsByOwner( + agent, + new PublicKey(input.ownerPublicKey), + input.limit, + ); + + return { + status: "success", + assets: assets, + message: `Successfully fetched assets for the wallet address: ${input.ownerPublicKey}`, + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to fetch assets: ${error.message}`, + }; + } + }, +}; + +export default getAssetsByOwnerAction; diff --git a/src/actions/helius/getWebhook.ts b/src/actions/helius/getWebhook.ts new file mode 100644 index 0000000..e10414d --- /dev/null +++ b/src/actions/helius/getWebhook.ts @@ -0,0 +1,47 @@ +import { Action } from "../../types/action"; +import { SolanaAgentKit } from "../../agent"; +import { z } from "zod"; +import { getHeliusWebhook } from "../../tools/helius"; + +const getWebhookAction: Action = { + name: "GET_HELIOUS_WEBHOOK", + similes: ["fetch webhook details", "retrieve webhook", "get webhook info"], + description: "Retrieves details of a Helius webhook by its unique ID", + examples: [ + [ + { + input: { + webhookID: "webhook_123", + }, + output: { + status: "success", + wallet: "WalletPublicKey", + webhookURL: "https://yourdomain.com/webhook", + transactionTypes: ["Any"], + accountAddresses: ["SomePublicKey", "AnotherPublicKey"], + webhookType: "enhanced", + message: "Webhook details retrieved successfully.", + }, + explanation: + "Retrieves detailed information about an existing Helius webhook, including the wallet address it monitors, the types of transactions it tracks, and the specific webhook URL.", + }, + ], + ], + schema: z.object({ + webhookID: z + .string() + .min(1) + .describe("The unique identifier of the Helius webhook to retrieve"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + const webhookDetails = await getHeliusWebhook(agent, input.webhookID); + + return { + status: "success", + ...webhookDetails, + message: "Webhook details retrieved successfully.", + }; + }, +}; + +export default getWebhookAction; diff --git a/src/actions/helius/parseTransaction.ts b/src/actions/helius/parseTransaction.ts new file mode 100644 index 0000000..ab08011 --- /dev/null +++ b/src/actions/helius/parseTransaction.ts @@ -0,0 +1,65 @@ +import { Action } from "../../types/action"; +import { SolanaAgentKit } from "../../agent"; +import { z } from "zod"; +import { parseTransaction } from "../../tools/helius"; + +const parseSolanaTransactionAction: Action = { + name: "PARSE_SOLANA_TRANSACTION", + similes: [ + "parse transaction", + "analyze transaction", + "inspect transaction", + "decode transaction", + ], + description: + "Parse a Solana transaction to retrieve detailed information using the Helius Enhanced Transactions API", + examples: [ + [ + { + input: { + transactionId: + "4zZVvbgzcriyjAeEiK1w7CeDCt7gYThUCZat3ULTNerzKHF4WLfRG2YUjbRovfFJ639TAyARB4oyRDcLVUvrakq7", + }, + output: { + status: "success", + transaction: { + details: "Transaction details...", + involvedAccounts: ["Account1", "Account2"], + executedOperations: [{ operation: "Transfer", amount: "1000 SOL" }], + }, + message: + "Successfully parsed transaction: 4zZVvbgzcriyjAeEiK1w7CeDCt7gYThUCZat3ULTNerzKHF4WLfRG2YUjbRovfFJ639TAyARB4oyRDcLVUvrakq7", + }, + explanation: + "Parse a Transaction to transform it into human readable format.", + }, + ], + ], + schema: z.object({ + transactionId: z + .string() + .min(1) + .describe("The Solana transaction ID to parse"), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + try { + const parsedTransactionData = await parseTransaction( + agent, + input.transactionId, + ); + + return { + status: "success", + transaction: parsedTransactionData, + message: `Successfully parsed transaction: ${input.transactionId}`, + }; + } catch (error: any) { + return { + status: "error", + message: `Failed to parse transaction: ${error.message}`, + }; + } + }, +}; + +export default parseSolanaTransactionAction; diff --git a/src/actions/helius/sendTransactionWithPriority.ts b/src/actions/helius/sendTransactionWithPriority.ts new file mode 100644 index 0000000..87e392f --- /dev/null +++ b/src/actions/helius/sendTransactionWithPriority.ts @@ -0,0 +1,76 @@ +import { Action } from "../../types/action"; +import { SolanaAgentKit } from "../../agent"; +import { z } from "zod"; +import { sendTransactionWithPriorityFee } from "../../tools/helius"; +import { PublicKey } from "@solana/web3.js"; + +const sendTransactionWithPriorityFeeAction: Action = { + name: "SEND_TRANSACTION_WITH_PRIORITY_FEE", + similes: [ + "send SOL with fee", + "transfer tokens with priority", + "execute priority transaction", + ], + description: + "Sends SOL or SPL tokens from a wallet with an estimated priority fee, ensuring faster processing on the Solana network.", + examples: [ + [ + { + input: { + priorityLevel: "High", + amount: 2, + to: "BVdNLvyG2DNiWAXBE9qAmc4MTQXymd5Bzfo9xrQSUzVP", + splmintAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + }, + output: { + status: "success", + transactionId: "5Xgq9xVABhwXpNStWpfqxS6Vm5Eau91pjfeHNwJbRgis", + fee: 5000, + message: "Transaction sent with priority fee successfully.", + }, + explanation: + "Sends 2 USDC to BVdNLvyG2DNiWAXBE9qAmc4MTQXymd5Bzfo9xrQSUzVP with High priority fee option.", + }, + ], + ], + schema: z.object({ + priorityLevel: z + .enum(["Min", "Low", "Medium", "High", "VeryHigh", "UnsafeMax"]) + .describe("Priority level to determine the urgency of the transaction."), + amount: z + .number() + .positive() + .describe("Amount of SOL or SPL tokens to send."), + to: z.string().describe("Recipient's PublicKey."), + splmintAddress: z + .string() + .optional() + .describe( + "Optional SPL token address, if transferring tokens other than SOL.", + ), + }), + handler: async (agent: SolanaAgentKit, input: Record) => { + const { priorityLevel, amount, to, splmintAddress } = input; + const toPublicKey = new PublicKey(to); + const splmintPublicKey = splmintAddress + ? new PublicKey(splmintAddress) + : undefined; + + const result = await sendTransactionWithPriorityFee( + agent, + priorityLevel, + amount, + toPublicKey, + splmintPublicKey, + ); + + return { + status: "success", + transactionId: result.transactionId, + fee: result.fee, + message: "Transaction sent with priority fee successfully.", + }; + }, +}; + +export default sendTransactionWithPriorityFeeAction; diff --git a/src/actions/index.ts b/src/actions/index.ts index 96f40ab..6da1b02 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -37,6 +37,12 @@ import depositToMultisigAction from "./squads/depositToMultisigTreasury"; import executeMultisigProposalAction from "./squads/executeMultisigProposal"; import rejectMultisigProposalAction from "./squads/rejectMultisigProposal"; import transferFromMultisigAction from "./squads/transferFromMultisigTreasury"; +import createWebhookAction from "./helius/createWebhook"; +import deleteWebhookAction from "./helius/deleteWebhook"; +import getAssetsByOwnerAction from "./helius/getAssetsbyOwner"; +import getWebhookAction from "./helius/getWebhook"; +import parseSolanaTransactionAction from "./helius/parseTransaction"; +import sendTransactionWithPriorityFeeAction from "./helius/sendTransactionWithPriority"; export const ACTIONS = { WALLET_ADDRESS_ACTION: getWalletAddressAction, @@ -79,6 +85,12 @@ export const ACTIONS = { APPROVE_MULTISIG_PROPOSAL_ACTION: approveMultisigProposalAction, REJECT_MULTISIG_PROPOSAL_ACTION: rejectMultisigProposalAction, EXECUTE_MULTISIG_PROPOSAL_ACTION: executeMultisigProposalAction, + CREATE_WEBHOOK_ACTION: createWebhookAction, + DELETE_WEBHOOK_ACTION: deleteWebhookAction, + GET_ASSETS_BY_OWNER_ACTION: getAssetsByOwnerAction, + GET_WEBHOOK_ACTION: getWebhookAction, + PARSE_TRANSACTION_ACTION: parseSolanaTransactionAction, + SEND_TRANSACTION_WITH_PRIORITY_ACTION: sendTransactionWithPriorityFeeAction, }; export type { Action, ActionExample, Handler } from "../types/action"; diff --git a/src/agent/index.ts b/src/agent/index.ts index a80bfb6..97cfb21 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -78,6 +78,12 @@ import { multisig_reject_proposal, multisig_approve_proposal, multisig_execute_proposal, + parseTransaction, + sendTransactionWithPriorityFee, + getAssetsByOwner, + getHeliusWebhook, + create_HeliusWebhook, + deleteHeliusWebhook, } from "../tools"; import { Config, @@ -92,6 +98,8 @@ import { OrderParams, FlashTradeParams, FlashCloseTradeParams, + HeliusWebhookIdResponse, + HeliusWebhookResponse, } from "../types"; /** @@ -643,6 +651,12 @@ export class SolanaAgentKit { async flashCloseTrade(params: FlashCloseTradeParams): Promise { return flashCloseTrade(this, params); } + async heliusParseTransactions(transactionId: string): Promise { + return parseTransaction(this, transactionId); + } + async getAllAssetsbyOwner(owner: PublicKey, limit: number): Promise { + return getAssetsByOwner(this, owner, limit); + } async create3LandCollection( optionsWithBase58: StoreInitOptions, @@ -666,6 +680,20 @@ export class SolanaAgentKit { ); return `Transaction: ${tx}`; } + async sendTranctionWithPriority( + priorityLevel: string, + amount: number, + to: PublicKey, + splmintAddress?: PublicKey, + ): Promise<{ transactionId: string; fee: number }> { + return sendTransactionWithPriorityFee( + this, + priorityLevel, + amount, + to, + splmintAddress, + ); + } async createSquadsMultisig(creator: PublicKey): Promise { return create_squads_multisig(this, creator); @@ -711,4 +739,16 @@ export class SolanaAgentKit { ): Promise { return multisig_execute_proposal(this, transactionIndex); } + async CreateWebhook( + accountAddresses: string[], + webhookURL: string, + ): Promise { + return create_HeliusWebhook(this, accountAddresses, webhookURL); + } + async getWebhook(id: string): Promise { + return getHeliusWebhook(this, id); + } + async deleteWebhook(webhookID: string): Promise { + return deleteHeliusWebhook(this, webhookID); + } } diff --git a/src/langchain/helius/create_webhook.ts b/src/langchain/helius/create_webhook.ts new file mode 100644 index 0000000..05dd6d5 --- /dev/null +++ b/src/langchain/helius/create_webhook.ts @@ -0,0 +1,65 @@ +import { Tool } from "langchain/tools"; +import { SolanaAgentKit } from "../../agent"; + +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", + }); + } + } +} diff --git a/src/langchain/helius/delete_webhook.ts b/src/langchain/helius/delete_webhook.ts new file mode 100644 index 0000000..dd7d952 --- /dev/null +++ b/src/langchain/helius/delete_webhook.ts @@ -0,0 +1,39 @@ +import { Tool } from "langchain/tools"; +import { SolanaAgentKit } from "../../agent"; + +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", + }); + } + } +} diff --git a/src/langchain/helius/get_all_assets.ts b/src/langchain/helius/get_all_assets.ts new file mode 100644 index 0000000..eb06c46 --- /dev/null +++ b/src/langchain/helius/get_all_assets.ts @@ -0,0 +1,38 @@ +import { Tool } from "langchain/tools"; +import { SolanaAgentKit } from "../../agent"; +import { PublicKey } from "@solana/web3.js"; + +export class SolanaGetAllAssetsByOwner extends Tool { + name = "solana_get_all_assets_by_owner"; + description = `Get all assets owned by a specific wallet address. + Inputs: + - owner: string, the wallet address of the owner, e.g., "4Be9CvxqHW6BYiRAxW9Q3xu1ycTMWaL5z8NX4HR3ha7t" (required) + - limit: number, the maximum number of assets to retrieve (optional)`; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const { owner, limit } = JSON.parse(input); + const ownerPubkey = new PublicKey(owner); + + const assets = await this.solanaKit.getAllAssetsbyOwner( + ownerPubkey, + limit, + ); + return JSON.stringify({ + status: "success", + message: "Assets retrieved successfully", + assets: assets, + }); + } catch (error: any) { + return JSON.stringify({ + status: "error", + message: error.message, + code: error.code || "UNKNOWN_ERROR", + }); + } + } +} diff --git a/src/langchain/helius/get_webhook.ts b/src/langchain/helius/get_webhook.ts new file mode 100644 index 0000000..410580f --- /dev/null +++ b/src/langchain/helius/get_webhook.ts @@ -0,0 +1,43 @@ +import { Tool } from "langchain/tools"; +import { SolanaAgentKit } from "../../agent"; + +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", + }); + } + } +} diff --git a/src/langchain/helius/index.ts b/src/langchain/helius/index.ts new file mode 100644 index 0000000..652b20e --- /dev/null +++ b/src/langchain/helius/index.ts @@ -0,0 +1,6 @@ +export * from "./create_webhook"; +export * from "./delete_webhook"; +export * from "./get_all_assets"; +export * from "./get_webhook"; +export * from "./parse_transaction"; +export * from "./send_transaction_priority"; diff --git a/src/langchain/helius/parse_transaction.ts b/src/langchain/helius/parse_transaction.ts new file mode 100644 index 0000000..33d318c --- /dev/null +++ b/src/langchain/helius/parse_transaction.ts @@ -0,0 +1,32 @@ +import { Tool } from "langchain/tools"; +import { SolanaAgentKit } from "../../agent"; + +export class SolanaParseTransactionHeliusTool extends Tool { + name = "solana_parse_transaction_helius"; + description = `Parse a Solana transaction using Helius API. + Inputs: + - transactionId: string, the ID of the transaction to parse, e.g., "5h3k...9d2k" (required).`; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const transactionId = input.trim(); + const parsedTransaction = + await this.solanaKit.heliusParseTransactions(transactionId); + return JSON.stringify({ + status: "success", + message: "transaction parsed successfully", + transaction: parsedTransaction, + }); + } catch (error: any) { + return JSON.stringify({ + status: "error", + message: error.message, + code: error.code || "NOt able to Parse transaction", + }); + } + } +} diff --git a/src/langchain/helius/send_transaction_priority.ts b/src/langchain/helius/send_transaction_priority.ts new file mode 100644 index 0000000..ee492e5 --- /dev/null +++ b/src/langchain/helius/send_transaction_priority.ts @@ -0,0 +1,63 @@ +import { Tool } from "langchain/tools"; +import { SolanaAgentKit } from "../../agent"; +import { PublicKey } from "@solana/web3.js"; + +export class SolanaSendTransactionWithPriorityFee extends Tool { + name = "solana_send_transaction_with_priority_fee"; + description = `Sends a Solana transaction with a user-defined priority fee. + **Inputs (JSON-encoded string)**: + - priorityLevel: string — the priority level ("NONE", "Min", "Low", "Medium", "High", "VeryHigh", or "UnsafeMax") + - amount: number — the amount of SOL to send + - to: string — the recipient's wallet address (public key in base58);`; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const { priorityLevel, amount, to, splmintAddress } = JSON.parse(input); + + const validPriorityLevels = [ + "NONE", + "Min", + "Low", + "Medium", + "High", + "VeryHigh", + "UnsafeMax", + ]; + if (!validPriorityLevels.includes(priorityLevel)) { + throw new Error( + `Invalid priority level. Must be one of: ${validPriorityLevels.join(", ")}. Received: ${priorityLevel}`, + ); + } + + if (!amount || !to) { + throw new Error( + `Missing required fields. Received: priorityLevel=${priorityLevel}, amount=${amount}, to=${to}`, + ); + } + + const toPubkey = new PublicKey(to); + const priorityFeeTx = await this.solanaKit.sendTranctionWithPriority( + priorityLevel, + amount, + toPubkey, + splmintAddress, + ); + + return JSON.stringify({ + status: "success", + message: "Transaction sent successfully", + priorityFeeTx, + }); + } catch (error: any) { + return JSON.stringify({ + status: "error", + message: error.message, + code: error.code || "UNKNOWN_ERROR", + }); + } + } +} diff --git a/src/langchain/index.ts b/src/langchain/index.ts index 12a804f..a1aaff6 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -25,6 +25,7 @@ export * from "./sns"; export * from "./lightprotocol"; export * from "./squads"; export * from "./meteora"; +export * from "./helius"; import { SolanaAgentKit } from "../agent"; import { @@ -94,6 +95,12 @@ import { SolanaRejectProposal2by2Multisig, SolanaMeteoraCreateDynamicPool, SolanaMeteoraCreateDlmmPool, + SolanaSendTransactionWithPriorityFee, + SolanaHeliusWebhookTool, + SolanaGetHeliusWebhookTool, + SolanaDeleteHeliusWebhookTool, + SolanaParseTransactionHeliusTool, + SolanaGetAllAssetsByOwner, } from "./index"; export function createSolanaTools(solanaKit: SolanaAgentKit) { @@ -164,5 +171,16 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) { new SolanaExecuteProposal2by2Multisig(solanaKit), new SolanaDepositTo2by2Multisig(solanaKit), new SolanaTransferFrom2by2Multisig(solanaKit), + new SolanaSendTransactionWithPriorityFee(solanaKit), + new SolanaHeliusWebhookTool(solanaKit), + new SolanaGetHeliusWebhookTool(solanaKit), + 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/get_assets_by_owner.ts b/src/tools/helius/get_assets_by_owner.ts new file mode 100644 index 0000000..05d225c --- /dev/null +++ b/src/tools/helius/get_assets_by_owner.ts @@ -0,0 +1,57 @@ +import { SolanaAgentKit } from "../../index"; +import { PublicKey } from "@solana/web3.js"; + +/** + * Fetch assets by owner using the Helius Digital Asset Standard (DAS) API + * @param agent SolanaAgentKit instance + * @param ownerPublicKey Owner's Solana wallet PublicKey + * @param limit Number of assets to retrieve per request + * @returns Assets owned by the specified address + */ +export async function getAssetsByOwner( + agent: SolanaAgentKit, + ownerPublicKey: PublicKey, + limit: number, +): Promise { + try { + const apiKey = agent.config.HELIUS_API_KEY; + if (!apiKey) { + throw new Error("HELIUS_API_KEY not found in environment variables"); + } + + const url = `https://mainnet.helius-rpc.com/?api-key=${apiKey}`; + + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: "get-assets", + method: "getAssetsByOwner", + params: { + ownerAddress: ownerPublicKey.toString(), + page: 3, + limit: limit, + displayOptions: { + showFungible: true, + }, + }, + }), + }); + + if (!response.ok) { + throw new Error( + `Failed to fetch: ${response.status} - ${response.statusText}`, + ); + } + + const data = await response.json(); + + return data.result.items; + } catch (error: any) { + console.error("Error retrieving assets: ", error.message); + throw new Error(`Assets retrieval failed: ${error.message}`); + } +} diff --git a/src/tools/helius/helius_transaction_parsing.ts b/src/tools/helius/helius_transaction_parsing.ts new file mode 100644 index 0000000..54a39a6 --- /dev/null +++ b/src/tools/helius/helius_transaction_parsing.ts @@ -0,0 +1,44 @@ +import { SolanaAgentKit } from "../../index"; + +/** + * Parse a Solana transaction using the Helius Enhanced Transactions API + * @param agent SolanaAgentKit instance + * @param transactionId The transaction ID to parse + * @returns Parsed transaction data + */ +export async function parseTransaction( + agent: SolanaAgentKit, + transactionId: string, +): Promise { + try { + const apiKey = agent.config.HELIUS_API_KEY; + if (!apiKey) { + throw new Error("HELIUS_API_KEY not found in environment variables"); + } + + const url = `https://api.helius.xyz/v0/transactions/?api-key=${apiKey}`; + + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + transactions: [transactionId], + }), + }); + + if (!response.ok) { + throw new Error( + `Failed to fetch: ${response.status} - ${response.statusText}`, + ); + } + + const data = await response.json(); + + return data; + } catch (error: any) { + console.error("Error parsing transaction: ", error.message); + throw new Error(`Transaction parsing failed: ${error.message}`); + } +} diff --git a/src/tools/helius/helius_webhooks.ts b/src/tools/helius/helius_webhooks.ts new file mode 100644 index 0000000..48a16e3 --- /dev/null +++ b/src/tools/helius/helius_webhooks.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/tools/helius/index.ts b/src/tools/helius/index.ts new file mode 100644 index 0000000..cec732b --- /dev/null +++ b/src/tools/helius/index.ts @@ -0,0 +1,4 @@ +export * from "./get_assets_by_owner"; +export * from "./helius_transaction_parsing"; +export * from "./helius_webhooks"; +export * from "./send_transaction_with_priority"; diff --git a/src/tools/helius/send_transaction_with_priority.ts b/src/tools/helius/send_transaction_with_priority.ts new file mode 100644 index 0000000..3d49401 --- /dev/null +++ b/src/tools/helius/send_transaction_with_priority.ts @@ -0,0 +1,174 @@ +import { SolanaAgentKit, PriorityFeeResponse } from "../../index"; +import { + SystemProgram, + Transaction, + sendAndConfirmTransaction, + ComputeBudgetProgram, + PublicKey, + LAMPORTS_PER_SOL, +} from "@solana/web3.js"; +import { + getAssociatedTokenAddress, + createTransferInstruction, + getMint, + createAssociatedTokenAccountInstruction, +} from "@solana/spl-token"; +import bs58 from "bs58"; + +/** + * Sends a transaction with an estimated priority fee using the provided SolanaAgentKit. + * + * @param agent An instance of SolanaAgentKit containing connection, wallet, etc. + * @param priorityLevel The priority level (e.g., "Min", "Low", "Medium", "High", "VeryHigh", or "UnsafeMax"). + * @param amount The amount of SOL to send (in SOL, not lamports). + * @param to The recipient's PublicKey. + * @returns The transaction signature (string) once confirmed along with the fee used. + */ +export async function sendTransactionWithPriorityFee( + agent: SolanaAgentKit, + priorityLevel: string, + amount: number, + to: PublicKey, + splmintAddress?: PublicKey, +): Promise<{ transactionId: string; fee: number }> { + try { + if (!splmintAddress) { + const transaction = new Transaction(); + const { blockhash, lastValidBlockHeight } = + await agent.connection.getLatestBlockhash(); + transaction.recentBlockhash = blockhash; + transaction.lastValidBlockHeight = lastValidBlockHeight; + transaction.feePayer = agent.wallet_address; + + const transferIx = SystemProgram.transfer({ + fromPubkey: agent.wallet_address, + toPubkey: to, + lamports: amount * LAMPORTS_PER_SOL, + }); + + transaction.add(transferIx); + transaction.sign(agent.wallet); + + const response = await fetch( + `https://mainnet.helius-rpc.com/?api-key=${agent.config.HELIUS_API_KEY}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: "1", + method: "getPriorityFeeEstimate", + params: [ + { + transaction: bs58.encode(transaction.serialize()), + options: { priorityLevel: priorityLevel }, + }, + ], + } as PriorityFeeResponse), + }, + ); + + const data = await response.json(); + if (data.error) { + throw new Error("Error fetching priority fee:"); + } + const feeEstimate: number = data.result.priorityFeeEstimate; + + // Set the priority fee if applicable + const computePriceIx = ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: feeEstimate, + }); + transaction.add(computePriceIx); + + // Send the transaction and confirm + const txSignature = await sendAndConfirmTransaction( + agent.connection, + transaction, + [agent.wallet], + ); + + return { + transactionId: txSignature, + fee: feeEstimate, + }; + } else { + const fromAta = await getAssociatedTokenAddress( + splmintAddress, + agent.wallet_address, + ); + const toAta = await getAssociatedTokenAddress(splmintAddress, to); + + const mintInfo = await getMint(agent.connection, splmintAddress); + const adjustedAmount = amount * Math.pow(10, mintInfo.decimals); + + const transaction = new Transaction(); + const { blockhash, lastValidBlockHeight } = + await agent.connection.getLatestBlockhash(); + transaction.recentBlockhash = blockhash; + transaction.lastValidBlockHeight = lastValidBlockHeight; + transaction.feePayer = agent.wallet_address; + + const response = await fetch( + `https://mainnet.helius-rpc.com/?api-key=${agent.config.HELIUS_API_KEY}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: "1", + method: "getPriorityFeeEstimate", + params: [ + { + transaction: bs58.encode(transaction.serialize()), + options: { priorityLevel: priorityLevel }, + }, + ], + } as PriorityFeeResponse), + }, + ); + + const data = await response.json(); + if (data.error) { + throw new Error("Error fetching priority fee:"); + } + const feeEstimate: number = data.result.priorityFeeEstimate; + + transaction.add( + ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: feeEstimate, + }), + ); + + transaction.add( + createAssociatedTokenAccountInstruction( + agent.wallet_address, + toAta, + to, + splmintAddress, + ), + ); + + transaction.add( + createTransferInstruction( + fromAta, + toAta, + agent.wallet_address, + adjustedAmount, + ), + ); + + const txSignature = await sendAndConfirmTransaction( + agent.connection, + transaction, + [agent.wallet], + ); + + return { + transactionId: txSignature, + fee: feeEstimate, + }; + } + } catch (error: any) { + throw new Error(`Failed to process transaction: ${error.message}`); + } +} diff --git a/src/tools/index.ts b/src/tools/index.ts index a8de007..d061d31 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -24,3 +24,4 @@ export * from "./tiplink"; export * from "./lightprotocol"; export * from "./squads"; export * from "./meteora"; +export * from "./helius"; diff --git a/src/types/index.ts b/src/types/index.ts index 01ac152..2c0a54c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -7,6 +7,7 @@ export interface Config { JUPITER_REFERRAL_ACCOUNT?: string; JUPITER_FEE_BPS?: number; FLASH_PRIVILEGE?: string; + HELIUS_API_KEY?: string; } export interface Creator { @@ -237,3 +238,25 @@ export interface FlashCloseTradeParams { token: string; side: "long" | "short"; } + +export interface HeliusWebhookResponse { + webhookURL: string; + webhookID: string; +} +export interface HeliusWebhookIdResponse { + wallet: string; + webhookURL: string; + transactionTypes: string[]; + accountAddresses: string[]; + webhookType: string; +} + +export interface PriorityFeeResponse { + jsonrpc: string; + id: string; + method: string; + params: Array<{ + transaction: string; + options: { priorityLevel: string }; + }>; +} diff --git a/src/utils/send_tx.ts b/src/utils/send_tx.ts index 11923b5..4ba9da1 100644 --- a/src/utils/send_tx.ts +++ b/src/utils/send_tx.ts @@ -5,8 +5,11 @@ import { TransactionInstruction, TransactionMessage, VersionedTransaction, + Transaction, } from "@solana/web3.js"; import { ComputeBudgetProgram } from "@solana/web3.js"; +import bs58 from "bs58"; +import { PriorityFeeResponse } from "../types/index"; const feeTiers = { min: 0.01, @@ -28,7 +31,8 @@ export async function getComputeBudgetInstructions( computeBudgetLimitInstruction: TransactionInstruction; computeBudgetPriorityFeeInstructions: TransactionInstruction; }> { - const blockhash = (await agent.connection.getLatestBlockhash()).blockhash; + const { blockhash, lastValidBlockHeight } = + await agent.connection.getLatestBlockhash(); const messageV0 = new TransactionMessage({ payerKey: agent.wallet_address, recentBlockhash: blockhash, @@ -47,15 +51,65 @@ export async function getComputeBudgetInstructions( units: safeComputeUnits, }); - const priorityFee = await agent.connection - .getRecentPrioritizationFees() - .then( - (fees) => - fees.sort((a, b) => a.prioritizationFee - b.prioritizationFee)[ - Math.floor(fees.length * feeTiers[feeTier]) - ].prioritizationFee, + let priorityFee: number; + + if (agent.config.HELIUS_API_KEY) { + // Create and set up a legacy transaction for Helius fee estimation + const legacyTransaction = new Transaction(); + legacyTransaction.recentBlockhash = blockhash; + legacyTransaction.lastValidBlockHeight = lastValidBlockHeight; + legacyTransaction.feePayer = agent.wallet_address; + + // Add the compute budget instruction and original instructions + legacyTransaction.add(computeBudgetLimitInstruction, ...instructions); + + // Sign the transaction + legacyTransaction.sign(agent.wallet); + + // Use Helius API for priority fee calculation + const response = await fetch( + `https://mainnet.helius-rpc.com/?api-key=${agent.config.HELIUS_API_KEY}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: "1", + method: "getPriorityFeeEstimate", + params: [ + { + transaction: bs58.encode(legacyTransaction.serialize()), + options: { + priorityLevel: + feeTier === "min" + ? "Min" + : feeTier === "mid" + ? "Medium" + : "High", + }, + }, + ], + } as PriorityFeeResponse), + }, ); + const data = await response.json(); + if (data.error) { + throw new Error("Error fetching priority fee from Helius API"); + } + priorityFee = data.result.priorityFeeEstimate; + } else { + // Use default implementation for priority fee calculation + priorityFee = await agent.connection + .getRecentPrioritizationFees() + .then( + (fees) => + fees.sort((a, b) => a.prioritizationFee - b.prioritizationFee)[ + Math.floor(fees.length * feeTiers[feeTier]) + ].prioritizationFee, + ); + } + const computeBudgetPriorityFeeInstructions = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee, diff --git a/test/index.ts b/test/index.ts index 00f9976..c666d33 100644 --- a/test/index.ts +++ b/test/index.ts @@ -55,6 +55,7 @@ async function initializeAgent() { process.env.RPC_URL!, { OPENAI_API_KEY: process.env.OPENAI_API_KEY!, + HELIUS_API_KEY: process.env.HELIUS_API_KEY!, }, );