Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dapp-feat: ExchangeRate contract feature complete #418

Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const handleExchangeRate = async (

const { data } = txReceipt.logs.filter((event: any) => event.fragment.name === eventNameMap[API])[0];

return { transactionHash: txReceipt.hash, convertedAmount: data };
return { transactionHash: txReceipt.hash, convertedAmount: Number(data) };
} catch (err: any) {
console.error(err);
return { err, transactionHash: err.receipt && err.receipt.hash };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/*-
*
* 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 { useToast } from '@chakra-ui/react';
import { useEffect, useState } from 'react';
import { CommonErrorToast } from '@/components/toast/CommonToast';
import { TransactionResult } from '@/types/contract-interactions/HTS';
import { handleAPIErrors } from '../../hts/shared/methods/handleAPIErrors';
import { handleExchangeRate } from '@/api/hedera/exchange-rate-interactions';
import { TRANSACTION_PAGE_SIZE } from '../../hts/shared/states/commonStates';
import { useToastSuccessful } from '../../hts/shared/hooks/useToastSuccessful';
import { usePaginatedTxResults } from '../../hts/shared/hooks/usePaginatedTxResults';
import { TransactionResultTable } from '../../hts/shared/components/TransactionResultTable';
import { handleSanitizeHederaFormInputs } from '../../hts/shared/methods/handleSanitizeFormInputs';
import { SharedExecuteButton, SharedFormInputField } from '../../hts/shared/components/ParamInputForm';
import { useUpdateTransactionResultsToLocalStorage } from '../../hts/shared/hooks/useUpdateLocalStorage';
import useFilterTransactionsByContractAddress from '../../hts/shared/hooks/useFilterTransactionsByContractAddress';
import { handleRetrievingTransactionResultsFromLocalStorage } from '../../hts/shared/methods/handleRetrievingTransactionResultsFromLocalStorage';
import {
CONTRACT_NAMES,
HEDERA_BRANDING_COLORS,
HEDERA_CHAKRA_INPUT_BOX_SIZES,
HEDERA_TRANSACTION_RESULT_STORAGE_KEYS,
HEDERA_CHAKRA_INPUT_BOX_SHARED_CLASSNAME,
} from '@/utils/common/constants';

interface PageProps {
baseContract: Contract;
}

type API_NAMES = 'CENT_TO_BAR' | 'BAR_TO_CENT';

const HederaExchangeRateMethods = ({ baseContract }: PageProps) => {
// general states
const toaster = useToast();
const [isSuccessful, setIsSuccessful] = useState(false);
const initialParamValues = { amountToConvert: '', feeValue: '' };
const [paramValues, setParamValues] = useState(initialParamValues);
const hederaNetwork = JSON.parse(Cookies.get('_network') as string);
const [currentTransactionPage, setCurrentTransactionPage] = useState(1);
const currentContractAddress = Cookies.get(CONTRACT_NAMES.EXCHANGE_RATE) as string;
const [transactionResults, setTransactionResults] = useState<TransactionResult[]>([]);
const transactionResultStorageKey =
HEDERA_TRANSACTION_RESULT_STORAGE_KEYS['EXCHANGE-RATE-RESULT']['EXCHANGE-RATE'];
const [isLoading, setIsLoading] = useState({
CENT_TO_BAR: false,
BAR_TO_CENT: false,
});
const transactionResultsToShow = useFilterTransactionsByContractAddress(
transactionResults,
currentContractAddress
);

/** @dev retrieve token creation results from localStorage to maintain data on re-renders */
useEffect(() => {
handleRetrievingTransactionResultsFromLocalStorage(
toaster,
transactionResultStorageKey,
setCurrentTransactionPage,
setTransactionResults
);
}, [toaster, transactionResultStorageKey]);

// declare a paginatedTransactionResults
const paginatedTransactionResults = usePaginatedTxResults(currentTransactionPage, transactionResultsToShow);

/** @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 IHRC contract*/
const handleExecuteExchangeRateAPIs = async (API: API_NAMES) => {
// sanitize params
const sanitizeErr = handleSanitizeHederaFormInputs({
API: 'ExchangeRate',
feeValue: paramValues.feeValue,
amount: paramValues.amountToConvert,
});

if (sanitizeErr) {
CommonErrorToast({ toaster, title: 'Invalid parameters', description: sanitizeErr });
return;
}

// turn isLoading on
setIsLoading((prev) => ({ ...prev, [API]: true }));

// invoke method APIS
const { transactionHash, err, convertedAmount } = await handleExchangeRate(
baseContract,
API,
Number(paramValues.amountToConvert),
Number(paramValues.feeValue)
);

// turn isLoading off
setIsLoading((prev) => ({ ...prev, [API]: false }));

// handle err
if (err || !convertedAmount) {
handleAPIErrors({
err,
toaster,
transactionHash,
transactionType: API,
setTransactionResults,
initialAmount: paramValues.amountToConvert,
sessionedContractAddress: currentContractAddress,
});
return;
} else {
// handle succesfull
setTransactionResults((prev) => [
...prev,
{
status: 'success',
APICalled: API,
transactionType: API,
transactionTimeStamp: Date.now(),
txHash: transactionHash as string,
initialAmount: paramValues.amountToConvert,
convertedAmount: convertedAmount.toString(),
sessionedContractAddress: currentContractAddress,
},
]);

setIsSuccessful(true);
}
};

/** @dev listen to change event on transactionResults state => load to localStorage */
useUpdateTransactionResultsToLocalStorage(transactionResults, transactionResultStorageKey);

/** @dev toast successful */
useToastSuccessful({
toaster,
isSuccessful,
setParamValues,
setIsSuccessful,
transactionResults,
setCurrentTransactionPage,
toastTitle: 'Exchange successfully',
resetParamValues: initialParamValues,
});

return (
<div className="w-full mx-3 flex justify-center flex-col gap-20">
{/* Update token form */}
<div className="flex flex-col gap-6 justify-center tracking-tight text-white/70">
{/* Amount to convert */}
<SharedFormInputField
param={'amountToConvert'}
handleInputOnChange={handleInputOnChange}
paramValue={paramValues.amountToConvert}
paramKey={'amountToConvert'}
paramType={'number'}
paramSize={HEDERA_CHAKRA_INPUT_BOX_SIZES.medium}
explanation={`represents the amount to convert`}
paramClassName={HEDERA_CHAKRA_INPUT_BOX_SHARED_CLASSNAME}
paramPlaceholder={'Amount to convert...'}
paramFocusColor={HEDERA_BRANDING_COLORS.violet}
/>

{/* Execute button */}
<div className="flex gap-6">
<SharedExecuteButton
isLoading={isLoading.BAR_TO_CENT}
handleCreatingFungibleToken={() => handleExecuteExchangeRateAPIs('BAR_TO_CENT')}
buttonTitle={'Tinybars to Tinycents'}
/>
<div className="w-2/3">
<SharedFormInputField
param={'feeValue'}
paramType={'number'}
paramKey={'feeValue'}
paramPlaceholder={'Gas limit...'}
paramValue={paramValues.feeValue}
handleInputOnChange={handleInputOnChange}
explanation={'Gas limit for the transaction'}
paramSize={HEDERA_CHAKRA_INPUT_BOX_SIZES.large}
paramFocusColor={HEDERA_BRANDING_COLORS.violet}
paramClassName={HEDERA_CHAKRA_INPUT_BOX_SHARED_CLASSNAME}
/>
</div>
<SharedExecuteButton
isLoading={isLoading.CENT_TO_BAR}
handleCreatingFungibleToken={() => handleExecuteExchangeRateAPIs('CENT_TO_BAR')}
buttonTitle={'Tinycents To Tinybars'}
/>
</div>
</div>

{/* transaction results table */}
{transactionResultsToShow.length > 0 && (
<TransactionResultTable
API="ExchangeRate"
hederaNetwork={hederaNetwork}
transactionResults={transactionResults}
TRANSACTION_PAGE_SIZE={TRANSACTION_PAGE_SIZE}
setTransactionResults={setTransactionResults}
currentTransactionPage={currentTransactionPage}
setCurrentTransactionPage={setCurrentTransactionPage}
transactionResultStorageKey={transactionResultStorageKey}
paginatedTransactionResults={paginatedTransactionResults}
/>
)}
</div>
);
};

export default HederaExchangeRateMethods;
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ interface TransactionResultTablePageProps {
| 'GrantKYC'
| 'TokenMint'
| 'TokenCreate'
| 'ExchangeRate'
| 'QueryValidity'
| 'CryptoTransfer'
| 'TokenAssociate'
Expand Down Expand Up @@ -112,6 +113,7 @@ export const TransactionResultTable = ({
beginingHashIndex = 10;
endingHashIndex = -5;
break;
case 'ExchangeRate':
case 'TransferSingle':
case 'QueryTokenStatus':
beginingHashIndex = 4;
Expand All @@ -129,7 +131,7 @@ export const TransactionResultTable = ({
</Th>
<Th color={HEDERA_BRANDING_COLORS.violet}>Status</Th>
<Th color={HEDERA_BRANDING_COLORS.violet}>Tx hash</Th>
{API !== 'CryptoTransfer' && API !== 'PRNG' && (
{API !== 'CryptoTransfer' && API !== 'PRNG' && API !== 'ExchangeRate' && (
<Th color={HEDERA_BRANDING_COLORS.violet}>{`Token ${
API !== 'TransferSingle' ? `Address` : ``
}`}</Th>
Expand All @@ -146,12 +148,15 @@ export const TransactionResultTable = ({
{(API === 'QueryTokenGeneralInfo' ||
API === 'QuerySpecificInfo' ||
API === 'QueryTokenPermission') && <Th color={HEDERA_BRANDING_COLORS.violet}>Token Info</Th>}
{API === 'PRNG' && <Th color={HEDERA_BRANDING_COLORS.violet}>Seed</Th>}
{API === 'ExchangeRate' && <Th color={HEDERA_BRANDING_COLORS.violet}>Initial Amount</Th>}
{API === 'ExchangeRate' && <Th color={HEDERA_BRANDING_COLORS.violet}>Converted Amount</Th>}
{(API === 'QueryTokenGeneralInfo' ||
API === 'QuerySpecificInfo' ||
API === 'QueryTokenPermission' ||
API === 'QueryTokenStatus' ||
API === 'TransferSingle') && <Th color={HEDERA_BRANDING_COLORS.violet}>API called</Th>}
{API === 'PRNG' && <Th color={HEDERA_BRANDING_COLORS.violet}>Seed</Th>}
API === 'TransferSingle' ||
API === 'ExchangeRate') && <Th color={HEDERA_BRANDING_COLORS.violet}>API called</Th>}
<Th />
</Tr>
</Thead>
Expand Down Expand Up @@ -526,26 +531,18 @@ export const TransactionResultTable = ({
</Td>
)}

{/* query - API called */}
{(API === 'QueryTokenGeneralInfo' ||
API === 'QuerySpecificInfo' ||
API === 'QueryTokenPermission' ||
API === 'QueryTokenStatus' ||
API === 'TransferSingle') && (
<Td>
{transactionResult.APICalled ? (
<>
<p>
{transactionResult.APICalled === 'TOKEN_KEYS'
? `${transactionResult.APICalled.replace('TOKEN_', '')}_${
transactionResult.keyTypeCalled
}`
: transactionResult.APICalled === 'DEFAULT_FREEZE_STATUS' ||
transactionResult.APICalled === 'DEFAULT_KYC_STATUS'
? transactionResult.APICalled.replace('DEFAULT_', '')
: transactionResult.APICalled}
</p>
</>
{/* Exchange Rate - Initial Amount */}
{API === 'ExchangeRate' && (
<Td className="cursor-pointer">
<p className="w-[9rem]">{transactionResult.initialAmount}</p>
</Td>
)}

{/* Exchange Rate - ConvertedAmount */}
{API === 'ExchangeRate' && (
<Td className="cursor-pointer">
{transactionResult.convertedAmount ? (
<p className="w-[9rem]">{transactionResult.convertedAmount}</p>
) : (
<>NULL</>
)}
Expand Down Expand Up @@ -588,6 +585,33 @@ export const TransactionResultTable = ({
</Td>
)}

{/* query - API called */}
{(API === 'QueryTokenGeneralInfo' ||
API === 'QuerySpecificInfo' ||
API === 'QueryTokenPermission' ||
API === 'QueryTokenStatus' ||
API === 'TransferSingle' ||
API === 'ExchangeRate') && (
<Td>
{transactionResult.APICalled ? (
<>
<p>
{transactionResult.APICalled === 'TOKEN_KEYS'
? `${transactionResult.APICalled.replace('TOKEN_', '')}_${
transactionResult.keyTypeCalled
}`
: transactionResult.APICalled === 'DEFAULT_FREEZE_STATUS' ||
transactionResult.APICalled === 'DEFAULT_KYC_STATUS'
? transactionResult.APICalled.replace('DEFAULT_', '')
: transactionResult.APICalled}
</p>
</>
) : (
<>NULL</>
)}
</Td>
)}

{/* delete button */}
<Td>
<Tooltip label="delete this record" placement="top">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const handleAPIErrors = ({
APICalled,
tokenAddress,
keyTypeCalled,
initialAmount,
tokenAddresses,
accountAddress,
transactionType,
Expand All @@ -42,6 +43,7 @@ export const handleAPIErrors = ({
toaster: any;
APICalled?: string;
tokenAddress?: string;
initialAmount?: string;
accountAddress?: string;
transactionType: string;
receiverAddress?: string;
Expand Down Expand Up @@ -86,6 +88,7 @@ export const handleAPIErrors = ({
txHash: transactionHash,
sessionedContractAddress,
tokenAddress: tokenAddress ? tokenAddress : '',
initialAmount: initialAmount ? initialAmount : '',
accountAddress: accountAddress ? accountAddress : '',
tokenAddresses: tokenAddresses ? tokenAddresses : [''],
receiverAddress: receiverAddress ? receiverAddress : '',
Expand Down
Loading