@hashgraphonline/hedera-agent-kit
Version:
Build LLM-powered applications that interact with the Hedera Network. Create conversational agents that can understand user requests in natural language and execute Hedera transactions, or build backend systems that leverage AI for on-chain operations.
1 lines • 598 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","sources":["../../src/builders/base-service-builder.ts","../../src/builders/hcs/hcs-builder.ts","../../src/builders/hts/hts-builder.ts","../../src/builders/account/account-builder.ts","../../src/builders/scs/scs-builder.ts","../../src/builders/file/file-builder.ts","../../src/builders/query/query-builder.ts","../../src/langchain/tools/common/base-hedera-transaction-tool.ts","../../src/utils/key-utils.ts","../../src/langchain/tools/hcs/create-topic-tool.ts","../../src/langchain/tools/hcs/delete-topic-tool.ts","../../src/langchain/tools/hcs/submit-message-tool.ts","../../src/langchain/tools/hts/claim-airdrop-tool.ts","../../src/langchain/tools/hts/create-fungible-token-tool.ts","../../src/langchain/tools/hts/create-nft-tool.ts","../../src/langchain/tools/hts/mint-fungible-token-tool.ts","../../src/langchain/tools/hts/mint-nft-tool.ts","../../src/langchain/tools/hts/reject-tokens-tool.ts","../../src/langchain/tools/hts/transfer-tokens-tool.ts","../../src/langchain/tools/hts/dissociate-tokens-tool.ts","../../src/langchain/tools/hts/update-token-tool.ts","../../src/langchain/tools/hts/delete-token-tool.ts","../../src/langchain/tools/hts/pause-token-tool.ts","../../src/langchain/tools/hts/unpause-token-tool.ts","../../src/langchain/tools/hts/freeze-token-account-tool.ts","../../src/langchain/tools/hts/unfreeze-token-account-tool.ts","../../src/langchain/tools/hts/grant-kyc-token-tool.ts","../../src/langchain/tools/hts/revoke-kyc-token-tool.ts","../../src/langchain/tools/hts/wipe-token-account-tool.ts","../../src/langchain/tools/hts/token-fee-schedule-update-tool.ts","../../src/langchain/tools/hts/transfer-nft-tool.ts","../../src/langchain/tools/hts/burn-fungible-token-tool.ts","../../src/langchain/tools/hts/burn-nft-tool.ts","../../src/langchain/tools/account/approve-fungible-token-allowance-tool.ts","../../src/langchain/tools/account/approve-hbar-allowance-tool.ts","../../src/langchain/tools/account/approve-token-nft-allowance-tool.ts","../../src/langchain/tools/account/create-account-tool.ts","../../src/langchain/tools/account/delete-account-tool.ts","../../src/langchain/tools/account/update-account-tool.ts","../../src/langchain/tools/account/transfer-hbar-tool.ts","../../src/langchain/tools/account/revoke-hbar-allowance-tool.ts","../../src/langchain/tools/account/revoke-fungible-token-allowance-tool.ts","../../src/langchain/tools/account/sign-and-execute-scheduled-transaction-tool.ts","../../src/langchain/tools/file/create-file-tool.ts","../../src/langchain/tools/file/append-file-tool.ts","../../src/langchain/tools/file/update-file-tool.ts","../../src/langchain/tools/file/delete-file-tool.ts","../../src/langchain/tools/scs/create-contract-tool.ts","../../src/langchain/tools/scs/update-contract-tool.ts","../../src/langchain/tools/scs/delete-contract-tool.ts","../../src/langchain/tools/scs/execute-contract-tool.ts","../../src/types/model-capability.ts","../../src/utils/model-capability-detector.ts","../../src/langchain/tools/common/base-hedera-query-tool.ts","../../src/langchain/tools/hcs/get-topic-info-tool.ts","../../src/langchain/tools/hcs/get-topic-fees-tool.ts","../../src/langchain/tools/account/get-account-balance-tool.ts","../../src/langchain/tools/account/get-account-public-key-tool.ts","../../src/langchain/tools/account/get-account-info-tool.ts","../../src/langchain/tools/account/get-account-tokens-tool.ts","../../src/langchain/tools/account/get-account-nfts-tool.ts","../../src/langchain/tools/hts/get-token-info-tool.ts","../../src/langchain/tools/hts/validate-nft-ownership-tool.ts","../../src/langchain/tools/network/get-hbar-price-tool.ts","../../src/langchain/tools/transaction/get-transaction-tool.ts","../../src/langchain/tools/account/get-outstanding-airdrops-tool.ts","../../src/langchain/tools/account/get-pending-airdrops-tool.ts","../../src/langchain/tools/network/get-blocks-tool.ts","../../src/langchain/tools/scs/get-contracts-tool.ts","../../src/langchain/tools/scs/get-contract-tool.ts","../../src/langchain/tools/network/get-network-info-tool.ts","../../src/langchain/tools/network/get-network-fees-tool.ts","../../src/langchain/tools/hts/airdrop-token-tool.ts","../../src/langchain/tools/hts/associate-tokens-tool.ts","../../src/langchain/tools/hcs/update-topic-tool.ts","../../src/langchain/tools/hcs/get-topic-messages-tool.ts","../../src/langchain/tools/file/get-file-contents-tool.ts","../../src/langchain/tools/account/delete-nft-spender-allowance-tool.ts","../../src/langchain/tools/account/delete-nft-allowance-all-serials-tool.ts","../../src/langchain/index.ts","../../src/agent/agent.ts","../../src/utils/token-usage-tracker.ts","../../src/signer/abstract-signer.ts","../../src/types/index.ts","../../src/mcp/client.ts","../../src/signer/browser-signer.ts","../../src/agent/conversational-agent.ts","../../src/signer/server-signer.ts","../../src/utils/api-utils.ts"],"sourcesContent":["import {\n AccountId,\n Transaction,\n TransactionId,\n TransactionReceipt,\n ScheduleCreateTransaction,\n ScheduleId,\n Key,\n PublicKey,\n PrivateKey,\n KeyList,\n Long,\n} from '@hashgraph/sdk';\nimport { Buffer } from 'buffer';\nimport { AbstractSigner } from '../signer/abstract-signer';\nimport { Logger, detectKeyTypeFromString } from '@hashgraphonline/standards-sdk';\nimport type { HederaAgentKit } from '../agent/agent';\n\n/**\n * Defines the structure for the result of an execute operation.\n */\nexport interface ExecuteResult {\n success: boolean;\n receipt?: TransactionReceipt;\n scheduleId?: ScheduleId | string;\n error?: string;\n transactionId?: string | undefined;\n}\n\n/**\n * BaseServiceBuilder provides common functionality for service-specific builders.\n * It manages the current transaction being built and offers common execution and byte generation methods.\n */\nexport abstract class BaseServiceBuilder {\n protected currentTransaction: Transaction | null = null;\n protected logger: Logger;\n protected kit: HederaAgentKit;\n protected notes: string[] = [];\n\n /**\n * @param {HederaAgentKit} kit - The HederaAgentKit instance\n */\n constructor(protected readonly hederaKit: HederaAgentKit) {\n this.kit = hederaKit;\n\n const shouldDisableLogs = process.env.DISABLE_LOGS === 'true';\n\n this.logger = new Logger({\n module: 'ServiceBuilder',\n level: shouldDisableLogs ? 'silent' : 'info',\n silent: shouldDisableLogs,\n });\n }\n\n /**\n * Helper method to get the effective sender account to use for transactions.\n * In user-centric contexts, this will be the user's account. Otherwise, it falls back to the signer's account.\n * @returns {AccountId} The account ID to use as sender\n */\n protected getEffectiveSenderAccountId(): AccountId {\n if (this.kit.userAccountId) {\n return AccountId.fromString(this.kit.userAccountId);\n }\n return this.kit.signer.getAccountId();\n }\n\n /**\n * Helper method to determine if a transaction is a user-initiated transfer.\n * Used for properly constructing transfer arrays.\n * @param {boolean} isUserInitiated Whether this is a user-initiated transfer\n * @returns {AccountId} The account that should be used as the sender\n */\n protected getTransferSourceAccount(\n isUserInitiated: boolean = true\n ): AccountId {\n if (isUserInitiated && this.kit.userAccountId) {\n return AccountId.fromString(this.kit.userAccountId);\n }\n return this.kit.signer.getAccountId();\n }\n\n /**\n * @param {string} memo\n * @returns {this}\n * @throws {Error}\n */\n public setTransactionMemo(memo: string): this {\n if (!this.currentTransaction) {\n throw new Error(\n 'No transaction is currently being built. Call a specific transaction method first (e.g., createTopic).'\n );\n }\n this.currentTransaction.setTransactionMemo(memo);\n return this;\n }\n\n /**\n * @param {TransactionId} transactionId\n * @returns {this}\n * @throws {Error}\n */\n public setTransactionId(transactionId: TransactionId): this {\n if (!this.currentTransaction) {\n throw new Error(\n 'No transaction is currently being built. Call a specific transaction method first.'\n );\n }\n this.currentTransaction.setTransactionId(transactionId);\n return this;\n }\n\n /**\n * @param {AccountId[]} nodeAccountIds\n * @returns {this}\n * @throws {Error}\n */\n public setNodeAccountIds(nodeAccountIds: AccountId[]): this {\n if (!this.currentTransaction) {\n throw new Error(\n 'No transaction is currently being built. Call a specific transaction method first.'\n );\n }\n this.currentTransaction.setNodeAccountIds(nodeAccountIds);\n return this;\n }\n\n /**\n * @param {object} [options]\n * @param {boolean} [options.schedule]\n * @param {string} [options.scheduleMemo]\n * @param {string | AccountId} [options.schedulePayerAccountId]\n * @returns {Promise<ExecuteResult>}\n * @throws {Error}\n */\n public async execute(options?: {\n schedule?: boolean;\n scheduleMemo?: string;\n schedulePayerAccountId?: string | AccountId;\n }): Promise<ExecuteResult> {\n const innerTx = this.currentTransaction;\n\n if (!innerTx) {\n return { success: false, error: 'No transaction to execute.' };\n }\n\n let transactionToExecute: Transaction = innerTx;\n let originalTransactionIdForReporting = innerTx.transactionId?.toString();\n\n if (options?.schedule) {\n if (!innerTx.isFrozen() && this.kit.userAccountId) {\n innerTx.setTransactionId(\n TransactionId.generate(this.kit.userAccountId)\n );\n }\n\n const scheduleCreateTx =\n new ScheduleCreateTransaction().setScheduledTransaction(innerTx);\n\n if (options.scheduleMemo) {\n scheduleCreateTx.setScheduleMemo(options.scheduleMemo);\n }\n\n if (this.kit.userAccountId) {\n scheduleCreateTx.setPayerAccountId(\n AccountId.fromString(this.kit.userAccountId)\n );\n } else if (options.schedulePayerAccountId) {\n const payerForScheduleCreate =\n typeof options.schedulePayerAccountId === 'string'\n ? AccountId.fromString(options.schedulePayerAccountId)\n : options.schedulePayerAccountId;\n scheduleCreateTx.setPayerAccountId(payerForScheduleCreate);\n } else {\n scheduleCreateTx.setPayerAccountId(this.kit.signer.getAccountId());\n this.addNote(\n `Your agent account (${this.kit.signer\n .getAccountId()\n .toString()}) will pay the fee to create this schedule.`\n );\n }\n\n const agentOperator = await this.kit.getOperator();\n const adminKeyList = new KeyList().setThreshold(1);\n if (agentOperator.publicKey) {\n adminKeyList.push(agentOperator.publicKey);\n this.addNote(\n `The schedule admin key allows both your agent and user (${this.kit.userAccountId}) to manage the schedule.`\n );\n }\n\n if (this.kit.userAccountId) {\n try {\n const mirrorNode = this.kit.mirrorNode;\n const userAccountInfo = await mirrorNode.requestAccount(\n this.kit.userAccountId\n );\n if (userAccountInfo?.key?.key) {\n adminKeyList.push(PublicKey.fromString(userAccountInfo.key.key));\n this.addNote(\n `The schedule admin key allows both your agent and user (${this.kit.userAccountId}) to manage the schedule.`\n );\n } else {\n this.addNote(\n `The schedule admin key is set to your agent. User (${this.kit.userAccountId}) key not found or not a single key.`\n );\n }\n } catch (e) {\n this.logger.warn(\n `Failed to get user key for schedule admin key for ${\n this.kit.userAccountId\n }: ${(e as Error).message}`\n );\n this.addNote(\n `The schedule admin key is set to your agent. Could not retrieve user (${this.kit.userAccountId}) key.`\n );\n }\n }\n if (Array.from(adminKeyList).length > 0) {\n scheduleCreateTx.setAdminKey(adminKeyList);\n } else {\n this.addNote(\n 'No admin key could be set for the schedule (agent key missing and user key not found/retrieved).'\n );\n }\n\n transactionToExecute = scheduleCreateTx;\n }\n\n try {\n if (\n !transactionToExecute.isFrozen() &&\n !transactionToExecute.transactionId\n ) {\n await transactionToExecute.freezeWith(this.kit.client);\n }\n if (options?.schedule && transactionToExecute.transactionId) {\n originalTransactionIdForReporting =\n transactionToExecute.transactionId.toString();\n }\n\n const receipt = await this.kit.signer.signAndExecuteTransaction(\n transactionToExecute\n );\n const finalTransactionId =\n transactionToExecute.transactionId?.toString() ||\n originalTransactionIdForReporting;\n\n const result: ExecuteResult = {\n success: true,\n receipt: receipt,\n transactionId: finalTransactionId,\n };\n\n if (options?.schedule && receipt.scheduleId) {\n result.scheduleId = receipt.scheduleId.toString();\n }\n return result;\n } catch (e: unknown) {\n console.log('error is:', e);\n const error = e as Error;\n this.logger.error(\n `Transaction execution failed: ${error.message}`,\n error\n );\n const errorResult: ExecuteResult = {\n success: false,\n error:\n error.message ||\n 'An unknown error occurred during transaction execution.',\n transactionId: originalTransactionIdForReporting,\n };\n return errorResult;\n }\n }\n\n /**\n * @param {object} [options]\n * @param {boolean} [options.schedule]\n * @param {string} [options.scheduleMemo]\n * @param {string | AccountId} [options.schedulePayerAccountId]\n * @param {Key} [options.scheduleAdminKey]\n * @returns {Promise<string>}\n * @throws {Error}\n */\n public async getTransactionBytes(options?: {\n schedule?: boolean;\n scheduleMemo?: string;\n schedulePayerAccountId?: string | AccountId;\n scheduleAdminKey?: Key;\n }): Promise<string> {\n if (!this.currentTransaction) {\n throw new Error(\n 'No transaction to get bytes for. Call a specific transaction method first.'\n );\n }\n\n let transactionForBytes: Transaction = this.currentTransaction;\n\n if (options?.schedule) {\n const scheduleCreateTx =\n new ScheduleCreateTransaction().setScheduledTransaction(\n this.currentTransaction\n );\n\n if (options.scheduleMemo) {\n scheduleCreateTx.setScheduleMemo(options.scheduleMemo);\n }\n if (options.schedulePayerAccountId) {\n const payerAccountId =\n typeof options.schedulePayerAccountId === 'string'\n ? AccountId.fromString(options.schedulePayerAccountId)\n : options.schedulePayerAccountId;\n scheduleCreateTx.setPayerAccountId(payerAccountId);\n }\n if (options.scheduleAdminKey) {\n scheduleCreateTx.setAdminKey(options.scheduleAdminKey);\n }\n transactionForBytes = scheduleCreateTx;\n }\n\n return Buffer.from(transactionForBytes.toBytes()).toString('base64');\n }\n\n /**\n * Executes the current transaction using a provided signer.\n * This is useful if the transaction needs to be signed and paid for by a different account\n * than the one initially configured with the HederaAgentKit/builder instance.\n * Note: The transaction should ideally not be frozen, or if frozen, its transactionId\n * should be compatible with the newSigner's accountId as the payer.\n * @param {AbstractSigner} newSigner - The signer to use for this specific execution.\n * @returns {Promise<ExecuteResult>}\n * @throws {Error}\n */\n public async executeWithSigner(\n newSigner: AbstractSigner\n ): Promise<ExecuteResult> {\n if (!this.currentTransaction) {\n return {\n success: false,\n error:\n 'No transaction to execute. Call a specific transaction method first.',\n };\n }\n\n let transactionToExecute = this.currentTransaction;\n\n if (transactionToExecute.isFrozen()) {\n throw new Error(\n 'Transaction is frozen, try to call the builder method again and then executeWithSigner.'\n );\n }\n\n try {\n const receipt = await newSigner.signAndExecuteTransaction(\n transactionToExecute\n );\n const transactionId = transactionToExecute.transactionId?.toString();\n return {\n success: true,\n receipt: receipt,\n transactionId: transactionId,\n };\n } catch (e: unknown) {\n const error = e as Error;\n this.logger.error(\n `Transaction execution with new signer failed: ${error.message}`\n );\n return {\n success: false,\n error:\n error.message ||\n 'An unknown error occurred during transaction execution with new signer.',\n };\n }\n }\n\n /**\n * @param {Transaction} transaction\n */\n protected setCurrentTransaction(transaction: Transaction): void {\n this.currentTransaction = transaction;\n }\n\n /**\n * Retrieves the current transaction object being built.\n * @returns {Transaction | null} The current transaction or null.\n */\n public getCurrentTransaction(): Transaction | null {\n return this.currentTransaction;\n }\n\n public addNote(note: string): void {\n this.notes.push(note);\n }\n\n public getNotes(): string[] {\n return this.notes;\n }\n\n public clearNotes(): void {\n this.notes = [];\n }\n\n protected async parseKey(\n keyInput?: string | PublicKey | Key | null\n ): Promise<Key | undefined> {\n if (keyInput === undefined || keyInput === null) {\n return undefined;\n }\n if (\n typeof keyInput === 'object' &&\n ('_key' in keyInput ||\n keyInput instanceof PublicKey ||\n keyInput instanceof PrivateKey ||\n keyInput instanceof KeyList)\n ) {\n return keyInput as Key;\n }\n if (typeof keyInput === 'string') {\n if (keyInput.toLowerCase() === 'current_signer') {\n if (this.kit.signer) {\n this.logger.info(\n `[BaseServiceBuilder.parseKey] Substituting \"current_signer\" with signer's public key.`\n );\n return await this.kit.signer.getPublicKey();\n } else {\n throw new Error(\n '[BaseServiceBuilder.parseKey] Signer is not available to resolve \"current_signer\".'\n );\n }\n }\n try {\n return PublicKey.fromString(keyInput);\n } catch (e: unknown) {\n const error = e as Error;\n try {\n this.logger.warn(\n '[BaseServiceBuilder.parseKey] Attempting to parse key string as PrivateKey to derive PublicKey. This is generally not recommended for public-facing keys.',\n { error: error.message }\n );\n const keyDetection = detectKeyTypeFromString(keyInput);\n return keyDetection.privateKey;\n } catch (e2: unknown) {\n const error2 = e2 as Error;\n this.logger.error(\n `[BaseServiceBuilder.parseKey] Failed to parse key string as PublicKey or PrivateKey: ${keyInput.substring(\n 0,\n 30\n )}...`,\n { error: error2.message }\n );\n throw new Error(\n `[BaseServiceBuilder.parseKey] Invalid key string format: ${keyInput.substring(\n 0,\n 30\n )}...`\n );\n }\n }\n }\n this.logger.warn(\n `[BaseServiceBuilder.parseKey] Received an object that is not an SDK Key instance or a recognized string format: ${JSON.stringify(\n keyInput\n )}`\n );\n return undefined;\n }\n\n protected parseAmount(amount?: number | string | Long | BigNumber): Long {\n if (amount === undefined) {\n return Long.fromNumber(0);\n }\n if (typeof amount === 'number') {\n return Long.fromNumber(amount);\n }\n if (typeof amount === 'string') {\n return Long.fromString(amount);\n }\n if (amount instanceof BigNumber) {\n return Long.fromString(amount.toString());\n }\n return amount;\n }\n}\n","import {\n TopicCreateTransaction,\n TopicMessageSubmitTransaction,\n TopicDeleteTransaction,\n TopicUpdateTransaction,\n TopicId,\n PublicKey,\n AccountId,\n KeyList,\n} from '@hashgraph/sdk';\nimport { Buffer } from 'buffer';\nimport {\n CreateTopicParams,\n SubmitMessageParams,\n DeleteTopicParams,\n UpdateTopicParams,\n} from '../../types';\nimport { BaseServiceBuilder } from '../base-service-builder';\nimport { HederaAgentKit } from '../../agent/agent';\n\nconst DEFAULT_AUTORENEW_PERIOD_SECONDS = 7776000;\nconst MAX_SINGLE_MESSAGE_BYTES = 1000;\n\n/**\n * HcsBuilder facilitates the construction and execution of Hedera Consensus Service (HCS) transactions.\n * It extends BaseServiceBuilder to provide common transaction execution and byte generation methods.\n */\nexport class HcsBuilder extends BaseServiceBuilder {\n constructor(hederaKit: HederaAgentKit) {\n super(hederaKit);\n }\n\n /**\n * @param {CreateTopicParams} params\n * @returns {Promise<this>}\n */\n public async createTopic(params: CreateTopicParams): Promise<this> {\n this.clearNotes();\n const transaction = new TopicCreateTransaction();\n\n if (params.memo) {\n transaction.setTopicMemo(params.memo);\n }\n\n if (params.adminKey) {\n const parsedAdminKey = await this.parseKey(params.adminKey);\n if (parsedAdminKey) {\n transaction.setAdminKey(parsedAdminKey);\n }\n }\n\n if (params.feeScheduleKey) {\n const parsedFeeScheduleKey = await this.parseKey(params.feeScheduleKey);\n if (parsedFeeScheduleKey) {\n transaction.setFeeScheduleKey(parsedFeeScheduleKey);\n }\n }\n\n if (params.submitKey) {\n const parsedSubmitKey = await this.parseKey(params.submitKey);\n if (parsedSubmitKey) {\n transaction.setSubmitKey(parsedSubmitKey);\n }\n }\n\n if (params.autoRenewPeriod) {\n transaction.setAutoRenewPeriod(params.autoRenewPeriod);\n } else {\n transaction.setAutoRenewPeriod(DEFAULT_AUTORENEW_PERIOD_SECONDS);\n this.addNote(`Default auto-renew period of ${DEFAULT_AUTORENEW_PERIOD_SECONDS} seconds applied for topic.`);\n }\n\n if (params.autoRenewAccountId) {\n transaction.setAutoRenewAccountId(params.autoRenewAccountId);\n } else {\n this.logger.warn(\n 'MirrorNode client is not available on the signer, cannot set fee exempt keys by account ID for createTopic.'\n );\n this.addNote('Could not set fee exempt accounts for topic creation: MirrorNode client not available on signer.');\n }\n\n if (params.customFees && params.customFees.length > 0) {\n transaction.setCustomFees(params.customFees);\n }\n\n if (params.exemptAccountIds && params.exemptAccountIds.length > 0) {\n if (!this.kit.signer.mirrorNode) {\n this.logger.warn(\n 'MirrorNode client is not available on the signer, cannot set fee exempt keys by account ID for createTopic.'\n );\n this.addNote('Could not attempt to set fee exempt accounts for topic creation: MirrorNode client not available on signer.');\n } else {\n try {\n const publicKeys: PublicKey[] = [];\n for (const accountIdStr of params.exemptAccountIds) {\n const publicKey = await this.kit.signer.mirrorNode.getPublicKey(\n accountIdStr\n );\n publicKeys.push(publicKey);\n }\n if (publicKeys.length > 0) {\n this.logger.warn(\n 'TopicCreateTransaction does not support setFeeExemptKeys. This parameter will be ignored for topic creation.'\n );\n }\n } catch (e: unknown) {\n const error = e as Error;\n this.logger.error(\n `Failed to process exemptAccountIds for createTopic: ${error.message}`\n );\n this.addNote(`Error processing fee exempt accounts for topic creation: ${error.message}. They may not be set.`);\n }\n }\n }\n\n this.setCurrentTransaction(transaction);\n return this;\n }\n\n /**\n * Configures the builder to submit a message to an HCS topic.\n * The transaction will be signed by the primary signer (operator).\n * If the target topic has a specific submit key and it is different from the operator's key,\n * the transaction may fail at the network level unless the transaction bytes are retrieved\n * using `getTransactionBytes()` and signed externally by the required submit key(s) before submission.\n * The `params.submitKey` (if provided in `SubmitMessageParams`) is not directly used to sign\n * within this builder method for `TopicMessageSubmitTransaction` as the transaction type itself\n * does not have a field for an overriding submitter's public key; authorization is based on the topic's configuration.\n * @param {SubmitMessageParams} params - Parameters for submitting the message.\n * @returns {this} The HcsBuilder instance for fluent chaining.\n */\n public submitMessageToTopic(params: SubmitMessageParams): this {\n const topicId =\n typeof params.topicId === 'string'\n ? TopicId.fromString(params.topicId)\n : params.topicId;\n const messageContents = params.message;\n const messageBytesLength =\n typeof messageContents === 'string'\n ? Buffer.from(messageContents, 'utf8').length\n : messageContents.length;\n\n if (messageBytesLength > MAX_SINGLE_MESSAGE_BYTES) {\n this.logger.warn(\n `HcsBuilder: Message size (${messageBytesLength} bytes) exceeds recommended single transaction limit (${MAX_SINGLE_MESSAGE_BYTES} bytes). The transaction will likely fail if not accepted by the network.`\n );\n }\n\n let transaction = new TopicMessageSubmitTransaction()\n .setTopicId(topicId)\n .setMessage(messageContents);\n\n if (params.maxChunks) {\n transaction.setMaxChunks(params.maxChunks);\n }\n\n if (params.chunkSize) {\n transaction.setChunkSize(params.chunkSize);\n }\n\n this.setCurrentTransaction(transaction);\n return this;\n }\n\n /**\n * @param {DeleteTopicParams} params\n * @returns {this}\n * @throws {Error}\n */\n public deleteTopic(params: DeleteTopicParams): this {\n if (params.topicId === undefined) {\n throw new Error('Topic ID is required to delete a topic.');\n }\n const transaction = new TopicDeleteTransaction().setTopicId(params.topicId);\n this.setCurrentTransaction(transaction);\n return this;\n }\n\n /**\n * Configures the builder to update an HCS topic.\n * @param {UpdateTopicParams} params - Parameters for updating the topic.\n * @returns {Promise<this>} The HcsBuilder instance for fluent chaining.\n * @throws {Error} If topicId is not provided.\n */\n public async updateTopic(params: UpdateTopicParams): Promise<this> {\n this.clearNotes();\n if (!params.topicId) {\n throw new Error('Topic ID is required to update a topic.');\n }\n const transaction = new TopicUpdateTransaction().setTopicId(params.topicId);\n\n if (Object.prototype.hasOwnProperty.call(params, 'memo')) {\n transaction.setTopicMemo(params.memo === null ? '' : params.memo!);\n }\n\n if (Object.prototype.hasOwnProperty.call(params, 'adminKey')) {\n if (params.adminKey === null) {\n transaction.setAdminKey(new KeyList());\n } else if (params.adminKey) {\n const parsedAdminKey = await this.parseKey(params.adminKey);\n if (parsedAdminKey) transaction.setAdminKey(parsedAdminKey);\n }\n }\n\n if (Object.prototype.hasOwnProperty.call(params, 'submitKey')) {\n if (params.submitKey === null) {\n transaction.setSubmitKey(new KeyList());\n } else if (params.submitKey) {\n const parsedSubmitKey = await this.parseKey(params.submitKey);\n if (parsedSubmitKey) transaction.setSubmitKey(parsedSubmitKey);\n }\n }\n\n if (params.autoRenewPeriod) {\n transaction.setAutoRenewPeriod(params.autoRenewPeriod);\n }\n\n if (Object.prototype.hasOwnProperty.call(params, 'autoRenewAccountId')) {\n if (params.autoRenewAccountId === null) {\n transaction.setAutoRenewAccountId(AccountId.fromString('0.0.0'));\n } else if (params.autoRenewAccountId) {\n transaction.setAutoRenewAccountId(\n params.autoRenewAccountId as string | AccountId\n );\n }\n }\n\n if (Object.prototype.hasOwnProperty.call(params, 'exemptAccountIds')) {\n if (\n params.exemptAccountIds &&\n params.exemptAccountIds.length > 0 &&\n !this.kit.signer.mirrorNode \n ) {\n this.logger.warn(\n 'MirrorNode client is not available on the signer, cannot set fee exempt keys by account ID for updateTopic if account IDs are provided and not empty.'\n );\n this.addNote('Could not set fee exempt accounts for topic update: MirrorNode client not available on signer.');\n } else if (params.exemptAccountIds) {\n if (params.exemptAccountIds.length === 0) {\n transaction.setFeeExemptKeys([]);\n } else {\n try {\n const publicKeys: PublicKey[] = [];\n for (const accountIdStr of params.exemptAccountIds) {\n const publicKey = await this.kit.signer.mirrorNode.getPublicKey(\n accountIdStr\n );\n publicKeys.push(publicKey);\n }\n if (publicKeys.length > 0) {\n transaction.setFeeExemptKeys(publicKeys);\n } else {\n this.addNote('Fee exempt accounts were provided, but no valid public keys could be resolved for them.');\n }\n } catch (e: unknown) {\n const error = e as Error;\n this.logger.error(\n `Failed to process exemptAccountIds for updateTopic: ${error.message}`\n );\n this.addNote(`Error processing fee exempt accounts for topic update: ${error.message}. They may not be set.`);\n }\n }\n }\n }\n\n this.setCurrentTransaction(transaction);\n return this;\n }\n}\n","import {\n AccountId,\n CustomFee,\n TokenCreateTransaction,\n TokenSupplyType,\n TokenType,\n TokenId,\n Long,\n TokenMintTransaction,\n TokenBurnTransaction,\n TransferTransaction,\n TokenAssociateTransaction,\n TokenDissociateTransaction,\n Hbar,\n TokenWipeTransaction,\n TokenFreezeTransaction,\n TokenUnfreezeTransaction,\n TokenGrantKycTransaction,\n TokenRevokeKycTransaction,\n TokenPauseTransaction,\n TokenUnpauseTransaction,\n TokenUpdateTransaction,\n TokenDeleteTransaction,\n TokenFeeScheduleUpdateTransaction,\n NftId,\n TokenAirdropTransaction,\n TokenClaimAirdropTransaction,\n TokenCancelAirdropTransaction,\n TokenRejectTransaction,\n CustomFixedFee,\n CustomFractionalFee,\n CustomRoyaltyFee,\n KeyList,\n PublicKey,\n FeeAssessmentMethod,\n} from '@hashgraph/sdk';\n\nimport {\n FTCreateParams,\n NFTCreateParams,\n MintFTParams,\n BurnFTParams,\n MintNFTParams,\n BurnNFTParams,\n TransferNFTParams,\n AssociateTokensParams,\n DissociateTokensParams,\n TransferTokensParams,\n FungibleTokenTransferSpec,\n WipeTokenAccountParams,\n FreezeTokenAccountParams,\n UnfreezeTokenAccountParams,\n GrantKycTokenParams,\n RevokeKycTokenParams,\n PauseTokenParams,\n UnpauseTokenParams,\n UpdateTokenParams,\n DeleteTokenParams,\n TokenFeeScheduleUpdateParams,\n AirdropTokenParams,\n ClaimAirdropParams,\n CancelAirdropParams,\n RejectAirdropParams,\n} from '../../types';\nimport { BaseServiceBuilder } from '../base-service-builder';\nimport { Buffer } from 'buffer';\nimport { HederaAgentKit } from '../../agent/agent';\nimport { Logger } from '@hashgraphonline/standards-sdk';\nimport { CustomFeeInputData } from '../../langchain/tools/hts/create-fungible-token-tool';\nimport { AgentOperationalMode } from '../../types';\n\nconst DEFAULT_AUTORENEW_PERIOD_SECONDS = 7776000;\n\nfunction generateDefaultSymbol(tokenName: string): string {\n if (!tokenName) {\n return 'TOKEN';\n }\n const symbol = tokenName\n .replace(/[^a-zA-Z0-9]/g, '')\n .substring(0, 5)\n .toUpperCase();\n if (symbol) {\n return symbol;\n }\n return 'TOKEN';\n}\n\nfunction mapToSdkCustomFees(\n fees: CustomFeeInputData[],\n parseAmountFn: (amount?: number | string | Long | BigNumber) => Long,\n logger: Logger,\n kitUserAccountId?: string,\n kitOperationalMode?: AgentOperationalMode,\n addNoteFn?: (note: string) => void\n): CustomFee[] {\n if (!fees || fees.length === 0) {\n return [];\n }\n\n return fees.map((feeData: CustomFeeInputData) => {\n let feeCollectorStringToParse = feeData.feeCollectorAccountId;\n\n if (\n !feeCollectorStringToParse &&\n kitUserAccountId &&\n kitOperationalMode === 'provideBytes'\n ) {\n feeCollectorStringToParse = kitUserAccountId;\n if (addNoteFn) {\n let feeTypeForNote = 'custom';\n if (feeData.type === 'FIXED' || feeData.type === 'FIXED_FEE') {\n feeTypeForNote = 'fixed';\n } else if (\n feeData.type === 'FRACTIONAL' ||\n feeData.type === 'FRACTIONAL_FEE'\n ) {\n feeTypeForNote = 'fractional';\n } else if (\n feeData.type === 'ROYALTY' ||\n feeData.type === 'ROYALTY_FEE'\n ) {\n feeTypeForNote = 'royalty';\n }\n addNoteFn(\n `Fee collector for a ${feeTypeForNote} fee was defaulted to your account (${kitUserAccountId}).`\n );\n }\n }\n\n if (!feeCollectorStringToParse) {\n throw new Error(\n `Fee collector account ID is required for custom fee type ${feeData.type} but was not provided or defaulted.`\n );\n }\n\n let feeCollectorSdkAccountId: AccountId;\n try {\n feeCollectorSdkAccountId = AccountId.fromString(\n feeCollectorStringToParse\n );\n } catch (e) {\n logger.error(\n `Invalid feeCollectorAccountId: ${feeCollectorStringToParse}`,\n e\n );\n throw new Error(\n `Invalid feeCollectorAccountId: ${feeCollectorStringToParse}`\n );\n }\n\n switch (feeData.type) {\n case 'FIXED':\n case 'FIXED_FEE': {\n const fixedFee = new CustomFixedFee()\n .setFeeCollectorAccountId(feeCollectorSdkAccountId)\n .setAmount(parseAmountFn(feeData.amount));\n if (feeData.denominatingTokenId) {\n try {\n fixedFee.setDenominatingTokenId(\n TokenId.fromString(feeData.denominatingTokenId)\n );\n } catch (e) {\n logger.error(\n `Invalid denominatingTokenId for fixed fee: ${feeData.denominatingTokenId}`,\n e\n );\n throw new Error(\n `Invalid denominatingTokenId for fixed fee: ${feeData.denominatingTokenId}`\n );\n }\n }\n return fixedFee;\n }\n case 'FRACTIONAL':\n case 'FRACTIONAL_FEE': {\n const fractionalFee = new CustomFractionalFee()\n .setFeeCollectorAccountId(feeCollectorSdkAccountId)\n .setNumerator(parseAmountFn(feeData.numerator).toNumber())\n .setDenominator(parseAmountFn(feeData.denominator).toNumber());\n if (feeData.minAmount !== undefined) {\n fractionalFee.setMin(parseAmountFn(feeData.minAmount));\n }\n if (feeData.maxAmount !== undefined) {\n fractionalFee.setMax(parseAmountFn(feeData.maxAmount));\n }\n const fractionalFeeData = feeData as Extract<\n CustomFeeInputData,\n { type: 'FRACTIONAL' | 'FRACTIONAL_FEE' }\n >;\n if (fractionalFeeData.assessmentMethodInclusive !== undefined) {\n if (fractionalFeeData.assessmentMethodInclusive) {\n fractionalFee.setAssessmentMethod(FeeAssessmentMethod.Inclusive);\n } else {\n fractionalFee.setAssessmentMethod(FeeAssessmentMethod.Exclusive);\n }\n }\n return fractionalFee;\n }\n case 'ROYALTY':\n case 'ROYALTY_FEE': {\n const royaltyFee = new CustomRoyaltyFee()\n .setFeeCollectorAccountId(feeCollectorSdkAccountId)\n .setNumerator(parseAmountFn(feeData.numerator).toNumber())\n .setDenominator(parseAmountFn(feeData.denominator).toNumber());\n const royaltyFeeData = feeData as Extract<\n CustomFeeInputData,\n { type: 'ROYALTY' | 'ROYALTY_FEE' }\n >;\n if (royaltyFeeData.fallbackFee) {\n let fallbackFeeCollectorStringToParse =\n royaltyFeeData.fallbackFee.feeCollectorAccountId;\n if (\n !fallbackFeeCollectorStringToParse &&\n kitUserAccountId &&\n kitOperationalMode === 'provideBytes'\n ) {\n fallbackFeeCollectorStringToParse = kitUserAccountId;\n if (addNoteFn) {\n addNoteFn(\n `Fallback fee collector for a royalty fee was also defaulted to your account (${kitUserAccountId}).`\n );\n }\n }\n if (!fallbackFeeCollectorStringToParse) {\n throw new Error(\n `Fallback fee collector account ID is required for royalty fee but was not provided or defaulted.`\n );\n }\n let fallbackFeeCollectorSdkAccountId: AccountId;\n try {\n fallbackFeeCollectorSdkAccountId = AccountId.fromString(\n fallbackFeeCollectorStringToParse\n );\n } catch (e) {\n logger.error(\n `Invalid feeCollectorAccountId in fallbackFee: ${fallbackFeeCollectorStringToParse}`,\n e\n );\n throw new Error(\n `Invalid feeCollectorAccountId in fallbackFee: ${fallbackFeeCollectorStringToParse}`\n );\n }\n const fallback = new CustomFixedFee()\n .setFeeCollectorAccountId(fallbackFeeCollectorSdkAccountId)\n .setAmount(parseAmountFn(royaltyFeeData.fallbackFee.amount));\n if (royaltyFeeData.fallbackFee.denominatingTokenId) {\n try {\n fallback.setDenominatingTokenId(\n TokenId.fromString(\n royaltyFeeData.fallbackFee.denominatingTokenId\n )\n );\n } catch (e) {\n logger.error(\n `Invalid denominatingTokenId in fallbackFee: ${royaltyFeeData.fallbackFee.denominatingTokenId}`,\n e\n );\n throw new Error(\n `Invalid denominatingTokenId in fallbackFee: ${royaltyFeeData.fallbackFee.denominatingTokenId}`\n );\n }\n }\n royaltyFee.setFallbackFee(fallback);\n }\n return royaltyFee;\n }\n default: {\n const exhaustiveCheck: never = feeData;\n logger.warn(\n `Unsupported custom fee type encountered: ${\n (exhaustiveCheck as any).type\n }`\n );\n throw new Error(\n `Unsupported custom fee type: ${(exhaustiveCheck as any).type}`\n );\n }\n }\n });\n}\n\n/**\n * HtsBuilder facilitates the construction and execution of Hedera Token Service (HTS) transactions.\n */\nexport class HtsBuilder extends BaseServiceBuilder {\n constructor(hederaKit: HederaAgentKit) {\n super(hederaKit);\n }\n\n /**\n * @param {FTCreateParams} params\n * @returns {Promise<this>}\n * @throws {Error}\n */\n public async createFungibleToken(params: FTCreateParams): Promise<this> {\n this.clearNotes();\n let treasuryAccId = params.treasuryAccountId;\n if (\n !treasuryAccId &&\n this.kit.userAccountId &&\n this.kit.operationalMode === 'provideBytes'\n ) {\n this.logger.info(\n `[HtsBuilder.createFungibleToken] Using userAccountId ${this.kit.userAccountId} as treasury for FT creation in provideBytes mode.`\n );\n treasuryAccId = AccountId.fromString(this.kit.userAccountId);\n this.addNote(\n `Since no treasury was specified, your account (${this.kit.userAccountId}) has been set as the token's treasury.`\n );\n }\n if (!treasuryAccId) {\n throw new Error(\n '[HtsBuilder.createFungibleToken] Treasury Account ID is required (e.g., explicitly, via userAccountId for provideBytes mode, or via agent operator for directExecution if applicable).'\n );\n }\n\n let tokenSymbolToUse = params.tokenSymbol;\n if (!tokenSymbolToUse) {\n tokenSymbolToUse = generateDefaultSymbol(params.tokenName);\n this.addNote(\n `We've generated a token symbol '${tokenSymbolToUse}' for you, based on the token name '${params.tokenName}'.`\n );\n }\n\n let sdkSupplyType: TokenSupplyType;\n if (typeof params.supplyType === 'string') {\n const supplyTypeString: string = params.supplyType;\n if (\n supplyTypeString.toUpperCase() ===\n TokenSupplyType.Finite.toString().toUpperCase()\n ) {\n sdkSupplyType = TokenSupplyType.Finite;\n } else if (\n supplyTypeString.toUpperCase() ===\n TokenSupplyType.Infinite.toString().toUpperCase()\n ) {\n sdkSupplyType = TokenSupplyType.Infinite;\n } else {\n this.logger.warn(\n `Invalid string for supplyType: ${supplyTypeString}. Defaulting to INFINITE.`\n );\n this.addNote(\n `Invalid supplyType string '${supplyTypeString}' received, defaulted to INFINITE.`\n );\n sdkSupplyType = TokenSupplyType.Infinite;\n }\n } else {\n sdkSupplyType = params.supplyType;\n }\n\n const transaction = new TokenCreateTransaction()\n .setTokenName(params.tokenName)\n .setTokenSymbol(tokenSymbolToUse)\n .setTreasuryAccountId(treasuryAccId)\n .setTokenType(TokenType.FungibleCommon)\n .setSupplyType(sdkSupplyType)\n .setInitialSupply(this.parseAmount(params.initialSupply))\n .setDecimals(params.decimals);\n\n if (sdkSupplyType === TokenSupplyType.Finite && params.maxSupply) {\n transaction.setMaxSupply(this.parseAmount(params.maxSupply));\n }\n if (params.adminKey) {\n const parsedKey = await this.parseKey(params.adminKey);\n if (parsedKey) transaction.setAdminKey(parsedKey);\n }\n if (params.kycKey) {\n const parsedKey = await this.parseKey(params.kycKey);\n if (parsedKey) transaction.setKycKey(parsedKey);\n }\n if (params.freezeKey) {\n const parsedKey = await this.parseKey(params.freezeKey);\n if (parsedKey) transaction.setFreezeKey(parsedKey);\n }\n if (params.wipeKey) {\n const parsedKey = await this.parseKey(params.wipeKey);\n if (parsedKey) transaction.setWipeKey(parsedKey);\n }\n if (params.supplyKey) {\n const parsedKey = await this.parseKey(params.supplyKey);\n if (parsedKey) transaction.setSupplyKey(parsedKey);\n }\n if (params.feeScheduleKey) {\n const parsedKey = await this.parseKey(params.feeScheduleKey);\n if (parsedKey) transaction.setFeeScheduleKey(parsedKey);\n }\n if (params.pauseKey) {\n const parsedKey = await this.parseKey(params.pauseKey);\n if (parsedKey) transaction.setPauseKey(parsedKey);\n }\n if (params.memo) {\n transaction.setTokenMemo(params.memo);\n }\n if (params.customFees && params.customFees.length > 0) {\n const sdkCustomFees = mapToSdkCustomFees(\n params.customFees as unknown as CustomFeeInputData[],\n this.parseAmount.bind(this),\n this.logger,\n this.kit.userAccountId,\n this.kit.operationalMode,\n this.addNote.bind(this)\n );\n transaction.setCustomFees(sdkCustomFees);\n }\n if (params.autoRenewAccountId) {\n transaction.setAutoRenewAccountId(params.autoRenewAccountId);\n }\n if (params.autoRenewPeriod) {\n transaction.setAutoRenewPeriod(params.autoRenewPeriod);\n } else if (params.autoRenewAccountId) {\n transaction.setAutoRenewPeriod(DEFAULT_AUTORENEW_PERIOD_SECONDS);\n this.addNote(\n `A standard auto-renew period of ${\n DEFAULT_AUTORENEW_PERIOD_SECONDS / (24 * 60 * 60)\n } days has been set for this token.`\n );\n }\n\n this.setCurrentTransaction(transaction);\n return this;\n }\n\n /**\n * Creates a non-fungible token. If the supply key is not provided, the operator's public key will be used.\n * @param {NFTCreateParams} params\n * @returns {Promise<this>}\n * @throws {Error}\n */\n public async createNonFungibleToken(params: NFTCreateParams): Promise<this> {\n this.clearNotes();\n let treasuryAccId = params.treasuryAccountId;\n if (\n !treasuryAccId &&\n this.kit.userAccountId &&\n this.kit.operationalMode === 'provideBytes'\n ) {\n this.logger.info(\n `[HtsBuilder.createNonFungibleToken] Using userAccountId ${this.kit.userAccountId} as treasury for NFT creation in provideBytes mode.`\n );\n treasuryAccId = AccountId.fromString(this.kit.userAccountId);\n this.addNote(\n `Since no treasury was specified, your account (${this.kit.userAccountId}) has been set as the NFT collection's treasury.`\n );\n }\n if (!treasuryAccId) {\n throw new Error(\n '[HtsBuilder.createNonFungibleToken] Treasury Account ID is required (e.g., explicitly, via userAccountId for provideBytes mode, or via agent operator for directExecution if applicable).'\n );\n }\n\n let tokenSymbolToUse = params.tokenSymbol;\n if (!tokenSymbolToUse) {\n tokenSymbolToUse = generateDefaultSymbol(params.tokenName);\n this.addNote(\n `We've generated an NFT collection symbol '${tokenSymbolToUse}' for you, based on the collection name '${params.tokenName}'.`\n );\n }\n\n let sdkSupplyType: TokenSupplyType;\n if (typeof params.supplyType === 'string') {\n const supplyTypeString: string = params.supplyType;\n if (\n supplyTypeString.toUpperCase() ===\n TokenSupplyType.Finite.toString().toUpperCase()\n ) {\n sdkSupplyType = TokenSupplyType.Finite;\n } else if (\n supplyTypeString.toUpperCase() ===\n TokenSupplyType.Infinite.toString().toUpperCase()\n ) {\n sdkSupplyType = TokenSupplyType.Infinite;\n } else {\n this.logger.warn(\n `Invalid string for NFT supplyType: ${supplyTypeString}. Defaulting to FINITE as per NFT common practice.`\n );\n this.addNote(\n `Invalid supplyType string '${supplyTypeString}' received for NFT, defaulted to FINITE.`\n );\n sdkSupplyType = TokenSupplyType.Finite;\n }\n } else {\n sdkSupplyType = params.supplyType;\n }\n\n const transaction = new TokenCreateTransaction()\n .setTokenName(params.tokenName)\n .setTokenSymbol(tokenSymbolToUse)\n .setTreasuryAccountId(treasuryAccId)\n .setTokenType(TokenType.NonFungibleUnique)\n .setSupplyType(sdkSupplyType)\n .setInitialSupply(0)\n .setDecimals(0);\n\n if (sdkSupplyType === TokenSupplyType.Finite && params.maxSupply) {\n transaction.setMaxSupply(this.parseAmount(params.maxSupply));\n } else if (sdkSupplyType === TokenSupplyType.Finite && !params.maxSupply) {\n this.logger.warn(\n 'NFT supplyType is FINITE but no maxSupply was provided. This might lead to an unmintable token or undesired SDK default. Consider prompting user for maxSupply or setting a builder default.'\n );\n this.addNote(\n 'For this FINITE NFT collection, a specific maximum supply was not provided. The Hedera network might apply its own default or limit minting.'\n );\n }\n if (params.adminKey) {\n const parsedKey = await this.parseKey(params.adminKey);\n if (parsedKey) transaction.setAdminKey(parsedKey);\n }\n if (params.kycKey) {\n const parsedKey = await this.parseKey(params.kycKey);\n if (parsedKey) transaction.setKycKey(parsedKey);\n }\n if (params.freezeKey) {\n const parsedKey = await this.parseKey(params.freezeKey);\n if (parsedKey) transaction.setFreezeKey(parsedKey);\n }\n if (params.wipeKey) {\n const parsedKey = await this.parseKey(params.wipeKey);\n if (parsedKey) {\n transaction.setWipeKey(parsedKey);\n }\n }\n\n if (params.supplyKey) {\n const parsedKey = await this.parseKey(params.supplyKey);\n if (parsedKey) {\n transaction.setSupplyKey(parsedKey);\n }\n } else {\n const operator = await this.kit.query().getAccountInfo(treasuryAccId);\n const key = operator?.key?.key;\n if (key) {\n transaction.setSupplyKey(PublicKey.fromString(key));\n }\n }\n\n if (params.feeScheduleKey) {\n const parsedKey = await this.parseKey(params.feeScheduleKey);\n if (parsedKey) {\n transaction.setFeeScheduleKey(parsedKey);\n }\n }\n if (params.pauseKey) {\n const parsedKey = await this.parseKey(params.pauseKey);\n if (parsedKey) {\n transaction.setPauseKey(parsedKey);\n }\n }\n if (params.memo) {\n transaction.setTokenMemo(params.memo);\n }\n if (params.customFees && params.custo