Skip to content

Commit

Permalink
feat:added webhooks for live notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
shivaji43 committed Jan 8, 2025
1 parent 1654da2 commit afa3769
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 0 deletions.
17 changes: 17 additions & 0 deletions src/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ import {
parseTransaction,
getAssetsByOwner,
sendTransactionWithPriorityFee,
create_HeliusWebhook,
getHeliusWebhook,
deleteHeliusWebhook,
} from "../tools";
import {
CollectionDeployment,
Expand All @@ -79,6 +82,8 @@ import {
FlashTradeParams,
FlashCloseTradeParams,
PriorityFeeTransaction,
HeliusWebhookIdResponse,
HeliusWebhookResponse,
} from "../types";
import {
createCollection,
Expand Down Expand Up @@ -620,4 +625,16 @@ export class SolanaAgentKit {
): Promise<PriorityFeeTransaction> {
return sendTransactionWithPriorityFee(this, priorityLevel, amount, to);
}
async CreateWebhook(
accountAddresses: string[],
webhookURL: string,
): Promise<HeliusWebhookResponse> {
return create_HeliusWebhook(this, accountAddresses, webhookURL);
}
async getWebhook(id: string): Promise<HeliusWebhookIdResponse> {
return getHeliusWebhook(this, id);
}
async deleteWebhook(webhookID: string): Promise<any> {
return deleteHeliusWebhook(this, webhookID);
}
}
147 changes: 147 additions & 0 deletions src/langchain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2547,6 +2547,150 @@ export class SolanaSendTransactionWithPriorityFee extends Tool {
}
}

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<string> {
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<string> {
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<string> {
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),
Expand Down Expand Up @@ -2610,5 +2754,8 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
new SolanaGetAllAssetsByOwner(solanaKit),
new Solana3LandCreateSingle(solanaKit),
new SolanaSendTransactionWithPriorityFee(solanaKit),
new SolanaHeliusWebhookTool(solanaKit),
new SolanaGetHeliusWebhookTool(solanaKit),
new SolanaDeleteHeliusWebhookTool(solanaKit),
];
}
132 changes: 132 additions & 0 deletions src/tools/helius_webhook.ts
Original file line number Diff line number Diff line change
@@ -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<HeliusWebhookResponse> {
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<HeliusWebhookIdResponse> {
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<any> {
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}`);
}
}
1 change: 1 addition & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ export * from "./get_assets_by_owner";

export * from "./create_3land_collectible";
export * from "./send_transaction_with_priority";
export * from "./helius_webhook";
12 changes: 12 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,15 @@ 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;
}

0 comments on commit afa3769

Please sign in to comment.