diff --git a/jest/__tests__/getAmountToReceive.test.ts b/jest/__tests__/getAmountToReceive.test.ts index 2ec1b84a..7d8fce95 100644 --- a/jest/__tests__/getAmountToReceive.test.ts +++ b/jest/__tests__/getAmountToReceive.test.ts @@ -1,4 +1,3 @@ -import { formatFTAmount } from '@massalabs/react-ui-kit'; import { parseUnits } from 'viem'; import { getAmountToReceive, serviceFeeToPercent } from '../../src/utils/utils'; @@ -7,61 +6,55 @@ describe('should calculate service fees', () => { jest.clearAllMocks(); }); - test('should return 0.01% of 0.004', () => { - const amount = '0.004'; + test('should return 0.004 - a 0.1% service fee', () => { const serviceFee = 10n; const decimals = 6; - const result = getAmountToReceive(amount, serviceFee, decimals); - expect(result).toBe('0.003996'); + const amount = parseUnits('0.004', decimals); + const result = getAmountToReceive(amount, serviceFee); + expect(result).toBe(3996n); }); test('should return input amount when service fee is 0n', () => { - const amount = '100'; const serviceFee = 0n; const decimals = 6; - const result = getAmountToReceive(amount, serviceFee, decimals); + const amount = parseUnits('100', decimals); + const result = getAmountToReceive(amount, serviceFee); expect(result).toBe(amount); }); - test('should return 0.01% of 100', () => { - const amount = '100'; + test('should return 100 - 0.1% of service fees', () => { const serviceFee = 10n; const decimals = 6; - const result = getAmountToReceive(amount, serviceFee, decimals); - expect(result).toBe('99.900000'); + const amount = parseUnits('100', decimals); + const result = getAmountToReceive(amount, serviceFee); + expect(result).toBe(99900000n); }); - test('should return 0.02% of 5618.897000', () => { - const amount = '5618.897000'; + test('should return 5618.897000 - 0.02% of service fee', () => { const serviceFee = 20n; const decimals = 6; - const result = getAmountToReceive(amount, serviceFee, decimals); - expect(result).toBe('5,607.659206'); + const amount = parseUnits('5618.897000', decimals); + const result = getAmountToReceive(amount, serviceFee); + expect(result).toBe(5607659206n); }); - test('should return 0.02% of 101299120121.128893', () => { - const amount = '101299120121.128893'; + test('should return 101299120121.128893 - 0.02% of service fees', () => { const serviceFee = 20n; const decimals = 6; - const result = getAmountToReceive(amount, serviceFee, decimals); - expect(result).toBe('101,096,521,880.886640'); + const amount = parseUnits('101299120121.128893', decimals); + const result = getAmountToReceive(amount, serviceFee); + expect(result).toBe(101096521880886636n); }); test('should calculate 0.02% of MAX SAFE INT', () => { - const amount = Number.MAX_SAFE_INTEGER.toString(); const serviceFee = 20n; const decimals = 18; - const result = getAmountToReceive(amount, serviceFee, decimals); + const amount = parseUnits(Number.MAX_SAFE_INTEGER.toString(), decimals); + const result = getAmountToReceive(amount, serviceFee); - const _amount = parseUnits(amount, decimals); - const redeemFee = (_amount * serviceFee) / 10000n; - const expectedReceivedAmount = _amount - redeemFee; - const expected = formatFTAmount( - expectedReceivedAmount, - decimals, - ).amountFormattedFull; - - expect(result).toBe(expected); + const redeemFee = (amount * serviceFee) / 10000n; + const expectedReceivedAmount = amount - redeemFee; + expect(result).toBe(expectedReceivedAmount); }); describe('serviceFeeToPercent', () => { diff --git a/jest/__tests__/handleApproveRedeem.test.ts b/jest/__tests__/handleApproveRedeem.test.ts index 14613851..b4a72d21 100644 --- a/jest/__tests__/handleApproveRedeem.test.ts +++ b/jest/__tests__/handleApproveRedeem.test.ts @@ -11,7 +11,7 @@ describe('handleApproveRedeem', () => { }); test('should increaseAllowance and approve redeem', async () => { - const amount = U256_MAX.toString(); + const amount = U256_MAX; const opId = 'opId'; smartContractsMock.callSmartContract.mockResolvedValueOnce(opId); smartContractsMock.getOperationStatus.mockResolvedValueOnce( @@ -37,7 +37,7 @@ describe('handleApproveRedeem', () => { }); test('should not increaseAllowance and show success of redeem approval', async () => { - const amount = '1'; + const amount = 1n; const result = await handleApproveRedeem(amount); @@ -62,7 +62,7 @@ describe('handleApproveRedeem', () => { new Error('error'), ); - const amount = U256_MAX.toString(); + const amount = U256_MAX; const result = await handleApproveRedeem(amount); @@ -96,7 +96,7 @@ describe('handleApproveRedeem', () => { ), ); - const amount = U256_MAX.toString(); + const amount = U256_MAX; const result = await handleApproveRedeem(amount); @@ -124,7 +124,7 @@ describe('handleApproveRedeem', () => { .fn() .mockRejectedValueOnce(new Error(TIMEOUT)); - const amount = U256_MAX.toString(); + const amount = U256_MAX; const result = await handleApproveRedeem(amount); diff --git a/jest/__tests__/retrievePercent.test.ts b/jest/__tests__/retrievePercent.test.ts index b9e9a49b..cab4d6b5 100644 --- a/jest/__tests__/retrievePercent.test.ts +++ b/jest/__tests__/retrievePercent.test.ts @@ -4,7 +4,7 @@ describe('retrievePercent', () => { test('', () => { expect(retrievePercent('1000', '998')).toBe('0.2%'); expect(retrievePercent('10000', '9998')).toBe('0.02%'); - expect(retrievePercent('1', '1')).toBe('100%'); + expect(retrievePercent('1', '1')).toBe('0%'); expect(retrievePercent('10000000000', '1')).toBe('99.99%'); // minimal value in expect(retrievePercent('0', '0')).toBe('0%'); diff --git a/package-lock.json b/package-lock.json index a146ca63..4f737f04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "big.js": "^6.2.1", "currency.js": "^2.0.4", "delay": "^6.0.0", + "dot-object": "^2.1.5", "esbuild": "0.17.19", "localforage": "^1.10.0", "lottie-react": "^2.4.0", @@ -3494,9 +3495,9 @@ } }, "node_modules/@massalabs/react-ui-kit": { - "version": "0.0.5-dev.20240607130255", - "resolved": "https://registry.npmjs.org/@massalabs/react-ui-kit/-/react-ui-kit-0.0.5-dev.20240607130255.tgz", - "integrity": "sha512-LTF8r5EZ2Jj2SB98E0MkoD1vTDnyVji4tPGGGXe+43nyR47YreFJ9pLvrmvWkGexXdJc+Sp1DQMK3/3u9DjLvw==", + "version": "0.0.5-dev.20240611224415", + "resolved": "https://registry.npmjs.org/@massalabs/react-ui-kit/-/react-ui-kit-0.0.5-dev.20240611224415.tgz", + "integrity": "sha512-1H8pjj+DwV40BRrc0u7IyloJay+zhkgKteEOvNYzms66yhL/2Rbtp14nClbbzmg8hZeGQVq8YpdMd7t0qqOMCQ==", "dependencies": { "@headlessui/react": "^1.7.15", "copy-to-clipboard": "^3.3.3", @@ -10977,7 +10978,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/dot-object/-/dot-object-2.1.5.tgz", "integrity": "sha512-xHF8EP4XH/Ba9fvAF2LDd5O3IITVolerVV6xvkxoM8zlGEiCUrggpAnHyOoKJKCrhvPcGATFAUwIujj7bRG5UA==", - "peer": true, "dependencies": { "commander": "^6.1.0", "glob": "^7.1.6" @@ -10990,7 +10990,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "peer": true, "engines": { "node": ">= 6" } diff --git a/package.json b/package.json index 681e6f63..10bf37b6 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "big.js": "^6.2.1", "currency.js": "^2.0.4", "delay": "^6.0.0", + "dot-object": "^2.1.5", "esbuild": "0.17.19", "localforage": "^1.10.0", "lottie-react": "^2.4.0", diff --git a/src/components/ConnectWalletPopup/EvmWallets/EvmConnectButton.tsx b/src/components/ConnectWalletPopup/EvmWallets/EvmConnectButton.tsx index 889f2394..3618f532 100644 --- a/src/components/ConnectWalletPopup/EvmWallets/EvmConnectButton.tsx +++ b/src/components/ConnectWalletPopup/EvmWallets/EvmConnectButton.tsx @@ -100,7 +100,7 @@ export function EvmConnectButton(): JSX.Element { formatAmount( balanceData.value.toString(), balanceData.decimals, - ).amountFormattedFull + ).full } ${balanceData.symbol}` ) : ( diff --git a/src/components/ConnectWalletPopup/MassaWallets/MASBalance.tsx b/src/components/ConnectWalletPopup/MassaWallets/MASBalance.tsx index 32e621e9..19fe0849 100644 --- a/src/components/ConnectWalletPopup/MassaWallets/MASBalance.tsx +++ b/src/components/ConnectWalletPopup/MassaWallets/MASBalance.tsx @@ -29,10 +29,7 @@ export function MASBalance() { }); }, [connectedAccount, setBalance]); - const { amountFormattedFull } = formatAmount( - fromMAS(balance?.candidateBalance || '0').toString(), - 9, - ); + const { full } = formatAmount(fromMAS(balance?.candidateBalance || '0'), 9); const isBalanceZero = balance?.candidateBalance === '0'; @@ -46,7 +43,7 @@ export function MASBalance() { ) : ( <> - {amountFormattedFull} {MASSA_TOKEN} + {full} {MASSA_TOKEN} {renderCustomTooltip && } )} diff --git a/src/components/ServiceFeeTooltip/ServiceFeeTooltip.tsx b/src/components/ServiceFeeTooltip/ServiceFeeTooltip.tsx index fbc3c230..6b5fd4f2 100644 --- a/src/components/ServiceFeeTooltip/ServiceFeeTooltip.tsx +++ b/src/components/ServiceFeeTooltip/ServiceFeeTooltip.tsx @@ -3,14 +3,14 @@ import { FiInfo } from 'react-icons/fi'; import Intl from '@/i18n/i18n'; export interface ServiceFeeTooltipProps { - inputAmount: string; + inputAmount?: string; outputAmount?: string; serviceFee: string; symbol: string; } export function ServiceFeeTooltip(props: ServiceFeeTooltipProps) { - const { inputAmount, outputAmount = '-', serviceFee, symbol } = props; + const { inputAmount = '-', outputAmount = '-', serviceFee, symbol } = props; function ServiceFeeTooltipBody() { return ( diff --git a/src/const/const.ts b/src/const/const.ts index 65595b8f..51ec17d7 100644 --- a/src/const/const.ts +++ b/src/const/const.ts @@ -111,3 +111,11 @@ export const WAIT_STATUS_TIMEOUT = 300_000; export const STATUS_POLL_INTERVAL_MS = 1000; export const TIMEOUT = 'timeout'; + +// Service fees object to be used in claim page +export const CHAIN_ID_TO_SERVICE_FEE: Record = { + [mainnet.id]: 0n, + [sepolia.id]: 20n, + [bsc.id]: 0n, + [bscTestnet.id]: 10n, +}; diff --git a/src/custom/bridge/handlers/handleApproveRedeem.ts b/src/custom/bridge/handlers/handleApproveRedeem.ts index ce75e7b9..f23ffac9 100644 --- a/src/custom/bridge/handlers/handleApproveRedeem.ts +++ b/src/custom/bridge/handlers/handleApproveRedeem.ts @@ -1,5 +1,4 @@ import { toast } from '@massalabs/react-ui-kit'; -import { parseUnits } from 'viem'; import { TIMEOUT, U256_MAX } from '../../../const/const'; import Intl from '../../../i18n/i18n'; import { useTokenStore } from '../../../store/tokenStore'; @@ -11,7 +10,7 @@ import { isRejectedByUser, } from '@/utils/error'; -export async function handleApproveRedeem(amount: string) { +export async function handleApproveRedeem(amount: bigint) { const { setApprove, setBox } = useGlobalStatusesStore.getState(); try { const { selectedToken } = useTokenStore.getState(); @@ -19,8 +18,7 @@ export async function handleApproveRedeem(amount: string) { if (!selectedToken) throw new Error('Token not selected'); - const _amount = parseUnits(amount, selectedToken.decimals); - if (selectedToken.allowance < _amount) { + if (selectedToken.allowance < amount) { await increaseAllowance(U256_MAX); } diff --git a/src/custom/bridge/handlers/validateTransaction.ts b/src/custom/bridge/handlers/validateTransaction.ts index 8a9a6172..b0666598 100644 --- a/src/custom/bridge/handlers/validateTransaction.ts +++ b/src/custom/bridge/handlers/validateTransaction.ts @@ -1,4 +1,3 @@ -import { parseUnits } from 'viem'; import Intl from '@/i18n/i18n'; import { useGlobalStatusesStore, @@ -18,7 +17,6 @@ export function validate(tokenBalanceEVM: any) { return false; } - const _amount = parseUnits(amount, selectedToken.decimals); let _balance; if (massaToEvm) { @@ -27,12 +25,12 @@ export function validate(tokenBalanceEVM: any) { _balance = tokenBalanceEVM; } - if (_amount <= 0n) { + if (amount <= 0n) { setAmountError(Intl.t('index.approve.error.invalid-amount')); return false; } - if (_balance < _amount) { + if (_balance < amount) { setAmountError(Intl.t('index.approve.error.insufficient-funds')); return false; } diff --git a/src/custom/bridge/useBridgeUtils.ts b/src/custom/bridge/useBridgeUtils.ts index 810a96d2..19c16fdb 100644 --- a/src/custom/bridge/useBridgeUtils.ts +++ b/src/custom/bridge/useBridgeUtils.ts @@ -5,18 +5,16 @@ import { BurnState } from '@/utils/const'; export function useBridgeUtils() { const { reset } = useGlobalStatusesStore(); - const { - setInputAmount: setAmount, - resetTxIDs, - setBurnState, - } = useOperationStore(); + const { setInputAmount, setOutputAmount, resetTxIDs, setBurnState } = + useOperationStore(); const closeLoadingBox = useCallback(() => { reset(); - setAmount(''); + setInputAmount(undefined); + setOutputAmount(undefined); resetTxIDs(); setBurnState(BurnState.INIT); - }, [reset, setAmount, resetTxIDs, setBurnState]); + }, [reset, setInputAmount, setOutputAmount, resetTxIDs, setBurnState]); return { closeLoadingBox }; } diff --git a/src/custom/bridge/useBurn.ts b/src/custom/bridge/useBurn.ts index dc373d64..e0d61eca 100644 --- a/src/custom/bridge/useBurn.ts +++ b/src/custom/bridge/useBurn.ts @@ -1,6 +1,5 @@ import { useCallback } from 'react'; import { Args, EOperationStatus, MAX_GAS_CALL } from '@massalabs/massa-web3'; -import { parseUnits } from 'viem'; import { useAccount } from 'wagmi'; import { handleBurnError } from './handlers/handleTransactionErrors'; import { ForwardingRequest } from '../serializable/request'; @@ -27,15 +26,13 @@ export function useBurn() { if (!massaClient) throw new Error('Massa client not found'); if (!selectedToken) throw new Error('Token not selected'); - const amt = parseUnits(amount, selectedToken.decimals); - const tokenPair = new TokenPair( selectedToken.massaToken, selectedToken.evmToken, selectedToken.chainId, ); - const request = new ForwardingRequest(amt, evmAddress, tokenPair); + const request = new ForwardingRequest(amount, evmAddress, tokenPair); try { const callData = { diff --git a/src/custom/bridge/useHandleBurnRedeem.ts b/src/custom/bridge/useHandleBurnRedeem.ts index efa1c491..8ab07ea6 100644 --- a/src/custom/bridge/useHandleBurnRedeem.ts +++ b/src/custom/bridge/useHandleBurnRedeem.ts @@ -1,5 +1,4 @@ import { useState, useEffect } from 'react'; -import { parseUnits } from 'viem'; import { useAccount } from 'wagmi'; import { useFetchBurnEvent } from './useFetchBurnEvent'; import { Status, useGlobalStatusesStore } from '@/store/globalStatusesStore'; @@ -18,9 +17,10 @@ export function useHandleBurnRedeem() { const { burnTxId, appendBurnRedeemOperation, - inputAmount: amount, + inputAmount, setBurnState, claimTxId, + outputAmount, } = useOperationStore(); const { connectedAccount } = useAccountStore(); const { selectedToken } = useTokenStore(); @@ -42,7 +42,13 @@ export function useHandleBurnRedeem() { setCurrentIdToDisplay(burnTxId); } if (burn === Status.Success) return; - if (lambdaResponseIsEmpty || !amount || !evmAddress || !selectedToken) + if ( + lambdaResponseIsEmpty || + !inputAmount || + !outputAmount || + !evmAddress || + !selectedToken + ) return; if ( lambdaResponse[0].inputId === burnTxId && @@ -54,7 +60,8 @@ export function useHandleBurnRedeem() { inputId: burnTxId as string, signatures: [], claimState: ClaimState.RETRIEVING_INFO, - amount: parseUnits(amount, selectedToken.decimals).toString(), + amount: inputAmount.toString(), + outputAmount: outputAmount.toString(), recipient: evmAddress as string, evmToken: selectedToken.evmToken, massaToken: selectedToken.massaToken, @@ -72,7 +79,8 @@ export function useHandleBurnRedeem() { lambdaResponseIsEmpty, setBurn, burn, - amount, + inputAmount, + outputAmount, evmAddress, selectedToken, burnTxId, diff --git a/src/custom/bridge/useLock.ts b/src/custom/bridge/useLock.ts index 61bf4a27..d6f90bab 100644 --- a/src/custom/bridge/useLock.ts +++ b/src/custom/bridge/useLock.ts @@ -1,6 +1,5 @@ import { useCallback } from 'react'; import { useDebounceValue } from 'usehooks-ts'; -import { parseUnits } from 'viem'; import { useAccount, useWaitForTransactionReceipt, @@ -34,19 +33,18 @@ export function useLock() { const { data: hash, writeContract, error } = useWriteContract(); const write = useCallback(() => { - const amountInBigInt = parseUnits( - debouncedAmount || '0', - selectedToken?.decimals || 18, - ); writeContract({ address: bridgeContractAddr, abi: bridgeVaultAbi, functionName: 'lock', - args: [amountInBigInt.toString(), connectedAccount?.address(), evmToken], + args: [ + debouncedAmount?.toString(), + connectedAccount?.address(), + evmToken, + ], }); }, [ debouncedAmount, - selectedToken, connectedAccount, evmToken, bridgeContractAddr, diff --git a/src/custom/bridge/useSubmitBridge.ts b/src/custom/bridge/useSubmitBridge.ts index c67fcec6..8186777a 100644 --- a/src/custom/bridge/useSubmitBridge.ts +++ b/src/custom/bridge/useSubmitBridge.ts @@ -1,5 +1,4 @@ import { useCallback, useEffect } from 'react'; -import { parseUnits } from 'viem'; import { handleEvmApproveError, handleLockError, @@ -92,8 +91,8 @@ export function useSubmitBridge() { // Init bridge approval setApprove(Status.Loading); - let parsedAmount = parseUnits(amount, selectedToken.decimals); - const needApproval = allowanceEVM < parsedAmount; + + const needApproval = allowanceEVM < amount; if (needApproval) { // writing bridge approval diff --git a/src/i18n/en_US.json b/src/i18n/en_US.json index e3ac9f97..85a0d2ed 100644 --- a/src/i18n/en_US.json +++ b/src/i18n/en_US.json @@ -406,6 +406,7 @@ }, "service-fee": { "sending": "Sending amount", - "fee": "Service fee ({fee})" + "fee": "Service fee ({fee})", + "fee-tooltip": "We deducted the service fee of {fee} from the amount sent." } } diff --git a/src/pages/ClaimPage/ErrorClaim.tsx b/src/pages/ClaimPage/ErrorClaim.tsx index cb469221..41266805 100644 --- a/src/pages/ClaimPage/ErrorClaim.tsx +++ b/src/pages/ClaimPage/ErrorClaim.tsx @@ -13,7 +13,6 @@ interface ErrorClaimProps { export function ErrorClaim(props: ErrorClaimProps) { const { operation, symbol, decimals, onReset } = props; - let { amountFormattedPreview } = formatAmount(operation.amount, decimals); const isAlreadyExecuted = operation.claimState === ClaimState.ALREADY_EXECUTED; @@ -35,7 +34,7 @@ export function ErrorClaim(props: ErrorClaimProps) { {Intl.t('claim.error')} {' '} - {amountFormattedPreview} {symbol}{' '} + {formatAmount(operation.amount, decimals).preview} {symbol}{' '} {isChainIncompatible && (
{Intl.t('claim.wrong-network')}
diff --git a/src/pages/ClaimPage/InitClaim/OperationInfo.tsx b/src/pages/ClaimPage/InitClaim/OperationInfo.tsx index f0d06dfd..6b95713a 100644 --- a/src/pages/ClaimPage/InitClaim/OperationInfo.tsx +++ b/src/pages/ClaimPage/InitClaim/OperationInfo.tsx @@ -1,4 +1,4 @@ -import { formatAmount, getAssetIcons, Tooltip } from '@massalabs/react-ui-kit'; +import { getAssetIcons, Tooltip } from '@massalabs/react-ui-kit'; import { getEvmChainName, @@ -13,16 +13,18 @@ interface DisplayContentProps { claimState: ClaimState; operation: BurnRedeemOperation; symbol: string; - decimals?: number; + amountRedeemedPreview: string; + amountRedeemedFull: string; } export function OperationInfo(props: DisplayContentProps) { - const { claimState, operation, symbol, decimals } = props; - - let { amountFormattedFull, amountFormattedPreview } = formatAmount( - operation.amount, - decimals, - ); + const { + claimState, + operation, + symbol, + amountRedeemedFull, + amountRedeemedPreview, + } = props; const isClaimRejected = claimState === ClaimState.REJECTED; @@ -31,7 +33,7 @@ export function OperationInfo(props: DisplayContentProps) {
{Intl.t('claim.rejected-1')} - {amountFormattedPreview} {symbol} + {amountRedeemedPreview} {symbol} {Intl.t('claim.rejected-2')}
@@ -41,8 +43,8 @@ export function OperationInfo(props: DisplayContentProps) {
{getAssetIcons(symbol, operation.evmChainId, true, 26)} - {amountFormattedPreview} {symbol} - + {amountRedeemedPreview} {symbol} +
diff --git a/src/pages/ClaimPage/SuccessClaim.tsx b/src/pages/ClaimPage/SuccessClaim.tsx index 75d67096..300e116d 100644 --- a/src/pages/ClaimPage/SuccessClaim.tsx +++ b/src/pages/ClaimPage/SuccessClaim.tsx @@ -1,22 +1,31 @@ import { Tooltip, formatAmount } from '@massalabs/react-ui-kit'; import { SuccessCheck } from '@/components'; import { LinkExplorer } from '@/components/LinkExplorer'; +import { CHAIN_ID_TO_SERVICE_FEE } from '@/const'; import Intl from '@/i18n/i18n'; import { BurnRedeemOperation } from '@/store/operationStore'; +import { getAmountToReceive } from '@/utils/utils'; interface SuccessClaimProps { operation: BurnRedeemOperation; - symbol?: string; - decimals?: number; + symbol: string; + decimals: number; } -export function SuccessClaim(args: SuccessClaimProps) { - const { operation, symbol, decimals } = args; - let { amountFormattedFull, amountFormattedPreview } = formatAmount( - operation.amount, - decimals, +export function SuccessClaim(props: SuccessClaimProps) { + const { operation, symbol, decimals } = props; + + const serviceFee = CHAIN_ID_TO_SERVICE_FEE[operation.evmChainId]; + + // calculates amount received + const receivedAmount = getAmountToReceive( + BigInt(operation.amount), + serviceFee, ); + // format amount received + const { preview, full } = formatAmount(receivedAmount, decimals); + return (
{Intl.t('dao-maker.receive')}
- {amountFormattedFull} {MASSA_TOKEN} + {formatAmount(amount, wmasDecimals).full} {MASSA_TOKEN}
diff --git a/src/pages/HistoryPage/Amount.tsx b/src/pages/HistoryPage/Amount.tsx deleted file mode 100644 index e1201b01..00000000 --- a/src/pages/HistoryPage/Amount.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { formatAmount, Tooltip } from '@massalabs/react-ui-kit'; - -interface AmountProps { - amount?: string; - symbol?: string; - decimals?: number; -} - -export function Amount(props: AmountProps) { - const { amount, symbol = '', decimals = 9 } = props; - - let amountFormattedPreview = '-'; - let amountFormattedFull = '-'; - - if (amount !== undefined) { - const formattedResult = formatAmount(amount, decimals); - amountFormattedPreview = formattedResult.amountFormattedPreview; - amountFormattedFull = formattedResult.amountFormattedFull; - } - - return ( -
- {amountFormattedPreview} {symbol} - -
- ); -} diff --git a/src/pages/HistoryPage/Operation.tsx b/src/pages/HistoryPage/Operation.tsx index f1768133..c2f18e31 100644 --- a/src/pages/HistoryPage/Operation.tsx +++ b/src/pages/HistoryPage/Operation.tsx @@ -1,15 +1,14 @@ import { useMemo } from 'react'; -import { Amount } from './Amount'; import { Emitter } from './Emitter'; +import { Received } from './Received'; import { Recipient } from './Recipient'; +import { Sent } from './Sent'; import { ShowStatus } from './ShowStatus'; import { wmasDecimals, wmasSymbol } from '../DaoPage'; import { LinkExplorer } from '@/components/LinkExplorer'; -import { ServiceFeeTooltip } from '@/components/ServiceFeeTooltip/ServiceFeeTooltip'; import { MASSA_TOKEN } from '@/const'; import { useTokenStore } from '@/store/tokenStore'; import { Entities, OperationHistoryItem } from '@/utils/lambdaApi'; -import { retrievePercent } from '@/utils/utils'; function formatApiCreationTime(inputTimestamp: string) { const dateObject = new Date(inputTimestamp); @@ -20,23 +19,23 @@ interface OperationProps { } export function Operation(props: OperationProps) { - const { operation: op } = props; + const { operation } = props; const { tokens } = useTokenStore(); const memoizedStatusComponent = useMemo(() => { - return ; - }, [op.historyStatus]); + return ; + }, [operation.historyStatus]); function getTokenInfo() { - const token = tokens.find((t) => t.evmToken === op.evmToken); - if (op.entity === Entities.ReleaseMAS) { + const token = tokens.find((t) => t.evmToken === operation.evmToken); + if (operation.entity === Entities.ReleaseMAS) { return { sentSymbol: wmasSymbol, receivedSymbol: MASSA_TOKEN, tokenDecimals: wmasDecimals, }; - } else if (op.entity === Entities.Lock) { + } else if (operation.entity === Entities.Lock) { return { sentSymbol: token?.symbolEVM, receivedSymbol: token?.symbol, @@ -54,31 +53,28 @@ export function Operation(props: OperationProps) { return (
- - + +
- {formatApiCreationTime(op.createdAt)} + {formatApiCreationTime(operation.createdAt)}
{/* sent */} - + {/* received */} -
- - -
+ {memoizedStatusComponent}
diff --git a/src/pages/HistoryPage/Received.tsx b/src/pages/HistoryPage/Received.tsx new file mode 100644 index 00000000..b5526475 --- /dev/null +++ b/src/pages/HistoryPage/Received.tsx @@ -0,0 +1,35 @@ +import { formatAmount } from '@massalabs/react-ui-kit'; +import { ServiceFeeTooltip } from '@/components/ServiceFeeTooltip/ServiceFeeTooltip'; +import { retrievePercent } from '@/utils/utils'; + +interface AmountProps { + inputAmount: string; + outputAmount?: string; + symbol?: string; + decimals?: number; +} + +export function Received(props: AmountProps) { + const { inputAmount, outputAmount, symbol = '', decimals = 9 } = props; + + let outputPreview = '-'; + let outputFull = '-'; + + if (outputAmount !== undefined && outputAmount !== null) { + const formattedResult = formatAmount(outputAmount, decimals); + outputPreview = formattedResult.preview; + outputFull = formattedResult.full; + } + + return ( +
+ {outputPreview} {symbol} + +
+ ); +} diff --git a/src/pages/HistoryPage/Sent.tsx b/src/pages/HistoryPage/Sent.tsx new file mode 100644 index 00000000..3c0342c8 --- /dev/null +++ b/src/pages/HistoryPage/Sent.tsx @@ -0,0 +1,27 @@ +import { formatAmount, Tooltip } from '@massalabs/react-ui-kit'; + +interface AmountProps { + amount?: string; + symbol?: string; + decimals?: number; +} + +export function Sent(props: AmountProps) { + const { amount, symbol = '', decimals = 9 } = props; + + let preview = '-'; + let full = '-'; + + if (amount !== undefined && amount !== null) { + const formattedResult = formatAmount(amount, decimals); + preview = formattedResult.preview; + full = formattedResult.full; + } + + return ( +
+ {preview} {symbol} + +
+ ); +} diff --git a/src/pages/HistoryPage/index.ts b/src/pages/HistoryPage/index.ts index 8a7c4f15..a6f219f1 100644 --- a/src/pages/HistoryPage/index.ts +++ b/src/pages/HistoryPage/index.ts @@ -5,3 +5,5 @@ export * from './Operation'; export * from './ShowStatus'; export * from './Operation'; export * from './Recipient'; +export * from './Sent'; +export * from './Received'; diff --git a/src/pages/IndexPage/Layouts/BridgeRedeemLayout/BoxLayoutComponents/EvmBoxComponents.tsx b/src/pages/IndexPage/Layouts/BridgeRedeemLayout/BoxLayoutComponents/EvmBoxComponents.tsx index a23116da..d99541c7 100644 --- a/src/pages/IndexPage/Layouts/BridgeRedeemLayout/BoxLayoutComponents/EvmBoxComponents.tsx +++ b/src/pages/IndexPage/Layouts/BridgeRedeemLayout/BoxLayoutComponents/EvmBoxComponents.tsx @@ -29,8 +29,8 @@ export function EVMHeader() { const isMainnet = getIsMainnet(); function handleChangeEvmNetwork(selectedEvm: SupportedEvmBlockchain) { - setInputAmount(''); - setOutputAmount(''); + setInputAmount(undefined); + setOutputAmount(undefined); setSelectedEvm(selectedEvm); resetSelectedToken(); } diff --git a/src/pages/IndexPage/Layouts/BridgeRedeemLayout/BoxLayoutComponents/TokenBalance.tsx b/src/pages/IndexPage/Layouts/BridgeRedeemLayout/BoxLayoutComponents/TokenBalance.tsx index b3ab18aa..fb53f8a4 100644 --- a/src/pages/IndexPage/Layouts/BridgeRedeemLayout/BoxLayoutComponents/TokenBalance.tsx +++ b/src/pages/IndexPage/Layouts/BridgeRedeemLayout/BoxLayoutComponents/TokenBalance.tsx @@ -21,7 +21,7 @@ export function TokenBalance() { symbol = selectedToken?.symbolEVM; } - let { amountFormattedPreview, amountFormattedFull } = formatAmount( + let { preview, full } = formatAmount( amount ? amount.toString() : '0', decimals, ); @@ -36,8 +36,8 @@ export function TokenBalance() { ) : (
- {amountFormattedPreview} - + {preview} +
)}
diff --git a/src/pages/IndexPage/Layouts/BridgeRedeemLayout/BridgeRedeemLayout.tsx b/src/pages/IndexPage/Layouts/BridgeRedeemLayout/BridgeRedeemLayout.tsx index 81fd29e7..49f1755c 100644 --- a/src/pages/IndexPage/Layouts/BridgeRedeemLayout/BridgeRedeemLayout.tsx +++ b/src/pages/IndexPage/Layouts/BridgeRedeemLayout/BridgeRedeemLayout.tsx @@ -1,14 +1,8 @@ import { useState } from 'react'; - -import { - Button, - Money, - formatAmount, - formatAmountToDisplay, - removeTrailingZeros, -} from '@massalabs/react-ui-kit'; +import { Button, Money, formatAmount } from '@massalabs/react-ui-kit'; import Big from 'big.js'; import { FiRepeat } from 'react-icons/fi'; +import { parseUnits } from 'viem'; import { useAccount } from 'wagmi'; import { boxLayout } from './BoxLayout'; import { FeesEstimation } from './FeesEstimation'; @@ -57,7 +51,11 @@ export function BridgeRedeemLayout(props: BridgeRedeemProps) { setInputAmount, setOutputAmount, } = useOperationStore(); + + const [inputField, setInputField] = useState(); + const { isFetching } = useAccountStore(); + const { selectedToken: token } = useTokenStore(); const { serviceFee } = useServiceFee(); @@ -80,28 +78,32 @@ export function BridgeRedeemLayout(props: BridgeRedeemProps) { } const amount = massaToEvm - ? formatAmount(token?.balance.toString(), token.decimals, '') - .amountFormattedFull - : formatAmount(_tokenBalanceEVM.toString(), token.decimals, '') - .amountFormattedFull; + ? formatAmount(token?.balance.toString(), token.decimals, '').full + : formatAmount(_tokenBalanceEVM.toString(), token.decimals, '').full; const x = new Big(amount); const y = new Big(percent); const res = x.times(y).round(token.decimals).toFixed(); - setInputAmount(res); - setOutputAmount( - getAmountToReceive(res, serviceFee, token?.decimals, false), - ); + const _amount = parseUnits(res, token.decimals); + + setInputAmount(_amount); + setInputField(res); + const amountToReceive = getAmountToReceive(_amount, serviceFee); + setOutputAmount(amountToReceive); } function handleToggleLayout() { - setInputAmount(''); + setInputAmount(undefined); + setOutputAmount(undefined); + setInputField(undefined); setSide(massaToEvm ? SIDE.EVM_TO_MASSA : SIDE.MASSA_TO_EVM); } function handleGenericSubmit() { isMassaToEvm() ? handleSubmitRedeem() : handleSubmitBridge(); + // sets input field to undefined for next transfer + setInputField(undefined); } const isOperationPending = box !== Status.None; @@ -109,10 +111,24 @@ export function BridgeRedeemLayout(props: BridgeRedeemProps) { if (isOperationPending) return ; function changeAmount(amount: string) { - setInputAmount(amount); - setOutputAmount( - getAmountToReceive(amount, serviceFee, token?.decimals, false), - ); + if (!token) return; + if (!amount) { + setInputAmount(undefined); + setInputField(undefined); + setOutputAmount(undefined); + return; + } + + const parsedInputAmount = parseUnits(amount, token.decimals); + setInputAmount(parsedInputAmount); + setInputField(amount); + + if (isMassaToEvm()) { + const amountToReceive = getAmountToReceive(parsedInputAmount, serviceFee); + setOutputAmount(amountToReceive); + } else { + setOutputAmount(parsedInputAmount); + } } // Money component formats amount without decimals @@ -131,7 +147,7 @@ export function BridgeRedeemLayout(props: BridgeRedeemProps) { changeAmount(o.value)} placeholder={Intl.t('index.input.placeholder.amount')} suffix="" @@ -202,12 +218,13 @@ export function BridgeRedeemLayout(props: BridgeRedeemProps) { {Intl.t('index.input.placeholder.receive')}

@@ -221,7 +238,9 @@ export function BridgeRedeemLayout(props: BridgeRedeemProps) { { - const amountInBigInt = parseUnits(amount || '0', selectedToken.decimals); + async (selectedToken: IToken, amount?: bigint) => { + if (!amount) return; let storageCostMAS = forwardBurnFees.coins; let feesCostMAS = forwardBurnFees.fee; - if ( - selectedToken.allowance === 0n || - selectedToken.allowance < amountInBigInt - ) { + if (selectedToken.allowance === 0n || selectedToken.allowance < amount) { storageCostMAS += await increaseAllowanceStorageCost(); feesCostMAS += increaseAllowanceFee.fee; } @@ -119,7 +116,7 @@ export function FeesEstimation() { if (fees === 0n) { setFeesETH(undefined); } else { - setFeesETH(formatAmount(fees.toString(), 18).amountFormattedFull); + setFeesETH(formatAmount(fees.toString(), 18).full); } }; @@ -129,7 +126,7 @@ export function FeesEstimation() { setStorageMAS(undefined); return; } - estimateFeesMassa(selectedToken, amount); + estimateFeesMassa(selectedToken, inputAmount); setFeesETHWithCheck(estimateClaimFees()); } else { setFeesMAS(0n); @@ -138,9 +135,10 @@ export function FeesEstimation() { setFeesETH(undefined); return; } - const amountInBigInt = parseUnits(amount || '0', selectedToken.decimals); + const lockFees = estimateLockFees(); - if (allowance < amountInBigInt) { + if (!inputAmount) return; + if (allowance < inputAmount) { const approveFees = estimateApproveFees(); setFeesETHWithCheck(approveFees + lockFees); } else { @@ -149,7 +147,7 @@ export function FeesEstimation() { } }, [ massaToEvm, - amount, + inputAmount, allowance, estimateApproveFees, estimateLockFees, diff --git a/src/pages/IndexPage/Layouts/BridgeRedeemLayout/WarningNoEth.tsx b/src/pages/IndexPage/Layouts/BridgeRedeemLayout/WarningNoEth.tsx index 88c3c1b1..456c6d61 100644 --- a/src/pages/IndexPage/Layouts/BridgeRedeemLayout/WarningNoEth.tsx +++ b/src/pages/IndexPage/Layouts/BridgeRedeemLayout/WarningNoEth.tsx @@ -29,9 +29,7 @@ export function WarningNoEth() { const estimatedFees = estimateClaimFees(); const fees = - estimatedFees > 0n - ? formatAmount(estimatedFees.toString(), 18).amountFormattedFull - : ''; + estimatedFees > 0n ? formatAmount(estimatedFees.toString(), 18).full : ''; return (
diff --git a/src/pages/IndexPage/Layouts/LoadingLayout/RedeemLayout/ClaimRedeem.tsx b/src/pages/IndexPage/Layouts/LoadingLayout/RedeemLayout/ClaimRedeem.tsx index 615f2879..c7d0db07 100644 --- a/src/pages/IndexPage/Layouts/LoadingLayout/RedeemLayout/ClaimRedeem.tsx +++ b/src/pages/IndexPage/Layouts/LoadingLayout/RedeemLayout/ClaimRedeem.tsx @@ -1,9 +1,9 @@ import { useCallback, useEffect } from 'react'; -import { Button } from '@massalabs/react-ui-kit'; -import { parseUnits } from 'viem'; +import { Button, Tooltip, formatAmount } from '@massalabs/react-ui-kit'; import { useAccount } from 'wagmi'; import { handleEvmClaimBoxError } from '@/custom/bridge/handlers/handleTransactionErrors'; import { useClaim } from '@/custom/bridge/useClaim'; +import { useServiceFee } from '@/custom/bridge/useServiceFee'; import Intl from '@/i18n/i18n'; import { Status } from '@/store/globalStatusesStore'; import { BurnRedeemOperation } from '@/store/operationStore'; @@ -14,6 +14,7 @@ import { } from '@/store/store'; import { ClaimState } from '@/utils/const'; import { useFetchSignatures } from '@/utils/lambdaApi'; +import { serviceFeeToPercent } from '@/utils/utils'; // Renders when burn is successful, polls api to see if there is an operation to claim // If operation found, renders claim button that calls redeem function @@ -24,12 +25,15 @@ export function ClaimRedeem() { const { setClaim, setBox } = useGlobalStatusesStore(); const { burnTxId, - inputAmount: amount, + inputAmount, getCurrentRedeemOperation, updateBurnRedeemOperationById, setClaimTxId, + outputAmount, } = useOperationStore(); + const { serviceFee } = useServiceFee(); + useFetchSignatures(); const { write, error, isSuccess, hash, isPending } = useClaim(); @@ -94,7 +98,7 @@ export function ClaimRedeem() { // Event handler for claim button async function handleRedeem() { if ( - !amount || + !inputAmount || !evmAddress || !selectedToken || !burnTxId || @@ -109,7 +113,7 @@ export function ClaimRedeem() { claimState: ClaimState.AWAITING_SIGNATURE, }); write({ - amount: parseUnits(amount, selectedToken.decimals).toString(), + amount: inputAmount.toString(), evmToken: selectedToken.evmToken as `0x${string}`, inputOpId: burnTxId, signatures: currentRedeemOperation.signatures, @@ -118,6 +122,11 @@ export function ClaimRedeem() { }); } + const formattedOutputAmount = formatAmount( + outputAmount || '0', + selectedToken?.decimals, + ).full; + const symbol = selectedToken?.symbolEVM as string; const selectedChain = chain?.name as string; @@ -155,7 +164,14 @@ export function ClaimRedeem() { handleRedeem(); }} > - {Intl.t('index.loading-box.claim')} {symbol} + {Intl.t('index.loading-box.claim')} {formattedOutputAmount} {symbol}{' '} + ) : null}
diff --git a/src/pages/IndexPage/Layouts/LoadingLayout/SuccessLayout.tsx b/src/pages/IndexPage/Layouts/LoadingLayout/SuccessLayout.tsx index 5ae9819e..4b0732e1 100644 --- a/src/pages/IndexPage/Layouts/LoadingLayout/SuccessLayout.tsx +++ b/src/pages/IndexPage/Layouts/LoadingLayout/SuccessLayout.tsx @@ -1,6 +1,5 @@ -import { formatStandard } from '@massalabs/react-ui-kit'; +import { formatAmount } from '@massalabs/react-ui-kit'; import { Link } from 'react-router-dom'; -import { parseUnits } from 'viem'; import { useAccount } from 'wagmi'; import { BridgeLinkExplorer } from './BridgeLinkExplorer'; import { addTokensBuildnetLink, addTokensMainnetLink } from '@/const/faq'; @@ -14,12 +13,8 @@ import { } from '@/store/store'; export function SuccessLayout() { - const { - isMassaToEvm, - mintTxId, - getCurrentRedeemOperation, - inputAmount: amount, - } = useOperationStore(); + const { isMassaToEvm, mintTxId, getCurrentRedeemOperation, outputAmount } = + useOperationStore(); const { isMainnet: getIsMainnet, massaNetwork: getMassaNetwork } = useBridgeModeStore(); const { chain } = useAccount(); @@ -30,14 +25,10 @@ export function SuccessLayout() { const { selectedToken: token } = useTokenStore(); - if (!chain || !token || !amount) return null; + if (!chain || !token || !outputAmount) return null; const massaToEvm = isMassaToEvm(); const currentRedeemOperation = getCurrentRedeemOperation(); - const amountFormatted = formatStandard( - parseUnits(amount, token.decimals).toString(), - token.decimals, - ); const massaChainAndNetwork = `${Intl.t('general.Massa')} ${Intl.t( `general.${massaNetwork}`, @@ -50,6 +41,11 @@ export function SuccessLayout() { const currentTxID = massaToEvm ? currentRedeemOperation?.outputId : mintTxId; + const formattedOutputAmount = formatAmount( + outputAmount || '0', + token?.decimals, + ).full; + const redirectToFaq = getFaqUrl(); function getFaqUrl(): string { @@ -67,7 +63,7 @@ export function SuccessLayout() { ? Intl.t('index.loading-box.redeemed') : Intl.t('index.loading-box.bridged')}
- {amountFormatted} {massaToEvm ? token.symbol : token.symbolEVM} + {formattedOutputAmount} {massaToEvm ? token.symbol : token.symbolEVM}
{Intl.t('index.loading-box.from-to', { @@ -77,7 +73,7 @@ export function SuccessLayout() {
{Intl.t('index.loading-box.received-tokens', { - amount: `${amountFormatted} ${ + amount: `${formattedOutputAmount} ${ massaToEvm ? token.symbolEVM : token.symbol }`, })} diff --git a/src/store/operationStore.ts b/src/store/operationStore.ts index 56aadb4a..92539767 100644 --- a/src/store/operationStore.ts +++ b/src/store/operationStore.ts @@ -49,11 +49,11 @@ export interface OperationStoreState { claimTxId?: string; setClaimTxId(currentTxID?: string): void; - inputAmount?: string; - setInputAmount(amount?: string): void; + inputAmount?: bigint; + setInputAmount(amount?: bigint): void; - outputAmount?: string; - setOutputAmount(amount?: string): void; + outputAmount?: bigint; + setOutputAmount(amount?: bigint): void; resetTxIDs: () => void; } @@ -147,12 +147,12 @@ export const useOperationStore = create( }, inputAmount: undefined, - setInputAmount(amount?: string) { + setInputAmount(amount?: bigint) { set({ inputAmount: amount }); }, outputAmount: undefined, - setOutputAmount(amount?: string) { + setOutputAmount(amount?: bigint) { set({ outputAmount: amount }); }, diff --git a/src/utils/utils.ts b/src/utils/utils.ts index e83d54c1..ca6750b5 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,5 +1,3 @@ -import { formatFTAmount, removeTrailingZeros } from '@massalabs/react-ui-kit'; -import { parseUnits } from 'viem'; import { MASSA_EXPLORER_URL, MASSA_EXPLO_URL, @@ -55,28 +53,14 @@ export function getMinConfirmation( * @param serviceFee - bigint service fee received from the read sc * @param decimals - IToken selectedToken.decimals * @param inFull - boolean to return the full amount or amount with no trailing zeros - * @returns string + * @returns bigint of the amount to be received */ -export function getAmountToReceive( - amount: string | undefined, - serviceFee: bigint, - decimals: number | undefined, - inFull = true, -): string { - if (!amount || !decimals) { - return ''; - } - if (!serviceFee) { +export function getAmountToReceive(amount: bigint, serviceFee: bigint): bigint { + if (serviceFee === 0n) { return amount; } - const _amount = parseUnits(amount, decimals); - const redeemFee = (_amount * serviceFee) / 10000n; - const receivedAmount = _amount - redeemFee; - - const { amountFormattedFull } = formatFTAmount(receivedAmount, decimals); - return inFull - ? amountFormattedFull - : removeTrailingZeros(amountFormattedFull); + const redeemFee = (amount * serviceFee) / 10000n; + return amount - redeemFee; } /** @@ -91,7 +75,7 @@ export function serviceFeeToPercent(serviceFee: bigint): string { } export function retrievePercent(amountIn: string, amountOut?: string): string { - if (amountOut === undefined) { + if (amountOut === undefined || amountOut === null) { return '-%'; } const amountInNum = BigInt(amountIn); @@ -101,7 +85,7 @@ export function retrievePercent(amountIn: string, amountOut?: string): string { } const feeAmount = amountInNum - amountOutNum; if (feeAmount === 0n) { - return '100%'; + return '0%'; } const percent = (feeAmount * 10_000n) / amountInNum; return serviceFeeToPercent(percent);