UNPKG

@hashgraph/hedera-local

Version:

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

270 lines (248 loc) 10.2 kB
// SPDX-License-Identifier: Apache-2.0 import { AccountId, Client, CustomFee, PrivateKey, TokenAssociateTransaction, TokenCreateTransaction, TokenId, TokenMintTransaction, TokenSupplyType, TokenType, TransactionReceipt } from '@hashgraph/sdk'; import { ITokenProps } from '../configuration/types/ITokenProps'; import { getPrivateKey } from '../configuration/types/IPrivateKey'; /** * Provides utility methods for working with tokens. */ export class TokenUtils { /** * Associates an account with the given tokens. * @param accountId The account ID to associate. * @param tokenIds The token IDs to associate. * @param accountKey The account key to sign the transaction. * @param client The client to use for associating the account with tokens. * @returns {Promise<void>} * A promise that resolves when the account is associated with the tokens. */ public static async associateAccountWithTokens(accountId: AccountId, tokenIds: TokenId[], accountKey: PrivateKey, client: Client): Promise<void> { const signTx = await new TokenAssociateTransaction() .setAccountId(accountId) .setTokenIds(tokenIds) .freezeWith(client) .sign(accountKey); const txResponse = await signTx.execute(client); await txResponse.getReceipt(client); } /** * Mints the given amount of tokens for the given token. * @param tokenId The token ID to mint. * @param CID The CID metadata for the minted tokens. * @param supplyKey The supply key to sign the transaction. * @param client The client to use for minting the tokens. * @returns {TransactionReceipt} The receipt of the mint transaction. */ public static async mintToken(tokenId: TokenId, CID: string, supplyKey: PrivateKey, client: Client): Promise<TransactionReceipt> { const transaction = new TokenMintTransaction() .setTokenId(tokenId) .setMetadata([Buffer.from(CID)]) .freezeWith(client); const signTx = await transaction.sign(supplyKey); const txResponse = await signTx.execute(client); return txResponse.getReceipt(client); } /** * Creates a token with the given properties. * @param token The properties of the token to create. * @param client The client to use for creating the token. * @returns {TokenId} The ID of the created token. */ public static async createToken(token: ITokenProps, client: Client): Promise<TokenId> { const transaction = this.getTokenCreateTransaction(token); let signTx: TokenCreateTransaction = transaction.freezeWith(client); if (token.adminKey) { signTx = await signTx.sign(getPrivateKey(token.adminKey)); } signTx = await signTx.signWithOperator(client); const txResponse = await signTx.execute(client); const receipt = await txResponse.getReceipt(client); return receipt.tokenId!; } /** * Returns the supply key for the given token. * * NOTE: The operator key will be used as a supply key by default, * if the supply key is not provided in the properties * * @param token The properties of the token. * @returns {PrivateKey} The supply key for the token. */ public static getSupplyKey(token: ITokenProps): PrivateKey { // The operator key will be used as supply key if one is not provided if (token.supplyKey) { return getPrivateKey(token.supplyKey); } return PrivateKey.fromStringED25519(process.env.RELAY_OPERATOR_KEY_MAIN!); } /** * Returns the treasury account ID for the given token. * * NOTE: The operator ID will be used as a treasury account ID by default, * if the treasury key is not provided in the properties * * @param token The properties of the token. * @returns {AccountId} The treasury account ID for the token. */ public static getTreasuryAccountId(token: ITokenProps): AccountId { // The operator key will be used as treasury key if one is not provided if (token.treasuryKey) { return getPrivateKey(token.treasuryKey).publicKey.toAccountId(0, 0); } return AccountId.fromString(process.env.RELAY_OPERATOR_ID_MAIN!); } /** * Creates a token create transaction with the given properties. * @param token The properties of the token to create. * @returns {TokenCreateTransaction} The token create transaction. */ private static getTokenCreateTransaction(token: ITokenProps): TokenCreateTransaction { this.validateTokenProperties(token); const transaction = new TokenCreateTransaction(); this.setRequiredProperties(transaction, token); this.setKeyProperties(transaction, token); this.setOptionalProperties(transaction, token); return transaction; } /** * Sets the required properties of the token create transaction. * @param transaction The transaction to set the properties on. * @param token The properties of the token to create. */ private static setRequiredProperties(transaction: TokenCreateTransaction, token: ITokenProps): void { transaction.setTokenName(token.tokenName); transaction.setTokenSymbol(token.tokenSymbol); transaction.setTreasuryAccountId(this.getTreasuryAccountId(token)); transaction.setSupplyKey(this.getSupplyKey(token)); // If not provided, the TokenType is FUNGIBLE_COMMON by default if (token.tokenType === TokenType.NonFungibleUnique.toString()) { transaction.setTokenType(TokenType.NonFungibleUnique); transaction.setInitialSupply(0); } else { transaction.setTokenType(TokenType.FungibleCommon); if (token.initialSupply) { transaction.setInitialSupply(token.initialSupply); } if (token.decimals) { transaction.setDecimals(token.decimals); } } // If not provided, the TokenSupplyType is INFINITE by default if (token.supplyType === TokenSupplyType.Finite.toString()) { transaction.setSupplyType(TokenSupplyType.Finite); if (token.maxSupply) { transaction.setMaxSupply(token.maxSupply); } } else { transaction.setSupplyType(TokenSupplyType.Infinite); } } /** * Sets the key properties of the token create transaction. * @param transaction The transaction to set the properties on. * @param token The properties of the token to create. */ private static setKeyProperties(transaction: TokenCreateTransaction, token: ITokenProps): void { if (token.adminKey) { transaction.setAdminKey(getPrivateKey(token.adminKey)); } if (token.kycKey) { transaction.setKycKey(getPrivateKey(token.kycKey)); } if (token.freezeKey) { transaction.setFreezeKey(getPrivateKey(token.freezeKey)); } if (token.pauseKey) { transaction.setPauseKey(getPrivateKey(token.pauseKey)); } if (token.wipeKey) { transaction.setWipeKey(getPrivateKey(token.wipeKey)); } if (token.feeScheduleKey) { transaction.setFeeScheduleKey(getPrivateKey(token.feeScheduleKey)); } } /** * Sets the optional properties of the token create transaction. * @param transaction The transaction to set the properties on. * @param token The properties of the token to create. */ private static setOptionalProperties(transaction: TokenCreateTransaction, token: ITokenProps): void { if (token.freezeDefault !== undefined) { transaction.setFreezeDefault(token.freezeDefault); } if (token.autoRenewAccountId) { transaction.setAutoRenewAccountId(token.autoRenewAccountId); } if (token.expirationTime) { transaction.setExpirationTime(new Date(token.expirationTime)); } if (token.autoRenewPeriod) { transaction.setAutoRenewPeriod(token.autoRenewPeriod); } if (token.tokenMemo) { transaction.setTokenMemo(token.tokenMemo); } if (token.customFees) { // TODO: Test this transaction.setCustomFees(token.customFees.map(CustomFee._fromProtobuf)); } } private static validateTokenProperties(token: ITokenProps): void { this.assertTruthy(token.tokenName, 'Token name is required'); this.assertTruthy(token.tokenSymbol, 'Token symbol is required'); this.assertTruthy(token.tokenType, 'Token type is required'); this.assertTruthy(token.supplyType, 'Supply type is required'); // If the token type is NON_FUNGIBLE_UNIQUE, // the initial supply must be 0 and decimals must be undefined if (token.tokenType === TokenType.NonFungibleUnique.toString()) { this.assertFalsy(token.initialSupply, 'Initial supply must be 0 or undefined for non-fungible tokens'); this.assertFalsy(token.decimals, 'Decimals must be 0 or undefined for non-fungible tokens'); } else { this.assertTruthy(token.initialSupply, 'Initial supply is required for fungible tokens'); this.assertTruthy(token.decimals, 'Decimals is required for fungible tokens'); } // If the token supply type is FINITE, the max supply must be provided if (token.supplyType === TokenSupplyType.Finite.toString()) { this.assertTruthy(token.maxSupply, 'Max supply is required for finite supply tokens'); } else { this.assertFalsy(token.maxSupply, `Max supply must be undefined for infinite supply tokens, was ${token.maxSupply}`); } if (token.autoRenewPeriod) { this.assertTruthy(token.autoRenewAccountId, 'Auto renew account ID is required for auto renew period'); this.assertInRange(token.autoRenewPeriod, 2_592_000, 8_000_000, 'Auto renew period must be between 30 days and 3 months'); } } private static assertTruthy(condition: unknown, errorMessage: string): void { if (!condition) { throw new Error(errorMessage); } } private static assertFalsy(condition: unknown, errorMessage: string): void { if (condition) { throw new Error(errorMessage); } } private static assertInRange(value: number, min: number, max: number, errorMessage: string): void { if (value < min || value > max) { throw new Error(errorMessage); } } }