Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
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
11 changes: 11 additions & 0 deletions src/core/schemas/common-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -930,3 +930,14 @@ export const HexEncodedDataSchema = z
'Data must be a hex encoded string starting in format "0x12abfe456123"',
)
.describe('HEX encoded data format');

export const ResolvedAccountCredentialSchema = z.object({
keyRefId: z.string(),
accountId: z.string(),
publicKey: z.string(),
});

export const ResolvedPublicKeySchema = z.object({
keyRefId: z.string(),
publicKey: z.string(),
});
8 changes: 7 additions & 1 deletion src/plugins/batch/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,13 @@ export const batchPluginManifest: PluginManifest = {
summary: 'Execute a batch',
description:
'Execute a batch by name, signing and submitting its transactions',
registeredHooks: ['account-create-batch-state'],
registeredHooks: [
'account-create-batch-state',
'topic-create-batch-state',
'token-create-ft-batch-state',
'token-create-ft-from-file-batch-state',
'token-create-nft-batch-state',
],
options: [
{
name: 'name',
Expand Down
133 changes: 133 additions & 0 deletions src/plugins/token/hooks/batch-create-ft-from-file/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import type { CommandHandlerArgs, CoreApi, Logger } from '@/core';
import type {
HookResult,
PreOutputPreparationParams,
} from '@/core/hooks/types';
import type { Credential } from '@/core/services/kms/kms-types.interface';
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_FROM_FILE_COMMAND_NAME } from '@/plugins/token/commands/create-ft-from-file';
import { processTokenAssociations } from '@/plugins/token/utils/token-associations';
import { buildTokenDataFromFile } from '@/plugins/token/utils/token-data-builders';
import { ZustandTokenStateHelper } from '@/plugins/token/zustand-state-helper';

import { CreateFtFromFileNormalizedParamsSchema } from './types';

export class TokenCreateFtFromFileBatchStateHook extends AbstractHook {
override async preOutputPreparationHook(
args: CommandHandlerArgs,
params: PreOutputPreparationParams<
unknown,
unknown,
unknown,
BatchExecuteTransactionResult
>,
): Promise<HookResult> {
const { api, logger } = args;
const batchData = params.executeTransactionResult.updatedBatchData;
if (!batchData.success) {
return Promise.resolve({
breakFlow: false,
result: {
message: 'Batch transaction status failure',
},
});
}
for (const batchDataItem of [...batchData.transactions].filter(
(item) => item.command === TOKEN_CREATE_FT_FROM_FILE_COMMAND_NAME,
)) {
await this.saveToken(api, logger, batchDataItem);
}
return Promise.resolve({
breakFlow: false,
result: {
message: 'success',
},
});
}

private async saveToken(
api: CoreApi,
logger: Logger,
batchDataItem: BatchDataItem,
): Promise<void> {
const parseResult = CreateFtFromFileNormalizedParamsSchema.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 = buildTokenDataFromFile(
innerTransactionResult,
normalisedParams.tokenDefinition,
normalisedParams.treasury.accountId,
normalisedParams.adminKey.publicKey,
normalisedParams.network,
{
supplyPublicKey: normalisedParams.supplyKey?.publicKey,
wipePublicKey: normalisedParams.wipeKey?.publicKey,
kycPublicKey: normalisedParams.kycKey?.publicKey,
freezePublicKey: normalisedParams.freezeKey?.publicKey,
pausePublicKey: normalisedParams.pauseKey?.publicKey,
feeSchedulePublicKey: normalisedParams.feeScheduleKey?.publicKey,
},
);

tokenData.associations = await processTokenAssociations(
innerTransactionResult.tokenId,
normalisedParams.tokenDefinition.associations as Credential[],
api,
logger,
normalisedParams.keyManager,
);

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');

api.alias.register({
alias: normalisedParams.tokenDefinition.name,
type: AliasType.Token,
network: normalisedParams.network,
entityId: innerTransactionResult.tokenId,
createdAt: innerTransactionResult.consensusTimestamp,
});
logger.info(` Name registered: ${normalisedParams.tokenDefinition.name}`);
}
}
1 change: 1 addition & 0 deletions src/plugins/token/hooks/batch-create-ft-from-file/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TokenCreateFtFromFileBatchStateHook } from './handler';
56 changes: 56 additions & 0 deletions src/plugins/token/hooks/batch-create-ft-from-file/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { z } from 'zod';

import {
HtsDecimalsSchema,
KeySchema,
MemoSchema,
NetworkSchema,
PrivateKeySchema,
PrivateKeyWithAccountIdSchema,
ResolvedAccountCredentialSchema,
ResolvedPublicKeySchema,
TinybarSchema,
TokenNameSchema,
TokenSymbolSchema,
TokenTypeSchema,
} from '@/core/schemas/common-schemas';
import { keyManagerNameSchema } from '@/core/services/kms/kms-types.interface';
import { TokenFileCustomFeeSchema } from '@/plugins/token/schema';

export const FungibleTokenFileSchema = z.object({
name: TokenNameSchema,
symbol: TokenSymbolSchema,
decimals: HtsDecimalsSchema,
supplyType: z.union([z.literal('finite'), z.literal('infinite')]),
initialSupply: TinybarSchema,
maxSupply: TinybarSchema,
treasuryKey: PrivateKeyWithAccountIdSchema,
adminKey: PrivateKeySchema,
supplyKey: KeySchema.optional(),
wipeKey: KeySchema.optional(),
kycKey: KeySchema.optional(),
freezeKey: KeySchema.optional(),
pauseKey: KeySchema.optional(),
feeScheduleKey: KeySchema.optional(),
associations: z.array(PrivateKeyWithAccountIdSchema).default([]),
customFees: z
.array(TokenFileCustomFeeSchema)
.max(10, 'Maximum 10 custom fees allowed per token')
.default([]),
memo: MemoSchema.default(''),
tokenType: TokenTypeSchema,
});

export const CreateFtFromFileNormalizedParamsSchema = z.object({
keyManager: keyManagerNameSchema,
tokenDefinition: FungibleTokenFileSchema,
network: NetworkSchema,
treasury: ResolvedAccountCredentialSchema,
adminKey: ResolvedPublicKeySchema,
supplyKey: ResolvedPublicKeySchema.optional(),
wipeKey: ResolvedPublicKeySchema.optional(),
kycKey: ResolvedPublicKeySchema.optional(),
freezeKey: ResolvedPublicKeySchema.optional(),
pauseKey: ResolvedPublicKeySchema.optional(),
feeScheduleKey: ResolvedPublicKeySchema.optional(),
});
114 changes: 114 additions & 0 deletions src/plugins/token/hooks/batch-create-ft/handler.ts
Original file line number Diff line number Diff line change
@@ -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<HookResult> {
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<void> {
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}`);
}
}
}
1 change: 1 addition & 0 deletions src/plugins/token/hooks/batch-create-ft/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TokenCreateFtBatchStateHook } from './handler';
39 changes: 39 additions & 0 deletions src/plugins/token/hooks/batch-create-ft/types.ts
Original file line number Diff line number Diff line change
@@ -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(),
});
Loading
Loading