Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4c1f90b
feat(1522): add adr for class based handler and hook architecture
mmyslblocky Mar 5, 2026
9a2acb9
feat(1527): added class based hook definition and hooks definition. F…
mmyslblocky Mar 5, 2026
609fc48
feat(1527): removed redundant steps
mmyslblocky Mar 5, 2026
4b541a5
feat(1527): changes in hook and command architecture
mmyslblocky Mar 5, 2026
a2aeac7
feat(1527): updated adr-009 with full implementation
mmyslblocky Mar 5, 2026
bf4f18c
feat(1522): updated adr
mmyslblocky Mar 5, 2026
88cbadf
added process hook result method
mmyslblocky Mar 6, 2026
a301676
feat(1527): added new implementation of command methods
mmyslblocky Mar 6, 2026
505b929
feat(1522): added new updates to the ADR
mmyslblocky Mar 6, 2026
5d09f75
feat(1527): resolved merge conflicts
mmyslblocky Mar 6, 2026
83dc4d8
feat(1527): added chage for output schema validation
mmyslblocky Mar 6, 2026
4024c4e
feat(1541): batch plugin command implementation - first draft
mmyslblocky Mar 9, 2026
f2dfc83
feat(1523): add adr with batch transaction implementation
mmyslblocky Mar 9, 2026
b5f5e46
feat(1527): divided buildAndSign into two separate steps, updated ADR
mmyslblocky Mar 9, 2026
3574e20
feat(1527): changes in ADR
mmyslblocky Mar 9, 2026
efd936b
feat(1527): command adjustment
mmyslblocky Mar 9, 2026
9da8f9b
feat(1527): added options to the hook
mmyslblocky Mar 10, 2026
94e8805
feat(1527): deleted relevantCommand from command definition
mmyslblocky Mar 10, 2026
0271bdd
feat(1527): added ADR-009 update
mmyslblocky Mar 10, 2026
2962b3e
Merge branch 'feat/1527-implement-hook-and-command-class' of github.c…
mmyslblocky Mar 10, 2026
b32e382
feat(1523): update ADR-010
mmyslblocky Mar 10, 2026
b619589
feat(1541): resolve merge conflicts
mmyslblocky Mar 10, 2026
252ddf0
feat(1541): adjusted changes after ADR
mmyslblocky Mar 10, 2026
a6f2863
feat(1541): fixed tests
mmyslblocky Mar 10, 2026
3f46414
feat(1527): change method name
mmyslblocky Mar 10, 2026
d5983b1
Merge branch 'feat/1527-implement-hook-and-command-class' of github.c…
mmyslblocky Mar 10, 2026
ff69cf4
feat(1523): change method name
mmyslblocky Mar 10, 2026
3d5c0e5
Merge branch 'feat/1523-adr-batch-transaction' of github.com:hiero-le…
mmyslblocky Mar 10, 2026
4cc95bb
feat(1541): resolve merge conflicts
mmyslblocky Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
561 changes: 561 additions & 0 deletions docs/adr/ADR-010-batch-transaction-plugin.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/core/core-api/core-api.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
import type { AccountService } from '@/core/services/account/account-transaction-service.interface';
import type { AliasService } from '@/core/services/alias/alias-service.interface';
import type { BatchTransactionService } from '@/core/services/batch/batch-transaction-service.interface';
import type { ConfigService } from '@/core/services/config/config-service.interface';
import type { ContractCompilerService } from '@/core/services/contract-compiler/contract-compiler-service.interface';
import type { ContractQueryService } from '@/core/services/contract-query/contract-query-service.interface';
Expand Down Expand Up @@ -109,4 +110,5 @@ export interface CoreApi {
contractVerifier: ContractVerifierService;
contractQuery: ContractQueryService;
identityResolution: IdentityResolutionService;
batch: BatchTransactionService;
}
4 changes: 4 additions & 0 deletions src/core/core-api/core-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import type { CoreApi } from '@/core';
import type { AccountService } from '@/core/services/account/account-transaction-service.interface';
import type { AliasService } from '@/core/services/alias/alias-service.interface';
import type { BatchTransactionService } from '@/core/services/batch/batch-transaction-service.interface';
import type { ConfigService } from '@/core/services/config/config-service.interface';
import type { ContractCompilerService } from '@/core/services/contract-compiler/contract-compiler-service.interface';
import type { ContractQueryService } from '@/core/services/contract-query/contract-query-service.interface';
Expand All @@ -30,6 +31,7 @@ import type { TxSignService } from '@/core/services/tx-sign/tx-sign-service.inte

import { AccountServiceImpl } from '@/core/services/account/account-transaction-service';
import { AliasServiceImpl } from '@/core/services/alias/alias-service';
import { BatchTransactionServiceImpl } from '@/core/services/batch/batch-transaction-service';
import { ConfigServiceImpl } from '@/core/services/config/config-service';
import { ContractCompilerServiceImpl } from '@/core/services/contract-compiler/contract-compiler-service';
import { ContractQueryServiceImpl } from '@/core/services/contract-query/contract-query-service';
Expand Down Expand Up @@ -72,6 +74,7 @@ export class CoreApiImplementation implements CoreApi {
public contractVerifier: ContractVerifierService;
public contractQuery: ContractQueryService;
public identityResolution: IdentityResolutionService;
public batch: BatchTransactionService;

constructor(storageDir?: string) {
this.logger = new LoggerService();
Expand Down Expand Up @@ -128,6 +131,7 @@ export class CoreApiImplementation implements CoreApi {
this.alias,
this.mirror,
);
this.batch = new BatchTransactionServiceImpl(this.logger);
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/core/plugins/plugin-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,10 @@ export class PluginManager {
let result: CommandResult;
if (commandSpec.command) {
result = await commandSpec.command.execute(handlerArgs);
} else {
} else if (commandSpec.handler) {
result = await commandSpec.handler(handlerArgs);
} else {
throw new InternalError('Command handler not found');
}
const outputSchema = result.overrideSchema ?? commandSpec.output.schema;
outputSchema.parse(result.result);
Expand Down
2 changes: 1 addition & 1 deletion src/core/plugins/plugin.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export interface CommandSpec {
description: string;
options?: CommandOption[];
command?: Command;
handler: CommandHandler;
handler?: CommandHandler;
output: CommandOutputSpec;
excessArguments?: boolean;
registeredHooks?: string[];
Expand Down
10 changes: 10 additions & 0 deletions src/core/services/batch/batch-transaction-service.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type {
CreateBatchTransactionParams,
CreateBatchTransactionResult,
} from '@/core/services/batch/types';

export interface BatchTransactionService {
createBatchTransaction(
params: CreateBatchTransactionParams,
): CreateBatchTransactionResult;
}
31 changes: 31 additions & 0 deletions src/core/services/batch/batch-transaction-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { BatchTransactionService } from '@/core/services/batch/batch-transaction-service.interface';
import type {
CreateBatchTransactionParams,
CreateBatchTransactionResult,
} from '@/core/services/batch/types';
import type { Logger } from '@/core/services/logger/logger-service.interface';

import { BatchTransaction } from '@hashgraph/sdk';

export class BatchTransactionServiceImpl implements BatchTransactionService {
private logger: Logger;

constructor(logger: Logger) {
this.logger = logger;
}

createBatchTransaction(
params: CreateBatchTransactionParams,
): CreateBatchTransactionResult {
this.logger.debug(
`[BATCH TX] Creating batch transaction with ${params.transactions.length} inner transactions`,
);
const batchTransaction = new BatchTransaction();
params.transactions.forEach((tx) => {
batchTransaction.addInnerTransaction(tx);
});
return {
transaction: batchTransaction,
};
}
}
10 changes: 10 additions & 0 deletions src/core/services/batch/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { BatchTransaction, Transaction } from '@hashgraph/sdk';

export interface CreateBatchTransactionResult {
transaction: BatchTransaction;
}

// Parameter types for account operations
export interface CreateBatchTransactionParams {
transactions: Transaction[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,6 @@ export class HederaMirrornodeServiceDefaultImpl implements HederaMirrornodeServi

async getTopicInfo(topicId: string): Promise<TopicInfo> {
const url = `${this.getBaseUrl()}/topics/${topicId}`;
console.log(url);
try {
const response = await fetch(url);

Expand Down
2 changes: 2 additions & 0 deletions src/core/shared/config/cli-options.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { PluginManifest } from '@/core/plugins/plugin.types';

import accountPluginManifest from '@/plugins/account/manifest';
import batchPluginManifest from '@/plugins/batch/manifest';
import configPluginManifest from '@/plugins/config/manifest';
import contractPluginManifest from '@/plugins/contract/manifest';
import contractErc20PluginManifest from '@/plugins/contract-erc20/manifest';
Expand Down Expand Up @@ -38,6 +39,7 @@ export const RESERVED_SHORT_OPTIONS = new Set<string>([

export const DEFAULT_PLUGIN_STATE: PluginManifest[] = [
accountPluginManifest,
batchPluginManifest,
tokenPluginManifest,
networkPluginManifest,
pluginManagementManifest,
Expand Down
52 changes: 52 additions & 0 deletions src/plugins/batch/commands/create/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Batch Create Command Handler
*/
import type { CommandHandlerArgs, CommandResult } from '@/core';
import type { Command } from '@/core/commands/command.interface';
import type { KeyManagerName } from '@/core/services/kms/kms-types.interface';
import type { CreateBatchOutput } from './output';

import { ValidationError } from '@/core/errors';
import { ZustandBatchStateHelper } from '@/plugins/batch/zustand-state-helper';

import { CreateBatchInputSchema } from './input';

export class CreateBatchCommand implements Command {
async execute(args: CommandHandlerArgs): Promise<CommandResult> {
const { api, logger } = args;

const batchState = new ZustandBatchStateHelper(api.state, logger);
const validArgs = CreateBatchInputSchema.parse(args.args);

if (batchState.hasBatch(validArgs.name)) {
throw new ValidationError(
`Batch with name '${validArgs.name}' already exists`,
);
}

const keyManager =
validArgs.keyManager ||
api.config.getOption<KeyManagerName>('default_key_manager');

const resolved = await api.keyResolver.resolveSigningKey(
validArgs.key,
keyManager,
['batch:signer'],
);

const batchData = {
name: validArgs.name,
keyRefId: resolved.keyRefId,
transactions: [],
};

batchState.saveBatch(validArgs.name, batchData);

const outputData: CreateBatchOutput = {
name: batchData.name,
keyRefId: batchData.keyRefId,
};

return { result: outputData };
}
}
3 changes: 3 additions & 0 deletions src/plugins/batch/commands/create/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { CreateBatchCommand } from './handler';
export type { CreateBatchOutput } from './output';
export { CREATE_BATCH_TEMPLATE, CreateBatchOutputSchema } from './output';
22 changes: 22 additions & 0 deletions src/plugins/batch/commands/create/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { z } from 'zod';

import {
AliasNameSchema,
KeyManagerTypeSchema,
PrivateKeySchema,
} from '@/core/schemas';

/**
* Input schema for batch create command
*/
export const CreateBatchInputSchema = z.object({
name: AliasNameSchema.describe('Batch name'),
key: PrivateKeySchema.describe(
'Key to sign transactions in the batch. Can be {accountId}:{privateKey} pair, account private key in {ed25519|ecdsa}:private:{private-key} format, key reference or account alias',
),
keyManager: KeyManagerTypeSchema.optional().describe(
'Key manager type (defaults to config setting)',
),
});

export type CreateBatchInput = z.infer<typeof CreateBatchInputSchema>;
23 changes: 23 additions & 0 deletions src/plugins/batch/commands/create/output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Create Batch Command Output Schema and Template
*/
import { z } from 'zod';

/**
* Create Batch Command Output Schema
*/
export const CreateBatchOutputSchema = z.object({
name: z.string().describe('Batch name'),
keyRefId: z.string().describe('Key reference ID for signing'),
});

export type CreateBatchOutput = z.infer<typeof CreateBatchOutputSchema>;

/**
* Human-readable template for create batch output
*/
export const CREATE_BATCH_TEMPLATE = `
✅ Batch created successfully
Name: {{name}}
Batch Key Reference ID: {{keyRefId}}
`.trim();
124 changes: 124 additions & 0 deletions src/plugins/batch/commands/execute/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* Batch Execute Command Handler
*/
import type {
CommandHandlerArgs,
CommandResult,
TransactionResult,
} from '@/core';
import type {
BatchBuildTransactionResult,
BatchNormalisedParams,
BatchSignTransactionResult,
} from '@/plugins/batch/commands/execute/types';
import type { ExecuteBatchOutput } from './output';

import { Transaction } from '@hashgraph/sdk';

import { BaseTransactionCommand } from '@/core/commands/command';
import { NotFoundError, TransactionError } from '@/core/errors';
import { composeKey } from '@/core/utils/key-composer';
import { ZustandBatchStateHelper } from '@/plugins/batch/zustand-state-helper';

import { ExecuteBatchInputSchema } from './input';

export class ExecuteBatchCommand extends BaseTransactionCommand<
BatchNormalisedParams,
BatchBuildTransactionResult,
BatchSignTransactionResult,
TransactionResult
> {
async normalizeParams(
args: CommandHandlerArgs,
): Promise<BatchNormalisedParams> {
const { api, logger } = args;
const batchState = new ZustandBatchStateHelper(api.state, logger);
const validArgs = ExecuteBatchInputSchema.parse(args.args);
const name = validArgs.name;
const network = api.network.getCurrentNetwork();
const key = composeKey(network, name);
const batchData = batchState.getBatch(key);
if (!batchData) {
throw new NotFoundError(`Batch not found: ${validArgs.name}`);
}
return {
name,
network,
batchData,
};
}
async buildTransaction(
args: CommandHandlerArgs,
normalisedParams: BatchNormalisedParams,
): Promise<BatchBuildTransactionResult> {
void normalisedParams;
const { api } = args;
const innerTransactions = [...normalisedParams.batchData.transactions]
.sort((a, b) => a.order - b.order)
.map((txItem) => {
return Transaction.fromBytes(
//@todo ensure that transaction will be stored in hex format
Uint8Array.from(Buffer.from(txItem.transactionBytes, 'hex')),
);
});
const result = api.batch.createBatchTransaction({
transactions: innerTransactions,
});
return { transaction: result.transaction };
}
async signTransaction(
args: CommandHandlerArgs,
normalisedParams: BatchNormalisedParams,
buildTransactionResult: BatchBuildTransactionResult,
): Promise<BatchSignTransactionResult> {
const { api } = args;
void normalisedParams;
const batchKey = normalisedParams.batchData.keyRefId;
const signedTransaction = await api.txSign.sign(
buildTransactionResult.transaction,
[batchKey],
);
return {
transaction: signedTransaction,
};
}
async executeTransaction(
args: CommandHandlerArgs,
normalisedParams: BatchNormalisedParams,
buildTransactionResult: BatchBuildTransactionResult,
signTransactionResult: BatchSignTransactionResult,
): Promise<TransactionResult> {
void normalisedParams;
void buildTransactionResult;
const { api } = args;
const result = await api.txExecute.execute(
signTransactionResult.transaction,
);
if (!result.success) {
throw new TransactionError(
`Failed to execute batch (txId: ${result.transactionId})`,
false,
);
}
return result;
}
async outputPreparation(
args: CommandHandlerArgs,
normalisedParams: BatchNormalisedParams,
buildTransactionResult: BatchBuildTransactionResult,
signTransactionResult: BatchSignTransactionResult,
executeTransactionResult: TransactionResult,
): Promise<CommandResult> {
void args;
void buildTransactionResult;
void signTransactionResult;
const outputData: ExecuteBatchOutput = {
batchName: normalisedParams.name,
transactionId: executeTransactionResult?.transactionId || '',
success: executeTransactionResult?.success || false,
network: normalisedParams.network,
};

return { result: outputData };
}
}
3 changes: 3 additions & 0 deletions src/plugins/batch/commands/execute/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { ExecuteBatchCommand } from './handler';
export type { ExecuteBatchOutput } from './output';
export { EXECUTE_BATCH_TEMPLATE, ExecuteBatchOutputSchema } from './output';
12 changes: 12 additions & 0 deletions src/plugins/batch/commands/execute/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { z } from 'zod';

import { AliasNameSchema } from '@/core/schemas';

/**
* Input schema for batch execute command
*/
export const ExecuteBatchInputSchema = z.object({
name: AliasNameSchema.describe('Batch name'),
});

export type ExecuteBatchInput = z.infer<typeof ExecuteBatchInputSchema>;
Loading
Loading