diff --git a/src/plugins/batch/manifest.ts b/src/plugins/batch/manifest.ts index ada8d32df..520187d21 100644 --- a/src/plugins/batch/manifest.ts +++ b/src/plugins/batch/manifest.ts @@ -95,6 +95,7 @@ export const batchPluginManifest: PluginManifest = { registeredHooks: [ 'account-create-batch-state', 'topic-create-batch-state', + 'token-create-ft-batch-state', ], options: [ { diff --git a/src/plugins/token/hooks/batch-create-ft/handler.ts b/src/plugins/token/hooks/batch-create-ft/handler.ts new file mode 100644 index 000000000..63cb09ebe --- /dev/null +++ b/src/plugins/token/hooks/batch-create-ft/handler.ts @@ -0,0 +1,114 @@ +import type { CommandHandlerArgs, CoreApi, Logger } from '@/core'; +import type { + HookResult, + PreOutputPreparationParams, +} from '@/core/hooks/types'; +import type { + BatchDataItem, + BatchExecuteTransactionResult, + TransactionResult, +} from '@/core/types/shared.types'; + +import { StateError } from '@/core'; +import { AbstractHook } from '@/core/hooks/abstract-hook'; +import { AliasType } from '@/core/services/alias/alias-service.interface'; +import { composeKey } from '@/core/utils/key-composer'; +import { TOKEN_CREATE_FT_COMMAND_NAME } from '@/plugins/token/commands/create-ft'; +import { buildTokenData } from '@/plugins/token/utils/token-data-builders'; +import { ZustandTokenStateHelper } from '@/plugins/token/zustand-state-helper'; + +import { CreateFtNormalizedParamsSchema } from './types'; + +export class TokenCreateFtBatchStateHook extends AbstractHook { + override async preOutputPreparationHook( + args: CommandHandlerArgs, + params: PreOutputPreparationParams< + unknown, + unknown, + unknown, + BatchExecuteTransactionResult + >, + ): Promise { + const { api, logger } = args; + const batchData = params.executeTransactionResult.updatedBatchData; + await Promise.all( + [...batchData.transactions] + .filter((item) => item.command === TOKEN_CREATE_FT_COMMAND_NAME) + .map((batchDataItem) => this.saveCreateFt(api, logger, batchDataItem)), + ); + return Promise.resolve({ + breakFlow: false, + result: { + message: 'success', + }, + }); + } + + private async saveCreateFt( + api: CoreApi, + logger: Logger, + batchDataItem: BatchDataItem, + ): Promise { + const parseResult = CreateFtNormalizedParamsSchema.safeParse( + batchDataItem.normalizedParams, + ); + if (!parseResult.success) { + logger.warn( + `There was a problem with parsing data schema. The saving will not be done`, + ); + return; + } + const normalisedParams = parseResult.data; + const innerTransactionId = batchDataItem.transactionId; + if (!innerTransactionId) { + logger.warn( + `No transaction ID found for batch transaction ${batchDataItem.order}`, + ); + return; + } + + const innerTransactionResult: TransactionResult = + await api.receipt.getReceipt({ + transactionId: innerTransactionId, + }); + + if (!innerTransactionResult.tokenId) { + throw new StateError( + 'Transaction completed but did not return a token ID', + { context: { transactionId: innerTransactionResult.transactionId } }, + ); + } + + const tokenData = buildTokenData(innerTransactionResult, { + name: normalisedParams.name, + symbol: normalisedParams.symbol, + treasuryId: normalisedParams.treasury.accountId, + decimals: normalisedParams.decimals, + initialSupply: normalisedParams.initialSupply, + tokenType: normalisedParams.tokenType, + supplyType: normalisedParams.supplyType, + adminPublicKey: normalisedParams.admin.publicKey, + supplyPublicKey: normalisedParams.supply?.publicKey, + network: normalisedParams.network, + }); + + const key = composeKey( + normalisedParams.network, + innerTransactionResult.tokenId, + ); + const tokenState = new ZustandTokenStateHelper(api.state, logger); + tokenState.saveToken(key, tokenData); + logger.info(' Token data saved to state'); + + if (normalisedParams.alias) { + api.alias.register({ + alias: normalisedParams.alias, + type: AliasType.Token, + network: normalisedParams.network, + entityId: innerTransactionResult.tokenId, + createdAt: innerTransactionResult.consensusTimestamp, + }); + logger.info(` Name registered: ${normalisedParams.alias}`); + } + } +} diff --git a/src/plugins/token/hooks/batch-create-ft/index.ts b/src/plugins/token/hooks/batch-create-ft/index.ts new file mode 100644 index 000000000..7d3951eeb --- /dev/null +++ b/src/plugins/token/hooks/batch-create-ft/index.ts @@ -0,0 +1 @@ +export { TokenCreateFtBatchStateHook } from './handler'; diff --git a/src/plugins/token/hooks/batch-create-ft/types.ts b/src/plugins/token/hooks/batch-create-ft/types.ts new file mode 100644 index 000000000..fb64bfb00 --- /dev/null +++ b/src/plugins/token/hooks/batch-create-ft/types.ts @@ -0,0 +1,39 @@ +import { z } from 'zod'; + +import { + NetworkSchema, + SupplyTypeSchema, + TinybarSchema, +} from '@/core/schemas/common-schemas'; +import { keyManagerNameSchema } from '@/core/services/kms/kms-types.interface'; +import { HederaTokenType } from '@/core/shared/constants'; + +const ResolvedAccountCredentialSchema = z.object({ + keyRefId: z.string(), + accountId: z.string(), + publicKey: z.string(), +}); + +const ResolvedPublicKeySchema = z.object({ + keyRefId: z.string(), + publicKey: z.string(), +}); + +export const CreateFtNormalizedParamsSchema = z.object({ + name: z.string(), + symbol: z.string(), + decimals: z.number(), + initialSupply: TinybarSchema, + supplyType: SupplyTypeSchema, + alias: z.string().optional(), + memo: z.string().optional(), + tokenType: z.enum([ + HederaTokenType.NON_FUNGIBLE_TOKEN, + HederaTokenType.FUNGIBLE_COMMON, + ]), + network: NetworkSchema, + keyManager: keyManagerNameSchema, + treasury: ResolvedAccountCredentialSchema, + admin: ResolvedAccountCredentialSchema, + supply: ResolvedPublicKeySchema.optional(), +}); diff --git a/src/plugins/token/manifest.ts b/src/plugins/token/manifest.ts index 5afc41aa7..10d2e2a77 100644 --- a/src/plugins/token/manifest.ts +++ b/src/plugins/token/manifest.ts @@ -72,12 +72,20 @@ import { tokenView, TokenViewOutputSchema, } from './commands/view'; +import { TokenCreateFtBatchStateHook } from './hooks/batch-create-ft'; export const tokenPluginManifest: PluginManifest = { name: 'token', version: '1.0.0', displayName: 'Token Plugin', description: 'Plugin for managing Hedera fungible and non-fungible tokens', + hooks: [ + { + name: 'token-create-ft-batch-state', + hook: new TokenCreateFtBatchStateHook(), + options: [], + }, + ], commands: [ { name: 'mint-ft',