UNPKG

@hashgraph/hedera-local

Version:

Developer tooling for running Local Hedera Network (Consensus + Mirror Nodes).

216 lines (192 loc) 9.02 kB
// SPDX-License-Identifier: Apache-2.0 import { TokenId, TokenType } from '@hashgraph/sdk'; import { IOBserver } from '../controller/IObserver'; import { LoggerService } from '../services/LoggerService'; import { ServiceLocator } from '../services/ServiceLocator'; import { IState } from './IState'; import { CLIService } from '../services/CLIService'; import { ClientService } from '../services/ClientService'; import { accounts, tokens } from '../configuration/initialResources.json'; import { EventType } from '../types/EventType'; import { TokenUtils } from '../utils/TokenUtils'; import { ITokenProps } from '../configuration/types/ITokenProps'; import { IAccountProps } from '../configuration/types/IAccountProps'; import { AccountUtils } from '../utils/AccountUtils'; import { LOADING, RESOURCE_CREATION_STATE_INIT_MESSAGE } from '../constants'; /** * Represents the state of resource creation. * This class is responsible for initializing the ResourceCreationState object. * * Uses {@link accounts} and {@link tokens} from the initialResources.json * to create initial resources for the local-node environment * * @implements {IState} */ export class ResourceCreationState implements IState { /** * The name of the state. */ private readonly stateName: string; /** * The logger used for logging resource creation state information. */ private readonly logger: LoggerService; /** * The CLI service used for resource creation. */ private readonly cliService: CLIService; /** * The client service used for resource creation. */ private readonly clientService: ClientService; /** * The observer for the resource creation state. */ private observer: IOBserver | undefined; /** * Represents the state of resource creation. * This class is responsible for initializing the ResourceCreationState object. */ constructor() { this.stateName = ResourceCreationState.name; this.logger = ServiceLocator.Current.get<LoggerService>(LoggerService.name); this.cliService = ServiceLocator.Current.get<CLIService>(CLIService.name); this.clientService = ServiceLocator.Current.get<ClientService>(ClientService.name); this.logger.trace(RESOURCE_CREATION_STATE_INIT_MESSAGE, this.stateName); } /** * Subscribes an observer to receive updates from the ResourceCreationState. * @param {IOBserver} observer The observer to subscribe. */ public subscribe(observer: IOBserver): void { this.observer = observer; } /** * This method is responsible for starting the ResourceCreationState. * It creates tokens asynchronously or synchronously based on the CLI arguments. * @returns Promise that resolves when the state is started. * @emits {EventType.Finish} When the state is finished. */ public async onStart(): Promise<void> { const { async, createInitialResources } = this.cliService.getCurrentArgv(); if (!createInitialResources) { this.observer!.update(EventType.Finish); return; } const mode = async ? 'asynchronous' : 'synchronous'; this.logger.info( `${LOADING} Starting Resource Creation state in ${mode} mode...`, this.stateName); const promise = this.createResources(); if (!async) { await promise; } this.observer!.update(EventType.Finish); } /** * Creates accounts and tokens with the given properties and associates them. * @returns Promise that resolves when all resources are created. */ private async createResources(): Promise<void> { const accountProps: IAccountProps[] = accounts as IAccountProps[]; const tokenProps: ITokenProps[] = tokens as ITokenProps[]; const tokenIds: Map<string, TokenId> = await this.createTokens(tokenProps); await this.createAndAssociateAccounts(accountProps, tokenIds); await this.mintTokens(tokenProps, tokenIds); } /** * Creates accounts with the given properties. * @param accountProps The properties of the accounts to create. * @param tokenIdsBySymbol Map of token symbols to token IDs. * @returns Promise that resolves when all accounts are created (and associated with tokens). */ private async createAndAssociateAccounts(accountProps: IAccountProps[], tokenIdsBySymbol: Map<string, TokenId>): Promise<void> { this.logger.info('Creating accounts', this.stateName); const client = this.clientService.getClient(); const promises = accountProps.map(async (account: IAccountProps): Promise<void> => { const { privateKey, accountInfo } = await AccountUtils.createAccountFromProps(account, client); this.logger.info( `Successfully created account with: * normal account ID: ${accountInfo.accountId.toString()} * aliased account ID: ${accountInfo.aliasKey ? `0.0.${ accountInfo.aliasKey?.toString()}` : 'N/A'} * private key (use this in SDK/Hedera-native wallets): ${privateKey.toStringDer()} * raw private key (use this for JSON RPC wallet import): ${privateKey.toStringRaw()}`, this.stateName); if (account.associatedTokens && account.associatedTokens.length > 0) { const associatedTokenIds = this.getTokenIdsFor(account.associatedTokens, tokenIdsBySymbol); await TokenUtils.associateAccountWithTokens(accountInfo.accountId, associatedTokenIds, privateKey, client); this.logger.info( `Associated account ${accountInfo.accountId} with tokens: ${associatedTokenIds.join(', ')}`, this.stateName ); } }); await Promise.all(promises); } /** * Creates tokens with the given properties. * @param tokenProps The properties of the tokens to create. * @returns Promise that resolves with a map of token symbols to token IDs. */ private async createTokens(tokenProps: ITokenProps[]): Promise<Map<string, TokenId>> { this.logger.info('Creating tokens', this.stateName); const client = this.clientService.getClient(); const promises = tokenProps.map(async (token: ITokenProps): Promise<[string, TokenId]> => { const tokenId = await TokenUtils.createToken(token, client); this.logger.info( `Successfully created ${token.tokenType} token '${token.tokenSymbol}' with ID ${tokenId}`, this.stateName ); return [token.tokenSymbol, tokenId]; }); return new Map<string, TokenId>(await Promise.all(promises)); } /** * Gets the token IDs associated with the given token symbols. * @param tokenSymbols The token symbols to get IDs for. * @param tokenIdsBySymbol Map of token symbols to token IDs. * @returns The token IDs associated with the account. */ private getTokenIdsFor(tokenSymbols: string[], tokenIdsBySymbol: Map<string, TokenId>): TokenId[] { return tokenSymbols?.filter(tokenSymbol => { if (!tokenIdsBySymbol.has(tokenSymbol)) { this.logger.warn(`Token ID for ${tokenSymbol} not found`, this.stateName); return false; } return true; }).map(tokenSymbol => tokenIdsBySymbol.get(tokenSymbol)!) || []; } /** * Mints tokens with the given properties. * @param tokenProps The properties of the tokens to mint. * @param tokenIdsBySymbol Map of token symbols to token IDs. */ private async mintTokens(tokenProps: ITokenProps[], tokenIdsBySymbol: Map<string, TokenId>): Promise<void> { this.logger.info('Minting NFTs', this.stateName); const client = this.clientService.getClient(); const promises = tokenProps .filter(token => { const isNft = token.tokenType === TokenType.NonFungibleUnique.toString(); const shouldMint = isNft && !!token.mints?.length; if (shouldMint && !tokenIdsBySymbol.has(token.tokenSymbol)) { this.logger.warn(`Token ID for ${token.tokenSymbol} not found`, this.stateName); return false; } return shouldMint; }) .map(async (token: ITokenProps): Promise<void> => { const tokenId = tokenIdsBySymbol.get(token.tokenSymbol)!; const supplyKey = TokenUtils.getSupplyKey(token); await Promise.all(token.mints!.map(async ({ CID }) => { await TokenUtils.mintToken(tokenId, CID, supplyKey, client); this.logger.info( `Minted token ID ${tokenId} with CID '${CID}'`, this.stateName ); })); }); await Promise.all(promises); } }