Skip to content

Commit

Permalink
Added Hedera tool integration, and hedera doc
Browse files Browse the repository at this point in the history
  • Loading branch information
MuhannadJam committed Nov 24, 2024
1 parent cb2c42c commit 6337983
Show file tree
Hide file tree
Showing 14 changed files with 1,070 additions and 15 deletions.
29 changes: 29 additions & 0 deletions docs/core_docs/docs/integrations/tools/hedera.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
hide_table_of_contents: true
---

import CodeBlock from "@theme/CodeBlock";

# Hedera Tool

The Hedera Tool gives your agent the ability to create an account, delete an account, transfer
between accounts, and query the balance of an account.

## Setup

To use the Hedera Tool you need to install the following official peer depencency:

```bash npm2yarn
npm install @hashgraph/sdk
```

## Usage

import ToolExample from "@examples/tools/hedera.ts";

<CodeBlock language="typescript">{ToolExample}</CodeBlock>

## Related

- Tool [conceptual guide](/docs/concepts/tools)
- Tool [how-to guides](/docs/how_to/#tools)
83 changes: 83 additions & 0 deletions examples/src/tools/hedera.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
HederaAccountBalance,
HederaTransfer,
HederaCreateAccount,
HederaDeleteAccount,
} from "@langchain/community/tools/hedera";
import { PrivateKey } from "@hashgraph/sdk"
import { StructuredTool } from "@langchain/core/tools";
import { ChatOpenAI } from "@langchain/openai";
import type { ChatPromptTemplate } from "@langchain/core/prompts";
import { createOpenAIFunctionsAgent, AgentExecutor } from "langchain/agents";
import { pull } from "langchain/hub";
import { tr } from "@faker-js/faker";

export async function run() {
const llm = new ChatOpenAI({
model: "gpt-3.5-turbo",
temperature: 0,
});

// Initalize paramters for hedera tools to use
const hederaParams = {
credentials: {
accountId: process.env.HEDERA_ACCOUNT_ID,
privateKey: process.env.HEDERA_PRIVATE_KEY,
},
network: "testnet",
maxTransactionFee: 100,
maxQueryPayment: 50,
};

// Provide the hedera tools to be used
const tools: StructuredTool[] = [
new HederaAccountBalance(hederaParams),
new HederaTransfer(hederaParams),
new HederaCreateAccount(hederaParams),
new HederaDeleteAccount(hederaParams),
];

// Setup the agent to use the hedera tool
const prompt = await pull<ChatPromptTemplate>(
"hwchase17/openai-functions-agent"
);

const agent = await createOpenAIFunctionsAgent({
llm,
tools,
prompt,
});

const agentExecutor = new AgentExecutor({
agent,
tools,
verbose: false,
});

const newAccountPrivateKey = PrivateKey.generateED25519();
const newAccountPublicKey = newAccountPrivateKey.publicKey;

// Create hedera account
const result1 = await agentExecutor.invoke({
input: `Can you create an account with public key ${newAccountPublicKey}`,
});
console.log(result1.output);

// Get the account balance of client operator
const result2 = await agentExecutor.invoke({
input: `What is my account balance`,
});
console.log(result2.output);

// Get the account balance of a specific account
const result3 = await agentExecutor.invoke({
input: `What is the account balance of 0.0.1111111`,
});
console.log(result3.output);

// Transfer tinybars between accounts
const result4 = await agentExecutor.invoke({
input: `Can you transfer 100000000 tinybars from my account to 0.0.1111111`,
});
console.log(result4.output);
}
4 changes: 4 additions & 0 deletions libs/langchain-community/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ tools/google_routes.cjs
tools/google_routes.js
tools/google_routes.d.ts
tools/google_routes.d.cts
tools/hedera.cjs
tools/hedera.js
tools/hedera.d.ts
tools/hedera.d.cts
tools/ifttt.cjs
tools/ifttt.js
tools/ifttt.d.ts
Expand Down
2 changes: 2 additions & 0 deletions libs/langchain-community/langchain.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const config = {
"tools/google_custom_search": "tools/google_custom_search",
"tools/google_places": "tools/google_places",
"tools/google_routes": "tools/google_routes",
"tools/hedera": "tools/hedera/index",
"tools/ifttt": "tools/ifttt",
"tools/searchapi": "tools/searchapi",
"tools/searxng_search": "tools/searxng_search",
Expand Down Expand Up @@ -335,6 +336,7 @@ export const config = {
"tools/discord",
"tools/gmail",
"tools/google_calendar",
"tools/hedera",
"agents/toolkits/aws_sfn",
"callbacks/handlers/llmonitor",
"callbacks/handlers/lunary",
Expand Down
14 changes: 14 additions & 0 deletions libs/langchain-community/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"author": "LangChain",
"license": "MIT",
"dependencies": {
"@hashgraph/sdk": "^2.53.0",
"@langchain/openai": ">=0.2.0 <0.4.0",
"binary-extensions": "^2.2.0",
"expr-eval": "^2.0.2",
Expand Down Expand Up @@ -878,6 +879,15 @@
"import": "./tools/google_routes.js",
"require": "./tools/google_routes.cjs"
},
"./tools/hedera": {
"types": {
"import": "./tools/hedera.d.ts",
"require": "./tools/hedera.d.cts",
"default": "./tools/hedera.d.ts"
},
"import": "./tools/hedera.js",
"require": "./tools/hedera.cjs"
},
"./tools/ifttt": {
"types": {
"import": "./tools/ifttt.d.ts",
Expand Down Expand Up @@ -3154,6 +3164,10 @@
"tools/google_routes.js",
"tools/google_routes.d.ts",
"tools/google_routes.d.cts",
"tools/hedera.cjs",
"tools/hedera.js",
"tools/hedera.d.ts",
"tools/hedera.d.cts",
"tools/ifttt.cjs",
"tools/ifttt.js",
"tools/ifttt.d.ts",
Expand Down
1 change: 1 addition & 0 deletions libs/langchain-community/src/load/import_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const optionalImportEntrypoints: string[] = [
"langchain_community/tools/discord",
"langchain_community/tools/gmail",
"langchain_community/tools/google_calendar",
"langchain_community/tools/hedera",
"langchain_community/agents/toolkits/aws_sfn",
"langchain_community/embeddings/bedrock",
"langchain_community/embeddings/cloudflare_workersai",
Expand Down
87 changes: 87 additions & 0 deletions libs/langchain-community/src/tools/hedera/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Client, Hbar } from "@hashgraph/sdk";
import { z } from "zod";
import { StructuredTool } from "@langchain/core/tools";
import { getEnvironmentVariable } from "@langchain/core/utils/env";

export interface HederaBaseToolParams {
credentials?: {
accountId?: string;
privateKey?: string;
};
network?: string;
maxTransactionFee?: number;
maxQueryPayment?: number;
}

export abstract class HederaBaseTool extends StructuredTool {
private CredentialsSchema = z
.object({
accountId: z
.string()
.min(1)
.default(getEnvironmentVariable("HEDERA_ACCOUNT_ID") ?? ""),
privateKey: z
.string()
.min(1)
.default(getEnvironmentVariable("HEDERA_PRIVATE_KEY") ?? ""),
})
.refine(
(credentials: { accountId: string; privateKey: string }) =>
credentials.accountId !== "" || credentials.privateKey !== "",
{
message:
"Missing HEDERA_ACCOUNT_ID or HEDERA_PRIVATE_KEY to interact with Hedera",
}
);

private HederaBaseToolParamsSchema = z
.object({
credentials: this.CredentialsSchema.default({}),
network: z.enum(["mainnet", "testnet", "previewnet"]).default("testnet"),
maxTransactionFee: z.number().default(100),
maxQueryPayment: z.number().default(50),
})
.default({});

name = "Hedera Tool";

description = "A tool for interacting with the Hedera network.";

protected client: Client;

constructor(fields?: Partial<HederaBaseToolParams>) {
super(...arguments);

const { credentials, network, maxTransactionFee, maxQueryPayment } =
this.HederaBaseToolParamsSchema.parse(fields);

this.client = this.getHederaClient(
network,
credentials.accountId,
credentials.privateKey,
maxTransactionFee,
maxQueryPayment
);
}

private getHederaClient(
network: "mainnet" | "testnet" | "previewnet",
accountId: string,
privateKey: string,
maxTransactionFee: number,
maxQueryPayment: number
): Client {
let client: Client;
if (network === "mainnet") {
client = Client.forMainnet();
} else if (network === "testnet") {
client = Client.forTestnet();
} else {
client = Client.forPreviewnet();
}
client.setOperator(accountId, privateKey);
client.setDefaultMaxTransactionFee(new Hbar(maxTransactionFee));
client.setDefaultMaxQueryPayment(new Hbar(maxQueryPayment));
return client;
}
}
59 changes: 59 additions & 0 deletions libs/langchain-community/src/tools/hedera/create_account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
AccountCreateTransaction,
AccountBalanceQuery,
Hbar,
PublicKey,
} from "@hashgraph/sdk";
import { z } from "zod";
import { HederaBaseTool, HederaBaseToolParams } from "./base.js";

export class HederaCreateAccount extends HederaBaseTool {
name = "hedera_create_account";

schema = z.object({
newAccountPublicKey: z.string(),
initialAmount: z.number().default(1000),
});

description = `A tool for creating a new Hedera account. Takes the new account's public key and the
intial amount to set the starting balance of the account to. If no inital amount is provided
it will set the starting balance to 1000 tinybar.`;

constructor(fields?: HederaBaseToolParams) {
super(fields);
}

async _call(arg: z.output<typeof this.schema>): Promise<string> {
const { newAccountPublicKey, initialAmount } = arg;

try {
const publicKey = PublicKey.fromString(newAccountPublicKey);
const newAccount = await new AccountCreateTransaction()
.setKey(publicKey)
.setInitialBalance(Hbar.fromTinybars(initialAmount))
.execute(this.client);

const getReceipt = await newAccount.getReceipt(this.client);
const newAccountId = getReceipt.accountId;

if (!newAccountId) {
throw new Error("Account creation failed.");
}

const accountBalance = await new AccountBalanceQuery()
.setAccountId(newAccountId)
.execute(this.client);

return `Account created successfully. Account ID: ${newAccountId},
Initial Balance: ${accountBalance.hbars.toString()} tinybars`;
} catch (error) {
const typedError = error as Error;
throw new Error(`Failed to create account: ${typedError.message}`);
}
}
}

export type AccountCreateSchema = {
newAccountPublicKey: string;
initialAmount?: number;
};
52 changes: 52 additions & 0 deletions libs/langchain-community/src/tools/hedera/delete_account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { AccountDeleteTransaction, PrivateKey } from "@hashgraph/sdk";
import { z } from "zod";
import { HederaBaseTool, HederaBaseToolParams } from "./base.js";

export class HederaDeleteAccount extends HederaBaseTool {
name = "hedera_delete_account";

schema = z.object({
accountId: z.string(),
accountPrivateKey: z.string(),
});

description = `A tool for deleting a Hedera account. Takes the account id and the
private key of the account to delete.`;

constructor(fields?: HederaBaseToolParams) {
super(fields);
}

async _call(arg: z.output<typeof this.schema>): Promise<string> {
const { accountId, accountPrivateKey } = arg;

try {
const operatorAccountId = this.client.operatorAccountId?.toString();

if (!operatorAccountId) {
throw new Error("Operator account ID is null.");
}

const transaction = await new AccountDeleteTransaction()
.setAccountId(accountId)
.setTransferAccountId(operatorAccountId)
.freezeWith(this.client);

const privateKey = PrivateKey.fromStringED25519(accountPrivateKey);
const signTx = await transaction.sign(privateKey);
const txResponse = await signTx.execute(this.client);
const receipt = await txResponse.getReceipt(this.client);
const transactionStatus = receipt.status;

return `Deleted account ${accountId} ${transactionStatus}`;
} catch (error) {
const typedError = error as Error;
throw new Error(`Failed to delete account: ${typedError.message}`);
}
}
}

export type AccountDeleteSchema = {
accountId: string;
accountPrivateKey: string;
};
Loading

0 comments on commit 6337983

Please sign in to comment.