UNPKG

@hashgraphonline/standards-agent-kit

Version:

A modular SDK for building on-chain autonomous agents using Hashgraph Online Standards, including HCS-10 for agent discovery and communication. https://hol.org

502 lines (461 loc) 20.4 kB
import type { HederaAgentKit } from 'hedera-agent-kit'; import { AccountId, Client, TopicCreateTransaction, TopicId, TopicMessageSubmitTransaction, TransactionId, } from '@hashgraph/sdk'; import { buildHcs2CreateRegistryTx, buildHcs2RegisterTx, buildHcs2UpdateTx, buildHcs2DeleteTx, buildHcs2MigrateTx, HCS2RegistryType, buildHcs6CreateRegistryTx, buildHcs6RegisterEntryTx, buildHcs10SendMessageTx, buildHcs10SubmitConnectionRequestTx, buildHcs20DeployTx, buildHcs20MintTx, buildHcs20TransferTx, buildHcs20BurnTx, buildHcs20RegisterTx, buildHcs12CreateRegistryTopicTx, buildHcs12SubmitMessageTx, buildHcs12RegisterAssemblyTx, buildHcs12AddBlockToAssemblyTx, buildHcs12AddActionToAssemblyTx, buildHcs12UpdateAssemblyTx, buildHcs7SubmitMessageTx, buildHcs7EvmMessageTx, buildHcs7WasmMessageTx, } from '@hashgraphonline/standards-sdk'; export type ByteBuildHandler = (ctx: { hederaKit: HederaAgentKit; request: Record<string, unknown>; }) => Promise<{ transactionBytes: string }>; class ByteBuildRegistryImpl { private handlers = new Map<string, ByteBuildHandler>(); register(op: string, handler: ByteBuildHandler): void { this.handlers.set(op, handler); } has(op: string): boolean { return this.handlers.has(op); } async build( op: string, hederaKit: HederaAgentKit, request: Record<string, unknown> ): Promise<{ transactionBytes: string } | null> { const h = this.handlers.get(op); if (!h) return null; return await h({ hederaKit, request }); } } export const ByteBuildRegistry = new ByteBuildRegistryImpl(); async function freezeTxToBytes( hederaKit: HederaAgentKit, tx: any, ): Promise<{ transactionBytes: string }> { const network = hederaKit.client.network.toString().includes('mainnet') ? 'mainnet' : 'testnet'; const client = network === 'mainnet' ? Client.forMainnet() : Client.forTestnet(); const operatorId = hederaKit.signer.getAccountId().toString(); const payer = AccountId.fromString(operatorId); if (typeof tx.setTransactionId === 'function') { tx.setTransactionId(TransactionId.generate(payer)); } if (typeof tx.setAutoRenewAccountId === 'function') { try { tx.setAutoRenewAccountId(operatorId); } catch {} } const frozen = await tx.freezeWith(client); return { transactionBytes: Buffer.from(frozen.toBytes()).toString('base64') }; } async function buildTopicCreateBytes( hederaKit: HederaAgentKit, memo: string, ttlSeconds?: number ): Promise<{ transactionBytes: string }> { const network = hederaKit.client.network.toString().includes('mainnet') ? 'mainnet' : 'testnet'; const client = network === 'mainnet' ? Client.forMainnet() : Client.forTestnet(); const operatorId = hederaKit.signer.getAccountId().toString(); const payer = AccountId.fromString(operatorId); let tx = new TopicCreateTransaction() .setTransactionId(TransactionId.generate(payer)) .setTopicMemo(memo); if (ttlSeconds && Number.isFinite(ttlSeconds)) { try { tx = tx.setAutoRenewPeriod(ttlSeconds); } catch {} } const frozen = await tx.freezeWith(client); return { transactionBytes: Buffer.from(frozen.toBytes()).toString('base64') }; } async function buildTopicMessageBytes( hederaKit: HederaAgentKit, topicId: string, message: string | Uint8Array, memo?: string ): Promise<{ transactionBytes: string }> { const network = hederaKit.client.network.toString().includes('mainnet') ? 'mainnet' : 'testnet'; const client = network === 'mainnet' ? Client.forMainnet() : Client.forTestnet(); const operatorId = hederaKit.signer.getAccountId().toString(); const payer = AccountId.fromString(operatorId); const tx = new TopicMessageSubmitTransaction() .setTopicId(TopicId.fromString(topicId)) .setTransactionId(TransactionId.generate(payer)) .setMessage(message); if (memo && memo.length > 0) { tx.setTransactionMemo(memo); } const frozen = await tx.freezeWith(client); return { transactionBytes: Buffer.from(frozen.toBytes()).toString('base64') }; } ByteBuildRegistry.register( 'hcs2.createRegistry', async ({ hederaKit, request }) => { const opts = (request?.options || {}) as { registryType?: number; ttl?: number; adminKey?: string | boolean; submitKey?: string | boolean; }; let operatorPublicKey: any | undefined; try { const priv = (hederaKit as any)?.signer?.getOperatorPrivateKey?.(); operatorPublicKey = priv?.publicKey; } catch {} const tx = buildHcs2CreateRegistryTx({ registryType: (opts.registryType ?? HCS2RegistryType.INDEXED) as any, ttl: opts.ttl ?? 86400, adminKey: opts.adminKey as any, submitKey: opts.submitKey as any, operatorPublicKey, }); return await freezeTxToBytes(hederaKit, tx); } ); ByteBuildRegistry.register( 'hcs2.migrateRegistry', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs2.migrateRegistry: invalid request payload'); const registryTopicId = getStringProp(request, 'registryTopicId') || ''; if (!registryTopicId) throw new Error('hcs2.migrateRegistry: registryTopicId is required'); const options = getObjectProp(request, 'options') || {}; const targetTopicId = getStringProp(options, 'targetTopicId') || ''; const metadata = getStringProp(options, 'metadata'); const memo = getStringProp(options, 'memo'); const tx = buildHcs2MigrateTx({ registryTopicId, targetTopicId, metadata, memo, }); return await freezeTxToBytes(hederaKit, tx); } ); function isRecord(x: unknown): x is Record<string, unknown> { return typeof x === 'object' && x !== null; } function getStringProp( obj: Record<string, unknown>, key: string ): string | undefined { const val = obj[key]; return typeof val === 'string' ? val : undefined; } function getNumberProp( obj: Record<string, unknown>, key: string ): number | undefined { const val = obj[key]; return typeof val === 'number' && Number.isFinite(val) ? val : undefined; } function getObjectProp( obj: Record<string, unknown>, key: string ): Record<string, unknown> | undefined { const val = obj[key]; return isRecord(val) ? val : undefined; } ByteBuildRegistry.register( 'hcs2.submitMessage', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs2.submitMessage: invalid request payload'); const topicId = getStringProp(request, 'topicId') || getStringProp(request, 'registryTopicId') || ''; if (!topicId) throw new Error('hcs2.submitMessage: topicId is required'); const payload = request['payload']; const message = typeof payload === 'string' ? payload : JSON.stringify(payload ?? {}); return await buildTopicMessageBytes(hederaKit, topicId, message); } ); ByteBuildRegistry.register('hcs2.registerEntry', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs2.registerEntry: invalid request payload'); const registryTopicId = getStringProp(request, 'registryTopicId') || ''; if (!registryTopicId) throw new Error('hcs2.registerEntry: registryTopicId is required'); const options = getObjectProp(request, 'options') || {}; const targetTopicId = getStringProp(options, 'targetTopicId') || ''; const metadata = getStringProp(options, 'metadata'); const memo = getStringProp(options, 'memo'); const tx = buildHcs2RegisterTx({ registryTopicId, targetTopicId, metadata, memo, }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs2.updateEntry', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs2.updateEntry: invalid request payload'); const registryTopicId = getStringProp(request, 'registryTopicId') || ''; if (!registryTopicId) throw new Error('hcs2.updateEntry: registryTopicId is required'); const options = getObjectProp(request, 'options') || {}; const targetTopicId = getStringProp(options, 'targetTopicId') || ''; const uid = getStringProp(options, 'uid') || ''; const metadata = getStringProp(options, 'metadata'); const memo = getStringProp(options, 'memo'); const tx = buildHcs2UpdateTx({ registryTopicId, uid, targetTopicId, metadata, memo, }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs2.deleteEntry', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs2.deleteEntry: invalid request payload'); const registryTopicId = getStringProp(request, 'registryTopicId') || ''; if (!registryTopicId) throw new Error('hcs2.deleteEntry: registryTopicId is required'); const options = getObjectProp(request, 'options') || {}; const uid = getStringProp(options, 'uid') || ''; const memo = getStringProp(options, 'memo'); const tx = buildHcs2DeleteTx({ registryTopicId, uid, memo, }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register( 'hcs6.createRegistry', async ({ hederaKit, request }) => { const opts = isRecord(request) ? getObjectProp(request, 'options') : undefined; const ttl = opts ? getNumberProp(opts, 'ttl') : undefined; let operatorPublicKey: any | undefined; try { const priv = (hederaKit as any)?.signer?.getOperatorPrivateKey?.(); operatorPublicKey = priv?.publicKey; } catch {} const tx = buildHcs6CreateRegistryTx({ ttl: ttl ?? 86400, operatorPublicKey }); return await freezeTxToBytes(hederaKit, tx); } ); ByteBuildRegistry.register( 'hcs6.registerEntry', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs6.registerEntry: invalid request payload'); const registryTopicId = getStringProp(request, 'registryTopicId') || ''; if (!registryTopicId) throw new Error('hcs6.registerEntry: registryTopicId is required'); const options = getObjectProp(request, 'options') || {}; const targetTopicId = getStringProp(options, 'targetTopicId') || ''; const memo = getStringProp(options, 'memo'); const tx = buildHcs6RegisterEntryTx({ registryTopicId, targetTopicId, memo, }); return await freezeTxToBytes(hederaKit, tx); } ); ByteBuildRegistry.register( 'hcs6.submitMessage', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs6.submitMessage: invalid request payload'); const topicId = getStringProp(request, 'topicId') || ''; if (!topicId) throw new Error('hcs6.submitMessage: topicId is required'); const payload = request['payload']; const message = typeof payload === 'string' ? payload : JSON.stringify(payload ?? {}); return await buildTopicMessageBytes(hederaKit, topicId, message); } ); ByteBuildRegistry.register('sendMessage', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('sendMessage: invalid request payload'); const topicId = getStringProp(request, 'topicId') || ''; if (!topicId) throw new Error('sendMessage: topicId is required'); const dataVal = request['data']; const memo = getStringProp(request, 'memo') || ''; const operatorId = hederaKit.signer.getAccountId().toString(); const data = typeof dataVal === 'string' ? dataVal : JSON.stringify(dataVal ?? {}); const tx = buildHcs10SendMessageTx({ connectionTopicId: topicId, operatorId, data, memo }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register( 'submitConnectionRequest', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('submitConnectionRequest: invalid request payload'); const inboundTopicId = getStringProp(request, 'inboundTopicId') || ''; if (!inboundTopicId) throw new Error('submitConnectionRequest: inboundTopicId is required'); const memo = getStringProp(request, 'memo') || ''; const operatorId = hederaKit.signer.getAccountId().toString(); const tx = buildHcs10SubmitConnectionRequestTx({ inboundTopicId, operatorId, memo }); return await freezeTxToBytes(hederaKit, tx); } ); ByteBuildRegistry.register('hcs20.deploy', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs20.deploy: invalid request payload'); const topicId = getStringProp(request, 'topicId') || ''; if (!topicId) throw new Error('hcs20.deploy: topicId is required'); const name = getStringProp(request, 'name') || ''; const tick = getStringProp(request, 'tick') || ''; const max = getStringProp(request, 'max') || ''; const lim = getStringProp(request, 'lim'); const metadata = getStringProp(request, 'metadata'); const memo = getStringProp(request, 'memo'); const tx = buildHcs20DeployTx({ topicId, name, tick, max, lim, metadata, memo }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs20.mint', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs20.mint: invalid request payload'); const topicId = getStringProp(request, 'topicId') || ''; if (!topicId) throw new Error('hcs20.mint: topicId is required'); const tick = getStringProp(request, 'tick') || ''; const amt = getStringProp(request, 'amt') || ''; const to = getStringProp(request, 'to') || ''; const memo = getStringProp(request, 'memo'); const tx = buildHcs20MintTx({ topicId, tick, amt, to, memo }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs20.transfer', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs20.transfer: invalid request payload'); const topicId = getStringProp(request, 'topicId') || ''; if (!topicId) throw new Error('hcs20.transfer: topicId is required'); const tick = getStringProp(request, 'tick') || ''; const amt = getStringProp(request, 'amt') || ''; const from = getStringProp(request, 'from') || ''; const to = getStringProp(request, 'to') || ''; const memo = getStringProp(request, 'memo'); const tx = buildHcs20TransferTx({ topicId, tick, amt, from, to, memo }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs20.burn', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs20.burn: invalid request payload'); const topicId = getStringProp(request, 'topicId') || ''; if (!topicId) throw new Error('hcs20.burn: topicId is required'); const tick = getStringProp(request, 'tick') || ''; const amt = getStringProp(request, 'amt') || ''; const from = getStringProp(request, 'from') || ''; const memo = getStringProp(request, 'memo'); const tx = buildHcs20BurnTx({ topicId, tick, amt, from, memo }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs20.register', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs20.register: invalid request payload'); const registryTopicId = getStringProp(request, 'registryTopicId') || ''; if (!registryTopicId) throw new Error('hcs20.register: registryTopicId is required'); const topicId = getStringProp(request, 'topicId') || ''; const name = getStringProp(request, 'name') || ''; const isPrivateVal = (request as any)['isPrivate']; const isPrivate = typeof isPrivateVal === 'boolean' ? isPrivateVal : false; const metadata = getStringProp(request, 'metadata'); const memo = getStringProp(request, 'memo'); const tx = buildHcs20RegisterTx({ registryTopicId, name, topicId, isPrivate, metadata, memo }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs12.createRegistry', async ({ hederaKit, request }) => { const opts = isRecord(request) ? getObjectProp(request, 'options') : undefined; const ttlVal = opts ? getNumberProp(opts, 'ttl') : undefined; const ttl = (ttlVal ?? 60) as number; const registry = (opts && getStringProp(opts, 'registry')) || 'hashlinks'; let operatorPublicKey: any | undefined; try { const priv = (hederaKit as any)?.signer?.getOperatorPrivateKey?.(); operatorPublicKey = priv?.publicKey; } catch {} const tx = buildHcs12CreateRegistryTopicTx({ registry: registry as any, ttl, operatorPublicKey }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs12.submitMessage', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs12.submitMessage: invalid request payload'); const topicId = getStringProp(request, 'topicId') || ''; if (!topicId) throw new Error('hcs12.submitMessage: topicId is required'); const payload = (request as any)['payload']; const tx = buildHcs12SubmitMessageTx({ topicId, payload: payload as any }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs12.registerAssembly', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs12.registerAssembly: invalid request payload'); const assemblyTopicId = getStringProp(request, 'assemblyTopicId') || ''; if (!assemblyTopicId) throw new Error('hcs12.registerAssembly: assemblyTopicId is required'); const registration = getObjectProp(request, 'registration') || {}; const tx = buildHcs12RegisterAssemblyTx({ assemblyTopicId, registration: registration as any }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs12.addBlock', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs12.addBlock: invalid request payload'); const assemblyTopicId = getStringProp(request, 'assemblyTopicId') || ''; if (!assemblyTopicId) throw new Error('hcs12.addBlock: assemblyTopicId is required'); const operation = getObjectProp(request, 'operation') || {}; const tx = buildHcs12AddBlockToAssemblyTx({ assemblyTopicId, operation: operation as any }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs12.addAction', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs12.addAction: invalid request payload'); const assemblyTopicId = getStringProp(request, 'assemblyTopicId') || ''; if (!assemblyTopicId) throw new Error('hcs12.addAction: assemblyTopicId is required'); const operation = getObjectProp(request, 'operation') || {}; const tx = buildHcs12AddActionToAssemblyTx({ assemblyTopicId, operation: operation as any }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs12.updateAssembly', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs12.updateAssembly: invalid request payload'); const assemblyTopicId = getStringProp(request, 'assemblyTopicId') || ''; if (!assemblyTopicId) throw new Error('hcs12.updateAssembly: assemblyTopicId is required'); const operation = getObjectProp(request, 'operation') || {}; const tx = buildHcs12UpdateAssemblyTx({ assemblyTopicId, operation: operation as any }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs7.submitMessage', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs7.submitMessage: invalid request payload'); const topicId = getStringProp(request, 'topicId') || ''; if (!topicId) throw new Error('hcs7.submitMessage: topicId is required'); const message = getObjectProp(request, 'message') || {}; const tx = buildHcs7SubmitMessageTx({ topicId, message: message as any }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs7.evm', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs7.evm: invalid request payload'); const topicId = getStringProp(request, 'topicId') || ''; if (!topicId) throw new Error('hcs7.evm: topicId is required'); const config = getObjectProp(request, 'config') || {}; const tx = buildHcs7EvmMessageTx({ topicId, config: config as any }); return await freezeTxToBytes(hederaKit, tx); }); ByteBuildRegistry.register('hcs7.wasm', async ({ hederaKit, request }) => { if (!isRecord(request)) throw new Error('hcs7.wasm: invalid request payload'); const topicId = getStringProp(request, 'topicId') || ''; if (!topicId) throw new Error('hcs7.wasm: topicId is required'); const config = getObjectProp(request, 'config') || {}; const tx = buildHcs7WasmMessageTx({ topicId, config: config as any }); return await freezeTxToBytes(hederaKit, tx); });