Available Hedera Plugins

July 24, 2026 · View on GitHub

The Hedera Agent Kit provides a comprehensive set of tools organized into plugins, which can be installed alongside the Hedera Agent Kit and used to extend the core functionality of the Hedera Agent Kit SDK. These tools can be used both by the conversational agent and when you are building with the SDK.

The Hedera services built into this agent toolkit are also implemented as plugins, you can see a description of each plugin in the HEDERAPLUGINS.md file, as well as list of the individual tools for each Hedera service that are included in each plugin.

Available Third Party Plugins

See this list of available third party plugins for the Hedera Agent Kit in the Hedera Docs.

Plugin Architecture

The tools are now organized into plugins, each containing a set functionality related to the Hedera service or project they are created for.

Creating a Plugin

All commits for your plugin must be DCO signed. To avoid having pull requests blocked in the future, always include a sign-off.

Plugin Interface

Every plugin must implement the Plugin interface:

export interface Plugin {
  name: string;
  version?: string;
  description?: string;
  tools: (context: Context) => Tool[];
}

Tool Interface

Each tool must implement the Tool interface:

export type Tool = {
  method: string;
  name: string;
  description: string;
  parameters: z.ZodObject<any, any>;
  execute: (client: Client, context: Context, params: any) => Promise<any>;
  // transactionToolOutputParser and untypedQueryOutputParser can be used. If required, define a custom parser
  outputParser?: (rawOutput: string) => { raw: any; humanMessage: string };
};

See packages/core/src/shared/tools.ts for the full definition.

Important

BaseTool is the recommended way to implement tools in v4. It is an abstract class that implements the Tool interface, so it is a fully backward-compatible, non-breaking upgrade. Tools based on the older functional pattern (plain object literals) continue to work, but they do not support hooks and policies.

BaseTool enforces a clean 7-stage lifecycle that lets the hooks and policies system tap in automatically — you never call hooks manually:

[1] preToolExecutionHook        ← hooks/policies
[2] normalizeParams             ← your logic
[3] postParamsNormalizationHook ← hooks/policies
[4] coreAction                  ← your logic (build tx or run query)
[5] postCoreActionHook          ← hooks/policies
[6] secondaryAction             ← your logic (sign/submit tx; skip for queries)
[7] postToolExecutionHook       ← hooks/policies

For a step-by-step migration guide with fully annotated before/after code, see Migrating Custom Tools to BaseTool in the v4 migration guide.

Step-by-Step Guide

Step 1: Create Plugin Directory Structure

  my-custom-plugin/
  ├── index.ts                    # Plugin definition and exports
  ├── tools/
  │   └── my-service/
  │       └── my-tool.ts         # Individual tool implementation

Step 2: Implement Your Tool

Create your tool file (e.g., tools/my-service/my-tool.ts).

Tip

v4 Recommended approach — extend BaseTool.
BaseTool implements the Tool interface, so this is a non-breaking change: your plugin and all framework adapters keep working unchanged. The benefit is that BaseTool-based tools automatically participate in the hooks and policies lifecycle.

import { z } from "zod";
import { Context, BaseTool } from "@hashgraph/hedera-agent-kit";
import { Client } from "@hiero-ledger/sdk";

// Define your parameter schema (same as before)
const myToolParameters = z.object({
  requiredParam: z.string().describe("Description of required parameter"),
  optionalParam: z
    .string()
    .optional()
    .describe("Description of optional parameter"),
});

export const MY_TOOL = "my_tool";

// Extend BaseTool — BaseTool implements Tool, so this is backward-compatible
export class MyTool extends BaseTool {
  method = MY_TOOL;
  name = "My Custom Tool";
  description = `
  This tool performs a specific operation.

  Parameters:
  - requiredParam (string, required): Description
  - optionalParam (string, optional): Description
  `;
  parameters = myToolParameters;

  // Stage 1 - Here preToolExecutionHook() will be called automatically - see the 7-stage lifecycle above.

  // Stage 2 — validate / transform raw params from the LLM
  async normalizeParams(
    params: z.infer<typeof myToolParameters>,
    _context: Context,
    _client: Client,
  ) {
    return params; // pass-through; add validation/transformation here
  }

  // Stage 3 - Here postParamsNormalizationHook() will be called automatically.

  // Stage 4 — core business logic (build a transaction or run a query)
  async coreAction(
    normalisedParams: z.infer<typeof myToolParameters>,
    _context: Context,
    _client: Client,
  ) {
    // Your implementation here
    return `Result for ${normalisedParams.requiredParam}`;
  }

  // Stage 5 - Here postCoreActionHook() will be called automatically.

  // Skip secondary action for non-transaction tools
  async shouldSecondaryAction(_result: any, _context: Context) {
    return false; // return true (default) if you need to sign/submit a transaction
  }

  // Stage 6 — sign/submit the transaction (omit for query-only tools)
  async secondaryAction(result: any, _client: Client, _context: Context) {
    return result; // no-op for non-transaction tools
  }

  // Stage 7 - Here postToolExecutionHook() will be called automatically.
}

// Factory function: return a BaseTool instance (satisfies the Tool interface)
const tool = (_context: Context) => new MyTool();

export default tool;

Note


Stages 1, 3, 5, and 7 must not be defined by the plugin. They are automatically handled by the `BaseTool` implementation and the hooks/policies system. Developers only need to implement the core logic stages (2, 4, and 6).

Legacy v3 pattern (still works, but no hook/policy support)
import { z } from "zod";
import { Context, Tool } from "@hashgraph/hedera-agent-kit";
import { Client } from "@hiero-ledger/sdk";

const myToolParameters = (context: Context = {}) =>
  z.object({
    requiredParam: z.string().describe("Description of required parameter"),
    optionalParam: z
      .string()
      .optional()
      .describe("Description of optional parameter"),
  });

const myToolPrompt = (context: Context = {}) => {
  return `
  This tool performs a specific operation.

  Parameters:
  - requiredParam (string, required): Description
  - optionalParam (string, optional): Description
  `;
};

const myToolExecute = async (
  client: Client,
  context: Context,
  params: z.infer<ReturnType<typeof myToolParameters>>,
) => {
  try {
    const result = await someHederaOperation(params);
    return result;
  } catch (error) {
    if (error instanceof Error) {
      return error.message;
    }
    return "Operation failed";
  }
};

export const MY_TOOL = "my_tool";

// This pattern works in v4 but does NOT support hooks or policies
const tool = (context: Context): Tool => ({
  method: MY_TOOL,
  name: "My Custom Tool",
  description: myToolPrompt(context),
  parameters: myToolParameters(context),
  execute: myToolExecute,
});

export default tool;

Step 3: Create Plugin Definition

Create your plugin index file (index.ts):

  import { Context, Plugin } from '@hashgraph/hedera-agent-kit';
  import myTool, { MY_TOOL } from './tools/my-service/my-tool';

  export const myCustomPlugin: Plugin = {
    name: 'my-custom-plugin',
    version: '1.0.0',
    description: 'A plugin for custom functionality',
    tools: (context: Context) => {
      return [myTool(context)];
    },
  };

  export const myCustomPluginToolNames = {
    MY_TOOL,
  } as const;

  export default { myCustomPlugin, myCustomPluginToolNames };

Step 4: Use Your Plugin

If you are building an external / custom plugin (the common case), there is nothing to register in this repository. Your plugin is a plain object — import it and pass it to the toolkit's plugins: [...] array, exactly as shown in Using Your Custom Plugin below:

import { myCustomPlugin } from './my-custom-plugin';

const toolkit = new HederaLangchainToolkit({
  client,
  configuration: {
    plugins: [myCustomPlugin],
    context: { mode: AgentMode.AUTONOMOUS },
  },
});

Note

Editing this repository's packages/core/src/plugins/index.ts is only needed when you are contributing a plugin into the core SDK via a pull request — not for your own external plugin. See Publish and Register Your Plugin for that flow.

Best Practices

Parameter Validation

  • Use Zod schemas for robust input validation
  • Provide clear descriptions for all parameters
  • Mark required vs optional parameters appropriately

Tool Organization

  • Group related tools by service type
  • Use consistent naming conventions
  • Follow the established directory structure

Transaction Handling

  • Use handleTransaction() to facilitate human-in-the-loop and autonomous execution flows
  • Respect the AgentMode (AUTONOMOUS vs RETURN_BYTES)
  • Implement proper transaction building patterns

Multi-Account Signing

handleTransaction() signs with the operator of whichever Client you pass — the toolkit's client is just the default your tool receives. To sign from a different account than the agent's operator (e.g. a separate treasury or distributor wallet), build a dedicated client inside your tool and pass that one instead:

import { Client, PrivateKey, TransferTransaction } from '@hiero-ledger/sdk';
import { BaseTool, Context, handleTransaction } from '@hashgraph/hedera-agent-kit';

export class TreasuryPayoutTool extends BaseTool {
  // method, name, description, parameters, normalizeParams, coreAction:
  // see the Step-by-Step Guide above. coreAction builds the TransferTransaction.

  // A client whose operator is the treasury account — not the agent's operator.
  // The account ID is a string, but the key must be a PrivateKey instance.
  private treasuryClient = Client.forTestnet().setOperator(
    process.env.TREASURY_ACCOUNT_ID!,
    PrivateKey.fromStringECDSA(process.env.TREASURY_PRIVATE_KEY!), // or fromStringED25519 for an ED25519 key
  );

  async secondaryAction(tx: TransferTransaction, _client: Client, context: Context) {
    // Sign and submit as the treasury account instead of the agent's operator
    return handleTransaction(tx, this.treasuryClient, context);
  }
}

The signer swap above only changes behaviour in AUTONOMOUS mode — see Signer and transport setups for how AgentMode drives signing; RETURN_BYTES flows are unaffected by which client you pass.

The Context object

Context is the shared, per-request state that the toolkit threads through every plugin and tool. You pass it once when you construct the toolkit (configuration.context), and the toolkit hands the same object to Plugin.tools(context) and to every execute(client, context, params) call (and to normalizeParams / coreAction / secondaryAction on BaseTool). Tools read from it — they do not construct it.

FieldTypeDescription
accountIdstring?The connected/operating account. Used to resolve the payer/source account, and required in RETURN_BYTES mode.
accountPublicKeystring?Public key for accountId. Either passed in configuration or fetched from the mirror node based on accountId.
modeAgentMode?AUTONOMOUS (the kit signs and submits) or RETURN_BYTES (the kit returns unsigned transaction bytes). See Signer and transport setups.
mirrornodeServiceIHederaMirrornodeService?Mirror-node client used by query tools for read access.
hooksAbstractHook[]?Hooks and policies run at lifecycle stages. Only BaseTool-based tools participate. See HOOKS_AND_POLICIES.md.

See packages/core/src/shared/configuration.ts for the source definition.

// Reading context inside a tool
async coreAction(params: MyParams, context: Context, _client: Client) {
  const payer = context.accountId; // who the request acts on behalf of
  if (context.mode === AgentMode.RETURN_BYTES) {
    // e.g. skip anything that assumes an operator is available to sign
  }
  // ...
}

Signer and transport setups

Every tool receives a Hedera SDK Client (from @hiero-ledger/sdk) as the first argument to execute (and to coreAction / secondaryAction on BaseTool). How signing and submission happen is driven by context.mode:

  • AUTONOMOUS — the injected Client carries a local operator key set by the host app (client.setOperator(accountId, PrivateKey.fromStringECDSA(...))). When a tool calls handleTransaction(), the transaction is signed and submitted by that operator via tx.execute(client). Both ECDSA and ED25519 operator keys are supported.
  • RETURN_BYTEShandleTransaction() freezes the transaction and returns { bytes } (unsigned) for an external wallet to sign and submit. context.accountId must be set; nothing is signed inside the kit.

Note

There is no built-in WalletConnect / dApp-connector pairing path in this repository. External-wallet signing is expressed through RETURN_BYTES: the kit hands back unsigned transaction bytes and the host app relays them to whatever wallet or signing flow it uses.

Tool Output Parsing

The Hedera Agent Kit tools return a structured JSON output that needs to be parsed to be useful for the agent and the user.

LangChain v0.3 (Classic) In the classic approach, the agent handles the tool output automatically, but you may need to parse it if you are handling tool calls manually.

LangChain v1 (New) In LangChain v1, we use the ResponseParserService to handle tool outputs. This service normalizes the output from both transaction and query tools into a consistent format:

{
  raw: any;          // The raw data returned by the tool (e.g., transaction receipt, query result)
  humanMessage: string; // A human-readable message describing the result
}

This allows you to easily display a user-friendly message while still having access to the raw data for further processing.

Built-in parsers. A tool declares which parser to use via its optional outputParser field. The kit ships two ready-made parsers, both importable from @hashgraph/hedera-agent-kit:

  • transactionToolOutputParser — for transaction tools. Handles both AUTONOMOUS output (a { raw, humanMessage } receipt) and RETURN_BYTES output (an object with a bytes field), and reports a PARSE_ERROR shape for malformed output.
  • untypedQueryOutputParser — a generic pass-through for query tools that already return { raw, humanMessage }.

If you omit outputParser (undefined), the default handling applies — fine for simple non-transaction tools.

Writing a custom parser. When your tool returns a shape the built-ins don't cover (e.g. a third-party API response), provide your own. It receives the tool's stringified output and must return { raw, humanMessage }:

import { Context, BaseTool } from '@hashgraph/hedera-agent-kit';

export class GetHbarPriceTool extends BaseTool {
  // ...method, name, description, parameters...

  // A custom parser: turn the tool's raw JSON output into { raw, humanMessage }.
  outputParser = (rawOutput: string) => {
    try {
      const data = JSON.parse(rawOutput);
      return {
        raw: data, // structured data for programmatic use
        humanMessage: `HBAR price: $${data.priceUsd}`, // user-facing text for the agent
      };
    } catch (error) {
      return {
        raw: { status: 'PARSE_ERROR', originalOutput: rawOutput },
        humanMessage: 'Error: could not parse the price response.',
      };
    }
  };
}

See packages/core/src/shared/utils/default-tool-output-parsing.ts for the reference implementations.

Typed tool results

The { raw, humanMessage } envelope above is intentionally untyped (raw: any). When you want to branch on success vs. failure with compile-time safety, pass it to classifyToolResult, which maps it to a discriminated union. Everything below is importable from @hashgraph/hedera-agent-kit:

import {
  transactionToolOutputParser,
  classifyToolResult,
  TOOL_STATUS, // { SUCCESS, ERROR, PARSE_ERROR } — the known raw.status values
} from '@hashgraph/hedera-agent-kit';

const envelope = transactionToolOutputParser(rawToolOutput);
const result = classifyToolResult<{ transactionId: string; topicId?: string }>(envelope);

switch (result.kind) {
  case 'success':
    // result.data is typed as T; result.transactionId is lifted out when present
    console.log('ok', result.transactionId, result.data.topicId);
    break;
  case 'failure':
    // result.errorCode is the SDK status (e.g. 'INSUFFICIENT_PAYER_BALANCE') or 'ERROR'
    throw new Error(`tool failed (${result.errorCode}): ${result.error}`);
  case 'parse_error':
    throw new Error(`tool output unparseable: ${result.humanMessage}`);
  case 'unknown':
    throw new Error(result.humanMessage);
}

ToolResultStatus<T> (the return type) and ToolRawStatus (the raw.status string union) are also exported for annotating your own code. classifyToolResult is additive and opt-in — the parsers still return { raw, humanMessage } unchanged.

RETURN_BYTES results are typed too. In RETURN_BYTES mode a transaction tool's raw is a ReturnBytesResult (also exported from @hashgraph/hedera-agent-kit): { bytes, status, transactionId, payerAccountId, type, expiresAt, memo }. Its status is always TOOL_STATUS.SUCCESS, so it classifies as kind: 'success'. See MCP.md for the full field table and the wallet handoff.

Using Your Custom Plugin

LangChain v0.3 (Classic)

import { AgentMode } from "@hashgraph/hedera-agent-kit";
import { HederaLangchainToolkit } from "@hashgraph/hedera-agent-kit-langchain";
import {
  myCustomPlugin,
  myCustomPluginToolNames,
} from "./plugins/my-custom-plugin";

const toolkit = new HederaLangchainToolkit({
  client,
  configuration: {
    tools: [myCustomPluginToolNames.MY_TOOL],
    plugins: [myCustomPlugin],
    context: {
      mode: AgentMode.AUTONOMOUS,
    },
  },
});

LangChain v1 (New)

import { AgentMode } from "@hashgraph/hedera-agent-kit";
import { HederaLangchainToolkit, ResponseParserService } from "@hashgraph/hedera-agent-kit-langchain";
import {
  myCustomPlugin,
  myCustomPluginToolNames,
} from "./plugins/my-custom-plugin";

// Initialize toolkit
const toolkit = new HederaLangchainToolkit({
  client,
  configuration: {
    tools: [myCustomPluginToolNames.MY_TOOL],
    plugins: [myCustomPlugin],
    context: {
      mode: AgentMode.AUTONOMOUS,
    },
  },
});

// Initialize response parser
const responseParsingService = new ResponseParserService(toolkit.getTools());

// ... inside your agent loop ...
const response = await agent.invoke({ messages: [/* ... */] });

// Parse tool outputs
const parsedToolData = responseParsingService.parseNewToolMessages(response);
const toolCall = parsedToolData[0]; // assuming only one tool was called

if (toolCall) {
  console.log('Human Message:', toolCall.parsedData.humanMessage);
  console.log('Raw Data:', toolCall.parsedData.raw);
}

Testing Your Plugin (no LLM required)

You do not need an LLM, operator credentials, or a funded account to smoke-test a plugin. Tools are plain objects — instantiate the plugin and call tool.execute() directly:

import assert from 'node:assert';
import { Client } from '@hiero-ledger/sdk';
import { AgentMode, Context } from '@hashgraph/hedera-agent-kit';
import myPlugin from './my-plugin';

const client = Client.forTestnet(); // no operator needed — nothing is signed or submitted
const context: Context = { mode: AgentMode.RETURN_BYTES, accountId: '0.0.1001' };

const tools = myPlugin.tools(context);
const myTool = tools.find(t => t.method === 'my_tool')!;

// Non-transaction tools return their result directly.
// Transaction tools in RETURN_BYTES mode return frozen transaction bytes
// without signing or submitting — a safe dry run.
const result = await myTool.execute(client, context, { requiredParam: 'value' });
assert.ok(result);

See examples/plugin/smoke-test.ts for a complete runnable example that also verifies the hook lifecycle.

Optional audit logging: BaseTool-based tools can log their executions to an HCS topic via HcsAuditTrailHook — add it to context.hooks, no tool changes required. See HOOKS_AND_POLICIES.md.

Troubleshooting: duplicate transitive dependencies (protobufjs & the Hedera SDK chain)

Symptom. Intermittent runtime protobuf errors — type/registry mismatches or instanceof-style failures during transaction serialization — when your plugin depends on the Hedera SDK chain alongside another SDK (for example an asset-tokenization / ATS SDK) that drags in a different protobufjs major.

Cause. The Hedera SDK chain currently spans two protobufjs majors: @hiero-ledger/sdk resolves protobufjs@8.x, while the @hiero-ledger/proto / @hashgraph/proto packages resolve protobufjs@7.x. npm and yarn can legitimately keep both copies in the tree, but protobuf keeps a global type registry that does not tolerate two majors loaded at once — hence the runtime errors. This cannot be fixed from inside a published plugin: overrides/resolutions declared in a library's package.json are ignored for downstream installs — only the consumer/app root can force a single copy.

Fix. Pin a single protobufjs from your app's root package.json:

// npm — package.json
"overrides": { "protobufjs": "8.0.1" }
// yarn / pnpm — package.json
"resolutions": { "protobufjs": "8.0.1" }

@hiero-ledger/sdk resolves protobufjs@8.0.0, which is why that's the major to standardize on, but pin to 8.0.1 (or later) instead of 8.0.0 itself — 8.0.0 and 7.5.4 are both affected by CVE-2026-41242 (critical, arbitrary code execution via crafted protobuf definitions), fixed in 8.0.1/7.5.5. Then re-test. Confirm only one copy remains:

npm ls protobufjs      # or: pnpm why protobufjs / yarn why protobufjs

The same single-copy rule applies to any other "singleton" transitive dependency whose object identity must be shared across the whole tree.

Examples and References

Publish and Register Your Plugin

All commits for your plugin must be DCO signed. To avoid having pull requests blocked in the future, always include a sign-off.

To create a plugin to be used with the Hedera Agent Kit, you will need to create a plugin in your own repository, publish a npm package, and provide a description of the functionality included in that plugin, as well as the required and optional parameters.

Once you have a repository, published npm package, and a README with a description of the functionality included in that plugin in your plugin's repo, as well as the required and optional parameters, you can add it to the Hedera Agent Kit by forking and opening a Pull Request to:

  1. Include the plugin as a bullet point under the Available Third Party Plugin section on this page. Include the name, a brief description, and a link to the repository with the README, as well the URL linked to the published npm package.

  2. Include the same information in the README.md of this repository under the Third Party Plugins section.

  3. All commits for your plugin must be DCO signed, have the names of the tools & core actions exposed by the plugin, and point to the exact version of the npm packages. To avoid having pull requests blocked in the future, always include a sign-off:

NPM: https://www.npmjs.com/package/@bonzofinancelabs/hak-bonzo-plugin
Github repository: https://github.com/Bonzo-Labs/bonzoPlugin
Version: @bonzofinancelabs/hak-bonzo-plugin@1.0.1
Status: Not validated by HAK team, v3-compatible release 

Feel free to also reach out to the Hedera Agent Kit maintainers on Discord or another channel so we can test out your plugin, include it in our docs, and let our community know thorough marketing and community channels.

Please also reach out in the Hedera Discord in the Support > developer-help-desk channel create an Issue in this repository for help building, publishing, and promoting your plugin

Plugin README Template

## Plugin Name

This plugin was built by <?> for the <project, platform, etc>. It was built to enable <who?> to <do what?>

<Include a description of your project and how it can be used with the Hedera Agent Kit.>

### Installation

'''bash
npm install <plugin-name>
'''


### Usage

'''javascript
import { myPlugin } from "<plugin-name>";
'''

'''typescript
import { AgentMode } from '@hashgraph/hedera-agent-kit';
import { coreTokenPlugin, coreAccountPlugin, coreConsensusPlugin } from '@hashgraph/hedera-agent-kit/plugins';
import { HederaLangchainToolkit } from '@hashgraph/hedera-agent-kit-langchain';

const hederaAgentToolkit = new HederaLangchainToolkit({
    client,
    configuration: {
        context: {
            mode: AgentMode.AUTONOMOUS,
        },
        plugins: [
            coreTokenPlugin,
            coreAccountPlugin,
            coreConsensusPlugin,
            myPlugin,
        ],
    },
});
'''

### Functionality

Describe the different tools or individual pieces of functionality included in this plugin, and how to use them.

**Plugin Name**
_High level description of the plugin_

| Tool Name               | Description  | Usage                                                           |
| ----------------------- | ------------ | --------------------------------------------------------------- |
| `YOUR_PLUGIN_TOOL_NAME` | What it does | How to use. Include a list of parameters and their descriptions |