@hashgraph/hedera-local
Version:
Developer tooling for running Local Hedera Network (Consensus + Mirror Nodes).
216 lines (192 loc) • 9.02 kB
text/typescript
// 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);
}
}