From 58cc9ec716ab49eb33fff10c2a9ba3751b7ad096 Mon Sep 17 00:00:00 2001 From: Logan Nguyen Date: Thu, 31 Aug 2023 22:23:50 -0500 Subject: [PATCH] dapp-feat: finished integrating ManageTokenInfo APIs to interact with TokenManage contract (#364) Signed-off-by: Logan Nguyen --- .../hts/shared/hooks/useToastSuccessful.tsx | 10 +- .../methods/handleSanitizeFormInputs.tsx | 77 +++- .../methods/FungibleTokenCreate.tsx | 2 +- .../manageTokenInfo/TokenExpiryForm.tsx | 58 +++ .../methods/manageTokenInfo/TokenInfoForm.tsx | 135 ++++++ .../methods/manageTokenInfo/TokenKeysForm.tsx | 161 +++++++ .../methods/manageTokenInfo/index.tsx | 417 ++++++++++++++++++ .../HTS/token-management-custom/constant.ts | 124 ------ .../HTS/token-management/constant.ts | 296 +++++++++++++ 9 files changed, 1147 insertions(+), 133 deletions(-) create mode 100644 system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/TokenExpiryForm.tsx create mode 100644 system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/TokenInfoForm.tsx create mode 100644 system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/TokenKeysForm.tsx create mode 100644 system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/index.tsx delete mode 100644 system-contract-dapp-playground/src/utils/contract-interactions/HTS/token-management-custom/constant.ts create mode 100644 system-contract-dapp-playground/src/utils/contract-interactions/HTS/token-management/constant.ts diff --git a/system-contract-dapp-playground/src/components/contract-interaction/hts/shared/hooks/useToastSuccessful.tsx b/system-contract-dapp-playground/src/components/contract-interaction/hts/shared/hooks/useToastSuccessful.tsx index 66a41b8d9..f31635302 100644 --- a/system-contract-dapp-playground/src/components/contract-interaction/hts/shared/hooks/useToastSuccessful.tsx +++ b/system-contract-dapp-playground/src/components/contract-interaction/hts/shared/hooks/useToastSuccessful.tsx @@ -36,6 +36,7 @@ interface HookProps { toastDescription?: string; setParamValues: Dispatch; initialTokenAddressesValues?: any; + initialKeyValues?: CommonKeyObject[]; transactionResults: TransactionResult[]; setIsSuccessful: Dispatch>; setWithCustomFee?: Dispatch>; @@ -54,6 +55,7 @@ export const useToastSuccessful = ({ setChosenKeys, setParamValues, setIsSuccessful, + initialKeyValues, toastDescription, resetParamValues, setWithCustomFee, @@ -78,6 +80,7 @@ export const useToastSuccessful = ({ if (setMetadata) setMetadata([]); setParamValues(resetParamValues); if (setWithCustomFee) setWithCustomFee(false); + if (initialKeyValues && setKeys) setKeys(initialKeyValues); if (setKeyTypesToShow) setKeyTypesToShow(new Set(HederaTokenKeyTypes)); if (setTokenAddresses) setTokenAddresses([initialTokenAddressesValues]); if (setChosenKeys) setChosenKeys(new Set()); @@ -94,13 +97,14 @@ export const useToastSuccessful = ({ setChosenKeys, setParamValues, setIsSuccessful, + initialKeyValues, setWithCustomFee, - resetParamValues, toastDescription, - setTokenAddresses, + resetParamValues, setKeyTypesToShow, - setCurrentTransactionPage, + setTokenAddresses, transactionResults.length, + setCurrentTransactionPage, initialTokenAddressesValues, ]); }; diff --git a/system-contract-dapp-playground/src/components/contract-interaction/hts/shared/methods/handleSanitizeFormInputs.tsx b/system-contract-dapp-playground/src/components/contract-interaction/hts/shared/methods/handleSanitizeFormInputs.tsx index dd12d4b40..0ade496c9 100644 --- a/system-contract-dapp-playground/src/components/contract-interaction/hts/shared/methods/handleSanitizeFormInputs.tsx +++ b/system-contract-dapp-playground/src/components/contract-interaction/hts/shared/methods/handleSanitizeFormInputs.tsx @@ -26,21 +26,35 @@ interface ParamsProps { name?: string; amount?: string; symbol?: string; + second?: string; treasury?: string; decimals?: string; - msgValue?: string; + feeValue?: string; maxSupply?: string; initSupply?: string; + serialNumber?: string; withCustomFee?: boolean; + accountAddress?: string; feeTokenAddress?: string; keys?: CommonKeyObject[]; - tokenAddresses?: string[]; + autoRenewPeriod?: string; + autoRenewAccount?: string; recipientAddress?: string; + tokenAddresses?: string[]; associatingAddress?: string; tokenAddressToMint?: string; hederaTokenAddress?: string; grantingKYCAccountAddress?: string; - API: 'TokenCreate' | 'Mint' | 'Associate' | 'GrantKYC'; + API: + | 'TokenCreate' + | 'Mint' + | 'Associate' + | 'GrantKYC' + | 'UpdateTokenInfo' + | 'UpdateTokenExpiry' + | 'APPROVED_FUNGIBLE' + | 'APPROVED_NON_FUNGIBLE' + | 'SET_APPROVAL'; } /** @dev handle sanitizing Hedera token form inputs */ export const handleSanitizeHederaFormInputs = ({ @@ -49,15 +63,20 @@ export const handleSanitizeHederaFormInputs = ({ keys, amount, symbol, - msgValue, + second, decimals, treasury, + feeValue, maxSupply, initSupply, + serialNumber, withCustomFee, tokenAddresses, + accountAddress, feeTokenAddress, + autoRenewPeriod, recipientAddress, + autoRenewAccount, tokenAddressToMint, associatingAddress, hederaTokenAddress, @@ -104,7 +123,7 @@ export const handleSanitizeHederaFormInputs = ({ } // service fee - if (!sanitizeErr && msgValue === '') { + if (!sanitizeErr && feeValue === '') { sanitizeErr = 'Service fee field cannot be empty'; } } else if (API === 'Mint') { @@ -132,6 +151,54 @@ export const handleSanitizeHederaFormInputs = ({ } else if (!isAddress(grantingKYCAccountAddress)) { sanitizeErr = 'Invalid token address'; } + } else if (API === 'UpdateTokenInfo') { + if (!isAddress(hederaTokenAddress)) { + sanitizeErr = 'Invalid token address'; + } else if (name === '') { + sanitizeErr = "Token name can't be empty"; + } else if (symbol === '') { + sanitizeErr = "Token symbol can't be empty"; + } else if (!isAddress(treasury)) { + sanitizeErr = 'Invalid treasury address'; + } else if (maxSupply === '' || Number(maxSupply) < 0) { + sanitizeErr = 'Max supply cannot be negative'; + } else if (feeValue === '') { + sanitizeErr = 'Gas limit should be set for this transaction'; + } + } else if (API === 'UpdateTokenExpiry') { + if (!isAddress(hederaTokenAddress)) { + sanitizeErr = 'Invalid token address'; + } else if (second === '' || Number(second) < 0) { + sanitizeErr = 'Invalid expiry time'; + } else if (!isAddress(autoRenewAccount)) { + sanitizeErr = 'Invalid auto renew account address'; + } else if (autoRenewPeriod === '' || Number(autoRenewPeriod) < 0) { + sanitizeErr = 'Invalid auto renew period'; + } else if (feeValue === '') { + sanitizeErr = 'Gas limit should be set for this transaction'; + } + } else if (API === 'APPROVED_FUNGIBLE') { + if (!isAddress(hederaTokenAddress)) { + sanitizeErr = 'Invalid token address'; + } else if (!isAddress(accountAddress)) { + sanitizeErr = 'Invalid account address'; + } else if (amount === '' || Number(amount) < 0) { + sanitizeErr = 'Invalid amount to approved'; + } + } else if (API === 'APPROVED_NON_FUNGIBLE') { + if (!isAddress(hederaTokenAddress)) { + sanitizeErr = 'Invalid token address'; + } else if (!isAddress(accountAddress)) { + sanitizeErr = 'Invalid account address'; + } else if (serialNumber === '' || Number(serialNumber) < 0) { + sanitizeErr = 'Invalid serial number approved'; + } + } else if (API === 'SET_APPROVAL') { + if (!isAddress(hederaTokenAddress)) { + sanitizeErr = 'Invalid token address'; + } else if (!isAddress(accountAddress)) { + sanitizeErr = 'Invalid account address'; + } } return sanitizeErr; diff --git a/system-contract-dapp-playground/src/components/contract-interaction/hts/token-create-custom/methods/FungibleTokenCreate.tsx b/system-contract-dapp-playground/src/components/contract-interaction/hts/token-create-custom/methods/FungibleTokenCreate.tsx index 8f9fff04a..f40dc4fe3 100644 --- a/system-contract-dapp-playground/src/components/contract-interaction/hts/token-create-custom/methods/FungibleTokenCreate.tsx +++ b/system-contract-dapp-playground/src/components/contract-interaction/hts/token-create-custom/methods/FungibleTokenCreate.tsx @@ -354,7 +354,7 @@ const FungibleTokenCreate = ({ baseContract }: PageProps) => { executeBtnTitle={'Create Fungible Token'} handleInputOnChange={handleInputOnChange} explanation={ - 'Represents the fee in HBAR directly paid to the contract system of the Hedera Token Service' + 'Represents the transaction fee paid in HBAR directly paid to the contract system of the Hedera Token Service' } handleInvokingAPIMethod={handleCreatingFungibleToken} /> diff --git a/system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/TokenExpiryForm.tsx b/system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/TokenExpiryForm.tsx new file mode 100644 index 000000000..ed5a5160e --- /dev/null +++ b/system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/TokenExpiryForm.tsx @@ -0,0 +1,58 @@ +/*- + * + * Hedera Smart Contracts + * + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { SharedFormInputField } from '../../../shared/components/ParamInputForm'; +import { htsUpdateTokenExpiryParamFields } from '@/utils/contract-interactions/HTS/token-management/constant'; + +interface PageProps { + paramValues: any; + tokenInfoFields: string[]; + handleInputOnChange: (e: any, param: string) => void; +} + +const TokenExpiryForm = ({ paramValues, tokenInfoFields, handleInputOnChange }: PageProps) => { + return ( +
+ {/* hederaTokenAddress & second & autoRenewAccount && autoRenewPeriod */} + {tokenInfoFields.map((param) => { + return ( +
+ +
+ ); + })} +
+ ); +}; + +export default TokenExpiryForm; diff --git a/system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/TokenInfoForm.tsx b/system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/TokenInfoForm.tsx new file mode 100644 index 000000000..98d4238f2 --- /dev/null +++ b/system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/TokenInfoForm.tsx @@ -0,0 +1,135 @@ +/*- + * + * Hedera Smart Contracts + * + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { SharedFormInputField, SharedFormButton } from '../../../shared/components/ParamInputForm'; +import { htsTokenCreateParamFields } from '@/utils/contract-interactions/HTS/token-create-custom/constant'; +import { htsUpdateTokenInfoParamFields } from '@/utils/contract-interactions/HTS/token-management/constant'; + +interface PageProps { + paramValues: any; + tokenInfoFields: string[][]; + setParamValues: (value: any) => void; + handleInputOnChange: (e: any, param: string) => void; +} + +const TokenInfoForm = ({ + paramValues, + setParamValues, + tokenInfoFields, + handleInputOnChange, +}: PageProps) => { + return ( +
+ {/* name & symbol & memo*/} +
+ {(tokenInfoFields as string[][])[0].map((param) => { + return ( +
+ +
+ ); + })} +
+ + {/* Token supply type */} +
+ {/* infinite */} + { + setParamValues((prev: any) => ({ ...prev, tokenSupplyType: false })); + }} + explanation={ + (htsUpdateTokenInfoParamFields as any)['tokenSupplyType'].explanation.infinite + } + /> + + {/* finite */} + { + setParamValues((prev: any) => ({ ...prev, tokenSupplyType: true })); + }} + explanation={(htsUpdateTokenInfoParamFields as any)['tokenSupplyType'].explanation.finite} + /> +
+ + {/* treasury & maxSupply */} + {tokenInfoFields[1].map((param) => { + return ( +
+ +
+ ); + })} + + {/* freeze status */} +
+ {/* false */} + { + setParamValues((prev: any) => ({ ...prev, isDefaultFreeze: false })); + }} + /> + + {/* true */} + { + setParamValues((prev: any) => ({ ...prev, isDefaultFreeze: true })); + }} + /> +
+
+ ); +}; + +export default TokenInfoForm; diff --git a/system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/TokenKeysForm.tsx b/system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/TokenKeysForm.tsx new file mode 100644 index 000000000..42ed9c087 --- /dev/null +++ b/system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/TokenKeysForm.tsx @@ -0,0 +1,161 @@ +/*- + * + * Hedera Smart Contracts + * + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Select, Input } from '@chakra-ui/react'; +import { convertCalmelCaseFunctionName } from '@/utils/common/helpers'; +import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'; +import { + handleKeyValueTypeOnChange, + handleUpdateKeyValue, +} from '../../../shared/methods/signingKeys'; +import { + CommonKeyObject, + IHederaTokenServiceKeyType, + IHederaTokenServiceKeyValueType, +} from '@/types/contract-interactions/HTS'; + +interface PageProps { + isSuccessful?: boolean; + keys: CommonKeyObject[]; + keyTypeArrays?: IHederaTokenServiceKeyType[]; + setKeys: Dispatch>; + HederaTokenKeyValueType: IHederaTokenServiceKeyValueType[]; +} + +const TokenKeysForm = ({ + keys, + setKeys, + isSuccessful, + keyTypeArrays, + HederaTokenKeyValueType, +}: PageProps) => { + const [keyRefs] = useState>({ + ADMIN: { + keyValueType: useRef(null), + keyValue: useRef(null), + }, + KYC: { + keyValueType: useRef(null), + keyValue: useRef(null), + }, + FREEZE: { + keyValueType: useRef(null), + keyValue: useRef(null), + }, + WIPE: { + keyValueType: useRef(null), + keyValue: useRef(null), + }, + SUPPLY: { + keyValueType: useRef(null), + keyValue: useRef(null), + }, + FEE: { + keyValueType: useRef(null), + keyValue: useRef(null), + }, + PAUSE: { + keyValueType: useRef(null), + keyValue: useRef(null), + }, + }); + + // reset select option's values + useEffect(() => { + if (isSuccessful && keyTypeArrays) { + keyTypeArrays.forEach((keyType) => { + if (keyRefs[keyType].keyValueType.current) + keyRefs[keyType].keyValueType.current.value = 'inheritAccountKey'; + if (keyRefs[keyType].keyValue.current) keyRefs[keyType].keyValue.current.value = ''; + }); + } + }, [isSuccessful, keyRefs, keyTypeArrays]); + + return ( +
+ {/* Key wrapper */} + {keys.map((key) => { + return ( +
+ {/* Key Type & Key Value Type */} +
+
+
{key.keyType}
+
+ +
+ {/* Key Value type */} + +
+
+ + {/* Key value */} +
+ {key.keyValueType === 'inheritAccountKey' ? ( + + ) : ( + handleUpdateKeyValue(e, key, setKeys)} + placeholder={ + key.keyValueType === 'contractId' || + key.keyValueType === 'delegatableContractId' + ? 'ID of a smart contract instance...' + : `${key.keyValueType.split('_')[0].toUpperCase()} compressed public key...` + } + size={'md'} + focusBorderColor={'#A98DF4'} + className={'w-full border-white/30'} + /> + )} +
+
+ ); + })} +
+ ); +}; + +export default TokenKeysForm; diff --git a/system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/index.tsx b/system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/index.tsx new file mode 100644 index 000000000..f72543d86 --- /dev/null +++ b/system-contract-dapp-playground/src/components/contract-interaction/hts/token-management-contract/methods/manageTokenInfo/index.tsx @@ -0,0 +1,417 @@ +/*- + * + * Hedera Smart Contracts + * + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import Cookies from 'js-cookie'; +import { Contract } from 'ethers'; +import TokenInfoForm from './TokenInfoForm'; +import { useToast } from '@chakra-ui/react'; +import TokenExpiryForm from './TokenExpiryForm'; +import { useState, useMemo, useEffect } from 'react'; +import { CommonErrorToast } from '@/components/toast/CommonToast'; +import { handleAPIErrors } from '../../../shared/methods/handleAPIErrors'; +import { useToastSuccessful } from '../../../shared/hooks/useToastSuccessful'; +import { manageTokenInfomation } from '@/api/hedera/tokenManagement-interactions'; +import { usePaginatedTxResults } from '../../../shared/hooks/usePaginatedTxResults'; +import { SharedSigningKeysComponent } from '../../../shared/components/SigningKeysForm'; +import { TransactionResultTable } from '../../../shared/components/TransactionResultTable'; +import { HederaTokenKeyTypes, TRANSACTION_PAGE_SIZE } from '../../../shared/states/commonStates'; +import { handleSanitizeHederaFormInputs } from '../../../shared/methods/handleSanitizeFormInputs'; +import { useUpdateTransactionResultsToLocalStorage } from '../../../shared/hooks/useUpdateLocalStorage'; +import { handleRetrievingTransactionResultsFromLocalStorage } from '../../../shared/methods/handleRetrievingTransactionResultsFromLocalStorage'; +import { + DEFAULT_HEDERA_TOKEN_INFO_VALUE, + DEFAULT_TOKEN_EXIPIRY_VALUE, + htsUpdateTokenInfoParamFields, +} from '@/utils/contract-interactions/HTS/token-management/constant'; +import { + SharedFormButton, + SharedFormInputField, + SharedExecuteButtonWithFee, +} from '../../../shared/components/ParamInputForm'; +import { + CommonKeyObject, + TransactionResult, + IHederaTokenServiceKeyType, + IHederaTokenServiceKeyValueType, + IHederaTokenServiceExpiry, + IHederaTokenServiceHederaToken, +} from '@/types/contract-interactions/HTS'; + +interface PageProps { + baseContract: Contract; +} + +type API_NAMES = 'UPDATE_INFO' | 'UPDATE_EXPIRY' | 'UPDATE_KEYS'; + +const ManageTokenInfo = ({ baseContract }: PageProps) => { + // general states + const toaster = useToast(); + const [isLoading, setIsLoading] = useState(false); + const [isSuccessful, setIsSuccessful] = useState(false); + const hederaNetwork = JSON.parse(Cookies.get('_network') as string); + const [APIMethods, setAPIMethods] = useState('UPDATE_INFO'); + const [currentTransactionPage, setCurrentTransactionPage] = useState(1); + const transactionResultStorageKey = 'HEDERA.HTS.TOKEN-MANAGEMENT.TOKEN-INFO-RESULTS'; + const [transactionResults, setTransactionResults] = useState([]); + const APIButtonTitles: { API: API_NAMES; apiSwitchTitle: string; executeTitle: string }[] = [ + { + API: 'UPDATE_INFO', + apiSwitchTitle: 'General Information', + executeTitle: 'Update Token Information', + }, + { API: 'UPDATE_EXPIRY', apiSwitchTitle: 'Token Exipry', executeTitle: 'Update Token Expiry' }, + { API: 'UPDATE_KEYS', apiSwitchTitle: 'Token Keys', executeTitle: 'Update Token Keys' }, + ]; + const tokenInfoFields = useMemo(() => { + switch (APIMethods) { + case 'UPDATE_INFO': + return [ + ['name', 'symbol', 'memo'], + ['treasury', 'maxSupply'], + ]; + case 'UPDATE_EXPIRY': + return ['second', 'autoRenewAccount', 'autoRenewPeriod']; + } + }, [APIMethods]); + const initialParamValues = { + name: '', + memo: '', + second: '', + symbol: '', + treasury: '', + feeValue: '', + maxSupply: '', + freezeStatus: false, + autoRenewPeriod: '', + autoRenewAccount: '', + tokenSupplyType: false, + hederaTokenAddress: '', + }; + const [paramValues, setParamValues] = useState(initialParamValues); + + const HederaTokenKeyValueType: IHederaTokenServiceKeyValueType[] = [ + 'inheritAccountKey', + 'contractId', + 'ed25519', + 'ECDSA_secp256k1', + 'delegatableContractId', + ]; + + const keyTypeArrays = [ + 'ADMIN', + 'KYC', + 'FREEZE', + 'WIPE', + 'SUPPLY', + 'FEE', + 'PAUSE', + ] as IHederaTokenServiceKeyType[]; + + const initialKeyValues = keyTypeArrays.map((keyType) => ({ + keyType: keyType, + keyValueType: 'inheritAccountKey', + keyValue: '', + })) as CommonKeyObject[]; + + // Keys states + const [keys, setKeys] = useState([]); // keeps track of keys array to pass to the API + const [chosenKeys, setChosenKeys] = useState(new Set()); // keeps track of keyTypes which have already been chosen in the list + const [keyTypesToShow, setKeyTypesToShow] = useState(new Set(HederaTokenKeyTypes)); // keeps track of the left over keyTypes to show in the drop down + + useEffect(() => { + setKeys((prev) => + prev.map((key) => { + key.keyValueType = 'inheritAccountKey'; + return key; + }) + ); + }, [APIMethods]); + + /** @dev retrieve token creation results from localStorage to maintain data on re-renders */ + useEffect(() => { + handleRetrievingTransactionResultsFromLocalStorage( + toaster, + transactionResultStorageKey, + setCurrentTransactionPage, + setTransactionResults + ); + }, [toaster]); + + // declare a paginatedTransactionResults + const paginatedTransactionResults = usePaginatedTxResults( + currentTransactionPage, + transactionResults + ); + + /** @dev handle form inputs on change */ + const handleInputOnChange = (e: any, param: string) => { + setParamValues((prev: any) => ({ ...prev, [param]: e.target.value })); + }; + + /** @dev handle invoking the API to interact with smart contract and update token information */ + const handleUpdatingTokenInfo = async (API: API_NAMES) => { + // destructuring param values + const { + name, + memo, + second, + symbol, + treasury, + feeValue, + maxSupply, + freezeStatus, + autoRenewPeriod, + tokenSupplyType, + autoRenewAccount, + hederaTokenAddress, + } = paramValues; + + // sanitize params + let sanitizeErr; + switch (API) { + case 'UPDATE_INFO': + sanitizeErr = handleSanitizeHederaFormInputs({ + API: 'UpdateTokenInfo', + hederaTokenAddress, + maxSupply, + feeValue, + treasury, + symbol, + name, + }); + break; + case 'UPDATE_EXPIRY': + sanitizeErr = handleSanitizeHederaFormInputs({ + API: 'UpdateTokenExpiry', + second, + feeValue, + autoRenewPeriod, + autoRenewAccount, + hederaTokenAddress, + }); + break; + } + // toast error if any param is invalid + if (sanitizeErr) { + CommonErrorToast({ toaster, title: 'Invalid parameters', description: sanitizeErr }); + return; + } + + // prepare params for manageTokenInformation() API method + const tokenInfo = { + ...DEFAULT_HEDERA_TOKEN_INFO_VALUE, + name, + symbol, + memo, + treasury, + maxSupply, + freezeStatus, + tokenSupplyType, + } as IHederaTokenServiceHederaToken; + + const expiryInfo = { + ...DEFAULT_TOKEN_EXIPIRY_VALUE, + second: Number(second), + autoRenewAccount, + autoRenewPeriod, + } as IHederaTokenServiceExpiry; + + // turn is loading on + setIsLoading(true); + + // invoke method APIS + const { result, transactionHash, err } = await manageTokenInfomation( + baseContract, + APIMethods, + hederaTokenAddress, + Number(feeValue), + tokenInfo, + expiryInfo, + keys + ); + + // turn is loading on + setIsLoading(false); + + // handle err + if (err || !result) { + handleAPIErrors({ + err, + toaster, + transactionHash, + setTransactionResults, + tokenAddress: hederaTokenAddress, + }); + return; + } else { + // handle succesfull + setTransactionResults((prev) => [ + ...prev, + { + status: 'sucess', + tokenAddress: hederaTokenAddress, + txHash: transactionHash as string, + }, + ]); + + setIsSuccessful(true); + } + }; + + /** @dev listen to change event on transactionResults state => load to localStorage */ + useUpdateTransactionResultsToLocalStorage(transactionResults, transactionResultStorageKey); + + /** @dev toast successful */ + useToastSuccessful({ + toaster, + setKeys, + isSuccessful, + setParamValues, + setIsSuccessful, + initialKeyValues, + transactionResults, + setCurrentTransactionPage, + resetParamValues: initialParamValues, + toastTitle: 'Token update successful', + }); + + return ( +
+ {/* Update token form */} +
+ {/* notice component */} +

+ *important: Should you choose not to update + certain fields, kindly populate the token's current values. +

+ + {/* API methods */} +
+ {APIButtonTitles.map((APIButton) => { + return ( +
+ setAPIMethods(APIButton.API)} + explanation={''} + /> +
+ ); + })} +
+ + {/* Hedera token address */} + + + {/* UPDATE_INFO form */} + {APIMethods === 'UPDATE_INFO' && ( + + )} + + {/* UPDATE_EXPIRY form */} + {APIMethods === 'UPDATE_EXPIRY' && ( + + )} + + {/* UPDATE_KEYS form */} + {APIMethods === 'UPDATE_KEYS' && ( + <> + {/* keys */} + + + )} + + {/* Execute buttons */} + {APIButtonTitles.map((APIButton) => { + if (APIMethods === APIButton.API) { + return ( +
+ handleUpdatingTokenInfo(APIButton.API)} + /> +
+ ); + } + })} +
+ + {/* transaction results table */} + {transactionResults.length > 0 && ( + + )} +
+ ); +}; + +export default ManageTokenInfo; diff --git a/system-contract-dapp-playground/src/utils/contract-interactions/HTS/token-management-custom/constant.ts b/system-contract-dapp-playground/src/utils/contract-interactions/HTS/token-management-custom/constant.ts deleted file mode 100644 index c8be7ccb8..000000000 --- a/system-contract-dapp-playground/src/utils/contract-interactions/HTS/token-management-custom/constant.ts +++ /dev/null @@ -1,124 +0,0 @@ -/*- - * - * Hedera Smart Contracts - * - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -import { - IHederaTokenServiceExpiry, - IHederaTokenServiceHederaToken, -} from '@/types/contract-interactions/HTS'; -import { ethers } from 'ethers'; - -/** - * @notice an object for the IhederaTokenService.Expiry - */ -export const DEFAULT_TOKEN_EXIPIRY_VALUE: IHederaTokenServiceExpiry = { - second: 0, - autoRenewPeriod: 0, - autoRenewAccount: ethers.ZeroAddress, -}; - -/** - * @notice an object for the IHederaTokenService.HederaToken default values - */ -export const DEFAULT_HEDERA_TOKEN_INFO_VALUE: IHederaTokenServiceHederaToken = { - memo: '', - name: '', - symbol: '', - treasury: '', - maxSupply: 0, - tokenKeys: [], - freezeDefault: false, - tokenSupplyType: false, - expiry: DEFAULT_TOKEN_EXIPIRY_VALUE, -}; - -/** @notice an object holding information for the updateTokenInfo's input fields */ -export const htsUpdateTokenInfoParamFields = { - hederaTokenAddress: { - inputType: 'text', - inputPlaceholder: 'Token address...', - inputSize: 'md', - inputFocusBorderColor: '#A98DF4', - inputClassname: 'w-full border-white/30', - paramKey: 'hederaTokenAddress', - explanation: 'represents the Hedera Token for updating', - }, - name: { - inputType: 'text', - inputPlaceholder: 'Name of the token...', - inputSize: 'md', - inputFocusBorderColor: '#A98DF4', - inputClassname: 'w-full border-white/30', - paramKey: 'name', - explanation: 'represents the name by which the token should be known', - }, - symbol: { - inputType: 'text', - inputPlaceholder: 'Ticket symbol of the token...', - inputSize: 'md', - inputFocusBorderColor: '#A98DF4', - inputClassname: 'w-full border-white/30', - paramKey: 'symbol', - explanation: 'represents the ticket symbol of the token', - }, - memo: { - inputType: 'text', - inputPlaceholder: 'A memo for the token...', - inputSize: 'md', - inputFocusBorderColor: '#A98DF4', - inputClassname: 'w-full border-white/30', - paramKey: 'memo', - explanation: 'represents an optional note that can be attached to a token transfer', - }, - maxSupply: { - inputType: 'number', - inputPlaceholder: 'Max supply...', - inputSize: 'md', - inputFocusBorderColor: '#A98DF4', - inputClassname: 'w-full border-white/30', - paramKey: 'maxSupply', - explanation: 'defines the maximum number of tokens that can ever exist for the token', - }, - treasury: { - inputType: 'text', - inputPlaceholder: 'The token treasury account ID...', - inputSize: 'md', - inputFocusBorderColor: '#A98DF4', - inputClassname: 'w-full border-white/30', - paramKey: 'treasury', - explanation: - 'represents the account will receive the specified initial supply or the newly minted NFTs', - }, - tokenSupplyType: { - paramKey: 'tokenSupplyType', - explanation: { - infinite: 'Indicates that tokens of that type have an upper bound of Long.MAX_VALUE.', - finite: - 'Indicates that tokens of that type have an upper bound of maxSupply, provided on token creation.', - }, - }, - freezeStatus: { - paramKey: 'freezeStatus', - explanation: { - off: 'Accounts can receive the token without needing to be unfrozen', - on: ' Accounts must be unfrozen before they can receive the token ', - }, - }, -}; - diff --git a/system-contract-dapp-playground/src/utils/contract-interactions/HTS/token-management/constant.ts b/system-contract-dapp-playground/src/utils/contract-interactions/HTS/token-management/constant.ts new file mode 100644 index 000000000..91b9905d4 --- /dev/null +++ b/system-contract-dapp-playground/src/utils/contract-interactions/HTS/token-management/constant.ts @@ -0,0 +1,296 @@ +/*- + * + * Hedera Smart Contracts + * + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { + IHederaTokenServiceExpiry, + IHederaTokenServiceHederaToken, +} from '@/types/contract-interactions/HTS'; +import { ethers } from 'ethers'; + +/** + * @notice an object for the IhederaTokenService.Expiry + */ +export const DEFAULT_TOKEN_EXIPIRY_VALUE: IHederaTokenServiceExpiry = { + second: 0, + autoRenewPeriod: 0, + autoRenewAccount: ethers.ZeroAddress, +}; + +/** + * @notice an object for the IHederaTokenService.HederaToken default values + */ +export const DEFAULT_HEDERA_TOKEN_INFO_VALUE: IHederaTokenServiceHederaToken = { + memo: '', + name: '', + symbol: '', + treasury: '', + maxSupply: 0, + tokenKeys: [], + freezeDefault: false, + tokenSupplyType: false, + expiry: DEFAULT_TOKEN_EXIPIRY_VALUE, +}; + +/** @notice an object holding information for the updateTokenInfo's input fields */ +export const htsUpdateTokenInfoParamFields = { + hederaTokenAddress: { + inputType: 'text', + inputPlaceholder: 'Token address...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'hederaTokenAddress', + explanation: 'represents the Hedera Token for updating', + }, + name: { + inputType: 'text', + inputPlaceholder: 'Name of the token...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'name', + explanation: 'represents the name by which the token should be known', + }, + symbol: { + inputType: 'text', + inputPlaceholder: 'Ticket symbol of the token...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'symbol', + explanation: 'represents the ticket symbol of the token', + }, + memo: { + inputType: 'text', + inputPlaceholder: 'A memo for the token...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'memo', + explanation: 'represents an optional note that can be attached to a token transfer', + }, + maxSupply: { + inputType: 'number', + inputPlaceholder: 'Max supply...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'maxSupply', + explanation: 'defines the maximum number of tokens that can ever exist for the token', + }, + treasury: { + inputType: 'text', + inputPlaceholder: 'The token treasury account ID...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'treasury', + explanation: + 'represents the account will receive the specified initial supply or the newly minted NFTs', + }, + tokenSupplyType: { + paramKey: 'tokenSupplyType', + explanation: { + infinite: 'Indicates that tokens of that type have an upper bound of Long.MAX_VALUE.', + finite: + 'Indicates that tokens of that type have an upper bound of maxSupply, provided on token creation.', + }, + }, + freezeStatus: { + paramKey: 'freezeStatus', + explanation: { + off: 'Accounts can receive the token without needing to be unfrozen', + on: ' Accounts must be unfrozen before they can receive the token ', + }, + }, +}; + +/** @notice an object holding information for the updateTokenExpiry's input fields */ +export const htsUpdateTokenExpiryParamFields = { + hederaTokenAddress: { + inputType: 'text', + inputPlaceholder: 'Token address...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'hederaTokenAddress', + explanation: 'represents the Hedera Token for updating', + }, + second: { + inputType: 'number', + inputPlaceholder: 'The new expiry time of the token...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'second', + explanation: 'represents the epoch second at which the token should expire', + }, + autoRenewAccount: { + inputType: 'text', + inputPlaceholder: 'Account address...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'autoRenewAccount', + explanation: + "represents the new account which will be automatically charged to renew the token's expiration", + }, + autoRenewPeriod: { + inputType: 'number', + inputPlaceholder: 'Expiry interval...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'autoRenewPeriod', + explanation: + "represents the new interval at which the auto-renew account will be charged to extend the token's expiry. The default auto-renew period is 131,500 minutes.", + }, +}; + +/** @notice an object holding information for the tokenPermission's input fields */ +export const htsTokenPermissionParamFields = { + hederaTokenAddress: { + inputType: 'text', + inputPlaceholder: 'Token address...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'hederaTokenAddress', + explanation: 'represents the Hedera Token for updating', + }, + targetApprovedAddress: { + inputType: 'text', + inputPlaceholder: 'Account address...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'targetApprovedAddress', + explanation: 'represents the operator of the update transaction', + }, + amountToApprove: { + inputType: 'number', + inputPlaceholder: 'The amount to approved...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'amountToApprove', + explanation: 'represents the allocated allowance for the operator', + }, + serialNumber: { + inputType: 'number', + inputPlaceholder: 'Serial number...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'serialNumber', + explanation: "represents the NFT's serial number to be approved for the operator", + }, + approvedStatus: { + paramKey: 'approvedStatus', + explanation: { + on: 'authorize the operator to utilize the allowance on behalf of the token owner.', + off: "revoke the operator's authorization to utilize the allowance on behalf of the token owner", + }, + }, +}; + +/** @notice an object holding information for the tokenStatus's input fields */ +export const htsTokenStatusParamFields = { + hederaTokenAddress: { + inputType: 'text', + inputPlaceholder: 'Token address...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'hederaTokenAddress', + explanation: 'represents the Hedera Token for updating', + }, +}; + +/** @notice an object holding information for the tokenRelation's input fields */ +export const htsTokenRelationParamFields = { + hederaTokenAddress: { + inputType: 'text', + inputPlaceholder: 'Token address...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'hederaTokenAddress', + explanation: 'represents the Hedera Token for updating', + }, + hederaTokenAddresses: { + inputType: 'text', + inputPlaceholder: 'Token addresses (comma-separated)...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'hederaTokenAddresses', + explanation: 'represents the tokens to be dissociated with the provided account', + }, + accountAddress: { + inputType: 'text', + inputPlaceholder: 'Account address...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'accountAddress', + explanation: 'represents the account address of the update transaction', + }, +}; + +/** @notice an object holding information for the tokenDeduction's input fields */ +export const htsTokenDeductionParamFields = { + hederaTokenAddress: { + inputType: 'text', + inputPlaceholder: 'Token address...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'hederaTokenAddress', + explanation: 'represents the Hedera Token for updating', + }, + accountAddress: { + inputType: 'text', + inputPlaceholder: 'Account address...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'accountAddress', + explanation: 'represents the account address of the update transaction', + }, + amount: { + inputType: 'number', + inputPlaceholder: 'The amount of token...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'amount', + explanation: 'represents the amount of token to be deducted from the transaction', + }, + serialNumbers: { + inputType: 'text', + inputPlaceholder: 'Serial numbers (comma-separated)...', + inputSize: 'md', + inputFocusBorderColor: '#A98DF4', + inputClassname: 'w-full border-white/30', + paramKey: 'serialNumbers', + explanation: "represents the NFT's serial numbers to be deducted from the transaction", + }, +};