diff --git a/src/plugins/batch/manifest.ts b/src/plugins/batch/manifest.ts index 65e659f63..6557feb75 100644 --- a/src/plugins/batch/manifest.ts +++ b/src/plugins/batch/manifest.ts @@ -97,6 +97,7 @@ export const batchPluginManifest: PluginManifest = { 'topic-create-batch-state', 'token-create-ft-batch-state', 'token-create-ft-from-file-batch-state', + 'token-create-nft-batch-state', ], options: [ { diff --git a/src/plugins/token/hooks/batch-create-nft/handler.ts b/src/plugins/token/hooks/batch-create-nft/handler.ts new file mode 100644 index 000000000..1f5cdd6d6 --- /dev/null +++ b/src/plugins/token/hooks/batch-create-nft/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_NFT_COMMAND_NAME } from '@/plugins/token/commands/create-nft'; +import { buildTokenData } from '@/plugins/token/utils/token-data-builders'; +import { ZustandTokenStateHelper } from '@/plugins/token/zustand-state-helper'; + +import { CreateNftNormalizedParamsSchema } from './types'; + +export class TokenCreateNftBatchStateHook 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_NFT_COMMAND_NAME) + .map((batchDataItem) => this.saveNft(api, logger, batchDataItem)), + ); + return Promise.resolve({ + breakFlow: false, + result: { + message: 'success', + }, + }); + } + + private async saveNft( + api: CoreApi, + logger: Logger, + batchDataItem: BatchDataItem, + ): Promise { + const parseResult = CreateNftNormalizedParamsSchema.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(' Non-fungible 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-nft/index.ts b/src/plugins/token/hooks/batch-create-nft/index.ts new file mode 100644 index 000000000..29c104dc8 --- /dev/null +++ b/src/plugins/token/hooks/batch-create-nft/index.ts @@ -0,0 +1 @@ +export { TokenCreateNftBatchStateHook } from './handler'; diff --git a/src/plugins/token/hooks/batch-create-nft/types.ts b/src/plugins/token/hooks/batch-create-nft/types.ts new file mode 100644 index 000000000..ee32302a8 --- /dev/null +++ b/src/plugins/token/hooks/batch-create-nft/types.ts @@ -0,0 +1,36 @@ +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(), +}); + +export const CreateNftNormalizedParamsSchema = z.object({ + name: z.string(), + symbol: z.string(), + decimals: z.number(), + initialSupply: TinybarSchema, + tokenType: z.enum([ + HederaTokenType.NON_FUNGIBLE_TOKEN, + HederaTokenType.FUNGIBLE_COMMON, + ]), + supplyType: SupplyTypeSchema, + alias: z.string().optional(), + memo: z.string().optional(), + network: NetworkSchema, + keyManager: keyManagerNameSchema, + treasury: ResolvedAccountCredentialSchema, + admin: ResolvedAccountCredentialSchema, + supply: ResolvedAccountCredentialSchema, + finalMaxSupply: TinybarSchema.optional(), + adminKeyProvided: z.boolean(), +}); diff --git a/src/plugins/token/manifest.ts b/src/plugins/token/manifest.ts index fe3bae043..d0c485143 100644 --- a/src/plugins/token/manifest.ts +++ b/src/plugins/token/manifest.ts @@ -74,6 +74,7 @@ import { } from './commands/view'; import { TokenCreateFtBatchStateHook } from './hooks/batch-create-ft'; import { TokenCreateFtFromFileBatchStateHook } from './hooks/batch-create-ft-from-file'; +import { TokenCreateNftBatchStateHook } from './hooks/batch-create-nft'; export const tokenPluginManifest: PluginManifest = { name: 'token', @@ -91,6 +92,11 @@ export const tokenPluginManifest: PluginManifest = { hook: new TokenCreateFtFromFileBatchStateHook(), options: [], }, + { + name: 'token-create-nft-batch-state', + hook: new TokenCreateNftBatchStateHook(), + options: [], + }, ], commands: [ {