From 9f66cbb3d3f75154a1d7f696fe1e2bfc6a78a6c5 Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Mon, 2 Dec 2024 15:39:41 +0200 Subject: [PATCH 01/23] Added transactions utils --- src/constants/index.ts | 7 +- src/constants/transactions.constants.ts | 2 + src/types/serverTransactions.types.ts | 341 +++++++++++++++ src/types/tokens.types.ts | 136 ++++++ src/types/transactions.types.ts | 285 +++++++++++++ .../generateBatchTransactionsGrouping.test.ts | 74 ++++ .../generateBatchTransactionsGrouping.ts | 11 + .../transactions/batch/getIsSequential.ts | 9 + .../batch/getTransactionsStatus.ts | 37 ++ .../batch/sequentialToFlatArray.ts | 12 + .../batch/updateBatchTransactionsStatuses.ts | 42 ++ .../decodeTransactionData.ts | 66 +++ .../helpers/decodeByMethod.ts | 61 +++ .../getDisplayValueAndValidationWarnings.ts | 52 +++ .../helpers/getHexValidationWarnings.ts | 21 + .../helpers/getSmartDecodedParts.ts | 30 ++ .../decodeTransactionData/helpers/index.ts | 4 + .../decodeTransactionData/index.ts | 1 + .../tests/decodeByMethod.spec.ts | 122 ++++++ .../tests/decodeForDisplay.spec.ts | 149 +++++++ ...tDisplayValueAndValidationWarnings.spec.ts | 199 +++++++++ .../tests/getHexValidationWarnings.spec.ts | 59 +++ .../tests/getSmartDecodedParts.spec.ts | 93 +++++ .../getDataPayloadForTransaction.ts | 20 + .../dataDecoders/getScResultsDecodedData.ts | 15 + .../dataDecoders/getScResultsHighlight.ts | 11 + .../getScResultsInitialDecodeMethod.ts | 17 + .../dataDecoders/getTokenFromData.ts | 93 +++++ .../getUnHighlightedDataFieldParts.ts | 22 + .../dataDecoders/smartContractTransaction.ts | 111 +++++ .../dataDecoders/useDataDecode.ts | 64 +++ .../dataDecoders/useDataDecodeMethod.ts | 26 ++ src/utils/transactions/index.ts | 14 + src/utils/transactions/isGuardianTx.tsx | 21 + src/utils/transactions/isTokenTransfer.ts | 9 + .../parsers/getAreTransactionsOnSameShard.ts | 34 ++ .../parsers/getEventListDataHexValue.ts | 7 + .../parsers/getEventListHighlight.ts | 13 + .../getEventListInitialDecodeMethod.ts | 9 + .../getInterpretedTransaction/constants.ts | 32 ++ .../getInterpretedTransaction.ts | 88 ++++ .../helpers/explorerUrlBuilder.ts | 59 +++ .../helpers/getExplorerLink.ts | 28 ++ .../helpers/getHumanReadableTimeFormat.ts | 32 ++ .../helpers/getOperationsMessages.ts | 9 + .../helpers/getReceiptMessage.ts | 39 ++ .../helpers/getScResultsMessages.ts | 11 + .../helpers/getTransactionMethod.ts | 28 ++ .../helpers/getTransactionReceiver.ts | 10 + .../helpers/getTransactionReceiverAssets.ts | 15 + .../helpers/getTransactionTokens.ts | 21 + .../helpers/getTransactionTransferType.ts | 28 ++ .../getTransactionValue.ts | 121 ++++++ .../helpers/getEgldValueData.ts | 10 + .../helpers/getTitleText.ts | 46 +++ .../helpers/getValueFromActions.ts | 23 ++ .../helpers/getValueFromDataField.ts | 27 ++ .../helpers/getValueFromOperations.ts | 31 ++ .../getTransactionValue/helpers/index.ts | 5 + .../helpers/getTransactionValue/index.ts | 1 + .../helpers/index.ts | 15 + .../helpers/tests/base-transaction-mock.ts | 28 ++ .../helpers/tests/getExplorerkLink.test.ts | 27 ++ .../tests/getHumanReadableTimeFormat.test.ts | 139 +++++++ .../tests/getOperationsMessages.test.ts | 48 +++ .../helpers/tests/getReceiptMessage.test.ts | 56 +++ .../tests/getScResultsMessages.test.ts | 46 +++ .../tests/getTransactionMethod.test.ts | 65 +++ .../tests/getTransactionReceiver.test.ts | 37 ++ .../getTransactionReceiverAssets.test.ts | 52 +++ .../tests/getTransactionTokens.test.ts | 59 +++ .../tests/getTransactionTransferType.test.ts | 67 +++ .../helpers/types.ts | 26 ++ .../getInterpretedTransaction/index.tsx | 3 + .../tests/extended-transaction-mock.ts | 91 ++++ .../tests/getInterpretedTransaction.test.ts | 164 ++++++++ .../parsers/getOperationDirection.ts | 33 ++ .../parsers/getOperationsDetails.ts | 50 +++ .../transactions/parsers/getScamFlag.tsx | 71 ++++ .../transactions/parsers/getShardText.ts | 26 ++ .../parsers/getTransactionActionNftText.ts | 71 ++++ .../parsers/getTransactionActionTokenText.ts | 51 +++ .../transactions/parsers/getTransactionFee.ts | 22 + .../parsers/getTransactionLink.ts | 9 + .../parsers/getTransactionLinkWithLabel.ts | 49 +++ .../parsers/getTransactionMessages.ts | 17 + .../parsers/getTransactionStatus.ts | 24 ++ .../parsers/getTransactionStatusText.ts | 15 + .../transactions/parsers/getTrimmedHash.ts | 7 + .../parsers/getUsernameForTransaction.ts | 13 + .../parsers/getVisibleOperations.ts | 17 + src/utils/transactions/parsers/index.ts | 20 + .../parsers/parseMultiEsdtTransferData.ts | 96 +++++ ...EsdtTransferDataForMultipleTransactions.ts | 96 +++++ .../parsers/parseTransactionAfterSigning.ts | 33 ++ .../parsers/tests/getScamFlag.test.ts | 40 ++ .../parsers/tests/getShardText.test.ts | 13 + .../tests/getTransactionLinkWithLabel.test.ts | 39 ++ .../helpers/esdtNftUnwrapper.ts | 22 + .../helpers/index.ts | 3 + .../helpers/mexUnwrapper.ts | 77 ++++ .../helpers/stakeUnwrapper.ts | 60 +++ .../transactionActionUnwrapper/index.ts | 1 + .../tests/transactionActionUnwrapper.test.tsx | 390 ++++++++++++++++++ .../transactionActionUnwrapper.ts | 27 ++ .../parsers/useGetOperationList.tsx | 94 +++++ .../tests/builtCallbackUrl.test.ts | 62 +++ .../getDataPayloadForTransaction.test.ts | 63 +++ .../tests/getOperationsDetails.test.ts | 248 +++++++++++ .../tests/getTokenFromData.test.ts | 49 +++ .../getUnHighlightedDataFieldParts.test.ts | 93 +++++ .../transactions/tests/isGuardianTx.test.ts | 54 +++ .../tests/parseMultiEsdtTransferData.test.ts | 189 +++++++++ .../removeTransactionParamsFromUrl.test.ts | 43 ++ .../transactions/transactionStateByStatus.ts | 143 +++++++ .../transactions/url/buildCallbackUrl.ts | 49 +++ .../url/removeTransactionParamsFromUrl.ts | 23 ++ src/utils/window/clearNavigationHistory.ts | 12 + src/utils/window/index.ts | 7 +- src/utils/window/parseNavigationParams.ts | 57 +++ src/utils/window/removeSearchParamsFromUrl.ts | 36 ++ .../tests/clearNavigationHistory.test.ts | 75 ++++ .../tests/parseNavigationParams.test.ts | 50 +++ .../tests/removeSearchParamsFromUrl.test.ts | 42 ++ 124 files changed, 6662 insertions(+), 5 deletions(-) create mode 100644 src/constants/transactions.constants.ts create mode 100644 src/types/serverTransactions.types.ts create mode 100644 src/types/tokens.types.ts create mode 100644 src/types/transactions.types.ts create mode 100644 src/utils/transactions/batch/generateBatchTransactionsGrouping.test.ts create mode 100644 src/utils/transactions/batch/generateBatchTransactionsGrouping.ts create mode 100644 src/utils/transactions/batch/getIsSequential.ts create mode 100644 src/utils/transactions/batch/getTransactionsStatus.ts create mode 100644 src/utils/transactions/batch/sequentialToFlatArray.ts create mode 100644 src/utils/transactions/batch/updateBatchTransactionsStatuses.ts create mode 100644 src/utils/transactions/dataDecoders/decodeTransactionData/decodeTransactionData.ts create mode 100644 src/utils/transactions/dataDecoders/decodeTransactionData/helpers/decodeByMethod.ts create mode 100644 src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getDisplayValueAndValidationWarnings.ts create mode 100644 src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getHexValidationWarnings.ts create mode 100644 src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getSmartDecodedParts.ts create mode 100644 src/utils/transactions/dataDecoders/decodeTransactionData/helpers/index.ts create mode 100644 src/utils/transactions/dataDecoders/decodeTransactionData/index.ts create mode 100644 src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeByMethod.spec.ts create mode 100644 src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts create mode 100644 src/utils/transactions/dataDecoders/decodeTransactionData/tests/getDisplayValueAndValidationWarnings.spec.ts create mode 100644 src/utils/transactions/dataDecoders/decodeTransactionData/tests/getHexValidationWarnings.spec.ts create mode 100644 src/utils/transactions/dataDecoders/decodeTransactionData/tests/getSmartDecodedParts.spec.ts create mode 100644 src/utils/transactions/dataDecoders/getDataPayloadForTransaction.ts create mode 100644 src/utils/transactions/dataDecoders/getScResultsDecodedData.ts create mode 100644 src/utils/transactions/dataDecoders/getScResultsHighlight.ts create mode 100644 src/utils/transactions/dataDecoders/getScResultsInitialDecodeMethod.ts create mode 100644 src/utils/transactions/dataDecoders/getTokenFromData.ts create mode 100644 src/utils/transactions/dataDecoders/getUnHighlightedDataFieldParts.ts create mode 100644 src/utils/transactions/dataDecoders/smartContractTransaction.ts create mode 100644 src/utils/transactions/dataDecoders/useDataDecode.ts create mode 100644 src/utils/transactions/dataDecoders/useDataDecodeMethod.ts create mode 100644 src/utils/transactions/index.ts create mode 100644 src/utils/transactions/isGuardianTx.tsx create mode 100644 src/utils/transactions/isTokenTransfer.ts create mode 100644 src/utils/transactions/parsers/getAreTransactionsOnSameShard.ts create mode 100644 src/utils/transactions/parsers/getEventListDataHexValue.ts create mode 100644 src/utils/transactions/parsers/getEventListHighlight.ts create mode 100644 src/utils/transactions/parsers/getEventListInitialDecodeMethod.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/constants.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/getInterpretedTransaction.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/explorerUrlBuilder.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getExplorerLink.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getHumanReadableTimeFormat.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getOperationsMessages.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getReceiptMessage.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getScResultsMessages.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiver.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiverAssets.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTokens.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTransferType.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getEgldValueData.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromActions.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromDataField.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromOperations.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/index.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/index.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/index.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/base-transaction-mock.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getExplorerkLink.test.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getHumanReadableTimeFormat.test.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getOperationsMessages.test.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getReceiptMessage.test.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getScResultsMessages.test.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionMethod.test.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiver.test.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiverAssets.test.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTokens.test.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTransferType.test.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/types.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/index.tsx create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/tests/extended-transaction-mock.ts create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/tests/getInterpretedTransaction.test.ts create mode 100644 src/utils/transactions/parsers/getOperationDirection.ts create mode 100644 src/utils/transactions/parsers/getOperationsDetails.ts create mode 100644 src/utils/transactions/parsers/getScamFlag.tsx create mode 100644 src/utils/transactions/parsers/getShardText.ts create mode 100644 src/utils/transactions/parsers/getTransactionActionNftText.ts create mode 100644 src/utils/transactions/parsers/getTransactionActionTokenText.ts create mode 100644 src/utils/transactions/parsers/getTransactionFee.ts create mode 100644 src/utils/transactions/parsers/getTransactionLink.ts create mode 100644 src/utils/transactions/parsers/getTransactionLinkWithLabel.ts create mode 100644 src/utils/transactions/parsers/getTransactionMessages.ts create mode 100644 src/utils/transactions/parsers/getTransactionStatus.ts create mode 100644 src/utils/transactions/parsers/getTransactionStatusText.ts create mode 100644 src/utils/transactions/parsers/getTrimmedHash.ts create mode 100644 src/utils/transactions/parsers/getUsernameForTransaction.ts create mode 100644 src/utils/transactions/parsers/getVisibleOperations.ts create mode 100644 src/utils/transactions/parsers/index.ts create mode 100644 src/utils/transactions/parsers/parseMultiEsdtTransferData.ts create mode 100644 src/utils/transactions/parsers/parseMultiEsdtTransferDataForMultipleTransactions.ts create mode 100644 src/utils/transactions/parsers/parseTransactionAfterSigning.ts create mode 100644 src/utils/transactions/parsers/tests/getScamFlag.test.ts create mode 100644 src/utils/transactions/parsers/tests/getShardText.test.ts create mode 100644 src/utils/transactions/parsers/tests/getTransactionLinkWithLabel.test.ts create mode 100644 src/utils/transactions/parsers/transactionActionUnwrapper/helpers/esdtNftUnwrapper.ts create mode 100644 src/utils/transactions/parsers/transactionActionUnwrapper/helpers/index.ts create mode 100644 src/utils/transactions/parsers/transactionActionUnwrapper/helpers/mexUnwrapper.ts create mode 100644 src/utils/transactions/parsers/transactionActionUnwrapper/helpers/stakeUnwrapper.ts create mode 100644 src/utils/transactions/parsers/transactionActionUnwrapper/index.ts create mode 100644 src/utils/transactions/parsers/transactionActionUnwrapper/tests/transactionActionUnwrapper.test.tsx create mode 100644 src/utils/transactions/parsers/transactionActionUnwrapper/transactionActionUnwrapper.ts create mode 100644 src/utils/transactions/parsers/useGetOperationList.tsx create mode 100644 src/utils/transactions/tests/builtCallbackUrl.test.ts create mode 100644 src/utils/transactions/tests/getDataPayloadForTransaction.test.ts create mode 100644 src/utils/transactions/tests/getOperationsDetails.test.ts create mode 100644 src/utils/transactions/tests/getTokenFromData.test.ts create mode 100644 src/utils/transactions/tests/getUnHighlightedDataFieldParts.test.ts create mode 100644 src/utils/transactions/tests/isGuardianTx.test.ts create mode 100644 src/utils/transactions/tests/parseMultiEsdtTransferData.test.ts create mode 100644 src/utils/transactions/tests/removeTransactionParamsFromUrl.test.ts create mode 100644 src/utils/transactions/transactionStateByStatus.ts create mode 100644 src/utils/transactions/url/buildCallbackUrl.ts create mode 100644 src/utils/transactions/url/removeTransactionParamsFromUrl.ts create mode 100644 src/utils/window/clearNavigationHistory.ts create mode 100644 src/utils/window/parseNavigationParams.ts create mode 100644 src/utils/window/removeSearchParamsFromUrl.ts create mode 100644 src/utils/window/tests/clearNavigationHistory.test.ts create mode 100644 src/utils/window/tests/parseNavigationParams.test.ts create mode 100644 src/utils/window/tests/removeSearchParamsFromUrl.test.ts diff --git a/src/constants/index.ts b/src/constants/index.ts index 9ebad72..8632378 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,7 +1,8 @@ +export * from './browser.constants'; +export * from './errorMessages.constants'; +export * from './ledger.constants'; export * from './network.constants'; export * from './placeholders.constants'; export * from './storage.constants'; +export * from './transactions.constants' export * from './window.constants'; -export * from './browser.constants'; -export * from './errorMessages.constants'; -export * from './ledger.constants'; diff --git a/src/constants/transactions.constants.ts b/src/constants/transactions.constants.ts new file mode 100644 index 0000000..beb6deb --- /dev/null +++ b/src/constants/transactions.constants.ts @@ -0,0 +1,2 @@ +export const WALLET_SIGN_SESSION = 'signSession'; +export const SDK_DAPP_VERSION = 'sdk-dapp-version'; \ No newline at end of file diff --git a/src/types/serverTransactions.types.ts b/src/types/serverTransactions.types.ts new file mode 100644 index 0000000..1e98f35 --- /dev/null +++ b/src/types/serverTransactions.types.ts @@ -0,0 +1,341 @@ +import { AssetType, ScamInfoType } from './account.types'; +import { EsdtEnumType, NftEnumType } from './tokens.types'; +import { SignedTransactionType } from './transactions.types'; + +export interface ScResultType { + callType: string; + data?: string; + gasLimit: number; + gasPrice: number; + hash: string; + nonce: number; + originalTxHash: string; + prevTxHash: string; + receiver: string; + returnMessage?: string; + sender: string; + timestamp: number; + value: string; +} + +export interface TransactionTokensType { + esdts: string[]; + nfts: string[]; +} + +export enum TransactionActionsEnum { + addLiquidity = 'addLiquidity', + addLiquidityProxy = 'addLiquidityProxy', + claimDualYield = 'claimDualYield', + claimDualYieldProxy = 'claimDualYieldProxy', + claimLockedAssets = 'claimLockedAssets', + claimRewards = 'claimRewards', + claimRewardsProxy = 'claimRewardsProxy', + compoundRewards = 'compoundRewards', + compoundRewardsProxy = 'compoundRewardsProxy', + delegate = 'delegate', + enterFarm = 'enterFarm', + enterFarmAndLockRewards = 'enterFarmAndLockRewards', + enterFarmAndLockRewardsProxy = 'enterFarmAndLockRewardsProxy', + enterFarmProxy = 'enterFarmProxy', + exitFarm = 'exitFarm', + exitFarmProxy = 'exitFarmProxy', + lockTokens = 'lockTokens', + mergeLockedAssetTokens = 'mergeLockedAssetTokens', + migrateOldTokens = 'migrateOldTokens', + ping = 'ping', + reDelegateRewards = 'reDelegateRewards', + removeLiquidity = 'removeLiquidity', + removeLiquidityProxy = 'removeLiquidityProxy', + stake = 'stake', + stakeClaimRewards = 'claimRewards', + stakeFarm = 'stakeFarm', + stakeFarmProxy = 'stakeFarmProxy', + stakeFarmTokens = 'stakeFarmTokens', + stakeFarmTokensProxy = 'stakeFarmTokensProxy', + swap = 'swap', + swapTokensFixedInput = 'swapTokensFixedInput', + swapTokensFixedOutput = 'swapTokensFixedOutput', + transfer = 'transfer', + unBond = 'unBond', + unDelegate = 'unDelegate', + unStake = 'unStake', + unbondFarm = 'unbondFarm', + unlockAssets = 'unlockAssets', + unstakeFarm = 'unstakeFarm', + unstakeFarmProxy = 'unstakeFarmProxy', + unstakeFarmTokens = 'unstakeFarmTokens', + unstakeFarmTokensProxy = 'unstakeFarmTokensProxy', + unwrapEgld = 'unwrapEgld', + withdraw = 'withdraw', + wrapEgld = 'wrapEgld', +} + +export enum TransactionActionCategoryEnum { + esdtNft = 'esdtNft', + mex = 'mex', + stake = 'stake', + scCall = 'scCall' +} + +export interface TokenArgumentType { + type: NftEnumType | EsdtEnumType; + name: string; + ticker: string; + collection?: string; + identifier?: string; + token?: string; + decimals: number; + value: string; + providerName?: string; + providerAvatar?: string; + svgUrl?: string; + valueUSD?: string; +} + +export interface TransactionActionType { + category: string; + name: TransactionActionsEnum; + description?: string; + arguments?: { [key: string]: any }; +} + +export interface UnwrapperType { + token?: TokenArgumentType[]; + tokenNoValue?: TokenArgumentType[]; + tokenNoLink?: TokenArgumentType[]; + address?: string; + egldValue?: string; + value?: string; + providerName?: string; + providerAvatar?: string; +} + +export enum TransactionOperationActionTypeEnum { + none = 'none', + transfer = 'transfer', + burn = 'burn', + addQuantity = 'addQuantity', + create = 'create', + multiTransfer = 'multiTransfer', + localMint = 'localMint', + localBurn = 'localBurn', + wipe = 'wipe', + freeze = 'freeze', + writeLog = 'writeLog', + signalError = 'signalError', + + // to be deprecated ? + ESDTLocalMint = 'ESDTLocalMint', + ESDTLocalBurn = 'ESDTLocalBurn' +} + +export enum VisibleTransactionOperationType { + nft = 'nft', + esdt = 'esdt', + egld = 'egld' +} +export enum HiddenTransactionOperationType { + none = 'none', + error = 'error', + log = 'log' +} + +export interface OperationType { + id?: string; + action: TransactionOperationActionTypeEnum; + type: VisibleTransactionOperationType | HiddenTransactionOperationType; + esdtType?: NftEnumType | EsdtEnumType; + collection?: string; + name?: string; + identifier?: string; + sender: string; + ticker?: string; + receiver: string; + value: string; + decimals?: number; + data?: string; + message?: string; + svgUrl?: string; + senderAssets?: AssetType; + receiverAssets?: AssetType; + valueUSD?: string; +} + +export interface LogType { + hash: string; + callType: string; + gasLimit: number; + gasPrice: number; + nonce: number; + prevTxHash: string; + receiver?: string; + sender: string; + value: string; + data?: string; + originalTxHash: string; + returnMessage?: string; + logs?: any; +} + +export interface EventType { + address: string; + identifier: string; + topics: string[]; + order: number; + data?: string; + additionalData?: string[]; +} + +export interface ResultLogType { + id: string; + address: string; + events: EventType[]; +} + +export interface ResultType { + hash: string; + callType: string; + gasLimit: number; + gasPrice: number; + nonce: number; + prevTxHash: string; + receiver?: string; + sender: string; + value: string; + data?: string; + originalTxHash: string; + returnMessage?: string; + logs?: ResultLogType; + senderAssets?: AssetType; + receiverAssets?: AssetType; + miniBlockHash?: string; + function?: string; + timestamp?: number; +} + +export interface ReceiptType { + value: string; + sender: string; + data: string; +} + +export interface ServerTransactionType { + fee?: string; + data: string; + gasLimit: number; + gasPrice: number; + gasUsed: number; + txHash: string; + miniBlockHash: string; + nonce: number; + receiver: string; + receiverShard: number; + round: number; + sender: string; + senderShard: number; + signature: string; + status: string; + inTransit?: boolean; + timestamp: number; + value: string; + price: number; + results?: ResultType[]; + operations?: OperationType[]; + action?: TransactionActionType; + logs?: { + id: string; + address: string; + events: EventType[]; + }; + scamInfo?: ScamInfoType; + pendingResults?: boolean; + receipt?: ReceiptType; + senderAssets?: AssetType; + receiverAssets?: AssetType; + type?: TransferTypeEnum; + originalTxHash?: string; + isNew?: boolean; // UI flag + tokenValue?: string; + tokenIdentifier?: string; + function?: string; +} + +export enum TransferTypeEnum { + Transaction = 'Transaction', + SmartContractResult = 'SmartContractResult' +} + +//#endregion + +//#region interpreted trasactions + +export enum TransactionDirectionEnum { + SELF = 'Self', + INTERNAL = 'Internal', + IN = 'In', + OUT = 'Out' +} + +export interface InterpretedTransactionType extends ServerTransactionType { + transactionDetails: { + direction?: TransactionDirectionEnum; + method: string; + transactionTokens: TokenArgumentType[]; + isContract?: boolean; + }; + links: { + senderLink?: string; + receiverLink?: string; + senderShardLink?: string; + receiverShardLink?: string; + transactionLink?: string; + }; +} + +export interface DecodeForDisplayPropsType { + input: string; + decodeMethod: DecodeMethodEnum; + identifier?: string; +} + +export interface DecodedDisplayType { + displayValue: string; + validationWarnings: string[]; +} + +export enum DecodeMethodEnum { + raw = 'raw', + text = 'text', + decimal = 'decimal', + smart = 'smart' +} + +//#endregion + +export enum BatchTransactionStatus { + pending = 'pending', + success = 'success', + invalid = 'invalid', + dropped = 'dropped', + fail = 'fail' +} + +export interface BatchTransactionsRequestType { + id: string; + transactions: SignedTransactionType[][]; +} + +export interface BatchTransactionsResponseType { + id: string; + status: BatchTransactionStatus; + transactions: SignedTransactionType[][]; + error?: string; + message?: string; + statusCode?: string; +} + +export type BatchTransactionsWSResponseType = { + batchId: string; + txHashes: string[]; +}; diff --git a/src/types/tokens.types.ts b/src/types/tokens.types.ts new file mode 100644 index 0000000..b39b9c1 --- /dev/null +++ b/src/types/tokens.types.ts @@ -0,0 +1,136 @@ +import { ScamInfoType } from './account.types'; + +export interface TokenRolesType { + address: string; + roles: string[]; +} + +export interface TokenLockedAccountType { + address: string; + name: string; + balance: string; +} +export interface TokenSupplyType { + supply: number; + circulatingSupply: number; + minted: number; + burnt: number; + initialMinted: number; + lockedAccounts?: TokenLockedAccountType[]; +} + +export interface TokenType { + identifier: string; + ticker?: string; + name: string; + balance?: string; + decimals?: number; + owner: string; + minted: string; + burnt: string; + supply: string | number; + circulatingSupply: string | number; + canBurn: boolean; + canChangeOwner: boolean; + canFreeze: boolean; + canMint: boolean; + canPause: boolean; + canUpgrade: boolean; + canWipe: boolean; + isPaused: boolean; + transactions: number; + accounts: number; + price?: number; + marketCap?: number; + valueUsd?: number; + assets?: { + website?: string; + description?: string; + status?: string; + pngUrl?: string; + svgUrl?: string; + social?: any; + extraTokens?: string[]; + lockedAccounts?: { [key: string]: string }; + ledgerSignature?: string; + }; +} + +export interface CollectionType { + collection: string; + type: NftEnumType; + name: string; + ticker: string; + timestamp: number; + canFreeze: boolean; + canWipe: boolean; + canPause: boolean; + canTransferRole: boolean; + owner: string; + decimals?: number; + assets?: { + website?: string; + description?: string; + status?: string; + pngUrl?: string; + svgUrl?: string; + }; + scamInfo?: ScamInfoType; +} + +export enum EsdtEnumType { + FungibleESDT = 'FungibleESDT' +} + +export enum NftEnumType { + NonFungibleESDT = 'NonFungibleESDT', + SemiFungibleESDT = 'SemiFungibleESDT', + MetaESDT = 'MetaESDT' +} + +export interface NftType { + identifier: string; + collection: string; + ticker?: string; + timestamp: number; + attributes: string; + nonce: number; + type: NftEnumType; + name: string; + creator: string; + royalties: number; + balance: string; + uris?: string[]; + url?: string; + thumbnailUrl?: string; + tags?: string[]; + decimals?: number; + owner?: string; + supply?: string; + isWhitelistedStorage?: boolean; + owners?: { + address: string; + balance: string; + }[]; + assets?: { + website?: string; + description?: string; + status?: string; + pngUrl?: string; + svgUrl?: string; + }; + metadata?: { + description?: string; + fileType?: string; + fileUri?: string; + fileName?: string; + }; + media?: { + url: string; + originalUrl: string; + thumbnailUrl: string; + fileType: string; + fileSize: number; + }[]; + scamInfo?: ScamInfoType; +} diff --git a/src/types/transactions.types.ts b/src/types/transactions.types.ts new file mode 100644 index 0000000..2bba39b --- /dev/null +++ b/src/types/transactions.types.ts @@ -0,0 +1,285 @@ +import { ReactNode, Dispatch, SetStateAction } from 'react'; +import { Address, Transaction } from '@multiversx/sdk-core'; +import { IPlainTransactionObject } from '@multiversx/sdk-core/out/interface'; + +import { SignStepInnerClassesType } from '../UI/SignTransactionsModals/SignWithDeviceModal/SignStep'; +import { WithClassnameType } from '../UI/types'; +import { + TransactionBatchStatusesEnum, + TransactionServerStatusesEnum, + TransactionTypesEnum +} from './enums.types'; + +export interface TransactionsToSignType { + transactions: IPlainTransactionObject[]; + callbackRoute?: string; + sessionId: string; + customTransactionInformation: CustomTransactionInformation; +} + +export interface SignedTransactionsBodyType { + transactions?: SignedTransactionType[]; + status?: TransactionBatchStatusesEnum; + errorMessage?: string; + redirectRoute?: string; + customTransactionInformation?: CustomTransactionInformation; +} + +export interface SignedTransactionsType { + [sessionId: string]: SignedTransactionsBodyType; +} + +export interface TransactionParameter { + sender: Address; + receiver: Address; + functionName: string; + inputParameters: string[]; + outputParameters: string[]; +} + +export type RawTransactionType = IPlainTransactionObject; + +export interface SignedTransactionType extends RawTransactionType { + hash: string; + status: TransactionServerStatusesEnum; + inTransit?: boolean; + errorMessage?: string; + customTransactionInformation?: CustomTransactionInformation; +} + +export interface TransactionDataTokenType { + tokenId: string; + amount: string; + receiver: string; + type?: MultiEsdtTransactionType['type'] | ''; + nonce?: string; + multiTxData?: string; +} + +export type TransactionsDataTokensType = + | Record + | undefined; + +interface MultiEsdtType { + type: + | TransactionTypesEnum.esdtTransaction + | TransactionTypesEnum.nftTransaction; + receiver: string; + token?: string; + nonce?: string; + amount?: string; + data: string; +} + +interface MultiEsdtScCallType { + type: TransactionTypesEnum.scCall; + receiver: string; + token?: string; + nonce?: string; + amount?: string; + data: string; +} + +export type MultiEsdtTransactionType = MultiEsdtType | MultiEsdtScCallType; + +export interface MultiSignTransactionType { + multiTxData?: string; + transactionIndex: number; + transaction: Transaction; +} + +export interface TokenOptionType { + name: string; + identifier: string; + balance: string; + decimals: number; + collection?: string; + avatar?: string; +} + +export interface SimpleTransactionType { + value: string; + receiver: string; + data?: string; + gasPrice?: number; + gasLimit?: number; + chainID?: string; + version?: number; + options?: number; + guardian?: string; + guardianSignature?: string; + nonce?: number; +} + +export interface TransactionsDisplayInfoType { + errorMessage?: string; + successMessage?: string; + processingMessage?: string; + submittedMessage?: string; + transactionDuration?: number; + timedOutMessage?: string; + invalidMessage?: string; +} + +export interface SendSimpleTransactionPropsType { + transactions: SimpleTransactionType[]; + minGasLimit?: number; +} + +export interface SendTransactionsPropsType { + transactions: + | Transaction + | SimpleTransactionType + | (Transaction | SimpleTransactionType)[]; + redirectAfterSign?: boolean; + signWithoutSending: boolean; + skipGuardian?: boolean; + completedTransactionsDelay?: number; + callbackRoute?: string; + transactionsDisplayInfo: TransactionsDisplayInfoType; + minGasLimit?: number; + sessionInformation?: any; + hasConsentPopup?: boolean; +} + +export interface SendBatchTransactionsPropsType { + transactions: (Transaction | SimpleTransactionType)[][]; + redirectAfterSign?: boolean; + signWithoutSending?: boolean; + skipGuardian?: boolean; + /** + * For Cross-Window provider in Safari browser, performing async calls before signing transactions needs a consent popup in order to open a new tab. + */ + hasConsentPopup?: boolean; + completedTransactionsDelay?: number; + callbackRoute?: string; + transactionsDisplayInfo: TransactionsDisplayInfoType; + minGasLimit?: number; + sessionInformation?: any; +} + +export interface SignTransactionsPropsType { + transactions: Transaction[] | Transaction; + minGasLimit?: number; // unused, will be removed in v3.0.0 + callbackRoute?: string; + transactionsDisplayInfo: TransactionsDisplayInfoType; + customTransactionInformation: CustomTransactionInformation; +} + +export interface ActiveLedgerTransactionType { + dataField: string; + isTokenTransaction: boolean; + receiverScamInfo: string | null; + transaction: Transaction; + transactionIndex: number; + transactionTokenInfo: TransactionDataTokenType; +} + +export interface SmartContractResult { + hash: string; + timestamp: number; + nonce: number; + gasLimit: number; + gasPrice: number; + value: string; + sender: string; + receiver: string; + data: string; + prevTxHash: string; + originalTxHash: string; + callType: string; + miniBlockHash: string; + returnMessage: string; +} + +export type DeviceSignedTransactions = Record; + +export interface GuardianScreenType extends WithClassnameType { + address: string; + onSignTransaction: () => Promise; + onPrev: () => void; + title?: ReactNode; + signStepInnerClasses?: SignStepInnerClassesType; + signedTransactions?: DeviceSignedTransactions; + guardianFormDescription?: ReactNode; + setSignedTransactions?: Dispatch< + SetStateAction + >; +} + +export interface SignModalPropsType extends WithClassnameType { + callbackRoute?: string; + error: string | null; + GuardianScreen?: (signProps: GuardianScreenType) => JSX.Element; + handleClose: () => void; + handleSubmit?: () => void; + modalContentClassName?: string; + sessionId?: string; + signStepInnerClasses?: SignStepInnerClassesType; + title?: ReactNode; + transactions: Transaction[]; + verifyReceiverScam?: boolean; +} + +export interface CustomTransactionInformation { + redirectAfterSign: boolean; + sessionInformation: any; + completedTransactionsDelay?: number; + signWithoutSending: boolean; + /** + * If true, transactions with lower nonces than the account nonce will not be updated with the correct nonce + */ + skipUpdateNonces?: boolean; + /** + * If true, the change guardian action will not trigger transaction version update + */ + skipGuardian?: boolean; + /** + * Keeps indexes of transactions that should be grouped together. If not provided, all transactions will be grouped together. Used only for batch transactions. + */ + grouping?: number[][]; + /** + * For Cross-Window provider in Safari browser, performing async calls before signing transactions needs a consent popup in order to open a new tab. + */ + hasConsentPopup?: boolean; +} + +export interface SendTransactionReturnType { + error?: string; + sessionId: string | null; +} + +export interface SendBatchTransactionReturnType { + error?: string; + batchId: string | null; +} + +export type GetTransactionsByHashesType = ( + pendingTransactions: PendingTransactionsType +) => Promise; + +export type GetTransactionsByHashesReturnType = { + hash: string; + invalidTransaction: boolean; + status: TransactionServerStatusesEnum; + inTransit?: boolean; + results: SmartContractResult[]; + sender: string; + receiver: string; + data: string; + previousStatus: string; + hasStatusChanged: boolean; +}[]; + +export type PendingTransactionsType = { + hash: string; + previousStatus: string; +}[]; + +export interface TransactionLinkType { + link: string; + label: string; + address: string; +} + +export type Nullable = T | null; diff --git a/src/utils/transactions/batch/generateBatchTransactionsGrouping.test.ts b/src/utils/transactions/batch/generateBatchTransactionsGrouping.test.ts new file mode 100644 index 0000000..8c7df20 --- /dev/null +++ b/src/utils/transactions/batch/generateBatchTransactionsGrouping.test.ts @@ -0,0 +1,74 @@ +import { generateBatchTransactionsGrouping } from './generateBatchTransactionsGrouping'; // Replace 'your-module' with the actual module path + +describe('generateBatchTransactionsGrouping', () => { + it('should generate batch transactions grouping correctly', () => { + const address = + 'erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv'; + const transactions = [ + [ + { + receiver: address, + sender: address, + value: '0', + data: '1' + }, + { + receiver: address, + sender: address, + value: '0', + data: '2' + } + ], + [ + { + receiver: address, + sender: address, + value: '0', + data: '3' + } + ], + [ + { + receiver: address, + sender: address, + value: '0', + data: '4' + }, + { + receiver: address, + sender: address, + value: '0', + data: '5' + }, + { + receiver: address, + sender: address, + value: '0', + data: '6' + } + ] + ]; + + const expectedGrouping = [[0, 1], [2], [3, 4, 5]]; + + const result = generateBatchTransactionsGrouping(transactions); + + expect(result).toEqual(expectedGrouping); + }); + + it('should handle empty input', () => { + const transactions: any[][] = []; + + const result = generateBatchTransactionsGrouping(transactions); + + expect(result).toEqual([]); + }); + + it('should handle nested empty arrays', () => { + const transactions = [[], [], []]; + + const result = generateBatchTransactionsGrouping(transactions); + + expect(result).toEqual([[], [], []]); + }); +}); diff --git a/src/utils/transactions/batch/generateBatchTransactionsGrouping.ts b/src/utils/transactions/batch/generateBatchTransactionsGrouping.ts new file mode 100644 index 0000000..5dabb04 --- /dev/null +++ b/src/utils/transactions/batch/generateBatchTransactionsGrouping.ts @@ -0,0 +1,11 @@ +import { Transaction } from '@multiversx/sdk-core/out'; +import { SimpleTransactionType } from 'types'; + +export const generateBatchTransactionsGrouping = ( + transactions: (Transaction | SimpleTransactionType)[][] +) => { + let indexInFlatArray = 0; + return transactions.map((group) => { + return group.map(() => indexInFlatArray++); + }); +}; diff --git a/src/utils/transactions/batch/getIsSequential.ts b/src/utils/transactions/batch/getIsSequential.ts new file mode 100644 index 0000000..7aabbaf --- /dev/null +++ b/src/utils/transactions/batch/getIsSequential.ts @@ -0,0 +1,9 @@ +import { SignedTransactionType } from 'types'; + +export function getIsSequential({ + transactions +}: { + transactions?: SignedTransactionType[] | SignedTransactionType[][]; +}) { + return transactions?.some((transaction) => Array.isArray(transaction)); +} diff --git a/src/utils/transactions/batch/getTransactionsStatus.ts b/src/utils/transactions/batch/getTransactionsStatus.ts new file mode 100644 index 0000000..8ee759c --- /dev/null +++ b/src/utils/transactions/batch/getTransactionsStatus.ts @@ -0,0 +1,37 @@ +import { SignedTransactionType, TransactionServerStatusesEnum } from 'types'; + +export const getTransactionsStatus = ({ + transactions, + hasUnrelatedTransactions +}: { + transactions: SignedTransactionType[]; + hasUnrelatedTransactions?: boolean; +}) => { + const allTxFailed = transactions.every( + ({ status }) => status === TransactionServerStatusesEnum.fail + ); + + const someTxFailed = transactions.some( + ({ status }) => status === TransactionServerStatusesEnum.fail + ); + + const isPending = transactions.some( + ({ status }) => status === TransactionServerStatusesEnum.pending + ); + + const isSuccessful = transactions.every( + ({ status }) => status === TransactionServerStatusesEnum.success + ); + + const isIncompleteFailed = + hasUnrelatedTransactions && + Boolean(!isPending && !allTxFailed && someTxFailed); + + const isFailed = hasUnrelatedTransactions + ? isIncompleteFailed + ? false + : allTxFailed + : someTxFailed; + + return { isPending, isSuccessful, isFailed, isIncompleteFailed }; +}; diff --git a/src/utils/transactions/batch/sequentialToFlatArray.ts b/src/utils/transactions/batch/sequentialToFlatArray.ts new file mode 100644 index 0000000..7a2692c --- /dev/null +++ b/src/utils/transactions/batch/sequentialToFlatArray.ts @@ -0,0 +1,12 @@ +import { SignedTransactionType } from 'types'; +import { getIsSequential } from './getIsSequential'; + +export function sequentialToFlatArray({ + transactions = [] +}: { + transactions?: SignedTransactionType[] | SignedTransactionType[][]; +}) { + return getIsSequential({ transactions }) + ? transactions.flat() + : (transactions as SignedTransactionType[]); +} diff --git a/src/utils/transactions/batch/updateBatchTransactionsStatuses.ts b/src/utils/transactions/batch/updateBatchTransactionsStatuses.ts new file mode 100644 index 0000000..cdf4e77 --- /dev/null +++ b/src/utils/transactions/batch/updateBatchTransactionsStatuses.ts @@ -0,0 +1,42 @@ +import { updateSignedTransactionStatus } from 'reduxStore/slices'; +import { store } from 'reduxStore/store'; +import { removeBatchTransactions } from 'services/transactions'; +import { SignedTransactionType, TransactionServerStatusesEnum } from 'types'; +import { sequentialToFlatArray } from './sequentialToFlatArray'; + +export function updateBatchTransactionsStatuses({ + batchId, + sessionId, + transactions +}: { + batchId: string; + sessionId: string; + transactions: SignedTransactionType[] | SignedTransactionType[][]; +}) { + const transactionsArray = sequentialToFlatArray({ transactions }); + + const batchIsSuccessful = transactionsArray.every( + (transaction) => + transaction.status === TransactionServerStatusesEnum.success + ); + + if (transactionsArray.length === 0) { + return; + } + + for (const transaction of transactionsArray) { + const { hash, status } = transaction; + + store.dispatch( + updateSignedTransactionStatus({ + sessionId, + status, + transactionHash: hash + }) + ); + } + + if (batchIsSuccessful) { + removeBatchTransactions(batchId); + } +} diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/decodeTransactionData.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/decodeTransactionData.ts new file mode 100644 index 0000000..68b05ff --- /dev/null +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/decodeTransactionData.ts @@ -0,0 +1,66 @@ +import { + DecodedDisplayType, + DecodeForDisplayPropsType, + DecodeMethodEnum +} from 'types/serverTransactions.types'; + +import { + decodeByMethod, + getDisplayValueAndValidationWarnings, + getSmartDecodedParts +} from './helpers'; + +export const decodeTransactionData = ({ + input, + decodeMethod, + identifier +}: DecodeForDisplayPropsType) => { + const display: DecodedDisplayType = { + displayValue: '', + validationWarnings: [] + }; + + if (!input.includes('@') && !input.includes('\n')) { + display.displayValue = decodeByMethod(input, decodeMethod); + + return display; + } + + if (input.includes('@')) { + const parts = input.split('@'); + const decodedParts = getDisplayValueAndValidationWarnings({ + parts, + identifier, + decodeMethod, + display + }); + + display.displayValue = decodedParts.join('@'); + } + + if (input.includes('\n')) { + const parts = input.split('\n'); + const initialDecodedParts = parts.map((part) => { + const base64Buffer = Buffer.from(part, 'base64'); + + if (decodeMethod === DecodeMethodEnum.raw) { + return part; + } + + return decodeByMethod(base64Buffer.toString('hex'), decodeMethod); + }); + + const decodedParts = + decodeMethod === DecodeMethodEnum.smart + ? getSmartDecodedParts({ + parts, + decodedParts: initialDecodedParts, + identifier + }) + : initialDecodedParts; + + display.displayValue = decodedParts.join('\n'); + } + + return display; +}; diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/decodeByMethod.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/decodeByMethod.ts new file mode 100644 index 0000000..a8d0b71 --- /dev/null +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/decodeByMethod.ts @@ -0,0 +1,61 @@ +import { Address } from '@multiversx/sdk-core/out'; +import BigNumber from 'bignumber.js'; +import { + DecodeMethodEnum, + TransactionTokensType +} from 'types/serverTransactions.types'; +import { addressIsValid } from 'utils/account/addressIsValid'; +import { isUtf8 } from 'utils/decoders'; + +export const decodeByMethod = ( + part: string, + decodeMethod: DecodeMethodEnum | string, + transactionTokens?: TransactionTokensType +) => { + switch (decodeMethod) { + case DecodeMethodEnum.text: + try { + return Buffer.from(part, 'hex').toString('utf8'); + } catch {} + + return part; + case DecodeMethodEnum.decimal: + return part !== '' ? new BigNumber(part, 16).toString(10) : ''; + case DecodeMethodEnum.smart: + try { + const bech32Encoded = Address.fromHex(part).toString(); + + if (addressIsValid(bech32Encoded)) { + return bech32Encoded; + } + } catch {} + + try { + const decoded = Buffer.from(part, 'hex').toString('utf8'); + + if (!isUtf8(decoded)) { + if (transactionTokens) { + const tokens = [ + ...transactionTokens.esdts, + ...transactionTokens.nfts + ]; + + if (tokens.some((token) => decoded.includes(token))) { + return decoded; + } + } + + const bn = new BigNumber(part, 16); + + return bn.isFinite() ? bn.toString(10) : part; + } else { + return decoded; + } + } catch {} + + return part; + case DecodeMethodEnum.raw: + default: + return part; + } +}; diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getDisplayValueAndValidationWarnings.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getDisplayValueAndValidationWarnings.ts new file mode 100644 index 0000000..b4f031e --- /dev/null +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getDisplayValueAndValidationWarnings.ts @@ -0,0 +1,52 @@ +import { DecodeMethodEnum, DecodedDisplayType } from 'types'; +import { decodeByMethod } from './decodeByMethod'; +import { getHexValidationWarnings } from './getHexValidationWarnings'; +import { getSmartDecodedParts } from './getSmartDecodedParts'; + +interface GetDecodedPartsPropsType { + parts: string[]; + decodeMethod: DecodeMethodEnum; + identifier?: string; + display: DecodedDisplayType; +} + +export const getDisplayValueAndValidationWarnings = ({ + parts, + decodeMethod, + identifier, + display +}: GetDecodedPartsPropsType) => { + const initialDecodedParts = parts.map((part, index) => { + if ( + parts.length >= 2 && + ((index === 0 && part.length < 64) || (index === 1 && !parts[0])) + ) { + const encodedDisplayValue = /[^a-z0-9]/gi.test(part); + if (encodedDisplayValue) { + return decodeByMethod(part, decodeMethod); + } else { + return part; + } + } else { + const hexValidationWarnings = getHexValidationWarnings(part); + if (hexValidationWarnings.length) { + display.validationWarnings = Array.from( + new Set([...display.validationWarnings, ...hexValidationWarnings]) + ); + } + + return decodeByMethod(part, decodeMethod); + } + }); + + const decodedParts = + decodeMethod === DecodeMethodEnum.smart + ? getSmartDecodedParts({ + parts, + decodedParts: initialDecodedParts, + identifier + }) + : initialDecodedParts; + + return decodedParts; +}; diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getHexValidationWarnings.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getHexValidationWarnings.ts new file mode 100644 index 0000000..a578926 --- /dev/null +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getHexValidationWarnings.ts @@ -0,0 +1,21 @@ +export const isHexValidCharacters = (str: string) => { + return str.toLowerCase().match(/^[0-9a-f]+$/i); +}; + +export const isHexValidLength = (str: string) => { + return str.length % 2 === 0; +}; + +export const getHexValidationWarnings = (str: string) => { + const warnings = []; + + if (str && !isHexValidCharacters(str)) { + warnings.push(`Invalid Hex characters on argument @${str}`); + } + + if (str && !isHexValidLength(str)) { + warnings.push(`Odd number of Hex characters on argument @${str}`); + } + + return warnings; +}; diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getSmartDecodedParts.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getSmartDecodedParts.ts new file mode 100644 index 0000000..ac78349 --- /dev/null +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getSmartDecodedParts.ts @@ -0,0 +1,30 @@ +import { DecodeMethodEnum, TransactionTypesEnum } from 'types'; +import { decodeByMethod } from './decodeByMethod'; + +interface SmartDecodedPartsType { + parts: string[]; + decodedParts: string[]; + identifier?: string; +} + +export const getSmartDecodedParts = ({ + parts, + decodedParts, + identifier +}: SmartDecodedPartsType) => { + const updatedParts = [...decodedParts]; + + if (parts[0] === TransactionTypesEnum.ESDTNFTTransfer && parts[2]) { + updatedParts[2] = decodeByMethod(parts[2], DecodeMethodEnum.decimal); + } + + if (identifier === TransactionTypesEnum.ESDTNFTTransfer && parts[1]) { + const base64Buffer = Buffer.from(String(parts[1]), 'base64'); + updatedParts[1] = decodeByMethod( + base64Buffer.toString('hex'), + DecodeMethodEnum.decimal + ); + } + + return updatedParts; +}; diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/index.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/index.ts new file mode 100644 index 0000000..8423728 --- /dev/null +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/index.ts @@ -0,0 +1,4 @@ +export * from './decodeByMethod'; +export * from './getDisplayValueAndValidationWarnings'; +export * from './getHexValidationWarnings'; +export * from './getSmartDecodedParts'; diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/index.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/index.ts new file mode 100644 index 0000000..41d5cb6 --- /dev/null +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/index.ts @@ -0,0 +1 @@ +export * from './decodeTransactionData'; diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeByMethod.spec.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeByMethod.spec.ts new file mode 100644 index 0000000..581370c --- /dev/null +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeByMethod.spec.ts @@ -0,0 +1,122 @@ +import { Address } from '@multiversx/sdk-core/out'; +import { DecodeMethodEnum } from 'types'; +import { addressIsValid } from 'utils/account/addressIsValid'; +import { isUtf8 } from 'utils/decoders'; +import { decodeByMethod } from '../helpers'; + +jest.mock('@multiversx/sdk-core/out', () => ({ + Address: { + fromHex: jest.fn() + } +})); + +jest.mock('utils/account/addressIsValid'); +jest.mock('utils/decoders'); + +describe('decodeByMethod', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('text decode method', () => { + it('should decode hex to utf8 text', () => { + const hexString = Buffer.from('Hello').toString('hex'); + const result = decodeByMethod(hexString, DecodeMethodEnum.text); + expect(result).toBe('Hello'); + }); + + it('should return empty string if the hex is invalid', () => { + const invalidHex = '{test: test}'; + const result = decodeByMethod(invalidHex, DecodeMethodEnum.text); + expect(result).toBe(''); + }); + }); + + describe('decimal decode method', () => { + it('should convert hex to decimal', () => { + const result = decodeByMethod('a', DecodeMethodEnum.decimal); + expect(result).toBe('10'); + }); + + it('should return empty string for empty input', () => { + const result = decodeByMethod('', DecodeMethodEnum.decimal); + expect(result).toBe(''); + }); + }); + + describe('smart decode method', () => { + it('should return bech32 address when valid', () => { + const mockAddress = + 'erd1zwq3qaa3vk5suenlkj4cf0ullwefa6h3n2394k25pxv4sz0pwhhsj9u9vk'; + + (Address.fromHex as jest.Mock).mockReturnValue({ + toString: () => mockAddress + }); + + (addressIsValid as jest.Mock).mockReturnValue(true); + + const result = decodeByMethod('validHex', DecodeMethodEnum.smart); + expect(result).toBe(mockAddress); + }); + + it('should decode to utf8 when possible and valid', () => { + (Address.fromHex as jest.Mock).mockImplementation(() => { + throw new Error(); + }); + (isUtf8 as jest.Mock).mockReturnValue(true); + + const hexString = Buffer.from('ValidText').toString('hex'); + const result = decodeByMethod(hexString, DecodeMethodEnum.smart); + expect(result).toBe('ValidText'); + }); + + it('should check for tokens when utf8 decoded but invalid', () => { + const mockTokens = { + esdts: ['token1'], + nfts: ['nft1'] + }; + + (Address.fromHex as jest.Mock).mockImplementation(() => { + throw new Error(); + }); + + (isUtf8 as jest.Mock).mockReturnValue(false); + + const hexString = Buffer.from('token1').toString('hex'); + const result = decodeByMethod( + hexString, + DecodeMethodEnum.smart, + mockTokens + ); + expect(result).toBe('token1'); + }); + + it('should convert to decimal when no other conditions met', () => { + (Address.fromHex as jest.Mock).mockImplementation(() => { + throw new Error(); + }); + (isUtf8 as jest.Mock).mockReturnValue(false); + + const result = decodeByMethod('a', DecodeMethodEnum.smart); + expect(result).toBe('10'); + }); + + it('should return original part when all decoding fails', () => { + (Address.fromHex as jest.Mock).mockImplementation(() => { + throw new Error(); + }); + + const invalidInput = 'invalid'; + const result = decodeByMethod(invalidInput, DecodeMethodEnum.smart); + expect(result).toBe(invalidInput); + }); + }); + + describe('raw decode method', () => { + it('should return original part', () => { + const part = 'rawData'; + const result = decodeByMethod(part, DecodeMethodEnum.raw); + expect(result).toBe(part); + }); + }); +}); diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts new file mode 100644 index 0000000..8f1c0d9 --- /dev/null +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts @@ -0,0 +1,149 @@ +import { + DecodeMethodEnum, + DecodeForDisplayPropsType +} from 'types/serverTransactions.types'; +import { decodeTransactionData } from '../decodeTransactionData'; +import { + decodeByMethod, + getDisplayValueAndValidationWarnings, + getSmartDecodedParts +} from '../helpers'; + +jest.mock('../helpers/decodeByMethod'); +jest.mock('../helpers/getDisplayValueAndValidationWarnings'); +jest.mock('../helpers/getSmartDecodedParts'); + +describe('decodeForDisplay', () => { + beforeEach(() => { + jest.clearAllMocks(); + (decodeByMethod as jest.Mock).mockReturnValue('decoded'); + (getDisplayValueAndValidationWarnings as jest.Mock).mockReturnValue([ + 'decodedPart1', + 'decodedPart2' + ]); + (getSmartDecodedParts as jest.Mock).mockReturnValue([ + 'smartDecoded1', + 'smartDecoded2' + ]); + }); + + it('should handle input with @ symbol', () => { + const input = 'part1@part2'; + const props: DecodeForDisplayPropsType = { + input, + decodeMethod: DecodeMethodEnum.text, + identifier: 'test' + }; + + const result = decodeTransactionData(props); + + expect(getDisplayValueAndValidationWarnings).toHaveBeenCalledWith({ + parts: ['part1', 'part2'], + identifier: 'test', + decodeMethod: DecodeMethodEnum.text, + display: expect.any(Object) + }); + expect(result.displayValue).toBe('decodedPart1@decodedPart2'); + expect(result.validationWarnings).toEqual([]); + }); + + it('should handle input with newline character using raw decode method', () => { + const input = 'part1\npart2'; + + const props: DecodeForDisplayPropsType = { + input, + decodeMethod: DecodeMethodEnum.raw, + identifier: 'test' + }; + + const result = decodeTransactionData(props); + + expect(result.displayValue).toBe(input); + expect(result.validationWarnings).toEqual([]); + }); + + it('should handle input with newline character using smart decode method', () => { + const input = 'part1\npart2'; + const props: DecodeForDisplayPropsType = { + input, + decodeMethod: DecodeMethodEnum.smart, + identifier: 'test' + }; + + const result = decodeTransactionData(props); + + expect(getSmartDecodedParts).toHaveBeenCalledWith({ + parts: ['part1', 'part2'], + decodedParts: ['decoded', 'decoded'], + identifier: 'test' + }); + + expect(result.displayValue).toBe('smartDecoded1\nsmartDecoded2'); + expect(result.validationWarnings).toEqual([]); + }); + + it('should handle input with both @ and newline characters', () => { + const input = 'part1@part2\npart3'; + const props: DecodeForDisplayPropsType = { + input, + decodeMethod: DecodeMethodEnum.text, + identifier: 'test' + }; + + const result = decodeTransactionData(props); + + expect(getDisplayValueAndValidationWarnings).toHaveBeenCalled(); + expect(decodeByMethod).toHaveBeenCalled(); + expect(result.validationWarnings).toEqual([]); + }); + + it('should handle simple input without @ or newline', () => { + const input = 'simpleInput'; + const props: DecodeForDisplayPropsType = { + input, + decodeMethod: DecodeMethodEnum.text, + identifier: 'test' + }; + + const result = decodeTransactionData(props); + + expect(decodeByMethod).toHaveBeenCalledWith(input, DecodeMethodEnum.text); + expect(result.displayValue).toBe('decoded'); + expect(result.validationWarnings).toEqual([]); + }); + + it('should preserve validation warnings from getDisplayValueAndValidationWarnings', () => { + const input = 'part1@part2'; + const mockWarnings = ['warning1']; + (getDisplayValueAndValidationWarnings as jest.Mock).mockImplementation( + ({ display }) => { + display.validationWarnings = mockWarnings; + return ['decodedPart1', 'decodedPart2']; + } + ); + + const props: DecodeForDisplayPropsType = { + input, + decodeMethod: DecodeMethodEnum.text, + identifier: 'test' + }; + + const result = decodeTransactionData(props); + + expect(result.validationWarnings).toEqual(mockWarnings); + }); + + it('should handle empty input', () => { + const props: DecodeForDisplayPropsType = { + input: '', + decodeMethod: DecodeMethodEnum.text, + identifier: 'test' + }; + + const result = decodeTransactionData(props); + + expect(decodeByMethod).toHaveBeenCalledWith('', DecodeMethodEnum.text); + expect(result.displayValue).toBe('decoded'); + expect(result.validationWarnings).toEqual([]); + }); +}); diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getDisplayValueAndValidationWarnings.spec.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getDisplayValueAndValidationWarnings.spec.ts new file mode 100644 index 0000000..2617bf4 --- /dev/null +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getDisplayValueAndValidationWarnings.spec.ts @@ -0,0 +1,199 @@ +import { DecodeMethodEnum, DecodedDisplayType } from 'types'; +import { + decodeByMethod, + getHexValidationWarnings, + getSmartDecodedParts, + getDisplayValueAndValidationWarnings +} from '../helpers'; + +jest.mock('../helpers/decodeByMethod'); +jest.mock('../helpers/getHexValidationWarnings'); +jest.mock('../helpers/getSmartDecodedParts'); + +describe('getDisplayValueAndValidationWarnings', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const createMockDisplay = (): DecodedDisplayType => ({ + displayValue: '', + validationWarnings: [] + }); + + it('should handle encoded display value in first two parts', () => { + const mockParts = ['short', 'test@123']; + const mockDisplay = createMockDisplay(); + (decodeByMethod as jest.Mock).mockReturnValue('decoded'); + + const result = getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(decodeByMethod).toHaveBeenCalledWith( + 'test@123', + DecodeMethodEnum.text + ); + + expect(result).toEqual(['short', 'decoded']); + }); + + it('should pass through non-encoded values in first two parts', () => { + const mockParts = ['short', 'test123']; + const mockDisplay = createMockDisplay(); + + const result = getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(decodeByMethod).toHaveBeenCalledWith( + 'test123', + DecodeMethodEnum.text + ); + + expect(result).toEqual(['short', 'decoded']); + }); + + it('should handle hex validation warnings', () => { + const mockParts = ['a'.repeat(64), 'value2']; + const mockWarnings = ['warning1']; + const mockDisplay = createMockDisplay(); + mockDisplay.validationWarnings = ['existing']; + + (getHexValidationWarnings as jest.Mock).mockReturnValue(mockWarnings); + (decodeByMethod as jest.Mock).mockReturnValue('decoded'); + + const result = getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(getHexValidationWarnings).toHaveBeenCalledWith('a'.repeat(64)); + expect(mockDisplay.validationWarnings).toEqual(['existing', 'warning1']); + expect(result).toEqual(['decoded', 'decoded']); + }); + + it('should use smart decoding when decodeMethod is smart', () => { + const mockParts = ['value1', 'value2']; + const mockInitialDecoded = ['value1', 'decoded1']; + const mockSmartDecoded = ['smart1', 'smart2']; + const mockDisplay = createMockDisplay(); + + (decodeByMethod as jest.Mock) + .mockReturnValueOnce('decoded1') + .mockReturnValueOnce('decoded2'); + (getSmartDecodedParts as jest.Mock).mockReturnValue(mockSmartDecoded); + + const result = getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.smart, + identifier: 'test', + display: mockDisplay + }); + + expect(getSmartDecodedParts).toHaveBeenCalledWith({ + parts: mockParts, + decodedParts: mockInitialDecoded, + identifier: 'test' + }); + + expect(result).toEqual(mockSmartDecoded); + }); + + it('should handle empty parts array', () => { + const mockDisplay = createMockDisplay(); + + const result = getDisplayValueAndValidationWarnings({ + parts: [], + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(result).toEqual([]); + expect(decodeByMethod).not.toHaveBeenCalled(); + }); + + it('should deduplicate validation warnings', () => { + const mockParts = ['value1', 'value2']; + const mockWarnings = ['warning1', 'warning1', 'warning2']; + const mockDisplay = createMockDisplay(); + mockDisplay.validationWarnings = ['warning1']; + + (getHexValidationWarnings as jest.Mock).mockReturnValue(mockWarnings); + (decodeByMethod as jest.Mock).mockReturnValue('decoded'); + + getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(mockDisplay.validationWarnings).toEqual(['warning1', 'warning2']); + }); + + it('should preserve displayValue in display object', () => { + const mockParts = ['value1', 'value2']; + const mockDisplay = createMockDisplay(); + mockDisplay.displayValue = 'initial value'; + + getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(mockDisplay.displayValue).toBe('initial value'); + }); + + it('should handle hex validation and decoding for non-special parts', () => { + const mockParts = ['short', 'xyz123']; + const mockDisplay = createMockDisplay(); + (decodeByMethod as jest.Mock).mockReturnValue('decoded'); + (getHexValidationWarnings as jest.Mock).mockReturnValue([ + 'Invalid Hex characters on argument @xyz123' + ]); + + const result = getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(getHexValidationWarnings).toHaveBeenCalledWith('xyz123'); + expect(decodeByMethod).toHaveBeenCalledWith( + 'xyz123', + DecodeMethodEnum.text + ); + expect(mockDisplay.validationWarnings).toContain( + 'Invalid Hex characters on argument @xyz123' + ); + expect(result).toEqual(['short', 'decoded']); + }); + + it('should not add duplicate validation warnings', () => { + const mockParts = ['short', 'xyz123']; + const mockDisplay = createMockDisplay(); + mockDisplay.validationWarnings = [ + 'Invalid Hex characters on argument @xyz123' + ]; + (decodeByMethod as jest.Mock).mockReturnValue('decoded'); + (getHexValidationWarnings as jest.Mock).mockReturnValue([ + 'Invalid Hex characters on argument @xyz123' + ]); + + getDisplayValueAndValidationWarnings({ + parts: mockParts, + decodeMethod: DecodeMethodEnum.text, + display: mockDisplay + }); + + expect(mockDisplay.validationWarnings).toHaveLength(1); + expect(mockDisplay.validationWarnings).toEqual([ + 'Invalid Hex characters on argument @xyz123' + ]); + }); +}); diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getHexValidationWarnings.spec.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getHexValidationWarnings.spec.ts new file mode 100644 index 0000000..ca73785 --- /dev/null +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getHexValidationWarnings.spec.ts @@ -0,0 +1,59 @@ +import { + getHexValidationWarnings, + isHexValidCharacters, + isHexValidLength +} from '../helpers'; + +describe('Hex Validation Functions', () => { + describe('isHexValidCharacters', () => { + it('should return match for valid hex characters', () => { + expect(isHexValidCharacters('123abc')).toBeTruthy(); + expect(isHexValidCharacters('ABCDEF')).toBeTruthy(); + }); + + it('should return null for invalid hex characters', () => { + expect(isHexValidCharacters('xyz')).toBeNull(); + expect(isHexValidCharacters('123g')).toBeNull(); + }); + }); + + describe('isHexValidLength', () => { + it('should return true for even length strings', () => { + expect(isHexValidLength('1234')).toBeTruthy(); + expect(isHexValidLength('ab')).toBeTruthy(); + }); + + it('should return false for odd length strings', () => { + expect(isHexValidLength('123')).toBeFalsy(); + expect(isHexValidLength('a')).toBeFalsy(); + }); + }); + + describe('getHexValidationWarnings', () => { + it('should return empty array for valid hex string', () => { + expect(getHexValidationWarnings('1234ab')).toEqual([]); + }); + + it('should return warning for invalid hex characters', () => { + const result = getHexValidationWarnings('xyz123'); + + expect(result).toContain('Invalid Hex characters on argument @xyz123'); + }); + + it('should return warning for odd length', () => { + const result = getHexValidationWarnings('123'); + expect(result).toContain('Odd number of Hex characters on argument @123'); + }); + + it('should return both warnings when both conditions fail', () => { + const result = getHexValidationWarnings('xyz'); + expect(result).toHaveLength(2); + expect(result).toContain('Invalid Hex characters on argument @xyz'); + expect(result).toContain('Odd number of Hex characters on argument @xyz'); + }); + + it('should return empty array for empty string', () => { + expect(getHexValidationWarnings('')).toEqual([]); + }); + }); +}); diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getSmartDecodedParts.spec.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getSmartDecodedParts.spec.ts new file mode 100644 index 0000000..d4ad1d1 --- /dev/null +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getSmartDecodedParts.spec.ts @@ -0,0 +1,93 @@ +import { DecodeMethodEnum, TransactionTypesEnum } from 'types'; +import { decodeByMethod, getSmartDecodedParts } from '../helpers'; + +jest.mock('../helpers/decodeByMethod'); + +describe('getSmartDecodedParts', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return unchanged decodedParts when no conditions are met', () => { + const input = { + parts: ['someType'], + decodedParts: ['decodedValue'] + }; + + const result = getSmartDecodedParts(input); + expect(result).toEqual(['decodedValue']); + expect(decodeByMethod).not.toHaveBeenCalled(); + }); + + it('should decode parts[2] when first part is ESDTNFTTransfer', () => { + const mockDecodedValue = '123'; + (decodeByMethod as jest.Mock).mockReturnValue(mockDecodedValue); + + const input = { + parts: [TransactionTypesEnum.ESDTNFTTransfer, 'value1', 'hexValue'], + decodedParts: ['decoded1', 'decoded2', 'decoded3'] + }; + + const result = getSmartDecodedParts(input); + + expect(decodeByMethod).toHaveBeenCalledWith( + 'hexValue', + DecodeMethodEnum.decimal + ); + expect(result).toEqual(['decoded1', 'decoded2', mockDecodedValue]); + }); + + it('should decode parts[1] when identifier is ESDTNFTTransfer', () => { + const mockDecodedValue = '456'; + (decodeByMethod as jest.Mock).mockReturnValue(mockDecodedValue); + + const base64Value = Buffer.from('test').toString('base64'); + const expectedHexValue = Buffer.from('test').toString('hex'); + + const input = { + parts: ['someType', base64Value], + decodedParts: ['decoded1', 'decoded2'], + identifier: TransactionTypesEnum.ESDTNFTTransfer + }; + + const result = getSmartDecodedParts(input); + + expect(decodeByMethod).toHaveBeenCalledWith( + expectedHexValue, + DecodeMethodEnum.decimal + ); + expect(result).toEqual(['decoded1', mockDecodedValue]); + }); + + it('should handle both conditions simultaneously', () => { + const mockDecodedValues = ['123', '456']; + (decodeByMethod as jest.Mock) + .mockReturnValueOnce(mockDecodedValues[0]) + .mockReturnValueOnce(mockDecodedValues[1]); + + const base64Value = Buffer.from('test').toString('base64'); + const expectedHexValue = Buffer.from('test').toString('hex'); + + const input = { + parts: [TransactionTypesEnum.ESDTNFTTransfer, base64Value, 'hexValue'], + decodedParts: ['decoded1', 'decoded2', 'decoded3'], + identifier: TransactionTypesEnum.ESDTNFTTransfer + }; + + const result = getSmartDecodedParts(input); + + expect(decodeByMethod).toHaveBeenCalledWith( + 'hexValue', + DecodeMethodEnum.decimal + ); + expect(decodeByMethod).toHaveBeenCalledWith( + expectedHexValue, + DecodeMethodEnum.decimal + ); + expect(result).toEqual([ + 'decoded1', + mockDecodedValues[1], + mockDecodedValues[0] + ]); + }); +}); diff --git a/src/utils/transactions/dataDecoders/getDataPayloadForTransaction.ts b/src/utils/transactions/dataDecoders/getDataPayloadForTransaction.ts new file mode 100644 index 0000000..ece5ccd --- /dev/null +++ b/src/utils/transactions/dataDecoders/getDataPayloadForTransaction.ts @@ -0,0 +1,20 @@ +import { TransactionPayload } from '@multiversx/sdk-core/out'; +import { isStringBase64 } from '../../decoders'; + +/** + * @description We need to check if the data field was already encoded and to prevent additional encoding + * The TransactionPayload is used in the context of the Transaction class + * which must always have a non-encoded data field + * + * @see The tests for this function are in src/utils/transactions/tests/getDataPayloadForTransaction.test.ts + * @param data - data field from transaction + */ +export const getDataPayloadForTransaction = ( + data?: string +): TransactionPayload => { + const defaultData = data ?? ''; + + return isStringBase64(defaultData) + ? TransactionPayload.fromEncoded(defaultData) + : new TransactionPayload(defaultData); +}; diff --git a/src/utils/transactions/dataDecoders/getScResultsDecodedData.ts b/src/utils/transactions/dataDecoders/getScResultsDecodedData.ts new file mode 100644 index 0000000..409a176 --- /dev/null +++ b/src/utils/transactions/dataDecoders/getScResultsDecodedData.ts @@ -0,0 +1,15 @@ +import { decodePart } from 'utils/decoders/decodePart'; + +export const getScResultsDecodedData = (data: string) => { + const parts = Buffer.from(data, 'base64').toString().split('@'); + + if (parts.length >= 2) { + if (parts[0].length > 0) { + parts[0] = decodePart(parts[0]); + } else { + parts[1] = decodePart(parts[1]); + } + } + + return parts.join('@'); +}; diff --git a/src/utils/transactions/dataDecoders/getScResultsHighlight.ts b/src/utils/transactions/dataDecoders/getScResultsHighlight.ts new file mode 100644 index 0000000..c42064d --- /dev/null +++ b/src/utils/transactions/dataDecoders/getScResultsHighlight.ts @@ -0,0 +1,11 @@ +import { getWindowLocation } from 'utils/window/getWindowLocation'; + +export const getScResultsHighlight = (resultHash: string) => { + const { hash } = getWindowLocation(); + const formattedHash = hash + .substring(0, hash.indexOf('/') > 0 ? hash.indexOf('/') : hash.length) + .replace('#', ''); + const highlight = formattedHash === resultHash; + + return highlight; +}; diff --git a/src/utils/transactions/dataDecoders/getScResultsInitialDecodeMethod.ts b/src/utils/transactions/dataDecoders/getScResultsInitialDecodeMethod.ts new file mode 100644 index 0000000..c7c20f2 --- /dev/null +++ b/src/utils/transactions/dataDecoders/getScResultsInitialDecodeMethod.ts @@ -0,0 +1,17 @@ + +import { getWindowLocation } from 'utils/window/getWindowLocation'; + +export const getInitialScResultsDecodeMethod = () => { + const { hash } = getWindowLocation(); + + const initialDecodeMethod = + hash.indexOf('/') > 0 + ? hash.substring(hash.indexOf('/') + 1) + : DecodeMethodEnum.raw; + + const isInitialDecodedMethod = + initialDecodeMethod && + Object.values(DecodeMethodEnum).includes(initialDecodeMethod); + + return isInitialDecodedMethod ? initialDecodeMethod : DecodeMethodEnum.raw; +}; diff --git a/src/utils/transactions/dataDecoders/getTokenFromData.ts b/src/utils/transactions/dataDecoders/getTokenFromData.ts new file mode 100644 index 0000000..a0b650f --- /dev/null +++ b/src/utils/transactions/dataDecoders/getTokenFromData.ts @@ -0,0 +1,93 @@ +import { Address } from '@multiversx/sdk-core'; +import BigNumber from 'bignumber.js'; +import { decodePart } from 'utils/decoders/decodePart'; + +const noData = { + tokenId: '', + amount: '' +}; + +export const decodeData = (data: string) => { + const nonceIndex = 2; + const amountIndex = 3; + const parts = data.split('@'); + const decodedParts = parts.map((part, i) => + [nonceIndex, amountIndex].includes(i) ? part : decodePart(part) + ); + return decodedParts; +}; + +export function getTokenFromData(data?: string): { + tokenId: string; + amount: string; + collection?: string; + nonce?: string; + receiver?: string; +} { + if (!data) { + return noData; + } + + const isTokenTransfer = data.startsWith(TransactionTypesEnum.ESDTTransfer); + const isNftTransfer = + data.startsWith(TransactionTypesEnum.ESDTNFTTransfer) && data.includes('@'); + const isNftBurn = + data.startsWith(TransactionTypesEnum.ESDTNFTBurn) && data.includes('@'); + + if (isTokenTransfer) { + const [, encodedToken, encodedAmount] = data.split('@'); + try { + const tokenId = Buffer.from(encodedToken, 'hex').toString('ascii'); + + if (!tokenId) { + return noData; + } + + const amount = new BigNumber( + '0x' + encodedAmount.replace('0x', '') + ).toString(10); + + return { + tokenId, + amount + }; + } catch (e) { + console.error('Error getting token from transaction data', e); + } + } + + if (isNftTransfer) { + try { + const [, /*ESDTNFTTransfer*/ collection, nonce, quantity, receiver] = + decodeData(data); + if ( + [collection, nonce, quantity, receiver].every((el) => Boolean(el)) && + addressIsValid(new Address(receiver).bech32()) + ) { + return { + tokenId: `${collection}-${nonce}`, + amount: new BigNumber(quantity, 16).toString(10), + collection, + nonce, + receiver: new Address(receiver).bech32() + }; + } + } catch (err) {} + } + + if (isNftBurn) { + try { + const [, /*ESDTNFTBurn*/ collection, nonce, quantity] = decodeData(data); + if ([collection, nonce, quantity].every((el) => Boolean(el))) { + return { + tokenId: `${collection}-${nonce}`, + amount: new BigNumber(quantity, 16).toString(10), + collection, + nonce + }; + } + } catch (err) {} + } + + return noData; +} diff --git a/src/utils/transactions/dataDecoders/getUnHighlightedDataFieldParts.ts b/src/utils/transactions/dataDecoders/getUnHighlightedDataFieldParts.ts new file mode 100644 index 0000000..9e8e66e --- /dev/null +++ b/src/utils/transactions/dataDecoders/getUnHighlightedDataFieldParts.ts @@ -0,0 +1,22 @@ +export const getUnHighlightedDataFieldParts = ({ + data, + highlight, + occurrences, + transactionIndex +}: { + data: string; + highlight: string; + occurrences: number[]; + transactionIndex: number; +}) => { + const highlightIndex = + occurrences[transactionIndex] || data.indexOf(highlight); + const highlightLength = highlight.length; + const start = data.slice(0, highlightIndex); + const end = data.slice(highlightIndex + highlightLength); + + return { + start, + end + }; +}; diff --git a/src/utils/transactions/dataDecoders/smartContractTransaction.ts b/src/utils/transactions/dataDecoders/smartContractTransaction.ts new file mode 100644 index 0000000..a5f0137 --- /dev/null +++ b/src/utils/transactions/dataDecoders/smartContractTransaction.ts @@ -0,0 +1,111 @@ +import { Address, TransactionPayload } from '@multiversx/sdk-core'; +import { TypesOfSmartContractCallsEnum } from 'types'; +import { addressIsValid } from '../../validation'; +import { isStringBase64 } from '../../decoders'; + +export const ESDTTransferTypes = [ + 'ESDTNFTTransfer', + 'ESDTNFTBurn', + 'ESDTNFTAddQuantity', + 'ESDTNFTCreate', + 'MultiESDTNFTTransfer', + 'ESDTTransfer', + 'ESDTBurn', + 'ESDTLocalMint', + 'ESDTLocalBurn', + 'ESDTWipe', + 'ESDTFreeze' +]; + +export const isContract = ( + receiver: string, + sender?: string, + data = '' +) => { + const isValid = addressIsValid(receiver); + + if (!isValid) { + return false; + } + + const isContract = new Address(receiver).isContractAddress(); + + if (isContract) { + return true; + } + + const extractedAddress = getAddressFromDataField({ receiver, data }); + + if (!extractedAddress) { + return false; + } + + const isExtractedAddressContractCall = new Address( + extractedAddress + ).isContractAddress(); + + return ( + isExtractedAddressContractCall || isSelfESDTContract(receiver, sender, data) + ); +} + +const isHexValidCharacters = (str: string) => { + return str.toLowerCase().match(/[0-9a-f]/g); +}; +const isHexValidLength = (str: string) => { + return str.length % 2 === 0; +}; + +export function isSelfESDTContract( + receiver: string, + sender?: string, + data?: string +) { + const parts = data?.split('@'); + if (parts == null) { + return false; + } + const [type, ...restParts] = parts; + const isSelfTransaction = + sender != null && receiver != null && receiver === sender; + const isCorrectESDTType = ESDTTransferTypes.includes(type); + const areDataPartsValid = restParts.every( + (part) => isHexValidCharacters(part) && isHexValidLength(part) + ); + return isSelfTransaction && isCorrectESDTType && areDataPartsValid; +} + +export function getAddressFromDataField({ + receiver, + data +}: { + receiver: string; + data: string; +}) { + try { + if (!data) { + return receiver; + } + const parsedData = isStringBase64(data) + ? TransactionPayload.fromEncoded(data).toString() + : data; + + const addressIndex = getAddressIndex(parsedData); + + const parts = parsedData.split('@'); + return addressIndex > -1 ? parts[addressIndex] : receiver; + } catch (err) { + console.log(err); + return; + } +} + +function getAddressIndex(data: string) { + if (data.includes(TypesOfSmartContractCallsEnum.MultiESDTNFTTransfer)) { + return 1; + } + if (data.includes(TypesOfSmartContractCallsEnum.ESDTNFTTransfer)) { + return 4; + } + return -1; +} diff --git a/src/utils/transactions/dataDecoders/useDataDecode.ts b/src/utils/transactions/dataDecoders/useDataDecode.ts new file mode 100644 index 0000000..63e4f54 --- /dev/null +++ b/src/utils/transactions/dataDecoders/useDataDecode.ts @@ -0,0 +1,64 @@ +import { useEffect, useState, SetStateAction, Dispatch } from 'react'; + +import { DecodeMethodEnum } from 'types'; +import { decodeTransactionData } from './decodeTransactionData'; + +export interface DataDecodeType { + value: string; + className?: string; + initialDecodeMethod?: DecodeMethodEnum | string; + setDecodeMethod?: Dispatch>; + identifier?: string; +} + +const decodeOptions = [ + { + label: 'Raw', + value: DecodeMethodEnum.raw + }, + { + label: 'Text', + value: DecodeMethodEnum.text + }, + { + label: 'Decimal', + value: DecodeMethodEnum.decimal + }, + { + label: 'Smart', + value: DecodeMethodEnum.smart + } +]; + +export const useDataDecode = ({ + value, + initialDecodeMethod, + setDecodeMethod, + identifier +}: DataDecodeType) => { + const [activeKey, setActiveKey] = useState( + initialDecodeMethod && + Object.values(DecodeMethodEnum).includes(initialDecodeMethod) + ? initialDecodeMethod + : DecodeMethodEnum.raw + ); + + const { displayValue, validationWarnings } = decodeTransactionData({ + input: value, + decodeMethod: activeKey as DecodeMethodEnum, + identifier + }); + + useEffect(() => { + if (setDecodeMethod) { + setDecodeMethod(activeKey); + } + }, [activeKey]); + + return { + displayValue, + validationWarnings, + setActiveKey, + decodeOptions + }; +}; diff --git a/src/utils/transactions/dataDecoders/useDataDecodeMethod.ts b/src/utils/transactions/dataDecoders/useDataDecodeMethod.ts new file mode 100644 index 0000000..99674c3 --- /dev/null +++ b/src/utils/transactions/dataDecoders/useDataDecodeMethod.ts @@ -0,0 +1,26 @@ +import { useEffect, useState } from 'react'; +import { DecodeMethodEnum } from 'types'; +import { getWindowLocation } from 'utils/window/getWindowLocation'; + +export const useDataDecodeMethod = () => { + const { hash, pathname } = getWindowLocation(); + const hashDecodeMethod = hash.replace('#', ''); + const initialDecodeMethod = + hashDecodeMethod && + Object.values(DecodeMethodEnum).includes(hashDecodeMethod) + ? hashDecodeMethod + : DecodeMethodEnum.raw; + const [decodeMethod, setDecodeMethod] = useState(hashDecodeMethod); + + useEffect(() => { + if (decodeMethod && decodeMethod !== DecodeMethodEnum.raw) { + window?.history.replaceState( + {}, + document?.title, + `${pathname}#${decodeMethod}` + ); + } + }, [decodeMethod, pathname]); + + return { initialDecodeMethod, decodeMethod, setDecodeMethod }; +}; diff --git a/src/utils/transactions/index.ts b/src/utils/transactions/index.ts new file mode 100644 index 0000000..74706c6 --- /dev/null +++ b/src/utils/transactions/index.ts @@ -0,0 +1,14 @@ +export * from './url/buildCallbackUrl'; +export * from './parsers/getAreTransactionsOnSameShard'; +export * from './getInterpretedTransaction'; +export * from './dataDecoders/getTokenFromData'; +export * from './parsers/getTransactionLink'; +export * from './dataDecoders/getUnHighlightedDataFieldParts'; +export * from './isGuardianTx'; +export * from './isTokenTransfer'; +export * from './parsers/parseMultiEsdtTransferData'; +export * from './parsers/parseTransactionAfterSigning'; +export * from './url/removeTransactionParamsFromUrl'; +export * from './parsers'; +export * from './transactionStateByStatus'; +export * from './parsers/getOperationsDetails'; diff --git a/src/utils/transactions/isGuardianTx.tsx b/src/utils/transactions/isGuardianTx.tsx new file mode 100644 index 0000000..00a49bf --- /dev/null +++ b/src/utils/transactions/isGuardianTx.tsx @@ -0,0 +1,21 @@ +import { GuardianActionsEnum } from 'constants/index'; + +export const isGuardianTx = ({ + data, + onlySetGuardian +}: { + data?: string; + onlySetGuardian?: boolean; +}) => { + if (!data) { + return false; + } + + if (onlySetGuardian) { + return data.startsWith(GuardianActionsEnum.SetGuardian); + } + + return Object.values(GuardianActionsEnum).some((action) => + data.startsWith(action) + ); +}; diff --git a/src/utils/transactions/isTokenTransfer.ts b/src/utils/transactions/isTokenTransfer.ts new file mode 100644 index 0000000..8575d9e --- /dev/null +++ b/src/utils/transactions/isTokenTransfer.ts @@ -0,0 +1,9 @@ +export function isTokenTransfer({ + tokenId, + erdLabel +}: { + tokenId: string | undefined; + erdLabel: string; +}) { + return Boolean(tokenId && tokenId !== erdLabel); +} diff --git a/src/utils/transactions/parsers/getAreTransactionsOnSameShard.ts b/src/utils/transactions/parsers/getAreTransactionsOnSameShard.ts new file mode 100644 index 0000000..6debfc5 --- /dev/null +++ b/src/utils/transactions/parsers/getAreTransactionsOnSameShard.ts @@ -0,0 +1,34 @@ +import { isCrossShardTransaction } from 'services/transactions/isCrossShardTransaction'; +import { SignedTransactionType } from 'types'; +import { getAddressFromDataField } from 'utils/smartContracts'; + +export const getAreTransactionsOnSameShard = ( + transactions?: SignedTransactionType[], + accountShard = 1 +): boolean => { + if (!transactions?.length) { + return true; + } + + return transactions.reduce( + (prevTxIsSameShard: boolean, { receiver, data }: SignedTransactionType) => { + const receiverAddress = getAddressFromDataField({ + receiver, + data: data ?? '' + }); + + if (receiverAddress == null) { + return prevTxIsSameShard; + } + + return ( + prevTxIsSameShard && + isCrossShardTransaction({ + receiverAddress, + senderShard: accountShard + }) + ); + }, + true + ); +}; diff --git a/src/utils/transactions/parsers/getEventListDataHexValue.ts b/src/utils/transactions/parsers/getEventListDataHexValue.ts new file mode 100644 index 0000000..f6ff593 --- /dev/null +++ b/src/utils/transactions/parsers/getEventListDataHexValue.ts @@ -0,0 +1,7 @@ +import { EventType } from 'types/serverTransactions.types'; + +export const getEventListDataHexValue = (event: EventType) => { + const dataBase64Buffer = Buffer.from(String(event?.data), 'base64'); + const dataHexValue = dataBase64Buffer.toString('hex'); + return dataHexValue; +}; diff --git a/src/utils/transactions/parsers/getEventListHighlight.ts b/src/utils/transactions/parsers/getEventListHighlight.ts new file mode 100644 index 0000000..8ddcf08 --- /dev/null +++ b/src/utils/transactions/parsers/getEventListHighlight.ts @@ -0,0 +1,13 @@ +import { EventType } from 'types/serverTransactions.types'; +import { getWindowLocation } from 'utils/window/getWindowLocation'; + +export const getEventListHighlight = (event: EventType, id?: string) => { + const { hash } = getWindowLocation(); + const hashValues = hash.split('/'); + const formattedHash = hashValues[0] ? hashValues[0].replace('#', '') : ''; + const eventOrder = hashValues[1] ?? 0; + + const highlight = formattedHash === id && event.order === Number(eventOrder); + + return highlight; +}; diff --git a/src/utils/transactions/parsers/getEventListInitialDecodeMethod.ts b/src/utils/transactions/parsers/getEventListInitialDecodeMethod.ts new file mode 100644 index 0000000..e6ed1d1 --- /dev/null +++ b/src/utils/transactions/parsers/getEventListInitialDecodeMethod.ts @@ -0,0 +1,9 @@ +import { DecodeMethodEnum } from 'types/serverTransactions.types'; +import { getWindowLocation } from 'utils/window/getWindowLocation'; + +export const getEventListInitialDecodeMethod = () => { + const { hash } = getWindowLocation(); + const hashValues = hash.split('/'); + const initialDecodeMethod = hashValues[2] ?? DecodeMethodEnum.raw; + return initialDecodeMethod; +}; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/constants.ts b/src/utils/transactions/parsers/getInterpretedTransaction/constants.ts new file mode 100644 index 0000000..20dd6f6 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/constants.ts @@ -0,0 +1,32 @@ +import { TransactionActionsEnum } from 'types/serverTransactions.types'; + +/** + * If `action.name` or `function` is in `ACTIONS_WITH_MANDATORY_OPERATIONS[]`, transaction value will be computed based `operations` field + */ +export const ACTIONS_WITH_MANDATORY_OPERATIONS = [ + TransactionActionsEnum.reDelegateRewards, + TransactionActionsEnum.claimRewards, + TransactionActionsEnum.unBond +]; + +/** + * If `action.name` is in `ACTIONS_WITH_EGLD_VALUE[]`, transaction value will be returned directly + */ +export const ACTIONS_WITH_EGLD_VALUE = [ + TransactionActionsEnum.wrapEgld, + TransactionActionsEnum.unwrapEgld +]; + +/** + * If `action.name` is in `ACTIONS_WITH_VALUE_IN_DATA_FIELD[]`, transaction value will be computed based `data` field + */ +export const ACTIONS_WITH_VALUE_IN_DATA_FIELD = [ + TransactionActionsEnum.unStake +]; + +/** + * If `action.name` is in `ACTIONS_WITH_VALUE_IN_ACTION_FIELD[]`, transaction value will be computed based `action` field + */ +export const ACTIONS_WITH_VALUE_IN_ACTION_FIELD = [ + TransactionActionsEnum.unDelegate +]; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/getInterpretedTransaction.ts b/src/utils/transactions/parsers/getInterpretedTransaction/getInterpretedTransaction.ts new file mode 100644 index 0000000..ec66ae3 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/getInterpretedTransaction.ts @@ -0,0 +1,88 @@ +import { + InterpretedTransactionType, + ServerTransactionType +} from 'types/serverTransactions.types'; +import { TokenArgumentType } from 'types/serverTransactions.types'; +import { isContract } from '../../dataDecoders/smartContractTransaction'; +import { getTokenFromData } from 'utils/transactions/dataDecoders/getTokenFromData'; +import { + getExplorerLink, + getTransactionMethod, + getTransactionReceiver, + getTransactionReceiverAssets, + getTransactionTokens, + getTransactionTransferType, + explorerUrlBuilder +} from './helpers'; + +export interface GetInterpretedTransactionType { + address: string; + explorerAddress: string; + transaction: ServerTransactionType; +} + +export function getInterpretedTransaction({ + transaction, + address, + explorerAddress +}: GetInterpretedTransactionType): InterpretedTransactionType { + const tokenIdentifier = + transaction.tokenIdentifier ?? getTokenFromData(transaction.data).tokenId; + + const receiver = getTransactionReceiver(transaction); + const receiverAssets = getTransactionReceiverAssets(transaction); + + const direction = getTransactionTransferType(address, transaction, receiver); + const method = getTransactionMethod(transaction); + const transactionTokens: TokenArgumentType[] = + getTransactionTokens(transaction); + + const senderLink = getExplorerLink({ + explorerAddress, + to: explorerUrlBuilder.accountDetails(transaction.sender) + }); + + const receiverLink = getExplorerLink({ + explorerAddress, + to: explorerUrlBuilder.accountDetails(receiver) + }); + + const senderShardLink = getExplorerLink({ + explorerAddress, + to: explorerUrlBuilder.senderShard(transaction.senderShard) + }); + + const receiverShardLink = getExplorerLink({ + explorerAddress, + to: explorerUrlBuilder.receiverShard(transaction.receiverShard) + }); + + const transactionHash = transaction.originalTxHash + ? `${transaction.originalTxHash}#${transaction.txHash}` + : transaction.txHash; + + const transactionLink = getExplorerLink({ + explorerAddress, + to: explorerUrlBuilder.transactionDetails(transactionHash) + }); + + return { + ...transaction, + tokenIdentifier, + receiver, + receiverAssets, + transactionDetails: { + direction, + method, + transactionTokens, + isContract: isContract(transaction.sender) + }, + links: { + senderLink, + receiverLink, + senderShardLink, + receiverShardLink, + transactionLink + } + }; +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/explorerUrlBuilder.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/explorerUrlBuilder.ts new file mode 100644 index 0000000..2b4c5ab --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/explorerUrlBuilder.ts @@ -0,0 +1,59 @@ +import { + ACCOUNTS_ENDPOINT, + BLOCKS_ENDPOINT, + CODE_ENDPOINT, + COLLECTIONS_ENDPOINT, + CONTRACTS_ENDPOINT, + IDENTITIES_ENDPOINT, + LOCKED_ACCOUNTS_ENDPOINT, + LOGS_ENDPOINT, + MINIBLOCKS_ENDPOINT, + NFTS_ENDPOINT, + NODES_ENDPOINT, + PROVIDERS_ENDPOINT, + ROLES_ENDPOINT, + SC_RESULTS_ENDPOINT, + TOKENS_ENDPOINT, + TRANSACTIONS_ENDPOINT +} from 'apiCalls/endpoints'; + +export const explorerUrlBuilder = { + shard: (shard: number | string) => `/${BLOCKS_ENDPOINT}?shard=${shard}`, + receiverShard: (shard: number | string) => + `/${TRANSACTIONS_ENDPOINT}?receivershard=${shard}`, + senderShard: (shard: number | string) => + `/${TRANSACTIONS_ENDPOINT}?sendershard=${shard}`, + transactionDetails: (hash: number | string) => + `/${TRANSACTIONS_ENDPOINT}/${hash}`, + transactionDetailsScResults: (hash: string) => + `/${TRANSACTIONS_ENDPOINT}/${hash}/${SC_RESULTS_ENDPOINT}`, + transactionDetailsLogs: (hash: string) => + `/${TRANSACTIONS_ENDPOINT}/${hash}/${LOGS_ENDPOINT}`, + nodeDetails: (publicKey: string) => `/${NODES_ENDPOINT}/${publicKey}`, + accountDetails: (address: string) => `/${ACCOUNTS_ENDPOINT}/${address}`, + accountDetailsContractCode: (address: string) => + `/${ACCOUNTS_ENDPOINT}/${address}/${CODE_ENDPOINT}`, + accountDetailsTokens: (address: string) => + `/${ACCOUNTS_ENDPOINT}/${address}/${TOKENS_ENDPOINT}`, + accountDetailsNfts: (address: string) => + `/${ACCOUNTS_ENDPOINT}/${address}/${NFTS_ENDPOINT}`, + accountDetailsScResults: (address: string) => + `/${ACCOUNTS_ENDPOINT}/${address}/${SC_RESULTS_ENDPOINT}`, + accountDetailsContracts: (address: string) => + `/${ACCOUNTS_ENDPOINT}/${address}/${CONTRACTS_ENDPOINT}`, + identityDetails: (id: string) => `/${IDENTITIES_ENDPOINT}/${id}`, + tokenDetails: (tokenId: string) => `/${TOKENS_ENDPOINT}/${tokenId}`, + tokenDetailsAccounts: (tokenId: string) => + `/${TOKENS_ENDPOINT}/${tokenId}/${ACCOUNTS_ENDPOINT}`, + tokenDetailsLockedAccounts: (tokenId: string) => + `/${TOKENS_ENDPOINT}/${tokenId}/${LOCKED_ACCOUNTS_ENDPOINT}`, + tokenDetailsRoles: (tokenId: string) => + `/${TOKENS_ENDPOINT}/${tokenId}/${ROLES_ENDPOINT}`, + collectionDetails: (identifier: string) => + `/${COLLECTIONS_ENDPOINT}/${identifier}`, + nftDetails: (identifier: string) => `/${NFTS_ENDPOINT}/${identifier}`, + providerDetails: (address: string) => `/${PROVIDERS_ENDPOINT}/${address}`, + providerDetailsTransactions: (address: string) => + `/${PROVIDERS_ENDPOINT}/${address}/${TRANSACTIONS_ENDPOINT}`, + miniblockDetails: (hash: string) => `/${MINIBLOCKS_ENDPOINT}/${hash}` +}; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getExplorerLink.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getExplorerLink.ts new file mode 100644 index 0000000..9da5078 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getExplorerLink.ts @@ -0,0 +1,28 @@ +let errorMessageDisplayed = false; + +function logError(error: string) { + if (!errorMessageDisplayed) { + console.error(error); + errorMessageDisplayed = true; + } +} + +export function getExplorerLink({ + explorerAddress, + to +}: { + explorerAddress: string; + to: string; +}) { + try { + // if a valid url is sent, return the original url + const url = new URL(to); + return url.href; + } catch { + if (!to.startsWith('/')) { + logError(`Link not prepended by / : ${to}`); + to = `/${to}`; + } + return explorerAddress ? `${explorerAddress}${to}` : to; + } +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getHumanReadableTimeFormat.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getHumanReadableTimeFormat.ts new file mode 100644 index 0000000..da9e2ac --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getHumanReadableTimeFormat.ts @@ -0,0 +1,32 @@ +export interface GetHumanReadableTimeFormatType { + value: number; + noSeconds?: boolean; + utc?: boolean; + meridiem?: boolean; +} + +/** + * @param value - UNIX timestamp + * */ +export function getHumanReadableTimeFormat({ + value, + noSeconds, + utc, + meridiem = true +}: GetHumanReadableTimeFormatType) { + const utcDate = new Date(value * 1000); + const [, AmPm] = utcDate + .toLocaleString('en-US', { hour: 'numeric', hour12: meridiem }) + .split(' '); + const formatted = utcDate.toUTCString(); + const [, date] = formatted.split(','); + const [day, month, year, time] = date.trim().split(' '); + const [hours, minutes, sec] = time.split(':'); + const seconds = `:${sec}`; + const timeFormatted = `${hours}:${minutes}${noSeconds ? '' : seconds}`; + const utcSuffix = utc ? 'UTC' : ''; + const meridiemSuffix = meridiem ? AmPm : ''; + const suffix = `${meridiemSuffix} ${utcSuffix}`.trim(); + + return `${month} ${day}, ${year} ${timeFormatted} ${suffix}`.trim(); +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getOperationsMessages.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getOperationsMessages.ts new file mode 100644 index 0000000..577bb17 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getOperationsMessages.ts @@ -0,0 +1,9 @@ +import { ServerTransactionType } from 'types/serverTransactions.types'; + +export function getOperationsMessages(transaction: ServerTransactionType) { + return ( + transaction?.operations + ?.map((operation) => operation.message) + .filter((messages): messages is string => Boolean(messages)) ?? [] + ); +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getReceiptMessage.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getReceiptMessage.ts new file mode 100644 index 0000000..89c6efd --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getReceiptMessage.ts @@ -0,0 +1,39 @@ +import BigNumber from 'bignumber.js'; +import { DECIMALS, DIGITS, REFUNDED_GAS } from 'constants/index'; +import { ServerTransactionType } from 'types/serverTransactions.types'; +import { formatAmount } from 'utils/operations/formatAmount'; + +const getReceiptValue = (transaction: ServerTransactionType) => { + if (!transaction.receipt?.value) { + return ''; + } + + if (transaction.receipt?.data === REFUNDED_GAS) { + const formattedGas = formatAmount({ + input: transaction.receipt.value, + decimals: DECIMALS, + digits: DIGITS, + showLastNonZeroDecimal: true + }); + const gasRefunded = new BigNumber(formattedGas) + .times(transaction.gasPrice) + .times(100); + + return gasRefunded.toFixed(); + } + + return transaction.receipt.value; +}; + +export function getReceiptMessage(transaction: ServerTransactionType) { + const message = transaction.receipt?.data; + + if (!message) { + return ''; + } + + const receiptValue = getReceiptValue(transaction); + const value = receiptValue ? `: ${receiptValue}` : ''; + + return `${message}${value}`; +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getScResultsMessages.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getScResultsMessages.ts new file mode 100644 index 0000000..4973437 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getScResultsMessages.ts @@ -0,0 +1,11 @@ +import { ServerTransactionType } from 'types/serverTransactions.types'; + +export default function getScResultsMessages( + transaction: ServerTransactionType +) { + return ( + transaction?.results + ?.map((result) => result.returnMessage) + .filter((messages): messages is string => Boolean(messages)) ?? [] + ); +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts new file mode 100644 index 0000000..f84b4cc --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts @@ -0,0 +1,28 @@ +import { + TransactionActionCategoryEnum, + TransactionActionsEnum, + ServerTransactionType +} from 'types/serverTransactions.types'; + +export const getTransactionMethod = (transaction: ServerTransactionType) => { + let transactionAction = 'Transaction'; + const transactionHasAction = + transaction.action?.name && transaction.action?.category; + + if (transactionHasAction) { + if ( + transaction.action?.category === TransactionActionCategoryEnum.esdtNft && + transaction.action?.name === TransactionActionsEnum.transfer + ) { + transactionAction = 'Transaction'; + } else if (transaction.action) { + transactionAction = transaction.action.name; + } + + if (transaction.action?.arguments?.functionName) { + transactionAction = transaction.action.arguments.functionName; + } + } + + return transactionAction; +}; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiver.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiver.ts new file mode 100644 index 0000000..0ffa550 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiver.ts @@ -0,0 +1,10 @@ +import { ServerTransactionType } from 'types/serverTransactions.types'; + +export function getTransactionReceiver(transaction: ServerTransactionType) { + let receiver = transaction.receiver; + if (transaction.action?.arguments?.receiver) { + receiver = transaction.action.arguments.receiver; + } + + return receiver; +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiverAssets.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiverAssets.ts new file mode 100644 index 0000000..0c2e484 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiverAssets.ts @@ -0,0 +1,15 @@ +import { ServerTransactionType } from 'types/serverTransactions.types'; +import { getTransactionReceiver } from './getTransactionReceiver'; + +/** + * The information about an account like name, icon, etc... + * */ +export function getTransactionReceiverAssets( + transaction: ServerTransactionType +) { + const receiver = getTransactionReceiver(transaction); + + return transaction.receiver === receiver + ? transaction.receiverAssets + : undefined; +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTokens.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTokens.ts new file mode 100644 index 0000000..e78c142 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTokens.ts @@ -0,0 +1,21 @@ +import { + ServerTransactionType, + TokenArgumentType +} from 'types/serverTransactions.types'; + +export const getTransactionTokens = ( + transaction: ServerTransactionType +): TokenArgumentType[] => { + if (transaction.action) { + const merged = [ + transaction.action.arguments?.token, + transaction.action.arguments?.token1, + transaction.action.arguments?.token2, + transaction.action.arguments?.transfers // array of tokens + ].filter((x) => x != null); + const flattenTransfers = [].concat(...merged); + return flattenTransfers; + } + + return []; +}; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTransferType.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTransferType.ts new file mode 100644 index 0000000..775bdf9 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTransferType.ts @@ -0,0 +1,28 @@ +import { + ServerTransactionType, + TransferTypeEnum, + TransactionDirectionEnum +} from 'types/serverTransactions.types'; + +export function getTransactionTransferType( + address: string, + transaction: ServerTransactionType, + receiver: string +): TransactionDirectionEnum { + const directionOut = address === transaction.sender; + const directionIn = address === receiver; + const directionSelf = directionOut && directionIn; + const isScResult = transaction?.type === TransferTypeEnum.SmartContractResult; + + switch (true) { + case isScResult: + return TransactionDirectionEnum.INTERNAL; + case directionSelf: + return TransactionDirectionEnum.SELF; + case directionIn: + return TransactionDirectionEnum.IN; + case directionOut: + default: + return TransactionDirectionEnum.OUT; + } +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts new file mode 100644 index 0000000..ad02e2b --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts @@ -0,0 +1,121 @@ +import { DECIMALS } from 'constants/index'; +import { NftEnumType } from 'types/tokens.types'; + +import { getTransactionTokens } from 'utils/transactions/getInterpretedTransaction/helpers/getTransactionTokens'; +import { + EgldValueDataType, + NFTValueDataType, + TokenValueDataType +} from 'utils/transactions/getInterpretedTransaction/helpers/types'; +import { getTransactionActionNftText } from 'utils/transactions/parsers/getTransactionActionNftText'; +import { getTransactionActionTokenText } from 'utils/transactions/parsers/getTransactionActionTokenText'; + +import { WithTransactionType } from '../../../../../UI/types'; +import { + ACTIONS_WITH_EGLD_VALUE, + ACTIONS_WITH_MANDATORY_OPERATIONS, + ACTIONS_WITH_VALUE_IN_ACTION_FIELD, + ACTIONS_WITH_VALUE_IN_DATA_FIELD +} from '../../constants'; + +import { + getValueFromActions, + getValueFromDataField, + getValueFromOperations +} from './helpers'; +import { getEgldValueData } from './helpers/getEgldValueData'; +import { getTitleText } from './helpers/getTitleText'; + +export interface GetTransactionValueReturnType { + egldValueData?: EgldValueDataType; + tokenValueData?: TokenValueDataType; + nftValueData?: NFTValueDataType; +} + +export interface GetTransactionValueType extends WithTransactionType { + hideMultipleBadge?: boolean; +} + +export const getTransactionValue = ({ + transaction, + hideMultipleBadge +}: GetTransactionValueType): GetTransactionValueReturnType => { + if (transaction.action) { + if (ACTIONS_WITH_EGLD_VALUE.includes(transaction.action.name)) { + return getEgldValueData(transaction.value); + } + + if (ACTIONS_WITH_VALUE_IN_DATA_FIELD.includes(transaction.action.name)) { + return getValueFromDataField(transaction); + } + + if (ACTIONS_WITH_MANDATORY_OPERATIONS.includes(transaction.action.name)) { + return getValueFromOperations(transaction); + } + + if (ACTIONS_WITH_VALUE_IN_ACTION_FIELD.includes(transaction.action.name)) { + return getValueFromActions(transaction); + } + + const transactionTokens = getTransactionTokens(transaction); + + if (transactionTokens.length) { + const txToken = transactionTokens[0]; + const isNft = Object.values(NftEnumType).includes( + txToken.type as NftEnumType + ); + + const hasTitleText = !hideMultipleBadge && transactionTokens.length > 1; + const titleText = hasTitleText ? getTitleText(transactionTokens) : ''; + + if (isNft) { + const { + badgeText, + tokenFormattedAmount, + tokenExplorerLink, + tokenLinkText + } = getTransactionActionNftText({ token: txToken }); + + return { + nftValueData: { + badgeText, + tokenFormattedAmount, + tokenExplorerLink, + tokenLinkText, + transactionTokens, + token: txToken, + value: tokenFormattedAmount != null ? txToken.value : null, + decimals: tokenFormattedAmount != null ? txToken.decimals : null, + titleText + } + }; + } + + const { + tokenExplorerLink, + showFormattedAmount, + tokenFormattedAmount, + tokenLinkText, + token + } = getTransactionActionTokenText({ + token: txToken + }); + + return { + tokenValueData: { + tokenExplorerLink, + showFormattedAmount, + tokenFormattedAmount, + tokenLinkText, + transactionTokens, + token, + value: txToken.value, + decimals: txToken.decimals ?? DECIMALS, + titleText + } + }; + } + } + + return getEgldValueData(transaction.value); +}; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getEgldValueData.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getEgldValueData.ts new file mode 100644 index 0000000..c915a62 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getEgldValueData.ts @@ -0,0 +1,10 @@ +import { DECIMALS } from 'constants/index'; +import { formatAmount } from 'utils/operations/formatAmount'; + +export const getEgldValueData = (value: string) => ({ + egldValueData: { + value, + formattedValue: formatAmount({ input: value }), + decimals: DECIMALS + } +}); diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts new file mode 100644 index 0000000..19252d2 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts @@ -0,0 +1,46 @@ +import { TokenArgumentType } from 'types/serverTransactions.types'; +import { + EgldValueDataType, + NFTValueDataType, + TokenValueDataType +} from 'utils/transactions/getInterpretedTransaction/helpers/types'; +import { getTransactionActionNftText } from 'utils/transactions/parsers/getTransactionActionNftText'; +import { getTransactionActionTokenText } from 'utils/transactions/parsers/getTransactionActionTokenText'; +import { getIdentifierType } from 'utils/validation/getIdentifierType'; + +export interface GetTransactionValueReturnType { + egldValueData?: EgldValueDataType; + tokenValueData?: TokenValueDataType; + nftValueData?: NFTValueDataType; +} + +export const getTitleText = ( + transactionTokens: TokenArgumentType[] +): string => { + const tokensArray = transactionTokens.map((transactionToken) => { + const { isNft } = getIdentifierType(transactionToken.type); + if (isNft) { + const { badgeText, tokenFormattedAmount, tokenLinkText } = + getTransactionActionNftText({ + token: transactionToken + }); + + const badge = badgeText != null ? `(${badgeText}) ` : ''; + + const value = `${badge}${tokenFormattedAmount} ${tokenLinkText}`; + return value; + } + const { tokenFormattedAmount, tokenLinkText, token } = + getTransactionActionTokenText({ + token: transactionToken as TokenArgumentType + }); + + const identifier = token.collection ? token.identifier : token.token; + + const value = `${tokenFormattedAmount} ${tokenLinkText} (${identifier})`; + return value; + }); + + const joinedTokensWithLineBreak = decodeURI(tokensArray.join('%0A')); + return joinedTokensWithLineBreak; +}; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromActions.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromActions.ts new file mode 100644 index 0000000..16cc542 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromActions.ts @@ -0,0 +1,23 @@ +import BigNumber from 'bignumber.js'; +import { InterpretedTransactionType } from 'types/serverTransactions.types'; +import { getEgldValueData } from './getEgldValueData'; + +let warningLogged = false; + +export function getValueFromActions(transaction: InterpretedTransactionType) { + const value = new BigNumber(transaction.action?.arguments?.value); + + if (!value.isNaN()) { + return getEgldValueData(transaction.action?.arguments?.value); + } + + if (!warningLogged) { + console.error( + `Unable to interpret ${transaction.action?.name} data for txHash: ${transaction.txHash}` + ); + warningLogged = true; + } + + // fallback on transaction value + return getEgldValueData(transaction.value); +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromDataField.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromDataField.ts new file mode 100644 index 0000000..4205e8e --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromDataField.ts @@ -0,0 +1,27 @@ +import BigNumber from 'bignumber.js'; +import { InterpretedTransactionType } from 'types/serverTransactions.types'; +import { decodeBase64 } from 'utils/decoders'; +import { getEgldValueData } from './getEgldValueData'; + +let warningLogged = false; + +export function getValueFromDataField(transaction: InterpretedTransactionType) { + try { + const data = decodeBase64(transaction.data); + const encodedValue = data.replace(`${transaction.action?.name}@`, ''); + const value = new BigNumber(encodedValue, 16); + if (!value.isNaN()) { + return getEgldValueData(value.toString(10)); + } + } catch (err) { + if (!warningLogged) { + console.error( + `Unable to extract value for txHash: ${transaction.txHash}` + ); + warningLogged = true; + } + } + + // fallback on transaction value + return getEgldValueData(transaction.value); +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromOperations.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromOperations.ts new file mode 100644 index 0000000..3775156 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromOperations.ts @@ -0,0 +1,31 @@ +import { InterpretedTransactionType } from 'types/serverTransactions.types'; +import { getVisibleOperations } from 'utils/transactions/parsers/getVisibleOperations'; +import { getEgldValueData } from './getEgldValueData'; + +let warningLogged = false; + +const logError = (hash: string) => { + if (!warningLogged) { + console.error( + `Operations field missing for txHash: ${hash}. + Unable to compute value field.` + ); + warningLogged = true; + } +}; + +export function getValueFromOperations( + transaction: InterpretedTransactionType +) { + try { + if (transaction.operations) { + const [operation] = getVisibleOperations(transaction); + return getEgldValueData(operation?.value); + } else { + logError(transaction.txHash); + } + } catch (err) { + logError(transaction.txHash); + } + return getEgldValueData(transaction.value); +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/index.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/index.ts new file mode 100644 index 0000000..80d2309 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/index.ts @@ -0,0 +1,5 @@ +export * from './getTitleText'; +export * from './getEgldValueData'; +export * from './getValueFromDataField'; +export * from './getValueFromOperations'; +export * from './getValueFromActions'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/index.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/index.ts new file mode 100644 index 0000000..da93a6d --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/index.ts @@ -0,0 +1 @@ +export * from './getTransactionValue'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/index.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/index.ts new file mode 100644 index 0000000..87c997e --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/index.ts @@ -0,0 +1,15 @@ +export * from './explorerUrlBuilder'; +export * from './getTransactionTokens'; +export * from './getTransactionReceiverAssets'; +export * from './getTransactionReceiver'; +export * from './getTransactionMethod'; +export * from './getExplorerLink'; +export * from './getTransactionTransferType'; +export * from './explorerUrlBuilder'; +export * from './getHumanReadableTimeFormat'; +export * from './getExplorerLink'; +export * from './getOperationsMessages'; +export * from './getReceiptMessage'; +export * from './getScResultsMessages'; +export * from './getExplorerLink'; +export * from './getTransactionValue'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/base-transaction-mock.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/base-transaction-mock.ts new file mode 100644 index 0000000..23c9285 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/base-transaction-mock.ts @@ -0,0 +1,28 @@ +import { TransactionActionsEnum } from 'types/serverTransactions.types'; + +export const baseTransactionMock = { + blockHash: '', + price: 60, + txHash: 'c747f6ea467fb68e2f152a5baa57edbc7e04d297954878084363cbdb961eed7e', + gasLimit: 60000000, + gasPrice: 1000000000, + gasUsed: 60000000, + miniBlockHash: + 'd7ad7c1f8f57e3a370b12092604c42157cb5228ab598e9bbca641ee2e4ee7bd2', + nonce: 965, + receiver: 'erd1qqqqqqqqqqqqqpgq4gdcg0k83u7lpv4s4532w3au9y9h0vm70eqq6m8qk2', + receiverShard: 0, + round: 2044988, + sender: 'erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv', + senderShard: 0, + signature: 'transaction-signature-hash', + status: 'fail', + value: '1234', + fee: '655440000000000', + timestamp: 1660821528, + data: 'cGluZw==', + action: { + category: 'scCall', + name: TransactionActionsEnum.ping + } +}; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getExplorerkLink.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getExplorerkLink.test.ts new file mode 100644 index 0000000..55c1d99 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getExplorerkLink.test.ts @@ -0,0 +1,27 @@ +import { getExplorerLink } from '../getExplorerLink'; + +describe('getNetworkLink', () => { + it('return "/${to}" parameter when the explorerAddress is empty and log an error in console', () => { + // prevent showing errors in Jest console + jest.mock('console', () => ({ + error: () => null + })); + + const input = 'address'; + const consoleErrorSpy = jest.spyOn(console, 'error'); + + const result = getExplorerLink({ explorerAddress: '', to: input }); + + expect(consoleErrorSpy).toHaveBeenCalled(); + expect(result).toEqual(`/${input}`); + }); + + it('compose link using explorer address and "to" parameter', () => { + const explorerAddress = 'http://devnet-explorer.multiversx.com'; + const to = '/address'; + + const result = getExplorerLink({ explorerAddress, to }); + + expect(result).toEqual(`${explorerAddress}${to}`); + }); +}); diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getHumanReadableTimeFormat.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getHumanReadableTimeFormat.test.ts new file mode 100644 index 0000000..ee41c4d --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getHumanReadableTimeFormat.test.ts @@ -0,0 +1,139 @@ +import { getHumanReadableTimeFormat } from '../getHumanReadableTimeFormat'; + +describe('getHumanReadableTimeFormat', () => { + it('returns full readable date, non-UTC', () => { + const date = new Date(1993, 3, 23, 8, 33, 12); + const value = date.getTime() / 1000; // UNIX timestamp + const hours = date.getUTCHours(); + const noSeconds = false; + const utc = false; + + const result = getHumanReadableTimeFormat({ + value, + noSeconds, + utc + }); + + expect(result).toEqual(`Apr 23, 1993 0${hours}:33:12 AM`); + }); + + it('returns readable date without seconds, non-UTC', () => { + const date = new Date(1993, 3, 23, 8, 33, 12); + const value = date.getTime() / 1000; // UNIX timestamp + const hours = date.getUTCHours(); + const noSeconds = true; + const utc = false; + + const result = getHumanReadableTimeFormat({ + value, + noSeconds, + utc + }); + + expect(result).toEqual(`Apr 23, 1993 0${hours}:33 AM`); + }); + + it('returns full readable date in UTC', () => { + const date = new Date(1993, 3, 23, 8, 33, 12); + const value = date.getTime() / 1000; // UNIX timestamp + const noSeconds = false; + const utc = true; + const hours = date.getUTCHours(); + + const result = getHumanReadableTimeFormat({ + value, + noSeconds, + utc + }); + + expect(result).toEqual(`Apr 23, 1993 0${hours}:33:12 AM UTC`); + }); + + it('returns full readable date without seconds in UTC', () => { + const date = new Date(1993, 3, 23, 8, 33, 12); + const value = date.getTime() / 1000; // UNIX timestamp + const noSeconds = true; + const utc = true; + const hours = date.getUTCHours(); + + const result = getHumanReadableTimeFormat({ + value, + noSeconds, + utc + }); + + expect(result).toEqual(`Apr 23, 1993 0${hours}:33 AM UTC`); + }); + + it('returns full readable date, non-UTC, without meridiem', () => { + const date = new Date(1993, 3, 23, 8, 33, 12); + const value = date.getTime() / 1000; // UNIX timestamp + const hours = date.getUTCHours(); + const noSeconds = false; + const utc = false; + const meridiem = false; + + const result = getHumanReadableTimeFormat({ + value, + noSeconds, + utc, + meridiem + }); + + expect(result).toEqual(`Apr 23, 1993 0${hours}:33:12`); + }); + + it('returns readable date without seconds, non-UTC, without meridiem', () => { + const date = new Date(1993, 3, 23, 8, 33, 12); + const value = date.getTime() / 1000; // UNIX timestamp + const hours = date.getUTCHours(); + const noSeconds = true; + const utc = false; + const meridiem = false; + + const result = getHumanReadableTimeFormat({ + value, + noSeconds, + utc, + meridiem + }); + + expect(result).toEqual(`Apr 23, 1993 0${hours}:33`); + }); + + it('returns full readable date in UTC, without meridiem', () => { + const date = new Date(1993, 3, 23, 8, 33, 12); + const value = date.getTime() / 1000; // UNIX timestamp + const noSeconds = false; + const utc = true; + const hours = date.getUTCHours(); + const meridiem = false; + + const result = getHumanReadableTimeFormat({ + value, + noSeconds, + utc, + meridiem + }); + + expect(result).toEqual(`Apr 23, 1993 0${hours}:33:12 UTC`); + }); + + it('returns full readable date without seconds in UTC, without meridiem', () => { + const date = new Date(1993, 3, 23, 8, 33, 12); + const value = date.getTime() / 1000; // UNIX timestamp + const noSeconds = true; + const utc = true; + const hours = date.getUTCHours(); + const meridiem = false; + + const result = getHumanReadableTimeFormat({ + value, + noSeconds, + utc, + meridiem + }); + + expect(result).toEqual(`Apr 23, 1993 0${hours}:33 UTC`); + }); +}); diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getOperationsMessages.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getOperationsMessages.test.ts new file mode 100644 index 0000000..bda15cf --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getOperationsMessages.test.ts @@ -0,0 +1,48 @@ +import { + OperationType, + TransactionOperationActionTypeEnum, + VisibleTransactionOperationType +} from 'types/serverTransactions.types'; +import { getOperationsMessages } from '../getOperationsMessages'; +import { baseTransactionMock } from './base-transaction-mock'; + +describe('getOperationsMessages', () => { + it('receive empty array if no operations messages exists on the transaction', () => { + const result = getOperationsMessages(baseTransactionMock); + + expect(result).toEqual([]); + }); + + it('receive an array with all operations messages', () => { + const baseTransactionOperation = { + name: 'send', + type: VisibleTransactionOperationType.egld, + action: TransactionOperationActionTypeEnum.transfer, + esdtType: 'FungibleESDT', + receiver: + 'erd1qqqqqqqqqqqqqpgq4gdcg0k83u7lpv4s4532w3au9y9h0vm70eqq6m8qk2', + sender: 'erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv', + value: '1', + decimals: 4, + identifier: 'EGLD' + }; + + const transaction = { + ...baseTransactionMock, + operations: [ + { + ...baseTransactionOperation, + message: 'message 0' + }, + { + ...baseTransactionOperation, + message: 'message 1' + } + ] as OperationType[] + }; + + const result = getOperationsMessages(transaction); + + expect(result).toEqual(['message 0', 'message 1']); + }); +}); diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getReceiptMessage.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getReceiptMessage.test.ts new file mode 100644 index 0000000..74634d8 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getReceiptMessage.test.ts @@ -0,0 +1,56 @@ +import BigNumber from 'bignumber.js'; +import { DECIMALS, DIGITS, REFUNDED_GAS } from 'constants/index'; +import { formatAmount } from 'utils/operations/formatAmount'; +import { getReceiptMessage } from '../getReceiptMessage'; +import { baseTransactionMock } from './base-transaction-mock'; + +describe('getReceiptMessage', () => { + it('returns empty string if no data exists on the transaction receipt field', () => { + const transaction = { + ...baseTransactionMock, + receipt: undefined + }; + const result = getReceiptMessage(transaction); + + expect(result).toEqual(''); + }); + + it(`returns a message that contains the refunded gas value if the receipt data field contains ${REFUNDED_GAS}`, () => { + const transaction = { + ...baseTransactionMock, + receipt: { + data: REFUNDED_GAS, + value: '1000', + sender: '' + } + }; + const result = getReceiptMessage(transaction); + const formattedGas = formatAmount({ + input: transaction.receipt.value, + decimals: DECIMALS, + digits: DIGITS, + showLastNonZeroDecimal: true + }); + const gasRefunded = new BigNumber(formattedGas) + .times(transaction.gasPrice) + .times(100); + + expect(result).toEqual(`${transaction.receipt.data}: ${gasRefunded}`); + }); + + it(`returns a message that contains the receipt data field value and the receipt value if the receipt data field does not contains ${REFUNDED_GAS}`, () => { + const transaction = { + ...baseTransactionMock, + receipt: { + data: '@some-data', + value: '1000', + sender: 'sender-hash' + } + }; + const result = getReceiptMessage(transaction); + + expect(result).toEqual( + `${transaction.receipt.data}: ${transaction.receipt.value}` + ); + }); +}); diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getScResultsMessages.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getScResultsMessages.test.ts new file mode 100644 index 0000000..d6cc71d --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getScResultsMessages.test.ts @@ -0,0 +1,46 @@ +import { + ResultType, + ServerTransactionType +} from 'types/serverTransactions.types'; +import getScResultsMessages from '../getScResultsMessages'; +import { baseTransactionMock } from './base-transaction-mock'; + +describe('getScResultsMessages', () => { + it('receive empty array if no results exists on the transaction', () => { + const result = getScResultsMessages(baseTransactionMock); + + expect(result).toEqual([]); + }); + + it('receive an array with all results messages', () => { + const baseTransactionResult = { + callType: '', + value: '1', + gasLimit: 100, + gasPrice: 0.001, + hash: 'tx-hash', + nonce: 0, + sender: 'sender-hash', + originalTxHash: 'original-tx-hash', + prevTxHash: 'prev-tx-hash' + } as ResultType; + + const transaction: ServerTransactionType = { + ...baseTransactionMock, + results: [ + { + ...baseTransactionResult, + returnMessage: 'message 0' + }, + { + ...baseTransactionResult, + returnMessage: 'message 1' + } + ] as ResultType[] + }; + + const result = getScResultsMessages(transaction); + + expect(result).toEqual(['message 0', 'message 1']); + }); +}); diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionMethod.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionMethod.test.ts new file mode 100644 index 0000000..2ff9899 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionMethod.test.ts @@ -0,0 +1,65 @@ +import { + ServerTransactionType, + TransactionActionCategoryEnum, + TransactionActionsEnum +} from 'types/serverTransactions.types'; +import { getTransactionMethod } from '../getTransactionMethod'; +import { baseTransactionMock } from './base-transaction-mock'; + +describe('getTransactionMethod', () => { + it('returns default value "Transaction" in case of missing "action" field ', () => { + const transaction: ServerTransactionType = { + ...baseTransactionMock, + action: undefined + }; + + const result = getTransactionMethod(transaction); + + expect(result).toEqual('Transaction'); + }); + + it(`returns default value "Transaction" when the transaction is a "${TransactionActionsEnum.transfer}" and the action's category is "${TransactionActionCategoryEnum.esdtNft}"`, () => { + const transaction: ServerTransactionType = { + ...baseTransactionMock, + action: { + category: TransactionActionCategoryEnum.esdtNft, + name: TransactionActionsEnum.transfer + } + }; + + const result = getTransactionMethod(transaction); + + expect(result).toEqual('Transaction'); + }); + + it(`returns the transaction method read from the action field when the transaction is not a "${TransactionActionsEnum.transfer}" or the action's category is not "${TransactionActionCategoryEnum.esdtNft}"`, () => { + const transaction: ServerTransactionType = { + ...baseTransactionMock, + action: { + category: TransactionActionCategoryEnum.scCall, + name: TransactionActionsEnum.claimRewards + } + }; + + const result = getTransactionMethod(transaction); + + expect(result).toEqual(transaction.action?.name); + }); + + it('overrides the transaction method when there is a transaction method defined on the action arguments', () => { + const transaction: ServerTransactionType = { + ...baseTransactionMock, + action: { + category: TransactionActionCategoryEnum.scCall, + name: TransactionActionsEnum.claimRewards, + arguments: { + functionName: 'customFunction' + } + } + }; + + const result = getTransactionMethod(transaction); + + expect(result).toEqual(transaction.action?.arguments?.functionName); + }); +}); diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiver.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiver.test.ts new file mode 100644 index 0000000..60de128 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiver.test.ts @@ -0,0 +1,37 @@ +import { + ServerTransactionType, + TransactionActionCategoryEnum, + TransactionActionsEnum +} from 'types/serverTransactions.types'; +import { getTransactionReceiver } from '../getTransactionReceiver'; +import { baseTransactionMock } from './base-transaction-mock'; + +describe('getTransactionReceiver', () => { + it('returns receiver address from transaction body', () => { + const transaction: ServerTransactionType = { + ...baseTransactionMock, + receiver: 'receiver-hash' + }; + + const result = getTransactionReceiver(transaction); + + expect(result).toEqual(transaction.receiver); + }); + + it('returns receiver address from the transaction action arguments if exists', () => { + const transaction: ServerTransactionType = { + ...baseTransactionMock, + action: { + category: TransactionActionCategoryEnum.esdtNft, + name: TransactionActionsEnum.transfer, + arguments: { + receiver: 'receiver-hash' + } + } + }; + + const result = getTransactionReceiver(transaction); + + expect(result).toEqual(transaction.action?.arguments?.receiver); + }); +}); diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiverAssets.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiverAssets.test.ts new file mode 100644 index 0000000..148027d --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiverAssets.test.ts @@ -0,0 +1,52 @@ +import { + ServerTransactionType, + TransactionActionCategoryEnum, + TransactionActionsEnum +} from 'types/serverTransactions.types'; +import { getTransactionReceiverAssets } from '../getTransactionReceiverAssets'; +import { baseTransactionMock } from './base-transaction-mock'; + +describe('getTransactionReceiverAssets', () => { + it('returns receiver assets', () => { + const transaction: ServerTransactionType = { + ...baseTransactionMock, + receiver: 'receiver-hash', + receiverAssets: { + name: 'john doe', + description: 'description', + iconPng: 'icon.png', + iconSvg: 'icon.svg', + tags: ['@tag1', '@tag2'] + } + }; + + const result = getTransactionReceiverAssets(transaction); + + expect(result).toBe(transaction.receiverAssets); + }); + + it('returns "undefined" when the transaction receiver does not match the receiver address from the transaction action arguments', () => { + const transaction: ServerTransactionType = { + ...baseTransactionMock, + receiver: 'receiver-hash', + receiverAssets: { + name: 'john doe', + description: 'description', + iconPng: 'icon.png', + iconSvg: 'icon.svg', + tags: ['@tag1', '@tag2'] + }, + action: { + name: TransactionActionsEnum.transfer, + category: TransactionActionCategoryEnum.scCall, + arguments: { + receiver: 'receiver-hash-from-action-arguments' + } + } + }; + + const result = getTransactionReceiverAssets(transaction); + + expect(result).toBeUndefined(); + }); +}); diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTokens.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTokens.test.ts new file mode 100644 index 0000000..cdbce40 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTokens.test.ts @@ -0,0 +1,59 @@ +import { + ServerTransactionType, + TransactionActionCategoryEnum, + TransactionActionsEnum +} from 'types/serverTransactions.types'; +import { getTransactionTokens } from '../getTransactionTokens'; +import { baseTransactionMock } from './base-transaction-mock'; + +describe('getTransactionTokens', () => { + it('returns empty array when the transaction action details are missing', () => { + const transaction: ServerTransactionType = { + ...baseTransactionMock, + action: undefined + }; + + const result = getTransactionTokens(transaction); + + expect(result).toEqual([]); + }); + + it('returns an array with all existing tokens in the action arguments', () => { + const transaction: ServerTransactionType = { + ...baseTransactionMock, + action: { + name: TransactionActionsEnum.swap, + category: TransactionActionCategoryEnum.mex, + description: 'Swap 1 WEGLD for a minimum of 45.117988 USDC', + arguments: { + transfers: [ + { + type: 'FungibleESDT', + name: 'WrappedEGLD', + ticker: 'WEGLD', + svgUrl: + 'https://devnet-media.multiversx.com/tokens/asset/WEGLD-d7c6bb/logo.svg', + token: 'WEGLD-d7c6bb', + decimals: 18, + value: '1000000000000000000' + }, + { + type: 'FungibleESDT', + name: 'WrappedUSDC', + ticker: 'USDC', + svgUrl: + 'https://devnet-media.multiversx.com/tokens/asset/USDC-8d4068/logo.svg', + token: 'USDC-8d4068', + decimals: 6, + value: '45117988' + } + ] + } + } + }; + + const result = getTransactionTokens(transaction); + + expect(result).toEqual(transaction.action?.arguments?.transfers); + }); +}); diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTransferType.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTransferType.test.ts new file mode 100644 index 0000000..e4ccc26 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTransferType.test.ts @@ -0,0 +1,67 @@ +import { TransactionDirectionEnum } from 'types/serverTransactions.types'; +import { + ServerTransactionType, + TransferTypeEnum +} from 'types/serverTransactions.types'; +import { getTransactionTransferType } from '../getTransactionTransferType'; +import { baseTransactionMock } from './base-transaction-mock'; + +describe('getTransactionTransferType', () => { + it(`returns "${TransactionDirectionEnum.INTERNAL}" when the transaction type is "${TransferTypeEnum.SmartContractResult}" `, () => { + const transaction: ServerTransactionType = { + ...baseTransactionMock, + type: TransferTypeEnum.SmartContractResult + }; + + const result = getTransactionTransferType( + 'address-hash', + transaction, + 'receiver-hash' + ); + + expect(result).toEqual(TransactionDirectionEnum.INTERNAL); + }); + + it(`returns "${TransactionDirectionEnum.SELF}" for self transfers`, () => { + const sender = 'my-address-hash'; + + const transaction: ServerTransactionType = { + ...baseTransactionMock, + sender + }; + + const result = getTransactionTransferType(sender, transaction, sender); + + expect(result).toEqual(TransactionDirectionEnum.SELF); + }); + + it(`returns "${TransactionDirectionEnum.IN}" when receive something from an address`, () => { + const receiver = 'my-address-hash'; + + const transaction: ServerTransactionType = { + ...baseTransactionMock, + receiver + }; + + const result = getTransactionTransferType(receiver, transaction, receiver); + + expect(result).toEqual(TransactionDirectionEnum.IN); + }); + + it(`returns "${TransactionDirectionEnum.OUT}" for when transfer something to an address`, () => { + const sender = 'my-address-hash'; + + const transaction: ServerTransactionType = { + ...baseTransactionMock, + sender + }; + + const result = getTransactionTransferType( + sender, + transaction, + 'receiver-hash' + ); + + expect(result).toEqual(TransactionDirectionEnum.OUT); + }); +}); diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/types.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/types.ts new file mode 100644 index 0000000..c94050f --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/types.ts @@ -0,0 +1,26 @@ +import { TokenArgumentType } from 'types'; + +export interface ESDTValueDataType { + tokenFormattedAmount: string | null; + tokenExplorerLink: string; + tokenLinkText: string; + transactionTokens: TokenArgumentType[]; + token: TokenArgumentType; + value: string | null; + decimals: number | null; + titleText: string; +} + +export interface NFTValueDataType extends ESDTValueDataType { + badgeText: string | null; +} + +export interface TokenValueDataType extends ESDTValueDataType { + showFormattedAmount: boolean; +} + +export interface EgldValueDataType { + value: string; + formattedValue: string; + decimals: number; +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/index.tsx b/src/utils/transactions/parsers/getInterpretedTransaction/index.tsx new file mode 100644 index 0000000..81608d6 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/index.tsx @@ -0,0 +1,3 @@ +export * from './getInterpretedTransaction'; +export * from './constants'; +export * from './helpers'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/tests/extended-transaction-mock.ts b/src/utils/transactions/parsers/getInterpretedTransaction/tests/extended-transaction-mock.ts new file mode 100644 index 0000000..33dece5 --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/tests/extended-transaction-mock.ts @@ -0,0 +1,91 @@ +import { + ServerTransactionType, + TransactionOperationActionTypeEnum, + TransactionActionCategoryEnum, + TransactionActionsEnum, + VisibleTransactionOperationType +} from 'types/serverTransactions.types'; +import { EsdtEnumType } from 'types/tokens.types'; +import { baseTransactionMock } from '../helpers/tests/base-transaction-mock'; + +export const transactionMock: ServerTransactionType = { + ...baseTransactionMock, + action: { + name: TransactionActionsEnum.transfer, + category: TransactionActionCategoryEnum.scCall, + arguments: { + token: 'token', + token1: 'token1', + token2: 'token2', + transfers: 'transfers', + null: null, + undefined: undefined + } + }, + receiverAssets: { + name: 'john doe', + description: 'description', + iconPng: 'icon.png', + iconSvg: 'icon.svg', + tags: ['@tag1', '@tag2'] + }, + operations: [ + { + name: 'send', + type: VisibleTransactionOperationType.egld, + action: TransactionOperationActionTypeEnum.transfer, + esdtType: EsdtEnumType.FungibleESDT, + receiver: + 'erd1qqqqqqqqqqqqqpgq4gdcg0k83u7lpv4s4532w3au9y9h0vm70eqq6m8qk2', + sender: 'erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv', + value: '1', + decimals: 4, + identifier: 'EGLD', + message: 'message 0' + }, + { + name: 'send', + type: VisibleTransactionOperationType.egld, + action: TransactionOperationActionTypeEnum.transfer, + esdtType: EsdtEnumType.FungibleESDT, + receiver: + 'erd1qqqqqqqqqqqqqpgq4gdcg0k83u7lpv4s4532w3au9y9h0vm70eqq6m8qk2', + sender: 'erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv', + value: '1', + decimals: 4, + identifier: 'EGLD', + message: 'message 1' + } + ], + results: [ + { + callType: '', + value: '1', + gasLimit: 100, + gasPrice: 0.001, + hash: 'tx-hash', + nonce: 0, + sender: 'sender-hash', + originalTxHash: 'original-tx-hash', + prevTxHash: 'prev-tx-hash', + returnMessage: 'message 0' + }, + { + callType: '', + value: '1', + gasLimit: 100, + gasPrice: 0.001, + hash: 'tx-hash', + nonce: 0, + sender: 'sender-hash', + originalTxHash: 'original-tx-hash', + prevTxHash: 'prev-tx-hash', + returnMessage: 'message 1' + } + ], + receipt: { + data: '@some-data', + value: '1000', + sender: 'sender-hash' + } +}; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/tests/getInterpretedTransaction.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/tests/getInterpretedTransaction.test.ts new file mode 100644 index 0000000..121490f --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/tests/getInterpretedTransaction.test.ts @@ -0,0 +1,164 @@ +import { + InterpretedTransactionType, + TransactionDirectionEnum +} from 'types/serverTransactions.types'; +import { + ServerTransactionType, + TransactionActionsEnum +} from 'types/serverTransactions.types'; +import { getInterpretedTransaction } from '../getInterpretedTransaction'; + +import { explorerUrlBuilder } from '../helpers/explorerUrlBuilder'; + +import { transactionMock } from './extended-transaction-mock'; + +const explorerAddress = 'https://testing.devnet.com'; + +describe('transaction interpreter', () => { + describe('interpretServerTransaction', () => { + it('creates an extended model of the existing transaction, containing all the needed information necessary to build the UI without processing inside the components', () => { + const transaction: ServerTransactionType = { + ...transactionMock, + tokenIdentifier: 'token-id' + }; + const output: InterpretedTransactionType = { + ...transaction, + links: { + senderLink: `${explorerAddress}${explorerUrlBuilder.accountDetails( + transaction.sender + )}`, + receiverLink: `${explorerAddress}${explorerUrlBuilder.accountDetails( + transaction.receiver + )}`, + receiverShardLink: `${explorerAddress}${explorerUrlBuilder.receiverShard( + transaction.receiverShard + )}`, + senderShardLink: `${explorerAddress}${explorerUrlBuilder.senderShard( + transaction.senderShard + )}`, + transactionLink: `${explorerAddress}${explorerUrlBuilder.transactionDetails( + transaction.txHash + )}` + }, + transactionDetails: { + transactionTokens: [ + transaction?.action?.arguments?.token, + transaction?.action?.arguments?.token1, + transaction?.action?.arguments?.token2, + transaction?.action?.arguments?.transfers + ].filter((x) => x != null), + direction: TransactionDirectionEnum.OUT, + isContract: false, + method: TransactionActionsEnum.transfer + } + }; + + const result = getInterpretedTransaction({ + transaction, + address: 'erd1-my-address-hash', + explorerAddress + }); + + expect(result).toEqual(output); + }); + }); + + describe('interpretServerTransactions', () => { + it('parse all the transactions and creates transactions extended models, containing all the needed information necessary to build the UI without processing inside the components', () => { + const networkAddress = 'https://testing.devnet.com'; + const transactions: ServerTransactionType[] = [ + { + ...transactionMock, + tokenIdentifier: 'token-id' + }, + { + ...transactionMock, + tokenIdentifier: 'token-id-2', + // SC address. Use to set the isContract field + sender: + 'erd1qqqqqqqqqqqqqpgq4gdcg0k83u7lpv4s4532w3au9y9h0vm70eqq6m8qk2', + // Use the same with the account address to have SELF transfer + receiver: + 'erd1qqqqqqqqqqqqqpgq4gdcg0k83u7lpv4s4532w3au9y9h0vm70eqq6m8qk2', + senderShard: 2, + receiverShard: 3 + } + ]; + const output: InterpretedTransactionType[] = [ + { + ...transactions[0], + links: { + senderLink: `${networkAddress}${explorerUrlBuilder.accountDetails( + transactions[0].sender + )}`, + receiverLink: `${networkAddress}${explorerUrlBuilder.accountDetails( + transactions[0].receiver + )}`, + receiverShardLink: `${networkAddress}${explorerUrlBuilder.receiverShard( + transactions[0].receiverShard + )}`, + senderShardLink: `${networkAddress}${explorerUrlBuilder.senderShard( + transactions[0].senderShard + )}`, + transactionLink: `${networkAddress}${explorerUrlBuilder.transactionDetails( + transactions[0].txHash + )}` + }, + transactionDetails: { + transactionTokens: [ + transactions[0]?.action?.arguments?.token, + transactions[0]?.action?.arguments?.token1, + transactions[0]?.action?.arguments?.token2, + transactions[0]?.action?.arguments?.transfers + ].filter((x) => x != null), + direction: TransactionDirectionEnum.IN, + isContract: false, + method: TransactionActionsEnum.transfer + } + }, + { + ...transactions[1], + links: { + senderLink: `${networkAddress}${explorerUrlBuilder.accountDetails( + transactions[1].sender + )}`, + receiverLink: `${networkAddress}${explorerUrlBuilder.accountDetails( + transactions[1].receiver + )}`, + receiverShardLink: `${networkAddress}${explorerUrlBuilder.receiverShard( + transactions[1].receiverShard + )}`, + senderShardLink: `${networkAddress}${explorerUrlBuilder.senderShard( + transactions[1].senderShard + )}`, + transactionLink: `${networkAddress}${explorerUrlBuilder.transactionDetails( + transactions[1].txHash + )}` + }, + transactionDetails: { + transactionTokens: [ + transactions[1]?.action?.arguments?.token, + transactions[1]?.action?.arguments?.token1, + transactions[1]?.action?.arguments?.token2, + transactions[1]?.action?.arguments?.transfers + ].filter((x) => x != null), + direction: TransactionDirectionEnum.SELF, + isContract: true, + method: TransactionActionsEnum.transfer + } + } + ]; + + const result = transactions.map((transaction) => + getInterpretedTransaction({ + transaction, + address: + 'erd1qqqqqqqqqqqqqpgq4gdcg0k83u7lpv4s4532w3au9y9h0vm70eqq6m8qk2', + explorerAddress + }) + ); + + expect(result).toEqual(output); + }); + }); +}); diff --git a/src/utils/transactions/parsers/getOperationDirection.ts b/src/utils/transactions/parsers/getOperationDirection.ts new file mode 100644 index 0000000..e3da287 --- /dev/null +++ b/src/utils/transactions/parsers/getOperationDirection.ts @@ -0,0 +1,33 @@ +import { TransactionDirectionEnum } from 'types/serverTransactions.types'; + +import { WithOperationType } from '../../../UI/types'; + +export const getOperationDirection = ({ + operation, + address +}: { address: string } & WithOperationType) => { + const directionOut = address === operation.sender; + const directionIn = address === operation.receiver; + const directionSelf = directionOut && directionIn; + const directionInternal = !directionSelf; + + let direction = ''; + switch (true) { + case directionOut: + direction = TransactionDirectionEnum.OUT; + break; + case directionIn: + direction = TransactionDirectionEnum.IN; + break; + case directionSelf: + direction = TransactionDirectionEnum.SELF; + break; + case directionInternal: + direction = TransactionDirectionEnum.INTERNAL; + break; + } + + return { + direction + }; +}; diff --git a/src/utils/transactions/parsers/getOperationsDetails.ts b/src/utils/transactions/parsers/getOperationsDetails.ts new file mode 100644 index 0000000..8d249f8 --- /dev/null +++ b/src/utils/transactions/parsers/getOperationsDetails.ts @@ -0,0 +1,50 @@ +import { InterpretedTransactionType, OperationType } from 'types'; +import { getVisibleOperations } from 'utils/transactions/index'; + +export type OperationDetailsPropsType = { + transaction: InterpretedTransactionType; + filterBy?: { + action?: OperationType['action']; + sender?: OperationType['sender']; + receiver?: OperationType['receiver']; + }; +}; + +export const getOperationsDetails = ({ + transaction, + filterBy +}: OperationDetailsPropsType): OperationType[] => { + if (!transaction.operations) { + return []; + } + + const operations = getVisibleOperations(transaction); + + if (operations.length === 0) { + return []; + } + + if (!filterBy) { + return operations; + } + + const { action, receiver, sender } = filterBy; + + const filteredOperations = operations.filter((operation) => { + if (action && operation.action !== action) { + return false; + } + + if (sender && operation.sender !== sender) { + return false; + } + + if (receiver && operation.receiver !== receiver) { + return false; + } + + return true; + }); + + return filteredOperations; +}; diff --git a/src/utils/transactions/parsers/getScamFlag.tsx b/src/utils/transactions/parsers/getScamFlag.tsx new file mode 100644 index 0000000..72660db --- /dev/null +++ b/src/utils/transactions/parsers/getScamFlag.tsx @@ -0,0 +1,71 @@ +import * as linkify from 'linkifyjs'; +import { + SuspiciousLinkType, + SuspiciousLinkPropsType, + TextWithLinksType +} from 'types'; + +export const getTextWithLinks = (text: string): TextWithLinksType => { + const links = linkify.find(text); + + // If no links are present in the text, return the text unmodified + if (!links.length) { + return { + textWithLinks: text, + hasLinks: false + }; + } + + let textWithLinks = text; + + // Replace the previous links in text with normalized links + for (const link of links) { + const previousLink = text.substring(link.start, link.end); + textWithLinks = textWithLinks.replace(previousLink, link.value); + } + + return { + textWithLinks, + hasLinks: true + }; +}; + +/** + * @description It checks if an asset contains suspicious info + * If it contains text with links inside, it contains scam info, or it is marked as NSFW, + * then it has suspicious info and may be a scam + */ +export const getScamFlag = ({ + message, + scamInfo, + isNsfw, + verified, + messagePrefix = 'Message hidden due to suspicious content - ' +}: SuspiciousLinkPropsType): SuspiciousLinkType => { + if (verified) { + return { + message: '', + textWithLinks: '', + isSuspicious: false + }; + } + + const outputMessage = `${messagePrefix}${ + scamInfo?.info ?? 'suspicious content' + }`; + const { textWithLinks, hasLinks } = getTextWithLinks(message); + + if (hasLinks || isNsfw || scamInfo) { + return { + message: outputMessage, + textWithLinks, + isSuspicious: true + }; + } + + return { + message: '', + textWithLinks, + isSuspicious: false + }; +}; diff --git a/src/utils/transactions/parsers/getShardText.ts b/src/utils/transactions/parsers/getShardText.ts new file mode 100644 index 0000000..0dafdb0 --- /dev/null +++ b/src/utils/transactions/parsers/getShardText.ts @@ -0,0 +1,26 @@ +import { ALL_SHARDS_SHARD_ID, METACHAIN_SHARD_ID } from 'constants/index'; + +export const getShardText = (shard: number | string) => { + let shardText = shard; + + if (typeof shardText === 'string' && shardText.includes('Shard')) { + shardText = shardText.replace('Shard', '').replace(' ', ''); + } + + const isMetachain = + METACHAIN_SHARD_ID.toString() === String(shardText).toString() || + String(shardText) === 'metachain'; + + const isAllShards = + ALL_SHARDS_SHARD_ID.toString() === String(shardText).toString(); + + if (isMetachain) { + return 'Metachain'; + } + + if (isAllShards) { + return 'All Shards'; + } + + return `Shard ${shardText}`; +}; diff --git a/src/utils/transactions/parsers/getTransactionActionNftText.ts b/src/utils/transactions/parsers/getTransactionActionNftText.ts new file mode 100644 index 0000000..3c57307 --- /dev/null +++ b/src/utils/transactions/parsers/getTransactionActionNftText.ts @@ -0,0 +1,71 @@ +import { TokenArgumentType } from 'types/serverTransactions.types'; +import { NftEnumType } from 'types/tokens.types'; +import { formatAmount } from 'utils/operations'; +import { explorerUrlBuilder } from '../getInterpretedTransaction/helpers/explorerUrlBuilder'; + +export interface TransactionActionNftType { + token: TokenArgumentType; + noValue?: boolean; + showLastNonZeroDecimal?: boolean; +} +export interface TransactionActionNftReturnType { + badgeText: string | null; + tokenFormattedAmount: string | null; + tokenExplorerLink: string; + tokenLinkText: string; + token: TokenArgumentType; + showLastNonZeroDecimal?: boolean; + noValue?: boolean; +} + +export function getTransactionActionNftText({ + token, + noValue, + showLastNonZeroDecimal +}: TransactionActionNftType): TransactionActionNftReturnType { + const isTokenAmountVisible = + !noValue && token.value && token.type !== NftEnumType.NonFungibleESDT; + const canShowFormattedAmount = token.decimals != null; + + let badgeText = ''; + if (token.type === NftEnumType.NonFungibleESDT) { + badgeText = 'NFT'; + } + if (token.type === NftEnumType.SemiFungibleESDT) { + badgeText = 'SFT'; + } + if (token.type === NftEnumType.MetaESDT) { + badgeText = 'Meta-ESDT'; + } + + let tokenFormattedAmount = ''; + if (isTokenAmountVisible && canShowFormattedAmount) { + tokenFormattedAmount = canShowFormattedAmount + ? formatAmount({ + input: token.value, + decimals: token.decimals, + digits: 2, + showLastNonZeroDecimal + }) + : Number(token.value).toLocaleString('en'); + } + + const tokenExplorerLink = explorerUrlBuilder.nftDetails( + String(token.identifier) + ); + + const tokenLinkText = + token.ticker === token.collection + ? token.identifier ?? token.ticker + : token.ticker; + + return { + badgeText, + tokenFormattedAmount: isTokenAmountVisible ? tokenFormattedAmount : null, + tokenExplorerLink, + tokenLinkText, + token, + noValue, + showLastNonZeroDecimal + }; +} diff --git a/src/utils/transactions/parsers/getTransactionActionTokenText.ts b/src/utils/transactions/parsers/getTransactionActionTokenText.ts new file mode 100644 index 0000000..823753b --- /dev/null +++ b/src/utils/transactions/parsers/getTransactionActionTokenText.ts @@ -0,0 +1,51 @@ +import { DECIMALS } from 'constants/index'; +import { TokenArgumentType } from 'types/serverTransactions.types'; +import { formatAmount } from 'utils/operations'; +import { explorerUrlBuilder } from '../getInterpretedTransaction/helpers/explorerUrlBuilder'; + +export interface TransactionActionTokenType { + token: TokenArgumentType; + noValue?: boolean; + showLastNonZeroDecimal?: boolean; +} +export interface TransactionActionTokenReturnType { + tokenExplorerLink: string; + tokenFormattedAmount: string | null; + showFormattedAmount: boolean; + tokenLinkText: string; + token: TokenArgumentType; + showLastNonZeroDecimal?: boolean; +} + +export function getTransactionActionTokenText({ + token, + noValue, + showLastNonZeroDecimal +}: TransactionActionTokenType): TransactionActionTokenReturnType { + const showFormattedAmount = Boolean(!noValue && token.value); + + const tokenFormattedAmount = showFormattedAmount + ? formatAmount({ + input: token.value, + decimals: token.decimals ?? DECIMALS, + digits: 2, + showLastNonZeroDecimal, + addCommas: true + }) + : null; + + const tokenExplorerLink = explorerUrlBuilder.tokenDetails( + String(token.token) + ); + + const tokenLinkText = token.ticker; + + return { + tokenExplorerLink, + tokenFormattedAmount, + showFormattedAmount, + tokenLinkText, + token, + showLastNonZeroDecimal + }; +} diff --git a/src/utils/transactions/parsers/getTransactionFee.ts b/src/utils/transactions/parsers/getTransactionFee.ts new file mode 100644 index 0000000..dcc4fc1 --- /dev/null +++ b/src/utils/transactions/parsers/getTransactionFee.ts @@ -0,0 +1,22 @@ +import BigNumber from 'bignumber.js'; +import { InterpretedTransactionType } from 'types'; + +const getFee = (transaction: InterpretedTransactionType) => { + const bNgasPrice = new BigNumber(transaction.gasPrice); + const bNgasUsed = new BigNumber(transaction.gasUsed); + const output = bNgasPrice.times(bNgasUsed).toString(); + + return output; +}; + +export function getTransactionFee(transaction: InterpretedTransactionType) { + if (transaction.fee) { + return transaction.fee; + } + + if (transaction.gasUsed === undefined) { + return null; + } + + return getFee(transaction); +} diff --git a/src/utils/transactions/parsers/getTransactionLink.ts b/src/utils/transactions/parsers/getTransactionLink.ts new file mode 100644 index 0000000..e208ffe --- /dev/null +++ b/src/utils/transactions/parsers/getTransactionLink.ts @@ -0,0 +1,9 @@ +import { explorerAddressSelector } from 'reduxStore/selectors'; +import { store } from 'reduxStore/store'; + +export function getTransactionLink( + transactionHash: string, + explorerAddress: string = explorerAddressSelector(store.getState()) +) { + return `${explorerAddress}/transactions/${transactionHash}`; +} diff --git a/src/utils/transactions/parsers/getTransactionLinkWithLabel.ts b/src/utils/transactions/parsers/getTransactionLinkWithLabel.ts new file mode 100644 index 0000000..a49c644 --- /dev/null +++ b/src/utils/transactions/parsers/getTransactionLinkWithLabel.ts @@ -0,0 +1,49 @@ +import { + InterpretedTransactionType, + TransactionDirectionEnum, + TransactionLinkType +} from 'types'; + +export interface GetTransactionLinkWithLabelParamsType { + transaction: InterpretedTransactionType; + direction: TransactionDirectionEnum; +} + +export const getTransactionLinkWithLabel = ({ + transaction, + direction +}: GetTransactionLinkWithLabelParamsType): TransactionLinkType => { + const isSmartContract = direction === TransactionDirectionEnum.INTERNAL; + let address = transaction.sender; + + if (isSmartContract) { + return { + link: transaction.links.senderLink ?? '', + label: 'Smart Contract transaction', + address + }; + } + + const isOut = direction === TransactionDirectionEnum.OUT; + + const link = isOut + ? transaction.links.receiverLink + : transaction.links.senderLink; + + let label = ''; + + const isSelf = direction === TransactionDirectionEnum.SELF; + + if (isSelf && transaction.receiverAssets) { + label = transaction.receiverAssets.name; + } else { + address = isOut ? transaction.receiver : transaction.sender; + label = isOut ? 'To:' : 'From:'; + } + + return { + label, + address, + link: link ?? '' + }; +}; diff --git a/src/utils/transactions/parsers/getTransactionMessages.ts b/src/utils/transactions/parsers/getTransactionMessages.ts new file mode 100644 index 0000000..fafdaa1 --- /dev/null +++ b/src/utils/transactions/parsers/getTransactionMessages.ts @@ -0,0 +1,17 @@ +import { InterpretedTransactionType } from 'types'; +import { getOperationsMessages } from '../getInterpretedTransaction/helpers/getOperationsMessages'; +import { getReceiptMessage } from '../getInterpretedTransaction/helpers/getReceiptMessage'; +import getScResultsMessages from '../getInterpretedTransaction/helpers/getScResultsMessages'; + +export function getTransactionMessages( + transaction: InterpretedTransactionType +) { + const transactionMessages = Array.from( + new Set([ + ...getScResultsMessages(transaction), + ...getOperationsMessages(transaction), + getReceiptMessage(transaction) + ]) + ).filter((el) => Boolean(el)); + return transactionMessages; +} diff --git a/src/utils/transactions/parsers/getTransactionStatus.ts b/src/utils/transactions/parsers/getTransactionStatus.ts new file mode 100644 index 0000000..73be8a4 --- /dev/null +++ b/src/utils/transactions/parsers/getTransactionStatus.ts @@ -0,0 +1,24 @@ +import { TransactionServerStatusesEnum } from 'types/enums.types'; +import { InterpretedTransactionType } from 'types/serverTransactions.types'; + +export function getTransactionStatus(transaction: InterpretedTransactionType) { + const statusIs = (compareTo: string) => + transaction.status.toLowerCase() === compareTo.toLowerCase(); + + const failed = + statusIs(TransactionServerStatusesEnum.fail) || + statusIs(TransactionServerStatusesEnum.rewardReverted); + const success = statusIs(TransactionServerStatusesEnum.success); + const invalid = + statusIs(TransactionServerStatusesEnum.notExecuted) || + statusIs(TransactionServerStatusesEnum.invalid); + const pending = + statusIs(TransactionServerStatusesEnum.pending) || + transaction.pendingResults; + return { + failed, + success, + invalid, + pending + }; +} diff --git a/src/utils/transactions/parsers/getTransactionStatusText.ts b/src/utils/transactions/parsers/getTransactionStatusText.ts new file mode 100644 index 0000000..cbdff12 --- /dev/null +++ b/src/utils/transactions/parsers/getTransactionStatusText.ts @@ -0,0 +1,15 @@ +import { TransactionServerStatusesEnum } from 'types/enums.types'; +import { InterpretedTransactionType } from 'types/serverTransactions.types'; + +export const getTransactionStatusText = ( + transaction: InterpretedTransactionType +) => { + switch (true) { + case transaction.pendingResults: + return 'Pending (Smart Contract Execution)'; + case transaction.status === TransactionServerStatusesEnum.rewardReverted: + return TransactionServerStatusesEnum.fail; + default: + return transaction.status.toString(); + } +}; diff --git a/src/utils/transactions/parsers/getTrimmedHash.ts b/src/utils/transactions/parsers/getTrimmedHash.ts new file mode 100644 index 0000000..f06d4f5 --- /dev/null +++ b/src/utils/transactions/parsers/getTrimmedHash.ts @@ -0,0 +1,7 @@ +export const getTrimmedHash = (address: string, divider = 4): string => + `${address.substring( + 0, + Math.floor(address.length / divider) + )}...${address.substring( + address.length - Math.ceil(address.length / divider) + )}`; diff --git a/src/utils/transactions/parsers/getUsernameForTransaction.ts b/src/utils/transactions/parsers/getUsernameForTransaction.ts new file mode 100644 index 0000000..5c227a0 --- /dev/null +++ b/src/utils/transactions/parsers/getUsernameForTransaction.ts @@ -0,0 +1,13 @@ +import { isStringBase64 } from '../../decoders'; + +export const getUsernameForTransaction = (username?: string) => { + if (!username) { + return; + } + + const defaultData = username ?? ''; + + return isStringBase64(defaultData) + ? defaultData + : Buffer.from(defaultData, 'base64').toString(); +}; diff --git a/src/utils/transactions/parsers/getVisibleOperations.ts b/src/utils/transactions/parsers/getVisibleOperations.ts new file mode 100644 index 0000000..dfbaea4 --- /dev/null +++ b/src/utils/transactions/parsers/getVisibleOperations.ts @@ -0,0 +1,17 @@ +import { + InterpretedTransactionType, + VisibleTransactionOperationType +} from 'types/serverTransactions.types'; + +export const getVisibleOperations = ( + transaction: InterpretedTransactionType +) => { + const operations = + transaction?.operations?.filter((operation) => + Object.values(VisibleTransactionOperationType).includes( + operation.type + ) + ) ?? []; + + return operations; +}; diff --git a/src/utils/transactions/parsers/index.ts b/src/utils/transactions/parsers/index.ts new file mode 100644 index 0000000..5898063 --- /dev/null +++ b/src/utils/transactions/parsers/index.ts @@ -0,0 +1,20 @@ +export * from './getEventListDataHexValue'; +export * from './getEventListHighlight'; +export * from './getOperationDirection'; +export * from '../dataDecoders/getScResultsDecodedData'; +export * from '../dataDecoders/getScResultsHighlight'; +export * from './getScamFlag'; +export * from './getShardText'; +export * from './getTransactionActionNftText'; +export * from './getTransactionActionTokenText'; +export * from './getTransactionFee'; +export * from './getTransactionLinkWithLabel'; +export * from './getTransactionMessages'; +export * from './getTransactionStatus'; +export * from './getTransactionStatusText'; +export * from './getTrimmedHash'; +export * from './getVisibleOperations'; +export * from './transactionActionUnwrapper'; +export * from '../dataDecoders/useDataDecode'; +export * from '../dataDecoders/useDataDecodeMethod'; +export * from './useGetOperationList'; diff --git a/src/utils/transactions/parsers/parseMultiEsdtTransferData.ts b/src/utils/transactions/parsers/parseMultiEsdtTransferData.ts new file mode 100644 index 0000000..8a5b668 --- /dev/null +++ b/src/utils/transactions/parsers/parseMultiEsdtTransferData.ts @@ -0,0 +1,96 @@ +import BigNumber from 'bignumber.js'; +import { MultiEsdtTransactionType, TransactionTypesEnum } from 'types'; +import { decodePart } from 'utils/decoders/decodePart'; +import { getAllStringOccurrences } from '../getAllStringOccurrences'; + +export function parseMultiEsdtTransferData(data?: string) { + const transactions: MultiEsdtTransactionType[] = []; + let contractCallDataIndex = 0; + try { + if ( + data?.startsWith(TransactionTypesEnum.MultiESDTNFTTransfer) && + data?.includes('@') + ) { + const [, receiver, encodedTxCount, ...rest] = data?.split('@'); + + if (receiver) { + const txCount = new BigNumber(encodedTxCount, 16).toNumber(); + + if (txCount >= Number.MAX_SAFE_INTEGER) { + return []; + } + + let itemIndex = 0; + + for (let txIndex = 0; txIndex < txCount; txIndex++) { + const transaction: MultiEsdtTransactionType = { + type: TransactionTypesEnum.nftTransaction, + data: '', + receiver + }; + + for (let index = 0; index < 3; index++) { + switch (index) { + case 0: + transaction.token = decodePart(rest[itemIndex]); + transaction.data = rest[itemIndex]; + break; + case 1: { + const encodedNonce = + rest[itemIndex] && rest[itemIndex].length + ? rest[itemIndex] + : ''; + if (encodedNonce && encodedNonce !== '00') { + transaction.nonce = encodedNonce; + } else { + transaction.type = TransactionTypesEnum.esdtTransaction; + } + transaction.data = `${transaction.data}@${rest[itemIndex]}`; + break; + } + case 2: + transaction.amount = new BigNumber( + rest[itemIndex], + 16 + ).toString(10); + transaction.data = `${transaction.data}@${rest[itemIndex]}`; + break; + default: + break; + } + contractCallDataIndex = itemIndex + 1; + itemIndex++; + } + transactions[txIndex] = transaction; + } + + const isDifferentFromTxCount = transactions.length !== txCount; + const hasInvalidNoOfAdSigns = transactions.some((tx) => { + const adSignOccurences = getAllStringOccurrences(tx.data, '@').length; + return adSignOccurences !== 2; + }); + + const hasAdStart = transactions.some((tx) => tx.data.startsWith('@')); + if (isDifferentFromTxCount || hasInvalidNoOfAdSigns || hasAdStart) { + return []; + } + + if (rest[contractCallDataIndex]) { + let scCallData = rest[contractCallDataIndex]; + for (let i = contractCallDataIndex + 1; i < rest.length; i++) { + scCallData += '@' + rest[i]; + } + transactions[txCount] = { + type: TransactionTypesEnum.scCall, + data: scCallData, + receiver + }; + } + } + } + } catch (err) { + console.error('failed parsing tx', err); + return transactions; + } + return transactions; +} diff --git a/src/utils/transactions/parsers/parseMultiEsdtTransferDataForMultipleTransactions.ts b/src/utils/transactions/parsers/parseMultiEsdtTransferDataForMultipleTransactions.ts new file mode 100644 index 0000000..e34b71c --- /dev/null +++ b/src/utils/transactions/parsers/parseMultiEsdtTransferDataForMultipleTransactions.ts @@ -0,0 +1,96 @@ +import type { Transaction } from '@multiversx/sdk-core'; +import { + MultiSignTransactionType, + TransactionDataTokenType, + TransactionsDataTokensType +} from 'types'; +import { getTokenFromData } from '../dataDecoders/getTokenFromData'; +import { parseMultiEsdtTransferData } from './parseMultiEsdtTransferData'; + +function addTransactionDataToParsedInfo({ + parsedTransactionsByDataField, + data, + txInfo +}: { + parsedTransactionsByDataField?: TransactionsDataTokensType; + data: string; + txInfo: TransactionDataTokenType; +}) { + if (!parsedTransactionsByDataField) { + return; + } + + parsedTransactionsByDataField[data] = txInfo; +} + +export function parseMultiEsdtTransferDataForMultipleTransactions({ + transactions +}: { + transactions?: Transaction[]; +}) { + const parsedTransactionsByDataField: TransactionsDataTokensType = {}; + const allTransactions: MultiSignTransactionType[] = []; + + if (!transactions || transactions.length === 0) { + return { + allTransactions, + parsedTransactionsByDataField + }; + } + + transactions.forEach((transaction, transactionIndex) => { + const txData = transaction.getData().toString(); + const multiTxs = parseMultiEsdtTransferData(txData); + + if (multiTxs.length > 0) { + multiTxs.forEach((trx, idx) => { + const newTx: MultiSignTransactionType = { + transaction, + multiTxData: trx.data, + transactionIndex: idx + }; + + addTransactionDataToParsedInfo({ + parsedTransactionsByDataField, + data: trx.data, + txInfo: { + tokenId: trx.token ? trx.token : '', + amount: trx.amount ? trx.amount : '', + type: trx.type, + nonce: trx.nonce ? trx.nonce : '', + multiTxData: trx.data, + receiver: trx.receiver + } + }); + + allTransactions.push(newTx); + }); + } else { + const transactionData = transaction.getData().toString(); + + const { tokenId, amount } = getTokenFromData(transactionData); + + if (tokenId) { + addTransactionDataToParsedInfo({ + parsedTransactionsByDataField, + data: transactionData, + txInfo: { + tokenId, + amount, + receiver: transaction.getReceiver().bech32() + } + }); + } + allTransactions.push({ + transaction, + transactionIndex, + multiTxData: transactionData + }); + } + }); + + return { + allTransactions, + parsedTransactionsByDataField + }; +} diff --git a/src/utils/transactions/parsers/parseTransactionAfterSigning.ts b/src/utils/transactions/parsers/parseTransactionAfterSigning.ts new file mode 100644 index 0000000..ce25f91 --- /dev/null +++ b/src/utils/transactions/parsers/parseTransactionAfterSigning.ts @@ -0,0 +1,33 @@ +import { Transaction } from '@multiversx/sdk-core/out'; +import { PlainSignedTransaction } from '@multiversx/sdk-web-wallet-provider/out/plainSignedTransaction'; +import { newTransaction } from 'models'; +import { SignedTransactionType } from 'types'; +import { TransactionServerStatusesEnum } from 'types/enums.types'; +import { isGuardianTx } from '../isGuardianTx'; + +export function parseTransactionAfterSigning( + signedTransaction: Transaction | PlainSignedTransaction +) { + const isComplexTransaction = + Object.getPrototypeOf(signedTransaction).toPlainObject != null; + + const transaction = isComplexTransaction + ? (signedTransaction as Transaction) + : newTransaction(signedTransaction as PlainSignedTransaction); + + const parsedTransaction: SignedTransactionType = { + ...transaction.toPlainObject(), + hash: transaction.getHash().hex(), + senderUsername: transaction.getSenderUsername().valueOf(), + receiverUsername: transaction.getReceiverUsername().valueOf(), + status: TransactionServerStatusesEnum.pending + }; + + // TODO: Remove when the protocol supports usernames for guardian transactions + if (isGuardianTx({ data: parsedTransaction.data, onlySetGuardian: true })) { + delete parsedTransaction.senderUsername; + delete parsedTransaction.receiverUsername; + } + + return parsedTransaction; +} diff --git a/src/utils/transactions/parsers/tests/getScamFlag.test.ts b/src/utils/transactions/parsers/tests/getScamFlag.test.ts new file mode 100644 index 0000000..9b45baf --- /dev/null +++ b/src/utils/transactions/parsers/tests/getScamFlag.test.ts @@ -0,0 +1,40 @@ +import { getScamFlag } from '../getScamFlag'; + +describe('scamDetect tests', () => { + const output = 'Message hidden due to suspicious content - '; + const strings: { [key: string]: string[] } = { + '👉 link.com': ['👉 link.com', ''], + 'first-link.com or 🎉 second-link.com 🎉': [ + 'first-link.com or 🎉 second-link.com 🎉', + '' + ], + 'http://google.com 🎉': ['http://google.com 🎉', ''], + '👉 https://linkedin.com 🎉': ['👉 https://linkedin.com 🎉', ''], + 'http://google.com?asd=true': ['http://google.com?asd=true', ''], + 'http://www1.google.com': ['http://www1.google.com', ''], + 'http://www.google.ceva.com': ['http://www.google.ceva.com', ''], + 'access: 👉 www.lottery-multiversx.com': [ + 'access: 👉 www.lottery-multiversx.com', + ' - Scam report' + ], + '[...] 🅻🅾🆃🆃🅴🆁🆈': ['[...] 🅻🅾🆃🆃🅴🆁🆈', 'Lottery scam report'], + 'Cool nft': ['Cool nft', '', 'yes'] + }; + for (let i = 0; i < Object.keys(strings).length; i++) { + const inputMessage = Object.keys(strings)[i]; + const [msg, reason, isNsfw] = strings[inputMessage]; + + test(`anonymize ${inputMessage} -> ${msg}`, () => { + const { message: result, textWithLinks } = getScamFlag({ + message: inputMessage, + scamInfo: { + info: reason, + type: msg + }, + isNsfw: Boolean(isNsfw) + }); + expect(result).toEqual(`${output}${reason}`); + expect(textWithLinks).toEqual(msg); + }); + } +}); diff --git a/src/utils/transactions/parsers/tests/getShardText.test.ts b/src/utils/transactions/parsers/tests/getShardText.test.ts new file mode 100644 index 0000000..0e041c4 --- /dev/null +++ b/src/utils/transactions/parsers/tests/getShardText.test.ts @@ -0,0 +1,13 @@ +import { getShardText } from '../getShardText'; + +describe('getShardText tests', () => { + it('formats metachain', () => { + expect(getShardText(4294967295)).toBe('Metachain'); + }); + it('formats Shard 1', () => { + expect(getShardText(1)).toBe('Shard 1'); + }); + it('formats Shard 2', () => { + expect(getShardText('Shard 2')).toBe('Shard 2'); + }); +}); diff --git a/src/utils/transactions/parsers/tests/getTransactionLinkWithLabel.test.ts b/src/utils/transactions/parsers/tests/getTransactionLinkWithLabel.test.ts new file mode 100644 index 0000000..d1a8217 --- /dev/null +++ b/src/utils/transactions/parsers/tests/getTransactionLinkWithLabel.test.ts @@ -0,0 +1,39 @@ +import { ServerTransactionType, TransactionDirectionEnum } from 'types'; +import { getInterpretedTransaction } from 'utils/transactions/getInterpretedTransaction'; +import { transactionMock } from '../../getInterpretedTransaction/tests/extended-transaction-mock'; +import { getTransactionLinkWithLabel } from '../getTransactionLinkWithLabel'; +const explorerAddress = 'https://testing.devnet.com'; + +describe('getTransactionLinkWithLabel tests', () => { + const transaction: ServerTransactionType = { + ...transactionMock, + tokenIdentifier: 'token-id' + }; + + const interpretedTransaction = getInterpretedTransaction({ + transaction, + address: 'erd1-my-address-hash', + explorerAddress + }); + + const { link, label, address } = getTransactionLinkWithLabel({ + transaction: interpretedTransaction, + direction: TransactionDirectionEnum.OUT + }); + + it('should return correct link', () => { + expect(link).toStrictEqual( + `${explorerAddress}/accounts/${transaction.receiver}` + ); + + expect(link).toStrictEqual(interpretedTransaction.links.receiverLink); + }); + + it('should return correct label', () => { + expect(label).toStrictEqual('To:'); + }); + + it('should return correct address', () => { + expect(address).toStrictEqual(transaction.receiver); + }); +}); diff --git a/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/esdtNftUnwrapper.ts b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/esdtNftUnwrapper.ts new file mode 100644 index 0000000..a48f8f0 --- /dev/null +++ b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/esdtNftUnwrapper.ts @@ -0,0 +1,22 @@ +import { + TransactionActionType, + UnwrapperType, + TransactionActionsEnum +} from 'types/serverTransactions.types'; + +export const esdtNftUnwrapper = ( + action: TransactionActionType +): Array => { + switch (action.name) { + case TransactionActionsEnum.transfer: + return [ + 'Transfer', + { token: action.arguments?.transfers }, + 'to', + { address: action.arguments?.receiver } + ]; + + default: + return []; + } +}; diff --git a/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/index.ts b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/index.ts new file mode 100644 index 0000000..be28b0e --- /dev/null +++ b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/index.ts @@ -0,0 +1,3 @@ +export * from './stakeUnwrapper'; +export * from './esdtNftUnwrapper'; +export * from './mexUnwrapper'; diff --git a/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/mexUnwrapper.ts b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/mexUnwrapper.ts new file mode 100644 index 0000000..c9591ea --- /dev/null +++ b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/mexUnwrapper.ts @@ -0,0 +1,77 @@ +import BigNumber from 'bignumber.js'; +import { ZERO } from 'constants/index'; +import { + TransactionActionType, + UnwrapperType, + TransactionActionsEnum +} from 'types/serverTransactions.types'; + +export const mexUnwrapper = ( + action: TransactionActionType +): Array => { + switch (action.name) { + // distribution + case TransactionActionsEnum.claimLockedAssets: + return ['Claim locked assets']; + // farm + case TransactionActionsEnum.enterFarm: + case TransactionActionsEnum.enterFarmProxy: { + return ['Enter farm with', { token: action.arguments?.transfers }]; + } + case TransactionActionsEnum.enterFarmAndLockRewards: + case TransactionActionsEnum.enterFarmAndLockRewardsProxy: + return [ + 'Enter farm and lock rewards with', + { token: action.arguments?.transfers } + ]; + case TransactionActionsEnum.exitFarm: + case TransactionActionsEnum.exitFarmProxy: + return ['Exit farm with', { token: action.arguments?.transfers }]; + case TransactionActionsEnum.claimRewards: + case TransactionActionsEnum.claimRewardsProxy: + return ['Claim rewards', { token: action.arguments?.transfers }]; + case TransactionActionsEnum.compoundRewards: + case TransactionActionsEnum.compoundRewardsProxy: + return ['Reinvest rewards', { token: action.arguments?.transfers }]; + // pairs + case TransactionActionsEnum.swapTokensFixedInput: + case TransactionActionsEnum.swap: + return action.description ? [action.description] : []; + case TransactionActionsEnum.swapTokensFixedOutput: + return action.description ? [action.description] : []; + case TransactionActionsEnum.addLiquidity: + case TransactionActionsEnum.addLiquidityProxy: + return [ + 'Added liquidity for', + { token: [action.arguments?.transfers[0]] }, + 'and', + { token: [action.arguments?.transfers[1]] } + ]; + case TransactionActionsEnum.removeLiquidity: + case TransactionActionsEnum.removeLiquidityProxy: + return [ + 'Removed liquidity with ', + { token: action.arguments?.transfers } + ]; + case TransactionActionsEnum.mergeLockedAssetTokens: + let value = ZERO; + if (action.arguments?.transfers) { + const values = action.arguments.transfers.map( + ({ value }: { value: string }) => value + ); + value = BigNumber.sum.apply(null, values).toString(10); + } + return [ + `Merge ${action.arguments?.transfers.length}`, + { tokenNoLink: [action.arguments?.transfers[0]] }, + 'positions into a single', + { tokenNoLink: [action.arguments?.transfers[0]] }, + 'position of value', + { value } + ]; + case TransactionActionsEnum.wrapEgld: + case TransactionActionsEnum.unwrapEgld: + default: + return action.description ? [action.description] : []; + } +}; diff --git a/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/stakeUnwrapper.ts b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/stakeUnwrapper.ts new file mode 100644 index 0000000..8b6acc7 --- /dev/null +++ b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/stakeUnwrapper.ts @@ -0,0 +1,60 @@ +import { + TransactionActionType, + UnwrapperType, + TransactionActionsEnum +} from 'types/serverTransactions.types'; + +export const stakeUnwrapper = ( + action: TransactionActionType +): Array => { + switch (action.name) { + case TransactionActionsEnum.delegate: + case TransactionActionsEnum.stake: + return [ + 'Delegate', + { egldValue: action.arguments?.value }, + 'to staking provider', + { + providerName: action.arguments?.providerName, + providerAvatar: action.arguments?.providerAvatar + } + ]; + case TransactionActionsEnum.unDelegate: + return [ + 'Undelegate', + { egldValue: action.arguments?.value }, + 'from staking provider', + { + providerName: action.arguments?.providerName, + providerAvatar: action.arguments?.providerAvatar + } + ]; + case TransactionActionsEnum.stakeClaimRewards: + return [ + 'Claim rewards from staking provider', + { + providerName: action.arguments?.providerName, + providerAvatar: action.arguments?.providerAvatar + } + ]; + case TransactionActionsEnum.reDelegateRewards: + return [ + 'Redelegate rewards from staking provider', + { + providerName: action.arguments?.providerName, + providerAvatar: action.arguments?.providerAvatar + } + ]; + case TransactionActionsEnum.withdraw: + return [ + 'Withdraw from staking provider', + { + providerName: action.arguments?.providerName, + providerAvatar: action.arguments?.providerAvatar + } + ]; + + default: + return []; + } +}; diff --git a/src/utils/transactions/parsers/transactionActionUnwrapper/index.ts b/src/utils/transactions/parsers/transactionActionUnwrapper/index.ts new file mode 100644 index 0000000..2684fc4 --- /dev/null +++ b/src/utils/transactions/parsers/transactionActionUnwrapper/index.ts @@ -0,0 +1 @@ +export * from './transactionActionUnwrapper'; diff --git a/src/utils/transactions/parsers/transactionActionUnwrapper/tests/transactionActionUnwrapper.test.tsx b/src/utils/transactions/parsers/transactionActionUnwrapper/tests/transactionActionUnwrapper.test.tsx new file mode 100644 index 0000000..cef1ec2 --- /dev/null +++ b/src/utils/transactions/parsers/transactionActionUnwrapper/tests/transactionActionUnwrapper.test.tsx @@ -0,0 +1,390 @@ +import { TransactionActionsEnum } from 'types/serverTransactions.types'; +import { transactionActionUnwrapper } from '../transactionActionUnwrapper'; + +// file.only +describe('Tx Description unwrapper tests', () => { + test('Token Transfer', () => { + const { action } = { + action: { + category: 'esdtNft', + name: TransactionActionsEnum.transfer, + description: + 'Transfer 2950.00 USDC-c76f1f to erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv', + arguments: { + transfers: [ + { + collection: 'USDC-c76f1f', + identifier: 'USDC-c76f1f', + ticker: 'USDC', + name: 'WrappedUSDC', + value: '2950000000' + } + ], + receiver: + 'erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv' + } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Transfer', + { token: action.arguments.transfers }, + 'to', + { address: action.arguments.receiver } + ]); + }); + test('NFT Transfer', () => { + const { action } = { + action: { + category: 'esdtNft', + name: TransactionActionsEnum.transfer, + description: + 'Transfer NFT ARTCRAFT-322c6e-11 to erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv', + arguments: { + transfers: [ + { + type: 'NonFungibleESDT', + collection: 'ARTCRAFT-322c6e', + identifier: 'ARTCRAFT-322c6e-11', + ticker: 'ARTCRAFT-322c6e', + name: 'Hope', + value: '1' + } + ], + receiver: + 'erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv' + } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Transfer', + { token: action.arguments.transfers }, + 'to', + { address: action.arguments.receiver } + ]); + }); + test('MetaESDT Transfer', () => { + const { action } = { + action: { + category: 'esdtNft', + name: TransactionActionsEnum.transfer, + description: + 'Transfer 1.537276176898979513 LKFARM (LKFARM-9d1ea8-126c17) to erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv', + arguments: { + transfers: [ + { + type: 'MetaESDT', + collection: 'LKFARM-9d1ea8', + identifier: 'LKFARM-9d1ea8-126c17', + ticker: 'LKFARM', + name: 'LockedLPStaked', + value: '1537276176898979513', + decimals: 18 + } + ], + receiver: + 'erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv' + } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Transfer', + { token: action.arguments.transfers }, + 'to', + { address: action.arguments.receiver } + ]); + }); + + test('stake', () => { + const { action } = { + action: { + category: 'stake', + name: TransactionActionsEnum.delegate, + description: + 'Delegate 14.167802221131358682 EGLD to staking provider ARC Stake', + arguments: { value: '14167802221131358682', providerName: 'ARC Stake' } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Delegate', + { egldValue: action.arguments?.value }, + 'to staking provider', + { providerName: action.arguments?.providerName } + ]); + }); + + test('stake unDelegate', () => { + const { action } = { + action: { + category: 'stake', + name: TransactionActionsEnum.unDelegate, + description: 'Undelegate 5 eGLD from staking provider ARC Stake', + arguments: { value: '5000000000000000000', providerName: 'ARC Stake' } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Undelegate', + { egldValue: action.arguments?.value }, + 'from staking provider', + { providerName: action.arguments?.providerName } + ]); + }); + + test('stake claimRewards', () => { + const { action } = { + action: { + category: 'stake', + name: TransactionActionsEnum.claimRewards, + description: 'Claim rewards from staking provider ARC Stake', + arguments: { providerName: 'ARC Stake' } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Claim rewards from staking provider', + { providerName: action.arguments?.providerName } + ]); + }); + + test('stake reDelegateRewards', () => { + const { action } = { + action: { + category: 'stake', + name: TransactionActionsEnum.reDelegateRewards, + description: 'Redelegate rewards from staking provider ARC Stake', + arguments: { providerName: 'ARC Stake' } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Redelegate rewards from staking provider', + { providerName: action.arguments?.providerName } + ]); + }); + + test('stake withdraw', () => { + const { action } = { + action: { + category: 'stake', + name: TransactionActionsEnum.withdraw, + description: 'Withdraw from staking provider ARC Stake', + arguments: { providerName: 'ARC Stake' } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Withdraw from staking provider', + { providerName: action.arguments?.providerName } + ]); + }); + + // //TODO claimlockedassets + test('Enter farm', () => { + const { action } = { + action: { + category: 'mex', + name: TransactionActionsEnum.enterFarm, + description: 'Enter farm with 20 LKMEX', + arguments: { + transfers: [ + { + type: 'MetaESDT', + name: 'LockedMEX', + collection: 'LKMEX-aab910', + identifier: 'LKMEX-aab910-04', + ticker: 'LKMEX', + decimals: 18, + value: '20000000000000000000' + } + ] + } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Enter farm with', + { token: action.arguments.transfers } + ]); + }); + test('Enter farm and lock rewards', () => { + const { action } = { + action: { + category: 'mex', + name: TransactionActionsEnum.enterFarmAndLockRewards, + description: + 'Enter farm and lock rewards with 61.229451140721546841 EGLDMEX', + arguments: { + transfers: [ + { + name: 'EGLDMEXLP', + collection: 'EGLDMEX-0be9e5', + identifier: 'EGLDMEX-0be9e5', + ticker: 'EGLDMEX', + value: '61229451140721546841' + } + ] + } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Enter farm and lock rewards with', + { token: action.arguments.transfers } + ]); + }); + test('Exit farm', () => { + const { action } = { + action: { + category: 'mex', + name: TransactionActionsEnum.exitFarm, + description: 'Exit farm with 1358392.837361997848576 LKFARM', + arguments: { + transfers: [ + { + type: 'MetaESDT', + name: 'LockedLPStaked', + collection: 'LKFARM-9d1ea8', + identifier: 'LKFARM-9d1ea8-7ff1', + ticker: 'LKFARM', + decimals: 18, + value: '1358392837361997848576000' + } + ] + } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Exit farm with', + { token: action.arguments.transfers } + ]); + }); + test('Claim rewards', () => { + const { action } = { + action: { + category: 'mex', + name: TransactionActionsEnum.claimRewards, + description: 'Claim rewards 8.113902227485916134 LKFARM', + arguments: { + transfers: [ + { + type: 'MetaESDT', + name: 'LockedLPStaked', + collection: 'LKFARM-9d1ea8', + identifier: 'LKFARM-9d1ea8-016711', + ticker: 'LKFARM', + decimals: 18, + value: '8113902227485916134' + } + ] + } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Claim rewards', + { token: action.arguments.transfers } + ]); + }); + test('Compound rewards', () => { + const { action } = { + action: { + category: 'mex', + name: TransactionActionsEnum.compoundRewards, + description: 'Reinvest rewards 20 LKFARM', + arguments: { + transfers: [ + { + type: 'MetaESDT', + name: 'LockedLPStaked', + collection: 'LKFARM-9d1ea8', + identifier: 'LKFARM-9d1ea8-01ee91', + ticker: 'LKFARM', + decimals: 18, + value: '20000000000000000000' + } + ] + } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Reinvest rewards', + { token: action.arguments.transfers } + ]); + }); + test('Swap tokens', () => { + const { action } = { + action: { + category: 'mex', + name: TransactionActionsEnum.swap, + description: + 'Swap 1 WEGLD for a minimum of 281066.754391919467235791 MEX', + arguments: { + token1: { + name: 'WrappedEGLD', + collection: 'WEGLD-bd4d79', + identifier: 'WEGLD-bd4d79', + ticker: 'WEGLD', + value: '1000000000000000000' + }, + token2: { + name: 'MEX', + collection: 'MEX-455c57', + identifier: 'MEX-455c57', + ticker: 'MEX', + value: '281066754391919467235791' + } + } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([action.description]); + }); + test('Add liquidity', () => { + const { action } = { + action: { + category: 'mex', + name: TransactionActionsEnum.addLiquidity, + description: + 'Added liquidity for 0.309893556225986569 WEGLD and 88246.97734239037166522 MEX', + arguments: { + transfers: [ + { + name: 'WrappedEGLD', + collection: 'WEGLD-bd4d79', + identifier: 'WEGLD-bd4d79', + ticker: 'WEGLD', + value: '309893556225986569' + }, + { + name: 'MEX', + collection: 'MEX-455c57', + identifier: 'MEX-455c57', + ticker: 'MEX', + value: '88246977342390371665220' + } + ] + } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Added liquidity for', + { token: [action.arguments.transfers[0]] }, + 'and', + { token: [action.arguments.transfers[1]] } + ]); + }); + test('Remove liquidity', () => { + const { action } = { + action: { + category: 'mex', + name: TransactionActionsEnum.removeLiquidity, + description: 'Removed liquidity 0.297 EGLDMEX', + arguments: { + transfers: { + name: 'EGLDMEXLP', + collection: 'EGLDMEX-0be9e5', + identifier: 'EGLDMEX-0be9e5', + ticker: 'EGLDMEX', + value: '297000000000000000' + } + } + } + }; + expect(transactionActionUnwrapper(action)).toEqual([ + 'Removed liquidity with ', + { token: action.arguments.transfers } + ]); + }); +}); diff --git a/src/utils/transactions/parsers/transactionActionUnwrapper/transactionActionUnwrapper.ts b/src/utils/transactions/parsers/transactionActionUnwrapper/transactionActionUnwrapper.ts new file mode 100644 index 0000000..d2a5803 --- /dev/null +++ b/src/utils/transactions/parsers/transactionActionUnwrapper/transactionActionUnwrapper.ts @@ -0,0 +1,27 @@ +import { + TransactionActionType, + UnwrapperType, + TransactionActionCategoryEnum +} from 'types/serverTransactions.types'; +import { esdtNftUnwrapper } from './helpers/esdtNftUnwrapper'; +import { mexUnwrapper } from './helpers/mexUnwrapper'; +import { stakeUnwrapper } from './helpers/stakeUnwrapper'; + +export const transactionActionUnwrapper = ( + action: TransactionActionType +): Array => { + if (!action.arguments) { + return action.description ? [action.description] : [action.name]; + } + + switch (action.category) { + case TransactionActionCategoryEnum.esdtNft: + return esdtNftUnwrapper(action); + case TransactionActionCategoryEnum.mex: + return mexUnwrapper(action); + case TransactionActionCategoryEnum.stake: + return stakeUnwrapper(action); + default: + return action.description ? [action.description] : []; + } +}; diff --git a/src/utils/transactions/parsers/useGetOperationList.tsx b/src/utils/transactions/parsers/useGetOperationList.tsx new file mode 100644 index 0000000..137e01f --- /dev/null +++ b/src/utils/transactions/parsers/useGetOperationList.tsx @@ -0,0 +1,94 @@ +import { useState } from 'react'; +import { + OperationType, + TransactionOperationActionTypeEnum +} from 'types/serverTransactions.types'; + +import { WithTransactionType } from '../../../UI/types'; + +export const internalTransactionActions = [ + TransactionOperationActionTypeEnum.create, + TransactionOperationActionTypeEnum.localMint, + TransactionOperationActionTypeEnum.ESDTLocalMint, + TransactionOperationActionTypeEnum.addQuantity, + TransactionOperationActionTypeEnum.burn, + TransactionOperationActionTypeEnum.localBurn, + TransactionOperationActionTypeEnum.ESDTLocalBurn, + TransactionOperationActionTypeEnum.wipe, + TransactionOperationActionTypeEnum.writeLog, + TransactionOperationActionTypeEnum.signalError +]; + +enum ButtonTextEnum { + inOut = 'Show in/out operations', + fewer = 'Show fewer operations', + all = 'Show all operations' +} + +export interface OperationListType extends WithTransactionType { + operations: OperationType[]; + listLength?: number; +} + +export function getOperationList({ + operations, + transaction, + isExpanded = false, + listLength = 25 +}: OperationListType & { + isExpanded?: boolean; +}) { + const filteredOperations = operations.filter( + (operation) => + !internalTransactionActions.includes(operation.action) && + (operation.sender === transaction.sender || + operation.receiver === transaction.sender) + ); + + const importantOperations = + filteredOperations.length > 0 ? filteredOperations : operations; + + const displayedOperations = + importantOperations.length > listLength + ? importantOperations.slice(0, listLength) + : importantOperations; + + const collapsedOperations = + importantOperations.length > listLength + ? importantOperations.slice(listLength, importantOperations.length) + : []; + + const toggleButtonText = isExpanded + ? filteredOperations.length > 0 + ? ButtonTextEnum.inOut + : ButtonTextEnum.fewer + : ButtonTextEnum.all; + + const showToggleButton = + displayedOperations.length !== operations.length || + collapsedOperations.length > 0; + + return { + displayedOperations: isExpanded ? operations : displayedOperations, + toggleButtonText, + showToggleButton + }; +} + +export function useGetOperationList(props: OperationListType) { + const [isExpanded, setIsExpanded] = useState(false); + const onToggleButtonClick = () => { + setIsExpanded((existing) => !existing); + }; + + const { displayedOperations, showToggleButton, toggleButtonText } = + getOperationList({ ...props, isExpanded }); + + return { + isExpanded, + displayedOperations, + showToggleButton, + toggleButtonText, + onToggleButtonClick + }; +} diff --git a/src/utils/transactions/tests/builtCallbackUrl.test.ts b/src/utils/transactions/tests/builtCallbackUrl.test.ts new file mode 100644 index 0000000..7aa9dba --- /dev/null +++ b/src/utils/transactions/tests/builtCallbackUrl.test.ts @@ -0,0 +1,62 @@ +import { buildCallbackUrl } from '../url/buildCallbackUrl'; + +describe('builtCallbackUrl tests', () => { + const url = 'https://wallet.multiversx.com'; + + test('returns callbackUrl unmodified if urlParams is empty', () => { + expect(buildCallbackUrl({ callbackUrl: url })).toBe(url); + }); + + test('adds urlParams', () => { + expect( + buildCallbackUrl({ + callbackUrl: url, + urlParams: { status: 'success' } + }) + ).toBe( + 'https://wallet.multiversx.com/?status=success&sdk-dapp-version=__sdkDappVersion' + ); + }); + + test('adds urlParams and keeps existing hash', () => { + expect( + buildCallbackUrl({ + callbackUrl: url + '#test', + urlParams: { status: 'success' } + }) + ).toBe( + 'https://wallet.multiversx.com/?status=success&sdk-dapp-version=__sdkDappVersion#test' + ); + }); + + test('keeps existing urlParams', () => { + expect( + buildCallbackUrl({ + callbackUrl: url + '?page=1', + urlParams: { status: 'success' } + }) + ).toBe( + 'https://wallet.multiversx.com/?page=1&status=success&sdk-dapp-version=__sdkDappVersion' + ); + }); + + test('keeps existing hash', () => { + expect( + buildCallbackUrl({ + callbackUrl: url + '?page=1#logs', + urlParams: { status: 'success' } + }) + ).toBe( + 'https://wallet.multiversx.com/?page=1&status=success&sdk-dapp-version=__sdkDappVersion#logs' + ); + }); + + test('throws error if callbackUrl is invalid and urlParams are defined', () => { + expect( + buildCallbackUrl({ + callbackUrl: '', + urlParams: { status: 'success' } + }) + ).toBe(''); + }); +}); diff --git a/src/utils/transactions/tests/getDataPayloadForTransaction.test.ts b/src/utils/transactions/tests/getDataPayloadForTransaction.test.ts new file mode 100644 index 0000000..a4701ef --- /dev/null +++ b/src/utils/transactions/tests/getDataPayloadForTransaction.test.ts @@ -0,0 +1,63 @@ +import { getDataPayloadForTransaction } from '../dataDecoders/getDataPayloadForTransaction'; + +describe('getDataPayloadForTransaction', () => { + it('should return empty string when data is not provided', async () => { + const data = ''; + const result = getDataPayloadForTransaction(data); + expect(result.toString()).toStrictEqual(data); + }); + + it('should return the data unmodified when a string with letters is provided', async () => { + const data = 'test data'; + const result = getDataPayloadForTransaction(data); + expect(result.toString()).toStrictEqual(data); + }); + + it('should return the decoded base64 string with emoji', async () => { + const data = 'dGVzdCB0cmFuc2FjdGlvbiDwn5mA'; + const decoded = 'test transaction 🙀'; + const result = getDataPayloadForTransaction(data); + expect(result.toString()).toStrictEqual(decoded); + }); + + it('should return the decoded base64 string of another base64 string', async () => { + const data = 'ZEdWemRDQjBjbUZ1YzJGamRHbHZiaUR3bjVtQQ=='; + const decoded = 'dGVzdCB0cmFuc2FjdGlvbiDwn5mA'; + const result = getDataPayloadForTransaction(data); + expect(result.toString()).toStrictEqual(decoded); + }); + + it('should return the decoded base64 string of transaction sign data', async () => { + const data = 'bGVuZGVnYXRlQDAx'; + const decoded = 'lendegate@01'; + const result = getDataPayloadForTransaction(data); + expect(result.toString()).toStrictEqual(decoded); + }); + + it('should return the decoded base64 string with chinese characters', async () => { + const data = '5aeT5ZCN'; + const decoded = '姓名'; + const result = getDataPayloadForTransaction(data); + expect(result.toString()).toStrictEqual(decoded); + }); + + it('should return the data unmodified when ESDT data is provided', async () => { + const data = + 'ESDTNFTTransfer@524f424f54532d316365303334@01@01@0000000000000000050006bdc61ebbec719b07b4a7ebfd1fb215c0706e3c7ceb'; + const result = getDataPayloadForTransaction(data); + expect(result.toString()).toStrictEqual(data); + }); + + it('should return the data unmodified when non-ASCII string is provided', async () => { + const data = + '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~��������������������������������������������������������������������������������������������������������������������������������'; + const result = getDataPayloadForTransaction(data); + expect(result.toString()).toStrictEqual(data); + }); + + it('should return the data unmodified when chinese string is provided', async () => { + const data = '姓名'; + const result = getDataPayloadForTransaction(data); + expect(result.toString()).toStrictEqual(data); + }); +}); diff --git a/src/utils/transactions/tests/getOperationsDetails.test.ts b/src/utils/transactions/tests/getOperationsDetails.test.ts new file mode 100644 index 0000000..2f0e585 --- /dev/null +++ b/src/utils/transactions/tests/getOperationsDetails.test.ts @@ -0,0 +1,248 @@ +import { testAddress, testReceiver } from '__mocks__'; +import { + HiddenTransactionOperationType, + InterpretedTransactionType, + TransactionActionsEnum, + TransactionOperationActionTypeEnum, + VisibleTransactionOperationType +} from 'types'; +import { NftEnumType } from 'types/tokens.types'; +import { getOperationsDetails } from '../parsers/getOperationsDetails'; + +const TRANSACTION: InterpretedTransactionType = { + txHash: 'f6e4f89f693ba8da573b448ea89a295aa4f2db8f3160019a7b3e25b852bdfcf0', + gasLimit: 62800000, + gasPrice: 1000000000, + gasUsed: 41562941, + miniBlockHash: + 'e701717634203ff12f98ec5449974f9a27591d35a717aa1bc084f7deab9af5ff', + nonce: 34, + receiver: testReceiver, + receiverShard: 1, + round: 2419148, + sender: testAddress, + senderShard: 1, + signature: + 'c9b5d9156f22bfa370d66374f4508e5b987b5f94487df6e81083e608f89d37f31b1f3bfa3a6b41ff7e2d8b21c69737d31bef3f26c17dd3b4efe9afecea349205', + status: 'success', + value: '0', + fee: '582444410000000', + timestamp: 1708514888, + data: 'YnV5Q2FyZHNAMDFAMDFANTQwYTc3ODE4ODcxMDQyZDU1MDE5YzE3NTMwMjZkM2U3MTQzNDJhNDE1YjA2ZjYwMTFmMGU4MDgzNDI3ZjA1Nw==', + function: 'buyCards', + action: { + category: 'scCall', + name: TransactionActionsEnum.transfer + }, + results: [ + { + hash: '69fb01546ef00577bd2fa3aa07be315f1c52bd975a77bc343f23ab6eb7f5bbcb', + timestamp: 1708514888, + nonce: 35, + gasLimit: 0, + gasPrice: 1000000000, + value: '212370590000000', + sender: testAddress, + receiver: testReceiver, + data: 'QDZmNmJAMDAwMDAwMDEwMDAwMDAwZDQ3NDU0ZTJkMzAzNzYyNjYzNzY2MmQzMzM0MDAwMDAwMDAwMDAwMDBhOTAwMDAwMDI0MzE2NTY1NjQzMDYxMzkzNjJkMzczODMyMzUyZDM2MzgzMzMwMmQzOTYzMzMzNjJkNjUzOTM0MzY2MTM5Mzg2NjY2MzEzODM0MDAwMDAwMDc0YzY5NmQ2OTc0NjU2NDAwMDAwMDAwNjVkNWRlNDgwMDAwMDAwMTAwMDAwMDA1MDAwMDAwMGQ0NzQ1NGUyZDMwMzc2MjY2Mzc2NjJkMzMzNTAwMDAwMDAwMDAwMDAwN2QwMDAwMDAyNDMxNjU2NTY0MzA2MTM5MzYyZDM3MzgzMjM1MmQzNjM4MzMzMDJkMzk2MzMzMzYyZDY1MzkzNDM2NjEzOTM4NjY2NjMxMzgzNDAwMDAwMDA3NGM2OTZkNjk3NDY1NjQwMDAwMDAwMDY1ZDVkZTQ4MDAwMDAwMGQ0NzQ1NGUyZDMwMzc2MjY2Mzc2NjJkMzMzNjAwMDAwMDAwMDAwMDAwZDEwMDAwMDAyNDMxNjU2NTY0MzA2MTM5MzYyZDM3MzgzMjM1MmQzNjM4MzMzMDJkMzk2MzMzMzYyZDY1MzkzNDM2NjEzOTM4NjY2NjMxMzgzNDAwMDAwMDA3NGM2OTZkNjk3NDY1NjQwMDAwMDAwMDY1ZDVkZTQ4MDAwMDAwMGQ0NzQ1NGUyZDMwMzc2MjY2Mzc2NjJkMzMzNzAwMDAwMDAwMDAwMDAwMmQwMDAwMDAyNDMxNjU2NTY0MzA2MTM5MzYyZDM3MzgzMjM1MmQzNjM4MzMzMDJkMzk2MzMzMzYyZDY1MzkzNDM2NjEzOTM4NjY2NjMxMzgzNDAwMDAwMDA3NGM2OTZkNjk3NDY1NjQwMDAwMDAwMDY1ZDVkZTQ4MDAwMDAwMGQ0NzQ1NGUyZDMwMzc2MjY2Mzc2NjJkMzMzODAwMDAwMDAwMDAwMDAwMTMwMDAwMDAyNDMxNjU2NTY0MzA2MTM5MzYyZDM3MzgzMjM1MmQzNjM4MzMzMDJkMzk2MzMzMzYyZDY1MzkzNDM2NjEzOTM4NjY2NjMxMzgzNDAwMDAwMDA0NTI2MTcyNjUwMDAwMDAwMDY1ZDVkZTQ4MDAwMDAwMGQ0NzQ1NGUyZDMwMzc2MjY2Mzc2NjJkMzMzOTAwMDAwMDAwMDAwMDAwYWEwMDAwMDAyNDMxNjU2NTY0MzA2MTM5MzYyZDM3MzgzMjM1MmQzNjM4MzMzMDJkMzk2MzMzMzYyZDY1MzkzNDM2NjEzOTM4NjY2NjMxMzgzNDAwMDAwMDA0NTI2MTcyNjUwMDAwMDAwMDY1ZDVkZTQ4MDAwMDAwOGM2ODc0NzQ3MDczM2EyZjJmNjM2OTM0MzMzMTMwMzAyZDY0NmY2Mzc1NmQ2NTZlNzQyZDczNjU3Mjc2Njk2MzY1MmQ2NzY1NmU2NTdhNzk3MzJkNjQ2ZjYzNzU2ZDY1NmU3NDczMmQ2Mjc1NjM2YjY1NzQyZTczMzMyZTY1NzUyZDc3NjU3Mzc0MmQzMzJlNjE2ZDYxN2E2ZjZlNjE3NzczMmU2MzZmNmQyZjMxNjU2NTY0MzA2MTM5MzcyZDMwMzIzNDM5MmQzNjM0MzgzMDJkMzQzNjMzMzgyZDMwMzYzNDM1NjQzMzM2MzQ2MjM5MzkzMDJmNDM2MTcyNjQ0ZDY1NzQ2MTY0NjE3NDYxNDk2ZDYxNjc2NTJmMDAwMDAwOGI2ODc0NzQ3MDczM2EyZjJmNjM2OTM0MzMzMTMwMzAyZDY0NmY2Mzc1NmQ2NTZlNzQyZDczNjU3Mjc2Njk2MzY1MmQ2NzY1NmU2NTdhNzk3MzJkNjQ2ZjYzNzU2ZDY1NmU3NDczMmQ2Mjc1NjM2YjY1NzQyZTczMzMyZTY1NzUyZDc3NjU3Mzc0MmQzMzJlNjE2ZDYxN2E2ZjZlNjE3NzczMmU2MzZmNmQyZjMxNjU2NTY0MzA2MTM5MzcyZDMwMzIzNDM5MmQzNjM0MzgzMDJkMzQzNjMzMzgyZDMwMzYzNDM1NjQzMzM2MzQ2MjM5MzkzMDJmNDM2MTcyNjQ0ZDY1NzQ2MTY0NjE3NDYxNGE3MzZmNmUyZg==', + prevTxHash: + 'f6e4f89f693ba8da573b448ea89a295aa4f2db8f3160019a7b3e25b852bdfcf0', + originalTxHash: + 'f6e4f89f693ba8da573b448ea89a295aa4f2db8f3160019a7b3e25b852bdfcf0', + callType: '0', + miniBlockHash: + 'f02e34e2a2e90990141ddf8c3e9fc478f37368d90248931a7e7213f3000a1807', + function: 'transfer' + }, + { + hash: 'b7abcad8ab36164730affb48bdacf6c737ce5232494ea8b8ca96aaf1d9fa9fb9', + timestamp: 1708514888, + nonce: 1, + gasLimit: 0, + gasPrice: 1000000000, + value: '0', + sender: testAddress, + receiver: testReceiver, + data: 'TXVsdGlFU0RUTkZUVHJhbnNmZXJANTQwYTc3ODE4ODcxMDQyZDU1MDE5YzE3NTMwMjZkM2U3MTQzNDJhNDE1YjA2ZjYwMTFmMGU4MDgzNDI3ZjA1N0AwNUA0NzQ1NGUyZDMwMzc2MjY2Mzc2NkAzNUAwMUA0NzQ1NGUyZDMwMzc2MjY2Mzc2NkAzNkAwMUA0NzQ1NGUyZDMwMzc2MjY2Mzc2NkAzN0AwMUA0NzQ1NGUyZDMwMzc2MjY2Mzc2NkAzOEAwMUA0NzQ1NGUyZDMwMzc2MjY2Mzc2NkAzOUAwMQ==', + prevTxHash: + 'f6e4f89f693ba8da573b448ea89a295aa4f2db8f3160019a7b3e25b852bdfcf0', + originalTxHash: + 'f6e4f89f693ba8da573b448ea89a295aa4f2db8f3160019a7b3e25b852bdfcf0', + callType: '0', + miniBlockHash: + 'f02e34e2a2e90990141ddf8c3e9fc478f37368d90248931a7e7213f3000a1807', + function: 'MultiESDTNFTTransfer' + }, + { + hash: '62688728fd2f3bcb6ae6b96e63edf2789ab7dbbbfe46bb226d280c31899c538d', + timestamp: 1708514888, + nonce: 0, + gasLimit: 0, + gasPrice: 1000000000, + value: '0', + sender: testAddress, + receiver: testReceiver, + data: 'RVNEVE5GVFRyYW5zZmVyQDQ3NDU0ZTJkMzAzNzYyNjYzNzY2QDM0QDAxQDU0MGE3NzgxODg3MTA0MmQ1NTAxOWMxNzUzMDI2ZDNlNzE0MzQyYTQxNWIwNmY2MDExZjBlODA4MzQyN2YwNTc=', + prevTxHash: + 'f6e4f89f693ba8da573b448ea89a295aa4f2db8f3160019a7b3e25b852bdfcf0', + originalTxHash: + 'f6e4f89f693ba8da573b448ea89a295aa4f2db8f3160019a7b3e25b852bdfcf0', + callType: '0', + miniBlockHash: + 'f02e34e2a2e90990141ddf8c3e9fc478f37368d90248931a7e7213f3000a1807', + function: 'ESDTNFTTransfer' + } + ], + price: 57.31, + logs: { + id: 'f6e4f89f693ba8da573b448ea89a295aa4f2db8f3160019a7b3e25b852bdfcf0', + address: testAddress, + events: [] + }, + operations: [ + { + id: 'f6e4f89f693ba8da573b448ea89a295aa4f2db8f3160019a7b3e25b852bdfcf0', + action: TransactionOperationActionTypeEnum.create, + type: VisibleTransactionOperationType.nft, + esdtType: NftEnumType.NonFungibleESDT, + collection: 'GEN-07bf7f', + identifier: 'GEN-07bf7f-34', + ticker: 'GEN-07bf7f', + name: 'col 1 #52', + sender: testAddress, + receiver: testAddress, + value: '1' + }, + { + id: 'f6e4f89f693ba8da573b448ea89a295aa4f2db8f3160019a7b3e25b852bdfcf0', + action: TransactionOperationActionTypeEnum.transfer, + type: VisibleTransactionOperationType.nft, + esdtType: NftEnumType.NonFungibleESDT, + collection: 'GEN-07bf7f', + identifier: 'GEN-07bf7f-34', + ticker: 'GEN-07bf7f', + name: 'col 1 #52', + sender: testAddress, + receiver: testReceiver, + value: '1' + }, + { + id: 'f6e4f89f693ba8da573b448ea89a295aa4f2db8f3160019a7b3e25b852bdfcf0', + action: TransactionOperationActionTypeEnum.create, + type: VisibleTransactionOperationType.nft, + esdtType: NftEnumType.NonFungibleESDT, + collection: 'GEN-07bf7f', + identifier: 'GEN-07bf7f-35', + ticker: 'GEN-07bf7f', + name: 'col 1 #53', + sender: testAddress, + receiver: testAddress, + value: '1' + }, + { + id: 'f6e4f89f693ba8da573b448ea89a295aa4f2db8f3160019a7b3e25b852bdfcf0', + action: TransactionOperationActionTypeEnum.create, + type: VisibleTransactionOperationType.nft, + esdtType: NftEnumType.NonFungibleESDT, + collection: 'GEN-07bf7f', + identifier: 'GEN-07bf7f-36', + ticker: 'GEN-07bf7f', + name: 'col 1 #54', + sender: testAddress, + receiver: testAddress, + value: '1' + }, + { + id: '6b9cd262f250c0197c679582daca50a86a552df717dabc1733fc203cd62b204f', + action: TransactionOperationActionTypeEnum.writeLog, + type: HiddenTransactionOperationType.log, + sender: testAddress, + receiver: testReceiver, + senderAssets: { + description: 'test transaction', + name: 'test.elrond', + tags: ['dns', 'username'] + }, + value: '1', + data: '@ff2b' + } + ], + transactionDetails: { + direction: undefined, + method: '', + transactionTokens: [], + isContract: undefined + }, + links: { + senderLink: undefined, + receiverLink: undefined, + senderShardLink: undefined, + receiverShardLink: undefined, + transactionLink: undefined + } +}; + +describe('getOperationsDetails tests', () => { + test('returns operations without hidden operation type', () => { + expect(getOperationsDetails({ transaction: TRANSACTION }).length).toBe(4); + }); + + test('returns operations without hidden operation type and with create action type', () => { + expect( + getOperationsDetails({ + transaction: TRANSACTION, + filterBy: { + action: TransactionOperationActionTypeEnum.create + } + }).length + ).toBe(3); + }); + + test('returns operations without hidden operation type and with transfer action type', () => { + expect( + getOperationsDetails({ + transaction: TRANSACTION, + filterBy: { + action: TransactionOperationActionTypeEnum.transfer + } + }).length + ).toBe(1); + }); + + test('returns operations without hidden operation type and with transfer action type', () => { + expect( + getOperationsDetails({ + transaction: TRANSACTION, + filterBy: { + action: TransactionOperationActionTypeEnum.transfer, + receiver: testReceiver + } + }).length + ).toBe(1); + }); + + test('returns operations without hidden operation type and with create action type', () => { + expect( + getOperationsDetails({ + transaction: TRANSACTION, + filterBy: { + action: TransactionOperationActionTypeEnum.create, + receiver: testReceiver + } + }).length + ).toBe(0); + }); + + test('returns operations without hidden operation type and with create action type, sender and receiver', () => { + expect( + getOperationsDetails({ + transaction: TRANSACTION, + filterBy: { + action: TransactionOperationActionTypeEnum.create, + sender: testAddress, + receiver: testAddress + } + }).length + ).toBe(3); + }); +}); diff --git a/src/utils/transactions/tests/getTokenFromData.test.ts b/src/utils/transactions/tests/getTokenFromData.test.ts new file mode 100644 index 0000000..d8db9e1 --- /dev/null +++ b/src/utils/transactions/tests/getTokenFromData.test.ts @@ -0,0 +1,49 @@ +import { getTokenFromData } from '../dataDecoders/getTokenFromData'; + +describe('getTokenFromData tests', () => { + test('get nft test', () => { + const result = getTokenFromData( + 'ESDTNFTTransfer@4d455441524944452d346264313933@4022@a688906bd8b00000@ebfd923cd251f857ed7639e87143ac83f12f423827abc4a0cdde0119c3e37915' + ); + + expect(result).toStrictEqual({ + collection: 'METARIDE-4bd193', + tokenId: 'METARIDE-4bd193-4022', + nonce: '4022', + amount: '12000000000000000000', + receiver: 'erd1a07ey0xj28u90mtk8858zsavs0cj7s3cy74ufgxdmcq3nslr0y2st2aaax' + }); + }); + test('get token test', () => { + const result = getTokenFromData( + 'ESDTTransfer@5745474c442d383836303061@8ac7230489e80000@73776170546f6b656e734669786564496e707574@555344432d613332393036@480e48f5' + ); + + expect(result).toStrictEqual({ + tokenId: 'WEGLD-88600a', + amount: '10000000000000000000' + }); + }); + test('get multiesdt test', () => { + const result = getTokenFromData( + 'MultiESDTNFTTransfer@00000000000000000500bfa6f00d36d61f232c3c7532fae6b08e3909aac27ceb@02@5745474c442d383836303061@@4657679aa5be59@4d45582d623662623764@@e5eb0b83150fb8ec2b@6164644c6971756964697479@45a354a5e6a567@e39e745b5f173ec0d8' + ); + + expect(result).toStrictEqual({ + tokenId: '', + amount: '' + }); + }); + test('get nft from burn transaction', () => { + const result = getTokenFromData( + 'ESDTNFTBurn@4554484f532d643735383863@015a@01' + ); + + expect(result).toStrictEqual({ + collection: 'ETHOS-d7588c', + tokenId: 'ETHOS-d7588c-015a', + nonce: '015a', + amount: '1' + }); + }); +}); diff --git a/src/utils/transactions/tests/getUnHighlightedDataFieldParts.test.ts b/src/utils/transactions/tests/getUnHighlightedDataFieldParts.test.ts new file mode 100644 index 0000000..c98fd17 --- /dev/null +++ b/src/utils/transactions/tests/getUnHighlightedDataFieldParts.test.ts @@ -0,0 +1,93 @@ +import { getUnHighlightedDataFieldParts } from '../dataDecoders/getUnHighlightedDataFieldParts'; + +describe('getUnHighlightedDataFieldParts tests', () => { + it('should show correctly for first occurrence when present in the middle', () => { + const firstOccurrenceIndex = 5; + const lastOccurrenceIndex = 11; + + const { start, end } = getUnHighlightedDataFieldParts({ + data: 'StartMiddleMiddleEnd', + highlight: 'Middle', + occurrences: [firstOccurrenceIndex, lastOccurrenceIndex], + transactionIndex: 0 + }); + + expect(start).toStrictEqual('Start'); + expect(end).toStrictEqual('MiddleEnd'); + }); + + it('should show correctly for last occurrence when present in the middle', () => { + const firstOccurrenceIndex = 5; + const lastOccurrenceIndex = 11; + + const { start, end } = getUnHighlightedDataFieldParts({ + data: 'StartMiddleMiddleEnd', + highlight: 'Middle', + occurrences: [firstOccurrenceIndex, lastOccurrenceIndex], + transactionIndex: 1 + }); + + expect(start).toStrictEqual('StartMiddle'); + expect(end).toStrictEqual('End'); + }); + + it('should show correctly for first occurrence when present in the beginning', () => { + const firstOccurrenceIndex = 0; + const lastOccurrenceIndex = 6; + + const { start, end } = getUnHighlightedDataFieldParts({ + data: 'MiddleMiddleEnd', + highlight: 'Middle', + occurrences: [firstOccurrenceIndex, lastOccurrenceIndex], + transactionIndex: 0 + }); + + expect(start).toStrictEqual(''); + expect(end).toStrictEqual('MiddleEnd'); + }); + + it('should show correctly for second occurrence when present in the beginning', () => { + const firstOccurrenceIndex = 0; + const lastOccurrenceIndex = 6; + + const { start, end } = getUnHighlightedDataFieldParts({ + data: 'MiddleMiddleEnd', + highlight: 'Middle', + occurrences: [firstOccurrenceIndex, lastOccurrenceIndex], + transactionIndex: 1 + }); + + expect(start).toStrictEqual('Middle'); + expect(end).toStrictEqual('End'); + }); + + it('should show correctly for first occurrence when present in the end', () => { + const firstOccurrenceIndex = 5; + const lastOccurrenceIndex = 11; + + const { start, end } = getUnHighlightedDataFieldParts({ + data: 'StartMiddleMiddle', + highlight: 'Middle', + occurrences: [firstOccurrenceIndex, lastOccurrenceIndex], + transactionIndex: 0 + }); + + expect(start).toStrictEqual('Start'); + expect(end).toStrictEqual('Middle'); + }); + + it('should show correctly for second occurrence when present in the end', () => { + const firstOccurrenceIndex = 5; + const lastOccurrenceIndex = 11; + + const { start, end } = getUnHighlightedDataFieldParts({ + data: 'StartMiddleMiddle', + highlight: 'Middle', + occurrences: [firstOccurrenceIndex, lastOccurrenceIndex], + transactionIndex: 1 + }); + + expect(start).toStrictEqual('StartMiddle'); + expect(end).toStrictEqual(''); + }); +}); diff --git a/src/utils/transactions/tests/isGuardianTx.test.ts b/src/utils/transactions/tests/isGuardianTx.test.ts new file mode 100644 index 0000000..a9ad3c6 --- /dev/null +++ b/src/utils/transactions/tests/isGuardianTx.test.ts @@ -0,0 +1,54 @@ +import { GuardianActionsEnum } from 'constants/index'; +import { isGuardianTx } from '../isGuardianTx'; + +describe('isGuardianTx Function', () => { + // Test Valid Actions + Object.values(GuardianActionsEnum).forEach((action) => { + test(`should return true for valid action starting with "${action}"`, () => { + expect(isGuardianTx({ data: `${action}ExtraInfo` })).toBe(true); + }); + }); + + // Test when onlySetGuardian is true + test('should return true only for SetGuardian action when onlySetGuardian is true', () => { + expect( + isGuardianTx({ data: 'SetGuardianExtraInfo', onlySetGuardian: true }) + ).toBe(true); + expect( + isGuardianTx({ + data: 'GuardAccountAdditionalData', + onlySetGuardian: true + }) + ).toBe(false); + expect( + isGuardianTx({ + data: 'UnGuardAccountSomethingElse', + onlySetGuardian: true + }) + ).toBe(false); + }); + + // Test Invalid Actions + test('should return false for invalid actions', () => { + expect(isGuardianTx({ data: 'InvalidAction' })).toBe(false); + expect(isGuardianTx({ data: 'SetAccountGuard' })).toBe(false); + expect(isGuardianTx({ data: 'AccountUnGuard' })).toBe(false); + }); + + // Test Empty String + test('should return false for an empty string', () => { + expect(isGuardianTx({ data: '' })).toBe(false); + }); + + // Test Null or Undefined + test('should return false for null or undefined data', () => { + expect(isGuardianTx({ data: undefined })).toBe(false); + }); + + // Test Partial Matches + test('should return false for partial matches', () => { + expect(isGuardianTx({ data: 'Set' })).toBe(false); + expect(isGuardianTx({ data: 'Guard' })).toBe(false); + expect(isGuardianTx({ data: 'UnGuard' })).toBe(false); + }); +}); diff --git a/src/utils/transactions/tests/parseMultiEsdtTransferData.test.ts b/src/utils/transactions/tests/parseMultiEsdtTransferData.test.ts new file mode 100644 index 0000000..519d866 --- /dev/null +++ b/src/utils/transactions/tests/parseMultiEsdtTransferData.test.ts @@ -0,0 +1,189 @@ +import { parseMultiEsdtTransferData } from '../parsers/parseMultiEsdtTransferData'; + +const removeWhiteSpaces = (str: string) => str.replace(/\s/g, ''); +const one = { + data: removeWhiteSpaces(`MultiESDTNFTTransfer@0000000000000000050074b70610036a10129194f8474f2f63e49e3f20ce7ceb + @03 + @5745474c442d376662623930@@1bc16d674ec80000 + @4c4b4d45582d636661313364@0243@ed1ba6a961f087c4cc + @4c4b4c502d613538643661@0396@01d8482e926769e0@6164644c697175696469747950726f7879 + @00000000000000000500e5d437e80396609650d2d30e1f8c1b2c71a331de7ceb@1b7a5f826f460000@eabca78e16b85d7378`), + esdt: { + amount: '2000000000000000000', + data: '5745474c442d376662623930@@1bc16d674ec80000', + receiver: + '0000000000000000050074b70610036a10129194f8474f2f63e49e3f20ce7ceb', + token: 'WEGLD-7fbb90', + type: 'esdtTransaction' + }, + sft1: { + amount: '4373870811592434107596', + token: 'LKMEX-cfa13d', + data: '4c4b4d45582d636661313364@0243@ed1ba6a961f087c4cc', + nonce: '0243', + receiver: + '0000000000000000050074b70610036a10129194f8474f2f63e49e3f20ce7ceb', + type: 'nftTransaction' + }, + sft2: { + amount: '132935553869375968', + token: 'LKLP-a58d6a', + data: '4c4b4c502d613538643661@0396@01d8482e926769e0', + nonce: '0396', + receiver: + '0000000000000000050074b70610036a10129194f8474f2f63e49e3f20ce7ceb', + type: 'nftTransaction' + }, + scCall: { + data: '6164644c697175696469747950726f7879@00000000000000000500e5d437e80396609650d2d30e1f8c1b2c71a331de7ceb@1b7a5f826f460000@eabca78e16b85d7378', + receiver: + '0000000000000000050074b70610036a10129194f8474f2f63e49e3f20ce7ceb', + type: 'scCall' + } +}; +const two = { + data: removeWhiteSpaces(`MultiESDTNFTTransfer@0000000000000000050023a2823d0afcea4373192e362c285287a6bb10c47ceb + @02 + @5745474c442d376662623930@@016345785d8a0000 + @425553442d363631303635@@01511107c5a79fa8da + @6164644c6971756964697479@015fb7f9b8c38000@014db223dab7de101f`), + esdt1: { + type: 'esdtTransaction', + data: '5745474c442d376662623930@@016345785d8a0000', + receiver: + '0000000000000000050023a2823d0afcea4373192e362c285287a6bb10c47ceb', + token: 'WEGLD-7fbb90', + amount: '100000000000000000' + }, + esdt2: { + type: 'esdtTransaction', + data: '425553442d363631303635@@01511107c5a79fa8da', + receiver: + '0000000000000000050023a2823d0afcea4373192e362c285287a6bb10c47ceb', + token: 'BUSD-661065', + amount: '24288202810888005850' + }, + scCall: { + data: '6164644c6971756964697479@015fb7f9b8c38000@014db223dab7de101f', + receiver: + '0000000000000000050023a2823d0afcea4373192e362c285287a6bb10c47ceb', + type: 'scCall' + } +}; +const three = { + data: removeWhiteSpaces(`MultiESDTNFTTransfer@000000000000000005003a6b2c77e799c53499791de274ebee24558681aa10fb + @02 + @534649544c4547454e442d356461396464@0ead@01 + @534649542d616562633930@00@01e0524e2357d3c000 + @75706772616465@6d657461646174613a626166796265696168776b34323576763669647767736a356f74356c32716a737170656c67366c716a6779777733747563727135777a62766664712f333036302e6a736f6e@7618623408A6A242813A40E471F2CE707599F0C12DA0790DCBB6305A3993F5B65771FCCB4EE925772E348458AAD916504F806F97485490F59C27EF56236A4801`), + sft: { + amount: '1', + data: '534649544c4547454e442d356461396464@0ead@01', + nonce: '0ead', + receiver: + '000000000000000005003a6b2c77e799c53499791de274ebee24558681aa10fb', + token: 'SFITLEGEND-5da9dd', + type: 'nftTransaction' + }, + esdt: { + amount: '34610812000000000000', + data: '534649542d616562633930@00@01e0524e2357d3c000', + receiver: + '000000000000000005003a6b2c77e799c53499791de274ebee24558681aa10fb', + token: 'SFIT-aebc90', + type: 'esdtTransaction' + }, + scCall: { + data: '75706772616465@6d657461646174613a626166796265696168776b34323576763669647767736a356f74356c32716a737170656c67366c716a6779777733747563727135777a62766664712f333036302e6a736f6e@7618623408A6A242813A40E471F2CE707599F0C12DA0790DCBB6305A3993F5B65771FCCB4EE925772E348458AAD916504F806F97485490F59C27EF56236A4801', + receiver: + '000000000000000005003a6b2c77e799c53499791de274ebee24558681aa10fb', + type: 'scCall' + } +}; + +const four = { + data: removeWhiteSpaces(`MultiESDTNFTTransfer + @000000000000000005006704c51b25a956ddbc643189ba7945b413890d4f0fd6 + @02 + @444d452d626465326238@01@01 + @444d452d626465326238@01@01 + @6e6674446973747269627574696f6e + @ee62513ef30aede25b3366b6e3219ee18084026f36d6105299ee9963b1338f09 + @ee62513ef30aede25b3366b6e3219ee18084026f36d6105299ee9963b1338f09`), + nft: { + amount: '1', + data: '444d452d626465326238@01@01', + nonce: '01', + receiver: + '000000000000000005006704c51b25a956ddbc643189ba7945b413890d4f0fd6', + token: 'DME-bde2b8', + type: 'nftTransaction' + }, + ssCall: { + data: '6e6674446973747269627574696f6e@ee62513ef30aede25b3366b6e3219ee18084026f36d6105299ee9963b1338f09@ee62513ef30aede25b3366b6e3219ee18084026f36d6105299ee9963b1338f09', + receiver: + '000000000000000005006704c51b25a956ddbc643189ba7945b413890d4f0fd6', + type: 'scCall' + } +}; + +const five = { + data: removeWhiteSpaces(`MultiESDTNFTTransfer + @000000000000000005006945e6647033941911d0e8e3b85c876b6ec449db1679 + @55544b2d353463323862 + @ + @091d + @55544b4641524d2d653539363331 + @02 + @04b4a8335ecc4a780000 + @55544b4641524d2d653539363331 + @03 + @a968163f0a57b4000000 + @7374616b654661726d`) +}; + +describe('parseMultiEsdtTransferData tests', () => { + test('Interprets data with scCall', () => { + const response = [one.esdt, one.sft1, one.sft2, one.scCall]; + const result = parseMultiEsdtTransferData(one.data); + expect(result).toEqual(response); + }); + test('Interprets data without scCall', () => { + const response = [one.esdt, one.sft1, one.sft2]; + const result = parseMultiEsdtTransferData( + one.data.replace(one.scCall.data, '') + ); + expect(result).toEqual(response); + }); + test('Fails if one tx is missing', () => { + const result = parseMultiEsdtTransferData( + one.data.replace(one.sft2.data, '') + ); + expect(result).toEqual([]); + }); + test('Fails if format is wrong', () => { + const result = parseMultiEsdtTransferData( + 'MultiESDTNFTTransfer@0000000000000000050074b70610036a10129194f8474f2f63e49e3f20ce7ceb@03@' + ); + expect(result).toEqual([]); + }); + test('Interprets data with two ESDT transactions', () => { + const response = [two.esdt1, two.esdt2, two.scCall]; + const result = parseMultiEsdtTransferData(two.data); + expect(result).toEqual(response); + }); + test('Interprets data with two ESDT and NFT transactions', () => { + const result = parseMultiEsdtTransferData(three.data); + const response = [three.sft, three.esdt, three.scCall]; + expect(result).toEqual(response); + }); + test('Interprets data with two identical NFT transactions', () => { + const result = parseMultiEsdtTransferData(four.data); + const response = [four.nft, four.nft, four.ssCall]; + expect(result).toEqual(response); + }); + test('Returns empty array for invalid data', () => { + const result = parseMultiEsdtTransferData(five.data); + expect(result).toEqual([]); + }); +}); diff --git a/src/utils/transactions/tests/removeTransactionParamsFromUrl.test.ts b/src/utils/transactions/tests/removeTransactionParamsFromUrl.test.ts new file mode 100644 index 0000000..10e4050 --- /dev/null +++ b/src/utils/transactions/tests/removeTransactionParamsFromUrl.test.ts @@ -0,0 +1,43 @@ +import { removeTransactionParamsFromUrl } from '../url/removeTransactionParamsFromUrl'; + +describe('removeTransactionParamsFromUrl tests', () => { + test('removes all data from URL', () => { + const search = + '?nonce=1&value=123&receiver=erd123&sender=erd456&gasLimit=60000&gasPrice=60000&chainID=D'; + const transaction = { + value: '123', + data: 'test', + nonce: '1', + receiver: 'erd123', + sender: 'erd456', + gasLimit: '60000', + gasPrice: '60000', + chainID: 'D' + }; + + const result = removeTransactionParamsFromUrl({ transaction, search }); + + expect(result).toStrictEqual({}); + }); + test('removes only tx data from the URL ', () => { + const search = + '?testParam=123&searchParam=asd&nonce=1&value=123&receiver=erd123&sender=erd456&gasLimit=60000&gasPrice=60000&chainID=D'; + const transaction = { + value: '123', + data: 'test', + nonce: '1', + receiver: 'erd123', + sender: 'erd456', + gasLimit: '60000', + gasPrice: '60000', + chainID: 'D' + }; + + const result = removeTransactionParamsFromUrl({ transaction, search }); + + expect(result).toStrictEqual({ + testParam: '123', + searchParam: 'asd' + }); + }); +}); diff --git a/src/utils/transactions/transactionStateByStatus.ts b/src/utils/transactions/transactionStateByStatus.ts new file mode 100644 index 0000000..4397715 --- /dev/null +++ b/src/utils/transactions/transactionStateByStatus.ts @@ -0,0 +1,143 @@ +import { + TransactionBatchStatusesEnum, + TransactionServerStatusesEnum +} from 'types/enums.types'; + +export const pendingBatchTransactionsStates = [ + TransactionBatchStatusesEnum.sent +]; + +export const successBatchTransactionsStates = [ + TransactionBatchStatusesEnum.success +]; + +export const failBatchTransactionsStates = [ + TransactionBatchStatusesEnum.fail, + TransactionBatchStatusesEnum.cancelled, + TransactionBatchStatusesEnum.timedOut +]; + +export const invalidBatchTransactionsStates = [ + TransactionBatchStatusesEnum.invalid +]; + +export const timedOutBatchTransactionsStates = [ + TransactionBatchStatusesEnum.timedOut +]; + +export const pendingServerTransactionsStatuses = [ + TransactionServerStatusesEnum.pending +]; + +export const successServerTransactionsStates = [ + TransactionServerStatusesEnum.success +]; + +export const failServerTransactionsStates = [ + TransactionServerStatusesEnum.fail, + TransactionServerStatusesEnum.invalid +]; + +export const notExecutedServerTransactionsStates = [ + TransactionServerStatusesEnum.notExecuted +]; + +export function getIsTransactionPending( + status?: TransactionServerStatusesEnum | TransactionBatchStatusesEnum +) { + return ( + status != null && + (isBatchTransactionPending(status as TransactionBatchStatusesEnum) || + isServerTransactionPending(status as TransactionServerStatusesEnum)) + ); +} + +export function getIsTransactionSuccessful( + status?: TransactionServerStatusesEnum | TransactionBatchStatusesEnum +) { + return ( + status != null && + (isBatchTransactionSuccessful(status as TransactionBatchStatusesEnum) || + isServerTransactionSuccessful(status as TransactionServerStatusesEnum)) + ); +} + +export function getIsTransactionFailed( + status?: TransactionServerStatusesEnum | TransactionBatchStatusesEnum +) { + return ( + status != null && + (isBatchTransactionFailed(status as TransactionBatchStatusesEnum) || + isServerTransactionFailed(status as TransactionServerStatusesEnum)) + ); +} + +export function getIsTransactionNotExecuted( + status?: TransactionServerStatusesEnum | TransactionBatchStatusesEnum +) { + return ( + status != null && + (isBatchTransactionInvalid(status as TransactionBatchStatusesEnum) || + isServerTransactionNotExecuted(status as TransactionServerStatusesEnum)) + ); +} + +export function getIsTransactionTimedOut( + status?: TransactionServerStatusesEnum | TransactionBatchStatusesEnum +) { + return ( + status != null && + isBatchTransactionTimedOut(status as TransactionBatchStatusesEnum) + ); +} + +export function isBatchTransactionPending( + status?: TransactionBatchStatusesEnum +) { + return status != null && pendingBatchTransactionsStates.includes(status); +} + +export function isBatchTransactionSuccessful( + status: TransactionBatchStatusesEnum +) { + return status != null && successBatchTransactionsStates.includes(status); +} + +export function isBatchTransactionFailed(status: TransactionBatchStatusesEnum) { + return status != null && failBatchTransactionsStates.includes(status); +} + +export function isBatchTransactionInvalid( + status: TransactionBatchStatusesEnum +) { + return status != null && invalidBatchTransactionsStates.includes(status); +} + +export function isBatchTransactionTimedOut( + status?: TransactionBatchStatusesEnum +) { + return status != null && timedOutBatchTransactionsStates.includes(status); +} + +export function isServerTransactionPending( + status?: TransactionServerStatusesEnum +) { + return status != null && pendingServerTransactionsStatuses.includes(status); +} +export function isServerTransactionSuccessful( + status: TransactionServerStatusesEnum +) { + return status != null && successServerTransactionsStates.includes(status); +} + +export function isServerTransactionFailed( + status: TransactionServerStatusesEnum +) { + return status != null && failServerTransactionsStates.includes(status); +} + +export function isServerTransactionNotExecuted( + status: TransactionServerStatusesEnum +) { + return status != null && notExecutedServerTransactionsStates.includes(status); +} diff --git a/src/utils/transactions/url/buildCallbackUrl.ts b/src/utils/transactions/url/buildCallbackUrl.ts new file mode 100644 index 0000000..e90a5fe --- /dev/null +++ b/src/utils/transactions/url/buildCallbackUrl.ts @@ -0,0 +1,49 @@ +import { SDK_DAPP_VERSION } from 'constants'; + +function buildUrlParams( + search: string, + urlParams: { + [key: string]: string; + } +) { + const urlSearchParams = new URLSearchParams(search); + const params = Object.fromEntries(urlSearchParams as any); + + const nextUrlParams = new URLSearchParams({ + ...params, + ...urlParams + }).toString(); + + return { nextUrlParams, params }; +} + +const version = '__sdkDappVersion'; // will be replaced at build time + +export interface ReplyUrlType { + callbackUrl: string; + urlParams?: { [key: string]: string }; +} + +export function buildCallbackUrl({ + callbackUrl, + urlParams = {} +}: ReplyUrlType) { + let url = callbackUrl; + + if (Object.entries(urlParams).length > 0) { + try { + const { search, origin, pathname, hash } = new URL(callbackUrl); + const urlParamsWithVersion = { + ...urlParams, + [SDK_DAPP_VERSION]: version + }; + const { nextUrlParams } = buildUrlParams(search, urlParamsWithVersion); + url = `${origin}${pathname}?${nextUrlParams}${hash}`; + } catch (err) { + console.error('Unable to construct URL from: ', callbackUrl, err); + return url; + } + } + + return url; +} diff --git a/src/utils/transactions/url/removeTransactionParamsFromUrl.ts b/src/utils/transactions/url/removeTransactionParamsFromUrl.ts new file mode 100644 index 0000000..81ebd47 --- /dev/null +++ b/src/utils/transactions/url/removeTransactionParamsFromUrl.ts @@ -0,0 +1,23 @@ +import { WALLET_PROVIDER_CALLBACK_PARAM } from '@multiversx/sdk-web-wallet-provider'; +import { SDK_DAPP_VERSION, WALLET_SIGN_SESSION } from 'constants'; +import { removeSearchParamsFromUrl } from '../removeSearchParamsFromUrl'; + +interface RemoveTransactionParamsFromUrlParamsType { + transaction: any; + search?: string; +} + +export const removeTransactionParamsFromUrl = ({ + transaction, + search +}: RemoveTransactionParamsFromUrlParamsType) => { + return removeSearchParamsFromUrl({ + removeParams: [ + ...Object.keys(transaction), + WALLET_PROVIDER_CALLBACK_PARAM, + WALLET_SIGN_SESSION, + SDK_DAPP_VERSION + ], + search + }); +}; diff --git a/src/utils/window/clearNavigationHistory.ts b/src/utils/window/clearNavigationHistory.ts new file mode 100644 index 0000000..e6a1ab8 --- /dev/null +++ b/src/utils/window/clearNavigationHistory.ts @@ -0,0 +1,12 @@ +import { getWindowLocation } from './getWindowLocation'; + +export const clearNavigationHistory = (remainingParams: any) => { + const newUrlParams = new URLSearchParams(remainingParams).toString(); + const { pathname, hash } = getWindowLocation(); + const newSearch = newUrlParams ? `?${newUrlParams}` : ''; + const fullPath = pathname ? `${pathname}${newSearch}${hash}` : './'; + + setTimeout(() => { + window?.history.replaceState({}, document?.title, fullPath); + }); +}; diff --git a/src/utils/window/index.ts b/src/utils/window/index.ts index 698dd2e..dfead60 100644 --- a/src/utils/window/index.ts +++ b/src/utils/window/index.ts @@ -1,6 +1,9 @@ +export * from './buildUrlParams'; +export * from './clearNavigationHistory'; export * from './getDefaultCallbackUrl'; +export * from './getIsAuthRoute'; export * from './getWindowLocation'; export * from './isWindowAvailable'; +export * from './parseNavigationParams'; +export * from './removeSearchParamsFromUrl' export * from './sanitizeCallbackUrl'; -export * from './buildUrlParams'; -export * from './getIsAuthRoute'; diff --git a/src/utils/window/parseNavigationParams.ts b/src/utils/window/parseNavigationParams.ts new file mode 100644 index 0000000..6ec47e0 --- /dev/null +++ b/src/utils/window/parseNavigationParams.ts @@ -0,0 +1,57 @@ +import { clearNavigationHistory } from './clearNavigationHistory'; +import { isWindowAvailable } from './isWindowAvailable'; + +interface ParseNavigationParamsOptionsType { + search?: string; + removeParams?: string[]; +} + +const defaultOptions: ParseNavigationParamsOptionsType = { + search: isWindowAvailable() ? window.location.search : '', + removeParams: [] +}; + +/** + * @param preserveParams allows extracting params from URL search object + * @param options.removeParams allows removing params from URL search object + * @returns the selected params, search object with removed params, and the `clearNavigationHistory` helper + */ +export const parseNavigationParams = ( + preserveParams: string[], + options = defaultOptions +) => { + let params: Record = {}; + const defaultSearch = isWindowAvailable() ? window.location.search : ''; + const search = options.search ?? defaultSearch; + + if (search) { + const urlSearchParams = search ? new URLSearchParams(search) : []; + params = Object.fromEntries(urlSearchParams); + } + + const remainingParams: Record = {}; + + preserveParams.forEach((key) => { + remainingParams[key] = params[key]; + delete params[key]; + }); + + if (options.removeParams != null) { + Object.keys(params).forEach((entry) => { + // param is of type a=1 or a[1]=1 + const [arrayEntry] = entry.split('['); + if ( + options.removeParams?.includes(entry) || + options.removeParams?.includes(arrayEntry) + ) { + delete params[entry]; + } + }); + } + + return { + remainingParams, + params, + clearNavigationHistory: () => clearNavigationHistory(params) + }; +}; diff --git a/src/utils/window/removeSearchParamsFromUrl.ts b/src/utils/window/removeSearchParamsFromUrl.ts new file mode 100644 index 0000000..5240867 --- /dev/null +++ b/src/utils/window/removeSearchParamsFromUrl.ts @@ -0,0 +1,36 @@ +import qs from 'qs'; +import { clearNavigationHistory } from './clearNavigationHistory'; +import { isWindowAvailable } from './isWindowAvailable'; +import { parseNavigationParams } from './parseNavigationParams'; + +interface RemoveSearchParamsFromUrlParamsType { + removeParams: string[]; + search?: string; +} + +export const removeSearchParamsFromUrl = ({ + removeParams, + search +}: RemoveSearchParamsFromUrlParamsType) => { + const windowSearch = isWindowAvailable() ? window.location.search : ''; + const defaultSearch = search ?? windowSearch; + + if (!defaultSearch) { + return {}; + } + + const searchData = qs.parse(defaultSearch.replace('?', '')); + + const preserveParams = Object.keys(searchData).filter( + (key) => !removeParams.includes(key) + ); + + const { remainingParams } = parseNavigationParams(preserveParams, { + search, + removeParams + }); + + clearNavigationHistory(remainingParams); + + return remainingParams; +}; diff --git a/src/utils/window/tests/clearNavigationHistory.test.ts b/src/utils/window/tests/clearNavigationHistory.test.ts new file mode 100644 index 0000000..e585e71 --- /dev/null +++ b/src/utils/window/tests/clearNavigationHistory.test.ts @@ -0,0 +1,75 @@ +import { clearNavigationHistory } from '../clearNavigationHistory'; +import { getWindowLocation } from '../getWindowLocation'; + +jest.mock('../getWindowLocation'); + +describe('clearNavigationHistory', () => { + beforeEach(() => { + jest.useFakeTimers(); + jest.spyOn(window.history, 'replaceState').mockImplementation(); + (getWindowLocation as jest.Mock).mockReturnValue({ + pathname: '/test', + hash: '#section1', + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + test('should update history with new URL params', () => { + const params = { page: '1', sort: 'desc' }; + clearNavigationHistory(params); + + jest.runAllTimers(); + + expect(window.history.replaceState).toHaveBeenCalledWith( + {}, + document.title, + '/test?page=1&sort=desc#section1' + ); + }); + + test('should handle empty params', () => { + clearNavigationHistory({}); + + jest.runAllTimers(); + + expect(window.history.replaceState).toHaveBeenCalledWith( + {}, + document.title, + '/test#section1' + ); + }); + + test('should use default path when pathname is empty', () => { + (getWindowLocation as jest.Mock).mockReturnValue({ + pathname: '', + hash: '', + }); + + clearNavigationHistory({}); + + jest.runAllTimers(); + + expect(window.history.replaceState).toHaveBeenCalledWith( + {}, + document.title, + './' + ); + }); + + test('should handle hash fragments', () => { + const params = { filter: 'active' }; + clearNavigationHistory(params); + + jest.runAllTimers(); + + expect(window.history.replaceState).toHaveBeenCalledWith( + {}, + document.title, + '/test?filter=active#section1' + ); + }); +}); \ No newline at end of file diff --git a/src/utils/window/tests/parseNavigationParams.test.ts b/src/utils/window/tests/parseNavigationParams.test.ts new file mode 100644 index 0000000..d46d959 --- /dev/null +++ b/src/utils/window/tests/parseNavigationParams.test.ts @@ -0,0 +1,50 @@ +import { testAddress } from '__mocks__'; +import { parseNavigationParams } from '../parseNavigationParams'; + +describe('parseNavigationParams tests', () => { + it('Returns remaining params without signature', () => { + const search = `?page=1&address=${testAddress}&signature=ff5b42b73bbdfca2103cf69861493b42a5b55ebb8d3ab9dab2909e0e985064a385863e802cffb18e6b523c8b7f8ee2ed883d7d4a3c35b47cb36d195a1130b602`; + + const keepParams = ['signature']; + const { params, remainingParams } = parseNavigationParams(keepParams, { + removeParams: ['signature'], + search + }); + expect(params).toStrictEqual({ + address: 'erd1dm9uxpf5awkn7uhju7zjn9lde0dhahy0qaxqqlu26xcuuw27qqrsqfmej3', + page: '1' + }); + expect(remainingParams).toStrictEqual({ + signature: + 'ff5b42b73bbdfca2103cf69861493b42a5b55ebb8d3ab9dab2909e0e985064a385863e802cffb18e6b523c8b7f8ee2ed883d7d4a3c35b47cb36d195a1130b602' + }); + }); + + it('Keeps original query params for multiple transactions', () => { + const search = + '?a=1&signSession=1678975873953&nonce[0]=368&nonce[1]=369&value[0]=0&value[1]=0&receiver[0]=erd1qqqqqqqqqqqqqpgqlgjwk8mpfycxpdf9q2sgzcndtdhdxr5ss0vqgygjmn&receiver[1]=erd1qqqqqqqqqqqqqpgqkhx64czgpc84eftg6q25lrfyv95ndwtcs0vqwusfuh&sender[0]=erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv&sender[1]=erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv&gasPrice[0]=1000000000&gasPrice[1]=1000000000&gasLimit[0]=5000000&gasLimit[1]=50000000&data[0]=ESDTTransfer%40555344432d663839646665%4005f5e100%40756e77726170546f6b656e%40455448555344432d656366663464&data[1]=ESDTTransfer%40455448555344432d656366663464%4005f5e100%406372656174655472616e73616374696f6e%40195f0b7d9ab15bbffdcd91b39db618bcee16edd0&chainID[0]=T&chainID[1]=T&version[0]=1&version[1]=1&signature[0]=043db9528587020e613d9e2f0d5bfee6b3477ad99d7aeee5e307244f517902d3374cdc8942dcc68aca564068219d58429d676fcf4ddf62d8fa518c07d0d3a102&signature[1]=a2d8c21d77b650e5618ceb4d3cf56e9224f1b8dd941f4eae71641940bf31341d323b7e8da0a7be533eb7bbb426099a16a24b95a78d24a28d5143cb9b31c57c05&walletProviderStatus=transactionsSigned'; + + const { params } = parseNavigationParams([], { + removeParams: [ + 'nonce', + 'value', + 'receiver', + 'sender', + 'signature', + 'gasPrice', + 'gasLimit', + 'chainID', + 'data', + 'version', + 'options', + // other + 'walletProviderStatus', + 'signSession' + ], + search + }); + expect(params).toStrictEqual({ + a: '1' + }); + }); +}); diff --git a/src/utils/window/tests/removeSearchParamsFromUrl.test.ts b/src/utils/window/tests/removeSearchParamsFromUrl.test.ts new file mode 100644 index 0000000..7ea5f2e --- /dev/null +++ b/src/utils/window/tests/removeSearchParamsFromUrl.test.ts @@ -0,0 +1,42 @@ +import { removeSearchParamsFromUrl } from '../removeSearchParamsFromUrl'; + +describe('removeSearchParamsFromUrl tests', () => { + it('removes all params from URL', () => { + const search = '?signature=123&sessionId=asd&status=1'; + const removeParams = ['signature', 'sessionId', 'status']; + + const result = removeSearchParamsFromUrl({ removeParams, search }); + + expect(result).toStrictEqual({}); + }); + + it('removes the provided data from URL', () => { + const search = '?signature=123&sessionId=asd&status=1'; + const removeParams = ['signature', 'sessionId']; + + const result = removeSearchParamsFromUrl({ removeParams, search }); + + expect(result).toStrictEqual({ + status: '1' + }); + }); + + it('preserves the URL when no removeData is provided', () => { + const search = '?testParam=123&searchParam=asd'; + const result = removeSearchParamsFromUrl({ removeParams: [], search }); + + expect(result).toStrictEqual({ + testParam: '123', + searchParam: 'asd' + }); + }); + + it('does nothing when search is missing', () => { + const result = removeSearchParamsFromUrl({ + removeParams: ['test'], + search: '' + }); + + expect(result).toStrictEqual({}); + }); +}); From 4e491ddf2d3d90e06daa12dc8ba30b55675831df Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Mon, 2 Dec 2024 17:51:17 +0200 Subject: [PATCH 02/23] Added store and tests --- src/constants/transactions.constants.ts | 16 +- src/store/selectors/transactionsSelectors.ts | 91 ++++ src/store/slices/index.ts | 5 +- src/store/slices/transactionsSlice/index.ts | 2 + .../transactionsSlice/transactionsSlice.ts | 22 + .../transactionsSlice.types.ts | 51 ++ src/types/enums.types.ts | 81 +++ src/types/index.ts | 5 +- src/types/serverTransactions.types.ts | 341 ------------- src/types/transactions.types.ts | 472 ++++++++++++++---- .../generateBatchTransactionsGrouping.test.ts | 74 --- .../generateBatchTransactionsGrouping.ts | 7 +- .../transactions/batch/getIsSequential.ts | 6 +- .../batch/getTransactionsStatus.ts | 6 +- .../batch/sequentialToFlatArray.ts | 9 +- .../generateBatchTransactionsGrouping.test.ts | 26 + .../batch/tests/getIsSequential.test.ts | 23 + .../batch/tests/getTransactionsStatus.test.ts | 103 ++++ .../batch/tests/sequentialToFlatArray.test.ts | 40 ++ .../batch/updateBatchTransactionsStatuses.ts | 19 +- .../getScResultsInitialDecodeMethod.ts | 2 +- .../dataDecoders/getTokenFromData.ts | 5 +- src/utils/transactions/index.ts | 3 +- .../{isGuardianTx.tsx => isGuardianTx.ts} | 2 +- src/utils/transactions/newTransaction.ts | 50 ++ src/utils/transactions/url/index.ts | 2 + 26 files changed, 912 insertions(+), 551 deletions(-) create mode 100644 src/store/selectors/transactionsSelectors.ts create mode 100644 src/store/slices/transactionsSlice/index.ts create mode 100644 src/store/slices/transactionsSlice/transactionsSlice.ts create mode 100644 src/store/slices/transactionsSlice/transactionsSlice.types.ts delete mode 100644 src/types/serverTransactions.types.ts delete mode 100644 src/utils/transactions/batch/generateBatchTransactionsGrouping.test.ts create mode 100644 src/utils/transactions/batch/tests/generateBatchTransactionsGrouping.test.ts create mode 100644 src/utils/transactions/batch/tests/getIsSequential.test.ts create mode 100644 src/utils/transactions/batch/tests/getTransactionsStatus.test.ts create mode 100644 src/utils/transactions/batch/tests/sequentialToFlatArray.test.ts rename src/utils/transactions/{isGuardianTx.tsx => isGuardianTx.ts} (86%) create mode 100644 src/utils/transactions/newTransaction.ts create mode 100644 src/utils/transactions/url/index.ts diff --git a/src/constants/transactions.constants.ts b/src/constants/transactions.constants.ts index beb6deb..b983a0b 100644 --- a/src/constants/transactions.constants.ts +++ b/src/constants/transactions.constants.ts @@ -1,2 +1,16 @@ export const WALLET_SIGN_SESSION = 'signSession'; -export const SDK_DAPP_VERSION = 'sdk-dapp-version'; \ No newline at end of file +export const SDK_DAPP_VERSION = 'sdk-dapp-version'; +export const CANCEL_TRANSACTION_TOAST_ID = 'cancel-transaction-toast'; +export const AVERAGE_TX_DURATION_MS = 6000; +export const CROSS_SHARD_ROUNDS = 5; +export const TRANSACTIONS_STATUS_POLLING_INTERVAL_MS = 90 * 1000; // 90sec +export const TRANSACTIONS_STATUS_DROP_INTERVAL_MS = 10 * 60 * 1000; // 10min +export const CANCEL_TRANSACTION_TOAST_DEFAULT_DURATION = 20000; +export const GAS_PRICE_MODIFIER = 0.01; +export const GAS_PER_DATA_BYTE = 1_500; +export const GAS_LIMIT = 50_000; +export const EXTRA_GAS_LIMIT_GUARDED_TX = 50_000; +export const GAS_PRICE = 1_000_000_000; +export const DECIMALS = 18; +export const DIGITS = 4; +export const VERSION = 1; diff --git a/src/store/selectors/transactionsSelectors.ts b/src/store/selectors/transactionsSelectors.ts new file mode 100644 index 0000000..1a0103a --- /dev/null +++ b/src/store/selectors/transactionsSelectors.ts @@ -0,0 +1,91 @@ +import { Transaction } from '@multiversx/sdk-core'; + +import { StoreType } from 'store/store.types'; +import { + getIsTransactionFailed, + getIsTransactionPending, + getIsTransactionSuccessful, + getIsTransactionTimedOut +} from 'utils/transactions'; +import { + CustomTransactionInformation, + RawTransactionType, + SignedTransactionsType +} from '../../types'; + +export const accountSelector = ({ + transactions: { accounts, address } +}: StoreType) => accounts[address]; + +export const signedTransactionsSelector = createDeepEqualSelector( + transactionsSelectors, + (state) => state.signedTransactions as SignedTransactionsType +); + +export const signTransactionsErrorSelector = createDeepEqualSelector( + transactionsSelectors, + (state) => state.signTransactionsError +); + +export const signTransactionsCancelMessageSelector = createDeepEqualSelector( + transactionsSelectors, + (state) => state.signTransactionsCancelMessage +); + +const selectTxByStatus = + (txStatusVerifier: typeof getIsTransactionPending) => + (signedTransactions: SignedTransactionsType) => + Object.entries(signedTransactions).reduce((acc, [sessionId, txBody]) => { + if (txStatusVerifier(txBody.status)) { + acc[sessionId] = txBody; + } + return acc; + }, {} as SignedTransactionsType); + +export const pendingSignedTransactionsSelector = createDeepEqualSelector( + signedTransactionsSelector, + selectTxByStatus(getIsTransactionPending) +); + +export const successfulTransactionsSelector = createDeepEqualSelector( + signedTransactionsSelector, + selectTxByStatus(getIsTransactionSuccessful) +); + +export const failedTransactionsSelector = createDeepEqualSelector( + signedTransactionsSelector, + selectTxByStatus(getIsTransactionFailed) +); + +export const timedOutTransactionsSelector = createDeepEqualSelector( + signedTransactionsSelector, + selectTxByStatus(getIsTransactionTimedOut) +); + +export const transactionsToSignSelector = createDeepEqualSelector( + transactionsSelectors, + (state): TransactionsToSignReturnType | null => { + if (state?.transactionsToSign == null) { + return null; + } + return { + ...state.transactionsToSign, + transactions: + state?.transactionsToSign?.transactions.map((tx: RawTransactionType) => + newTransaction(tx) + ) || [] + }; + } +); + +export const transactionStatusSelector = createDeepEqualSelector( + signedTransactionsSelector, + (_: RootState, transactionSessionId: string | null) => transactionSessionId, + ( + signedTransactions: SignedTransactionsType, + transactionSessionId: string | null + ) => + transactionSessionId != null + ? signedTransactions?.[transactionSessionId] || {} + : {} +); diff --git a/src/store/slices/index.ts b/src/store/slices/index.ts index 80d5a64..4eecf92 100644 --- a/src/store/slices/index.ts +++ b/src/store/slices/index.ts @@ -1,4 +1,5 @@ export * from './account'; -export * from './network'; -export * from './loginInfo'; export * from './config'; +export * from './loginInfo'; +export * from './network'; +export * from './transactionsSlice'; diff --git a/src/store/slices/transactionsSlice/index.ts b/src/store/slices/transactionsSlice/index.ts new file mode 100644 index 0000000..7b69550 --- /dev/null +++ b/src/store/slices/transactionsSlice/index.ts @@ -0,0 +1,2 @@ +export * from './transactionsSlice.types'; +export * from './transactionsSlice'; diff --git a/src/store/slices/transactionsSlice/transactionsSlice.ts b/src/store/slices/transactionsSlice/transactionsSlice.ts new file mode 100644 index 0000000..0edf994 --- /dev/null +++ b/src/store/slices/transactionsSlice/transactionsSlice.ts @@ -0,0 +1,22 @@ +import { TransactionsSliceStateType } from './transactionsSlice.types'; +import { StateCreator } from 'zustand/vanilla'; +import { MutatorsIn, StoreType } from 'store/store.types'; + +export const initialState: TransactionsSliceStateType = { + signedTransactions: {}, + transactionsToSign: null, + signTransactionsError: null, + signTransactionsCancelMessage: null, + customTransactionInformationForSessionId: {} +}; + +function getTransactionsSlice(): StateCreator< + StoreType, + MutatorsIn, + [], + TransactionsSliceStateType +> { + return () => initialState; +} + +export const transactionsSlice = getTransactionsSlice(); diff --git a/src/store/slices/transactionsSlice/transactionsSlice.types.ts b/src/store/slices/transactionsSlice/transactionsSlice.types.ts new file mode 100644 index 0000000..9d4e5e8 --- /dev/null +++ b/src/store/slices/transactionsSlice/transactionsSlice.types.ts @@ -0,0 +1,51 @@ +import { + CustomTransactionInformation, + ServerTransactionType, + SignedTransactionsBodyType, + SignedTransactionsType, + SignedTransactionType, + TransactionBatchStatusesEnum, + TransactionServerStatusesEnum, + TransactionsToSignType +} from 'types'; +import { Transaction } from '@multiversx/sdk-core/out'; + +export interface UpdateSignedTransactionsPayloadType { + sessionId: string; + status: TransactionBatchStatusesEnum; + errorMessage?: string; + transactions?: SignedTransactionType[]; + customTransactionInformationOverrides?: Partial; +} + +export interface MoveTransactionsToSignedStatePayloadType + extends SignedTransactionsBodyType { + sessionId: string; + customTransactionInformation?: CustomTransactionInformation; +} + +export interface UpdateSignedTransactionStatusPayloadType { + sessionId: string; + transactionHash: string; + status: TransactionServerStatusesEnum; + serverTransaction?: ServerTransactionType; + errorMessage?: string; + inTransit?: boolean; +} + +export interface TransactionsSliceStateType { + signedTransactions: SignedTransactionsType; + transactionsToSign: TransactionsToSignType | null; + signTransactionsError: string | null; + signTransactionsCancelMessage: string | null; + customTransactionInformationForSessionId: { + [sessionId: string]: CustomTransactionInformation; + }; +} + +export interface TransactionsToSignReturnType { + callbackRoute?: string; + sessionId: string; + transactions: Transaction[]; + customTransactionInformation: CustomTransactionInformation; +} diff --git a/src/types/enums.types.ts b/src/types/enums.types.ts index 4af22bd..94a12a4 100644 --- a/src/types/enums.types.ts +++ b/src/types/enums.types.ts @@ -22,3 +22,84 @@ export enum ESDTTransferTypesEnum { ESDTWipe = 'ESDTWipe', ESDTFreeze = 'ESDTFreeze' } + +export enum TransactionServerStatusesEnum { + pending = 'pending', + fail = 'fail', + invalid = 'invalid', + success = 'success', + executed = 'executed', + notExecuted = 'not executed', + rewardReverted = 'reward-reverted' +} + +export enum SignedMessageStatusesEnum { + pending = 'pending', + failed = 'failed', + signed = 'signed', + cancelled = 'cancelled' +} + +export enum TransactionBatchStatusesEnum { + signed = 'signed', + cancelled = 'cancelled', + success = 'success', + sent = 'sent', + fail = 'fail', + timedOut = 'timedOut', + invalid = 'invalid' +} + +export enum LoginMethodsEnum { + ledger = 'ledger', + walletconnect = 'walletconnect', + walletconnectv2 = 'walletconnectv2', + wallet = 'wallet', + crossWindow = 'crossWindow', + iframe = 'iframe', + extension = 'extension', + passkey = 'passkey', + metamask = 'metamask', + opera = 'opera', + extra = 'extra', + none = '' +} + +export enum NotificationTypesEnum { + warning = 'warning', + error = 'error', + success = 'success' +} + +export enum TransactionTypesEnum { + MultiESDTNFTTransfer = 'MultiESDTNFTTransfer', + ESDTTransfer = 'ESDTTransfer', + ESDTNFTBurn = 'ESDTNFTBurn', + ESDTNFTTransfer = 'ESDTNFTTransfer', + esdtTransaction = 'esdtTransaction', + nftTransaction = 'nftTransaction', + scCall = 'scCall' +} + +export enum TransactionsDefaultTitles { + success = 'Transaction successful', + received = 'Transaction received', + failed = 'Transaction failed', + pending = 'Processing transaction', + timedOut = 'Transaction timed out', + // Appears in batch transactions when the batch status is invalid (set the batch status to invalid for each transaction) + invalid = 'Transaction invalid' +} + +export enum PlatformsEnum { + ios = 'ios', + reactNative = 'reactNative', + web = 'web', + webWallet = 'webWallet' +} + +export enum GuardianActionsEnum { + SetGuardian = 'SetGuardian', + GuardAccount = 'GuardAccount', + UnGuardAccount = 'UnGuardAccount' +} diff --git a/src/types/index.ts b/src/types/index.ts index 9d0a1f8..da399ce 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,6 @@ export * from './enums.types'; +export * from './login.types'; +export * from './misc.types'; export * from './network.types'; -export * from './misc.types'; \ No newline at end of file +export * from './tokens.types' +export * from './transactions.types'; \ No newline at end of file diff --git a/src/types/serverTransactions.types.ts b/src/types/serverTransactions.types.ts deleted file mode 100644 index 1e98f35..0000000 --- a/src/types/serverTransactions.types.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { AssetType, ScamInfoType } from './account.types'; -import { EsdtEnumType, NftEnumType } from './tokens.types'; -import { SignedTransactionType } from './transactions.types'; - -export interface ScResultType { - callType: string; - data?: string; - gasLimit: number; - gasPrice: number; - hash: string; - nonce: number; - originalTxHash: string; - prevTxHash: string; - receiver: string; - returnMessage?: string; - sender: string; - timestamp: number; - value: string; -} - -export interface TransactionTokensType { - esdts: string[]; - nfts: string[]; -} - -export enum TransactionActionsEnum { - addLiquidity = 'addLiquidity', - addLiquidityProxy = 'addLiquidityProxy', - claimDualYield = 'claimDualYield', - claimDualYieldProxy = 'claimDualYieldProxy', - claimLockedAssets = 'claimLockedAssets', - claimRewards = 'claimRewards', - claimRewardsProxy = 'claimRewardsProxy', - compoundRewards = 'compoundRewards', - compoundRewardsProxy = 'compoundRewardsProxy', - delegate = 'delegate', - enterFarm = 'enterFarm', - enterFarmAndLockRewards = 'enterFarmAndLockRewards', - enterFarmAndLockRewardsProxy = 'enterFarmAndLockRewardsProxy', - enterFarmProxy = 'enterFarmProxy', - exitFarm = 'exitFarm', - exitFarmProxy = 'exitFarmProxy', - lockTokens = 'lockTokens', - mergeLockedAssetTokens = 'mergeLockedAssetTokens', - migrateOldTokens = 'migrateOldTokens', - ping = 'ping', - reDelegateRewards = 'reDelegateRewards', - removeLiquidity = 'removeLiquidity', - removeLiquidityProxy = 'removeLiquidityProxy', - stake = 'stake', - stakeClaimRewards = 'claimRewards', - stakeFarm = 'stakeFarm', - stakeFarmProxy = 'stakeFarmProxy', - stakeFarmTokens = 'stakeFarmTokens', - stakeFarmTokensProxy = 'stakeFarmTokensProxy', - swap = 'swap', - swapTokensFixedInput = 'swapTokensFixedInput', - swapTokensFixedOutput = 'swapTokensFixedOutput', - transfer = 'transfer', - unBond = 'unBond', - unDelegate = 'unDelegate', - unStake = 'unStake', - unbondFarm = 'unbondFarm', - unlockAssets = 'unlockAssets', - unstakeFarm = 'unstakeFarm', - unstakeFarmProxy = 'unstakeFarmProxy', - unstakeFarmTokens = 'unstakeFarmTokens', - unstakeFarmTokensProxy = 'unstakeFarmTokensProxy', - unwrapEgld = 'unwrapEgld', - withdraw = 'withdraw', - wrapEgld = 'wrapEgld', -} - -export enum TransactionActionCategoryEnum { - esdtNft = 'esdtNft', - mex = 'mex', - stake = 'stake', - scCall = 'scCall' -} - -export interface TokenArgumentType { - type: NftEnumType | EsdtEnumType; - name: string; - ticker: string; - collection?: string; - identifier?: string; - token?: string; - decimals: number; - value: string; - providerName?: string; - providerAvatar?: string; - svgUrl?: string; - valueUSD?: string; -} - -export interface TransactionActionType { - category: string; - name: TransactionActionsEnum; - description?: string; - arguments?: { [key: string]: any }; -} - -export interface UnwrapperType { - token?: TokenArgumentType[]; - tokenNoValue?: TokenArgumentType[]; - tokenNoLink?: TokenArgumentType[]; - address?: string; - egldValue?: string; - value?: string; - providerName?: string; - providerAvatar?: string; -} - -export enum TransactionOperationActionTypeEnum { - none = 'none', - transfer = 'transfer', - burn = 'burn', - addQuantity = 'addQuantity', - create = 'create', - multiTransfer = 'multiTransfer', - localMint = 'localMint', - localBurn = 'localBurn', - wipe = 'wipe', - freeze = 'freeze', - writeLog = 'writeLog', - signalError = 'signalError', - - // to be deprecated ? - ESDTLocalMint = 'ESDTLocalMint', - ESDTLocalBurn = 'ESDTLocalBurn' -} - -export enum VisibleTransactionOperationType { - nft = 'nft', - esdt = 'esdt', - egld = 'egld' -} -export enum HiddenTransactionOperationType { - none = 'none', - error = 'error', - log = 'log' -} - -export interface OperationType { - id?: string; - action: TransactionOperationActionTypeEnum; - type: VisibleTransactionOperationType | HiddenTransactionOperationType; - esdtType?: NftEnumType | EsdtEnumType; - collection?: string; - name?: string; - identifier?: string; - sender: string; - ticker?: string; - receiver: string; - value: string; - decimals?: number; - data?: string; - message?: string; - svgUrl?: string; - senderAssets?: AssetType; - receiverAssets?: AssetType; - valueUSD?: string; -} - -export interface LogType { - hash: string; - callType: string; - gasLimit: number; - gasPrice: number; - nonce: number; - prevTxHash: string; - receiver?: string; - sender: string; - value: string; - data?: string; - originalTxHash: string; - returnMessage?: string; - logs?: any; -} - -export interface EventType { - address: string; - identifier: string; - topics: string[]; - order: number; - data?: string; - additionalData?: string[]; -} - -export interface ResultLogType { - id: string; - address: string; - events: EventType[]; -} - -export interface ResultType { - hash: string; - callType: string; - gasLimit: number; - gasPrice: number; - nonce: number; - prevTxHash: string; - receiver?: string; - sender: string; - value: string; - data?: string; - originalTxHash: string; - returnMessage?: string; - logs?: ResultLogType; - senderAssets?: AssetType; - receiverAssets?: AssetType; - miniBlockHash?: string; - function?: string; - timestamp?: number; -} - -export interface ReceiptType { - value: string; - sender: string; - data: string; -} - -export interface ServerTransactionType { - fee?: string; - data: string; - gasLimit: number; - gasPrice: number; - gasUsed: number; - txHash: string; - miniBlockHash: string; - nonce: number; - receiver: string; - receiverShard: number; - round: number; - sender: string; - senderShard: number; - signature: string; - status: string; - inTransit?: boolean; - timestamp: number; - value: string; - price: number; - results?: ResultType[]; - operations?: OperationType[]; - action?: TransactionActionType; - logs?: { - id: string; - address: string; - events: EventType[]; - }; - scamInfo?: ScamInfoType; - pendingResults?: boolean; - receipt?: ReceiptType; - senderAssets?: AssetType; - receiverAssets?: AssetType; - type?: TransferTypeEnum; - originalTxHash?: string; - isNew?: boolean; // UI flag - tokenValue?: string; - tokenIdentifier?: string; - function?: string; -} - -export enum TransferTypeEnum { - Transaction = 'Transaction', - SmartContractResult = 'SmartContractResult' -} - -//#endregion - -//#region interpreted trasactions - -export enum TransactionDirectionEnum { - SELF = 'Self', - INTERNAL = 'Internal', - IN = 'In', - OUT = 'Out' -} - -export interface InterpretedTransactionType extends ServerTransactionType { - transactionDetails: { - direction?: TransactionDirectionEnum; - method: string; - transactionTokens: TokenArgumentType[]; - isContract?: boolean; - }; - links: { - senderLink?: string; - receiverLink?: string; - senderShardLink?: string; - receiverShardLink?: string; - transactionLink?: string; - }; -} - -export interface DecodeForDisplayPropsType { - input: string; - decodeMethod: DecodeMethodEnum; - identifier?: string; -} - -export interface DecodedDisplayType { - displayValue: string; - validationWarnings: string[]; -} - -export enum DecodeMethodEnum { - raw = 'raw', - text = 'text', - decimal = 'decimal', - smart = 'smart' -} - -//#endregion - -export enum BatchTransactionStatus { - pending = 'pending', - success = 'success', - invalid = 'invalid', - dropped = 'dropped', - fail = 'fail' -} - -export interface BatchTransactionsRequestType { - id: string; - transactions: SignedTransactionType[][]; -} - -export interface BatchTransactionsResponseType { - id: string; - status: BatchTransactionStatus; - transactions: SignedTransactionType[][]; - error?: string; - message?: string; - statusCode?: string; -} - -export type BatchTransactionsWSResponseType = { - batchId: string; - txHashes: string[]; -}; diff --git a/src/types/transactions.types.ts b/src/types/transactions.types.ts index 2bba39b..ec1e37b 100644 --- a/src/types/transactions.types.ts +++ b/src/types/transactions.types.ts @@ -1,15 +1,352 @@ -import { ReactNode, Dispatch, SetStateAction } from 'react'; import { Address, Transaction } from '@multiversx/sdk-core'; import { IPlainTransactionObject } from '@multiversx/sdk-core/out/interface'; -import { SignStepInnerClassesType } from '../UI/SignTransactionsModals/SignWithDeviceModal/SignStep'; -import { WithClassnameType } from '../UI/types'; +import { AssetType, ScamInfoType } from './account.types'; +import { EsdtEnumType, NftEnumType } from './tokens.types'; import { TransactionBatchStatusesEnum, TransactionServerStatusesEnum, TransactionTypesEnum } from './enums.types'; +export interface ScResultType { + callType: string; + data?: string; + gasLimit: number; + gasPrice: number; + hash: string; + nonce: number; + originalTxHash: string; + prevTxHash: string; + receiver: string; + returnMessage?: string; + sender: string; + timestamp: number; + value: string; +} + +export interface TransactionTokensType { + esdts: string[]; + nfts: string[]; +} + +export enum TransactionActionsEnum { + addLiquidity = 'addLiquidity', + addLiquidityProxy = 'addLiquidityProxy', + claimDualYield = 'claimDualYield', + claimDualYieldProxy = 'claimDualYieldProxy', + claimLockedAssets = 'claimLockedAssets', + claimRewards = 'claimRewards', + claimRewardsProxy = 'claimRewardsProxy', + compoundRewards = 'compoundRewards', + compoundRewardsProxy = 'compoundRewardsProxy', + delegate = 'delegate', + enterFarm = 'enterFarm', + enterFarmAndLockRewards = 'enterFarmAndLockRewards', + enterFarmAndLockRewardsProxy = 'enterFarmAndLockRewardsProxy', + enterFarmProxy = 'enterFarmProxy', + exitFarm = 'exitFarm', + exitFarmProxy = 'exitFarmProxy', + lockTokens = 'lockTokens', + mergeLockedAssetTokens = 'mergeLockedAssetTokens', + migrateOldTokens = 'migrateOldTokens', + ping = 'ping', + reDelegateRewards = 'reDelegateRewards', + removeLiquidity = 'removeLiquidity', + removeLiquidityProxy = 'removeLiquidityProxy', + stake = 'stake', + stakeClaimRewards = 'claimRewards', + stakeFarm = 'stakeFarm', + stakeFarmProxy = 'stakeFarmProxy', + stakeFarmTokens = 'stakeFarmTokens', + stakeFarmTokensProxy = 'stakeFarmTokensProxy', + swap = 'swap', + swapTokensFixedInput = 'swapTokensFixedInput', + swapTokensFixedOutput = 'swapTokensFixedOutput', + transfer = 'transfer', + unBond = 'unBond', + unDelegate = 'unDelegate', + unStake = 'unStake', + unbondFarm = 'unbondFarm', + unlockAssets = 'unlockAssets', + unstakeFarm = 'unstakeFarm', + unstakeFarmProxy = 'unstakeFarmProxy', + unstakeFarmTokens = 'unstakeFarmTokens', + unstakeFarmTokensProxy = 'unstakeFarmTokensProxy', + unwrapEgld = 'unwrapEgld', + withdraw = 'withdraw', + wrapEgld = 'wrapEgld' +} + +export enum TransactionActionCategoryEnum { + esdtNft = 'esdtNft', + mex = 'mex', + stake = 'stake', + scCall = 'scCall' +} + +export interface TokenArgumentType { + type: NftEnumType | EsdtEnumType; + name: string; + ticker: string; + collection?: string; + identifier?: string; + token?: string; + decimals: number; + value: string; + providerName?: string; + providerAvatar?: string; + svgUrl?: string; + valueUSD?: string; +} + +export interface TransactionActionType { + category: string; + name: TransactionActionsEnum; + description?: string; + arguments?: { [key: string]: any }; +} + +export interface UnwrapperType { + token?: TokenArgumentType[]; + tokenNoValue?: TokenArgumentType[]; + tokenNoLink?: TokenArgumentType[]; + address?: string; + egldValue?: string; + value?: string; + providerName?: string; + providerAvatar?: string; +} + +export enum TransactionOperationActionTypeEnum { + none = 'none', + transfer = 'transfer', + burn = 'burn', + addQuantity = 'addQuantity', + create = 'create', + multiTransfer = 'multiTransfer', + localMint = 'localMint', + localBurn = 'localBurn', + wipe = 'wipe', + freeze = 'freeze', + writeLog = 'writeLog', + signalError = 'signalError', + + // to be deprecated ? + ESDTLocalMint = 'ESDTLocalMint', + ESDTLocalBurn = 'ESDTLocalBurn' +} + +export enum VisibleTransactionOperationType { + nft = 'nft', + esdt = 'esdt', + egld = 'egld' +} +export enum HiddenTransactionOperationType { + none = 'none', + error = 'error', + log = 'log' +} + +export interface OperationType { + id?: string; + action: TransactionOperationActionTypeEnum; + type: VisibleTransactionOperationType | HiddenTransactionOperationType; + esdtType?: NftEnumType | EsdtEnumType; + collection?: string; + name?: string; + identifier?: string; + sender: string; + ticker?: string; + receiver: string; + value: string; + decimals?: number; + data?: string; + message?: string; + svgUrl?: string; + senderAssets?: AssetType; + receiverAssets?: AssetType; + valueUSD?: string; +} + +export interface LogType { + hash: string; + callType: string; + gasLimit: number; + gasPrice: number; + nonce: number; + prevTxHash: string; + receiver?: string; + sender: string; + value: string; + data?: string; + originalTxHash: string; + returnMessage?: string; + logs?: any; +} + +export interface EventType { + address: string; + identifier: string; + topics: string[]; + order: number; + data?: string; + additionalData?: string[]; +} + +export interface ResultLogType { + id: string; + address: string; + events: EventType[]; +} + +export interface ResultType { + hash: string; + callType: string; + gasLimit: number; + gasPrice: number; + nonce: number; + prevTxHash: string; + receiver?: string; + sender: string; + value: string; + data?: string; + originalTxHash: string; + returnMessage?: string; + logs?: ResultLogType; + senderAssets?: AssetType; + receiverAssets?: AssetType; + miniBlockHash?: string; + function?: string; + timestamp?: number; +} + +export interface ReceiptType { + value: string; + sender: string; + data: string; +} + +export interface ServerTransactionType { + fee?: string; + data: string; + gasLimit: number; + gasPrice: number; + gasUsed: number; + txHash: string; + miniBlockHash: string; + nonce: number; + receiver: string; + receiverShard: number; + round: number; + sender: string; + senderShard: number; + signature: string; + status: string; + inTransit?: boolean; + timestamp: number; + value: string; + price: number; + results?: ResultType[]; + operations?: OperationType[]; + action?: TransactionActionType; + logs?: { + id: string; + address: string; + events: EventType[]; + }; + scamInfo?: ScamInfoType; + pendingResults?: boolean; + receipt?: ReceiptType; + senderAssets?: AssetType; + receiverAssets?: AssetType; + type?: TransferTypeEnum; + originalTxHash?: string; + isNew?: boolean; // UI flag + tokenValue?: string; + tokenIdentifier?: string; + function?: string; +} + +export enum TransferTypeEnum { + Transaction = 'Transaction', + SmartContractResult = 'SmartContractResult' +} + +//#endregion + +//#region interpreted trasactions + +export enum TransactionDirectionEnum { + SELF = 'Self', + INTERNAL = 'Internal', + IN = 'In', + OUT = 'Out' +} + +export interface InterpretedTransactionType extends ServerTransactionType { + transactionDetails: { + direction?: TransactionDirectionEnum; + method: string; + transactionTokens: TokenArgumentType[]; + isContract?: boolean; + }; + links: { + senderLink?: string; + receiverLink?: string; + senderShardLink?: string; + receiverShardLink?: string; + transactionLink?: string; + }; +} + +export interface DecodeForDisplayPropsType { + input: string; + decodeMethod: DecodeMethodEnum; + identifier?: string; +} + +export interface DecodedDisplayType { + displayValue: string; + validationWarnings: string[]; +} + +export enum DecodeMethodEnum { + raw = 'raw', + text = 'text', + decimal = 'decimal', + smart = 'smart' +} + +//#endregion + +export enum BatchTransactionStatus { + pending = 'pending', + success = 'success', + invalid = 'invalid', + dropped = 'dropped', + fail = 'fail' +} + +export interface BatchTransactionsRequestType { + id: string; + transactions: SignedTransactionType[][]; +} + +export interface BatchTransactionsResponseType { + id: string; + status: BatchTransactionStatus; + transactions: SignedTransactionType[][]; + error?: string; + message?: string; + statusCode?: string; +} + +export type BatchTransactionsWSResponseType = { + batchId: string; + txHashes: string[]; +}; + export interface TransactionsToSignType { transactions: IPlainTransactionObject[]; callbackRoute?: string; @@ -39,14 +376,6 @@ export interface TransactionParameter { export type RawTransactionType = IPlainTransactionObject; -export interface SignedTransactionType extends RawTransactionType { - hash: string; - status: TransactionServerStatusesEnum; - inTransit?: boolean; - errorMessage?: string; - customTransactionInformation?: CustomTransactionInformation; -} - export interface TransactionDataTokenType { tokenId: string; amount: string; @@ -126,46 +455,6 @@ export interface SendSimpleTransactionPropsType { minGasLimit?: number; } -export interface SendTransactionsPropsType { - transactions: - | Transaction - | SimpleTransactionType - | (Transaction | SimpleTransactionType)[]; - redirectAfterSign?: boolean; - signWithoutSending: boolean; - skipGuardian?: boolean; - completedTransactionsDelay?: number; - callbackRoute?: string; - transactionsDisplayInfo: TransactionsDisplayInfoType; - minGasLimit?: number; - sessionInformation?: any; - hasConsentPopup?: boolean; -} - -export interface SendBatchTransactionsPropsType { - transactions: (Transaction | SimpleTransactionType)[][]; - redirectAfterSign?: boolean; - signWithoutSending?: boolean; - skipGuardian?: boolean; - /** - * For Cross-Window provider in Safari browser, performing async calls before signing transactions needs a consent popup in order to open a new tab. - */ - hasConsentPopup?: boolean; - completedTransactionsDelay?: number; - callbackRoute?: string; - transactionsDisplayInfo: TransactionsDisplayInfoType; - minGasLimit?: number; - sessionInformation?: any; -} - -export interface SignTransactionsPropsType { - transactions: Transaction[] | Transaction; - minGasLimit?: number; // unused, will be removed in v3.0.0 - callbackRoute?: string; - transactionsDisplayInfo: TransactionsDisplayInfoType; - customTransactionInformation: CustomTransactionInformation; -} - export interface ActiveLedgerTransactionType { dataField: string; isTokenTransaction: boolean; @@ -194,56 +483,6 @@ export interface SmartContractResult { export type DeviceSignedTransactions = Record; -export interface GuardianScreenType extends WithClassnameType { - address: string; - onSignTransaction: () => Promise; - onPrev: () => void; - title?: ReactNode; - signStepInnerClasses?: SignStepInnerClassesType; - signedTransactions?: DeviceSignedTransactions; - guardianFormDescription?: ReactNode; - setSignedTransactions?: Dispatch< - SetStateAction - >; -} - -export interface SignModalPropsType extends WithClassnameType { - callbackRoute?: string; - error: string | null; - GuardianScreen?: (signProps: GuardianScreenType) => JSX.Element; - handleClose: () => void; - handleSubmit?: () => void; - modalContentClassName?: string; - sessionId?: string; - signStepInnerClasses?: SignStepInnerClassesType; - title?: ReactNode; - transactions: Transaction[]; - verifyReceiverScam?: boolean; -} - -export interface CustomTransactionInformation { - redirectAfterSign: boolean; - sessionInformation: any; - completedTransactionsDelay?: number; - signWithoutSending: boolean; - /** - * If true, transactions with lower nonces than the account nonce will not be updated with the correct nonce - */ - skipUpdateNonces?: boolean; - /** - * If true, the change guardian action will not trigger transaction version update - */ - skipGuardian?: boolean; - /** - * Keeps indexes of transactions that should be grouped together. If not provided, all transactions will be grouped together. Used only for batch transactions. - */ - grouping?: number[][]; - /** - * For Cross-Window provider in Safari browser, performing async calls before signing transactions needs a consent popup in order to open a new tab. - */ - hasConsentPopup?: boolean; -} - export interface SendTransactionReturnType { error?: string; sessionId: string | null; @@ -282,4 +521,33 @@ export interface TransactionLinkType { address: string; } -export type Nullable = T | null; +export interface SignedTransactionType extends RawTransactionType { + hash: string; + status: TransactionServerStatusesEnum; + inTransit?: boolean; + errorMessage?: string; + customTransactionInformation?: CustomTransactionInformation; +} + +export interface CustomTransactionInformation { + redirectAfterSign: boolean; + sessionInformation: any; + completedTransactionsDelay?: number; + signWithoutSending: boolean; + /** + * If true, transactions with lower nonces than the account nonce will not be updated with the correct nonce + */ + skipUpdateNonces?: boolean; + /** + * If true, the change guardian action will not trigger transaction version update + */ + skipGuardian?: boolean; + /** + * Keeps indexes of transactions that should be grouped together. If not provided, all transactions will be grouped together. Used only for batch transactions. + */ + grouping?: number[][]; + /** + * For Cross-Window provider in Safari browser, performing async calls before signing transactions needs a consent popup in order to open a new tab. + */ + hasConsentPopup?: boolean; +} diff --git a/src/utils/transactions/batch/generateBatchTransactionsGrouping.test.ts b/src/utils/transactions/batch/generateBatchTransactionsGrouping.test.ts deleted file mode 100644 index 8c7df20..0000000 --- a/src/utils/transactions/batch/generateBatchTransactionsGrouping.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { generateBatchTransactionsGrouping } from './generateBatchTransactionsGrouping'; // Replace 'your-module' with the actual module path - -describe('generateBatchTransactionsGrouping', () => { - it('should generate batch transactions grouping correctly', () => { - const address = - 'erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv'; - const transactions = [ - [ - { - receiver: address, - sender: address, - value: '0', - data: '1' - }, - { - receiver: address, - sender: address, - value: '0', - data: '2' - } - ], - [ - { - receiver: address, - sender: address, - value: '0', - data: '3' - } - ], - [ - { - receiver: address, - sender: address, - value: '0', - data: '4' - }, - { - receiver: address, - sender: address, - value: '0', - data: '5' - }, - { - receiver: address, - sender: address, - value: '0', - data: '6' - } - ] - ]; - - const expectedGrouping = [[0, 1], [2], [3, 4, 5]]; - - const result = generateBatchTransactionsGrouping(transactions); - - expect(result).toEqual(expectedGrouping); - }); - - it('should handle empty input', () => { - const transactions: any[][] = []; - - const result = generateBatchTransactionsGrouping(transactions); - - expect(result).toEqual([]); - }); - - it('should handle nested empty arrays', () => { - const transactions = [[], [], []]; - - const result = generateBatchTransactionsGrouping(transactions); - - expect(result).toEqual([[], [], []]); - }); -}); diff --git a/src/utils/transactions/batch/generateBatchTransactionsGrouping.ts b/src/utils/transactions/batch/generateBatchTransactionsGrouping.ts index 5dabb04..8edbc56 100644 --- a/src/utils/transactions/batch/generateBatchTransactionsGrouping.ts +++ b/src/utils/transactions/batch/generateBatchTransactionsGrouping.ts @@ -1,8 +1,5 @@ -import { Transaction } from '@multiversx/sdk-core/out'; -import { SimpleTransactionType } from 'types'; - -export const generateBatchTransactionsGrouping = ( - transactions: (Transaction | SimpleTransactionType)[][] +export const generateBatchTransactionsGrouping = ( + transactions: T[][] ) => { let indexInFlatArray = 0; return transactions.map((group) => { diff --git a/src/utils/transactions/batch/getIsSequential.ts b/src/utils/transactions/batch/getIsSequential.ts index 7aabbaf..7cfe4b9 100644 --- a/src/utils/transactions/batch/getIsSequential.ts +++ b/src/utils/transactions/batch/getIsSequential.ts @@ -1,9 +1,7 @@ -import { SignedTransactionType } from 'types'; - -export function getIsSequential({ +export function getIsSequential({ transactions }: { - transactions?: SignedTransactionType[] | SignedTransactionType[][]; + transactions?: T[] | T[][]; }) { return transactions?.some((transaction) => Array.isArray(transaction)); } diff --git a/src/utils/transactions/batch/getTransactionsStatus.ts b/src/utils/transactions/batch/getTransactionsStatus.ts index 8ee759c..0bfed23 100644 --- a/src/utils/transactions/batch/getTransactionsStatus.ts +++ b/src/utils/transactions/batch/getTransactionsStatus.ts @@ -1,10 +1,10 @@ -import { SignedTransactionType, TransactionServerStatusesEnum } from 'types'; +import { TransactionServerStatusesEnum } from 'types'; export const getTransactionsStatus = ({ transactions, hasUnrelatedTransactions }: { - transactions: SignedTransactionType[]; + transactions: { status: TransactionServerStatusesEnum }[]; hasUnrelatedTransactions?: boolean; }) => { const allTxFailed = transactions.every( @@ -24,7 +24,7 @@ export const getTransactionsStatus = ({ ); const isIncompleteFailed = - hasUnrelatedTransactions && + Boolean(hasUnrelatedTransactions) && Boolean(!isPending && !allTxFailed && someTxFailed); const isFailed = hasUnrelatedTransactions diff --git a/src/utils/transactions/batch/sequentialToFlatArray.ts b/src/utils/transactions/batch/sequentialToFlatArray.ts index 7a2692c..bb90518 100644 --- a/src/utils/transactions/batch/sequentialToFlatArray.ts +++ b/src/utils/transactions/batch/sequentialToFlatArray.ts @@ -1,12 +1,11 @@ -import { SignedTransactionType } from 'types'; import { getIsSequential } from './getIsSequential'; -export function sequentialToFlatArray({ +export function sequentialToFlatArray({ transactions = [] }: { - transactions?: SignedTransactionType[] | SignedTransactionType[][]; + transactions?: T[] | T[][]; }) { - return getIsSequential({ transactions }) + return getIsSequential({ transactions }) ? transactions.flat() - : (transactions as SignedTransactionType[]); + : (transactions as T[]); } diff --git a/src/utils/transactions/batch/tests/generateBatchTransactionsGrouping.test.ts b/src/utils/transactions/batch/tests/generateBatchTransactionsGrouping.test.ts new file mode 100644 index 0000000..bd70211 --- /dev/null +++ b/src/utils/transactions/batch/tests/generateBatchTransactionsGrouping.test.ts @@ -0,0 +1,26 @@ +import { generateBatchTransactionsGrouping } from '../generateBatchTransactionsGrouping'; + +describe('generateBatchTransactionsGrouping', () => { + it('should generate correct indices for empty array', () => { + const result = generateBatchTransactionsGrouping([]); + expect(result).toEqual([]); + }); + + it('should generate correct indices for single group', () => { + const input = [[1, 2, 3]]; + const result = generateBatchTransactionsGrouping(input); + expect(result).toEqual([[0, 1, 2]]); + }); + + it('should generate correct indices for multiple groups', () => { + const input = [[1, 2], [3, 4], [5]]; + const result = generateBatchTransactionsGrouping(input); + expect(result).toEqual([[0, 1], [2, 3], [4]]); + }); + + it('should handle groups of different sizes', () => { + const input = [[1], [2, 3, 4], [5, 6]]; + const result = generateBatchTransactionsGrouping(input); + expect(result).toEqual([[0], [1, 2, 3], [4, 5]]); + }); +}); diff --git a/src/utils/transactions/batch/tests/getIsSequential.test.ts b/src/utils/transactions/batch/tests/getIsSequential.test.ts new file mode 100644 index 0000000..cb4d0ae --- /dev/null +++ b/src/utils/transactions/batch/tests/getIsSequential.test.ts @@ -0,0 +1,23 @@ +import { getIsSequential } from '../getIsSequential'; + +describe('getIsSequential', () => { + test('should return true for nested array transactions', () => { + const transactions = [[{ id: 1 }, { id: 2 }], [{ id: 3 }]]; + + expect(getIsSequential({ transactions })).toBe(true); + }); + + test('should return false for flat array transactions', () => { + const transactions = [{ id: 1 }, { id: 2 }, { id: 3 }]; + + expect(getIsSequential({ transactions })).toBe(false); + }); + + test('should return undefined for undefined transactions', () => { + expect(getIsSequential({ transactions: undefined })).toBe(undefined); + }); + + test('should return false for empty array', () => { + expect(getIsSequential({ transactions: [] })).toBe(false); + }); +}); diff --git a/src/utils/transactions/batch/tests/getTransactionsStatus.test.ts b/src/utils/transactions/batch/tests/getTransactionsStatus.test.ts new file mode 100644 index 0000000..e497046 --- /dev/null +++ b/src/utils/transactions/batch/tests/getTransactionsStatus.test.ts @@ -0,0 +1,103 @@ +import { getTransactionsStatus } from '../getTransactionsStatus'; +import { TransactionServerStatusesEnum } from 'types'; + +describe('getTransactionsStatus', () => { + it('should identify all transactions as successful', () => { + const transactions = [ + { status: TransactionServerStatusesEnum.success }, + { status: TransactionServerStatusesEnum.success } + ]; + + const result = getTransactionsStatus({ transactions }); + + expect(result).toEqual({ + isPending: false, + isSuccessful: true, + isFailed: false, + isIncompleteFailed: false + }); + }); + + it('should identify pending transactions', () => { + const transactions = [ + { status: TransactionServerStatusesEnum.success }, + { status: TransactionServerStatusesEnum.pending } + ]; + + const result = getTransactionsStatus({ transactions }); + + expect(result).toEqual({ + isPending: true, + isSuccessful: false, + isFailed: false, + isIncompleteFailed: false + }); + }); + + it('should identify failed transactions without unrelated transactions', () => { + const transactions = [ + { status: TransactionServerStatusesEnum.fail }, + { status: TransactionServerStatusesEnum.success } + ]; + + const result = getTransactionsStatus({ transactions }); + + expect(result).toEqual({ + isPending: false, + isSuccessful: false, + isFailed: true, + isIncompleteFailed: false + }); + }); + + it('should handle all failed transactions with unrelated transactions', () => { + const transactions = [ + { status: TransactionServerStatusesEnum.fail }, + { status: TransactionServerStatusesEnum.fail } + ]; + + const result = getTransactionsStatus({ + transactions, + hasUnrelatedTransactions: true + }); + + expect(result).toEqual({ + isPending: false, + isSuccessful: false, + isFailed: true, + isIncompleteFailed: false + }); + }); + + it('should identify incomplete failed status with unrelated transactions', () => { + const transactions = [ + { status: TransactionServerStatusesEnum.fail }, + { status: TransactionServerStatusesEnum.success } + ]; + + const result = getTransactionsStatus({ + transactions, + hasUnrelatedTransactions: true + }); + + expect(result).toEqual({ + isPending: false, + isSuccessful: false, + isFailed: false, + isIncompleteFailed: true + }); + }); + + it('should handle empty transactions array', () => { + const transactions: { status: TransactionServerStatusesEnum }[] = []; + + const result = getTransactionsStatus({ transactions }); + + expect(result).toEqual({ + isPending: false, + isSuccessful: true, + isFailed: false, + isIncompleteFailed: false + }); + }); +}); diff --git a/src/utils/transactions/batch/tests/sequentialToFlatArray.test.ts b/src/utils/transactions/batch/tests/sequentialToFlatArray.test.ts new file mode 100644 index 0000000..e7f7576 --- /dev/null +++ b/src/utils/transactions/batch/tests/sequentialToFlatArray.test.ts @@ -0,0 +1,40 @@ +import { sequentialToFlatArray } from '../sequentialToFlatArray'; + +describe('sequentialToFlatArray', () => { + it('should handle empty array input', () => { + const result = sequentialToFlatArray({ transactions: [] }); + expect(result).toEqual([]); + }); + + it('should return same array when input is flat', () => { + const input = [1, 2, 3]; + const result = sequentialToFlatArray({ transactions: input }); + expect(result).toEqual([1, 2, 3]); + }); + + it('should flatten sequential nested arrays', () => { + const input = [[1, 2], [3, 4], [5]]; + const result = sequentialToFlatArray({ transactions: input }); + expect(result).toEqual([1, 2, 3, 4, 5]); + }); + + it('should handle mixed type arrays', () => { + const input = [ + ['a', 'b'], + ['c', 'd'] + ]; + const result = sequentialToFlatArray({ transactions: input }); + expect(result).toEqual(['a', 'b', 'c', 'd']); + }); + + it('should flatten array near non-array element', () => { + const input = [[1, 2], 3]; + const result = sequentialToFlatArray({ transactions: input }); + expect(result).toEqual([1, 2, 3]); + }); + + it('should handle undefined input', () => { + const result = sequentialToFlatArray({}); + expect(result).toEqual([]); + }); +}); diff --git a/src/utils/transactions/batch/updateBatchTransactionsStatuses.ts b/src/utils/transactions/batch/updateBatchTransactionsStatuses.ts index cdf4e77..7af2ffc 100644 --- a/src/utils/transactions/batch/updateBatchTransactionsStatuses.ts +++ b/src/utils/transactions/batch/updateBatchTransactionsStatuses.ts @@ -1,10 +1,11 @@ -import { updateSignedTransactionStatus } from 'reduxStore/slices'; -import { store } from 'reduxStore/store'; -import { removeBatchTransactions } from 'services/transactions'; -import { SignedTransactionType, TransactionServerStatusesEnum } from 'types'; +import { + ServerTransactionType, + SignedTransactionType, + TransactionServerStatusesEnum +} from 'types'; import { sequentialToFlatArray } from './sequentialToFlatArray'; -export function updateBatchTransactionsStatuses({ +export const updateBatchTransactionsStatuses = ({ batchId, sessionId, transactions @@ -12,8 +13,10 @@ export function updateBatchTransactionsStatuses({ batchId: string; sessionId: string; transactions: SignedTransactionType[] | SignedTransactionType[][]; -}) { - const transactionsArray = sequentialToFlatArray({ transactions }); +}) => { + const transactionsArray = sequentialToFlatArray({ + transactions + }); const batchIsSuccessful = transactionsArray.every( (transaction) => @@ -39,4 +42,4 @@ export function updateBatchTransactionsStatuses({ if (batchIsSuccessful) { removeBatchTransactions(batchId); } -} +}; diff --git a/src/utils/transactions/dataDecoders/getScResultsInitialDecodeMethod.ts b/src/utils/transactions/dataDecoders/getScResultsInitialDecodeMethod.ts index c7c20f2..17999e1 100644 --- a/src/utils/transactions/dataDecoders/getScResultsInitialDecodeMethod.ts +++ b/src/utils/transactions/dataDecoders/getScResultsInitialDecodeMethod.ts @@ -1,4 +1,4 @@ - +import { DecodeMethodEnum } from 'types'; import { getWindowLocation } from 'utils/window/getWindowLocation'; export const getInitialScResultsDecodeMethod = () => { diff --git a/src/utils/transactions/dataDecoders/getTokenFromData.ts b/src/utils/transactions/dataDecoders/getTokenFromData.ts index a0b650f..cdc5de2 100644 --- a/src/utils/transactions/dataDecoders/getTokenFromData.ts +++ b/src/utils/transactions/dataDecoders/getTokenFromData.ts @@ -1,6 +1,9 @@ import { Address } from '@multiversx/sdk-core'; import BigNumber from 'bignumber.js'; -import { decodePart } from 'utils/decoders/decodePart'; +import { decodePart } from '../../decoders'; +import { TransactionTypesEnum } from '../../../types'; +import { addressIsValid } from '../../validation'; + const noData = { tokenId: '', diff --git a/src/utils/transactions/index.ts b/src/utils/transactions/index.ts index 74706c6..1d131da 100644 --- a/src/utils/transactions/index.ts +++ b/src/utils/transactions/index.ts @@ -1,6 +1,5 @@ -export * from './url/buildCallbackUrl'; +export * from './url'; export * from './parsers/getAreTransactionsOnSameShard'; -export * from './getInterpretedTransaction'; export * from './dataDecoders/getTokenFromData'; export * from './parsers/getTransactionLink'; export * from './dataDecoders/getUnHighlightedDataFieldParts'; diff --git a/src/utils/transactions/isGuardianTx.tsx b/src/utils/transactions/isGuardianTx.ts similarity index 86% rename from src/utils/transactions/isGuardianTx.tsx rename to src/utils/transactions/isGuardianTx.ts index 00a49bf..bf683d3 100644 --- a/src/utils/transactions/isGuardianTx.tsx +++ b/src/utils/transactions/isGuardianTx.ts @@ -1,4 +1,4 @@ -import { GuardianActionsEnum } from 'constants/index'; +import { GuardianActionsEnum } from 'types'; export const isGuardianTx = ({ data, diff --git a/src/utils/transactions/newTransaction.ts b/src/utils/transactions/newTransaction.ts new file mode 100644 index 0000000..6d6ba65 --- /dev/null +++ b/src/utils/transactions/newTransaction.ts @@ -0,0 +1,50 @@ +import { + Address, + Transaction, + TransactionOptions, + TransactionVersion +} from '@multiversx/sdk-core'; +import { GAS_LIMIT, GAS_PRICE, VERSION } from 'constants'; +import { RawTransactionType } from 'types'; + +export function newTransaction(rawTransaction: RawTransactionType) { + const rawTx = { ...rawTransaction }; + + // TODO: Remove when the protocol supports usernames for guardian transactions + if (isGuardianTx({ data: rawTx.data, onlySetGuardian: true })) { + delete rawTx.senderUsername; + delete rawTx.receiverUsername; + } + + const transaction = new Transaction({ + value: rawTx.value.valueOf(), + data: getDataPayloadForTransaction(rawTx.data), + nonce: rawTx.nonce.valueOf(), + receiver: new Address(rawTx.receiver), + ...(rawTx.receiverUsername + ? { receiverUsername: rawTx.receiverUsername } + : {}), + sender: new Address(rawTx.sender), + ...(rawTx.senderUsername ? { senderUsername: rawTx.senderUsername } : {}), + gasLimit: rawTx.gasLimit.valueOf() ?? GAS_LIMIT, + gasPrice: rawTx.gasPrice.valueOf() ?? GAS_PRICE, + chainID: rawTx.chainID.valueOf(), + version: new TransactionVersion(rawTx.version ?? VERSION), + ...(rawTx.options + ? { options: new TransactionOptions(rawTx.options) } + : {}), + ...(rawTx.guardian ? { guardian: new Address(rawTx.guardian) } : {}) + }); + + if (rawTx.guardianSignature) { + transaction.applyGuardianSignature( + Buffer.from(rawTx.guardianSignature, 'hex') + ); + } + + if (rawTx.signature) { + transaction.applySignature(Buffer.from(rawTx.signature, 'hex')); + } + + return transaction; +} diff --git a/src/utils/transactions/url/index.ts b/src/utils/transactions/url/index.ts new file mode 100644 index 0000000..3fda33b --- /dev/null +++ b/src/utils/transactions/url/index.ts @@ -0,0 +1,2 @@ +export * from './buildCallbackUrl'; +export * from './removeTransactionParamsFromUrl'; From 29898944ca001cee40bb0328de102edb82cbcb5f Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Tue, 3 Dec 2024 18:28:16 +0200 Subject: [PATCH 03/23] added services and apiCalls --- .../transactions/getTransactionByHash.ts | 23 ++ .../transactions/getTransactionsByHashes.ts | 54 ++++ .../transactions/getTransactionsByParams.ts | 17 ++ .../getTransactionsByParamsCount.ts | 18 ++ .../transactions/helpers/getTimeout.ts | 3 + .../helpers/getTransactionsParams.ts | 31 +++ src/apiCalls/transactions/helpers/index.ts | 2 + src/apiCalls/transactions/index.ts | 9 + .../sendSignedBatchTransactions.ts | 68 +++++ .../transactions/sendSignedTransactions.ts | 22 ++ .../types/getTransactions.types.ts | 20 ++ src/apiCalls/transactions/types/index.ts | 1 + .../transactions/useGetTransactionsFromApi.ts | 9 + src/constants/transactions.constants.ts | 24 +- src/core/methods/logout/logout.ts | 2 +- .../actions/network/initializeNetwork.ts | 4 +- .../transactions/transactionsActions.ts | 0 src/store/selectors/networkSelectors.ts | 3 + src/store/selectors/transactionsSelectors.ts | 114 ++++---- .../transactionsSlice/transactionsSlice.ts | 6 +- .../transactionsSlice.types.ts | 2 +- src/store/store.types.ts | 6 +- src/types/transactions.types.ts | 49 +++- src/utils/account/getShardOfAddress.ts | 33 +++ src/utils/account/index.ts | 1 + src/utils/decoders/decodePart.ts | 4 +- src/utils/index.ts | 10 +- src/utils/operations/formatAmount.ts | 140 ++++++++++ src/utils/operations/index.ts | 2 + src/utils/operations/pipe.ts | 26 ++ .../operations/tests/formatAmount.test.ts | 247 ++++++++++++++++++ src/utils/operations/tests/pipe.test.ts | 29 ++ .../generateBatchTransactionsGrouping.ts | 6 +- .../getBatchTransactionsStatusFromApi.ts | 26 ++ .../batch/getTransactionsStatus.ts | 6 +- src/utils/transactions/batch/index.ts | 5 + .../batch/sendBatchTransactions.test.ts | 215 +++++++++++++++ .../batch/sendBatchTransactions.ts | 61 +++++ .../batch/updateBatchTransactionsStatuses.ts | 6 +- .../calculateTotalTransactionsFee.ts | 23 ++ .../decodeTransactionData.ts | 10 +- .../helpers/decodeByMethod.ts | 13 +- .../getDisplayValueAndValidationWarnings.ts | 8 +- .../helpers/getHexValidationWarnings.ts | 12 +- .../helpers/getSmartDecodedParts.ts | 6 +- .../tests/decodeForDisplay.spec.ts | 16 +- .../getDataPayloadForTransaction.ts | 6 +- .../dataDecoders/getScResultsDecodedData.ts | 4 +- .../dataDecoders/getScResultsHighlight.ts | 4 +- .../getScResultsInitialDecodeMethod.ts | 4 +- .../dataDecoders/getTokenFromData.ts | 5 +- .../getUnHighlightedDataFieldParts.ts | 6 +- src/utils/transactions/dataDecoders/index.ts | 10 + .../dataDecoders/smartContractTransaction.ts | 6 +- .../getDataPayloadForTransaction.test.ts | 2 +- .../tests/getTokenFromData.test.ts | 2 +- .../getUnHighlightedDataFieldParts.test.ts | 2 +- .../dataDecoders/useDataDecode.ts | 6 +- src/utils/transactions/index.ts | 17 +- .../operations/calculateFeeLimit.ts | 78 ++++++ .../operations/clearTransactions.ts | 33 +++ .../operations/computeTransactionNonce.ts | 17 ++ src/utils/transactions/operations/index.ts | 2 + .../{ => operations}/newTransaction.ts | 2 + .../operations/sendTransactions.ts | 51 ++++ .../operations/signTransactions.ts | 67 +++++ .../tests/calculateFeeLimit.test.ts | 96 +++++++ .../transformAndSignTransactions.ts | 98 +++++++ .../operations/transformTransactionsToSign.ts | 26 ++ .../operations/updateSignedTransactions.ts | 44 ++++ .../parsers/getAreTransactionsOnSameShard.ts | 11 +- .../parsers/getEventListDataHexValue.ts | 4 +- .../parsers/getEventListHighlight.ts | 4 +- .../getEventListInitialDecodeMethod.ts | 4 +- .../helpers/getTransactionMethod.ts | 4 +- .../helpers/getTransactionTokens.ts | 6 +- .../getTransactionValue.ts | 31 ++- .../helpers/getEgldValueData.ts | 18 +- .../helpers/getTitleText.ts | 16 +- .../parsers/getOperationDirection.ts | 14 +- .../parsers/getOperationsDetails.ts | 10 +- .../transactions/parsers/getShardText.ts | 4 +- .../parsers/getTransactionLinkWithLabel.ts | 6 +- .../parsers/getTransactionStatusText.ts | 8 +- .../parsers/getTransactionsDetails.ts | 52 ++++ .../transactions/parsers/getTrimmedHash.ts | 5 +- .../parsers/getUsernameForTransaction.ts | 4 +- .../parsers/getVisibleOperations.ts | 8 +- .../parsers/parseTransactionAfterSigning.ts | 2 +- .../tests/getOperationsDetails.test.ts | 2 +- .../tests/parseMultiEsdtTransferData.test.ts | 2 +- .../helpers/esdtNftUnwrapper.ts | 8 +- .../helpers/mexUnwrapper.ts | 8 +- .../helpers/stakeUnwrapper.ts | 8 +- .../transactionActionUnwrapper.ts | 16 +- .../{ => parsers}/transactionStateByStatus.ts | 0 .../url/removeTransactionParamsFromUrl.ts | 10 +- .../{ => url}/tests/builtCallbackUrl.test.ts | 2 +- .../removeTransactionParamsFromUrl.test.ts | 2 +- src/utils/transactions/validation/index.ts | 3 + .../validation/isCrossShardTransaction.ts | 26 ++ .../{ => validation}/isGuardianTx.ts | 6 +- .../{ => validation}/isTokenTransfer.ts | 0 .../tests/isGuardianTx.test.ts | 2 +- .../validation/tests/isTokenTransfer.test.ts | 35 +++ src/utils/walletconnect/index.ts | 1 + 106 files changed, 2035 insertions(+), 287 deletions(-) create mode 100644 src/apiCalls/transactions/getTransactionByHash.ts create mode 100644 src/apiCalls/transactions/getTransactionsByHashes.ts create mode 100644 src/apiCalls/transactions/getTransactionsByParams.ts create mode 100644 src/apiCalls/transactions/getTransactionsByParamsCount.ts create mode 100644 src/apiCalls/transactions/helpers/getTimeout.ts create mode 100644 src/apiCalls/transactions/helpers/getTransactionsParams.ts create mode 100644 src/apiCalls/transactions/helpers/index.ts create mode 100644 src/apiCalls/transactions/index.ts create mode 100644 src/apiCalls/transactions/sendSignedBatchTransactions.ts create mode 100644 src/apiCalls/transactions/sendSignedTransactions.ts create mode 100644 src/apiCalls/transactions/types/getTransactions.types.ts create mode 100644 src/apiCalls/transactions/types/index.ts create mode 100644 src/apiCalls/transactions/useGetTransactionsFromApi.ts create mode 100644 src/store/actions/transactions/transactionsActions.ts create mode 100644 src/utils/account/getShardOfAddress.ts create mode 100644 src/utils/operations/formatAmount.ts create mode 100644 src/utils/operations/index.ts create mode 100644 src/utils/operations/pipe.ts create mode 100644 src/utils/operations/tests/formatAmount.test.ts create mode 100644 src/utils/operations/tests/pipe.test.ts create mode 100644 src/utils/transactions/batch/getBatchTransactionsStatusFromApi.ts create mode 100644 src/utils/transactions/batch/index.ts create mode 100644 src/utils/transactions/batch/sendBatchTransactions.test.ts create mode 100644 src/utils/transactions/batch/sendBatchTransactions.ts create mode 100644 src/utils/transactions/calculateTotalTransactionsFee.ts create mode 100644 src/utils/transactions/dataDecoders/index.ts rename src/utils/transactions/{ => dataDecoders}/tests/getDataPayloadForTransaction.test.ts (96%) rename src/utils/transactions/{ => dataDecoders}/tests/getTokenFromData.test.ts (95%) rename src/utils/transactions/{ => dataDecoders}/tests/getUnHighlightedDataFieldParts.test.ts (96%) create mode 100755 src/utils/transactions/operations/calculateFeeLimit.ts create mode 100644 src/utils/transactions/operations/clearTransactions.ts create mode 100644 src/utils/transactions/operations/computeTransactionNonce.ts create mode 100644 src/utils/transactions/operations/index.ts rename src/utils/transactions/{ => operations}/newTransaction.ts (93%) create mode 100644 src/utils/transactions/operations/sendTransactions.ts create mode 100644 src/utils/transactions/operations/signTransactions.ts create mode 100644 src/utils/transactions/operations/tests/calculateFeeLimit.test.ts create mode 100644 src/utils/transactions/operations/transformAndSignTransactions.ts create mode 100644 src/utils/transactions/operations/transformTransactionsToSign.ts create mode 100644 src/utils/transactions/operations/updateSignedTransactions.ts create mode 100644 src/utils/transactions/parsers/getTransactionsDetails.ts rename src/utils/transactions/{ => parsers}/tests/getOperationsDetails.test.ts (99%) rename src/utils/transactions/{ => parsers}/tests/parseMultiEsdtTransferData.test.ts (98%) rename src/utils/transactions/{ => parsers}/transactionStateByStatus.ts (100%) rename src/utils/transactions/{ => url}/tests/builtCallbackUrl.test.ts (96%) rename src/utils/transactions/{ => url}/tests/removeTransactionParamsFromUrl.test.ts (92%) create mode 100644 src/utils/transactions/validation/index.ts create mode 100644 src/utils/transactions/validation/isCrossShardTransaction.ts rename src/utils/transactions/{ => validation}/isGuardianTx.ts (89%) rename src/utils/transactions/{ => validation}/isTokenTransfer.ts (100%) rename src/utils/transactions/{ => validation}/tests/isGuardianTx.test.ts (96%) create mode 100644 src/utils/transactions/validation/tests/isTokenTransfer.test.ts create mode 100644 src/utils/walletconnect/index.ts diff --git a/src/apiCalls/transactions/getTransactionByHash.ts b/src/apiCalls/transactions/getTransactionByHash.ts new file mode 100644 index 0000000..6d6e52e --- /dev/null +++ b/src/apiCalls/transactions/getTransactionByHash.ts @@ -0,0 +1,23 @@ +import axios from 'axios'; +import { ServerTransactionType } from 'types'; +import { TRANSACTIONS_ENDPOINT } from '../endpoints'; +import { getTimeout } from './helpers'; + +export interface GetTransactionType { + apiAddress: string; + apiTimeout?: string | number; + hash: string; +} + +export function getTransactionByHash({ + hash, + apiAddress, + apiTimeout +}: GetTransactionType) { + return axios.get( + `${apiAddress}/${TRANSACTIONS_ENDPOINT}/${hash}`, + { + ...getTimeout(apiTimeout) + } + ); +} diff --git a/src/apiCalls/transactions/getTransactionsByHashes.ts b/src/apiCalls/transactions/getTransactionsByHashes.ts new file mode 100644 index 0000000..8a45999 --- /dev/null +++ b/src/apiCalls/transactions/getTransactionsByHashes.ts @@ -0,0 +1,54 @@ +import axios from 'axios'; +import { TRANSACTIONS_ENDPOINT } from 'apiCalls/endpoints'; +import { ServerTransactionType } from 'types'; +import { + GetTransactionsByHashesReturnType, + PendingTransactionsType +} from 'types/transactions.types'; +import { apiAddressSelector } from 'store/selectors'; +import { getState } from 'store/store'; + +export const getTransactionsByHashes = async ( + pendingTransactions: PendingTransactionsType +): Promise => { + const apiAddress = apiAddressSelector(getState()); + const hashes = pendingTransactions.map((tx) => tx.hash); + const { data: responseData } = await axios.get( + `${apiAddress}/${TRANSACTIONS_ENDPOINT}`, + { + params: { + hashes: hashes.join(','), + withScResults: true + } + } + ); + + return pendingTransactions.map(({ hash, previousStatus }) => { + const txOnNetwork = responseData.find( + (txResponse: any) => txResponse?.txHash === hash + ); + + return { + hash, + data: txOnNetwork?.data, + invalidTransaction: txOnNetwork == null, + status: txOnNetwork?.status, + results: txOnNetwork?.results, + sender: txOnNetwork?.sender, + receiver: txOnNetwork?.receiver, + previousStatus, + hasStatusChanged: txOnNetwork && txOnNetwork.status !== previousStatus + }; + }); +}; + +export function getTransactionByHashPromise(hash: string) { + const apiAddress = apiAddressSelector(getState()); + + return axios.get( + `${apiAddress}/transactions/${hash}`, + { + timeout: 10000 // 10sec + } + ); +} diff --git a/src/apiCalls/transactions/getTransactionsByParams.ts b/src/apiCalls/transactions/getTransactionsByParams.ts new file mode 100644 index 0000000..642940d --- /dev/null +++ b/src/apiCalls/transactions/getTransactionsByParams.ts @@ -0,0 +1,17 @@ +import axios from 'axios'; +import { TRANSACTIONS_ENDPOINT } from 'apiCalls/endpoints'; +import { ServerTransactionType } from 'types'; +import { GetTransactionsParamsType } from './types/getTransactions.types'; +import { getTimeout, getTransactionsParams } from './helpers'; + +export function getTransactionsByParams(params: GetTransactionsParamsType) { + const parsedParams = getTransactionsParams(params); + + return axios.get( + `${params.apiAddress}/${TRANSACTIONS_ENDPOINT}`, + { + params: parsedParams, + ...getTimeout(params.apiTimeout) + } + ); +} diff --git a/src/apiCalls/transactions/getTransactionsByParamsCount.ts b/src/apiCalls/transactions/getTransactionsByParamsCount.ts new file mode 100644 index 0000000..712efc2 --- /dev/null +++ b/src/apiCalls/transactions/getTransactionsByParamsCount.ts @@ -0,0 +1,18 @@ +import axios from 'axios'; +import { TRANSACTIONS_COUNT_ENDPOINT } from 'apiCalls/endpoints'; +import { GetTransactionsParamsType } from './types/getTransactions.types'; +import { getTimeout, getTransactionsParams } from './helpers'; + +export function getTransactionsByParamsCount( + params: GetTransactionsParamsType +) { + const parsedParams = getTransactionsParams(params); + + return axios.get( + `${params.apiAddress}/${TRANSACTIONS_COUNT_ENDPOINT}`, + { + params: parsedParams, + ...getTimeout(params.apiTimeout) + } + ); +} diff --git a/src/apiCalls/transactions/helpers/getTimeout.ts b/src/apiCalls/transactions/helpers/getTimeout.ts new file mode 100644 index 0000000..eb0a7d3 --- /dev/null +++ b/src/apiCalls/transactions/helpers/getTimeout.ts @@ -0,0 +1,3 @@ +export function getTimeout(apiTimeout?: string | number) { + return apiTimeout ? { timeout: parseInt(String(apiTimeout)) } : {}; +} diff --git a/src/apiCalls/transactions/helpers/getTransactionsParams.ts b/src/apiCalls/transactions/helpers/getTransactionsParams.ts new file mode 100644 index 0000000..a2b1270 --- /dev/null +++ b/src/apiCalls/transactions/helpers/getTransactionsParams.ts @@ -0,0 +1,31 @@ +import { GetTransactionsParamsType } from '../types/getTransactions.types'; + +export function getTransactionsParams({ + sender, + receiver, + page = 1, + transactionSize = 15, + condition = 'should', + withScResults = true, + after, + before, + search, + status, + withUsername +}: GetTransactionsParamsType) { + const params = { + sender, + receiver, + condition, + after, + before, + search, + from: (page - 1) * transactionSize, + ...(transactionSize > 0 ? { size: transactionSize } : {}), + withScResults, + withUsername, + status + }; + + return params; +} diff --git a/src/apiCalls/transactions/helpers/index.ts b/src/apiCalls/transactions/helpers/index.ts new file mode 100644 index 0000000..41b4f8f --- /dev/null +++ b/src/apiCalls/transactions/helpers/index.ts @@ -0,0 +1,2 @@ +export * from './getTimeout'; +export * from './getTransactionsParams'; diff --git a/src/apiCalls/transactions/index.ts b/src/apiCalls/transactions/index.ts new file mode 100644 index 0000000..fab49a6 --- /dev/null +++ b/src/apiCalls/transactions/index.ts @@ -0,0 +1,9 @@ +export * from './getTransactionByHash'; +export * from './getTransactionsByParams'; +export * from './types/getTransactions.types'; +export * from './getTransactionsByHashes'; +export * from './getTransactionsByParamsCount'; +export * from './helpers'; +export * from './sendSignedBatchTransactions'; +export * from './sendSignedTransactions'; +export * from './useGetTransactionsFromApi'; diff --git a/src/apiCalls/transactions/sendSignedBatchTransactions.ts b/src/apiCalls/transactions/sendSignedBatchTransactions.ts new file mode 100644 index 0000000..1aafc58 --- /dev/null +++ b/src/apiCalls/transactions/sendSignedBatchTransactions.ts @@ -0,0 +1,68 @@ +import axios from 'axios'; +import { TIMEOUT } from 'constants/network'; +import { buildBatchId } from 'hooks/transactions/helpers/buildBatchId'; +import { addressSelector, networkSelector } from 'reduxStore/selectors'; +import { store } from 'reduxStore/store'; +import { + BatchTransactionsRequestType, + BatchTransactionsResponseType, + CustomTransactionInformation, + SignedTransactionType +} from 'types'; +import { TRANSACTIONS_BATCH } from '../endpoints'; + +export interface SendBatchTransactionsPropsType { + transactions: SignedTransactionType[][]; + sessionId: string; + customTransactionInformationOverrides?: Partial; +} + +export type SendSignedBatchTransactionsReturnType = { + error?: string | null; + batchId?: string | null; + data?: BatchTransactionsResponseType; +}; + +export async function sendSignedBatchTransactions({ + transactions, + sessionId +}: SendBatchTransactionsPropsType) { + const address = addressSelector(store.getState()); + const { apiAddress, apiTimeout } = networkSelector(store.getState()); + + if (!address) { + return { + error: + 'Invalid address provided. You need to be logged in to send transactions', + batchId: null + }; + } + + try { + const batchId = buildBatchId({ + sessionId, + address + }); + + const payload: BatchTransactionsRequestType = { + transactions, + id: batchId + }; + + const response = await axios.post( + `${apiAddress}/${TRANSACTIONS_BATCH}`, + payload, + { + timeout: Number(apiTimeout ?? TIMEOUT) + } + ); + + return { batchId, data: response.data }; + } catch (err) { + console.error('error sending batch transactions', err); + return { + error: (err as any)?.message ?? 'error sending batch transactions', + batchId: null + }; + } +} diff --git a/src/apiCalls/transactions/sendSignedTransactions.ts b/src/apiCalls/transactions/sendSignedTransactions.ts new file mode 100644 index 0000000..7288583 --- /dev/null +++ b/src/apiCalls/transactions/sendSignedTransactions.ts @@ -0,0 +1,22 @@ +import { Transaction } from '@multiversx/sdk-core'; +import axios from 'axios'; +import { networkSelector } from 'reduxStore/selectors'; +import { store } from 'reduxStore/store'; + +export type SendSignedTransactionsReturnType = string[]; + +export async function sendSignedTransactions( + signedTransactions: Transaction[] +): Promise { + const { apiAddress, apiTimeout } = networkSelector(store.getState()); + const promises = signedTransactions.map((transaction) => { + return axios.post( + `${apiAddress}/transactions`, + transaction.toPlainObject(), + { timeout: parseInt(apiTimeout) } + ); + }); + const response = await Promise.all(promises); + + return response.map(({ data }) => data.txHash); +} diff --git a/src/apiCalls/transactions/types/getTransactions.types.ts b/src/apiCalls/transactions/types/getTransactions.types.ts new file mode 100644 index 0000000..de5e734 --- /dev/null +++ b/src/apiCalls/transactions/types/getTransactions.types.ts @@ -0,0 +1,20 @@ +import { TransactionServerStatusesEnum } from 'types'; + +export interface GetTransactionsParamsType { + apiAddress: string; + apiTimeout?: string | number; + sender?: string; + receiver?: string; + page?: number; + transactionSize?: number; + after?: number; + condition?: 'should' | 'must'; + before?: number; + withScResults?: boolean; + withUsername?: boolean; + status?: TransactionServerStatusesEnum; + /** + * Search in data object + */ + search?: string; +} diff --git a/src/apiCalls/transactions/types/index.ts b/src/apiCalls/transactions/types/index.ts new file mode 100644 index 0000000..91ae4f6 --- /dev/null +++ b/src/apiCalls/transactions/types/index.ts @@ -0,0 +1 @@ +export * from './getTransactions.types'; diff --git a/src/apiCalls/transactions/useGetTransactionsFromApi.ts b/src/apiCalls/transactions/useGetTransactionsFromApi.ts new file mode 100644 index 0000000..17dd567 --- /dev/null +++ b/src/apiCalls/transactions/useGetTransactionsFromApi.ts @@ -0,0 +1,9 @@ +import { TRANSACTIONS_ENDPOINT } from '../endpoints'; +import { useApiFetch } from '../utils'; + +export const useGetTransactionsFromApi = (hash: string) => + useApiFetch({ + apiEndpoint: hash + ? `${TRANSACTIONS_ENDPOINT}/${hash}` + : TRANSACTIONS_ENDPOINT + }); diff --git a/src/constants/transactions.constants.ts b/src/constants/transactions.constants.ts index b983a0b..dd0f90f 100644 --- a/src/constants/transactions.constants.ts +++ b/src/constants/transactions.constants.ts @@ -1,16 +1,18 @@ -export const WALLET_SIGN_SESSION = 'signSession'; -export const SDK_DAPP_VERSION = 'sdk-dapp-version'; -export const CANCEL_TRANSACTION_TOAST_ID = 'cancel-transaction-toast'; +export const ALL_SHARDS_SHARD_ID = 4294967280; export const AVERAGE_TX_DURATION_MS = 6000; -export const CROSS_SHARD_ROUNDS = 5; -export const TRANSACTIONS_STATUS_POLLING_INTERVAL_MS = 90 * 1000; // 90sec -export const TRANSACTIONS_STATUS_DROP_INTERVAL_MS = 10 * 60 * 1000; // 10min export const CANCEL_TRANSACTION_TOAST_DEFAULT_DURATION = 20000; -export const GAS_PRICE_MODIFIER = 0.01; -export const GAS_PER_DATA_BYTE = 1_500; -export const GAS_LIMIT = 50_000; -export const EXTRA_GAS_LIMIT_GUARDED_TX = 50_000; -export const GAS_PRICE = 1_000_000_000; +export const CANCEL_TRANSACTION_TOAST_ID = 'cancel-transaction-toast'; +export const CROSS_SHARD_ROUNDS = 5; export const DECIMALS = 18; export const DIGITS = 4; +export const EXTRA_GAS_LIMIT_GUARDED_TX = 50_000; +export const GAS_LIMIT = 50_000; +export const GAS_PER_DATA_BYTE = 1_500; +export const GAS_PRICE = 1_000_000_000; +export const GAS_PRICE_MODIFIER = 0.01; +export const METACHAIN_SHARD_ID = 4294967295; +export const SDK_DAPP_VERSION = 'sdk-dapp-version'; +export const TRANSACTIONS_STATUS_DROP_INTERVAL_MS = 10 * 60 * 1000; // 10min +export const TRANSACTIONS_STATUS_POLLING_INTERVAL_MS = 90 * 1000; // 90sec export const VERSION = 1; +export const WALLET_SIGN_SESSION = 'signSession'; diff --git a/src/core/methods/logout/logout.ts b/src/core/methods/logout/logout.ts index b29a2bc..01e9ece 100644 --- a/src/core/methods/logout/logout.ts +++ b/src/core/methods/logout/logout.ts @@ -24,7 +24,7 @@ const broadcastLogoutAcrossTabs = (address: string) => { storage.local.removeItem(localStorageKeys.logoutEvent); }; -export type LogoutPropsType = { +export type LogoutParamsType = { shouldAttemptReLogin?: boolean; shouldBroadcastLogoutAcrossTabs?: boolean; /* diff --git a/src/store/actions/network/initializeNetwork.ts b/src/store/actions/network/initializeNetwork.ts index c03b1e3..8788fff 100644 --- a/src/store/actions/network/initializeNetwork.ts +++ b/src/store/actions/network/initializeNetwork.ts @@ -4,7 +4,7 @@ import { CustomNetworkType, NetworkType } from 'types/network.types'; import { initializeNetworkConfig } from './networkActions'; import { fallbackNetworkConfigurations } from 'constants/network.constants'; -export type InitializeNetworkPropsType = { +export type InitializeNetworkParamsType = { customNetworkConfig?: CustomNetworkType; environment: EnvironmentsEnum; }; @@ -12,7 +12,7 @@ export type InitializeNetworkPropsType = { export const initializeNetwork = async ({ customNetworkConfig = {}, environment -}: InitializeNetworkPropsType): Promise => { +}: InitializeNetworkParamsType): Promise => { const fetchConfigFromServer = !customNetworkConfig?.skipFetchFromServer; const customNetworkApiAddress = customNetworkConfig?.apiAddress; const fallbackConfig = fallbackNetworkConfigurations[environment] || {}; diff --git a/src/store/actions/transactions/transactionsActions.ts b/src/store/actions/transactions/transactionsActions.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/store/selectors/networkSelectors.ts b/src/store/selectors/networkSelectors.ts index fe6f319..5d9f44a 100644 --- a/src/store/selectors/networkSelectors.ts +++ b/src/store/selectors/networkSelectors.ts @@ -6,3 +6,6 @@ export const networkSelector = ({ network }: StoreType) => network.network; export const chainIdSelector = ({ network: { network } }: StoreType) => network.chainId; + +export const apiAddressSelector = ({ network: { network } }: StoreType) => + network.apiAddress; diff --git a/src/store/selectors/transactionsSelectors.ts b/src/store/selectors/transactionsSelectors.ts index 1a0103a..b68c98a 100644 --- a/src/store/selectors/transactionsSelectors.ts +++ b/src/store/selectors/transactionsSelectors.ts @@ -1,11 +1,10 @@ -import { Transaction } from '@multiversx/sdk-core'; - import { StoreType } from 'store/store.types'; import { getIsTransactionFailed, getIsTransactionPending, getIsTransactionSuccessful, - getIsTransactionTimedOut + getIsTransactionTimedOut, + newTransaction } from 'utils/transactions'; import { CustomTransactionInformation, @@ -13,79 +12,68 @@ import { SignedTransactionsType } from '../../types'; -export const accountSelector = ({ - transactions: { accounts, address } -}: StoreType) => accounts[address]; +export const signedTransactionsSelector = ({ transactions }: StoreType) => + transactions.signedTransactions; -export const signedTransactionsSelector = createDeepEqualSelector( - transactionsSelectors, - (state) => state.signedTransactions as SignedTransactionsType -); +export const signTransactionsErrorSelector = ({ transactions }: StoreType) => + transactions.signTransactionsError; -export const signTransactionsErrorSelector = createDeepEqualSelector( - transactionsSelectors, - (state) => state.signTransactionsError -); - -export const signTransactionsCancelMessageSelector = createDeepEqualSelector( - transactionsSelectors, - (state) => state.signTransactionsCancelMessage -); +export const signTransactionsCancelMessageSelector = ({ + transactions +}: StoreType) => transactions.signTransactionsCancelMessage; const selectTxByStatus = - (txStatusVerifier: typeof getIsTransactionPending) => - (signedTransactions: SignedTransactionsType) => - Object.entries(signedTransactions).reduce((acc, [sessionId, txBody]) => { - if (txStatusVerifier(txBody.status)) { - acc[sessionId] = txBody; - } - return acc; - }, {} as SignedTransactionsType); + (txStatusVerifier: typeof getIsTransactionPending) => (store: StoreType) => { + const signedTransactions = signedTransactionsSelector(store); + + return Object.entries(signedTransactions).reduce( + (acc, [sessionId, txBody]) => { + if (txStatusVerifier(txBody.status)) { + acc[sessionId] = txBody; + } + return acc; + }, + {} as SignedTransactionsType + ); + }; -export const pendingSignedTransactionsSelector = createDeepEqualSelector( - signedTransactionsSelector, - selectTxByStatus(getIsTransactionPending) +export const pendingSignedTransactionsSelector = selectTxByStatus( + getIsTransactionPending ); -export const successfulTransactionsSelector = createDeepEqualSelector( - signedTransactionsSelector, - selectTxByStatus(getIsTransactionSuccessful) +export const successfulTransactionsSelector = selectTxByStatus( + getIsTransactionSuccessful ); -export const failedTransactionsSelector = createDeepEqualSelector( - signedTransactionsSelector, - selectTxByStatus(getIsTransactionFailed) +export const failedTransactionsSelector = selectTxByStatus( + getIsTransactionFailed ); -export const timedOutTransactionsSelector = createDeepEqualSelector( - signedTransactionsSelector, - selectTxByStatus(getIsTransactionTimedOut) +export const timedOutTransactionsSelector = selectTxByStatus( + getIsTransactionTimedOut ); -export const transactionsToSignSelector = createDeepEqualSelector( - transactionsSelectors, - (state): TransactionsToSignReturnType | null => { - if (state?.transactionsToSign == null) { - return null; - } - return { - ...state.transactionsToSign, - transactions: - state?.transactionsToSign?.transactions.map((tx: RawTransactionType) => - newTransaction(tx) - ) || [] - }; +export const transactionsToSignSelector = ({ transactions }: StoreType) => { + const transactionsToSign = transactions.transactionsToSign; + + if (transactionsToSign == null) { + return null; } -); -export const transactionStatusSelector = createDeepEqualSelector( - signedTransactionsSelector, - (_: RootState, transactionSessionId: string | null) => transactionSessionId, - ( - signedTransactions: SignedTransactionsType, - transactionSessionId: string | null - ) => - transactionSessionId != null + return { + ...transactionsToSign, + transactions: + transactionsToSign?.transactions.map((tx: RawTransactionType) => + newTransaction(tx) + ) || [] + }; +}; + +export const transactionStatusSelector = + (transactionSessionId: number) => (store: StoreType) => { + const signedTransactions = signedTransactionsSelector(store); + + return signedTransactions.transactionSessionId != null ? signedTransactions?.[transactionSessionId] || {} - : {} -); + : {}; + }; diff --git a/src/store/slices/transactionsSlice/transactionsSlice.ts b/src/store/slices/transactionsSlice/transactionsSlice.ts index 0edf994..f2eb03d 100644 --- a/src/store/slices/transactionsSlice/transactionsSlice.ts +++ b/src/store/slices/transactionsSlice/transactionsSlice.ts @@ -1,8 +1,8 @@ -import { TransactionsSliceStateType } from './transactionsSlice.types'; +import { TransactionsSliceType } from './transactionsSlice.types'; import { StateCreator } from 'zustand/vanilla'; import { MutatorsIn, StoreType } from 'store/store.types'; -export const initialState: TransactionsSliceStateType = { +export const initialState: TransactionsSliceType = { signedTransactions: {}, transactionsToSign: null, signTransactionsError: null, @@ -14,7 +14,7 @@ function getTransactionsSlice(): StateCreator< StoreType, MutatorsIn, [], - TransactionsSliceStateType + TransactionsSliceType > { return () => initialState; } diff --git a/src/store/slices/transactionsSlice/transactionsSlice.types.ts b/src/store/slices/transactionsSlice/transactionsSlice.types.ts index 9d4e5e8..0b251c1 100644 --- a/src/store/slices/transactionsSlice/transactionsSlice.types.ts +++ b/src/store/slices/transactionsSlice/transactionsSlice.types.ts @@ -33,7 +33,7 @@ export interface UpdateSignedTransactionStatusPayloadType { inTransit?: boolean; } -export interface TransactionsSliceStateType { +export interface TransactionsSliceType { signedTransactions: SignedTransactionsType; transactionsToSign: TransactionsToSignType | null; signTransactionsError: string | null; diff --git a/src/store/store.types.ts b/src/store/store.types.ts index 8145523..8f02ba3 100644 --- a/src/store/store.types.ts +++ b/src/store/store.types.ts @@ -2,12 +2,14 @@ import { AccountSliceType } from './slices/account/account.types'; import { LoginInfoSliceType } from './slices/loginInfo/loginInfo.types'; import { NetworkSliceType } from './slices/network/networkSlice.types'; import { ConfigSliceType } from './slices/config/config.types'; +import { TransactionsSliceType } from './slices'; export type StoreType = { - network: NetworkSliceType; account: AccountSliceType; - loginInfo: LoginInfoSliceType; config: ConfigSliceType; + loginInfo: LoginInfoSliceType; + network: NetworkSliceType; + transactions: TransactionsSliceType; }; export type MutatorsIn = [ diff --git a/src/types/transactions.types.ts b/src/types/transactions.types.ts index ec1e37b..58bf786 100644 --- a/src/types/transactions.types.ts +++ b/src/types/transactions.types.ts @@ -300,7 +300,7 @@ export interface InterpretedTransactionType extends ServerTransactionType { }; } -export interface DecodeForDisplayPropsType { +export interface DecodeForDisplayParamsType { input: string; decodeMethod: DecodeMethodEnum; identifier?: string; @@ -354,6 +354,51 @@ export interface TransactionsToSignType { customTransactionInformation: CustomTransactionInformation; } +export interface SendSimpleTransactionParamsType { + transactions: SimpleTransactionType[]; + minGasLimit?: number; +} + +export interface SendTransactionsParamsType { + transactions: + | Transaction + | SimpleTransactionType + | (Transaction | SimpleTransactionType)[]; + redirectAfterSign?: boolean; + signWithoutSending: boolean; + skipGuardian?: boolean; + completedTransactionsDelay?: number; + callbackRoute?: string; + transactionsDisplayInfo: TransactionsDisplayInfoType; + minGasLimit?: number; + sessionInformation?: any; + hasConsentPopup?: boolean; +} + +export interface SendBatchTransactionsParamsType { + transactions: (Transaction | SimpleTransactionType)[][]; + redirectAfterSign?: boolean; + signWithoutSending?: boolean; + skipGuardian?: boolean; + /** + * For Cross-Window provider in Safari browser, performing async calls before signing transactions needs a consent popup in order to open a new tab. + */ + hasConsentPopup?: boolean; + completedTransactionsDelay?: number; + callbackRoute?: string; + transactionsDisplayInfo: TransactionsDisplayInfoType; + minGasLimit?: number; + sessionInformation?: any; +} + +export interface SignTransactionsParamsType { + transactions: Transaction[] | Transaction; + minGasLimit?: number; // unused, will be removed in v3.0.0 + callbackRoute?: string; + transactionsDisplayInfo: TransactionsDisplayInfoType; + customTransactionInformation: CustomTransactionInformation; +} + export interface SignedTransactionsBodyType { transactions?: SignedTransactionType[]; status?: TransactionBatchStatusesEnum; @@ -450,7 +495,7 @@ export interface TransactionsDisplayInfoType { invalidMessage?: string; } -export interface SendSimpleTransactionPropsType { +export interface SendSimpleTransactionParamsType { transactions: SimpleTransactionType[]; minGasLimit?: number; } diff --git a/src/utils/account/getShardOfAddress.ts b/src/utils/account/getShardOfAddress.ts new file mode 100644 index 0000000..265e887 --- /dev/null +++ b/src/utils/account/getShardOfAddress.ts @@ -0,0 +1,33 @@ +import { METACHAIN_SHARD_ID } from 'constants/index'; + +const isAddressOfMetachain = (pubKey: Buffer) => { + // prettier-ignore + const metachainPrefix = Buffer.from([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]); + const pubKeyPrefix = pubKey.slice(0, metachainPrefix.length); + if (pubKeyPrefix.equals(metachainPrefix)) { + return true; + } + const zeroAddress = Buffer.alloc(32).fill(0); + return pubKey.equals(zeroAddress); +}; +export const getShardOfAddress = (hexPubKey: any) => { + try { + const numShards = 3; + const maskHigh = parseInt('11', 2); + const maskLow = parseInt('01', 2); + const pubKey = Buffer.from(hexPubKey, 'hex'); + const lastByteOfPubKey = pubKey[31]; + if (isAddressOfMetachain(pubKey)) { + return METACHAIN_SHARD_ID; + } + let shard = lastByteOfPubKey & maskHigh; + if (shard > numShards - 1) { + shard = lastByteOfPubKey & maskLow; + } + return shard; + } catch (err) { + return -1; + } +}; diff --git a/src/utils/account/index.ts b/src/utils/account/index.ts index 7603022..3b79cf3 100644 --- a/src/utils/account/index.ts +++ b/src/utils/account/index.ts @@ -1 +1,2 @@ export * from './getAccount'; +export * from './getShardOfAddress'; diff --git a/src/utils/decoders/decodePart.ts b/src/utils/decoders/decodePart.ts index cb77935..333ba84 100644 --- a/src/utils/decoders/decodePart.ts +++ b/src/utils/decoders/decodePart.ts @@ -1,6 +1,6 @@ import { isUtf8 } from './isUtf8'; -export function decodePart(part: string) { +export const decodePart = (part: string) => { let decodedPart = part; try { @@ -12,4 +12,4 @@ export function decodePart(part: string) { } catch (error) {} return decodedPart; -} +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index c14cfcf..308d1ba 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,7 +1,9 @@ -export * from './dateTime'; -export * from './window'; +export * from './account'; export * from './asyncActions'; +export * from './dateTime'; export * from './decoders'; -export * from './validation'; -export * from './account'; +export * from './operations'; export * from './retryMultipleTimes'; +export * from './validation'; +export * from './walletconnect'; +export * from './window'; diff --git a/src/utils/operations/formatAmount.ts b/src/utils/operations/formatAmount.ts new file mode 100644 index 0000000..a2b6213 --- /dev/null +++ b/src/utils/operations/formatAmount.ts @@ -0,0 +1,140 @@ +import { TokenTransfer } from '@multiversx/sdk-core'; +import BigNumber from 'bignumber.js'; +import { DECIMALS, DIGITS, ZERO } from 'constants/index'; +import { stringIsInteger } from '../validation'; +import { pipe } from './pipe'; + +BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_FLOOR }); + +export interface FormatAmountType { + input: string; + decimals?: number; + digits?: number; + showIsLessThanDecimalsLabel?: boolean; + showLastNonZeroDecimal?: boolean; + addCommas?: boolean; +} + +export function formatAmount({ + input, + decimals = DECIMALS, + digits = DIGITS, + showLastNonZeroDecimal = true, + showIsLessThanDecimalsLabel = false, + addCommas = false +}: FormatAmountType) { + if (!stringIsInteger(input, false)) { + throw new Error('Invalid input'); + } + + const isNegative = new BigNumber(input).isNegative(); + let modInput = input; + + if (isNegative) { + // remove - at start of input + modInput = input.substring(1); + } + + return ( + pipe(modInput as string) + // format + .then(() => + TokenTransfer.fungibleFromBigInteger('', modInput as string, decimals) + .amountAsBigInteger.shiftedBy(-decimals) + .toFixed(decimals) + ) + + // format + .then((current) => { + const bnBalance = new BigNumber(current); + + if (bnBalance.isZero()) { + return ZERO; + } + const balance = bnBalance.toString(10); + const [integerPart, decimalPart] = balance.split('.'); + const bNdecimalPart = new BigNumber(decimalPart || 0); + + const decimalPlaces = pipe(0) + .if(Boolean(decimalPart && showLastNonZeroDecimal)) + .then(() => Math.max(decimalPart.length, digits)) + + .if(bNdecimalPart.isZero() && !showLastNonZeroDecimal) + .then(0) + + .if(Boolean(decimalPart && !showLastNonZeroDecimal)) + .then(() => Math.min(decimalPart.length, digits)) + + .valueOf(); + + const shownDecimalsAreZero = + decimalPart && + digits >= 1 && + digits <= decimalPart.length && + bNdecimalPart.isGreaterThan(0) && + new BigNumber(decimalPart.substring(0, digits)).isZero(); + + const formatted = bnBalance.toFormat(decimalPlaces); + + const formattedBalance = pipe(balance) + .if(addCommas) + .then(formatted) + + .if(Boolean(shownDecimalsAreZero)) + .then((current) => { + const integerPartZero = new BigNumber(integerPart).isZero(); + const [numericPart, decimalSide] = current.split('.'); + + const zeroPlaceholders = new Array(digits - 1).fill(0); + const zeros = [...zeroPlaceholders, 0].join(''); + const minAmount = [...zeroPlaceholders, 1].join(''); // 00..1 + + if (!integerPartZero) { + return `${numericPart}.${zeros}`; + } + + if (showIsLessThanDecimalsLabel) { + return `<${numericPart}.${minAmount}`; + } + + if (!showLastNonZeroDecimal) { + return numericPart; + } + + return `${numericPart}.${decimalSide}`; + }) + + .if(Boolean(!shownDecimalsAreZero && decimalPart)) + .then((current) => { + const [numericPart] = current.split('.'); + let decimalSide = decimalPart.substring(0, decimalPlaces); + + if (showLastNonZeroDecimal) { + const noOfZerosAtEnd = digits - decimalSide.length; + + if (noOfZerosAtEnd > 0) { + const zeroPadding = Array(noOfZerosAtEnd).fill(0).join(''); + decimalSide = `${decimalSide}${zeroPadding}`; + return `${numericPart}.${decimalSide}`; + } + + return current; + } + + if (!decimalSide) { + return numericPart; + } + + return `${numericPart}.${decimalSide}`; + }) + + .valueOf(); + + return formattedBalance; + }) + .if(isNegative) + .then((current) => `-${current}`) + + .valueOf() + ); +} diff --git a/src/utils/operations/index.ts b/src/utils/operations/index.ts new file mode 100644 index 0000000..5a1c31e --- /dev/null +++ b/src/utils/operations/index.ts @@ -0,0 +1,2 @@ +export * from './formatAmount'; +export * from './pipe'; diff --git a/src/utils/operations/pipe.ts b/src/utils/operations/pipe.ts new file mode 100644 index 0000000..97f28d0 --- /dev/null +++ b/src/utils/operations/pipe.ts @@ -0,0 +1,26 @@ +export function pipe(previous: ValueType) { + return { + if: function (condition: boolean) { + if (condition) { + return { + then: (newValue: ValueType | ((prop: ValueType) => ValueType)) => + // if a callback is passed, callback is executed with previous value + newValue instanceof Function + ? pipe(newValue(previous)) + : pipe(newValue) + }; + } else { + return { + then: () => pipe(previous) + }; + } + }, + + then: (newValue: ValueType | ((prop: ValueType) => ValueType)) => + newValue instanceof Function ? pipe(newValue(previous)) : pipe(newValue), + + valueOf: function () { + return previous; + } + }; +} diff --git a/src/utils/operations/tests/formatAmount.test.ts b/src/utils/operations/tests/formatAmount.test.ts new file mode 100644 index 0000000..68b187b --- /dev/null +++ b/src/utils/operations/tests/formatAmount.test.ts @@ -0,0 +1,247 @@ +import { formatAmount } from '../formatAmount'; + +describe('format with 4,4', () => { + const numbers: { [key: string]: string } = { + '9999999999999999999999990000': '999,999,999,999,999,999,999,999', + '0': '0' + }; + const decimals = 4; + const digits = 4; + for (let i = 0; i < Object.keys(numbers).length; i++) { + const input = Object.keys(numbers)[i]; + const output = numbers[input]; + it(`format ${input} -> ${output}`, () => { + const withCommas = formatAmount({ + input, + decimals, + digits, + showLastNonZeroDecimal: false, + addCommas: true + }); + expect(withCommas).toBe(output); + }); + } +}); + +describe('format with 8,4', () => { + const numbers: { [key: string]: string } = { + '9999999999999999999899996000': '99,999,999,999,999,999,998.9999', + '0': '0', + '10000': '0.0001' + }; + const decimals = 8; + const digits = 4; + for (let i = 0; i < Object.keys(numbers).length; i++) { + const input = Object.keys(numbers)[i]; + const output = numbers[input]; + it(`format ${input} -> ${output}`, () => { + const withCommas = formatAmount({ + input, + decimals, + digits, + showLastNonZeroDecimal: false, + addCommas: true + }); + expect(withCommas).toBe(output); + }); + } +}); + +describe('format with 0,0', () => { + const numbers: { [key: string]: string } = { + '350': '350' + }; + const decimals = 0; + const digits = 0; + for (let i = 0; i < Object.keys(numbers).length; i++) { + const input = Object.keys(numbers)[i]; + const output = numbers[input]; + it(`format ${input} -> ${output}`, () => { + const withCommas = formatAmount({ + input, + decimals, + digits, + showLastNonZeroDecimal: false + }); + expect(withCommas).toBe(output); + }); + } +}); + +describe('format with 4,8,true', () => { + const numbers: { [key: string]: string } = { + '12345678901234567890123': '123,456,789,012,345.67890123' + }; + const decimals = 8; + const digits = 4; + for (let i = 0; i < Object.keys(numbers).length; i++) { + const input = Object.keys(numbers)[i]; + const output = numbers[input]; + it(`format ${input} -> ${output}`, () => { + const withCommas = formatAmount({ + input, + decimals, + digits, + showLastNonZeroDecimal: true, + addCommas: true + }); + expect(withCommas).toBe(output); + }); + } +}); + +describe('format with 18,0,true', () => { + const numbers: { [key: string]: string } = { + '102000000000000000': '0.102', + '100000000000000000': '0.1', + '1000000000000000000': '1' + }; + const decimals = 18; + const digits = 0; + for (let i = 0; i < Object.keys(numbers).length; i++) { + const input = Object.keys(numbers)[i]; + const output = numbers[input]; + it(`format ${input} -> ${output}`, () => { + const withCommas = formatAmount({ + input, + decimals, + digits, + showLastNonZeroDecimal: true + }); + expect(withCommas).toBe(output); + }); + } +}); + +describe('format with float throws error', () => { + const numbers: { [key: string]: string } = { + '0.015': 'Throws error', + '01000000000000000000': 'Throws error' + }; + const decimals = 18; + const digits = 4; + for (let i = 0; i < Object.keys(numbers).length; i++) { + const input = Object.keys(numbers)[i]; + const output = numbers[input]; + it(`format ${input} -> ${output}`, () => { + let err = ''; + try { + formatAmount({ + input, + decimals, + digits, + addCommas: false, + showLastNonZeroDecimal: true + }); + expect(err).toBeInstanceOf(Error); + } catch (error) { + err = error as any; + expect(err).toBeInstanceOf(Error); + expect(error).toHaveProperty('message', 'Invalid input'); + } + }); + } +}); + +describe('format with negative', () => { + const numbers: { [key: string]: string } = { + '-922506751086064008': '-0.922506751086064008', + '-578345000000000000000': '-578.3450', + '-1578345000000000000000': '-1,578.3450', + '-3456000000000000000': '-3.4560' + }; + const decimals = 18; + const digits = 4; + for (let i = 0; i < Object.keys(numbers).length; i++) { + const input = Object.keys(numbers)[i]; + const output = numbers[input]; + it(`format ${input} -> ${output}`, () => { + const withCommas = formatAmount({ + input, + decimals, + digits, + showLastNonZeroDecimal: true, + addCommas: true + }); + expect(withCommas).toBe(output); + }); + } +}); + +describe('format with single tests', () => { + it('should show less than if decimal amount is too low', () => { + const result = formatAmount({ + input: (100_000_000_000_000).toString(), + digits: 2, + showIsLessThanDecimalsLabel: true, + showLastNonZeroDecimal: false + }); + expect(result).toBe('<0.01'); + }); + it('should not show digits when result is below 1', () => { + const result = formatAmount({ + input: (100_000_000_000_000).toString(), + showLastNonZeroDecimal: false, + digits: 2 + }); + expect(result).toBe('0'); + }); + it('should show zero digits for integers with decimal amount too low', () => { + const result = formatAmount({ + input: ['1', '000', '000', '001', '000', '000', '000', '000'].join(''), + digits: 2, + showLastNonZeroDecimal: false + }); + expect(result).toBe('1000.00'); + }); + it('should show a valid number if showLastNonZeroDecimal is set', () => { + const result = formatAmount({ + input: (1_000_000_000).toString(), + digits: 4, + showLastNonZeroDecimal: true + }); + expect(result).toBe('0.000000001'); + }); + + it('should show remove digits and not add commas', () => { + const result = formatAmount({ + input: '369884288127092846270928', + digits: 4, + showLastNonZeroDecimal: false, + addCommas: false + }); + expect(result).toBe('369884.2881'); + }); + + it('should not add . at the end for 0 digits', () => { + const result = formatAmount({ + input: '369884288127092846270928', + digits: 0, + showLastNonZeroDecimal: false, + addCommas: false + }); + expect(result).toBe('369884'); + }); + + describe('should show all 4 digits', () => { + const numbers: { [key: string]: string } = { + '995000000000000000': '0.9950' + }; + const decimals = 18; + const digits = 4; + for (let i = 0; i < Object.keys(numbers).length; i++) { + const input = Object.keys(numbers)[i]; + const output = numbers[input]; + it(`format ${input} -> ${output}`, () => { + const withCommas = formatAmount({ + input, + decimals, + digits, + showLastNonZeroDecimal: true, + addCommas: true + }); + expect(withCommas).toBe(output); + }); + } + }); +}); diff --git a/src/utils/operations/tests/pipe.test.ts b/src/utils/operations/tests/pipe.test.ts new file mode 100644 index 0000000..c26ea48 --- /dev/null +++ b/src/utils/operations/tests/pipe.test.ts @@ -0,0 +1,29 @@ +import { pipe } from '../pipe'; + +describe('pipe tests', () => { + test('pipe basic test', () => { + const result = pipe(2) + .if(false) + .then(3) + + .if(true) + .then((current) => current + 2) + + .if(false) + .then(5) + + .then((current) => current + 2) + .valueOf(); + + expect(result).toStrictEqual(6); + }); + test('pipe undefined test', () => { + const result = pipe(undefined) + .if(true) + .then((current) => current) + .then((current) => current) + .valueOf(); + + expect(result).toStrictEqual(undefined); + }); +}); diff --git a/src/utils/transactions/batch/generateBatchTransactionsGrouping.ts b/src/utils/transactions/batch/generateBatchTransactionsGrouping.ts index 8edbc56..abc2908 100644 --- a/src/utils/transactions/batch/generateBatchTransactionsGrouping.ts +++ b/src/utils/transactions/batch/generateBatchTransactionsGrouping.ts @@ -1,8 +1,6 @@ -export const generateBatchTransactionsGrouping = ( - transactions: T[][] -) => { +export function generateBatchTransactionsGrouping(transactions: T[][]) { let indexInFlatArray = 0; return transactions.map((group) => { return group.map(() => indexInFlatArray++); }); -}; +} diff --git a/src/utils/transactions/batch/getBatchTransactionsStatusFromApi.ts b/src/utils/transactions/batch/getBatchTransactionsStatusFromApi.ts new file mode 100644 index 0000000..3938f64 --- /dev/null +++ b/src/utils/transactions/batch/getBatchTransactionsStatusFromApi.ts @@ -0,0 +1,26 @@ +import axios from 'axios'; +import { TRANSACTIONS_BATCH } from 'apiCalls'; +import { BatchTransactionsResponseType } from 'types'; +import { networkSelector } from 'store/selectors'; +import { getState } from 'store/store'; + +export interface SendBatchTransactionsParamsType { + batchId: string; + address: string; +} + +export async function getBatchTransactionsStatusFromApi({ + batchId, + address +}: SendBatchTransactionsParamsType) { + const { apiAddress, apiTimeout } = networkSelector(getState()); + + const { data } = await axios.get( + `${apiAddress}/${TRANSACTIONS_BATCH}/${address}/${batchId}`, + { + timeout: Number(apiTimeout ?? TIMEOUT) + } + ); + + return data; +} diff --git a/src/utils/transactions/batch/getTransactionsStatus.ts b/src/utils/transactions/batch/getTransactionsStatus.ts index 0bfed23..68f5512 100644 --- a/src/utils/transactions/batch/getTransactionsStatus.ts +++ b/src/utils/transactions/batch/getTransactionsStatus.ts @@ -1,12 +1,12 @@ import { TransactionServerStatusesEnum } from 'types'; -export const getTransactionsStatus = ({ +export function getTransactionsStatus({ transactions, hasUnrelatedTransactions }: { transactions: { status: TransactionServerStatusesEnum }[]; hasUnrelatedTransactions?: boolean; -}) => { +}) { const allTxFailed = transactions.every( ({ status }) => status === TransactionServerStatusesEnum.fail ); @@ -34,4 +34,4 @@ export const getTransactionsStatus = ({ : someTxFailed; return { isPending, isSuccessful, isFailed, isIncompleteFailed }; -}; +} diff --git a/src/utils/transactions/batch/index.ts b/src/utils/transactions/batch/index.ts new file mode 100644 index 0000000..8b2fcc5 --- /dev/null +++ b/src/utils/transactions/batch/index.ts @@ -0,0 +1,5 @@ +export * from './generateBatchTransactionsGrouping'; +export * from './getIsSequential'; +export * from './getTransactionsStatus'; +export * from './sequentialToFlatArray'; +export * from './updateBatchTransactionsStatuses'; diff --git a/src/utils/transactions/batch/sendBatchTransactions.test.ts b/src/utils/transactions/batch/sendBatchTransactions.test.ts new file mode 100644 index 0000000..3512ab1 --- /dev/null +++ b/src/utils/transactions/batch/sendBatchTransactions.test.ts @@ -0,0 +1,215 @@ +import { addressSelector } from 'reduxStore/selectors'; +import { store } from 'reduxStore/store'; +import { getWindowLocation } from 'utils/window/getWindowLocation'; +import { sendBatchTransactions } from './sendBatchTransactions'; +import { signTransactions } from '../operations/signTransactions'; +import { transformTransactionsToSign } from './utils/transformTransactionsToSign'; + +jest.mock('reduxStore/selectors', () => ({ + addressSelector: jest.fn() +})); + +jest.mock('reduxStore/store', () => ({ + store: { + getState: jest.fn() + } +})); + +jest.mock('utils/window/getWindowLocation', () => ({ + getWindowLocation: jest.fn() +})); + +jest.mock('../operations/signTransactions', () => ({ + signTransactions: jest.fn() +})); + +jest.mock('./utils/transformTransactionsToSign', () => ({ + transformTransactionsToSign: jest.fn() +})); + +describe('sendBatchTransactions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should call all the dependencies and return the expected result', async () => { + const address = + 'erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv'; + const sessionId = '12345'; + const transactions = [ + [ + { + receiver: address, + sender: address, + value: '0', + data: '1' + }, + { + receiver: address, + sender: address, + value: '0', + data: '2' + } + ], + [ + { + receiver: address, + sender: address, + value: '0', + data: '3' + } + ], + [ + { + receiver: address, + sender: address, + value: '0', + data: '4' + }, + { + receiver: address, + sender: address, + value: '0', + data: '5' + }, + { + receiver: address, + sender: address, + value: '0', + data: '6' + } + ] + ]; + const transactionsDisplayInfo = {}; + const callbackRoute = '/callback'; + const minGasLimit = 21000; + + // Mock the dependencies + (addressSelector as unknown as jest.Mock).mockReturnValue(address); + // eslint-disable-next-line @typescript-eslint/no-empty-function + (store.getState as unknown as jest.Mock).mockReturnValue(() => {}); + (getWindowLocation as unknown as jest.Mock).mockReturnValue({ + pathname: callbackRoute + }); + (signTransactions as unknown as jest.Mock).mockResolvedValue({ sessionId }); + (transformTransactionsToSign as unknown as jest.Mock).mockResolvedValue([]); + + const result = await sendBatchTransactions({ + transactions, + transactionsDisplayInfo, + minGasLimit + }); + + expect(addressSelector).toHaveBeenCalled(); + expect(store.getState).toHaveBeenCalled(); + expect(getWindowLocation).toHaveBeenCalled(); + expect(transformTransactionsToSign).toHaveBeenCalledWith({ + transactions: expect.any(Array), + minGasLimit + }); + expect(signTransactions).toHaveBeenCalledWith({ + transactions: expect.any(Array), + minGasLimit, + callbackRoute, + transactionsDisplayInfo, + customTransactionInformation: { + grouping: expect.any(Array), + redirectAfterSign: true, + completedTransactionsDelay: undefined, + sessionInformation: undefined, + skipGuardian: undefined, + signWithoutSending: false + } + }); + + expect(result).toEqual({ + error: undefined, + batchId: `${sessionId}-${address}` + }); + }); + + it('should prepare the grouping field with the indexes from the flat transactions array', async () => { + const address = + 'erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv'; + const sessionId = '12345'; + const transactions = [ + [ + { + receiver: address, + sender: address, + value: '0', + data: '1' + }, + { + receiver: address, + sender: address, + value: '0', + data: '2' + } + ], + [ + { + receiver: address, + sender: address, + value: '0', + data: '3' + } + ], + [ + { + receiver: address, + sender: address, + value: '0', + data: '4' + }, + { + receiver: address, + sender: address, + value: '0', + data: '5' + }, + { + receiver: address, + sender: address, + value: '0', + data: '6' + } + ] + ]; + const transactionsDisplayInfo = {}; // Your test display info + const callbackRoute = '/callback'; + const minGasLimit = 21000; + + // Mock the dependencies + (addressSelector as unknown as jest.Mock).mockReturnValue(address); + (store.getState as unknown as jest.Mock).mockReturnValue({}); + (getWindowLocation as unknown as jest.Mock).mockReturnValue({ + pathname: callbackRoute + }); + (signTransactions as unknown as jest.Mock).mockResolvedValue({ sessionId }); + (transformTransactionsToSign as unknown as jest.Mock).mockResolvedValue( + transactions + ); + + await sendBatchTransactions({ + transactions, + transactionsDisplayInfo, + minGasLimit + }); + + expect(signTransactions).toHaveBeenCalledWith({ + transactions, + minGasLimit, + callbackRoute, + transactionsDisplayInfo, + customTransactionInformation: { + grouping: [[0, 1], [2], [3, 4, 5]], + redirectAfterSign: true, + completedTransactionsDelay: undefined, + sessionInformation: undefined, + skipGuardian: undefined, + signWithoutSending: false + } + }); + }); +}); diff --git a/src/utils/transactions/batch/sendBatchTransactions.ts b/src/utils/transactions/batch/sendBatchTransactions.ts new file mode 100644 index 0000000..a3795e3 --- /dev/null +++ b/src/utils/transactions/batch/sendBatchTransactions.ts @@ -0,0 +1,61 @@ +import { Transaction } from '@multiversx/sdk-core/out'; +import { addressSelector } from 'reduxStore/selectors'; +import { store } from 'reduxStore/store'; +import { + SendBatchTransactionReturnType, + SendBatchTransactionsParamsType, + SimpleTransactionType +} from 'types'; +import { generateBatchTransactionsGrouping } from 'utils/transactions/batch/generateBatchTransactionsGrouping'; +import { getDefaultCallbackUrl } from 'utils/window'; +import { signTransactions } from '../operations/signTransactions'; +import { transformTransactionsToSign } from './utils/transformTransactionsToSign'; + +export async function sendBatchTransactions({ + transactions, + transactionsDisplayInfo, + redirectAfterSign = true, + callbackRoute = getDefaultCallbackUrl(), + signWithoutSending = false, + completedTransactionsDelay, + sessionInformation, + skipGuardian, + hasConsentPopup, + minGasLimit +}: SendBatchTransactionsParamsType): Promise { + try { + const address = addressSelector(store.getState()); + const transactionsPayload = transactions.flat(); + + const transactionsToSign = await transformTransactionsToSign({ + transactions: transactionsPayload as SimpleTransactionType[], + minGasLimit + }); + + const grouping = generateBatchTransactionsGrouping(transactions); + + const { sessionId, error } = await signTransactions({ + transactions: transactionsToSign as Transaction[], + minGasLimit, + callbackRoute, + transactionsDisplayInfo, + customTransactionInformation: { + grouping, + redirectAfterSign, + completedTransactionsDelay, + sessionInformation, + skipGuardian, + signWithoutSending, + hasConsentPopup + } + }); + + return { + error, + batchId: `${sessionId}-${address}` + }; + } catch (err) { + console.error('error signing transaction', err as any); + return { error: err as any, batchId: null }; + } +} diff --git a/src/utils/transactions/batch/updateBatchTransactionsStatuses.ts b/src/utils/transactions/batch/updateBatchTransactionsStatuses.ts index 7af2ffc..8b3c845 100644 --- a/src/utils/transactions/batch/updateBatchTransactionsStatuses.ts +++ b/src/utils/transactions/batch/updateBatchTransactionsStatuses.ts @@ -5,7 +5,7 @@ import { } from 'types'; import { sequentialToFlatArray } from './sequentialToFlatArray'; -export const updateBatchTransactionsStatuses = ({ +export function updateBatchTransactionsStatuses({ batchId, sessionId, transactions @@ -13,7 +13,7 @@ export const updateBatchTransactionsStatuses = ({ batchId: string; sessionId: string; transactions: SignedTransactionType[] | SignedTransactionType[][]; -}) => { +}) { const transactionsArray = sequentialToFlatArray({ transactions }); @@ -42,4 +42,4 @@ export const updateBatchTransactionsStatuses = ({ if (batchIsSuccessful) { removeBatchTransactions(batchId); } -}; +} diff --git a/src/utils/transactions/calculateTotalTransactionsFee.ts b/src/utils/transactions/calculateTotalTransactionsFee.ts new file mode 100644 index 0000000..a4d2fdb --- /dev/null +++ b/src/utils/transactions/calculateTotalTransactionsFee.ts @@ -0,0 +1,23 @@ +import { Transaction } from '@multiversx/sdk-core/out'; +import BigNumber from 'bignumber.js'; +import { GAS_PER_DATA_BYTE, GAS_PRICE_MODIFIER } from 'constants/index'; +import { calculateFeeLimit } from './operations'; + +export function calcTotalFee(transactions: Transaction[], minGasLimit: number) { + let totalFee = new BigNumber(0); + + transactions.forEach((tx) => { + const fee = calculateFeeLimit({ + gasPerDataByte: String(GAS_PER_DATA_BYTE), + gasPriceModifier: String(GAS_PRICE_MODIFIER), + minGasLimit: String(minGasLimit), + gasLimit: tx.getGasLimit().valueOf().toString(), + gasPrice: tx.getGasPrice().valueOf().toString(), + data: tx.getData().toString(), + chainId: tx.getChainID().valueOf() + }); + totalFee = totalFee.plus(new BigNumber(fee)); + }); + + return totalFee; +} diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/decodeTransactionData.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/decodeTransactionData.ts index 68b05ff..768fb5c 100644 --- a/src/utils/transactions/dataDecoders/decodeTransactionData/decodeTransactionData.ts +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/decodeTransactionData.ts @@ -1,8 +1,8 @@ import { DecodedDisplayType, - DecodeForDisplayPropsType, + DecodeForDisplayParamsType, DecodeMethodEnum -} from 'types/serverTransactions.types'; +} from 'types'; import { decodeByMethod, @@ -10,11 +10,11 @@ import { getSmartDecodedParts } from './helpers'; -export const decodeTransactionData = ({ +export function decodeTransactionData({ input, decodeMethod, identifier -}: DecodeForDisplayPropsType) => { +}: DecodeForDisplayParamsType) { const display: DecodedDisplayType = { displayValue: '', validationWarnings: [] @@ -63,4 +63,4 @@ export const decodeTransactionData = ({ } return display; -}; +} diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/decodeByMethod.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/decodeByMethod.ts index a8d0b71..f0363cd 100644 --- a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/decodeByMethod.ts +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/decodeByMethod.ts @@ -1,17 +1,14 @@ import { Address } from '@multiversx/sdk-core/out'; import BigNumber from 'bignumber.js'; -import { - DecodeMethodEnum, - TransactionTokensType -} from 'types/serverTransactions.types'; -import { addressIsValid } from 'utils/account/addressIsValid'; +import { DecodeMethodEnum, TransactionTokensType } from 'types'; import { isUtf8 } from 'utils/decoders'; +import { addressIsValid } from 'utils/validation'; -export const decodeByMethod = ( +export function decodeByMethod( part: string, decodeMethod: DecodeMethodEnum | string, transactionTokens?: TransactionTokensType -) => { +) { switch (decodeMethod) { case DecodeMethodEnum.text: try { @@ -58,4 +55,4 @@ export const decodeByMethod = ( default: return part; } -}; +} diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getDisplayValueAndValidationWarnings.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getDisplayValueAndValidationWarnings.ts index b4f031e..72e70c1 100644 --- a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getDisplayValueAndValidationWarnings.ts +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getDisplayValueAndValidationWarnings.ts @@ -3,19 +3,19 @@ import { decodeByMethod } from './decodeByMethod'; import { getHexValidationWarnings } from './getHexValidationWarnings'; import { getSmartDecodedParts } from './getSmartDecodedParts'; -interface GetDecodedPartsPropsType { +interface GetDecodedPartsParamsType { parts: string[]; decodeMethod: DecodeMethodEnum; identifier?: string; display: DecodedDisplayType; } -export const getDisplayValueAndValidationWarnings = ({ +export function getDisplayValueAndValidationWarnings({ parts, decodeMethod, identifier, display -}: GetDecodedPartsPropsType) => { +}: GetDecodedPartsParamsType) { const initialDecodedParts = parts.map((part, index) => { if ( parts.length >= 2 && @@ -49,4 +49,4 @@ export const getDisplayValueAndValidationWarnings = ({ : initialDecodedParts; return decodedParts; -}; +} diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getHexValidationWarnings.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getHexValidationWarnings.ts index a578926..87fcfc7 100644 --- a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getHexValidationWarnings.ts +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getHexValidationWarnings.ts @@ -1,12 +1,12 @@ -export const isHexValidCharacters = (str: string) => { +export function isHexValidCharacters(str: string) { return str.toLowerCase().match(/^[0-9a-f]+$/i); -}; +} -export const isHexValidLength = (str: string) => { +export function isHexValidLength(str: string) { return str.length % 2 === 0; -}; +} -export const getHexValidationWarnings = (str: string) => { +export function getHexValidationWarnings(str: string) { const warnings = []; if (str && !isHexValidCharacters(str)) { @@ -18,4 +18,4 @@ export const getHexValidationWarnings = (str: string) => { } return warnings; -}; +} diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getSmartDecodedParts.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getSmartDecodedParts.ts index ac78349..9989afd 100644 --- a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getSmartDecodedParts.ts +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getSmartDecodedParts.ts @@ -7,11 +7,11 @@ interface SmartDecodedPartsType { identifier?: string; } -export const getSmartDecodedParts = ({ +export function getSmartDecodedParts({ parts, decodedParts, identifier -}: SmartDecodedPartsType) => { +}: SmartDecodedPartsType) { const updatedParts = [...decodedParts]; if (parts[0] === TransactionTypesEnum.ESDTNFTTransfer && parts[2]) { @@ -27,4 +27,4 @@ export const getSmartDecodedParts = ({ } return updatedParts; -}; +} diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts index 8f1c0d9..2553b60 100644 --- a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts @@ -1,6 +1,6 @@ import { DecodeMethodEnum, - DecodeForDisplayPropsType + DecodeForDisplayParamsType } from 'types/serverTransactions.types'; import { decodeTransactionData } from '../decodeTransactionData'; import { @@ -29,7 +29,7 @@ describe('decodeForDisplay', () => { it('should handle input with @ symbol', () => { const input = 'part1@part2'; - const props: DecodeForDisplayPropsType = { + const props: DecodeForDisplayParamsType = { input, decodeMethod: DecodeMethodEnum.text, identifier: 'test' @@ -50,7 +50,7 @@ describe('decodeForDisplay', () => { it('should handle input with newline character using raw decode method', () => { const input = 'part1\npart2'; - const props: DecodeForDisplayPropsType = { + const props: DecodeForDisplayParamsType = { input, decodeMethod: DecodeMethodEnum.raw, identifier: 'test' @@ -64,7 +64,7 @@ describe('decodeForDisplay', () => { it('should handle input with newline character using smart decode method', () => { const input = 'part1\npart2'; - const props: DecodeForDisplayPropsType = { + const props: DecodeForDisplayParamsType = { input, decodeMethod: DecodeMethodEnum.smart, identifier: 'test' @@ -84,7 +84,7 @@ describe('decodeForDisplay', () => { it('should handle input with both @ and newline characters', () => { const input = 'part1@part2\npart3'; - const props: DecodeForDisplayPropsType = { + const props: DecodeForDisplayParamsType = { input, decodeMethod: DecodeMethodEnum.text, identifier: 'test' @@ -99,7 +99,7 @@ describe('decodeForDisplay', () => { it('should handle simple input without @ or newline', () => { const input = 'simpleInput'; - const props: DecodeForDisplayPropsType = { + const props: DecodeForDisplayParamsType = { input, decodeMethod: DecodeMethodEnum.text, identifier: 'test' @@ -122,7 +122,7 @@ describe('decodeForDisplay', () => { } ); - const props: DecodeForDisplayPropsType = { + const props: DecodeForDisplayParamsType = { input, decodeMethod: DecodeMethodEnum.text, identifier: 'test' @@ -134,7 +134,7 @@ describe('decodeForDisplay', () => { }); it('should handle empty input', () => { - const props: DecodeForDisplayPropsType = { + const props: DecodeForDisplayParamsType = { input: '', decodeMethod: DecodeMethodEnum.text, identifier: 'test' diff --git a/src/utils/transactions/dataDecoders/getDataPayloadForTransaction.ts b/src/utils/transactions/dataDecoders/getDataPayloadForTransaction.ts index ece5ccd..9699216 100644 --- a/src/utils/transactions/dataDecoders/getDataPayloadForTransaction.ts +++ b/src/utils/transactions/dataDecoders/getDataPayloadForTransaction.ts @@ -9,12 +9,12 @@ import { isStringBase64 } from '../../decoders'; * @see The tests for this function are in src/utils/transactions/tests/getDataPayloadForTransaction.test.ts * @param data - data field from transaction */ -export const getDataPayloadForTransaction = ( +export function getDataPayloadForTransaction( data?: string -): TransactionPayload => { +): TransactionPayload { const defaultData = data ?? ''; return isStringBase64(defaultData) ? TransactionPayload.fromEncoded(defaultData) : new TransactionPayload(defaultData); -}; +} diff --git a/src/utils/transactions/dataDecoders/getScResultsDecodedData.ts b/src/utils/transactions/dataDecoders/getScResultsDecodedData.ts index 409a176..586a626 100644 --- a/src/utils/transactions/dataDecoders/getScResultsDecodedData.ts +++ b/src/utils/transactions/dataDecoders/getScResultsDecodedData.ts @@ -1,6 +1,6 @@ import { decodePart } from 'utils/decoders/decodePart'; -export const getScResultsDecodedData = (data: string) => { +export function getScResultsDecodedData(data: string) { const parts = Buffer.from(data, 'base64').toString().split('@'); if (parts.length >= 2) { @@ -12,4 +12,4 @@ export const getScResultsDecodedData = (data: string) => { } return parts.join('@'); -}; +} diff --git a/src/utils/transactions/dataDecoders/getScResultsHighlight.ts b/src/utils/transactions/dataDecoders/getScResultsHighlight.ts index c42064d..00a6951 100644 --- a/src/utils/transactions/dataDecoders/getScResultsHighlight.ts +++ b/src/utils/transactions/dataDecoders/getScResultsHighlight.ts @@ -1,6 +1,6 @@ import { getWindowLocation } from 'utils/window/getWindowLocation'; -export const getScResultsHighlight = (resultHash: string) => { +export function getScResultsHighlight(resultHash: string) { const { hash } = getWindowLocation(); const formattedHash = hash .substring(0, hash.indexOf('/') > 0 ? hash.indexOf('/') : hash.length) @@ -8,4 +8,4 @@ export const getScResultsHighlight = (resultHash: string) => { const highlight = formattedHash === resultHash; return highlight; -}; +} diff --git a/src/utils/transactions/dataDecoders/getScResultsInitialDecodeMethod.ts b/src/utils/transactions/dataDecoders/getScResultsInitialDecodeMethod.ts index 17999e1..a27a48f 100644 --- a/src/utils/transactions/dataDecoders/getScResultsInitialDecodeMethod.ts +++ b/src/utils/transactions/dataDecoders/getScResultsInitialDecodeMethod.ts @@ -1,7 +1,7 @@ import { DecodeMethodEnum } from 'types'; import { getWindowLocation } from 'utils/window/getWindowLocation'; -export const getInitialScResultsDecodeMethod = () => { +export function getInitialScResultsDecodeMethod() { const { hash } = getWindowLocation(); const initialDecodeMethod = @@ -14,4 +14,4 @@ export const getInitialScResultsDecodeMethod = () => { Object.values(DecodeMethodEnum).includes(initialDecodeMethod); return isInitialDecodedMethod ? initialDecodeMethod : DecodeMethodEnum.raw; -}; +} diff --git a/src/utils/transactions/dataDecoders/getTokenFromData.ts b/src/utils/transactions/dataDecoders/getTokenFromData.ts index cdc5de2..104f056 100644 --- a/src/utils/transactions/dataDecoders/getTokenFromData.ts +++ b/src/utils/transactions/dataDecoders/getTokenFromData.ts @@ -4,13 +4,12 @@ import { decodePart } from '../../decoders'; import { TransactionTypesEnum } from '../../../types'; import { addressIsValid } from '../../validation'; - const noData = { tokenId: '', amount: '' }; -export const decodeData = (data: string) => { +export function decodeData(data: string) { const nonceIndex = 2; const amountIndex = 3; const parts = data.split('@'); @@ -18,7 +17,7 @@ export const decodeData = (data: string) => { [nonceIndex, amountIndex].includes(i) ? part : decodePart(part) ); return decodedParts; -}; +} export function getTokenFromData(data?: string): { tokenId: string; diff --git a/src/utils/transactions/dataDecoders/getUnHighlightedDataFieldParts.ts b/src/utils/transactions/dataDecoders/getUnHighlightedDataFieldParts.ts index 9e8e66e..1eaf980 100644 --- a/src/utils/transactions/dataDecoders/getUnHighlightedDataFieldParts.ts +++ b/src/utils/transactions/dataDecoders/getUnHighlightedDataFieldParts.ts @@ -1,4 +1,4 @@ -export const getUnHighlightedDataFieldParts = ({ +export function getUnHighlightedDataFieldParts({ data, highlight, occurrences, @@ -8,7 +8,7 @@ export const getUnHighlightedDataFieldParts = ({ highlight: string; occurrences: number[]; transactionIndex: number; -}) => { +}) { const highlightIndex = occurrences[transactionIndex] || data.indexOf(highlight); const highlightLength = highlight.length; @@ -19,4 +19,4 @@ export const getUnHighlightedDataFieldParts = ({ start, end }; -}; +} diff --git a/src/utils/transactions/dataDecoders/index.ts b/src/utils/transactions/dataDecoders/index.ts new file mode 100644 index 0000000..e930cc2 --- /dev/null +++ b/src/utils/transactions/dataDecoders/index.ts @@ -0,0 +1,10 @@ +export * from './decodeTransactionData'; +export * from './getDataPayloadForTransaction'; +export * from './getScResultsDecodedData'; +export * from './getScResultsHighlight'; +export * from './getScResultsInitialDecodeMethod'; +export * from './getTokenFromData'; +export * from './getUnHighlightedDataFieldParts'; +export * from './smartContractTransaction'; +export * from './useDataDecode'; +export * from './useDataDecodeMethod'; diff --git a/src/utils/transactions/dataDecoders/smartContractTransaction.ts b/src/utils/transactions/dataDecoders/smartContractTransaction.ts index a5f0137..bae398b 100644 --- a/src/utils/transactions/dataDecoders/smartContractTransaction.ts +++ b/src/utils/transactions/dataDecoders/smartContractTransaction.ts @@ -17,11 +17,7 @@ export const ESDTTransferTypes = [ 'ESDTFreeze' ]; -export const isContract = ( - receiver: string, - sender?: string, - data = '' -) => { +export function isContract(receiver: string, sender?: string, data = '') { const isValid = addressIsValid(receiver); if (!isValid) { diff --git a/src/utils/transactions/tests/getDataPayloadForTransaction.test.ts b/src/utils/transactions/dataDecoders/tests/getDataPayloadForTransaction.test.ts similarity index 96% rename from src/utils/transactions/tests/getDataPayloadForTransaction.test.ts rename to src/utils/transactions/dataDecoders/tests/getDataPayloadForTransaction.test.ts index a4701ef..d9cfd78 100644 --- a/src/utils/transactions/tests/getDataPayloadForTransaction.test.ts +++ b/src/utils/transactions/dataDecoders/tests/getDataPayloadForTransaction.test.ts @@ -1,4 +1,4 @@ -import { getDataPayloadForTransaction } from '../dataDecoders/getDataPayloadForTransaction'; +import { getDataPayloadForTransaction } from '../getDataPayloadForTransaction'; describe('getDataPayloadForTransaction', () => { it('should return empty string when data is not provided', async () => { diff --git a/src/utils/transactions/tests/getTokenFromData.test.ts b/src/utils/transactions/dataDecoders/tests/getTokenFromData.test.ts similarity index 95% rename from src/utils/transactions/tests/getTokenFromData.test.ts rename to src/utils/transactions/dataDecoders/tests/getTokenFromData.test.ts index d8db9e1..ca8cce9 100644 --- a/src/utils/transactions/tests/getTokenFromData.test.ts +++ b/src/utils/transactions/dataDecoders/tests/getTokenFromData.test.ts @@ -1,4 +1,4 @@ -import { getTokenFromData } from '../dataDecoders/getTokenFromData'; +import { getTokenFromData } from '../getTokenFromData'; describe('getTokenFromData tests', () => { test('get nft test', () => { diff --git a/src/utils/transactions/tests/getUnHighlightedDataFieldParts.test.ts b/src/utils/transactions/dataDecoders/tests/getUnHighlightedDataFieldParts.test.ts similarity index 96% rename from src/utils/transactions/tests/getUnHighlightedDataFieldParts.test.ts rename to src/utils/transactions/dataDecoders/tests/getUnHighlightedDataFieldParts.test.ts index c98fd17..32618d1 100644 --- a/src/utils/transactions/tests/getUnHighlightedDataFieldParts.test.ts +++ b/src/utils/transactions/dataDecoders/tests/getUnHighlightedDataFieldParts.test.ts @@ -1,4 +1,4 @@ -import { getUnHighlightedDataFieldParts } from '../dataDecoders/getUnHighlightedDataFieldParts'; +import { getUnHighlightedDataFieldParts } from '../getUnHighlightedDataFieldParts'; describe('getUnHighlightedDataFieldParts tests', () => { it('should show correctly for first occurrence when present in the middle', () => { diff --git a/src/utils/transactions/dataDecoders/useDataDecode.ts b/src/utils/transactions/dataDecoders/useDataDecode.ts index 63e4f54..74725f3 100644 --- a/src/utils/transactions/dataDecoders/useDataDecode.ts +++ b/src/utils/transactions/dataDecoders/useDataDecode.ts @@ -30,12 +30,12 @@ const decodeOptions = [ } ]; -export const useDataDecode = ({ +export function useDataDecode({ value, initialDecodeMethod, setDecodeMethod, identifier -}: DataDecodeType) => { +}: DataDecodeType) { const [activeKey, setActiveKey] = useState( initialDecodeMethod && Object.values(DecodeMethodEnum).includes(initialDecodeMethod) @@ -61,4 +61,4 @@ export const useDataDecode = ({ setActiveKey, decodeOptions }; -}; +} diff --git a/src/utils/transactions/index.ts b/src/utils/transactions/index.ts index 1d131da..636aa12 100644 --- a/src/utils/transactions/index.ts +++ b/src/utils/transactions/index.ts @@ -1,13 +1,6 @@ -export * from './url'; -export * from './parsers/getAreTransactionsOnSameShard'; -export * from './dataDecoders/getTokenFromData'; -export * from './parsers/getTransactionLink'; -export * from './dataDecoders/getUnHighlightedDataFieldParts'; -export * from './isGuardianTx'; -export * from './isTokenTransfer'; -export * from './parsers/parseMultiEsdtTransferData'; -export * from './parsers/parseTransactionAfterSigning'; -export * from './url/removeTransactionParamsFromUrl'; +export * from './batch'; +export * from './dataDecoders'; +export * from './operations'; export * from './parsers'; -export * from './transactionStateByStatus'; -export * from './parsers/getOperationsDetails'; +export * from './url'; +export * from './validation'; diff --git a/src/utils/transactions/operations/calculateFeeLimit.ts b/src/utils/transactions/operations/calculateFeeLimit.ts new file mode 100755 index 0000000..a657540 --- /dev/null +++ b/src/utils/transactions/operations/calculateFeeLimit.ts @@ -0,0 +1,78 @@ +import { + Transaction, + TransactionPayload, + TransactionVersion, + Address, + TokenPayment +} from '@multiversx/sdk-core'; +import BigNumber from 'bignumber.js'; +import { + EXTRA_GAS_LIMIT_GUARDED_TX, + GAS_LIMIT, + GAS_PRICE, + ZERO +} from 'constants/index'; +import { stringIsFloat, stringIsInteger } from 'utils/validation'; +import { isGuardianTx } from '../validation'; + +export interface CalculateFeeLimitParamsType { + gasLimit: string; + gasPrice: string; + data: string; + gasPerDataByte: string; + gasPriceModifier: string; + chainId: string; + minGasLimit?: string; + defaultGasPrice?: string; +} + +const placeholderData = { + from: 'erd12dnfhej64s6c56ka369gkyj3hwv5ms0y5rxgsk2k7hkd2vuk7rvqxkalsa', + to: 'erd12dnfhej64s6c56ka369gkyj3hwv5ms0y5rxgsk2k7hkd2vuk7rvqxkalsa' +}; + +export function calculateFeeLimit({ + minGasLimit = String(GAS_LIMIT), + gasLimit, + gasPrice, + data: inputData, + gasPerDataByte, + gasPriceModifier, + defaultGasPrice = String(GAS_PRICE), + chainId +}: CalculateFeeLimitParamsType) { + const data = inputData || ''; + const validGasLimit = stringIsInteger(gasLimit) ? gasLimit : minGasLimit; + + // We need to add extra gas fee for guardian transactions + const extraGasLimit = isGuardianTx({ data }) ? EXTRA_GAS_LIMIT_GUARDED_TX : 0; + const usedGasLimit = new BigNumber(validGasLimit) + .plus(extraGasLimit) + .toNumber(); + + const validGasPrice = stringIsFloat(gasPrice) ? gasPrice : defaultGasPrice; + const transaction = new Transaction({ + nonce: 0, + value: TokenPayment.egldFromAmount('0'), + receiver: new Address(placeholderData.to), + sender: new Address(placeholderData.to), + gasPrice: parseInt(validGasPrice), + gasLimit: usedGasLimit, + data: new TransactionPayload(data.trim()), + chainID: chainId, + version: new TransactionVersion(1) + }); + + try { + const bNfee = transaction.computeFee({ + GasPerDataByte: parseInt(gasPerDataByte), + MinGasLimit: parseInt(minGasLimit), + GasPriceModifier: parseFloat(gasPriceModifier), + ChainID: chainId + }); + return bNfee.toString(10); + } catch (err) { + console.error(err); + return ZERO; + } +} diff --git a/src/utils/transactions/operations/clearTransactions.ts b/src/utils/transactions/operations/clearTransactions.ts new file mode 100644 index 0000000..4a6d4f5 --- /dev/null +++ b/src/utils/transactions/operations/clearTransactions.ts @@ -0,0 +1,33 @@ +export function removeTransactionsToSign(sessionId: string) { + store.dispatch(clearSignedTransaction(sessionId)); +} +export function removeSignedTransaction(sessionId: string) { + store.dispatch(clearSignedTransaction(sessionId)); + + const account = accountSelector(store.getState()); + store.dispatch( + clearBatchTransactions({ + batchId: buildBatchId({ + sessionId, + address: account?.address ?? '' + }) + }) + ); +} + +export function deleteTransactionToast(sessionId: string) { + store.dispatch(removeTransactionToast(sessionId)); + removeSignedTransaction(sessionId); +} + +export function removeAllSignedTransactions() { + store.dispatch(clearAllSignedTransactions()); +} + +export function removeAllTransactionsToSign() { + store.dispatch(clearAllTransactionsToSign()); +} + +export function removeBatchTransactions(batchId: string) { + store.dispatch(clearBatchTransactions({ batchId })); +} diff --git a/src/utils/transactions/operations/computeTransactionNonce.ts b/src/utils/transactions/operations/computeTransactionNonce.ts new file mode 100644 index 0000000..5dc5f29 --- /dev/null +++ b/src/utils/transactions/operations/computeTransactionNonce.ts @@ -0,0 +1,17 @@ +/** + * Returns the higher nonce between the latest nonce of the account and the transaction nonce + * Used to set the correct nonce for a transaction in the batch + */ +export function computeTransactionNonce({ + accountNonce, + transactionNonce +}: { + accountNonce: number; + transactionNonce?: number; +}) { + if (!transactionNonce) { + return accountNonce; + } + + return transactionNonce > accountNonce ? transactionNonce : accountNonce; +} diff --git a/src/utils/transactions/operations/index.ts b/src/utils/transactions/operations/index.ts new file mode 100644 index 0000000..1fe2537 --- /dev/null +++ b/src/utils/transactions/operations/index.ts @@ -0,0 +1,2 @@ +export * from './calculateFeeLimit'; +export * from './newTransaction'; diff --git a/src/utils/transactions/newTransaction.ts b/src/utils/transactions/operations/newTransaction.ts similarity index 93% rename from src/utils/transactions/newTransaction.ts rename to src/utils/transactions/operations/newTransaction.ts index 6d6ba65..565e380 100644 --- a/src/utils/transactions/newTransaction.ts +++ b/src/utils/transactions/operations/newTransaction.ts @@ -6,6 +6,8 @@ import { } from '@multiversx/sdk-core'; import { GAS_LIMIT, GAS_PRICE, VERSION } from 'constants'; import { RawTransactionType } from 'types'; +import { isGuardianTx } from '../validation'; +import { getDataPayloadForTransaction } from '../dataDecoders'; export function newTransaction(rawTransaction: RawTransactionType) { const rawTx = { ...rawTransaction }; diff --git a/src/utils/transactions/operations/sendTransactions.ts b/src/utils/transactions/operations/sendTransactions.ts new file mode 100644 index 0000000..00ccf69 --- /dev/null +++ b/src/utils/transactions/operations/sendTransactions.ts @@ -0,0 +1,51 @@ +import { Transaction } from '@multiversx/sdk-core/out'; +import { + SendTransactionReturnType, + SendTransactionsParamsType, + SimpleTransactionType +} from 'types'; +import { getDefaultCallbackUrl } from 'utils/window'; +import { signTransactions } from './signTransactions'; +import { transformTransactionsToSign } from './transformTransactionsToSign'; + +export async function sendTransactions({ + transactions, + transactionsDisplayInfo, + redirectAfterSign = true, + callbackRoute = getDefaultCallbackUrl(), + signWithoutSending = false, + completedTransactionsDelay, + sessionInformation, + skipGuardian, + minGasLimit, + hasConsentPopup +}: SendTransactionsParamsType): Promise { + try { + const transactionsPayload = Array.isArray(transactions) + ? transactions + : [transactions]; + + const transactionsToSign = await transformTransactionsToSign({ + transactions: transactionsPayload as SimpleTransactionType[], + minGasLimit + }); + + return signTransactions({ + transactions: transactionsToSign as Transaction[], + minGasLimit, + callbackRoute, + transactionsDisplayInfo, + customTransactionInformation: { + redirectAfterSign, + completedTransactionsDelay, + sessionInformation, + skipGuardian, + signWithoutSending, + hasConsentPopup + } + }); + } catch (err) { + console.error('error signing transaction', err as any); + return { error: err as any, sessionId: null }; + } +} diff --git a/src/utils/transactions/operations/signTransactions.ts b/src/utils/transactions/operations/signTransactions.ts new file mode 100644 index 0000000..51f6ed4 --- /dev/null +++ b/src/utils/transactions/operations/signTransactions.ts @@ -0,0 +1,67 @@ +import { + NotificationTypesEnum, + SendTransactionReturnType, + SignTransactionsParamsType +} from 'types'; +import { isGuardianTx } from 'utils/transactions/validation/isGuardianTx'; +import { chainIdSelector } from '../../../store/selectors'; +import { getState } from '../../../store/store'; + +export async function signTransactions({ + transactions, + callbackRoute, + customTransactionInformation, + transactionsDisplayInfo +}: SignTransactionsParamsType): Promise { + const appState = getState(); + const sessionId = Date.now().toString(); + const storeChainId = chainIdSelector(appState); + + const transactionsPayload = Array.isArray(transactions) + ? transactions + : [transactions]; + + const hasValidChainId = transactionsPayload?.every( + (tx) => tx.getChainID().valueOf() === storeChainId.valueOf() + ); + if (!hasValidChainId) { + const notificationPayload = { + type: NotificationTypesEnum.warning, + iconClassName: 'text-warning', + title: 'Network change detected', + description: 'The application tried to change the transaction network' + }; + store.dispatch(setNotificationModal(notificationPayload)); + return { error: 'Invalid ChainID', sessionId: null }; + } + + const signTransactionsPayload = { + sessionId, + callbackRoute, + customTransactionInformation: { + ...(customTransactionInformation ?? {}), + signWithoutSending: + customTransactionInformation?.signWithoutSending ?? true + }, + transactions: transactionsPayload.map((tx) => { + const transaction = tx.toPlainObject(); + + // TODO: Remove when the protocol supports usernames for guardian transactions + if (isGuardianTx({ data: transaction.data, onlySetGuardian: true })) { + return transaction; + } + + return { + ...transaction, + senderUsername: tx.getSenderUsername().valueOf(), + receiverUsername: tx.getReceiverUsername().valueOf() + }; + }) + }; + store.dispatch(setSignTransactionsCancelMessage(null)); + store.dispatch(setTransactionsToSign(signTransactionsPayload)); + store.dispatch( + setTransactionsDisplayInfo({ sessionId, transactionsDisplayInfo }) + ); + return { sessionId }; +} diff --git a/src/utils/transactions/operations/tests/calculateFeeLimit.test.ts b/src/utils/transactions/operations/tests/calculateFeeLimit.test.ts new file mode 100644 index 0000000..0523587 --- /dev/null +++ b/src/utils/transactions/operations/tests/calculateFeeLimit.test.ts @@ -0,0 +1,96 @@ +import { GAS_PER_DATA_BYTE, GAS_PRICE_MODIFIER } from 'constants'; +import { calculateFeeLimit } from '../calculateFeeLimit'; + +describe('calculateFeeLimit tests', () => { + it('computes correct fee', () => { + const feeLimit = calculateFeeLimit({ + gasLimit: '62000', + gasPrice: '1000000000', + data: 'testdata', + chainId: 'T', + gasPerDataByte: String(GAS_PER_DATA_BYTE), + gasPriceModifier: String(GAS_PRICE_MODIFIER) + }); + expect(feeLimit).toBe('62000000000000'); + }); + + it('computes correct fee', () => { + const feeLimit = calculateFeeLimit({ + gasLimit: '11100000', + gasPrice: '1000000000', + data: 'bid@0d59@43525a502d333663366162@25', + gasPerDataByte: String(GAS_PER_DATA_BYTE), + gasPriceModifier: String(GAS_PRICE_MODIFIER), + defaultGasPrice: '1000000000', + chainId: 'T' + }); + + expect(feeLimit).toBe('210990000000000'); + }); + + it('computes correct fee for SetGuardian tx', () => { + const feeLimit = calculateFeeLimit({ + gasLimit: '', + gasPrice: (1_000_000).toString(), + data: 'SetGuardian@qwerty@12345', + chainId: 'T', + gasPerDataByte: '1', + gasPriceModifier: '1' + }); + + expect(feeLimit).toBe((100_000_000_000).toString()); // (minGasLimit + extra guardian gas) * gasPrice + }); + + it('computes correct fee for GuardAccount tx', () => { + const feeLimit = calculateFeeLimit({ + gasLimit: '', + gasPrice: (1_000_000).toString(), + data: 'GuardAccount@qwerty@12345', + chainId: 'T', + gasPerDataByte: '1', + gasPriceModifier: '1' + }); + + expect(feeLimit).toBe((100_000_000_000).toString()); // (minGasLimit + extra guardian gas) * gasPrice + }); + + it('computes correct fee for UnGuardAccount tx', () => { + const feeLimit = calculateFeeLimit({ + gasLimit: '', + gasPrice: (1_000_000).toString(), + data: 'UnGuardAccount@qwerty@12345', + chainId: 'T', + gasPerDataByte: '1', + gasPriceModifier: '1' + }); + + expect(feeLimit).toBe((100_000_000_000).toString()); // (minGasLimit + extra guardian gas) * gasPrice + }); + + it('computes correct fee for UnGuardAccount tx and gas limit specified', () => { + const feeLimit = calculateFeeLimit({ + gasLimit: (1_000_000).toString(), + gasPrice: (1_000_000).toString(), + data: 'UnGuardAccount@qwerty@12345', + chainId: 'T', + gasPerDataByte: '1', + gasPriceModifier: '1' + }); + + expect(feeLimit).toBe((1_050_000_000_000).toString()); // (gasLimit + extra guardian gas) * gasPrice + }); + + it('computes correct fee for UnGuardAccount tx and min gas limit specified', () => { + const feeLimit = calculateFeeLimit({ + gasLimit: '', + minGasLimit: (1_000_000).toString(), + gasPrice: (1_000_000).toString(), + data: 'UnGuardAccount@qwerty@12345', + chainId: 'T', + gasPerDataByte: '1', + gasPriceModifier: '1' + }); + + expect(feeLimit).toBe((1_050_000_000_000).toString()); // (minGasLimit + extra guardian gas) * gasPrice + }); +}); diff --git a/src/utils/transactions/operations/transformAndSignTransactions.ts b/src/utils/transactions/operations/transformAndSignTransactions.ts new file mode 100644 index 0000000..233e1da --- /dev/null +++ b/src/utils/transactions/operations/transformAndSignTransactions.ts @@ -0,0 +1,98 @@ +import { Address, Transaction } from '@multiversx/sdk-core'; +import BigNumber from 'bignumber.js'; + +import { + EXTRA_GAS_LIMIT_GUARDED_TX, + GAS_LIMIT, + GAS_PER_DATA_BYTE, + GAS_PRICE +} from 'constants'; +import { newTransaction } from 'models/newTransaction'; +import { addressSelector, chainIDSelector } from 'reduxStore/selectors'; +import { store } from 'reduxStore/store'; +import { SendSimpleTransactionParamsType } from 'types'; + +import { getAccount } from 'utils/account/getAccount'; +import { getLatestNonce } from 'utils/account/getLatestNonce'; +import { computeTransactionNonce } from './computeTransactionNonce'; + +enum ErrorCodesEnum { + 'invalidReceiver' = 'Invalid Receiver address', + 'unknownError' = 'Unknown Error. Please check the transactions and try again' +} + +function calculateGasLimit({ + data, + isGuarded +}: { + data?: string; + isGuarded?: boolean; +}) { + const guardedAccountGasLimit = isGuarded ? EXTRA_GAS_LIMIT_GUARDED_TX : 0; + const bNconfigGasLimit = new BigNumber(GAS_LIMIT).plus( + guardedAccountGasLimit + ); + const bNgasPerDataByte = new BigNumber(GAS_PER_DATA_BYTE); + const bNgasValue = data + ? bNgasPerDataByte.times(Buffer.from(data).length) + : 0; + const bNgasLimit = bNconfigGasLimit.plus(bNgasValue); + const gasLimit = bNgasLimit.toString(10); + return gasLimit; +} + +export async function transformAndSignTransactions({ + transactions +}: SendSimpleTransactionParamsType): Promise { + const address = addressSelector(store.getState()); + const account = await getAccount(address); + const accountNonce = getLatestNonce(account); + return transactions.map((tx) => { + const { + value, + receiver, + data = '', + chainID, + version = 1, + options, + gasPrice = GAS_PRICE, + gasLimit = calculateGasLimit({ + data: tx.data, + isGuarded: account?.isGuarded + }), + guardian, + guardianSignature, + nonce: transactionNonce = 0 + } = tx; + let validatedReceiver = receiver; + + try { + const addr = new Address(receiver); + validatedReceiver = addr.hex(); + } catch (err) { + throw ErrorCodesEnum.invalidReceiver; + } + + const computedNonce = computeTransactionNonce({ + accountNonce, + transactionNonce + }); + + const storeChainId = chainIDSelector(store.getState()).valueOf().toString(); + const transactionsChainId = chainID || storeChainId; + return newTransaction({ + value, + receiver: validatedReceiver, + data, + gasPrice, + gasLimit: Number(gasLimit), + nonce: Number(computedNonce.valueOf().toString()), + sender: new Address(address).hex(), + chainID: transactionsChainId, + version, + options, + guardian, + guardianSignature + }); + }); +} diff --git a/src/utils/transactions/operations/transformTransactionsToSign.ts b/src/utils/transactions/operations/transformTransactionsToSign.ts new file mode 100644 index 0000000..296d661 --- /dev/null +++ b/src/utils/transactions/operations/transformTransactionsToSign.ts @@ -0,0 +1,26 @@ +import { Transaction } from '@multiversx/sdk-core'; +import { SimpleTransactionType } from 'types'; +import { transformAndSignTransactions } from '../transformAndSignTransactions'; + +export const transformTransactionsToSign = async ({ + transactions, + minGasLimit +}: { + transactions: (SimpleTransactionType | Transaction)[]; + minGasLimit?: number; +}) => { + const areComplexTransactions = transactions.every( + (tx) => Object.getPrototypeOf(tx).toPlainObject != null + ); + + let transactionsToSign = transactions; + + if (!areComplexTransactions) { + transactionsToSign = await transformAndSignTransactions({ + transactions: transactions as SimpleTransactionType[], + minGasLimit + }); + } + + return transactionsToSign; +}; diff --git a/src/utils/transactions/operations/updateSignedTransactions.ts b/src/utils/transactions/operations/updateSignedTransactions.ts new file mode 100644 index 0000000..3ea1d43 --- /dev/null +++ b/src/utils/transactions/operations/updateSignedTransactions.ts @@ -0,0 +1,44 @@ +import { + updateSignedTransactions, + moveTransactionsToSignedState, + UpdateSignedTransactionsPayloadType, + updateSignedTransactionStatus, + UpdateSignedTransactionStatusPayloadType, + MoveTransactionsToSignedStatePayloadType, + setTransactionsDisplayInfo, + SetTransactionsInfoPayloadType, + updateSignedTransactionsCustomTransactionInformation +} from 'reduxStore/slices'; +import { store } from 'reduxStore/store'; +import { CustomTransactionInformation } from 'types'; + +export function setTransactionsToSignedState( + payload: MoveTransactionsToSignedStatePayloadType +) { + store.dispatch(moveTransactionsToSignedState(payload)); +} + +export function updateSignedTransactionsState( + payload: UpdateSignedTransactionsPayloadType +) { + store.dispatch(updateSignedTransactions(payload)); +} + +export function updateSignedTransactionStatusState( + payload: UpdateSignedTransactionStatusPayloadType +) { + store.dispatch(updateSignedTransactionStatus(payload)); +} + +export function setTransactionsDisplayInfoState( + payload: SetTransactionsInfoPayloadType +) { + store.dispatch(setTransactionsDisplayInfo(payload)); +} + +export function updateSignedTransactionsCustomTransactionInformationState(payload: { + sessionId: string; + customTransactionInformationOverrides: Partial; +}) { + store.dispatch(updateSignedTransactionsCustomTransactionInformation(payload)); +} diff --git a/src/utils/transactions/parsers/getAreTransactionsOnSameShard.ts b/src/utils/transactions/parsers/getAreTransactionsOnSameShard.ts index 6debfc5..24de389 100644 --- a/src/utils/transactions/parsers/getAreTransactionsOnSameShard.ts +++ b/src/utils/transactions/parsers/getAreTransactionsOnSameShard.ts @@ -1,11 +1,10 @@ -import { isCrossShardTransaction } from 'services/transactions/isCrossShardTransaction'; -import { SignedTransactionType } from 'types'; -import { getAddressFromDataField } from 'utils/smartContracts'; +import { SignedTransactionType } from '../../../types'; +import { getAddressFromDataField } from '../dataDecoders'; -export const getAreTransactionsOnSameShard = ( +export function getAreTransactionsOnSameShard( transactions?: SignedTransactionType[], accountShard = 1 -): boolean => { +): boolean { if (!transactions?.length) { return true; } @@ -31,4 +30,4 @@ export const getAreTransactionsOnSameShard = ( }, true ); -}; +} diff --git a/src/utils/transactions/parsers/getEventListDataHexValue.ts b/src/utils/transactions/parsers/getEventListDataHexValue.ts index f6ff593..65e257b 100644 --- a/src/utils/transactions/parsers/getEventListDataHexValue.ts +++ b/src/utils/transactions/parsers/getEventListDataHexValue.ts @@ -1,7 +1,7 @@ import { EventType } from 'types/serverTransactions.types'; -export const getEventListDataHexValue = (event: EventType) => { +export function getEventListDataHexValue(event: EventType) { const dataBase64Buffer = Buffer.from(String(event?.data), 'base64'); const dataHexValue = dataBase64Buffer.toString('hex'); return dataHexValue; -}; +} diff --git a/src/utils/transactions/parsers/getEventListHighlight.ts b/src/utils/transactions/parsers/getEventListHighlight.ts index 8ddcf08..6d10a8b 100644 --- a/src/utils/transactions/parsers/getEventListHighlight.ts +++ b/src/utils/transactions/parsers/getEventListHighlight.ts @@ -1,7 +1,7 @@ import { EventType } from 'types/serverTransactions.types'; import { getWindowLocation } from 'utils/window/getWindowLocation'; -export const getEventListHighlight = (event: EventType, id?: string) => { +export function getEventListHighlight(event: EventType, id?: string) { const { hash } = getWindowLocation(); const hashValues = hash.split('/'); const formattedHash = hashValues[0] ? hashValues[0].replace('#', '') : ''; @@ -10,4 +10,4 @@ export const getEventListHighlight = (event: EventType, id?: string) => { const highlight = formattedHash === id && event.order === Number(eventOrder); return highlight; -}; +} diff --git a/src/utils/transactions/parsers/getEventListInitialDecodeMethod.ts b/src/utils/transactions/parsers/getEventListInitialDecodeMethod.ts index e6ed1d1..2724a63 100644 --- a/src/utils/transactions/parsers/getEventListInitialDecodeMethod.ts +++ b/src/utils/transactions/parsers/getEventListInitialDecodeMethod.ts @@ -1,9 +1,9 @@ import { DecodeMethodEnum } from 'types/serverTransactions.types'; import { getWindowLocation } from 'utils/window/getWindowLocation'; -export const getEventListInitialDecodeMethod = () => { +export function getEventListInitialDecodeMethod() { const { hash } = getWindowLocation(); const hashValues = hash.split('/'); const initialDecodeMethod = hashValues[2] ?? DecodeMethodEnum.raw; return initialDecodeMethod; -}; +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts index f84b4cc..5b2e50f 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts @@ -4,7 +4,7 @@ import { ServerTransactionType } from 'types/serverTransactions.types'; -export const getTransactionMethod = (transaction: ServerTransactionType) => { +export function getTransactionMethod(transaction: ServerTransactionType) { let transactionAction = 'Transaction'; const transactionHasAction = transaction.action?.name && transaction.action?.category; @@ -25,4 +25,4 @@ export const getTransactionMethod = (transaction: ServerTransactionType) => { } return transactionAction; -}; +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTokens.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTokens.ts index e78c142..85c966e 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTokens.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTokens.ts @@ -3,9 +3,9 @@ import { TokenArgumentType } from 'types/serverTransactions.types'; -export const getTransactionTokens = ( +export function getTransactionTokens( transaction: ServerTransactionType -): TokenArgumentType[] => { +): TokenArgumentType[] { if (transaction.action) { const merged = [ transaction.action.arguments?.token, @@ -18,4 +18,4 @@ export const getTransactionTokens = ( } return []; -}; +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts index ad02e2b..d0e36f9 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts @@ -1,16 +1,6 @@ import { DECIMALS } from 'constants/index'; import { NftEnumType } from 'types/tokens.types'; -import { getTransactionTokens } from 'utils/transactions/getInterpretedTransaction/helpers/getTransactionTokens'; -import { - EgldValueDataType, - NFTValueDataType, - TokenValueDataType -} from 'utils/transactions/getInterpretedTransaction/helpers/types'; -import { getTransactionActionNftText } from 'utils/transactions/parsers/getTransactionActionNftText'; -import { getTransactionActionTokenText } from 'utils/transactions/parsers/getTransactionActionTokenText'; - -import { WithTransactionType } from '../../../../../UI/types'; import { ACTIONS_WITH_EGLD_VALUE, ACTIONS_WITH_MANDATORY_OPERATIONS, @@ -23,8 +13,16 @@ import { getValueFromDataField, getValueFromOperations } from './helpers'; -import { getEgldValueData } from './helpers/getEgldValueData'; -import { getTitleText } from './helpers/getTitleText'; +import { getEgldValueData, getTitleText } from './helpers'; +import { + EgldValueDataType, + NFTValueDataType, + TokenValueDataType +} from '../types'; +import { InterpretedTransactionType } from 'types'; +import { getTransactionTokens } from '../getTransactionTokens'; +import { getTransactionActionNftText } from '../../../getTransactionActionNftText'; +import { getTransactionActionTokenText } from '../../../getTransactionActionTokenText'; export interface GetTransactionValueReturnType { egldValueData?: EgldValueDataType; @@ -32,14 +30,15 @@ export interface GetTransactionValueReturnType { nftValueData?: NFTValueDataType; } -export interface GetTransactionValueType extends WithTransactionType { +export interface GetTransactionValueType { hideMultipleBadge?: boolean; + transaction: InterpretedTransactionType; } -export const getTransactionValue = ({ +export function getTransactionValue({ transaction, hideMultipleBadge -}: GetTransactionValueType): GetTransactionValueReturnType => { +}: GetTransactionValueType): GetTransactionValueReturnType { if (transaction.action) { if (ACTIONS_WITH_EGLD_VALUE.includes(transaction.action.name)) { return getEgldValueData(transaction.value); @@ -118,4 +117,4 @@ export const getTransactionValue = ({ } return getEgldValueData(transaction.value); -}; +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getEgldValueData.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getEgldValueData.ts index c915a62..29be9b8 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getEgldValueData.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getEgldValueData.ts @@ -1,10 +1,12 @@ import { DECIMALS } from 'constants/index'; -import { formatAmount } from 'utils/operations/formatAmount'; +import { formatAmount } from 'utils'; -export const getEgldValueData = (value: string) => ({ - egldValueData: { - value, - formattedValue: formatAmount({ input: value }), - decimals: DECIMALS - } -}); +export function getEgldValueData(value: string) { + return { + egldValueData: { + value, + formattedValue: formatAmount({ input: value }), + decimals: DECIMALS + } + }; +} diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts index 19252d2..ca282ac 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts @@ -1,12 +1,12 @@ -import { TokenArgumentType } from 'types/serverTransactions.types'; import { EgldValueDataType, NFTValueDataType, TokenValueDataType -} from 'utils/transactions/getInterpretedTransaction/helpers/types'; -import { getTransactionActionNftText } from 'utils/transactions/parsers/getTransactionActionNftText'; -import { getTransactionActionTokenText } from 'utils/transactions/parsers/getTransactionActionTokenText'; -import { getIdentifierType } from 'utils/validation/getIdentifierType'; +} from '../../types'; +import { TokenArgumentType } from 'types'; +import { getIdentifierType } from 'utils'; +import { getTransactionActionNftText } from '../../../../getTransactionActionNftText'; +import { getTransactionActionTokenText } from '../../../../getTransactionActionTokenText'; export interface GetTransactionValueReturnType { egldValueData?: EgldValueDataType; @@ -14,9 +14,7 @@ export interface GetTransactionValueReturnType { nftValueData?: NFTValueDataType; } -export const getTitleText = ( - transactionTokens: TokenArgumentType[] -): string => { +export function getTitleText(transactionTokens: TokenArgumentType[]): string { const tokensArray = transactionTokens.map((transactionToken) => { const { isNft } = getIdentifierType(transactionToken.type); if (isNft) { @@ -43,4 +41,4 @@ export const getTitleText = ( const joinedTokensWithLineBreak = decodeURI(tokensArray.join('%0A')); return joinedTokensWithLineBreak; -}; +} diff --git a/src/utils/transactions/parsers/getOperationDirection.ts b/src/utils/transactions/parsers/getOperationDirection.ts index e3da287..99cdbd0 100644 --- a/src/utils/transactions/parsers/getOperationDirection.ts +++ b/src/utils/transactions/parsers/getOperationDirection.ts @@ -1,11 +1,13 @@ -import { TransactionDirectionEnum } from 'types/serverTransactions.types'; +import { TransactionDirectionEnum } from 'types'; +import { OperationType } from '../../../types'; -import { WithOperationType } from '../../../UI/types'; - -export const getOperationDirection = ({ +export function getOperationDirection({ operation, address -}: { address: string } & WithOperationType) => { +}: { + address: string; + operation: OperationType; +}) { const directionOut = address === operation.sender; const directionIn = address === operation.receiver; const directionSelf = directionOut && directionIn; @@ -30,4 +32,4 @@ export const getOperationDirection = ({ return { direction }; -}; +} diff --git a/src/utils/transactions/parsers/getOperationsDetails.ts b/src/utils/transactions/parsers/getOperationsDetails.ts index 8d249f8..8a6db15 100644 --- a/src/utils/transactions/parsers/getOperationsDetails.ts +++ b/src/utils/transactions/parsers/getOperationsDetails.ts @@ -1,7 +1,7 @@ import { InterpretedTransactionType, OperationType } from 'types'; -import { getVisibleOperations } from 'utils/transactions/index'; +import { getVisibleOperations } from './getVisibleOperations'; -export type OperationDetailsPropsType = { +export type OperationDetailsParamsType = { transaction: InterpretedTransactionType; filterBy?: { action?: OperationType['action']; @@ -10,10 +10,10 @@ export type OperationDetailsPropsType = { }; }; -export const getOperationsDetails = ({ +export function getOperationsDetails({ transaction, filterBy -}: OperationDetailsPropsType): OperationType[] => { +}: OperationDetailsParamsType): OperationType[] { if (!transaction.operations) { return []; } @@ -47,4 +47,4 @@ export const getOperationsDetails = ({ }); return filteredOperations; -}; +} diff --git a/src/utils/transactions/parsers/getShardText.ts b/src/utils/transactions/parsers/getShardText.ts index 0dafdb0..138049c 100644 --- a/src/utils/transactions/parsers/getShardText.ts +++ b/src/utils/transactions/parsers/getShardText.ts @@ -1,6 +1,6 @@ import { ALL_SHARDS_SHARD_ID, METACHAIN_SHARD_ID } from 'constants/index'; -export const getShardText = (shard: number | string) => { +export function getShardText(shard: number | string) { let shardText = shard; if (typeof shardText === 'string' && shardText.includes('Shard')) { @@ -23,4 +23,4 @@ export const getShardText = (shard: number | string) => { } return `Shard ${shardText}`; -}; +} diff --git a/src/utils/transactions/parsers/getTransactionLinkWithLabel.ts b/src/utils/transactions/parsers/getTransactionLinkWithLabel.ts index a49c644..97a2d20 100644 --- a/src/utils/transactions/parsers/getTransactionLinkWithLabel.ts +++ b/src/utils/transactions/parsers/getTransactionLinkWithLabel.ts @@ -9,10 +9,10 @@ export interface GetTransactionLinkWithLabelParamsType { direction: TransactionDirectionEnum; } -export const getTransactionLinkWithLabel = ({ +export function getTransactionLinkWithLabel({ transaction, direction -}: GetTransactionLinkWithLabelParamsType): TransactionLinkType => { +}: GetTransactionLinkWithLabelParamsType): TransactionLinkType { const isSmartContract = direction === TransactionDirectionEnum.INTERNAL; let address = transaction.sender; @@ -46,4 +46,4 @@ export const getTransactionLinkWithLabel = ({ address, link: link ?? '' }; -}; +} diff --git a/src/utils/transactions/parsers/getTransactionStatusText.ts b/src/utils/transactions/parsers/getTransactionStatusText.ts index cbdff12..cc9d4b0 100644 --- a/src/utils/transactions/parsers/getTransactionStatusText.ts +++ b/src/utils/transactions/parsers/getTransactionStatusText.ts @@ -1,9 +1,9 @@ import { TransactionServerStatusesEnum } from 'types/enums.types'; -import { InterpretedTransactionType } from 'types/serverTransactions.types'; +import { InterpretedTransactionType } from 'types'; -export const getTransactionStatusText = ( +export function getTransactionStatusText( transaction: InterpretedTransactionType -) => { +) { switch (true) { case transaction.pendingResults: return 'Pending (Smart Contract Execution)'; @@ -12,4 +12,4 @@ export const getTransactionStatusText = ( default: return transaction.status.toString(); } -}; +} diff --git a/src/utils/transactions/parsers/getTransactionsDetails.ts b/src/utils/transactions/parsers/getTransactionsDetails.ts new file mode 100644 index 0000000..e79aa18 --- /dev/null +++ b/src/utils/transactions/parsers/getTransactionsDetails.ts @@ -0,0 +1,52 @@ +import { getTransactionByHashPromise } from 'apiCalls'; +import { ServerTransactionType } from 'types'; + +export async function getTransactionsDetails(txHashes: string[]) { + const delayMs = 3000; + let retries = 4; + let transactions: ServerTransactionType[] | undefined; + + if (txHashes.length === 0) { + return { data: transactions, success: false }; + } + + while (transactions === undefined && retries > 0) { + try { + await delayWithPromise(delayMs); + + const promiseResponse = await Promise.allSettled( + txHashes.map((hash) => getTransactionByHashPromise(hash)) + ); + + const apiTransactions = promiseResponse + .map((response) => + response.status === 'fulfilled' ? response.value.data : undefined + ) + .filter((tx) => tx !== undefined) as ServerTransactionType[]; + + const success = apiTransactions.length > 0; + + if (success) { + const foundAll = apiTransactions.length === txHashes.length; + + const hasPendingTx = apiTransactions.some((tx) => { + const status = new TransactionStatus(tx.status); + return status.isPending(); + }); + + if ((foundAll && !hasPendingTx) || retries === 1) { + transactions = apiTransactions; + retries = 0; + } else { + retries -= 1; + } + } else { + retries -= 1; + } + } catch (e) { + retries -= 1; + } + } + + return { data: transactions, success: transactions !== undefined }; +} diff --git a/src/utils/transactions/parsers/getTrimmedHash.ts b/src/utils/transactions/parsers/getTrimmedHash.ts index f06d4f5..91104a7 100644 --- a/src/utils/transactions/parsers/getTrimmedHash.ts +++ b/src/utils/transactions/parsers/getTrimmedHash.ts @@ -1,7 +1,8 @@ -export const getTrimmedHash = (address: string, divider = 4): string => - `${address.substring( +export function getTrimmedHash(address: string, divider = 4): string { + return `${address.substring( 0, Math.floor(address.length / divider) )}...${address.substring( address.length - Math.ceil(address.length / divider) )}`; +} diff --git a/src/utils/transactions/parsers/getUsernameForTransaction.ts b/src/utils/transactions/parsers/getUsernameForTransaction.ts index 5c227a0..dd0917e 100644 --- a/src/utils/transactions/parsers/getUsernameForTransaction.ts +++ b/src/utils/transactions/parsers/getUsernameForTransaction.ts @@ -1,6 +1,6 @@ import { isStringBase64 } from '../../decoders'; -export const getUsernameForTransaction = (username?: string) => { +export function getUsernameForTransaction(username?: string) { if (!username) { return; } @@ -10,4 +10,4 @@ export const getUsernameForTransaction = (username?: string) => { return isStringBase64(defaultData) ? defaultData : Buffer.from(defaultData, 'base64').toString(); -}; +} diff --git a/src/utils/transactions/parsers/getVisibleOperations.ts b/src/utils/transactions/parsers/getVisibleOperations.ts index dfbaea4..32bb1b8 100644 --- a/src/utils/transactions/parsers/getVisibleOperations.ts +++ b/src/utils/transactions/parsers/getVisibleOperations.ts @@ -1,11 +1,9 @@ import { InterpretedTransactionType, VisibleTransactionOperationType -} from 'types/serverTransactions.types'; +} from 'types'; -export const getVisibleOperations = ( - transaction: InterpretedTransactionType -) => { +export function getVisibleOperations(transaction: InterpretedTransactionType) { const operations = transaction?.operations?.filter((operation) => Object.values(VisibleTransactionOperationType).includes( @@ -14,4 +12,4 @@ export const getVisibleOperations = ( ) ?? []; return operations; -}; +} diff --git a/src/utils/transactions/parsers/parseTransactionAfterSigning.ts b/src/utils/transactions/parsers/parseTransactionAfterSigning.ts index ce25f91..ecf82fa 100644 --- a/src/utils/transactions/parsers/parseTransactionAfterSigning.ts +++ b/src/utils/transactions/parsers/parseTransactionAfterSigning.ts @@ -3,7 +3,7 @@ import { PlainSignedTransaction } from '@multiversx/sdk-web-wallet-provider/out/ import { newTransaction } from 'models'; import { SignedTransactionType } from 'types'; import { TransactionServerStatusesEnum } from 'types/enums.types'; -import { isGuardianTx } from '../isGuardianTx'; +import { isGuardianTx } from '../validation/isGuardianTx'; export function parseTransactionAfterSigning( signedTransaction: Transaction | PlainSignedTransaction diff --git a/src/utils/transactions/tests/getOperationsDetails.test.ts b/src/utils/transactions/parsers/tests/getOperationsDetails.test.ts similarity index 99% rename from src/utils/transactions/tests/getOperationsDetails.test.ts rename to src/utils/transactions/parsers/tests/getOperationsDetails.test.ts index 2f0e585..19eef2d 100644 --- a/src/utils/transactions/tests/getOperationsDetails.test.ts +++ b/src/utils/transactions/parsers/tests/getOperationsDetails.test.ts @@ -7,7 +7,7 @@ import { VisibleTransactionOperationType } from 'types'; import { NftEnumType } from 'types/tokens.types'; -import { getOperationsDetails } from '../parsers/getOperationsDetails'; +import { getOperationsDetails } from '../getOperationsDetails'; const TRANSACTION: InterpretedTransactionType = { txHash: 'f6e4f89f693ba8da573b448ea89a295aa4f2db8f3160019a7b3e25b852bdfcf0', diff --git a/src/utils/transactions/tests/parseMultiEsdtTransferData.test.ts b/src/utils/transactions/parsers/tests/parseMultiEsdtTransferData.test.ts similarity index 98% rename from src/utils/transactions/tests/parseMultiEsdtTransferData.test.ts rename to src/utils/transactions/parsers/tests/parseMultiEsdtTransferData.test.ts index 519d866..dda13ad 100644 --- a/src/utils/transactions/tests/parseMultiEsdtTransferData.test.ts +++ b/src/utils/transactions/parsers/tests/parseMultiEsdtTransferData.test.ts @@ -1,4 +1,4 @@ -import { parseMultiEsdtTransferData } from '../parsers/parseMultiEsdtTransferData'; +import { parseMultiEsdtTransferData } from '../parseMultiEsdtTransferData'; const removeWhiteSpaces = (str: string) => str.replace(/\s/g, ''); const one = { diff --git a/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/esdtNftUnwrapper.ts b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/esdtNftUnwrapper.ts index a48f8f0..7da2486 100644 --- a/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/esdtNftUnwrapper.ts +++ b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/esdtNftUnwrapper.ts @@ -2,11 +2,11 @@ import { TransactionActionType, UnwrapperType, TransactionActionsEnum -} from 'types/serverTransactions.types'; +} from 'types'; -export const esdtNftUnwrapper = ( +export function esdtNftUnwrapper( action: TransactionActionType -): Array => { +): Array { switch (action.name) { case TransactionActionsEnum.transfer: return [ @@ -19,4 +19,4 @@ export const esdtNftUnwrapper = ( default: return []; } -}; +} diff --git a/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/mexUnwrapper.ts b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/mexUnwrapper.ts index c9591ea..1b2c0b8 100644 --- a/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/mexUnwrapper.ts +++ b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/mexUnwrapper.ts @@ -4,11 +4,11 @@ import { TransactionActionType, UnwrapperType, TransactionActionsEnum -} from 'types/serverTransactions.types'; +} from 'types'; -export const mexUnwrapper = ( +export function mexUnwrapper( action: TransactionActionType -): Array => { +): Array { switch (action.name) { // distribution case TransactionActionsEnum.claimLockedAssets: @@ -74,4 +74,4 @@ export const mexUnwrapper = ( default: return action.description ? [action.description] : []; } -}; +} diff --git a/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/stakeUnwrapper.ts b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/stakeUnwrapper.ts index 8b6acc7..4e552c8 100644 --- a/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/stakeUnwrapper.ts +++ b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/stakeUnwrapper.ts @@ -2,11 +2,11 @@ import { TransactionActionType, UnwrapperType, TransactionActionsEnum -} from 'types/serverTransactions.types'; +} from 'types'; -export const stakeUnwrapper = ( +export function stakeUnwrapper( action: TransactionActionType -): Array => { +): Array { switch (action.name) { case TransactionActionsEnum.delegate: case TransactionActionsEnum.stake: @@ -57,4 +57,4 @@ export const stakeUnwrapper = ( default: return []; } -}; +} diff --git a/src/utils/transactions/parsers/transactionActionUnwrapper/transactionActionUnwrapper.ts b/src/utils/transactions/parsers/transactionActionUnwrapper/transactionActionUnwrapper.ts index d2a5803..1b51ca5 100644 --- a/src/utils/transactions/parsers/transactionActionUnwrapper/transactionActionUnwrapper.ts +++ b/src/utils/transactions/parsers/transactionActionUnwrapper/transactionActionUnwrapper.ts @@ -1,15 +1,13 @@ import { + TransactionActionCategoryEnum, TransactionActionType, - UnwrapperType, - TransactionActionCategoryEnum -} from 'types/serverTransactions.types'; -import { esdtNftUnwrapper } from './helpers/esdtNftUnwrapper'; -import { mexUnwrapper } from './helpers/mexUnwrapper'; -import { stakeUnwrapper } from './helpers/stakeUnwrapper'; + UnwrapperType +} from '../../../../types'; +import { esdtNftUnwrapper, mexUnwrapper, stakeUnwrapper } from './helpers'; -export const transactionActionUnwrapper = ( +export function transactionActionUnwrapper( action: TransactionActionType -): Array => { +): Array { if (!action.arguments) { return action.description ? [action.description] : [action.name]; } @@ -24,4 +22,4 @@ export const transactionActionUnwrapper = ( default: return action.description ? [action.description] : []; } -}; +} diff --git a/src/utils/transactions/transactionStateByStatus.ts b/src/utils/transactions/parsers/transactionStateByStatus.ts similarity index 100% rename from src/utils/transactions/transactionStateByStatus.ts rename to src/utils/transactions/parsers/transactionStateByStatus.ts diff --git a/src/utils/transactions/url/removeTransactionParamsFromUrl.ts b/src/utils/transactions/url/removeTransactionParamsFromUrl.ts index 81ebd47..97732ee 100644 --- a/src/utils/transactions/url/removeTransactionParamsFromUrl.ts +++ b/src/utils/transactions/url/removeTransactionParamsFromUrl.ts @@ -1,16 +1,16 @@ import { WALLET_PROVIDER_CALLBACK_PARAM } from '@multiversx/sdk-web-wallet-provider'; -import { SDK_DAPP_VERSION, WALLET_SIGN_SESSION } from 'constants'; -import { removeSearchParamsFromUrl } from '../removeSearchParamsFromUrl'; +import { SDK_DAPP_VERSION, WALLET_SIGN_SESSION } from 'constants/index'; +import { removeSearchParamsFromUrl } from '../../window'; interface RemoveTransactionParamsFromUrlParamsType { transaction: any; search?: string; } -export const removeTransactionParamsFromUrl = ({ +export function removeTransactionParamsFromUrl({ transaction, search -}: RemoveTransactionParamsFromUrlParamsType) => { +}: RemoveTransactionParamsFromUrlParamsType) { return removeSearchParamsFromUrl({ removeParams: [ ...Object.keys(transaction), @@ -20,4 +20,4 @@ export const removeTransactionParamsFromUrl = ({ ], search }); -}; +} diff --git a/src/utils/transactions/tests/builtCallbackUrl.test.ts b/src/utils/transactions/url/tests/builtCallbackUrl.test.ts similarity index 96% rename from src/utils/transactions/tests/builtCallbackUrl.test.ts rename to src/utils/transactions/url/tests/builtCallbackUrl.test.ts index 7aa9dba..8a5c757 100644 --- a/src/utils/transactions/tests/builtCallbackUrl.test.ts +++ b/src/utils/transactions/url/tests/builtCallbackUrl.test.ts @@ -1,4 +1,4 @@ -import { buildCallbackUrl } from '../url/buildCallbackUrl'; +import { buildCallbackUrl } from '../buildCallbackUrl'; describe('builtCallbackUrl tests', () => { const url = 'https://wallet.multiversx.com'; diff --git a/src/utils/transactions/tests/removeTransactionParamsFromUrl.test.ts b/src/utils/transactions/url/tests/removeTransactionParamsFromUrl.test.ts similarity index 92% rename from src/utils/transactions/tests/removeTransactionParamsFromUrl.test.ts rename to src/utils/transactions/url/tests/removeTransactionParamsFromUrl.test.ts index 10e4050..153554b 100644 --- a/src/utils/transactions/tests/removeTransactionParamsFromUrl.test.ts +++ b/src/utils/transactions/url/tests/removeTransactionParamsFromUrl.test.ts @@ -1,4 +1,4 @@ -import { removeTransactionParamsFromUrl } from '../url/removeTransactionParamsFromUrl'; +import { removeTransactionParamsFromUrl } from '../removeTransactionParamsFromUrl'; describe('removeTransactionParamsFromUrl tests', () => { test('removes all data from URL', () => { diff --git a/src/utils/transactions/validation/index.ts b/src/utils/transactions/validation/index.ts new file mode 100644 index 0000000..105b9aa --- /dev/null +++ b/src/utils/transactions/validation/index.ts @@ -0,0 +1,3 @@ +export * from './isCrossShardTransaction'; +export * from './isGuardianTx'; +export * from './isTokenTransfer'; diff --git a/src/utils/transactions/validation/isCrossShardTransaction.ts b/src/utils/transactions/validation/isCrossShardTransaction.ts new file mode 100644 index 0000000..ba724dd --- /dev/null +++ b/src/utils/transactions/validation/isCrossShardTransaction.ts @@ -0,0 +1,26 @@ +import { Address } from '@multiversx/sdk-core/out'; +import { getShardOfAddress } from 'utils'; + +export interface IsCrossShardTransactionParamsType { + receiverAddress: string; + senderShard?: number; + senderAddress?: string; +} + +export function isCrossShardTransaction({ + receiverAddress, + senderShard, + senderAddress +}: IsCrossShardTransactionParamsType) { + try { + const receiver = new Address(receiverAddress); + const receiverShard = getShardOfAddress(receiver.pubkey()); + if (senderShard == null && senderAddress != null) { + const sender = new Address(senderAddress); + return getShardOfAddress(sender) === receiverShard; + } + return receiverShard === senderShard; + } catch (err) { + return false; + } +} diff --git a/src/utils/transactions/isGuardianTx.ts b/src/utils/transactions/validation/isGuardianTx.ts similarity index 89% rename from src/utils/transactions/isGuardianTx.ts rename to src/utils/transactions/validation/isGuardianTx.ts index bf683d3..2152cb3 100644 --- a/src/utils/transactions/isGuardianTx.ts +++ b/src/utils/transactions/validation/isGuardianTx.ts @@ -1,12 +1,12 @@ import { GuardianActionsEnum } from 'types'; -export const isGuardianTx = ({ +export function isGuardianTx({ data, onlySetGuardian }: { data?: string; onlySetGuardian?: boolean; -}) => { +}) { if (!data) { return false; } @@ -18,4 +18,4 @@ export const isGuardianTx = ({ return Object.values(GuardianActionsEnum).some((action) => data.startsWith(action) ); -}; +} diff --git a/src/utils/transactions/isTokenTransfer.ts b/src/utils/transactions/validation/isTokenTransfer.ts similarity index 100% rename from src/utils/transactions/isTokenTransfer.ts rename to src/utils/transactions/validation/isTokenTransfer.ts diff --git a/src/utils/transactions/tests/isGuardianTx.test.ts b/src/utils/transactions/validation/tests/isGuardianTx.test.ts similarity index 96% rename from src/utils/transactions/tests/isGuardianTx.test.ts rename to src/utils/transactions/validation/tests/isGuardianTx.test.ts index a9ad3c6..6defcd9 100644 --- a/src/utils/transactions/tests/isGuardianTx.test.ts +++ b/src/utils/transactions/validation/tests/isGuardianTx.test.ts @@ -1,4 +1,4 @@ -import { GuardianActionsEnum } from 'constants/index'; +import { GuardianActionsEnum } from 'constants'; import { isGuardianTx } from '../isGuardianTx'; describe('isGuardianTx Function', () => { diff --git a/src/utils/transactions/validation/tests/isTokenTransfer.test.ts b/src/utils/transactions/validation/tests/isTokenTransfer.test.ts new file mode 100644 index 0000000..7f1bdfa --- /dev/null +++ b/src/utils/transactions/validation/tests/isTokenTransfer.test.ts @@ -0,0 +1,35 @@ +import { isTokenTransfer } from '../isTokenTransfer'; + +describe('isTokenTransfer', () => { + it('should return true when tokenId exists and differs from erdLabel', () => { + const result = isTokenTransfer({ + tokenId: 'USDC-123456', + erdLabel: 'EGLD' + }); + expect(result).toBe(true); + }); + + it('should return false when tokenId is undefined', () => { + const result = isTokenTransfer({ + tokenId: undefined, + erdLabel: 'EGLD' + }); + expect(result).toBe(false); + }); + + it('should return false when tokenId equals erdLabel', () => { + const result = isTokenTransfer({ + tokenId: 'EGLD', + erdLabel: 'EGLD' + }); + expect(result).toBe(false); + }); + + it('should return false when tokenId is empty string', () => { + const result = isTokenTransfer({ + tokenId: '', + erdLabel: 'EGLD' + }); + expect(result).toBe(false); + }); +}); diff --git a/src/utils/walletconnect/index.ts b/src/utils/walletconnect/index.ts new file mode 100644 index 0000000..0e52835 --- /dev/null +++ b/src/utils/walletconnect/index.ts @@ -0,0 +1 @@ +export * from './__sdkWalletconnectProvider'; From 2f46a5c72b3158c769b324c7731e07d24080b325 Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Tue, 3 Dec 2024 21:06:38 +0200 Subject: [PATCH 04/23] Added tests and refactoring --- eslint.config.js | 4 +- src/utils/index.ts | 1 + .../getBatchTransactionsStatusFromApi.ts | 1 + src/utils/transactions/batch/index.ts | 2 + .../tests/decodeForDisplay.spec.ts | 5 +- .../operations/calculateGasLimit.ts | 26 +++ src/utils/transactions/operations/index.ts | 7 + .../operations/signTransactions.ts | 67 ------ ....ts => transformTransactionsForSigning.ts} | 45 +--- .../parsers/getEventListDataHexValue.ts | 2 +- .../parsers/getEventListHighlight.ts | 2 +- .../getEventListInitialDecodeMethod.ts | 2 +- .../getInterpretedTransaction/constants.ts | 2 +- .../getInterpretedTransaction.ts | 7 +- .../helpers/getOperationsMessages.ts | 2 +- .../helpers/getReceiptMessage.ts | 2 +- .../helpers/getScResultsMessages.ts | 2 +- .../helpers/getTransactionMethod.ts | 2 +- .../helpers/getTransactionReceiver.ts | 2 +- .../helpers/getTransactionReceiverAssets.ts | 2 +- .../helpers/getTransactionTokens.ts | 5 +- .../helpers/getTransactionTransferType.ts | 2 +- .../getTransactionValue.ts | 8 +- .../helpers/getEgldValueData.ts | 2 +- .../helpers/getTitleText.ts | 2 + .../helpers/getValueFromActions.ts | 2 +- .../helpers/getValueFromDataField.ts | 2 +- .../helpers/getValueFromOperations.ts | 4 +- .../tests/getTransactionValue.test.ts | 210 ++++++++++++++++++ .../helpers/index.ts | 1 + .../helpers/tests/base-transaction-mock.ts | 2 +- .../tests/getOperationsMessages.test.ts | 2 +- .../tests/getScResultsMessages.test.ts | 5 +- .../tests/getTransactionMethod.test.ts | 2 +- .../tests/getTransactionReceiver.test.ts | 2 +- .../getTransactionReceiverAssets.test.ts | 2 +- .../tests/getTransactionTokens.test.ts | 2 +- .../tests/getTransactionTransferType.test.ts | 7 +- .../{index.tsx => index.ts} | 2 +- .../tests/extended-transaction-mock.ts | 2 +- .../tests/getInterpretedTransaction.test.ts | 10 +- .../transactions/parsers/getScamFlag.tsx | 71 ------ .../parsers/getTransactionActionNftText.ts | 4 +- .../parsers/getTransactionActionTokenText.ts | 4 +- .../parsers/getTransactionStatus.ts | 2 +- src/utils/transactions/parsers/index.ts | 17 +- ...EsdtTransferDataForMultipleTransactions.ts | 2 +- .../parsers/tests/getScamFlag.test.ts | 40 ---- .../parsers/useGetOperationList.tsx | 94 -------- src/utils/validation/index.ts | 9 +- src/utils/validation/isContract.ts | 99 --------- src/utils/validation/tests/isContract.test.ts | 29 --- 52 files changed, 320 insertions(+), 512 deletions(-) create mode 100644 src/utils/transactions/operations/calculateGasLimit.ts delete mode 100644 src/utils/transactions/operations/signTransactions.ts rename src/utils/transactions/operations/{transformAndSignTransactions.ts => transformTransactionsForSigning.ts} (60%) create mode 100644 src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/tests/getTransactionValue.test.ts rename src/utils/transactions/parsers/getInterpretedTransaction/{index.tsx => index.ts} (100%) delete mode 100644 src/utils/transactions/parsers/getScamFlag.tsx delete mode 100644 src/utils/transactions/parsers/tests/getScamFlag.test.ts delete mode 100644 src/utils/transactions/parsers/useGetOperationList.tsx delete mode 100644 src/utils/validation/isContract.ts delete mode 100644 src/utils/validation/tests/isContract.test.ts diff --git a/eslint.config.js b/eslint.config.js index ddd388d..52d8eb1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -15,11 +15,11 @@ }, settings: { 'import/parsers': { - '@typescript-eslint/parser': ['.ts', '.tsx'] + '@typescript-eslint/parser': ['.ts'] }, 'import/resolver': { node: { - extensions: ['.js', '.jsx', '.ts', '.tsx'], + extensions: ['.js', '.ts'], moduleDirectory: ['node_modules', 'src/'] }, typescript: { diff --git a/src/utils/index.ts b/src/utils/index.ts index 308d1ba..c4f7da4 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -5,5 +5,6 @@ export * from './decoders'; export * from './operations'; export * from './retryMultipleTimes'; export * from './validation'; +export * from './transactions'; export * from './walletconnect'; export * from './window'; diff --git a/src/utils/transactions/batch/getBatchTransactionsStatusFromApi.ts b/src/utils/transactions/batch/getBatchTransactionsStatusFromApi.ts index 3938f64..970ca1e 100644 --- a/src/utils/transactions/batch/getBatchTransactionsStatusFromApi.ts +++ b/src/utils/transactions/batch/getBatchTransactionsStatusFromApi.ts @@ -3,6 +3,7 @@ import { TRANSACTIONS_BATCH } from 'apiCalls'; import { BatchTransactionsResponseType } from 'types'; import { networkSelector } from 'store/selectors'; import { getState } from 'store/store'; +import { TIMEOUT } from 'constants/index'; export interface SendBatchTransactionsParamsType { batchId: string; diff --git a/src/utils/transactions/batch/index.ts b/src/utils/transactions/batch/index.ts index 8b2fcc5..cd4f1c6 100644 --- a/src/utils/transactions/batch/index.ts +++ b/src/utils/transactions/batch/index.ts @@ -1,5 +1,7 @@ export * from './generateBatchTransactionsGrouping'; +export * from './getBatchTransactionsStatusFromApi'; export * from './getIsSequential'; export * from './getTransactionsStatus'; +export * from './sendBatchTransactions'; export * from './sequentialToFlatArray'; export * from './updateBatchTransactionsStatuses'; diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts index 2553b60..ff8127d 100644 --- a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts @@ -1,7 +1,4 @@ -import { - DecodeMethodEnum, - DecodeForDisplayParamsType -} from 'types/serverTransactions.types'; +import { DecodeMethodEnum, DecodeForDisplayParamsType } from 'types'; import { decodeTransactionData } from '../decodeTransactionData'; import { decodeByMethod, diff --git a/src/utils/transactions/operations/calculateGasLimit.ts b/src/utils/transactions/operations/calculateGasLimit.ts new file mode 100644 index 0000000..af31aca --- /dev/null +++ b/src/utils/transactions/operations/calculateGasLimit.ts @@ -0,0 +1,26 @@ +import { + EXTRA_GAS_LIMIT_GUARDED_TX, + GAS_LIMIT, + GAS_PER_DATA_BYTE +} from 'constants/index'; +import BigNumber from 'bignumber.js'; + +export function calculateGasLimit({ + data, + isGuarded +}: { + data?: string; + isGuarded?: boolean; +}) { + const guardedAccountGasLimit = isGuarded ? EXTRA_GAS_LIMIT_GUARDED_TX : 0; + const bNconfigGasLimit = new BigNumber(GAS_LIMIT).plus( + guardedAccountGasLimit + ); + const bNgasPerDataByte = new BigNumber(GAS_PER_DATA_BYTE); + const bNgasValue = data + ? bNgasPerDataByte.times(Buffer.from(data).length) + : 0; + const bNgasLimit = bNconfigGasLimit.plus(bNgasValue); + const gasLimit = bNgasLimit.toString(10); + return gasLimit; +} diff --git a/src/utils/transactions/operations/index.ts b/src/utils/transactions/operations/index.ts index 1fe2537..8459e22 100644 --- a/src/utils/transactions/operations/index.ts +++ b/src/utils/transactions/operations/index.ts @@ -1,2 +1,9 @@ export * from './calculateFeeLimit'; +export * from './calculateGasLimit'; +export * from './clearTransactions'; +export * from './computeTransactionNonce'; export * from './newTransaction'; +export * from './sendTransactions'; +export * from './transformTransactionsForSigning'; +export * from './transformTransactionsToSign'; +export * from './updateSignedTransactions'; diff --git a/src/utils/transactions/operations/signTransactions.ts b/src/utils/transactions/operations/signTransactions.ts deleted file mode 100644 index 51f6ed4..0000000 --- a/src/utils/transactions/operations/signTransactions.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - NotificationTypesEnum, - SendTransactionReturnType, - SignTransactionsParamsType -} from 'types'; -import { isGuardianTx } from 'utils/transactions/validation/isGuardianTx'; -import { chainIdSelector } from '../../../store/selectors'; -import { getState } from '../../../store/store'; - -export async function signTransactions({ - transactions, - callbackRoute, - customTransactionInformation, - transactionsDisplayInfo -}: SignTransactionsParamsType): Promise { - const appState = getState(); - const sessionId = Date.now().toString(); - const storeChainId = chainIdSelector(appState); - - const transactionsPayload = Array.isArray(transactions) - ? transactions - : [transactions]; - - const hasValidChainId = transactionsPayload?.every( - (tx) => tx.getChainID().valueOf() === storeChainId.valueOf() - ); - if (!hasValidChainId) { - const notificationPayload = { - type: NotificationTypesEnum.warning, - iconClassName: 'text-warning', - title: 'Network change detected', - description: 'The application tried to change the transaction network' - }; - store.dispatch(setNotificationModal(notificationPayload)); - return { error: 'Invalid ChainID', sessionId: null }; - } - - const signTransactionsPayload = { - sessionId, - callbackRoute, - customTransactionInformation: { - ...(customTransactionInformation ?? {}), - signWithoutSending: - customTransactionInformation?.signWithoutSending ?? true - }, - transactions: transactionsPayload.map((tx) => { - const transaction = tx.toPlainObject(); - - // TODO: Remove when the protocol supports usernames for guardian transactions - if (isGuardianTx({ data: transaction.data, onlySetGuardian: true })) { - return transaction; - } - - return { - ...transaction, - senderUsername: tx.getSenderUsername().valueOf(), - receiverUsername: tx.getReceiverUsername().valueOf() - }; - }) - }; - store.dispatch(setSignTransactionsCancelMessage(null)); - store.dispatch(setTransactionsToSign(signTransactionsPayload)); - store.dispatch( - setTransactionsDisplayInfo({ sessionId, transactionsDisplayInfo }) - ); - return { sessionId }; -} diff --git a/src/utils/transactions/operations/transformAndSignTransactions.ts b/src/utils/transactions/operations/transformTransactionsForSigning.ts similarity index 60% rename from src/utils/transactions/operations/transformAndSignTransactions.ts rename to src/utils/transactions/operations/transformTransactionsForSigning.ts index 233e1da..0b72126 100644 --- a/src/utils/transactions/operations/transformAndSignTransactions.ts +++ b/src/utils/transactions/operations/transformTransactionsForSigning.ts @@ -1,50 +1,26 @@ import { Address, Transaction } from '@multiversx/sdk-core'; -import BigNumber from 'bignumber.js'; -import { - EXTRA_GAS_LIMIT_GUARDED_TX, - GAS_LIMIT, - GAS_PER_DATA_BYTE, - GAS_PRICE -} from 'constants'; -import { newTransaction } from 'models/newTransaction'; -import { addressSelector, chainIDSelector } from 'reduxStore/selectors'; -import { store } from 'reduxStore/store'; +import { GAS_PRICE } from 'constants/index'; import { SendSimpleTransactionParamsType } from 'types'; import { getAccount } from 'utils/account/getAccount'; -import { getLatestNonce } from 'utils/account/getLatestNonce'; import { computeTransactionNonce } from './computeTransactionNonce'; +import { calculateGasLimit } from './calculateGasLimit'; +import { newTransaction } from './newTransaction'; +import { addressSelector, chainIdSelector } from 'store/selectors'; +import { getState } from 'store/store'; +import { getLatestNonce } from 'core/methods/account/getLatestNonce'; enum ErrorCodesEnum { 'invalidReceiver' = 'Invalid Receiver address', 'unknownError' = 'Unknown Error. Please check the transactions and try again' } -function calculateGasLimit({ - data, - isGuarded -}: { - data?: string; - isGuarded?: boolean; -}) { - const guardedAccountGasLimit = isGuarded ? EXTRA_GAS_LIMIT_GUARDED_TX : 0; - const bNconfigGasLimit = new BigNumber(GAS_LIMIT).plus( - guardedAccountGasLimit - ); - const bNgasPerDataByte = new BigNumber(GAS_PER_DATA_BYTE); - const bNgasValue = data - ? bNgasPerDataByte.times(Buffer.from(data).length) - : 0; - const bNgasLimit = bNconfigGasLimit.plus(bNgasValue); - const gasLimit = bNgasLimit.toString(10); - return gasLimit; -} - -export async function transformAndSignTransactions({ +export async function transformTransactionsForSigning({ transactions }: SendSimpleTransactionParamsType): Promise { - const address = addressSelector(store.getState()); + const state = getState(); + const address = addressSelector(state); const account = await getAccount(address); const accountNonce = getLatestNonce(account); return transactions.map((tx) => { @@ -78,8 +54,9 @@ export async function transformAndSignTransactions({ transactionNonce }); - const storeChainId = chainIDSelector(store.getState()).valueOf().toString(); + const storeChainId = chainIdSelector(state).valueOf().toString(); const transactionsChainId = chainID || storeChainId; + return newTransaction({ value, receiver: validatedReceiver, diff --git a/src/utils/transactions/parsers/getEventListDataHexValue.ts b/src/utils/transactions/parsers/getEventListDataHexValue.ts index 65e257b..a67d4be 100644 --- a/src/utils/transactions/parsers/getEventListDataHexValue.ts +++ b/src/utils/transactions/parsers/getEventListDataHexValue.ts @@ -1,4 +1,4 @@ -import { EventType } from 'types/serverTransactions.types'; +import { EventType } from 'types'; export function getEventListDataHexValue(event: EventType) { const dataBase64Buffer = Buffer.from(String(event?.data), 'base64'); diff --git a/src/utils/transactions/parsers/getEventListHighlight.ts b/src/utils/transactions/parsers/getEventListHighlight.ts index 6d10a8b..d463b31 100644 --- a/src/utils/transactions/parsers/getEventListHighlight.ts +++ b/src/utils/transactions/parsers/getEventListHighlight.ts @@ -1,4 +1,4 @@ -import { EventType } from 'types/serverTransactions.types'; +import { EventType } from 'types'; import { getWindowLocation } from 'utils/window/getWindowLocation'; export function getEventListHighlight(event: EventType, id?: string) { diff --git a/src/utils/transactions/parsers/getEventListInitialDecodeMethod.ts b/src/utils/transactions/parsers/getEventListInitialDecodeMethod.ts index 2724a63..570757c 100644 --- a/src/utils/transactions/parsers/getEventListInitialDecodeMethod.ts +++ b/src/utils/transactions/parsers/getEventListInitialDecodeMethod.ts @@ -1,4 +1,4 @@ -import { DecodeMethodEnum } from 'types/serverTransactions.types'; +import { DecodeMethodEnum } from 'types'; import { getWindowLocation } from 'utils/window/getWindowLocation'; export function getEventListInitialDecodeMethod() { diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/constants.ts b/src/utils/transactions/parsers/getInterpretedTransaction/constants.ts index 20dd6f6..2b7231b 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/constants.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/constants.ts @@ -1,4 +1,4 @@ -import { TransactionActionsEnum } from 'types/serverTransactions.types'; +import { TransactionActionsEnum } from 'types'; /** * If `action.name` or `function` is in `ACTIONS_WITH_MANDATORY_OPERATIONS[]`, transaction value will be computed based `operations` field diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/getInterpretedTransaction.ts b/src/utils/transactions/parsers/getInterpretedTransaction/getInterpretedTransaction.ts index ec66ae3..c8039ea 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/getInterpretedTransaction.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/getInterpretedTransaction.ts @@ -1,8 +1,5 @@ -import { - InterpretedTransactionType, - ServerTransactionType -} from 'types/serverTransactions.types'; -import { TokenArgumentType } from 'types/serverTransactions.types'; +import { InterpretedTransactionType, ServerTransactionType } from 'types'; +import { TokenArgumentType } from 'types'; import { isContract } from '../../dataDecoders/smartContractTransaction'; import { getTokenFromData } from 'utils/transactions/dataDecoders/getTokenFromData'; import { diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getOperationsMessages.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getOperationsMessages.ts index 577bb17..3ec26c4 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getOperationsMessages.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getOperationsMessages.ts @@ -1,4 +1,4 @@ -import { ServerTransactionType } from 'types/serverTransactions.types'; +import { ServerTransactionType } from 'types'; export function getOperationsMessages(transaction: ServerTransactionType) { return ( diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getReceiptMessage.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getReceiptMessage.ts index 89c6efd..1f02127 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getReceiptMessage.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getReceiptMessage.ts @@ -1,6 +1,6 @@ import BigNumber from 'bignumber.js'; import { DECIMALS, DIGITS, REFUNDED_GAS } from 'constants/index'; -import { ServerTransactionType } from 'types/serverTransactions.types'; +import { ServerTransactionType } from 'types'; import { formatAmount } from 'utils/operations/formatAmount'; const getReceiptValue = (transaction: ServerTransactionType) => { diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getScResultsMessages.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getScResultsMessages.ts index 4973437..9e9d8a9 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getScResultsMessages.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getScResultsMessages.ts @@ -1,4 +1,4 @@ -import { ServerTransactionType } from 'types/serverTransactions.types'; +import { ServerTransactionType } from 'types'; export default function getScResultsMessages( transaction: ServerTransactionType diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts index 5b2e50f..37c598f 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts @@ -2,7 +2,7 @@ import { TransactionActionCategoryEnum, TransactionActionsEnum, ServerTransactionType -} from 'types/serverTransactions.types'; +} from 'types'; export function getTransactionMethod(transaction: ServerTransactionType) { let transactionAction = 'Transaction'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiver.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiver.ts index 0ffa550..2e3967d 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiver.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiver.ts @@ -1,4 +1,4 @@ -import { ServerTransactionType } from 'types/serverTransactions.types'; +import { ServerTransactionType } from 'types'; export function getTransactionReceiver(transaction: ServerTransactionType) { let receiver = transaction.receiver; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiverAssets.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiverAssets.ts index 0c2e484..cbb7c97 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiverAssets.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionReceiverAssets.ts @@ -1,4 +1,4 @@ -import { ServerTransactionType } from 'types/serverTransactions.types'; +import { ServerTransactionType } from 'types'; import { getTransactionReceiver } from './getTransactionReceiver'; /** diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTokens.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTokens.ts index 85c966e..6434e8a 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTokens.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTokens.ts @@ -1,7 +1,4 @@ -import { - ServerTransactionType, - TokenArgumentType -} from 'types/serverTransactions.types'; +import { ServerTransactionType, TokenArgumentType } from 'types'; export function getTransactionTokens( transaction: ServerTransactionType diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTransferType.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTransferType.ts index 775bdf9..f08d8d8 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTransferType.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTransferType.ts @@ -2,7 +2,7 @@ import { ServerTransactionType, TransferTypeEnum, TransactionDirectionEnum -} from 'types/serverTransactions.types'; +} from 'types'; export function getTransactionTransferType( address: string, diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts index d0e36f9..a58b467 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts @@ -1,5 +1,4 @@ import { DECIMALS } from 'constants/index'; -import { NftEnumType } from 'types/tokens.types'; import { ACTIONS_WITH_EGLD_VALUE, @@ -11,15 +10,16 @@ import { import { getValueFromActions, getValueFromDataField, - getValueFromOperations + getValueFromOperations, + getEgldValueData, + getTitleText } from './helpers'; -import { getEgldValueData, getTitleText } from './helpers'; import { EgldValueDataType, NFTValueDataType, TokenValueDataType } from '../types'; -import { InterpretedTransactionType } from 'types'; +import { NftEnumType, InterpretedTransactionType } from 'types'; import { getTransactionTokens } from '../getTransactionTokens'; import { getTransactionActionNftText } from '../../../getTransactionActionNftText'; import { getTransactionActionTokenText } from '../../../getTransactionActionTokenText'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getEgldValueData.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getEgldValueData.ts index 29be9b8..bbdce49 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getEgldValueData.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getEgldValueData.ts @@ -1,5 +1,5 @@ import { DECIMALS } from 'constants/index'; -import { formatAmount } from 'utils'; +import { formatAmount } from 'utils/operations'; export function getEgldValueData(value: string) { return { diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts index ca282ac..5e0ebf3 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts @@ -17,6 +17,7 @@ export interface GetTransactionValueReturnType { export function getTitleText(transactionTokens: TokenArgumentType[]): string { const tokensArray = transactionTokens.map((transactionToken) => { const { isNft } = getIdentifierType(transactionToken.type); + if (isNft) { const { badgeText, tokenFormattedAmount, tokenLinkText } = getTransactionActionNftText({ @@ -28,6 +29,7 @@ export function getTitleText(transactionTokens: TokenArgumentType[]): string { const value = `${badge}${tokenFormattedAmount} ${tokenLinkText}`; return value; } + const { tokenFormattedAmount, tokenLinkText, token } = getTransactionActionTokenText({ token: transactionToken as TokenArgumentType diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromActions.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromActions.ts index 16cc542..123ba5e 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromActions.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromActions.ts @@ -1,5 +1,5 @@ import BigNumber from 'bignumber.js'; -import { InterpretedTransactionType } from 'types/serverTransactions.types'; +import { InterpretedTransactionType } from 'types'; import { getEgldValueData } from './getEgldValueData'; let warningLogged = false; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromDataField.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromDataField.ts index 4205e8e..2cbb266 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromDataField.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromDataField.ts @@ -1,5 +1,5 @@ import BigNumber from 'bignumber.js'; -import { InterpretedTransactionType } from 'types/serverTransactions.types'; +import { InterpretedTransactionType } from 'types'; import { decodeBase64 } from 'utils/decoders'; import { getEgldValueData } from './getEgldValueData'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromOperations.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromOperations.ts index 3775156..043bad0 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromOperations.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromOperations.ts @@ -1,6 +1,6 @@ -import { InterpretedTransactionType } from 'types/serverTransactions.types'; -import { getVisibleOperations } from 'utils/transactions/parsers/getVisibleOperations'; +import { InterpretedTransactionType } from 'types'; import { getEgldValueData } from './getEgldValueData'; +import { getVisibleOperations } from '../../../../getVisibleOperations'; let warningLogged = false; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/tests/getTransactionValue.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/tests/getTransactionValue.test.ts new file mode 100644 index 0000000..6409b1c --- /dev/null +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/tests/getTransactionValue.test.ts @@ -0,0 +1,210 @@ +import { NftEnumType, TransactionActionsEnum } from 'types'; +import { DECIMALS } from 'constants/index'; +import { getTransactionValue } from '../getTransactionValue'; +import { + ACTIONS_WITH_EGLD_VALUE, + ACTIONS_WITH_MANDATORY_OPERATIONS, + ACTIONS_WITH_VALUE_IN_DATA_FIELD +} from '../../../constants'; + +describe('getTransactionValue', () => { + const baseTransaction = { + fee: '1000000000000000', + data: '', + gasLimit: 50000, + gasPrice: 1000000000, + gasUsed: 50000, + txHash: '0x123...abc', + miniBlockHash: '0xabc...123', + nonce: 42, + receiver: 'erd1...receiver', + receiverShard: 1, + round: 100, + sender: 'erd1...sender', + senderShard: 0, + signature: '0xdef...456', + status: 'success', + timestamp: Date.now(), + value: '1000000000000000000', + price: 1.5, + transactionDetails: { + method: 'transfer', + transactionTokens: [] + }, + links: { + senderLink: 'sender/address', + receiverLink: 'receiver/address', + senderShardLink: 'shard/0', + receiverShardLink: 'shard/1', + transactionLink: 'tx/hash' + } + }; + + const baseAction = { + category: 'transfer', + name: TransactionActionsEnum.transfer, + description: 'Token transfer action', + arguments: { + tokenId: 'TEST-123', + amount: '1000000000000000000' + } + }; + + test('returns EGLD value for actions in ACTIONS_WITH_EGLD_VALUE', () => { + ACTIONS_WITH_EGLD_VALUE.forEach((actionName) => { + const transaction = { + ...baseTransaction, + action: { + ...baseAction, + name: actionName + } + }; + + const result = getTransactionValue({ transaction }); + + expect(result).toEqual({ + egldValueData: { + value: '1000000000000000000', + formattedValue: '1', + decimals: DECIMALS + } + }); + }); + }); + + test('handles NFT transactions', () => { + const mockNftToken = { + type: NftEnumType.NonFungibleESDT, + value: '1', + decimals: 0, + identifier: 'TEST-123-01' + }; + + const transaction = { + ...baseTransaction, + action: { + ...baseAction, + category: 'nft', + arguments: { + tokenId: mockNftToken.identifier, + amount: mockNftToken.value + } + }, + tokens: [mockNftToken], + tokenIdentifier: mockNftToken.identifier, + tokenValue: mockNftToken.value + }; + + const result = getTransactionValue({ transaction }); + + expect(result.nftValueData).toBeDefined(); + expect(result.nftValueData?.token).toEqual(mockNftToken); + expect(result.nftValueData?.value).toBe('1'); + expect(result.nftValueData?.decimals).toBe(0); + }); + + test('handles fungible token transactions', () => { + const mockToken = { + type: 'FungibleESDT', + value: '1000000000000000000', + decimals: 18, + identifier: 'TEST-123' + }; + + const transaction = { + ...baseTransaction, + action: { + ...baseAction, + category: 'esdt', + arguments: { + tokenId: mockToken.identifier, + amount: mockToken.value + } + }, + tokens: [mockToken], + tokenIdentifier: mockToken.identifier, + tokenValue: mockToken.value, + function: 'transfer', + operations: [], + results: [] + }; + + const result = getTransactionValue({ transaction }); + + expect(result.tokenValueData).toBeDefined(); + expect(result.tokenValueData?.token).toEqual(mockToken); + expect(result.tokenValueData?.value).toBe('1000000000000000000'); + expect(result.tokenValueData?.decimals).toBe(18); + }); + + test('handles multiple tokens with titleText', () => { + const mockTokens = [ + { + type: 'FungibleESDT', + value: '1000000000000000000', + decimals: 18, + identifier: 'TEST-123' + }, + { + type: 'FungibleESDT', + value: '2000000000000000000', + decimals: 18, + identifier: 'TEST-456' + } + ]; + + const transaction = { + ...baseTransaction, + action: { + ...baseAction, + category: 'multiToken', + arguments: { + tokens: mockTokens.map((token) => ({ + tokenId: token.identifier, + amount: token.value + })) + } + }, + tokens: mockTokens + }; + + const result = getTransactionValue({ transaction }); + + expect(result.tokenValueData?.titleText).toBeDefined(); + expect(result.tokenValueData?.transactionTokens).toEqual(mockTokens); + }); + + test('handles actions with value in data field', () => { + const transaction = { + ...baseTransaction, + action: { + ...baseAction, + name: ACTIONS_WITH_VALUE_IN_DATA_FIELD[0], + category: 'transfer', + arguments: { + value: '2000000000000000000' + } + } + }; + + const result = getTransactionValue({ transaction }); + expect(result).toBeDefined(); + }); + + test('handles actions with mandatory operations', () => { + const transaction = { + ...baseTransaction, + action: { + ...baseAction, + name: ACTIONS_WITH_MANDATORY_OPERATIONS[0], + category: 'operation', + arguments: { + operations: [] + } + } + }; + + const result = getTransactionValue({ transaction }); + expect(result).toBeDefined(); + }); +}); diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/index.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/index.ts index 87c997e..5b7a775 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/index.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/index.ts @@ -13,3 +13,4 @@ export * from './getReceiptMessage'; export * from './getScResultsMessages'; export * from './getExplorerLink'; export * from './getTransactionValue'; +export * from './types'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/base-transaction-mock.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/base-transaction-mock.ts index 23c9285..98a406f 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/base-transaction-mock.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/base-transaction-mock.ts @@ -1,4 +1,4 @@ -import { TransactionActionsEnum } from 'types/serverTransactions.types'; +import { TransactionActionsEnum } from 'types'; export const baseTransactionMock = { blockHash: '', diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getOperationsMessages.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getOperationsMessages.test.ts index bda15cf..0e68bd1 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getOperationsMessages.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getOperationsMessages.test.ts @@ -2,7 +2,7 @@ import { OperationType, TransactionOperationActionTypeEnum, VisibleTransactionOperationType -} from 'types/serverTransactions.types'; +} from 'types'; import { getOperationsMessages } from '../getOperationsMessages'; import { baseTransactionMock } from './base-transaction-mock'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getScResultsMessages.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getScResultsMessages.test.ts index d6cc71d..15c08c9 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getScResultsMessages.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getScResultsMessages.test.ts @@ -1,7 +1,4 @@ -import { - ResultType, - ServerTransactionType -} from 'types/serverTransactions.types'; +import { ResultType, ServerTransactionType } from 'types'; import getScResultsMessages from '../getScResultsMessages'; import { baseTransactionMock } from './base-transaction-mock'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionMethod.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionMethod.test.ts index 2ff9899..c3ce93b 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionMethod.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionMethod.test.ts @@ -2,7 +2,7 @@ import { ServerTransactionType, TransactionActionCategoryEnum, TransactionActionsEnum -} from 'types/serverTransactions.types'; +} from 'types'; import { getTransactionMethod } from '../getTransactionMethod'; import { baseTransactionMock } from './base-transaction-mock'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiver.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiver.test.ts index 60de128..242e1ac 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiver.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiver.test.ts @@ -2,7 +2,7 @@ import { ServerTransactionType, TransactionActionCategoryEnum, TransactionActionsEnum -} from 'types/serverTransactions.types'; +} from 'types'; import { getTransactionReceiver } from '../getTransactionReceiver'; import { baseTransactionMock } from './base-transaction-mock'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiverAssets.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiverAssets.test.ts index 148027d..9430b49 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiverAssets.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiverAssets.test.ts @@ -2,7 +2,7 @@ import { ServerTransactionType, TransactionActionCategoryEnum, TransactionActionsEnum -} from 'types/serverTransactions.types'; +} from 'types'; import { getTransactionReceiverAssets } from '../getTransactionReceiverAssets'; import { baseTransactionMock } from './base-transaction-mock'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTokens.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTokens.test.ts index cdbce40..ab24f1b 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTokens.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTokens.test.ts @@ -2,7 +2,7 @@ import { ServerTransactionType, TransactionActionCategoryEnum, TransactionActionsEnum -} from 'types/serverTransactions.types'; +} from 'types'; import { getTransactionTokens } from '../getTransactionTokens'; import { baseTransactionMock } from './base-transaction-mock'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTransferType.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTransferType.test.ts index e4ccc26..2d3ab66 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTransferType.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTransferType.test.ts @@ -1,8 +1,5 @@ -import { TransactionDirectionEnum } from 'types/serverTransactions.types'; -import { - ServerTransactionType, - TransferTypeEnum -} from 'types/serverTransactions.types'; +import { TransactionDirectionEnum } from 'types'; +import { ServerTransactionType, TransferTypeEnum } from 'types'; import { getTransactionTransferType } from '../getTransactionTransferType'; import { baseTransactionMock } from './base-transaction-mock'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/index.tsx b/src/utils/transactions/parsers/getInterpretedTransaction/index.ts similarity index 100% rename from src/utils/transactions/parsers/getInterpretedTransaction/index.tsx rename to src/utils/transactions/parsers/getInterpretedTransaction/index.ts index 81608d6..0c1d0ce 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/index.tsx +++ b/src/utils/transactions/parsers/getInterpretedTransaction/index.ts @@ -1,3 +1,3 @@ -export * from './getInterpretedTransaction'; export * from './constants'; +export * from './getInterpretedTransaction'; export * from './helpers'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/tests/extended-transaction-mock.ts b/src/utils/transactions/parsers/getInterpretedTransaction/tests/extended-transaction-mock.ts index 33dece5..d74738b 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/tests/extended-transaction-mock.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/tests/extended-transaction-mock.ts @@ -4,7 +4,7 @@ import { TransactionActionCategoryEnum, TransactionActionsEnum, VisibleTransactionOperationType -} from 'types/serverTransactions.types'; +} from 'types'; import { EsdtEnumType } from 'types/tokens.types'; import { baseTransactionMock } from '../helpers/tests/base-transaction-mock'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/tests/getInterpretedTransaction.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/tests/getInterpretedTransaction.test.ts index 121490f..fe4a4d9 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/tests/getInterpretedTransaction.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/tests/getInterpretedTransaction.test.ts @@ -1,11 +1,5 @@ -import { - InterpretedTransactionType, - TransactionDirectionEnum -} from 'types/serverTransactions.types'; -import { - ServerTransactionType, - TransactionActionsEnum -} from 'types/serverTransactions.types'; +import { InterpretedTransactionType, TransactionDirectionEnum } from 'types'; +import { ServerTransactionType, TransactionActionsEnum } from 'types'; import { getInterpretedTransaction } from '../getInterpretedTransaction'; import { explorerUrlBuilder } from '../helpers/explorerUrlBuilder'; diff --git a/src/utils/transactions/parsers/getScamFlag.tsx b/src/utils/transactions/parsers/getScamFlag.tsx deleted file mode 100644 index 72660db..0000000 --- a/src/utils/transactions/parsers/getScamFlag.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import * as linkify from 'linkifyjs'; -import { - SuspiciousLinkType, - SuspiciousLinkPropsType, - TextWithLinksType -} from 'types'; - -export const getTextWithLinks = (text: string): TextWithLinksType => { - const links = linkify.find(text); - - // If no links are present in the text, return the text unmodified - if (!links.length) { - return { - textWithLinks: text, - hasLinks: false - }; - } - - let textWithLinks = text; - - // Replace the previous links in text with normalized links - for (const link of links) { - const previousLink = text.substring(link.start, link.end); - textWithLinks = textWithLinks.replace(previousLink, link.value); - } - - return { - textWithLinks, - hasLinks: true - }; -}; - -/** - * @description It checks if an asset contains suspicious info - * If it contains text with links inside, it contains scam info, or it is marked as NSFW, - * then it has suspicious info and may be a scam - */ -export const getScamFlag = ({ - message, - scamInfo, - isNsfw, - verified, - messagePrefix = 'Message hidden due to suspicious content - ' -}: SuspiciousLinkPropsType): SuspiciousLinkType => { - if (verified) { - return { - message: '', - textWithLinks: '', - isSuspicious: false - }; - } - - const outputMessage = `${messagePrefix}${ - scamInfo?.info ?? 'suspicious content' - }`; - const { textWithLinks, hasLinks } = getTextWithLinks(message); - - if (hasLinks || isNsfw || scamInfo) { - return { - message: outputMessage, - textWithLinks, - isSuspicious: true - }; - } - - return { - message: '', - textWithLinks, - isSuspicious: false - }; -}; diff --git a/src/utils/transactions/parsers/getTransactionActionNftText.ts b/src/utils/transactions/parsers/getTransactionActionNftText.ts index 3c57307..90e7775 100644 --- a/src/utils/transactions/parsers/getTransactionActionNftText.ts +++ b/src/utils/transactions/parsers/getTransactionActionNftText.ts @@ -1,7 +1,7 @@ -import { TokenArgumentType } from 'types/serverTransactions.types'; +import { TokenArgumentType } from 'types'; import { NftEnumType } from 'types/tokens.types'; import { formatAmount } from 'utils/operations'; -import { explorerUrlBuilder } from '../getInterpretedTransaction/helpers/explorerUrlBuilder'; +import { explorerUrlBuilder } from './getInterpretedTransaction'; export interface TransactionActionNftType { token: TokenArgumentType; diff --git a/src/utils/transactions/parsers/getTransactionActionTokenText.ts b/src/utils/transactions/parsers/getTransactionActionTokenText.ts index 823753b..54d0ad0 100644 --- a/src/utils/transactions/parsers/getTransactionActionTokenText.ts +++ b/src/utils/transactions/parsers/getTransactionActionTokenText.ts @@ -1,7 +1,7 @@ import { DECIMALS } from 'constants/index'; -import { TokenArgumentType } from 'types/serverTransactions.types'; +import { TokenArgumentType } from 'types'; import { formatAmount } from 'utils/operations'; -import { explorerUrlBuilder } from '../getInterpretedTransaction/helpers/explorerUrlBuilder'; +import { explorerUrlBuilder } from './getInterpretedTransaction'; export interface TransactionActionTokenType { token: TokenArgumentType; diff --git a/src/utils/transactions/parsers/getTransactionStatus.ts b/src/utils/transactions/parsers/getTransactionStatus.ts index 73be8a4..315380b 100644 --- a/src/utils/transactions/parsers/getTransactionStatus.ts +++ b/src/utils/transactions/parsers/getTransactionStatus.ts @@ -1,5 +1,5 @@ import { TransactionServerStatusesEnum } from 'types/enums.types'; -import { InterpretedTransactionType } from 'types/serverTransactions.types'; +import { InterpretedTransactionType } from 'types'; export function getTransactionStatus(transaction: InterpretedTransactionType) { const statusIs = (compareTo: string) => diff --git a/src/utils/transactions/parsers/index.ts b/src/utils/transactions/parsers/index.ts index 5898063..849789b 100644 --- a/src/utils/transactions/parsers/index.ts +++ b/src/utils/transactions/parsers/index.ts @@ -1,20 +1,23 @@ +export * from './getAreTransactionsOnSameShard'; export * from './getEventListDataHexValue'; export * from './getEventListHighlight'; +export * from './getEventListInitialDecodeMethod'; export * from './getOperationDirection'; -export * from '../dataDecoders/getScResultsDecodedData'; -export * from '../dataDecoders/getScResultsHighlight'; -export * from './getScamFlag'; +export * from './getOperationsDetails'; export * from './getShardText'; export * from './getTransactionActionNftText'; export * from './getTransactionActionTokenText'; export * from './getTransactionFee'; +export * from './getTransactionLink'; export * from './getTransactionLinkWithLabel'; export * from './getTransactionMessages'; +export * from './getTransactionsDetails'; export * from './getTransactionStatus'; export * from './getTransactionStatusText'; export * from './getTrimmedHash'; +export * from './getUsernameForTransaction'; export * from './getVisibleOperations'; -export * from './transactionActionUnwrapper'; -export * from '../dataDecoders/useDataDecode'; -export * from '../dataDecoders/useDataDecodeMethod'; -export * from './useGetOperationList'; +export * from './parseMultiEsdtTransferData'; +export * from './parseMultiEsdtTransferDataForMultipleTransactions'; +export * from './parseTransactionAfterSigning'; +export * from './transactionStateByStatus'; diff --git a/src/utils/transactions/parsers/parseMultiEsdtTransferDataForMultipleTransactions.ts b/src/utils/transactions/parsers/parseMultiEsdtTransferDataForMultipleTransactions.ts index e34b71c..b171748 100644 --- a/src/utils/transactions/parsers/parseMultiEsdtTransferDataForMultipleTransactions.ts +++ b/src/utils/transactions/parsers/parseMultiEsdtTransferDataForMultipleTransactions.ts @@ -4,7 +4,7 @@ import { TransactionDataTokenType, TransactionsDataTokensType } from 'types'; -import { getTokenFromData } from '../dataDecoders/getTokenFromData'; +import { getTokenFromData } from '../dataDecoders'; import { parseMultiEsdtTransferData } from './parseMultiEsdtTransferData'; function addTransactionDataToParsedInfo({ diff --git a/src/utils/transactions/parsers/tests/getScamFlag.test.ts b/src/utils/transactions/parsers/tests/getScamFlag.test.ts deleted file mode 100644 index 9b45baf..0000000 --- a/src/utils/transactions/parsers/tests/getScamFlag.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { getScamFlag } from '../getScamFlag'; - -describe('scamDetect tests', () => { - const output = 'Message hidden due to suspicious content - '; - const strings: { [key: string]: string[] } = { - '👉 link.com': ['👉 link.com', ''], - 'first-link.com or 🎉 second-link.com 🎉': [ - 'first-link.com or 🎉 second-link.com 🎉', - '' - ], - 'http://google.com 🎉': ['http://google.com 🎉', ''], - '👉 https://linkedin.com 🎉': ['👉 https://linkedin.com 🎉', ''], - 'http://google.com?asd=true': ['http://google.com?asd=true', ''], - 'http://www1.google.com': ['http://www1.google.com', ''], - 'http://www.google.ceva.com': ['http://www.google.ceva.com', ''], - 'access: 👉 www.lottery-multiversx.com': [ - 'access: 👉 www.lottery-multiversx.com', - ' - Scam report' - ], - '[...] 🅻🅾🆃🆃🅴🆁🆈': ['[...] 🅻🅾🆃🆃🅴🆁🆈', 'Lottery scam report'], - 'Cool nft': ['Cool nft', '', 'yes'] - }; - for (let i = 0; i < Object.keys(strings).length; i++) { - const inputMessage = Object.keys(strings)[i]; - const [msg, reason, isNsfw] = strings[inputMessage]; - - test(`anonymize ${inputMessage} -> ${msg}`, () => { - const { message: result, textWithLinks } = getScamFlag({ - message: inputMessage, - scamInfo: { - info: reason, - type: msg - }, - isNsfw: Boolean(isNsfw) - }); - expect(result).toEqual(`${output}${reason}`); - expect(textWithLinks).toEqual(msg); - }); - } -}); diff --git a/src/utils/transactions/parsers/useGetOperationList.tsx b/src/utils/transactions/parsers/useGetOperationList.tsx deleted file mode 100644 index 137e01f..0000000 --- a/src/utils/transactions/parsers/useGetOperationList.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { useState } from 'react'; -import { - OperationType, - TransactionOperationActionTypeEnum -} from 'types/serverTransactions.types'; - -import { WithTransactionType } from '../../../UI/types'; - -export const internalTransactionActions = [ - TransactionOperationActionTypeEnum.create, - TransactionOperationActionTypeEnum.localMint, - TransactionOperationActionTypeEnum.ESDTLocalMint, - TransactionOperationActionTypeEnum.addQuantity, - TransactionOperationActionTypeEnum.burn, - TransactionOperationActionTypeEnum.localBurn, - TransactionOperationActionTypeEnum.ESDTLocalBurn, - TransactionOperationActionTypeEnum.wipe, - TransactionOperationActionTypeEnum.writeLog, - TransactionOperationActionTypeEnum.signalError -]; - -enum ButtonTextEnum { - inOut = 'Show in/out operations', - fewer = 'Show fewer operations', - all = 'Show all operations' -} - -export interface OperationListType extends WithTransactionType { - operations: OperationType[]; - listLength?: number; -} - -export function getOperationList({ - operations, - transaction, - isExpanded = false, - listLength = 25 -}: OperationListType & { - isExpanded?: boolean; -}) { - const filteredOperations = operations.filter( - (operation) => - !internalTransactionActions.includes(operation.action) && - (operation.sender === transaction.sender || - operation.receiver === transaction.sender) - ); - - const importantOperations = - filteredOperations.length > 0 ? filteredOperations : operations; - - const displayedOperations = - importantOperations.length > listLength - ? importantOperations.slice(0, listLength) - : importantOperations; - - const collapsedOperations = - importantOperations.length > listLength - ? importantOperations.slice(listLength, importantOperations.length) - : []; - - const toggleButtonText = isExpanded - ? filteredOperations.length > 0 - ? ButtonTextEnum.inOut - : ButtonTextEnum.fewer - : ButtonTextEnum.all; - - const showToggleButton = - displayedOperations.length !== operations.length || - collapsedOperations.length > 0; - - return { - displayedOperations: isExpanded ? operations : displayedOperations, - toggleButtonText, - showToggleButton - }; -} - -export function useGetOperationList(props: OperationListType) { - const [isExpanded, setIsExpanded] = useState(false); - const onToggleButtonClick = () => { - setIsExpanded((existing) => !existing); - }; - - const { displayedOperations, showToggleButton, toggleButtonText } = - getOperationList({ ...props, isExpanded }); - - return { - isExpanded, - displayedOperations, - showToggleButton, - toggleButtonText, - onToggleButtonClick - }; -} diff --git a/src/utils/validation/index.ts b/src/utils/validation/index.ts index ec1521a..3718fec 100644 --- a/src/utils/validation/index.ts +++ b/src/utils/validation/index.ts @@ -1,6 +1,5 @@ -export * from './stringIsInteger'; -export * from './stringIsFloat'; -export * from './maxDecimals'; -export * from './getIdentifierType'; export * from './addressIsValid'; -export * from './isContract'; +export * from './getIdentifierType'; +export * from './maxDecimals'; +export * from './stringIsFloat'; +export * from './stringIsInteger'; diff --git a/src/utils/validation/isContract.ts b/src/utils/validation/isContract.ts deleted file mode 100644 index a3a5b06..0000000 --- a/src/utils/validation/isContract.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Address, TransactionPayload } from '@multiversx/sdk-core'; -import { ESDTTransferTypesEnum, TypesOfSmartContractCallsEnum } from 'types'; -import { addressIsValid } from './addressIsValid'; -import { isStringBase64 } from 'utils/decoders/base64Utils'; - -export function isContract( - receiver: string, - sender?: string, - data = '' -): boolean { - const isValid = addressIsValid(receiver); - - if (!isValid) { - return false; - } - - const isContract = new Address(receiver).isContractAddress(); - - if (isContract) { - return true; - } - - const extractedAddress = getAddressFromDataField({ receiver, data }); - - if (!extractedAddress) { - return false; - } - - const isExtractedAddressContractCall = new Address( - extractedAddress - ).isContractAddress(); - - return ( - isExtractedAddressContractCall || isSelfESDTContract(receiver, sender, data) - ); -} - -const isHexValidCharacters = (str: string) => { - return str.toLowerCase().match(/[0-9a-f]/g); -}; -const isHexValidLength = (str: string) => { - return str.length % 2 === 0; -}; - -export function isSelfESDTContract( - receiver: string, - sender?: string, - data?: string -) { - const parts = data?.split('@'); - if (parts == null) { - return false; - } - const [type, ...restParts] = parts; - const isSelfTransaction = - sender != null && receiver != null && receiver === sender; - const isCorrectESDTType = Object.values(ESDTTransferTypesEnum).includes( - type as ESDTTransferTypesEnum - ); - const areDataPartsValid = restParts.every( - (part) => isHexValidCharacters(part) && isHexValidLength(part) - ); - return isSelfTransaction && isCorrectESDTType && areDataPartsValid; -} - -export function getAddressFromDataField({ - receiver, - data -}: { - receiver: string; - data: string; -}) { - try { - if (!data) { - return receiver; - } - const parsedData = isStringBase64(data) - ? TransactionPayload.fromEncoded(data).toString() - : data; - - const addressIndex = getAddressIndex(parsedData); - - const parts = parsedData.split('@'); - return addressIndex > -1 ? parts[addressIndex] : receiver; - } catch (err) { - console.log(err); - return; - } -} - -function getAddressIndex(data: string) { - if (data.includes(TypesOfSmartContractCallsEnum.MultiESDTNFTTransfer)) { - return 1; - } - if (data.includes(TypesOfSmartContractCallsEnum.ESDTNFTTransfer)) { - return 4; - } - return -1; -} diff --git a/src/utils/validation/tests/isContract.test.ts b/src/utils/validation/tests/isContract.test.ts deleted file mode 100644 index 88a9e24..0000000 --- a/src/utils/validation/tests/isContract.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { isContract } from '../isContract'; - -const userAddress = - 'erd1a07ey0xj28u90mtk8858zsavs0cj7s3cy74ufgxdmcq3nslr0y2st2aaax'; -const contractAddress = - 'erd1qqqqqqqqqqqqqpgqv9gxgq8nurz754spjfck6rdwlg9etpcp0n4sjg2dhc'; - -describe('isContract tests', () => { - it('returns false for user account', () => { - const valid = isContract(userAddress); - expect(valid).toBe(false); - }); - it('returns true for smart contract', () => { - const valid = isContract(contractAddress); - expect(valid).toBe(true); - }); - it('returns false for invalid address', () => { - const valid = isContract('erd1qqqqqqqqqqqqqqqpq'); - expect(valid).toBe(false); - }); - it('returns true for contract address in data', () => { - const valid = isContract( - userAddress, - undefined, - 'MultiESDTNFTTransfer@0000000000000000050061506400f3e0c5ea560192716d0daefa0b9587017ceb@02@5745474c442d383836303061@@8ac7230489e80000@555344432d613332393036@@464e61d6@6164644c6971756964697479@8963dd8c2c5e0000@459a65fa' - ); - expect(valid).toBe(true); - }); -}); From f7444649f266a5c056a38eed1704e0bdf51913b7 Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Wed, 4 Dec 2024 15:04:31 +0200 Subject: [PATCH 05/23] Removed unused code --- src/store/slices/transactionsSlice/index.ts | 2 - .../transactionsSlice/transactionsSlice.ts | 22 ----- .../transactionsSlice.types.ts | 51 ---------- src/utils/transactions/batch/index.ts | 2 - .../batch/sendBatchTransactions.ts | 61 ------------ .../{ => tests}/sendBatchTransactions.test.ts | 2 +- .../batch/updateBatchTransactionsStatuses.ts | 45 --------- src/utils/transactions/dataDecoders/index.ts | 2 - .../dataDecoders/smartContractTransaction.ts | 2 +- .../dataDecoders/useDataDecode.ts | 64 ------------- .../dataDecoders/useDataDecodeMethod.ts | 26 ----- .../operations/calculateFeeLimit.ts | 78 --------------- .../operations/calculateGasLimit.ts | 26 ----- .../operations/clearTransactions.ts | 33 ------- .../operations/computeTransactionNonce.ts | 17 ---- src/utils/transactions/operations/index.ts | 9 -- .../transactions/operations/newTransaction.ts | 52 ---------- .../operations/sendTransactions.ts | 51 ---------- .../tests/calculateFeeLimit.test.ts | 96 ------------------- .../transformTransactionsForSigning.ts | 75 --------------- .../operations/transformTransactionsToSign.ts | 26 ----- .../operations/updateSignedTransactions.ts | 44 --------- .../transactions/url/buildCallbackUrl.ts | 49 ---------- src/utils/transactions/url/index.ts | 2 - .../url/removeTransactionParamsFromUrl.ts | 23 ----- .../url/tests/builtCallbackUrl.test.ts | 62 ------------ .../removeTransactionParamsFromUrl.test.ts | 43 --------- 27 files changed, 2 insertions(+), 963 deletions(-) delete mode 100644 src/store/slices/transactionsSlice/index.ts delete mode 100644 src/store/slices/transactionsSlice/transactionsSlice.ts delete mode 100644 src/store/slices/transactionsSlice/transactionsSlice.types.ts delete mode 100644 src/utils/transactions/batch/sendBatchTransactions.ts rename src/utils/transactions/batch/{ => tests}/sendBatchTransactions.test.ts (98%) delete mode 100644 src/utils/transactions/batch/updateBatchTransactionsStatuses.ts delete mode 100644 src/utils/transactions/dataDecoders/useDataDecode.ts delete mode 100644 src/utils/transactions/dataDecoders/useDataDecodeMethod.ts delete mode 100755 src/utils/transactions/operations/calculateFeeLimit.ts delete mode 100644 src/utils/transactions/operations/calculateGasLimit.ts delete mode 100644 src/utils/transactions/operations/clearTransactions.ts delete mode 100644 src/utils/transactions/operations/computeTransactionNonce.ts delete mode 100644 src/utils/transactions/operations/index.ts delete mode 100644 src/utils/transactions/operations/newTransaction.ts delete mode 100644 src/utils/transactions/operations/sendTransactions.ts delete mode 100644 src/utils/transactions/operations/tests/calculateFeeLimit.test.ts delete mode 100644 src/utils/transactions/operations/transformTransactionsForSigning.ts delete mode 100644 src/utils/transactions/operations/transformTransactionsToSign.ts delete mode 100644 src/utils/transactions/operations/updateSignedTransactions.ts delete mode 100644 src/utils/transactions/url/buildCallbackUrl.ts delete mode 100644 src/utils/transactions/url/index.ts delete mode 100644 src/utils/transactions/url/removeTransactionParamsFromUrl.ts delete mode 100644 src/utils/transactions/url/tests/builtCallbackUrl.test.ts delete mode 100644 src/utils/transactions/url/tests/removeTransactionParamsFromUrl.test.ts diff --git a/src/store/slices/transactionsSlice/index.ts b/src/store/slices/transactionsSlice/index.ts deleted file mode 100644 index 7b69550..0000000 --- a/src/store/slices/transactionsSlice/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './transactionsSlice.types'; -export * from './transactionsSlice'; diff --git a/src/store/slices/transactionsSlice/transactionsSlice.ts b/src/store/slices/transactionsSlice/transactionsSlice.ts deleted file mode 100644 index f2eb03d..0000000 --- a/src/store/slices/transactionsSlice/transactionsSlice.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { TransactionsSliceType } from './transactionsSlice.types'; -import { StateCreator } from 'zustand/vanilla'; -import { MutatorsIn, StoreType } from 'store/store.types'; - -export const initialState: TransactionsSliceType = { - signedTransactions: {}, - transactionsToSign: null, - signTransactionsError: null, - signTransactionsCancelMessage: null, - customTransactionInformationForSessionId: {} -}; - -function getTransactionsSlice(): StateCreator< - StoreType, - MutatorsIn, - [], - TransactionsSliceType -> { - return () => initialState; -} - -export const transactionsSlice = getTransactionsSlice(); diff --git a/src/store/slices/transactionsSlice/transactionsSlice.types.ts b/src/store/slices/transactionsSlice/transactionsSlice.types.ts deleted file mode 100644 index 0b251c1..0000000 --- a/src/store/slices/transactionsSlice/transactionsSlice.types.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - CustomTransactionInformation, - ServerTransactionType, - SignedTransactionsBodyType, - SignedTransactionsType, - SignedTransactionType, - TransactionBatchStatusesEnum, - TransactionServerStatusesEnum, - TransactionsToSignType -} from 'types'; -import { Transaction } from '@multiversx/sdk-core/out'; - -export interface UpdateSignedTransactionsPayloadType { - sessionId: string; - status: TransactionBatchStatusesEnum; - errorMessage?: string; - transactions?: SignedTransactionType[]; - customTransactionInformationOverrides?: Partial; -} - -export interface MoveTransactionsToSignedStatePayloadType - extends SignedTransactionsBodyType { - sessionId: string; - customTransactionInformation?: CustomTransactionInformation; -} - -export interface UpdateSignedTransactionStatusPayloadType { - sessionId: string; - transactionHash: string; - status: TransactionServerStatusesEnum; - serverTransaction?: ServerTransactionType; - errorMessage?: string; - inTransit?: boolean; -} - -export interface TransactionsSliceType { - signedTransactions: SignedTransactionsType; - transactionsToSign: TransactionsToSignType | null; - signTransactionsError: string | null; - signTransactionsCancelMessage: string | null; - customTransactionInformationForSessionId: { - [sessionId: string]: CustomTransactionInformation; - }; -} - -export interface TransactionsToSignReturnType { - callbackRoute?: string; - sessionId: string; - transactions: Transaction[]; - customTransactionInformation: CustomTransactionInformation; -} diff --git a/src/utils/transactions/batch/index.ts b/src/utils/transactions/batch/index.ts index cd4f1c6..9b00dbf 100644 --- a/src/utils/transactions/batch/index.ts +++ b/src/utils/transactions/batch/index.ts @@ -2,6 +2,4 @@ export * from './generateBatchTransactionsGrouping'; export * from './getBatchTransactionsStatusFromApi'; export * from './getIsSequential'; export * from './getTransactionsStatus'; -export * from './sendBatchTransactions'; export * from './sequentialToFlatArray'; -export * from './updateBatchTransactionsStatuses'; diff --git a/src/utils/transactions/batch/sendBatchTransactions.ts b/src/utils/transactions/batch/sendBatchTransactions.ts deleted file mode 100644 index a3795e3..0000000 --- a/src/utils/transactions/batch/sendBatchTransactions.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Transaction } from '@multiversx/sdk-core/out'; -import { addressSelector } from 'reduxStore/selectors'; -import { store } from 'reduxStore/store'; -import { - SendBatchTransactionReturnType, - SendBatchTransactionsParamsType, - SimpleTransactionType -} from 'types'; -import { generateBatchTransactionsGrouping } from 'utils/transactions/batch/generateBatchTransactionsGrouping'; -import { getDefaultCallbackUrl } from 'utils/window'; -import { signTransactions } from '../operations/signTransactions'; -import { transformTransactionsToSign } from './utils/transformTransactionsToSign'; - -export async function sendBatchTransactions({ - transactions, - transactionsDisplayInfo, - redirectAfterSign = true, - callbackRoute = getDefaultCallbackUrl(), - signWithoutSending = false, - completedTransactionsDelay, - sessionInformation, - skipGuardian, - hasConsentPopup, - minGasLimit -}: SendBatchTransactionsParamsType): Promise { - try { - const address = addressSelector(store.getState()); - const transactionsPayload = transactions.flat(); - - const transactionsToSign = await transformTransactionsToSign({ - transactions: transactionsPayload as SimpleTransactionType[], - minGasLimit - }); - - const grouping = generateBatchTransactionsGrouping(transactions); - - const { sessionId, error } = await signTransactions({ - transactions: transactionsToSign as Transaction[], - minGasLimit, - callbackRoute, - transactionsDisplayInfo, - customTransactionInformation: { - grouping, - redirectAfterSign, - completedTransactionsDelay, - sessionInformation, - skipGuardian, - signWithoutSending, - hasConsentPopup - } - }); - - return { - error, - batchId: `${sessionId}-${address}` - }; - } catch (err) { - console.error('error signing transaction', err as any); - return { error: err as any, batchId: null }; - } -} diff --git a/src/utils/transactions/batch/sendBatchTransactions.test.ts b/src/utils/transactions/batch/tests/sendBatchTransactions.test.ts similarity index 98% rename from src/utils/transactions/batch/sendBatchTransactions.test.ts rename to src/utils/transactions/batch/tests/sendBatchTransactions.test.ts index 3512ab1..54e5edf 100644 --- a/src/utils/transactions/batch/sendBatchTransactions.test.ts +++ b/src/utils/transactions/batch/tests/sendBatchTransactions.test.ts @@ -1,7 +1,7 @@ import { addressSelector } from 'reduxStore/selectors'; import { store } from 'reduxStore/store'; import { getWindowLocation } from 'utils/window/getWindowLocation'; -import { sendBatchTransactions } from './sendBatchTransactions'; +import { sendBatchTransactions } from '../sendBatchTransactions'; import { signTransactions } from '../operations/signTransactions'; import { transformTransactionsToSign } from './utils/transformTransactionsToSign'; diff --git a/src/utils/transactions/batch/updateBatchTransactionsStatuses.ts b/src/utils/transactions/batch/updateBatchTransactionsStatuses.ts deleted file mode 100644 index 8b3c845..0000000 --- a/src/utils/transactions/batch/updateBatchTransactionsStatuses.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - ServerTransactionType, - SignedTransactionType, - TransactionServerStatusesEnum -} from 'types'; -import { sequentialToFlatArray } from './sequentialToFlatArray'; - -export function updateBatchTransactionsStatuses({ - batchId, - sessionId, - transactions -}: { - batchId: string; - sessionId: string; - transactions: SignedTransactionType[] | SignedTransactionType[][]; -}) { - const transactionsArray = sequentialToFlatArray({ - transactions - }); - - const batchIsSuccessful = transactionsArray.every( - (transaction) => - transaction.status === TransactionServerStatusesEnum.success - ); - - if (transactionsArray.length === 0) { - return; - } - - for (const transaction of transactionsArray) { - const { hash, status } = transaction; - - store.dispatch( - updateSignedTransactionStatus({ - sessionId, - status, - transactionHash: hash - }) - ); - } - - if (batchIsSuccessful) { - removeBatchTransactions(batchId); - } -} diff --git a/src/utils/transactions/dataDecoders/index.ts b/src/utils/transactions/dataDecoders/index.ts index e930cc2..2dbb6f4 100644 --- a/src/utils/transactions/dataDecoders/index.ts +++ b/src/utils/transactions/dataDecoders/index.ts @@ -6,5 +6,3 @@ export * from './getScResultsInitialDecodeMethod'; export * from './getTokenFromData'; export * from './getUnHighlightedDataFieldParts'; export * from './smartContractTransaction'; -export * from './useDataDecode'; -export * from './useDataDecodeMethod'; diff --git a/src/utils/transactions/dataDecoders/smartContractTransaction.ts b/src/utils/transactions/dataDecoders/smartContractTransaction.ts index 087acbf..0fad04f 100644 --- a/src/utils/transactions/dataDecoders/smartContractTransaction.ts +++ b/src/utils/transactions/dataDecoders/smartContractTransaction.ts @@ -1,7 +1,7 @@ import { Address, TransactionPayload } from '@multiversx/sdk-core'; import { ESDTTransferTypesEnum, TypesOfSmartContractCallsEnum } from 'types'; import { isStringBase64 } from 'utils/decoders/base64Utils'; -import { addressIsValid } from './addressIsValid'; +import { addressIsValid } from '../../validation'; export function isContract( receiver: string, diff --git a/src/utils/transactions/dataDecoders/useDataDecode.ts b/src/utils/transactions/dataDecoders/useDataDecode.ts deleted file mode 100644 index 74725f3..0000000 --- a/src/utils/transactions/dataDecoders/useDataDecode.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { useEffect, useState, SetStateAction, Dispatch } from 'react'; - -import { DecodeMethodEnum } from 'types'; -import { decodeTransactionData } from './decodeTransactionData'; - -export interface DataDecodeType { - value: string; - className?: string; - initialDecodeMethod?: DecodeMethodEnum | string; - setDecodeMethod?: Dispatch>; - identifier?: string; -} - -const decodeOptions = [ - { - label: 'Raw', - value: DecodeMethodEnum.raw - }, - { - label: 'Text', - value: DecodeMethodEnum.text - }, - { - label: 'Decimal', - value: DecodeMethodEnum.decimal - }, - { - label: 'Smart', - value: DecodeMethodEnum.smart - } -]; - -export function useDataDecode({ - value, - initialDecodeMethod, - setDecodeMethod, - identifier -}: DataDecodeType) { - const [activeKey, setActiveKey] = useState( - initialDecodeMethod && - Object.values(DecodeMethodEnum).includes(initialDecodeMethod) - ? initialDecodeMethod - : DecodeMethodEnum.raw - ); - - const { displayValue, validationWarnings } = decodeTransactionData({ - input: value, - decodeMethod: activeKey as DecodeMethodEnum, - identifier - }); - - useEffect(() => { - if (setDecodeMethod) { - setDecodeMethod(activeKey); - } - }, [activeKey]); - - return { - displayValue, - validationWarnings, - setActiveKey, - decodeOptions - }; -} diff --git a/src/utils/transactions/dataDecoders/useDataDecodeMethod.ts b/src/utils/transactions/dataDecoders/useDataDecodeMethod.ts deleted file mode 100644 index 99674c3..0000000 --- a/src/utils/transactions/dataDecoders/useDataDecodeMethod.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { useEffect, useState } from 'react'; -import { DecodeMethodEnum } from 'types'; -import { getWindowLocation } from 'utils/window/getWindowLocation'; - -export const useDataDecodeMethod = () => { - const { hash, pathname } = getWindowLocation(); - const hashDecodeMethod = hash.replace('#', ''); - const initialDecodeMethod = - hashDecodeMethod && - Object.values(DecodeMethodEnum).includes(hashDecodeMethod) - ? hashDecodeMethod - : DecodeMethodEnum.raw; - const [decodeMethod, setDecodeMethod] = useState(hashDecodeMethod); - - useEffect(() => { - if (decodeMethod && decodeMethod !== DecodeMethodEnum.raw) { - window?.history.replaceState( - {}, - document?.title, - `${pathname}#${decodeMethod}` - ); - } - }, [decodeMethod, pathname]); - - return { initialDecodeMethod, decodeMethod, setDecodeMethod }; -}; diff --git a/src/utils/transactions/operations/calculateFeeLimit.ts b/src/utils/transactions/operations/calculateFeeLimit.ts deleted file mode 100755 index a657540..0000000 --- a/src/utils/transactions/operations/calculateFeeLimit.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { - Transaction, - TransactionPayload, - TransactionVersion, - Address, - TokenPayment -} from '@multiversx/sdk-core'; -import BigNumber from 'bignumber.js'; -import { - EXTRA_GAS_LIMIT_GUARDED_TX, - GAS_LIMIT, - GAS_PRICE, - ZERO -} from 'constants/index'; -import { stringIsFloat, stringIsInteger } from 'utils/validation'; -import { isGuardianTx } from '../validation'; - -export interface CalculateFeeLimitParamsType { - gasLimit: string; - gasPrice: string; - data: string; - gasPerDataByte: string; - gasPriceModifier: string; - chainId: string; - minGasLimit?: string; - defaultGasPrice?: string; -} - -const placeholderData = { - from: 'erd12dnfhej64s6c56ka369gkyj3hwv5ms0y5rxgsk2k7hkd2vuk7rvqxkalsa', - to: 'erd12dnfhej64s6c56ka369gkyj3hwv5ms0y5rxgsk2k7hkd2vuk7rvqxkalsa' -}; - -export function calculateFeeLimit({ - minGasLimit = String(GAS_LIMIT), - gasLimit, - gasPrice, - data: inputData, - gasPerDataByte, - gasPriceModifier, - defaultGasPrice = String(GAS_PRICE), - chainId -}: CalculateFeeLimitParamsType) { - const data = inputData || ''; - const validGasLimit = stringIsInteger(gasLimit) ? gasLimit : minGasLimit; - - // We need to add extra gas fee for guardian transactions - const extraGasLimit = isGuardianTx({ data }) ? EXTRA_GAS_LIMIT_GUARDED_TX : 0; - const usedGasLimit = new BigNumber(validGasLimit) - .plus(extraGasLimit) - .toNumber(); - - const validGasPrice = stringIsFloat(gasPrice) ? gasPrice : defaultGasPrice; - const transaction = new Transaction({ - nonce: 0, - value: TokenPayment.egldFromAmount('0'), - receiver: new Address(placeholderData.to), - sender: new Address(placeholderData.to), - gasPrice: parseInt(validGasPrice), - gasLimit: usedGasLimit, - data: new TransactionPayload(data.trim()), - chainID: chainId, - version: new TransactionVersion(1) - }); - - try { - const bNfee = transaction.computeFee({ - GasPerDataByte: parseInt(gasPerDataByte), - MinGasLimit: parseInt(minGasLimit), - GasPriceModifier: parseFloat(gasPriceModifier), - ChainID: chainId - }); - return bNfee.toString(10); - } catch (err) { - console.error(err); - return ZERO; - } -} diff --git a/src/utils/transactions/operations/calculateGasLimit.ts b/src/utils/transactions/operations/calculateGasLimit.ts deleted file mode 100644 index af31aca..0000000 --- a/src/utils/transactions/operations/calculateGasLimit.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - EXTRA_GAS_LIMIT_GUARDED_TX, - GAS_LIMIT, - GAS_PER_DATA_BYTE -} from 'constants/index'; -import BigNumber from 'bignumber.js'; - -export function calculateGasLimit({ - data, - isGuarded -}: { - data?: string; - isGuarded?: boolean; -}) { - const guardedAccountGasLimit = isGuarded ? EXTRA_GAS_LIMIT_GUARDED_TX : 0; - const bNconfigGasLimit = new BigNumber(GAS_LIMIT).plus( - guardedAccountGasLimit - ); - const bNgasPerDataByte = new BigNumber(GAS_PER_DATA_BYTE); - const bNgasValue = data - ? bNgasPerDataByte.times(Buffer.from(data).length) - : 0; - const bNgasLimit = bNconfigGasLimit.plus(bNgasValue); - const gasLimit = bNgasLimit.toString(10); - return gasLimit; -} diff --git a/src/utils/transactions/operations/clearTransactions.ts b/src/utils/transactions/operations/clearTransactions.ts deleted file mode 100644 index 4a6d4f5..0000000 --- a/src/utils/transactions/operations/clearTransactions.ts +++ /dev/null @@ -1,33 +0,0 @@ -export function removeTransactionsToSign(sessionId: string) { - store.dispatch(clearSignedTransaction(sessionId)); -} -export function removeSignedTransaction(sessionId: string) { - store.dispatch(clearSignedTransaction(sessionId)); - - const account = accountSelector(store.getState()); - store.dispatch( - clearBatchTransactions({ - batchId: buildBatchId({ - sessionId, - address: account?.address ?? '' - }) - }) - ); -} - -export function deleteTransactionToast(sessionId: string) { - store.dispatch(removeTransactionToast(sessionId)); - removeSignedTransaction(sessionId); -} - -export function removeAllSignedTransactions() { - store.dispatch(clearAllSignedTransactions()); -} - -export function removeAllTransactionsToSign() { - store.dispatch(clearAllTransactionsToSign()); -} - -export function removeBatchTransactions(batchId: string) { - store.dispatch(clearBatchTransactions({ batchId })); -} diff --git a/src/utils/transactions/operations/computeTransactionNonce.ts b/src/utils/transactions/operations/computeTransactionNonce.ts deleted file mode 100644 index 5dc5f29..0000000 --- a/src/utils/transactions/operations/computeTransactionNonce.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Returns the higher nonce between the latest nonce of the account and the transaction nonce - * Used to set the correct nonce for a transaction in the batch - */ -export function computeTransactionNonce({ - accountNonce, - transactionNonce -}: { - accountNonce: number; - transactionNonce?: number; -}) { - if (!transactionNonce) { - return accountNonce; - } - - return transactionNonce > accountNonce ? transactionNonce : accountNonce; -} diff --git a/src/utils/transactions/operations/index.ts b/src/utils/transactions/operations/index.ts deleted file mode 100644 index 8459e22..0000000 --- a/src/utils/transactions/operations/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from './calculateFeeLimit'; -export * from './calculateGasLimit'; -export * from './clearTransactions'; -export * from './computeTransactionNonce'; -export * from './newTransaction'; -export * from './sendTransactions'; -export * from './transformTransactionsForSigning'; -export * from './transformTransactionsToSign'; -export * from './updateSignedTransactions'; diff --git a/src/utils/transactions/operations/newTransaction.ts b/src/utils/transactions/operations/newTransaction.ts deleted file mode 100644 index 565e380..0000000 --- a/src/utils/transactions/operations/newTransaction.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - Address, - Transaction, - TransactionOptions, - TransactionVersion -} from '@multiversx/sdk-core'; -import { GAS_LIMIT, GAS_PRICE, VERSION } from 'constants'; -import { RawTransactionType } from 'types'; -import { isGuardianTx } from '../validation'; -import { getDataPayloadForTransaction } from '../dataDecoders'; - -export function newTransaction(rawTransaction: RawTransactionType) { - const rawTx = { ...rawTransaction }; - - // TODO: Remove when the protocol supports usernames for guardian transactions - if (isGuardianTx({ data: rawTx.data, onlySetGuardian: true })) { - delete rawTx.senderUsername; - delete rawTx.receiverUsername; - } - - const transaction = new Transaction({ - value: rawTx.value.valueOf(), - data: getDataPayloadForTransaction(rawTx.data), - nonce: rawTx.nonce.valueOf(), - receiver: new Address(rawTx.receiver), - ...(rawTx.receiverUsername - ? { receiverUsername: rawTx.receiverUsername } - : {}), - sender: new Address(rawTx.sender), - ...(rawTx.senderUsername ? { senderUsername: rawTx.senderUsername } : {}), - gasLimit: rawTx.gasLimit.valueOf() ?? GAS_LIMIT, - gasPrice: rawTx.gasPrice.valueOf() ?? GAS_PRICE, - chainID: rawTx.chainID.valueOf(), - version: new TransactionVersion(rawTx.version ?? VERSION), - ...(rawTx.options - ? { options: new TransactionOptions(rawTx.options) } - : {}), - ...(rawTx.guardian ? { guardian: new Address(rawTx.guardian) } : {}) - }); - - if (rawTx.guardianSignature) { - transaction.applyGuardianSignature( - Buffer.from(rawTx.guardianSignature, 'hex') - ); - } - - if (rawTx.signature) { - transaction.applySignature(Buffer.from(rawTx.signature, 'hex')); - } - - return transaction; -} diff --git a/src/utils/transactions/operations/sendTransactions.ts b/src/utils/transactions/operations/sendTransactions.ts deleted file mode 100644 index 00ccf69..0000000 --- a/src/utils/transactions/operations/sendTransactions.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Transaction } from '@multiversx/sdk-core/out'; -import { - SendTransactionReturnType, - SendTransactionsParamsType, - SimpleTransactionType -} from 'types'; -import { getDefaultCallbackUrl } from 'utils/window'; -import { signTransactions } from './signTransactions'; -import { transformTransactionsToSign } from './transformTransactionsToSign'; - -export async function sendTransactions({ - transactions, - transactionsDisplayInfo, - redirectAfterSign = true, - callbackRoute = getDefaultCallbackUrl(), - signWithoutSending = false, - completedTransactionsDelay, - sessionInformation, - skipGuardian, - minGasLimit, - hasConsentPopup -}: SendTransactionsParamsType): Promise { - try { - const transactionsPayload = Array.isArray(transactions) - ? transactions - : [transactions]; - - const transactionsToSign = await transformTransactionsToSign({ - transactions: transactionsPayload as SimpleTransactionType[], - minGasLimit - }); - - return signTransactions({ - transactions: transactionsToSign as Transaction[], - minGasLimit, - callbackRoute, - transactionsDisplayInfo, - customTransactionInformation: { - redirectAfterSign, - completedTransactionsDelay, - sessionInformation, - skipGuardian, - signWithoutSending, - hasConsentPopup - } - }); - } catch (err) { - console.error('error signing transaction', err as any); - return { error: err as any, sessionId: null }; - } -} diff --git a/src/utils/transactions/operations/tests/calculateFeeLimit.test.ts b/src/utils/transactions/operations/tests/calculateFeeLimit.test.ts deleted file mode 100644 index 0523587..0000000 --- a/src/utils/transactions/operations/tests/calculateFeeLimit.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { GAS_PER_DATA_BYTE, GAS_PRICE_MODIFIER } from 'constants'; -import { calculateFeeLimit } from '../calculateFeeLimit'; - -describe('calculateFeeLimit tests', () => { - it('computes correct fee', () => { - const feeLimit = calculateFeeLimit({ - gasLimit: '62000', - gasPrice: '1000000000', - data: 'testdata', - chainId: 'T', - gasPerDataByte: String(GAS_PER_DATA_BYTE), - gasPriceModifier: String(GAS_PRICE_MODIFIER) - }); - expect(feeLimit).toBe('62000000000000'); - }); - - it('computes correct fee', () => { - const feeLimit = calculateFeeLimit({ - gasLimit: '11100000', - gasPrice: '1000000000', - data: 'bid@0d59@43525a502d333663366162@25', - gasPerDataByte: String(GAS_PER_DATA_BYTE), - gasPriceModifier: String(GAS_PRICE_MODIFIER), - defaultGasPrice: '1000000000', - chainId: 'T' - }); - - expect(feeLimit).toBe('210990000000000'); - }); - - it('computes correct fee for SetGuardian tx', () => { - const feeLimit = calculateFeeLimit({ - gasLimit: '', - gasPrice: (1_000_000).toString(), - data: 'SetGuardian@qwerty@12345', - chainId: 'T', - gasPerDataByte: '1', - gasPriceModifier: '1' - }); - - expect(feeLimit).toBe((100_000_000_000).toString()); // (minGasLimit + extra guardian gas) * gasPrice - }); - - it('computes correct fee for GuardAccount tx', () => { - const feeLimit = calculateFeeLimit({ - gasLimit: '', - gasPrice: (1_000_000).toString(), - data: 'GuardAccount@qwerty@12345', - chainId: 'T', - gasPerDataByte: '1', - gasPriceModifier: '1' - }); - - expect(feeLimit).toBe((100_000_000_000).toString()); // (minGasLimit + extra guardian gas) * gasPrice - }); - - it('computes correct fee for UnGuardAccount tx', () => { - const feeLimit = calculateFeeLimit({ - gasLimit: '', - gasPrice: (1_000_000).toString(), - data: 'UnGuardAccount@qwerty@12345', - chainId: 'T', - gasPerDataByte: '1', - gasPriceModifier: '1' - }); - - expect(feeLimit).toBe((100_000_000_000).toString()); // (minGasLimit + extra guardian gas) * gasPrice - }); - - it('computes correct fee for UnGuardAccount tx and gas limit specified', () => { - const feeLimit = calculateFeeLimit({ - gasLimit: (1_000_000).toString(), - gasPrice: (1_000_000).toString(), - data: 'UnGuardAccount@qwerty@12345', - chainId: 'T', - gasPerDataByte: '1', - gasPriceModifier: '1' - }); - - expect(feeLimit).toBe((1_050_000_000_000).toString()); // (gasLimit + extra guardian gas) * gasPrice - }); - - it('computes correct fee for UnGuardAccount tx and min gas limit specified', () => { - const feeLimit = calculateFeeLimit({ - gasLimit: '', - minGasLimit: (1_000_000).toString(), - gasPrice: (1_000_000).toString(), - data: 'UnGuardAccount@qwerty@12345', - chainId: 'T', - gasPerDataByte: '1', - gasPriceModifier: '1' - }); - - expect(feeLimit).toBe((1_050_000_000_000).toString()); // (minGasLimit + extra guardian gas) * gasPrice - }); -}); diff --git a/src/utils/transactions/operations/transformTransactionsForSigning.ts b/src/utils/transactions/operations/transformTransactionsForSigning.ts deleted file mode 100644 index 0b72126..0000000 --- a/src/utils/transactions/operations/transformTransactionsForSigning.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Address, Transaction } from '@multiversx/sdk-core'; - -import { GAS_PRICE } from 'constants/index'; -import { SendSimpleTransactionParamsType } from 'types'; - -import { getAccount } from 'utils/account/getAccount'; -import { computeTransactionNonce } from './computeTransactionNonce'; -import { calculateGasLimit } from './calculateGasLimit'; -import { newTransaction } from './newTransaction'; -import { addressSelector, chainIdSelector } from 'store/selectors'; -import { getState } from 'store/store'; -import { getLatestNonce } from 'core/methods/account/getLatestNonce'; - -enum ErrorCodesEnum { - 'invalidReceiver' = 'Invalid Receiver address', - 'unknownError' = 'Unknown Error. Please check the transactions and try again' -} - -export async function transformTransactionsForSigning({ - transactions -}: SendSimpleTransactionParamsType): Promise { - const state = getState(); - const address = addressSelector(state); - const account = await getAccount(address); - const accountNonce = getLatestNonce(account); - return transactions.map((tx) => { - const { - value, - receiver, - data = '', - chainID, - version = 1, - options, - gasPrice = GAS_PRICE, - gasLimit = calculateGasLimit({ - data: tx.data, - isGuarded: account?.isGuarded - }), - guardian, - guardianSignature, - nonce: transactionNonce = 0 - } = tx; - let validatedReceiver = receiver; - - try { - const addr = new Address(receiver); - validatedReceiver = addr.hex(); - } catch (err) { - throw ErrorCodesEnum.invalidReceiver; - } - - const computedNonce = computeTransactionNonce({ - accountNonce, - transactionNonce - }); - - const storeChainId = chainIdSelector(state).valueOf().toString(); - const transactionsChainId = chainID || storeChainId; - - return newTransaction({ - value, - receiver: validatedReceiver, - data, - gasPrice, - gasLimit: Number(gasLimit), - nonce: Number(computedNonce.valueOf().toString()), - sender: new Address(address).hex(), - chainID: transactionsChainId, - version, - options, - guardian, - guardianSignature - }); - }); -} diff --git a/src/utils/transactions/operations/transformTransactionsToSign.ts b/src/utils/transactions/operations/transformTransactionsToSign.ts deleted file mode 100644 index 296d661..0000000 --- a/src/utils/transactions/operations/transformTransactionsToSign.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Transaction } from '@multiversx/sdk-core'; -import { SimpleTransactionType } from 'types'; -import { transformAndSignTransactions } from '../transformAndSignTransactions'; - -export const transformTransactionsToSign = async ({ - transactions, - minGasLimit -}: { - transactions: (SimpleTransactionType | Transaction)[]; - minGasLimit?: number; -}) => { - const areComplexTransactions = transactions.every( - (tx) => Object.getPrototypeOf(tx).toPlainObject != null - ); - - let transactionsToSign = transactions; - - if (!areComplexTransactions) { - transactionsToSign = await transformAndSignTransactions({ - transactions: transactions as SimpleTransactionType[], - minGasLimit - }); - } - - return transactionsToSign; -}; diff --git a/src/utils/transactions/operations/updateSignedTransactions.ts b/src/utils/transactions/operations/updateSignedTransactions.ts deleted file mode 100644 index 3ea1d43..0000000 --- a/src/utils/transactions/operations/updateSignedTransactions.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - updateSignedTransactions, - moveTransactionsToSignedState, - UpdateSignedTransactionsPayloadType, - updateSignedTransactionStatus, - UpdateSignedTransactionStatusPayloadType, - MoveTransactionsToSignedStatePayloadType, - setTransactionsDisplayInfo, - SetTransactionsInfoPayloadType, - updateSignedTransactionsCustomTransactionInformation -} from 'reduxStore/slices'; -import { store } from 'reduxStore/store'; -import { CustomTransactionInformation } from 'types'; - -export function setTransactionsToSignedState( - payload: MoveTransactionsToSignedStatePayloadType -) { - store.dispatch(moveTransactionsToSignedState(payload)); -} - -export function updateSignedTransactionsState( - payload: UpdateSignedTransactionsPayloadType -) { - store.dispatch(updateSignedTransactions(payload)); -} - -export function updateSignedTransactionStatusState( - payload: UpdateSignedTransactionStatusPayloadType -) { - store.dispatch(updateSignedTransactionStatus(payload)); -} - -export function setTransactionsDisplayInfoState( - payload: SetTransactionsInfoPayloadType -) { - store.dispatch(setTransactionsDisplayInfo(payload)); -} - -export function updateSignedTransactionsCustomTransactionInformationState(payload: { - sessionId: string; - customTransactionInformationOverrides: Partial; -}) { - store.dispatch(updateSignedTransactionsCustomTransactionInformation(payload)); -} diff --git a/src/utils/transactions/url/buildCallbackUrl.ts b/src/utils/transactions/url/buildCallbackUrl.ts deleted file mode 100644 index e90a5fe..0000000 --- a/src/utils/transactions/url/buildCallbackUrl.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { SDK_DAPP_VERSION } from 'constants'; - -function buildUrlParams( - search: string, - urlParams: { - [key: string]: string; - } -) { - const urlSearchParams = new URLSearchParams(search); - const params = Object.fromEntries(urlSearchParams as any); - - const nextUrlParams = new URLSearchParams({ - ...params, - ...urlParams - }).toString(); - - return { nextUrlParams, params }; -} - -const version = '__sdkDappVersion'; // will be replaced at build time - -export interface ReplyUrlType { - callbackUrl: string; - urlParams?: { [key: string]: string }; -} - -export function buildCallbackUrl({ - callbackUrl, - urlParams = {} -}: ReplyUrlType) { - let url = callbackUrl; - - if (Object.entries(urlParams).length > 0) { - try { - const { search, origin, pathname, hash } = new URL(callbackUrl); - const urlParamsWithVersion = { - ...urlParams, - [SDK_DAPP_VERSION]: version - }; - const { nextUrlParams } = buildUrlParams(search, urlParamsWithVersion); - url = `${origin}${pathname}?${nextUrlParams}${hash}`; - } catch (err) { - console.error('Unable to construct URL from: ', callbackUrl, err); - return url; - } - } - - return url; -} diff --git a/src/utils/transactions/url/index.ts b/src/utils/transactions/url/index.ts deleted file mode 100644 index 3fda33b..0000000 --- a/src/utils/transactions/url/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './buildCallbackUrl'; -export * from './removeTransactionParamsFromUrl'; diff --git a/src/utils/transactions/url/removeTransactionParamsFromUrl.ts b/src/utils/transactions/url/removeTransactionParamsFromUrl.ts deleted file mode 100644 index 97732ee..0000000 --- a/src/utils/transactions/url/removeTransactionParamsFromUrl.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { WALLET_PROVIDER_CALLBACK_PARAM } from '@multiversx/sdk-web-wallet-provider'; -import { SDK_DAPP_VERSION, WALLET_SIGN_SESSION } from 'constants/index'; -import { removeSearchParamsFromUrl } from '../../window'; - -interface RemoveTransactionParamsFromUrlParamsType { - transaction: any; - search?: string; -} - -export function removeTransactionParamsFromUrl({ - transaction, - search -}: RemoveTransactionParamsFromUrlParamsType) { - return removeSearchParamsFromUrl({ - removeParams: [ - ...Object.keys(transaction), - WALLET_PROVIDER_CALLBACK_PARAM, - WALLET_SIGN_SESSION, - SDK_DAPP_VERSION - ], - search - }); -} diff --git a/src/utils/transactions/url/tests/builtCallbackUrl.test.ts b/src/utils/transactions/url/tests/builtCallbackUrl.test.ts deleted file mode 100644 index 8a5c757..0000000 --- a/src/utils/transactions/url/tests/builtCallbackUrl.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { buildCallbackUrl } from '../buildCallbackUrl'; - -describe('builtCallbackUrl tests', () => { - const url = 'https://wallet.multiversx.com'; - - test('returns callbackUrl unmodified if urlParams is empty', () => { - expect(buildCallbackUrl({ callbackUrl: url })).toBe(url); - }); - - test('adds urlParams', () => { - expect( - buildCallbackUrl({ - callbackUrl: url, - urlParams: { status: 'success' } - }) - ).toBe( - 'https://wallet.multiversx.com/?status=success&sdk-dapp-version=__sdkDappVersion' - ); - }); - - test('adds urlParams and keeps existing hash', () => { - expect( - buildCallbackUrl({ - callbackUrl: url + '#test', - urlParams: { status: 'success' } - }) - ).toBe( - 'https://wallet.multiversx.com/?status=success&sdk-dapp-version=__sdkDappVersion#test' - ); - }); - - test('keeps existing urlParams', () => { - expect( - buildCallbackUrl({ - callbackUrl: url + '?page=1', - urlParams: { status: 'success' } - }) - ).toBe( - 'https://wallet.multiversx.com/?page=1&status=success&sdk-dapp-version=__sdkDappVersion' - ); - }); - - test('keeps existing hash', () => { - expect( - buildCallbackUrl({ - callbackUrl: url + '?page=1#logs', - urlParams: { status: 'success' } - }) - ).toBe( - 'https://wallet.multiversx.com/?page=1&status=success&sdk-dapp-version=__sdkDappVersion#logs' - ); - }); - - test('throws error if callbackUrl is invalid and urlParams are defined', () => { - expect( - buildCallbackUrl({ - callbackUrl: '', - urlParams: { status: 'success' } - }) - ).toBe(''); - }); -}); diff --git a/src/utils/transactions/url/tests/removeTransactionParamsFromUrl.test.ts b/src/utils/transactions/url/tests/removeTransactionParamsFromUrl.test.ts deleted file mode 100644 index 153554b..0000000 --- a/src/utils/transactions/url/tests/removeTransactionParamsFromUrl.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { removeTransactionParamsFromUrl } from '../removeTransactionParamsFromUrl'; - -describe('removeTransactionParamsFromUrl tests', () => { - test('removes all data from URL', () => { - const search = - '?nonce=1&value=123&receiver=erd123&sender=erd456&gasLimit=60000&gasPrice=60000&chainID=D'; - const transaction = { - value: '123', - data: 'test', - nonce: '1', - receiver: 'erd123', - sender: 'erd456', - gasLimit: '60000', - gasPrice: '60000', - chainID: 'D' - }; - - const result = removeTransactionParamsFromUrl({ transaction, search }); - - expect(result).toStrictEqual({}); - }); - test('removes only tx data from the URL ', () => { - const search = - '?testParam=123&searchParam=asd&nonce=1&value=123&receiver=erd123&sender=erd456&gasLimit=60000&gasPrice=60000&chainID=D'; - const transaction = { - value: '123', - data: 'test', - nonce: '1', - receiver: 'erd123', - sender: 'erd456', - gasLimit: '60000', - gasPrice: '60000', - chainID: 'D' - }; - - const result = removeTransactionParamsFromUrl({ transaction, search }); - - expect(result).toStrictEqual({ - testParam: '123', - searchParam: 'asd' - }); - }); -}); From f2cb8f9bec618c64092a5c28ed1bcd2db35df763 Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Wed, 4 Dec 2024 15:05:34 +0200 Subject: [PATCH 06/23] fix --- src/utils/transactions/parsers/getAreTransactionsOnSameShard.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/transactions/parsers/getAreTransactionsOnSameShard.ts b/src/utils/transactions/parsers/getAreTransactionsOnSameShard.ts index 24de389..f50a617 100644 --- a/src/utils/transactions/parsers/getAreTransactionsOnSameShard.ts +++ b/src/utils/transactions/parsers/getAreTransactionsOnSameShard.ts @@ -1,5 +1,6 @@ import { SignedTransactionType } from '../../../types'; import { getAddressFromDataField } from '../dataDecoders'; +import { isCrossShardTransaction } from '../validation'; export function getAreTransactionsOnSameShard( transactions?: SignedTransactionType[], From 51bdb7ba642d1128cbee2216e70f87a5d81cc0d4 Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Wed, 4 Dec 2024 15:09:08 +0200 Subject: [PATCH 07/23] Updated --- src/types/transactions.types.ts | 588 ++------------------------------ 1 file changed, 20 insertions(+), 568 deletions(-) diff --git a/src/types/transactions.types.ts b/src/types/transactions.types.ts index 58bf786..e2ae663 100644 --- a/src/types/transactions.types.ts +++ b/src/types/transactions.types.ts @@ -1,513 +1,36 @@ -import { Address, Transaction } from '@multiversx/sdk-core'; -import { IPlainTransactionObject } from '@multiversx/sdk-core/out/interface'; - -import { AssetType, ScamInfoType } from './account.types'; -import { EsdtEnumType, NftEnumType } from './tokens.types'; +import { IPlainTransactionObject } from '@multiversx/sdk-core/out'; import { TransactionBatchStatusesEnum, - TransactionServerStatusesEnum, - TransactionTypesEnum -} from './enums.types'; + TransactionServerStatusesEnum +} from 'types/enums.types'; -export interface ScResultType { - callType: string; - data?: string; - gasLimit: number; - gasPrice: number; +export interface SignedTransactionType extends IPlainTransactionObject { hash: string; - nonce: number; - originalTxHash: string; - prevTxHash: string; - receiver: string; - returnMessage?: string; - sender: string; - timestamp: number; - value: string; -} - -export interface TransactionTokensType { - esdts: string[]; - nfts: string[]; -} - -export enum TransactionActionsEnum { - addLiquidity = 'addLiquidity', - addLiquidityProxy = 'addLiquidityProxy', - claimDualYield = 'claimDualYield', - claimDualYieldProxy = 'claimDualYieldProxy', - claimLockedAssets = 'claimLockedAssets', - claimRewards = 'claimRewards', - claimRewardsProxy = 'claimRewardsProxy', - compoundRewards = 'compoundRewards', - compoundRewardsProxy = 'compoundRewardsProxy', - delegate = 'delegate', - enterFarm = 'enterFarm', - enterFarmAndLockRewards = 'enterFarmAndLockRewards', - enterFarmAndLockRewardsProxy = 'enterFarmAndLockRewardsProxy', - enterFarmProxy = 'enterFarmProxy', - exitFarm = 'exitFarm', - exitFarmProxy = 'exitFarmProxy', - lockTokens = 'lockTokens', - mergeLockedAssetTokens = 'mergeLockedAssetTokens', - migrateOldTokens = 'migrateOldTokens', - ping = 'ping', - reDelegateRewards = 'reDelegateRewards', - removeLiquidity = 'removeLiquidity', - removeLiquidityProxy = 'removeLiquidityProxy', - stake = 'stake', - stakeClaimRewards = 'claimRewards', - stakeFarm = 'stakeFarm', - stakeFarmProxy = 'stakeFarmProxy', - stakeFarmTokens = 'stakeFarmTokens', - stakeFarmTokensProxy = 'stakeFarmTokensProxy', - swap = 'swap', - swapTokensFixedInput = 'swapTokensFixedInput', - swapTokensFixedOutput = 'swapTokensFixedOutput', - transfer = 'transfer', - unBond = 'unBond', - unDelegate = 'unDelegate', - unStake = 'unStake', - unbondFarm = 'unbondFarm', - unlockAssets = 'unlockAssets', - unstakeFarm = 'unstakeFarm', - unstakeFarmProxy = 'unstakeFarmProxy', - unstakeFarmTokens = 'unstakeFarmTokens', - unstakeFarmTokensProxy = 'unstakeFarmTokensProxy', - unwrapEgld = 'unwrapEgld', - withdraw = 'withdraw', - wrapEgld = 'wrapEgld' -} - -export enum TransactionActionCategoryEnum { - esdtNft = 'esdtNft', - mex = 'mex', - stake = 'stake', - scCall = 'scCall' -} - -export interface TokenArgumentType { - type: NftEnumType | EsdtEnumType; - name: string; - ticker: string; - collection?: string; - identifier?: string; - token?: string; - decimals: number; - value: string; - providerName?: string; - providerAvatar?: string; - svgUrl?: string; - valueUSD?: string; -} - -export interface TransactionActionType { - category: string; - name: TransactionActionsEnum; - description?: string; - arguments?: { [key: string]: any }; -} - -export interface UnwrapperType { - token?: TokenArgumentType[]; - tokenNoValue?: TokenArgumentType[]; - tokenNoLink?: TokenArgumentType[]; - address?: string; - egldValue?: string; - value?: string; - providerName?: string; - providerAvatar?: string; -} - -export enum TransactionOperationActionTypeEnum { - none = 'none', - transfer = 'transfer', - burn = 'burn', - addQuantity = 'addQuantity', - create = 'create', - multiTransfer = 'multiTransfer', - localMint = 'localMint', - localBurn = 'localBurn', - wipe = 'wipe', - freeze = 'freeze', - writeLog = 'writeLog', - signalError = 'signalError', - - // to be deprecated ? - ESDTLocalMint = 'ESDTLocalMint', - ESDTLocalBurn = 'ESDTLocalBurn' -} - -export enum VisibleTransactionOperationType { - nft = 'nft', - esdt = 'esdt', - egld = 'egld' -} -export enum HiddenTransactionOperationType { - none = 'none', - error = 'error', - log = 'log' -} - -export interface OperationType { - id?: string; - action: TransactionOperationActionTypeEnum; - type: VisibleTransactionOperationType | HiddenTransactionOperationType; - esdtType?: NftEnumType | EsdtEnumType; - collection?: string; - name?: string; - identifier?: string; - sender: string; - ticker?: string; - receiver: string; - value: string; - decimals?: number; - data?: string; - message?: string; - svgUrl?: string; - senderAssets?: AssetType; - receiverAssets?: AssetType; - valueUSD?: string; + status: TransactionServerStatusesEnum; + inTransit?: boolean; } -export interface LogType { +export type PendingTransactionsType = { hash: string; - callType: string; - gasLimit: number; - gasPrice: number; - nonce: number; - prevTxHash: string; - receiver?: string; - sender: string; - value: string; - data?: string; - originalTxHash: string; - returnMessage?: string; - logs?: any; -} - -export interface EventType { - address: string; - identifier: string; - topics: string[]; - order: number; - data?: string; - additionalData?: string[]; -} - -export interface ResultLogType { - id: string; - address: string; - events: EventType[]; -} + previousStatus: string; +}[]; -export interface ResultType { +export type GetTransactionsByHashesReturnType = { hash: string; - callType: string; - gasLimit: number; - gasPrice: number; - nonce: number; - prevTxHash: string; - receiver?: string; - sender: string; - value: string; - data?: string; - originalTxHash: string; - returnMessage?: string; - logs?: ResultLogType; - senderAssets?: AssetType; - receiverAssets?: AssetType; - miniBlockHash?: string; - function?: string; - timestamp?: number; -} - -export interface ReceiptType { - value: string; - sender: string; - data: string; -} - -export interface ServerTransactionType { - fee?: string; - data: string; - gasLimit: number; - gasPrice: number; - gasUsed: number; - txHash: string; - miniBlockHash: string; - nonce: number; - receiver: string; - receiverShard: number; - round: number; - sender: string; - senderShard: number; - signature: string; - status: string; + invalidTransaction: boolean; + status: TransactionServerStatusesEnum | TransactionBatchStatusesEnum; inTransit?: boolean; - timestamp: number; - value: string; - price: number; - results?: ResultType[]; - operations?: OperationType[]; - action?: TransactionActionType; - logs?: { - id: string; - address: string; - events: EventType[]; - }; - scamInfo?: ScamInfoType; - pendingResults?: boolean; - receipt?: ReceiptType; - senderAssets?: AssetType; - receiverAssets?: AssetType; - type?: TransferTypeEnum; - originalTxHash?: string; - isNew?: boolean; // UI flag - tokenValue?: string; - tokenIdentifier?: string; - function?: string; -} - -export enum TransferTypeEnum { - Transaction = 'Transaction', - SmartContractResult = 'SmartContractResult' -} - -//#endregion - -//#region interpreted trasactions - -export enum TransactionDirectionEnum { - SELF = 'Self', - INTERNAL = 'Internal', - IN = 'In', - OUT = 'Out' -} - -export interface InterpretedTransactionType extends ServerTransactionType { - transactionDetails: { - direction?: TransactionDirectionEnum; - method: string; - transactionTokens: TokenArgumentType[]; - isContract?: boolean; - }; - links: { - senderLink?: string; - receiverLink?: string; - senderShardLink?: string; - receiverShardLink?: string; - transactionLink?: string; - }; -} - -export interface DecodeForDisplayParamsType { - input: string; - decodeMethod: DecodeMethodEnum; - identifier?: string; -} - -export interface DecodedDisplayType { - displayValue: string; - validationWarnings: string[]; -} - -export enum DecodeMethodEnum { - raw = 'raw', - text = 'text', - decimal = 'decimal', - smart = 'smart' -} - -//#endregion - -export enum BatchTransactionStatus { - pending = 'pending', - success = 'success', - invalid = 'invalid', - dropped = 'dropped', - fail = 'fail' -} - -export interface BatchTransactionsRequestType { - id: string; - transactions: SignedTransactionType[][]; -} - -export interface BatchTransactionsResponseType { - id: string; - status: BatchTransactionStatus; - transactions: SignedTransactionType[][]; - error?: string; - message?: string; - statusCode?: string; -} - -export type BatchTransactionsWSResponseType = { - batchId: string; - txHashes: string[]; -}; - -export interface TransactionsToSignType { - transactions: IPlainTransactionObject[]; - callbackRoute?: string; - sessionId: string; - customTransactionInformation: CustomTransactionInformation; -} - -export interface SendSimpleTransactionParamsType { - transactions: SimpleTransactionType[]; - minGasLimit?: number; -} - -export interface SendTransactionsParamsType { - transactions: - | Transaction - | SimpleTransactionType - | (Transaction | SimpleTransactionType)[]; - redirectAfterSign?: boolean; - signWithoutSending: boolean; - skipGuardian?: boolean; - completedTransactionsDelay?: number; - callbackRoute?: string; - transactionsDisplayInfo: TransactionsDisplayInfoType; - minGasLimit?: number; - sessionInformation?: any; - hasConsentPopup?: boolean; -} - -export interface SendBatchTransactionsParamsType { - transactions: (Transaction | SimpleTransactionType)[][]; - redirectAfterSign?: boolean; - signWithoutSending?: boolean; - skipGuardian?: boolean; - /** - * For Cross-Window provider in Safari browser, performing async calls before signing transactions needs a consent popup in order to open a new tab. - */ - hasConsentPopup?: boolean; - completedTransactionsDelay?: number; - callbackRoute?: string; - transactionsDisplayInfo: TransactionsDisplayInfoType; - minGasLimit?: number; - sessionInformation?: any; -} - -export interface SignTransactionsParamsType { - transactions: Transaction[] | Transaction; - minGasLimit?: number; // unused, will be removed in v3.0.0 - callbackRoute?: string; - transactionsDisplayInfo: TransactionsDisplayInfoType; - customTransactionInformation: CustomTransactionInformation; -} - -export interface SignedTransactionsBodyType { - transactions?: SignedTransactionType[]; - status?: TransactionBatchStatusesEnum; - errorMessage?: string; - redirectRoute?: string; - customTransactionInformation?: CustomTransactionInformation; -} - -export interface SignedTransactionsType { - [sessionId: string]: SignedTransactionsBodyType; -} - -export interface TransactionParameter { - sender: Address; - receiver: Address; - functionName: string; - inputParameters: string[]; - outputParameters: string[]; -} - -export type RawTransactionType = IPlainTransactionObject; - -export interface TransactionDataTokenType { - tokenId: string; - amount: string; - receiver: string; - type?: MultiEsdtTransactionType['type'] | ''; - nonce?: string; - multiTxData?: string; -} - -export type TransactionsDataTokensType = - | Record - | undefined; - -interface MultiEsdtType { - type: - | TransactionTypesEnum.esdtTransaction - | TransactionTypesEnum.nftTransaction; - receiver: string; - token?: string; - nonce?: string; - amount?: string; - data: string; -} - -interface MultiEsdtScCallType { - type: TransactionTypesEnum.scCall; + results: SmartContractResult[]; + sender: string; receiver: string; - token?: string; - nonce?: string; - amount?: string; data: string; -} - -export type MultiEsdtTransactionType = MultiEsdtType | MultiEsdtScCallType; - -export interface MultiSignTransactionType { - multiTxData?: string; - transactionIndex: number; - transaction: Transaction; -} - -export interface TokenOptionType { - name: string; - identifier: string; - balance: string; - decimals: number; - collection?: string; - avatar?: string; -} - -export interface SimpleTransactionType { - value: string; - receiver: string; - data?: string; - gasPrice?: number; - gasLimit?: number; - chainID?: string; - version?: number; - options?: number; - guardian?: string; - guardianSignature?: string; - nonce?: number; -} - -export interface TransactionsDisplayInfoType { - errorMessage?: string; - successMessage?: string; - processingMessage?: string; - submittedMessage?: string; - transactionDuration?: number; - timedOutMessage?: string; - invalidMessage?: string; -} - -export interface SendSimpleTransactionParamsType { - transactions: SimpleTransactionType[]; - minGasLimit?: number; -} + previousStatus: string; + hasStatusChanged: boolean; +}[]; -export interface ActiveLedgerTransactionType { - dataField: string; - isTokenTransaction: boolean; - receiverScamInfo: string | null; - transaction: Transaction; - transactionIndex: number; - transactionTokenInfo: TransactionDataTokenType; -} +export type GetTransactionsByHashesType = ( + pendingTransactions: PendingTransactionsType +) => Promise; export interface SmartContractResult { hash: string; @@ -525,74 +48,3 @@ export interface SmartContractResult { miniBlockHash: string; returnMessage: string; } - -export type DeviceSignedTransactions = Record; - -export interface SendTransactionReturnType { - error?: string; - sessionId: string | null; -} - -export interface SendBatchTransactionReturnType { - error?: string; - batchId: string | null; -} - -export type GetTransactionsByHashesType = ( - pendingTransactions: PendingTransactionsType -) => Promise; - -export type GetTransactionsByHashesReturnType = { - hash: string; - invalidTransaction: boolean; - status: TransactionServerStatusesEnum; - inTransit?: boolean; - results: SmartContractResult[]; - sender: string; - receiver: string; - data: string; - previousStatus: string; - hasStatusChanged: boolean; -}[]; - -export type PendingTransactionsType = { - hash: string; - previousStatus: string; -}[]; - -export interface TransactionLinkType { - link: string; - label: string; - address: string; -} - -export interface SignedTransactionType extends RawTransactionType { - hash: string; - status: TransactionServerStatusesEnum; - inTransit?: boolean; - errorMessage?: string; - customTransactionInformation?: CustomTransactionInformation; -} - -export interface CustomTransactionInformation { - redirectAfterSign: boolean; - sessionInformation: any; - completedTransactionsDelay?: number; - signWithoutSending: boolean; - /** - * If true, transactions with lower nonces than the account nonce will not be updated with the correct nonce - */ - skipUpdateNonces?: boolean; - /** - * If true, the change guardian action will not trigger transaction version update - */ - skipGuardian?: boolean; - /** - * Keeps indexes of transactions that should be grouped together. If not provided, all transactions will be grouped together. Used only for batch transactions. - */ - grouping?: number[][]; - /** - * For Cross-Window provider in Safari browser, performing async calls before signing transactions needs a consent popup in order to open a new tab. - */ - hasConsentPopup?: boolean; -} From 26c3c9f3d21baf01a14abf510013f33b763c0d39 Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Wed, 4 Dec 2024 15:26:40 +0200 Subject: [PATCH 08/23] Removed endpoints --- .../transactions/getTransactionByHash.ts | 23 ------- .../transactions/getTransactionsByHashes.ts | 44 ------------ .../transactions/getTransactionsByParams.ts | 17 ----- .../getTransactionsByParamsCount.ts | 18 ----- .../transactions/helpers/getTimeout.ts | 3 - .../helpers/getTransactionsParams.ts | 31 --------- src/apiCalls/transactions/helpers/index.ts | 2 - src/apiCalls/transactions/index.ts | 9 --- .../sendSignedBatchTransactions.ts | 68 ------------------- .../transactions/sendSignedTransactions.ts | 22 ------ .../types/getTransactions.types.ts | 20 ------ src/apiCalls/transactions/types/index.ts | 1 - .../transactions/useGetTransactionsFromApi.ts | 9 --- 13 files changed, 267 deletions(-) delete mode 100644 src/apiCalls/transactions/getTransactionByHash.ts delete mode 100644 src/apiCalls/transactions/getTransactionsByHashes.ts delete mode 100644 src/apiCalls/transactions/getTransactionsByParams.ts delete mode 100644 src/apiCalls/transactions/getTransactionsByParamsCount.ts delete mode 100644 src/apiCalls/transactions/helpers/getTimeout.ts delete mode 100644 src/apiCalls/transactions/helpers/getTransactionsParams.ts delete mode 100644 src/apiCalls/transactions/helpers/index.ts delete mode 100644 src/apiCalls/transactions/index.ts delete mode 100644 src/apiCalls/transactions/sendSignedBatchTransactions.ts delete mode 100644 src/apiCalls/transactions/sendSignedTransactions.ts delete mode 100644 src/apiCalls/transactions/types/getTransactions.types.ts delete mode 100644 src/apiCalls/transactions/types/index.ts delete mode 100644 src/apiCalls/transactions/useGetTransactionsFromApi.ts diff --git a/src/apiCalls/transactions/getTransactionByHash.ts b/src/apiCalls/transactions/getTransactionByHash.ts deleted file mode 100644 index 6d6e52e..0000000 --- a/src/apiCalls/transactions/getTransactionByHash.ts +++ /dev/null @@ -1,23 +0,0 @@ -import axios from 'axios'; -import { ServerTransactionType } from 'types'; -import { TRANSACTIONS_ENDPOINT } from '../endpoints'; -import { getTimeout } from './helpers'; - -export interface GetTransactionType { - apiAddress: string; - apiTimeout?: string | number; - hash: string; -} - -export function getTransactionByHash({ - hash, - apiAddress, - apiTimeout -}: GetTransactionType) { - return axios.get( - `${apiAddress}/${TRANSACTIONS_ENDPOINT}/${hash}`, - { - ...getTimeout(apiTimeout) - } - ); -} diff --git a/src/apiCalls/transactions/getTransactionsByHashes.ts b/src/apiCalls/transactions/getTransactionsByHashes.ts deleted file mode 100644 index ed5856e..0000000 --- a/src/apiCalls/transactions/getTransactionsByHashes.ts +++ /dev/null @@ -1,44 +0,0 @@ -import axios from 'axios'; -import { TRANSACTIONS_ENDPOINT } from 'apiCalls/endpoints'; - -import { networkSelector } from 'store/selectors'; -import { getState } from 'store/store'; -import { - GetTransactionsByHashesReturnType, - PendingTransactionsType -} from 'types/transactions.types'; - -export const getTransactionsByHashes = async ( - pendingTransactions: PendingTransactionsType -): Promise => { - const { apiAddress } = networkSelector(getState()); - const hashes = pendingTransactions.map((tx) => tx.hash); - - const { data: responseData } = await axios.get( - `${apiAddress}/${TRANSACTIONS_ENDPOINT}`, - { - params: { - hashes: hashes.join(','), - withScResults: true - } - } - ); - - return pendingTransactions.map(({ hash, previousStatus }) => { - const txOnNetwork = responseData.find( - (txResponse: any) => txResponse?.txHash === hash - ); - - return { - hash, - data: txOnNetwork?.data, - invalidTransaction: txOnNetwork == null, - status: txOnNetwork?.status, - results: txOnNetwork?.results, - sender: txOnNetwork?.sender, - receiver: txOnNetwork?.receiver, - previousStatus, - hasStatusChanged: txOnNetwork && txOnNetwork.status !== previousStatus - }; - }); -}; diff --git a/src/apiCalls/transactions/getTransactionsByParams.ts b/src/apiCalls/transactions/getTransactionsByParams.ts deleted file mode 100644 index 642940d..0000000 --- a/src/apiCalls/transactions/getTransactionsByParams.ts +++ /dev/null @@ -1,17 +0,0 @@ -import axios from 'axios'; -import { TRANSACTIONS_ENDPOINT } from 'apiCalls/endpoints'; -import { ServerTransactionType } from 'types'; -import { GetTransactionsParamsType } from './types/getTransactions.types'; -import { getTimeout, getTransactionsParams } from './helpers'; - -export function getTransactionsByParams(params: GetTransactionsParamsType) { - const parsedParams = getTransactionsParams(params); - - return axios.get( - `${params.apiAddress}/${TRANSACTIONS_ENDPOINT}`, - { - params: parsedParams, - ...getTimeout(params.apiTimeout) - } - ); -} diff --git a/src/apiCalls/transactions/getTransactionsByParamsCount.ts b/src/apiCalls/transactions/getTransactionsByParamsCount.ts deleted file mode 100644 index 712efc2..0000000 --- a/src/apiCalls/transactions/getTransactionsByParamsCount.ts +++ /dev/null @@ -1,18 +0,0 @@ -import axios from 'axios'; -import { TRANSACTIONS_COUNT_ENDPOINT } from 'apiCalls/endpoints'; -import { GetTransactionsParamsType } from './types/getTransactions.types'; -import { getTimeout, getTransactionsParams } from './helpers'; - -export function getTransactionsByParamsCount( - params: GetTransactionsParamsType -) { - const parsedParams = getTransactionsParams(params); - - return axios.get( - `${params.apiAddress}/${TRANSACTIONS_COUNT_ENDPOINT}`, - { - params: parsedParams, - ...getTimeout(params.apiTimeout) - } - ); -} diff --git a/src/apiCalls/transactions/helpers/getTimeout.ts b/src/apiCalls/transactions/helpers/getTimeout.ts deleted file mode 100644 index eb0a7d3..0000000 --- a/src/apiCalls/transactions/helpers/getTimeout.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function getTimeout(apiTimeout?: string | number) { - return apiTimeout ? { timeout: parseInt(String(apiTimeout)) } : {}; -} diff --git a/src/apiCalls/transactions/helpers/getTransactionsParams.ts b/src/apiCalls/transactions/helpers/getTransactionsParams.ts deleted file mode 100644 index a2b1270..0000000 --- a/src/apiCalls/transactions/helpers/getTransactionsParams.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { GetTransactionsParamsType } from '../types/getTransactions.types'; - -export function getTransactionsParams({ - sender, - receiver, - page = 1, - transactionSize = 15, - condition = 'should', - withScResults = true, - after, - before, - search, - status, - withUsername -}: GetTransactionsParamsType) { - const params = { - sender, - receiver, - condition, - after, - before, - search, - from: (page - 1) * transactionSize, - ...(transactionSize > 0 ? { size: transactionSize } : {}), - withScResults, - withUsername, - status - }; - - return params; -} diff --git a/src/apiCalls/transactions/helpers/index.ts b/src/apiCalls/transactions/helpers/index.ts deleted file mode 100644 index 41b4f8f..0000000 --- a/src/apiCalls/transactions/helpers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './getTimeout'; -export * from './getTransactionsParams'; diff --git a/src/apiCalls/transactions/index.ts b/src/apiCalls/transactions/index.ts deleted file mode 100644 index fab49a6..0000000 --- a/src/apiCalls/transactions/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from './getTransactionByHash'; -export * from './getTransactionsByParams'; -export * from './types/getTransactions.types'; -export * from './getTransactionsByHashes'; -export * from './getTransactionsByParamsCount'; -export * from './helpers'; -export * from './sendSignedBatchTransactions'; -export * from './sendSignedTransactions'; -export * from './useGetTransactionsFromApi'; diff --git a/src/apiCalls/transactions/sendSignedBatchTransactions.ts b/src/apiCalls/transactions/sendSignedBatchTransactions.ts deleted file mode 100644 index 1aafc58..0000000 --- a/src/apiCalls/transactions/sendSignedBatchTransactions.ts +++ /dev/null @@ -1,68 +0,0 @@ -import axios from 'axios'; -import { TIMEOUT } from 'constants/network'; -import { buildBatchId } from 'hooks/transactions/helpers/buildBatchId'; -import { addressSelector, networkSelector } from 'reduxStore/selectors'; -import { store } from 'reduxStore/store'; -import { - BatchTransactionsRequestType, - BatchTransactionsResponseType, - CustomTransactionInformation, - SignedTransactionType -} from 'types'; -import { TRANSACTIONS_BATCH } from '../endpoints'; - -export interface SendBatchTransactionsPropsType { - transactions: SignedTransactionType[][]; - sessionId: string; - customTransactionInformationOverrides?: Partial; -} - -export type SendSignedBatchTransactionsReturnType = { - error?: string | null; - batchId?: string | null; - data?: BatchTransactionsResponseType; -}; - -export async function sendSignedBatchTransactions({ - transactions, - sessionId -}: SendBatchTransactionsPropsType) { - const address = addressSelector(store.getState()); - const { apiAddress, apiTimeout } = networkSelector(store.getState()); - - if (!address) { - return { - error: - 'Invalid address provided. You need to be logged in to send transactions', - batchId: null - }; - } - - try { - const batchId = buildBatchId({ - sessionId, - address - }); - - const payload: BatchTransactionsRequestType = { - transactions, - id: batchId - }; - - const response = await axios.post( - `${apiAddress}/${TRANSACTIONS_BATCH}`, - payload, - { - timeout: Number(apiTimeout ?? TIMEOUT) - } - ); - - return { batchId, data: response.data }; - } catch (err) { - console.error('error sending batch transactions', err); - return { - error: (err as any)?.message ?? 'error sending batch transactions', - batchId: null - }; - } -} diff --git a/src/apiCalls/transactions/sendSignedTransactions.ts b/src/apiCalls/transactions/sendSignedTransactions.ts deleted file mode 100644 index 7288583..0000000 --- a/src/apiCalls/transactions/sendSignedTransactions.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Transaction } from '@multiversx/sdk-core'; -import axios from 'axios'; -import { networkSelector } from 'reduxStore/selectors'; -import { store } from 'reduxStore/store'; - -export type SendSignedTransactionsReturnType = string[]; - -export async function sendSignedTransactions( - signedTransactions: Transaction[] -): Promise { - const { apiAddress, apiTimeout } = networkSelector(store.getState()); - const promises = signedTransactions.map((transaction) => { - return axios.post( - `${apiAddress}/transactions`, - transaction.toPlainObject(), - { timeout: parseInt(apiTimeout) } - ); - }); - const response = await Promise.all(promises); - - return response.map(({ data }) => data.txHash); -} diff --git a/src/apiCalls/transactions/types/getTransactions.types.ts b/src/apiCalls/transactions/types/getTransactions.types.ts deleted file mode 100644 index de5e734..0000000 --- a/src/apiCalls/transactions/types/getTransactions.types.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TransactionServerStatusesEnum } from 'types'; - -export interface GetTransactionsParamsType { - apiAddress: string; - apiTimeout?: string | number; - sender?: string; - receiver?: string; - page?: number; - transactionSize?: number; - after?: number; - condition?: 'should' | 'must'; - before?: number; - withScResults?: boolean; - withUsername?: boolean; - status?: TransactionServerStatusesEnum; - /** - * Search in data object - */ - search?: string; -} diff --git a/src/apiCalls/transactions/types/index.ts b/src/apiCalls/transactions/types/index.ts deleted file mode 100644 index 91ae4f6..0000000 --- a/src/apiCalls/transactions/types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './getTransactions.types'; diff --git a/src/apiCalls/transactions/useGetTransactionsFromApi.ts b/src/apiCalls/transactions/useGetTransactionsFromApi.ts deleted file mode 100644 index 17dd567..0000000 --- a/src/apiCalls/transactions/useGetTransactionsFromApi.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { TRANSACTIONS_ENDPOINT } from '../endpoints'; -import { useApiFetch } from '../utils'; - -export const useGetTransactionsFromApi = (hash: string) => - useApiFetch({ - apiEndpoint: hash - ? `${TRANSACTIONS_ENDPOINT}/${hash}` - : TRANSACTIONS_ENDPOINT - }); From 590c727e585ec4c9e48dcf045294c9c20f0d4aa2 Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Wed, 4 Dec 2024 15:29:45 +0200 Subject: [PATCH 09/23] Added getTransactionByHash.ts --- .../transactions/getTransactionByHash.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/apiCalls/transactions/getTransactionByHash.ts diff --git a/src/apiCalls/transactions/getTransactionByHash.ts b/src/apiCalls/transactions/getTransactionByHash.ts new file mode 100644 index 0000000..98a514c --- /dev/null +++ b/src/apiCalls/transactions/getTransactionByHash.ts @@ -0,0 +1,16 @@ +import axios from 'axios'; +import { TRANSACTIONS_ENDPOINT } from 'apiCalls/endpoints'; +import { networkSelector } from 'store/selectors'; +import { getState } from 'store/store'; +import { ServerTransactionType } from 'types/serverTransactions.types'; + +export const getTransactionByHash = (hash: string) => { + const { apiAddress } = networkSelector(getState()); + + return axios.get( + `${apiAddress}/${TRANSACTIONS_ENDPOINT}/${hash}`, + { + timeout: 10000 // 10sec + } + ); +}; From 9d77761e7da49270b2e294039220d50b70a01407 Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Wed, 4 Dec 2024 15:30:42 +0200 Subject: [PATCH 10/23] Added getTransactionsByHashes.ts --- .../transactions/getTransactionsByHashes.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/apiCalls/transactions/getTransactionsByHashes.ts diff --git a/src/apiCalls/transactions/getTransactionsByHashes.ts b/src/apiCalls/transactions/getTransactionsByHashes.ts new file mode 100644 index 0000000..ed5856e --- /dev/null +++ b/src/apiCalls/transactions/getTransactionsByHashes.ts @@ -0,0 +1,44 @@ +import axios from 'axios'; +import { TRANSACTIONS_ENDPOINT } from 'apiCalls/endpoints'; + +import { networkSelector } from 'store/selectors'; +import { getState } from 'store/store'; +import { + GetTransactionsByHashesReturnType, + PendingTransactionsType +} from 'types/transactions.types'; + +export const getTransactionsByHashes = async ( + pendingTransactions: PendingTransactionsType +): Promise => { + const { apiAddress } = networkSelector(getState()); + const hashes = pendingTransactions.map((tx) => tx.hash); + + const { data: responseData } = await axios.get( + `${apiAddress}/${TRANSACTIONS_ENDPOINT}`, + { + params: { + hashes: hashes.join(','), + withScResults: true + } + } + ); + + return pendingTransactions.map(({ hash, previousStatus }) => { + const txOnNetwork = responseData.find( + (txResponse: any) => txResponse?.txHash === hash + ); + + return { + hash, + data: txOnNetwork?.data, + invalidTransaction: txOnNetwork == null, + status: txOnNetwork?.status, + results: txOnNetwork?.results, + sender: txOnNetwork?.sender, + receiver: txOnNetwork?.receiver, + previousStatus, + hasStatusChanged: txOnNetwork && txOnNetwork.status !== previousStatus + }; + }); +}; From ed513b37e5b0230b8fccf4d0ca600542e4812aa7 Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Wed, 4 Dec 2024 18:20:21 +0200 Subject: [PATCH 11/23] Lint, test, and build fixes --- jest.config.js | 4 +- src/apiCalls/index.ts | 1 + src/apiCalls/transactions/index.ts | 2 + src/constants/index.ts | 2 +- src/constants/mvx.constants.ts | 13 +- src/constants/transactions.constants.ts | 8 +- .../initializeWebsocketConnection.ts | 2 +- .../checkTransactionStatus/checkBatch.ts | 4 +- .../DappProvider/helpers/logout/logout.ts | 2 +- src/core/providers/ProviderFactory.ts | 4 +- .../nativeAuth/helpers/getLatestBlockHash.ts | 2 +- .../actions/network/initializeNetwork.ts | 2 +- src/store/selectors/networkSelectors.ts | 3 + src/store/selectors/transactionsSelectors.ts | 79 -- src/types/enums.types.ts | 20 - src/types/index.ts | 5 +- src/types/serverTransactions.types.ts | 2 +- src/types/transactions.types.ts | 51 +- src/utils/account/fetchAccount.ts | 4 +- src/utils/account/getShardOfAddress.ts | 7 +- src/utils/account/refreshAccount.ts | 4 +- src/utils/dateTime/getUnixTimestamp.ts | 5 +- .../getUnixTimestampWithAddedMilliseconds.ts | 7 +- .../getUnixTimestampWithAddedSeconds.ts | 5 +- src/utils/decoders/base64Utils.ts | 1 + src/utils/decoders/decodePart.ts | 1 + src/utils/decoders/isAscii.ts | 7 +- src/utils/decoders/isUtf8.ts | 1 + src/utils/decoders/stringContainsNumbers.ts | 5 +- src/utils/index.ts | 2 +- src/utils/misc/getAllStringOccurrences.ts | 17 + src/utils/misc/index.ts | 2 + src/utils/{ => misc}/retryMultipleTimes.ts | 20 +- .../misc/tests/getAllStringOccurences.test.ts | 42 + src/utils/operations/formatAmount.ts | 1 + src/utils/operations/getUsdValue.ts | 20 + src/utils/operations/index.ts | 1 + src/utils/operations/pipe.ts | 1 + .../operations/tests/formatAmount.test.ts | 325 +++---- .../operations/tests/getUsdValue.test.ts | 23 + .../getBatchTransactionsStatusFromApi.ts | 4 +- .../batch/tests/getTransactionsStatus.test.ts | 2 +- .../batch/tests/sendBatchTransactions.test.ts | 215 ----- .../helpers/decodeByMethod.ts | 15 +- .../helpers/getSmartDecodedParts.ts | 3 +- .../tests/decodeByMethod.spec.ts | 13 +- .../tests/decodeForDisplay.spec.ts | 5 +- ...tDisplayValueAndValidationWarnings.spec.ts | 5 +- .../tests/getSmartDecodedParts.spec.ts | 4 +- .../dataDecoders/getTokenFromData.ts | 10 +- src/utils/transactions/index.ts | 1 - .../operations/calculateFeeInFiat.ts | 33 + .../operations/calculateFeeLimit.ts | 76 ++ .../calculateTotalTransactionsFee.ts | 2 +- src/utils/transactions/operations/index.ts | 2 + .../tests/calculateFeeInFiat.test.ts | 11 + .../tests/calculateFeeLimit.test.ts | 96 +++ .../getInterpretedTransaction/constants.ts | 2 +- .../getInterpretedTransaction.ts | 9 +- .../helpers/getTransactionMethod.ts | 2 +- .../helpers/getTransactionTransferType.ts | 2 +- .../getTransactionValue.ts | 21 +- .../helpers/getTitleText.ts | 8 +- .../helpers/getValueFromDataField.ts | 4 +- .../helpers/getValueFromOperations.ts | 5 +- .../tests/getTransactionValue.test.ts | 42 +- .../helpers/tests/base-transaction-mock.ts | 2 +- .../tests/getOperationsMessages.test.ts | 2 +- .../tests/getTransactionMethod.test.ts | 4 +- .../tests/getTransactionReceiver.test.ts | 2 +- .../tests/getTransactionTokens.test.ts | 2 +- .../tests/getTransactionTransferType.test.ts | 7 +- .../tests/extended-transaction-mock.ts | 2 +- .../tests/getInterpretedTransaction.test.ts | 11 +- .../parsers/getTransactionLink.ts | 6 +- .../parsers/getTransactionMessages.ts | 10 +- .../parsers/getTransactionStatus.ts | 2 +- .../parsers/getTransactionStatusText.ts | 2 +- .../parsers/getTransactionsDetails.ts | 12 +- .../parsers/getVisibleOperations.ts | 2 +- .../parsers/parseMultiEsdtTransferData.ts | 16 +- ...EsdtTransferDataForMultipleTransactions.ts | 1 + .../parsers/parseTransactionAfterSigning.ts | 15 +- .../tests/getOperationsDetails.test.ts | 20 +- .../tests/getTransactionLinkWithLabel.test.ts | 9 +- .../helpers/mexUnwrapper.ts | 9 +- .../transactionActionUnwrapper.ts | 2 +- .../validation/isCrossShardTransaction.ts | 2 +- .../validation/tests/isGuardianTx.test.ts | 2 +- src/utils/validation/addressIsValid.ts | 1 + src/utils/validation/maxDecimals.ts | 7 +- src/utils/validation/stringIsFloat.ts | 5 +- src/utils/validation/stringIsInteger.ts | 8 +- src/utils/window/index.ts | 2 +- .../tests/clearNavigationHistory.test.ts | 6 +- yarn.lock | 797 ++++++++++-------- 96 files changed, 1187 insertions(+), 1080 deletions(-) create mode 100644 src/apiCalls/transactions/index.ts delete mode 100644 src/store/selectors/transactionsSelectors.ts create mode 100644 src/utils/misc/getAllStringOccurrences.ts create mode 100644 src/utils/misc/index.ts rename src/utils/{ => misc}/retryMultipleTimes.ts (67%) create mode 100644 src/utils/misc/tests/getAllStringOccurences.test.ts create mode 100644 src/utils/operations/getUsdValue.ts create mode 100644 src/utils/operations/tests/getUsdValue.test.ts delete mode 100644 src/utils/transactions/batch/tests/sendBatchTransactions.test.ts create mode 100644 src/utils/transactions/operations/calculateFeeInFiat.ts create mode 100755 src/utils/transactions/operations/calculateFeeLimit.ts rename src/utils/transactions/{ => operations}/calculateTotalTransactionsFee.ts (93%) create mode 100644 src/utils/transactions/operations/index.ts create mode 100644 src/utils/transactions/operations/tests/calculateFeeInFiat.test.ts create mode 100644 src/utils/transactions/operations/tests/calculateFeeLimit.test.ts diff --git a/jest.config.js b/jest.config.js index af05d96..b02fd07 100644 --- a/jest.config.js +++ b/jest.config.js @@ -25,5 +25,7 @@ module.exports = { 'web.tsx', 'json', 'node' - ] + ], + workerIdleMemoryLimit: '512MB', // Memory used per worker. Required to prevent memory leaks + maxWorkers: '50%' // Maximum tests ran in parallel. Required to prevent CPU usage at 100% }; diff --git a/src/apiCalls/index.ts b/src/apiCalls/index.ts index 28f2150..fc13821 100644 --- a/src/apiCalls/index.ts +++ b/src/apiCalls/index.ts @@ -1,2 +1,3 @@ export * from './configuration'; export * from './endpoints'; +export * from './transactions'; diff --git a/src/apiCalls/transactions/index.ts b/src/apiCalls/transactions/index.ts new file mode 100644 index 0000000..ac35e8d --- /dev/null +++ b/src/apiCalls/transactions/index.ts @@ -0,0 +1,2 @@ +export * from './getTransactionByHash'; +export * from './getTransactionsByHashes'; diff --git a/src/constants/index.ts b/src/constants/index.ts index 7798f00..c563bd3 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -5,5 +5,5 @@ export * from './ledger.constants'; export * from './network.constants'; export * from './placeholders.constants'; export * from './storage.constants'; -export * from './transactions.constants' +export * from './transactions.constants'; export * from './window.constants'; diff --git a/src/constants/mvx.constants.ts b/src/constants/mvx.constants.ts index c983a41..d2ebe5e 100644 --- a/src/constants/mvx.constants.ts +++ b/src/constants/mvx.constants.ts @@ -1,14 +1,7 @@ -export const GAS_PRICE_MODIFIER = 0.01; -export const GAS_PER_DATA_BYTE = 1500; -export const GAS_LIMIT = 50000; -/** - * Extra gas limit for guarded transactions - */ -export const EXTRA_GAS_LIMIT_GUARDED_TX = 50000; -export const GAS_PRICE = 1000000000; +export const ALL_SHARDS_SHARD_ID = 4294967280; +export const CROSS_SHARD_ROUNDS = 5; export const DECIMALS = 18; export const DIGITS = 4; -export const VERSION = 1; export const LEDGER_CONTRACT_DATA_ENABLED_VALUE = 1; export const METACHAIN_SHARD_ID = 4294967295; -export const ALL_SHARDS_SHARD_ID = 4294967280; +export const VERSION = 1; diff --git a/src/constants/transactions.constants.ts b/src/constants/transactions.constants.ts index dd0f90f..ebcc89b 100644 --- a/src/constants/transactions.constants.ts +++ b/src/constants/transactions.constants.ts @@ -1,18 +1,12 @@ -export const ALL_SHARDS_SHARD_ID = 4294967280; export const AVERAGE_TX_DURATION_MS = 6000; export const CANCEL_TRANSACTION_TOAST_DEFAULT_DURATION = 20000; export const CANCEL_TRANSACTION_TOAST_ID = 'cancel-transaction-toast'; -export const CROSS_SHARD_ROUNDS = 5; -export const DECIMALS = 18; -export const DIGITS = 4; export const EXTRA_GAS_LIMIT_GUARDED_TX = 50_000; export const GAS_LIMIT = 50_000; export const GAS_PER_DATA_BYTE = 1_500; export const GAS_PRICE = 1_000_000_000; export const GAS_PRICE_MODIFIER = 0.01; -export const METACHAIN_SHARD_ID = 4294967295; -export const SDK_DAPP_VERSION = 'sdk-dapp-version'; +export const REFUNDED_GAS = 'refundedGas'; export const TRANSACTIONS_STATUS_DROP_INTERVAL_MS = 10 * 60 * 1000; // 10min export const TRANSACTIONS_STATUS_POLLING_INTERVAL_MS = 90 * 1000; // 90sec -export const VERSION = 1; export const WALLET_SIGN_SESSION = 'signSession'; diff --git a/src/core/methods/initApp/websocket/initializeWebsocketConnection.ts b/src/core/methods/initApp/websocket/initializeWebsocketConnection.ts index 9b8a39a..569dc51 100644 --- a/src/core/methods/initApp/websocket/initializeWebsocketConnection.ts +++ b/src/core/methods/initApp/websocket/initializeWebsocketConnection.ts @@ -7,7 +7,7 @@ import { } from 'store/actions/account/accountActions'; import { networkSelector } from 'store/selectors'; import { getStore } from 'store/store'; -import { retryMultipleTimes } from 'utils/retryMultipleTimes'; +import { retryMultipleTimes } from 'utils/misc/retryMultipleTimes'; import { BatchTransactionsWSResponseType, websocketConnection, diff --git a/src/core/methods/trackTransactions/helpers/checkTransactionStatus/checkBatch.ts b/src/core/methods/trackTransactions/helpers/checkTransactionStatus/checkBatch.ts index e789db8..3d1f8fa 100644 --- a/src/core/methods/trackTransactions/helpers/checkTransactionStatus/checkBatch.ts +++ b/src/core/methods/trackTransactions/helpers/checkTransactionStatus/checkBatch.ts @@ -19,7 +19,7 @@ import { getPendingTransactions } from './getPendingTransactions'; import { manageFailedTransactions } from './manageFailedTransactions'; import { TransactionsTrackerType } from '../../trackTransactions.types'; -export interface TransactionStatusTrackerPropsType +export interface TransactionStatusTrackerParamsType extends TransactionsTrackerType { sessionId: string; transactionBatch: SignedTransactionType[]; @@ -126,7 +126,7 @@ export async function checkBatch({ isSequential, onSuccess, onFail -}: TransactionStatusTrackerPropsType) { +}: TransactionStatusTrackerParamsType) { try { if (transactions == null) { return; diff --git a/src/core/providers/DappProvider/helpers/logout/logout.ts b/src/core/providers/DappProvider/helpers/logout/logout.ts index 026fe66..eeb871c 100644 --- a/src/core/providers/DappProvider/helpers/logout/logout.ts +++ b/src/core/providers/DappProvider/helpers/logout/logout.ts @@ -37,7 +37,7 @@ export type LogoutParamsType = { interface IProviderLogout { provider: IProvider; - options?: LogoutPropsType; + options?: LogoutParamsType; } export async function logout({ diff --git a/src/core/providers/ProviderFactory.ts b/src/core/providers/ProviderFactory.ts index d7427be..111f803 100644 --- a/src/core/providers/ProviderFactory.ts +++ b/src/core/providers/ProviderFactory.ts @@ -48,8 +48,8 @@ export class ProviderFactory { case ProviderTypeEnum.ledger: { const ledgerProvider = await createLedgerProvider( - ui.ledger.eventBus, - ui.ledger.mount + ui?.ledger.eventBus, + ui?.ledger.mount ); if (!ledgerProvider) { diff --git a/src/services/nativeAuth/helpers/getLatestBlockHash.ts b/src/services/nativeAuth/helpers/getLatestBlockHash.ts index 2f748f2..586ec2b 100644 --- a/src/services/nativeAuth/helpers/getLatestBlockHash.ts +++ b/src/services/nativeAuth/helpers/getLatestBlockHash.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { BLOCKS_ENDPOINT } from 'apiCalls/endpoints'; -import { retryMultipleTimes } from 'utils/retryMultipleTimes'; +import { retryMultipleTimes } from 'utils/misc/retryMultipleTimes'; export interface LatestBlockHashType { hash: string; diff --git a/src/store/actions/network/initializeNetwork.ts b/src/store/actions/network/initializeNetwork.ts index 34be0fe..97e2431 100644 --- a/src/store/actions/network/initializeNetwork.ts +++ b/src/store/actions/network/initializeNetwork.ts @@ -13,7 +13,7 @@ export type InitializeNetworkParamsType = { export const initializeNetwork = async ({ customNetworkConfig = {}, environment -}: InitializeNetworkParamsType): Promise => { +}: InitializeNetworkParamsType) => { const fetchConfigFromServer = !customNetworkConfig?.skipFetchFromServer; const customNetworkApiAddress = customNetworkConfig?.apiAddress; diff --git a/src/store/selectors/networkSelectors.ts b/src/store/selectors/networkSelectors.ts index 9e023f9..f95dbfe 100644 --- a/src/store/selectors/networkSelectors.ts +++ b/src/store/selectors/networkSelectors.ts @@ -16,3 +16,6 @@ export const roundDurationSelectorSelector = ({ export const apiAddressSelector = ({ network: { network } }: StoreType) => network.apiAddress; + +export const explorerAddressSelector = ({ network: { network } }: StoreType) => + network.explorerAddress; diff --git a/src/store/selectors/transactionsSelectors.ts b/src/store/selectors/transactionsSelectors.ts deleted file mode 100644 index b68c98a..0000000 --- a/src/store/selectors/transactionsSelectors.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { StoreType } from 'store/store.types'; -import { - getIsTransactionFailed, - getIsTransactionPending, - getIsTransactionSuccessful, - getIsTransactionTimedOut, - newTransaction -} from 'utils/transactions'; -import { - CustomTransactionInformation, - RawTransactionType, - SignedTransactionsType -} from '../../types'; - -export const signedTransactionsSelector = ({ transactions }: StoreType) => - transactions.signedTransactions; - -export const signTransactionsErrorSelector = ({ transactions }: StoreType) => - transactions.signTransactionsError; - -export const signTransactionsCancelMessageSelector = ({ - transactions -}: StoreType) => transactions.signTransactionsCancelMessage; - -const selectTxByStatus = - (txStatusVerifier: typeof getIsTransactionPending) => (store: StoreType) => { - const signedTransactions = signedTransactionsSelector(store); - - return Object.entries(signedTransactions).reduce( - (acc, [sessionId, txBody]) => { - if (txStatusVerifier(txBody.status)) { - acc[sessionId] = txBody; - } - return acc; - }, - {} as SignedTransactionsType - ); - }; - -export const pendingSignedTransactionsSelector = selectTxByStatus( - getIsTransactionPending -); - -export const successfulTransactionsSelector = selectTxByStatus( - getIsTransactionSuccessful -); - -export const failedTransactionsSelector = selectTxByStatus( - getIsTransactionFailed -); - -export const timedOutTransactionsSelector = selectTxByStatus( - getIsTransactionTimedOut -); - -export const transactionsToSignSelector = ({ transactions }: StoreType) => { - const transactionsToSign = transactions.transactionsToSign; - - if (transactionsToSign == null) { - return null; - } - - return { - ...transactionsToSign, - transactions: - transactionsToSign?.transactions.map((tx: RawTransactionType) => - newTransaction(tx) - ) || [] - }; -}; - -export const transactionStatusSelector = - (transactionSessionId: number) => (store: StoreType) => { - const signedTransactions = signedTransactionsSelector(store); - - return signedTransactions.transactionSessionId != null - ? signedTransactions?.[transactionSessionId] || {} - : {}; - }; diff --git a/src/types/enums.types.ts b/src/types/enums.types.ts index cf97633..676e179 100644 --- a/src/types/enums.types.ts +++ b/src/types/enums.types.ts @@ -43,16 +43,6 @@ export enum ESDTTransferTypesEnum { ESDTFreeze = 'ESDTFreeze' } -export enum TransactionServerStatusesEnum { - pending = 'pending', - fail = 'fail', - invalid = 'invalid', - success = 'success', - executed = 'executed', - notExecuted = 'not executed', - rewardReverted = 'reward-reverted' -} - export enum SignedMessageStatusesEnum { pending = 'pending', failed = 'failed', @@ -60,16 +50,6 @@ export enum SignedMessageStatusesEnum { cancelled = 'cancelled' } -export enum TransactionBatchStatusesEnum { - signed = 'signed', - cancelled = 'cancelled', - success = 'success', - sent = 'sent', - fail = 'fail', - timedOut = 'timedOut', - invalid = 'invalid' -} - export enum LoginMethodsEnum { ledger = 'ledger', walletconnect = 'walletconnect', diff --git a/src/types/index.ts b/src/types/index.ts index da399ce..4ee21aa 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -2,5 +2,6 @@ export * from './enums.types'; export * from './login.types'; export * from './misc.types'; export * from './network.types'; -export * from './tokens.types' -export * from './transactions.types'; \ No newline at end of file +export * from './serverTransactions.types'; +export * from './tokens.types'; +export * from './transactions.types'; diff --git a/src/types/serverTransactions.types.ts b/src/types/serverTransactions.types.ts index ae6b3c7..91e010d 100644 --- a/src/types/serverTransactions.types.ts +++ b/src/types/serverTransactions.types.ts @@ -299,7 +299,7 @@ export interface InterpretedTransactionType extends ServerTransactionType { }; } -export interface DecodeForDisplayPropsType { +export interface DecodeForDisplayParamsType { input: string; decodeMethod: DecodeMethodEnum; identifier?: string; diff --git a/src/types/transactions.types.ts b/src/types/transactions.types.ts index e2ae663..16df7f1 100644 --- a/src/types/transactions.types.ts +++ b/src/types/transactions.types.ts @@ -1,7 +1,9 @@ import { IPlainTransactionObject } from '@multiversx/sdk-core/out'; +import { Transaction } from '@multiversx/sdk-core/out/transaction'; import { TransactionBatchStatusesEnum, - TransactionServerStatusesEnum + TransactionServerStatusesEnum, + TransactionTypesEnum } from 'types/enums.types'; export interface SignedTransactionType extends IPlainTransactionObject { @@ -48,3 +50,50 @@ export interface SmartContractResult { miniBlockHash: string; returnMessage: string; } + +interface MultiEsdtType { + type: + | TransactionTypesEnum.esdtTransaction + | TransactionTypesEnum.nftTransaction; + receiver: string; + token?: string; + nonce?: string; + amount?: string; + data: string; +} + +interface MultiEsdtScCallType { + type: TransactionTypesEnum.scCall; + receiver: string; + token?: string; + nonce?: string; + amount?: string; + data: string; +} + +export type MultiEsdtTransactionType = MultiEsdtType | MultiEsdtScCallType; + +export interface TransactionDataTokenType { + tokenId: string; + amount: string; + receiver: string; + type?: MultiEsdtTransactionType['type'] | ''; + nonce?: string; + multiTxData?: string; +} + +export type TransactionsDataTokensType = + | Record + | undefined; + +export interface MultiSignTransactionType { + multiTxData?: string; + transactionIndex: number; + transaction: Transaction; +} + +export interface TransactionLinkType { + link: string; + label: string; + address: string; +} diff --git a/src/utils/account/fetchAccount.ts b/src/utils/account/fetchAccount.ts index 76e8587..6f13ede 100644 --- a/src/utils/account/fetchAccount.ts +++ b/src/utils/account/fetchAccount.ts @@ -1,3 +1,5 @@ import { getAccountFromApi } from 'apiCalls/account/getAccountFromApi'; -export const fetchAccount = (address?: string) => getAccountFromApi(address); +export function fetchAccount(address?: string) { + return getAccountFromApi(address); +} diff --git a/src/utils/account/getShardOfAddress.ts b/src/utils/account/getShardOfAddress.ts index 265e887..731b16c 100644 --- a/src/utils/account/getShardOfAddress.ts +++ b/src/utils/account/getShardOfAddress.ts @@ -12,7 +12,8 @@ const isAddressOfMetachain = (pubKey: Buffer) => { const zeroAddress = Buffer.alloc(32).fill(0); return pubKey.equals(zeroAddress); }; -export const getShardOfAddress = (hexPubKey: any) => { + +export function getShardOfAddress(hexPubKey: any) { try { const numShards = 3; const maskHigh = parseInt('11', 2); @@ -27,7 +28,7 @@ export const getShardOfAddress = (hexPubKey: any) => { shard = lastByteOfPubKey & maskLow; } return shard; - } catch (err) { + } catch { return -1; } -}; +} diff --git a/src/utils/account/refreshAccount.ts b/src/utils/account/refreshAccount.ts index 42f2503..def0e4d 100644 --- a/src/utils/account/refreshAccount.ts +++ b/src/utils/account/refreshAccount.ts @@ -4,7 +4,7 @@ import { getAccountProvider } from 'core/providers/accountProvider'; import { setAccount } from 'store/actions'; import { fetchAccount } from './fetchAccount'; -const setNewAccount = async () => { +async function setNewAccount() { try { const address = getAddress(); @@ -29,7 +29,7 @@ const setNewAccount = async () => { } return null; -}; +} export async function refreshAccount() { const provider = getAccountProvider(); diff --git a/src/utils/dateTime/getUnixTimestamp.ts b/src/utils/dateTime/getUnixTimestamp.ts index 1e1bcc3..a60b1ab 100644 --- a/src/utils/dateTime/getUnixTimestamp.ts +++ b/src/utils/dateTime/getUnixTimestamp.ts @@ -1,3 +1,4 @@ -export const getUnixTimestamp = () => { +// TODO: Move to utils +export function getUnixTimestamp() { return Date.now() / 1000; -}; +} diff --git a/src/utils/dateTime/getUnixTimestampWithAddedMilliseconds.ts b/src/utils/dateTime/getUnixTimestampWithAddedMilliseconds.ts index a2346bf..07dfbb0 100644 --- a/src/utils/dateTime/getUnixTimestampWithAddedMilliseconds.ts +++ b/src/utils/dateTime/getUnixTimestampWithAddedMilliseconds.ts @@ -1,9 +1,10 @@ -export const getUnixTimestampWithAddedMilliseconds = ( +// TODO: Move to utils +export function getUnixTimestampWithAddedMilliseconds( addedMilliseconds: number -) => { +) { return ( new Date().setMilliseconds( new Date().getMilliseconds() + addedMilliseconds ) / 1000 ); -}; +} diff --git a/src/utils/dateTime/getUnixTimestampWithAddedSeconds.ts b/src/utils/dateTime/getUnixTimestampWithAddedSeconds.ts index ad53a33..cb6c491 100644 --- a/src/utils/dateTime/getUnixTimestampWithAddedSeconds.ts +++ b/src/utils/dateTime/getUnixTimestampWithAddedSeconds.ts @@ -1,3 +1,4 @@ -export const getUnixTimestampWithAddedSeconds = (addedSeconds: number) => { +// TODO: Move to utils +export function getUnixTimestampWithAddedSeconds(addedSeconds: number) { return new Date().setSeconds(new Date().getSeconds() + addedSeconds); -}; +} diff --git a/src/utils/decoders/base64Utils.ts b/src/utils/decoders/base64Utils.ts index af97e3d..f77a620 100644 --- a/src/utils/decoders/base64Utils.ts +++ b/src/utils/decoders/base64Utils.ts @@ -17,6 +17,7 @@ * @see The tests for this function are in src/utils/decoders/tests/base64Utils.test.ts * @param str */ +// TODO: Move to utils export function isStringBase64(str: string) { try { // Try to decode the string and encode it back using base64 functions diff --git a/src/utils/decoders/decodePart.ts b/src/utils/decoders/decodePart.ts index 90e30f1..338c9be 100644 --- a/src/utils/decoders/decodePart.ts +++ b/src/utils/decoders/decodePart.ts @@ -1,5 +1,6 @@ import { isUtf8 } from './isUtf8'; +// TODO: Move to utils export const decodePart = (part: string) => { let decodedPart = part; diff --git a/src/utils/decoders/isAscii.ts b/src/utils/decoders/isAscii.ts index cc4c183..6ef84a6 100644 --- a/src/utils/decoders/isAscii.ts +++ b/src/utils/decoders/isAscii.ts @@ -1,2 +1,5 @@ -// eslint-disable-next-line no-control-regex -export const isAscii = (str: string) => !/[^\x00-\x7F]/gm.test(str); +// TODO: Move to utils +export function isAscii(str: string) { + // eslint-disable-next-line no-control-regex + return !/[^\x00-\x7F]/gm.test(str); +} diff --git a/src/utils/decoders/isUtf8.ts b/src/utils/decoders/isUtf8.ts index 9d08a13..f52a6aa 100644 --- a/src/utils/decoders/isUtf8.ts +++ b/src/utils/decoders/isUtf8.ts @@ -1,3 +1,4 @@ +// TODO: Move to utils export function isUtf8(str: string) { for (let i = 0; i < str.length; i++) { if (str.charCodeAt(i) > 127) { diff --git a/src/utils/decoders/stringContainsNumbers.ts b/src/utils/decoders/stringContainsNumbers.ts index fea36d6..1fdd4c5 100644 --- a/src/utils/decoders/stringContainsNumbers.ts +++ b/src/utils/decoders/stringContainsNumbers.ts @@ -1 +1,4 @@ -export const stringContainsNumbers = (str: string) => /\d/.test(str); +// TODO: Move to utils +export function stringContainsNumbers(str: string) { + return /\d/.test(str); +} diff --git a/src/utils/index.ts b/src/utils/index.ts index c4f7da4..b41d59f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -3,7 +3,7 @@ export * from './asyncActions'; export * from './dateTime'; export * from './decoders'; export * from './operations'; -export * from './retryMultipleTimes'; +export * from './misc/retryMultipleTimes'; export * from './validation'; export * from './transactions'; export * from './walletconnect'; diff --git a/src/utils/misc/getAllStringOccurrences.ts b/src/utils/misc/getAllStringOccurrences.ts new file mode 100644 index 0000000..470226f --- /dev/null +++ b/src/utils/misc/getAllStringOccurrences.ts @@ -0,0 +1,17 @@ +// TODO: Move to utils +export function getAllStringOccurrences(sourceStr: string, searchStr: string) { + if (!sourceStr || !searchStr) { + return []; + } + + const startingIndices = []; + + let indexOccurrence = sourceStr.indexOf(searchStr, 0); + + while (indexOccurrence >= 0) { + startingIndices.push(indexOccurrence); + indexOccurrence = sourceStr.indexOf(searchStr, indexOccurrence + 1); + } + + return startingIndices; +} diff --git a/src/utils/misc/index.ts b/src/utils/misc/index.ts new file mode 100644 index 0000000..2769629 --- /dev/null +++ b/src/utils/misc/index.ts @@ -0,0 +1,2 @@ +export * from './getAllStringOccurrences'; +export * from './retryMultipleTimes'; diff --git a/src/utils/retryMultipleTimes.ts b/src/utils/misc/retryMultipleTimes.ts similarity index 67% rename from src/utils/retryMultipleTimes.ts rename to src/utils/misc/retryMultipleTimes.ts index 943cd5c..504ecea 100644 --- a/src/utils/retryMultipleTimes.ts +++ b/src/utils/misc/retryMultipleTimes.ts @@ -1,16 +1,16 @@ -import { sleep } from './asyncActions'; +import { sleep } from '../asyncActions'; interface Options { retries: number; delay?: number; } -const executeAsyncCall = async ( +async function executeAsyncCall( cb: (..._args: any[]) => any, options: Options, args: any[], retries = 0 -): Promise => { +): Promise { try { return await cb(...args); // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -25,13 +25,13 @@ const executeAsyncCall = async ( return null; } -}; +} -export const retryMultipleTimes = - ( - cb: (..._args: any[]) => any, - options: Options = { retries: 5, delay: 500 } - ) => - async (...args: any[]) => { +export function retryMultipleTimes( + cb: (..._args: any[]) => any, + options: Options = { retries: 5, delay: 500 } +) { + return async function (...args: any[]) { return await executeAsyncCall(cb, options, args); }; +} diff --git a/src/utils/misc/tests/getAllStringOccurences.test.ts b/src/utils/misc/tests/getAllStringOccurences.test.ts new file mode 100644 index 0000000..4003a3d --- /dev/null +++ b/src/utils/misc/tests/getAllStringOccurences.test.ts @@ -0,0 +1,42 @@ +import { getAllStringOccurrences } from '../getAllStringOccurrences'; + +describe('getAllStringOccurrences', () => { + test('basic functionality with single character', () => { + expect(getAllStringOccurrences('hello world', 'o')).toEqual([4, 7]); + }); + + test('case sensitivity', () => { + expect(getAllStringOccurrences('Hello World', 'o')).toEqual([4, 7]); + expect(getAllStringOccurrences('Hello World', 'O')).toEqual([]); + }); + + test('no occurrences', () => { + expect(getAllStringOccurrences('hello world', 'z')).toEqual([]); + }); + + test('multiple occurrences of substring', () => { + expect(getAllStringOccurrences('banana', 'ana')).toEqual([1, 3]); + }); + + test('overlapping occurrences', () => { + expect(getAllStringOccurrences('aaa', 'aa')).toEqual([0, 1]); + }); + + test('empty search string', () => { + expect(getAllStringOccurrences('hello', '')).toEqual([]); + }); + + test('empty source string', () => { + expect(getAllStringOccurrences('', 'hello')).toEqual([]); + }); + + test('entire string match', () => { + expect(getAllStringOccurrences('test', 'test')).toEqual([0]); + }); + + test('multiple character occurrences', () => { + expect(getAllStringOccurrences('test test test', 'test')).toEqual([ + 0, 5, 10 + ]); + }); +}); diff --git a/src/utils/operations/formatAmount.ts b/src/utils/operations/formatAmount.ts index a2b6213..fddcc44 100644 --- a/src/utils/operations/formatAmount.ts +++ b/src/utils/operations/formatAmount.ts @@ -15,6 +15,7 @@ export interface FormatAmountType { addCommas?: boolean; } +// TODO: Move to utils export function formatAmount({ input, decimals = DECIMALS, diff --git a/src/utils/operations/getUsdValue.ts b/src/utils/operations/getUsdValue.ts new file mode 100644 index 0000000..76bcfb9 --- /dev/null +++ b/src/utils/operations/getUsdValue.ts @@ -0,0 +1,20 @@ +export function getUsdValue({ + amount, + usd, + decimals = 2, + addEqualSign +}: { + amount: string; + usd: number; + decimals?: number; + addEqualSign?: boolean; +}) { + const sum = (parseFloat(amount) * usd).toFixed(decimals); + const formattedValue = parseFloat(sum).toLocaleString('en', { + maximumFractionDigits: decimals, + minimumFractionDigits: decimals + }); + const equalSign = parseFloat(amount) > 0 ? '≈' : '='; + const equalSignPrefix = addEqualSign ? `${equalSign} ` : ''; + return `${equalSignPrefix}$${formattedValue}`; +} diff --git a/src/utils/operations/index.ts b/src/utils/operations/index.ts index 5a1c31e..60fe8df 100644 --- a/src/utils/operations/index.ts +++ b/src/utils/operations/index.ts @@ -1,2 +1,3 @@ export * from './formatAmount'; +export * from './getUsdValue'; export * from './pipe'; diff --git a/src/utils/operations/pipe.ts b/src/utils/operations/pipe.ts index 97f28d0..e752058 100644 --- a/src/utils/operations/pipe.ts +++ b/src/utils/operations/pipe.ts @@ -1,3 +1,4 @@ +// TODO: Move to utils export function pipe(previous: ValueType) { return { if: function (condition: boolean) { diff --git a/src/utils/operations/tests/formatAmount.test.ts b/src/utils/operations/tests/formatAmount.test.ts index 68b187b..6d55119 100644 --- a/src/utils/operations/tests/formatAmount.test.ts +++ b/src/utils/operations/tests/formatAmount.test.ts @@ -1,247 +1,190 @@ import { formatAmount } from '../formatAmount'; -describe('format with 4,4', () => { - const numbers: { [key: string]: string } = { - '9999999999999999999999990000': '999,999,999,999,999,999,999,999', - '0': '0' - }; - const decimals = 4; - const digits = 4; - for (let i = 0; i < Object.keys(numbers).length; i++) { - const input = Object.keys(numbers)[i]; - const output = numbers[input]; - it(`format ${input} -> ${output}`, () => { +describe('formatAmount', () => { + describe('format with 4,4', () => { + test.each([ + ['9999999999999999999999990000', '999,999,999,999,999,999,999,999'], + ['0', '0'] + ])('format %s -> %s', (input, expected) => { const withCommas = formatAmount({ input, - decimals, - digits, + decimals: 4, + digits: 4, showLastNonZeroDecimal: false, addCommas: true }); - expect(withCommas).toBe(output); + expect(withCommas).toBe(expected); }); - } -}); + }); -describe('format with 8,4', () => { - const numbers: { [key: string]: string } = { - '9999999999999999999899996000': '99,999,999,999,999,999,998.9999', - '0': '0', - '10000': '0.0001' - }; - const decimals = 8; - const digits = 4; - for (let i = 0; i < Object.keys(numbers).length; i++) { - const input = Object.keys(numbers)[i]; - const output = numbers[input]; - it(`format ${input} -> ${output}`, () => { + describe('format with 8,4', () => { + test.each([ + ['9999999999999999999899996000', '99,999,999,999,999,999,998.9999'], + ['0', '0'], + ['10000', '0.0001'] + ])('format %s -> %s', (input, expected) => { const withCommas = formatAmount({ input, - decimals, - digits, + decimals: 8, + digits: 4, showLastNonZeroDecimal: false, addCommas: true }); - expect(withCommas).toBe(output); + expect(withCommas).toBe(expected); }); - } -}); + }); -describe('format with 0,0', () => { - const numbers: { [key: string]: string } = { - '350': '350' - }; - const decimals = 0; - const digits = 0; - for (let i = 0; i < Object.keys(numbers).length; i++) { - const input = Object.keys(numbers)[i]; - const output = numbers[input]; - it(`format ${input} -> ${output}`, () => { + describe('format with 0,0', () => { + test.each([['350', '350']])('format %s -> %s', (input, expected) => { const withCommas = formatAmount({ input, - decimals, - digits, + decimals: 0, + digits: 0, showLastNonZeroDecimal: false }); - expect(withCommas).toBe(output); + expect(withCommas).toBe(expected); }); - } -}); + }); -describe('format with 4,8,true', () => { - const numbers: { [key: string]: string } = { - '12345678901234567890123': '123,456,789,012,345.67890123' - }; - const decimals = 8; - const digits = 4; - for (let i = 0; i < Object.keys(numbers).length; i++) { - const input = Object.keys(numbers)[i]; - const output = numbers[input]; - it(`format ${input} -> ${output}`, () => { - const withCommas = formatAmount({ - input, - decimals, - digits, - showLastNonZeroDecimal: true, - addCommas: true - }); - expect(withCommas).toBe(output); - }); - } -}); + describe('format with 4,8,true', () => { + test.each([['12345678901234567890123', '123,456,789,012,345.67890123']])( + 'format %s -> %s', + (input, expected) => { + const withCommas = formatAmount({ + input, + decimals: 8, + digits: 4, + showLastNonZeroDecimal: true, + addCommas: true + }); + expect(withCommas).toBe(expected); + } + ); + }); -describe('format with 18,0,true', () => { - const numbers: { [key: string]: string } = { - '102000000000000000': '0.102', - '100000000000000000': '0.1', - '1000000000000000000': '1' - }; - const decimals = 18; - const digits = 0; - for (let i = 0; i < Object.keys(numbers).length; i++) { - const input = Object.keys(numbers)[i]; - const output = numbers[input]; - it(`format ${input} -> ${output}`, () => { + describe('format with 18,0,true', () => { + test.each([ + ['102000000000000000', '0.102'], + ['100000000000000000', '0.1'], + ['1000000000000000000', '1'] + ])('format %s -> %s', (input, expected) => { const withCommas = formatAmount({ input, - decimals, - digits, + decimals: 18, + digits: 0, showLastNonZeroDecimal: true }); - expect(withCommas).toBe(output); + expect(withCommas).toBe(expected); }); - } -}); + }); -describe('format with float throws error', () => { - const numbers: { [key: string]: string } = { - '0.015': 'Throws error', - '01000000000000000000': 'Throws error' - }; - const decimals = 18; - const digits = 4; - for (let i = 0; i < Object.keys(numbers).length; i++) { - const input = Object.keys(numbers)[i]; - const output = numbers[input]; - it(`format ${input} -> ${output}`, () => { - let err = ''; - try { - formatAmount({ - input, - decimals, - digits, - addCommas: false, - showLastNonZeroDecimal: true - }); - expect(err).toBeInstanceOf(Error); - } catch (error) { - err = error as any; - expect(err).toBeInstanceOf(Error); - expect(error).toHaveProperty('message', 'Invalid input'); + describe('format with float throws error', () => { + test.each([['0.015'], ['01000000000000000000']])( + 'format %s throws error', + (input) => { + expect(() => { + formatAmount({ + input, + decimals: 18, + digits: 4, + addCommas: false, + showLastNonZeroDecimal: true + }); + }).toThrow('Invalid input'); } - }); - } -}); + ); + }); -describe('format with negative', () => { - const numbers: { [key: string]: string } = { - '-922506751086064008': '-0.922506751086064008', - '-578345000000000000000': '-578.3450', - '-1578345000000000000000': '-1,578.3450', - '-3456000000000000000': '-3.4560' - }; - const decimals = 18; - const digits = 4; - for (let i = 0; i < Object.keys(numbers).length; i++) { - const input = Object.keys(numbers)[i]; - const output = numbers[input]; - it(`format ${input} -> ${output}`, () => { + describe('format with negative', () => { + test.each([ + ['-922506751086064008', '-0.922506751086064008'], + ['-578345000000000000000', '-578.3450'], + ['-1578345000000000000000', '-1,578.3450'], + ['-3456000000000000000', '-3.4560'] + ])('format %s -> %s', (input, expected) => { const withCommas = formatAmount({ input, - decimals, - digits, + decimals: 18, + digits: 4, showLastNonZeroDecimal: true, addCommas: true }); - expect(withCommas).toBe(output); + expect(withCommas).toBe(expected); }); - } -}); + }); -describe('format with single tests', () => { - it('should show less than if decimal amount is too low', () => { - const result = formatAmount({ - input: (100_000_000_000_000).toString(), - digits: 2, - showIsLessThanDecimalsLabel: true, - showLastNonZeroDecimal: false + describe('format with single tests', () => { + test('should show less than if decimal amount is too low', () => { + const result = formatAmount({ + input: (100_000_000_000_000).toString(), + digits: 2, + showIsLessThanDecimalsLabel: true, + showLastNonZeroDecimal: false + }); + expect(result).toBe('<0.01'); }); - expect(result).toBe('<0.01'); - }); - it('should not show digits when result is below 1', () => { - const result = formatAmount({ - input: (100_000_000_000_000).toString(), - showLastNonZeroDecimal: false, - digits: 2 + + test('should not show digits when result is below 1', () => { + const result = formatAmount({ + input: (100_000_000_000_000).toString(), + showLastNonZeroDecimal: false, + digits: 2 + }); + expect(result).toBe('0'); }); - expect(result).toBe('0'); - }); - it('should show zero digits for integers with decimal amount too low', () => { - const result = formatAmount({ - input: ['1', '000', '000', '001', '000', '000', '000', '000'].join(''), - digits: 2, - showLastNonZeroDecimal: false + + test('should show zero digits for integers with decimal amount too low', () => { + const result = formatAmount({ + input: ['1', '000', '000', '001', '000', '000', '000', '000'].join(''), + digits: 2, + showLastNonZeroDecimal: false + }); + expect(result).toBe('1000.00'); }); - expect(result).toBe('1000.00'); - }); - it('should show a valid number if showLastNonZeroDecimal is set', () => { - const result = formatAmount({ - input: (1_000_000_000).toString(), - digits: 4, - showLastNonZeroDecimal: true + + test('should show a valid number if showLastNonZeroDecimal is set', () => { + const result = formatAmount({ + input: (1_000_000_000).toString(), + digits: 4, + showLastNonZeroDecimal: true + }); + expect(result).toBe('0.000000001'); }); - expect(result).toBe('0.000000001'); - }); - it('should show remove digits and not add commas', () => { - const result = formatAmount({ - input: '369884288127092846270928', - digits: 4, - showLastNonZeroDecimal: false, - addCommas: false + test('should show remove digits and not add commas', () => { + const result = formatAmount({ + input: '369884288127092846270928', + digits: 4, + showLastNonZeroDecimal: false, + addCommas: false + }); + expect(result).toBe('369884.2881'); }); - expect(result).toBe('369884.2881'); - }); - it('should not add . at the end for 0 digits', () => { - const result = formatAmount({ - input: '369884288127092846270928', - digits: 0, - showLastNonZeroDecimal: false, - addCommas: false + test('should not add . at the end for 0 digits', () => { + const result = formatAmount({ + input: '369884288127092846270928', + digits: 0, + showLastNonZeroDecimal: false, + addCommas: false + }); + expect(result).toBe('369884'); }); - expect(result).toBe('369884'); }); describe('should show all 4 digits', () => { - const numbers: { [key: string]: string } = { - '995000000000000000': '0.9950' - }; - const decimals = 18; - const digits = 4; - for (let i = 0; i < Object.keys(numbers).length; i++) { - const input = Object.keys(numbers)[i]; - const output = numbers[input]; - it(`format ${input} -> ${output}`, () => { + test.each([['995000000000000000', '0.9950']])( + 'format %s -> %s', + (input, expected) => { const withCommas = formatAmount({ input, - decimals, - digits, + decimals: 18, + digits: 4, showLastNonZeroDecimal: true, addCommas: true }); - expect(withCommas).toBe(output); - }); - } + expect(withCommas).toBe(expected); + } + ); }); }); diff --git a/src/utils/operations/tests/getUsdValue.test.ts b/src/utils/operations/tests/getUsdValue.test.ts new file mode 100644 index 0000000..927d6f4 --- /dev/null +++ b/src/utils/operations/tests/getUsdValue.test.ts @@ -0,0 +1,23 @@ +import { getUsdValue } from '../getUsdValue'; + +describe('getUsdValue tests', () => { + it('formats amount', () => { + expect( + getUsdValue({ + amount: '2', + usd: 40, + decimals: 4, + addEqualSign: true + }) + ).toBe('≈ $80.0000'); + }); + it('shows = for 0', () => { + expect( + getUsdValue({ + amount: '0', + usd: 40, + addEqualSign: true + }) + ).toBe('= $0.00'); + }); +}); diff --git a/src/utils/transactions/batch/getBatchTransactionsStatusFromApi.ts b/src/utils/transactions/batch/getBatchTransactionsStatusFromApi.ts index 970ca1e..373d5c9 100644 --- a/src/utils/transactions/batch/getBatchTransactionsStatusFromApi.ts +++ b/src/utils/transactions/batch/getBatchTransactionsStatusFromApi.ts @@ -1,9 +1,9 @@ import axios from 'axios'; import { TRANSACTIONS_BATCH } from 'apiCalls'; -import { BatchTransactionsResponseType } from 'types'; +import { TIMEOUT } from 'constants/index'; import { networkSelector } from 'store/selectors'; import { getState } from 'store/store'; -import { TIMEOUT } from 'constants/index'; +import { BatchTransactionsResponseType } from 'types'; export interface SendBatchTransactionsParamsType { batchId: string; diff --git a/src/utils/transactions/batch/tests/getTransactionsStatus.test.ts b/src/utils/transactions/batch/tests/getTransactionsStatus.test.ts index e497046..349869f 100644 --- a/src/utils/transactions/batch/tests/getTransactionsStatus.test.ts +++ b/src/utils/transactions/batch/tests/getTransactionsStatus.test.ts @@ -1,5 +1,5 @@ -import { getTransactionsStatus } from '../getTransactionsStatus'; import { TransactionServerStatusesEnum } from 'types'; +import { getTransactionsStatus } from '../getTransactionsStatus'; describe('getTransactionsStatus', () => { it('should identify all transactions as successful', () => { diff --git a/src/utils/transactions/batch/tests/sendBatchTransactions.test.ts b/src/utils/transactions/batch/tests/sendBatchTransactions.test.ts deleted file mode 100644 index 54e5edf..0000000 --- a/src/utils/transactions/batch/tests/sendBatchTransactions.test.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { addressSelector } from 'reduxStore/selectors'; -import { store } from 'reduxStore/store'; -import { getWindowLocation } from 'utils/window/getWindowLocation'; -import { sendBatchTransactions } from '../sendBatchTransactions'; -import { signTransactions } from '../operations/signTransactions'; -import { transformTransactionsToSign } from './utils/transformTransactionsToSign'; - -jest.mock('reduxStore/selectors', () => ({ - addressSelector: jest.fn() -})); - -jest.mock('reduxStore/store', () => ({ - store: { - getState: jest.fn() - } -})); - -jest.mock('utils/window/getWindowLocation', () => ({ - getWindowLocation: jest.fn() -})); - -jest.mock('../operations/signTransactions', () => ({ - signTransactions: jest.fn() -})); - -jest.mock('./utils/transformTransactionsToSign', () => ({ - transformTransactionsToSign: jest.fn() -})); - -describe('sendBatchTransactions', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should call all the dependencies and return the expected result', async () => { - const address = - 'erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv'; - const sessionId = '12345'; - const transactions = [ - [ - { - receiver: address, - sender: address, - value: '0', - data: '1' - }, - { - receiver: address, - sender: address, - value: '0', - data: '2' - } - ], - [ - { - receiver: address, - sender: address, - value: '0', - data: '3' - } - ], - [ - { - receiver: address, - sender: address, - value: '0', - data: '4' - }, - { - receiver: address, - sender: address, - value: '0', - data: '5' - }, - { - receiver: address, - sender: address, - value: '0', - data: '6' - } - ] - ]; - const transactionsDisplayInfo = {}; - const callbackRoute = '/callback'; - const minGasLimit = 21000; - - // Mock the dependencies - (addressSelector as unknown as jest.Mock).mockReturnValue(address); - // eslint-disable-next-line @typescript-eslint/no-empty-function - (store.getState as unknown as jest.Mock).mockReturnValue(() => {}); - (getWindowLocation as unknown as jest.Mock).mockReturnValue({ - pathname: callbackRoute - }); - (signTransactions as unknown as jest.Mock).mockResolvedValue({ sessionId }); - (transformTransactionsToSign as unknown as jest.Mock).mockResolvedValue([]); - - const result = await sendBatchTransactions({ - transactions, - transactionsDisplayInfo, - minGasLimit - }); - - expect(addressSelector).toHaveBeenCalled(); - expect(store.getState).toHaveBeenCalled(); - expect(getWindowLocation).toHaveBeenCalled(); - expect(transformTransactionsToSign).toHaveBeenCalledWith({ - transactions: expect.any(Array), - minGasLimit - }); - expect(signTransactions).toHaveBeenCalledWith({ - transactions: expect.any(Array), - minGasLimit, - callbackRoute, - transactionsDisplayInfo, - customTransactionInformation: { - grouping: expect.any(Array), - redirectAfterSign: true, - completedTransactionsDelay: undefined, - sessionInformation: undefined, - skipGuardian: undefined, - signWithoutSending: false - } - }); - - expect(result).toEqual({ - error: undefined, - batchId: `${sessionId}-${address}` - }); - }); - - it('should prepare the grouping field with the indexes from the flat transactions array', async () => { - const address = - 'erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv'; - const sessionId = '12345'; - const transactions = [ - [ - { - receiver: address, - sender: address, - value: '0', - data: '1' - }, - { - receiver: address, - sender: address, - value: '0', - data: '2' - } - ], - [ - { - receiver: address, - sender: address, - value: '0', - data: '3' - } - ], - [ - { - receiver: address, - sender: address, - value: '0', - data: '4' - }, - { - receiver: address, - sender: address, - value: '0', - data: '5' - }, - { - receiver: address, - sender: address, - value: '0', - data: '6' - } - ] - ]; - const transactionsDisplayInfo = {}; // Your test display info - const callbackRoute = '/callback'; - const minGasLimit = 21000; - - // Mock the dependencies - (addressSelector as unknown as jest.Mock).mockReturnValue(address); - (store.getState as unknown as jest.Mock).mockReturnValue({}); - (getWindowLocation as unknown as jest.Mock).mockReturnValue({ - pathname: callbackRoute - }); - (signTransactions as unknown as jest.Mock).mockResolvedValue({ sessionId }); - (transformTransactionsToSign as unknown as jest.Mock).mockResolvedValue( - transactions - ); - - await sendBatchTransactions({ - transactions, - transactionsDisplayInfo, - minGasLimit - }); - - expect(signTransactions).toHaveBeenCalledWith({ - transactions, - minGasLimit, - callbackRoute, - transactionsDisplayInfo, - customTransactionInformation: { - grouping: [[0, 1], [2], [3, 4, 5]], - redirectAfterSign: true, - completedTransactionsDelay: undefined, - sessionInformation: undefined, - skipGuardian: undefined, - signWithoutSending: false - } - }); - }); -}); diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/decodeByMethod.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/decodeByMethod.ts index f0363cd..de1d38d 100644 --- a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/decodeByMethod.ts +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/decodeByMethod.ts @@ -13,9 +13,10 @@ export function decodeByMethod( case DecodeMethodEnum.text: try { return Buffer.from(part, 'hex').toString('utf8'); - } catch {} + } catch { + return part; + } - return part; case DecodeMethodEnum.decimal: return part !== '' ? new BigNumber(part, 16).toString(10) : ''; case DecodeMethodEnum.smart: @@ -25,7 +26,9 @@ export function decodeByMethod( if (addressIsValid(bech32Encoded)) { return bech32Encoded; } - } catch {} + } catch (e) { + console.info('Decoded data is not an address: ', e); + } try { const decoded = Buffer.from(part, 'hex').toString('utf8'); @@ -48,9 +51,9 @@ export function decodeByMethod( } else { return decoded; } - } catch {} - - return part; + } catch { + return part; + } case DecodeMethodEnum.raw: default: return part; diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getSmartDecodedParts.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getSmartDecodedParts.ts index 9989afd..edd57ea 100644 --- a/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getSmartDecodedParts.ts +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/helpers/getSmartDecodedParts.ts @@ -1,4 +1,5 @@ -import { DecodeMethodEnum, TransactionTypesEnum } from 'types'; +import { TransactionTypesEnum } from 'types'; +import { DecodeMethodEnum } from 'types/serverTransactions.types'; import { decodeByMethod } from './decodeByMethod'; interface SmartDecodedPartsType { diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeByMethod.spec.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeByMethod.spec.ts index 581370c..b864943 100644 --- a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeByMethod.spec.ts +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeByMethod.spec.ts @@ -1,7 +1,7 @@ import { Address } from '@multiversx/sdk-core/out'; -import { DecodeMethodEnum } from 'types'; -import { addressIsValid } from 'utils/account/addressIsValid'; +import { DecodeMethodEnum } from 'types/serverTransactions.types'; import { isUtf8 } from 'utils/decoders'; +import { addressIsValid } from 'utils/validation'; import { decodeByMethod } from '../helpers'; jest.mock('@multiversx/sdk-core/out', () => ({ @@ -10,8 +10,8 @@ jest.mock('@multiversx/sdk-core/out', () => ({ } })); -jest.mock('utils/account/addressIsValid'); -jest.mock('utils/decoders'); +jest.mock('utils/validation/addressIsValid'); +jest.mock('utils/decoders/isUtf8'); describe('decodeByMethod', () => { beforeEach(() => { @@ -88,15 +88,16 @@ describe('decodeByMethod', () => { DecodeMethodEnum.smart, mockTokens ); + expect(result).toBe('token1'); }); - it('should convert to decimal when no other conditions met', () => { + it('should return the string unchanged', () => { (Address.fromHex as jest.Mock).mockImplementation(() => { throw new Error(); }); - (isUtf8 as jest.Mock).mockReturnValue(false); + (isUtf8 as jest.Mock).mockReturnValue(false); const result = decodeByMethod('a', DecodeMethodEnum.smart); expect(result).toBe('10'); }); diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts index ff8127d..047b923 100644 --- a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/decodeForDisplay.spec.ts @@ -1,4 +1,7 @@ -import { DecodeMethodEnum, DecodeForDisplayParamsType } from 'types'; +import { + DecodeForDisplayParamsType, + DecodeMethodEnum +} from '../../../../../types/serverTransactions.types'; import { decodeTransactionData } from '../decodeTransactionData'; import { decodeByMethod, diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getDisplayValueAndValidationWarnings.spec.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getDisplayValueAndValidationWarnings.spec.ts index 2617bf4..8a94ac5 100644 --- a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getDisplayValueAndValidationWarnings.spec.ts +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getDisplayValueAndValidationWarnings.spec.ts @@ -1,4 +1,7 @@ -import { DecodeMethodEnum, DecodedDisplayType } from 'types'; +import { + DecodedDisplayType, + DecodeMethodEnum +} from 'types/serverTransactions.types'; import { decodeByMethod, getHexValidationWarnings, diff --git a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getSmartDecodedParts.spec.ts b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getSmartDecodedParts.spec.ts index d4ad1d1..2193220 100644 --- a/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getSmartDecodedParts.spec.ts +++ b/src/utils/transactions/dataDecoders/decodeTransactionData/tests/getSmartDecodedParts.spec.ts @@ -1,4 +1,5 @@ -import { DecodeMethodEnum, TransactionTypesEnum } from 'types'; +import { TransactionTypesEnum } from 'types'; +import { DecodeMethodEnum } from '../../../../../types/serverTransactions.types'; import { decodeByMethod, getSmartDecodedParts } from '../helpers'; jest.mock('../helpers/decodeByMethod'); @@ -34,6 +35,7 @@ describe('getSmartDecodedParts', () => { 'hexValue', DecodeMethodEnum.decimal ); + expect(result).toEqual(['decoded1', 'decoded2', mockDecodedValue]); }); diff --git a/src/utils/transactions/dataDecoders/getTokenFromData.ts b/src/utils/transactions/dataDecoders/getTokenFromData.ts index 104f056..edd711a 100644 --- a/src/utils/transactions/dataDecoders/getTokenFromData.ts +++ b/src/utils/transactions/dataDecoders/getTokenFromData.ts @@ -1,7 +1,7 @@ import { Address } from '@multiversx/sdk-core'; import BigNumber from 'bignumber.js'; -import { decodePart } from '../../decoders'; import { TransactionTypesEnum } from '../../../types'; +import { decodePart } from '../../decoders'; import { addressIsValid } from '../../validation'; const noData = { @@ -74,7 +74,9 @@ export function getTokenFromData(data?: string): { receiver: new Address(receiver).bech32() }; } - } catch (err) {} + } catch (e) { + console.error('Error getting NFT from transaction data', e); + } } if (isNftBurn) { @@ -88,7 +90,9 @@ export function getTokenFromData(data?: string): { nonce }; } - } catch (err) {} + } catch (e) { + console.error('Error getting NFT from transaction data', e); + } } return noData; diff --git a/src/utils/transactions/index.ts b/src/utils/transactions/index.ts index 636aa12..4780a49 100644 --- a/src/utils/transactions/index.ts +++ b/src/utils/transactions/index.ts @@ -2,5 +2,4 @@ export * from './batch'; export * from './dataDecoders'; export * from './operations'; export * from './parsers'; -export * from './url'; export * from './validation'; diff --git a/src/utils/transactions/operations/calculateFeeInFiat.ts b/src/utils/transactions/operations/calculateFeeInFiat.ts new file mode 100644 index 0000000..bfeabf5 --- /dev/null +++ b/src/utils/transactions/operations/calculateFeeInFiat.ts @@ -0,0 +1,33 @@ +import { DIGITS, DECIMALS } from 'constants/index'; +import { formatAmount, getUsdValue } from '../../operations'; + +export interface CalculateFeeInFiatParamsType { + feeLimit: string; + egldPriceInUsd: number; + hideEqualSign?: boolean; +} + +export function calculateFeeInFiat({ + feeLimit, + egldPriceInUsd, + hideEqualSign +}: CalculateFeeInFiatParamsType) { + const amount = formatAmount({ + input: feeLimit, + decimals: DECIMALS, + digits: DIGITS, + showLastNonZeroDecimal: true + }); + + const feeAsUsdValue = getUsdValue({ + amount, + usd: egldPriceInUsd, + decimals: DIGITS + }); + + if (hideEqualSign) { + return feeAsUsdValue; + } + + return `≈ ${feeAsUsdValue}`; +} diff --git a/src/utils/transactions/operations/calculateFeeLimit.ts b/src/utils/transactions/operations/calculateFeeLimit.ts new file mode 100755 index 0000000..81a14fb --- /dev/null +++ b/src/utils/transactions/operations/calculateFeeLimit.ts @@ -0,0 +1,76 @@ +import { + Transaction, + TransactionPayload, + TransactionVersion, + Address, + TokenPayment +} from '@multiversx/sdk-core'; +import BigNumber from 'bignumber.js'; +import { + EXTRA_GAS_LIMIT_GUARDED_TX, + GAS_LIMIT, + GAS_PRICE, + ZERO +} from 'constants/index'; +import { stringIsFloat, stringIsInteger } from 'utils/validation'; +import { isGuardianTx } from '../validation'; + +export interface CalculateFeeLimitType { + gasLimit: string; + gasPrice: string; + data: string; + gasPerDataByte: string; + gasPriceModifier: string; + chainId: string; + minGasLimit?: string; + defaultGasPrice?: string; +} +const placeholderData = { + from: 'erd12dnfhej64s6c56ka369gkyj3hwv5ms0y5rxgsk2k7hkd2vuk7rvqxkalsa', + to: 'erd12dnfhej64s6c56ka369gkyj3hwv5ms0y5rxgsk2k7hkd2vuk7rvqxkalsa' +}; +export function calculateFeeLimit({ + minGasLimit = String(GAS_LIMIT), + gasLimit, + gasPrice, + data: inputData, + gasPerDataByte, + gasPriceModifier, + defaultGasPrice = String(GAS_PRICE), + chainId +}: CalculateFeeLimitType) { + const data = inputData || ''; + const validGasLimit = stringIsInteger(gasLimit) ? gasLimit : minGasLimit; + + // We need to add extra gas fee for guardian transactions + const extraGasLimit = isGuardianTx({ data }) ? EXTRA_GAS_LIMIT_GUARDED_TX : 0; + const usedGasLimit = new BigNumber(validGasLimit) + .plus(extraGasLimit) + .toNumber(); + + const validGasPrice = stringIsFloat(gasPrice) ? gasPrice : defaultGasPrice; + const transaction = new Transaction({ + nonce: 0, + value: TokenPayment.egldFromAmount('0'), + receiver: new Address(placeholderData.to), + sender: new Address(placeholderData.to), + gasPrice: parseInt(validGasPrice), + gasLimit: usedGasLimit, + data: new TransactionPayload(data.trim()), + chainID: chainId, + version: new TransactionVersion(1) + }); + + try { + const bNfee = transaction.computeFee({ + GasPerDataByte: parseInt(gasPerDataByte), + MinGasLimit: parseInt(minGasLimit), + GasPriceModifier: parseFloat(gasPriceModifier), + ChainID: chainId + }); + return bNfee.toString(10); + } catch (err) { + console.error(err); + return ZERO; + } +} diff --git a/src/utils/transactions/calculateTotalTransactionsFee.ts b/src/utils/transactions/operations/calculateTotalTransactionsFee.ts similarity index 93% rename from src/utils/transactions/calculateTotalTransactionsFee.ts rename to src/utils/transactions/operations/calculateTotalTransactionsFee.ts index a4d2fdb..21a785b 100644 --- a/src/utils/transactions/calculateTotalTransactionsFee.ts +++ b/src/utils/transactions/operations/calculateTotalTransactionsFee.ts @@ -1,7 +1,7 @@ import { Transaction } from '@multiversx/sdk-core/out'; import BigNumber from 'bignumber.js'; import { GAS_PER_DATA_BYTE, GAS_PRICE_MODIFIER } from 'constants/index'; -import { calculateFeeLimit } from './operations'; +import { calculateFeeLimit } from './calculateFeeLimit'; export function calcTotalFee(transactions: Transaction[], minGasLimit: number) { let totalFee = new BigNumber(0); diff --git a/src/utils/transactions/operations/index.ts b/src/utils/transactions/operations/index.ts new file mode 100644 index 0000000..ca95564 --- /dev/null +++ b/src/utils/transactions/operations/index.ts @@ -0,0 +1,2 @@ +export * from './calculateFeeLimit'; +export * from './calculateTotalTransactionsFee'; diff --git a/src/utils/transactions/operations/tests/calculateFeeInFiat.test.ts b/src/utils/transactions/operations/tests/calculateFeeInFiat.test.ts new file mode 100644 index 0000000..1d8257a --- /dev/null +++ b/src/utils/transactions/operations/tests/calculateFeeInFiat.test.ts @@ -0,0 +1,11 @@ +import { calculateFeeInFiat } from '../calculateFeeInFiat'; + +describe('calculateFeeInFiat tests', () => { + it('computes correct fee in fiat', () => { + const fee = calculateFeeInFiat({ + feeLimit: '50000000000000', + egldPriceInUsd: 135.78 + }); + expect(fee).toBe('≈ $0.0068'); + }); +}); diff --git a/src/utils/transactions/operations/tests/calculateFeeLimit.test.ts b/src/utils/transactions/operations/tests/calculateFeeLimit.test.ts new file mode 100644 index 0000000..415ffb8 --- /dev/null +++ b/src/utils/transactions/operations/tests/calculateFeeLimit.test.ts @@ -0,0 +1,96 @@ +import { GAS_PER_DATA_BYTE, GAS_PRICE_MODIFIER } from 'constants/index'; +import { calculateFeeLimit } from '../calculateFeeLimit'; + +describe('calculateFeeLimit tests', () => { + it('computes correct fee', () => { + const feeLimit = calculateFeeLimit({ + gasLimit: '62000', + gasPrice: '1000000000', + data: 'testdata', + chainId: 'T', + gasPerDataByte: String(GAS_PER_DATA_BYTE), + gasPriceModifier: String(GAS_PRICE_MODIFIER) + }); + expect(feeLimit).toBe('62000000000000'); + }); + + it('computes correct fee for data field with more parameters', () => { + const feeLimit = calculateFeeLimit({ + gasLimit: '11100000', + gasPrice: '1000000000', + data: 'bid@0d59@43525a502d333663366162@25', + gasPerDataByte: String(GAS_PER_DATA_BYTE), + gasPriceModifier: String(GAS_PRICE_MODIFIER), + defaultGasPrice: '1000000000', + chainId: 'T' + }); + + expect(feeLimit).toBe('210990000000000'); + }); + + it('computes correct fee for SetGuardian tx', () => { + const feeLimit = calculateFeeLimit({ + gasLimit: '', + gasPrice: (1_000_000).toString(), + data: 'SetGuardian@qwerty@12345', + chainId: 'T', + gasPerDataByte: '1', + gasPriceModifier: '1' + }); + + expect(feeLimit).toBe((100_000_000_000).toString()); // (minGasLimit + extra guardian gas) * gasPrice + }); + + it('computes correct fee for GuardAccount tx', () => { + const feeLimit = calculateFeeLimit({ + gasLimit: '', + gasPrice: (1_000_000).toString(), + data: 'GuardAccount@qwerty@12345', + chainId: 'T', + gasPerDataByte: '1', + gasPriceModifier: '1' + }); + + expect(feeLimit).toBe((100_000_000_000).toString()); // (minGasLimit + extra guardian gas) * gasPrice + }); + + it('computes correct fee for UnGuardAccount tx', () => { + const feeLimit = calculateFeeLimit({ + gasLimit: '', + gasPrice: (1_000_000).toString(), + data: 'UnGuardAccount@qwerty@12345', + chainId: 'T', + gasPerDataByte: '1', + gasPriceModifier: '1' + }); + + expect(feeLimit).toBe((100_000_000_000).toString()); // (minGasLimit + extra guardian gas) * gasPrice + }); + + it('computes correct fee for UnGuardAccount tx and gas limit specified', () => { + const feeLimit = calculateFeeLimit({ + gasLimit: (1_000_000).toString(), + gasPrice: (1_000_000).toString(), + data: 'UnGuardAccount@qwerty@12345', + chainId: 'T', + gasPerDataByte: '1', + gasPriceModifier: '1' + }); + + expect(feeLimit).toBe((1_050_000_000_000).toString()); // (gasLimit + extra guardian gas) * gasPrice + }); + + it('computes correct fee for UnGuardAccount tx and min gas limit specified', () => { + const feeLimit = calculateFeeLimit({ + gasLimit: '', + minGasLimit: (1_000_000).toString(), + gasPrice: (1_000_000).toString(), + data: 'UnGuardAccount@qwerty@12345', + chainId: 'T', + gasPerDataByte: '1', + gasPriceModifier: '1' + }); + + expect(feeLimit).toBe((1_050_000_000_000).toString()); // (minGasLimit + extra guardian gas) * gasPrice + }); +}); diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/constants.ts b/src/utils/transactions/parsers/getInterpretedTransaction/constants.ts index 2b7231b..20dd6f6 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/constants.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/constants.ts @@ -1,4 +1,4 @@ -import { TransactionActionsEnum } from 'types'; +import { TransactionActionsEnum } from 'types/serverTransactions.types'; /** * If `action.name` or `function` is in `ACTIONS_WITH_MANDATORY_OPERATIONS[]`, transaction value will be computed based `operations` field diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/getInterpretedTransaction.ts b/src/utils/transactions/parsers/getInterpretedTransaction/getInterpretedTransaction.ts index c8039ea..2b106a6 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/getInterpretedTransaction.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/getInterpretedTransaction.ts @@ -1,6 +1,8 @@ -import { InterpretedTransactionType, ServerTransactionType } from 'types'; -import { TokenArgumentType } from 'types'; -import { isContract } from '../../dataDecoders/smartContractTransaction'; +import { + InterpretedTransactionType, + ServerTransactionType, + TokenArgumentType +} from 'types/serverTransactions.types'; import { getTokenFromData } from 'utils/transactions/dataDecoders/getTokenFromData'; import { getExplorerLink, @@ -11,6 +13,7 @@ import { getTransactionTransferType, explorerUrlBuilder } from './helpers'; +import { isContract } from '../../dataDecoders'; export interface GetInterpretedTransactionType { address: string; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts index 37c598f..5b2e50f 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionMethod.ts @@ -2,7 +2,7 @@ import { TransactionActionCategoryEnum, TransactionActionsEnum, ServerTransactionType -} from 'types'; +} from 'types/serverTransactions.types'; export function getTransactionMethod(transaction: ServerTransactionType) { let transactionAction = 'Transaction'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTransferType.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTransferType.ts index f08d8d8..775bdf9 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTransferType.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionTransferType.ts @@ -2,7 +2,7 @@ import { ServerTransactionType, TransferTypeEnum, TransactionDirectionEnum -} from 'types'; +} from 'types/serverTransactions.types'; export function getTransactionTransferType( address: string, diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts index a58b467..57762e1 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/getTransactionValue.ts @@ -1,12 +1,6 @@ import { DECIMALS } from 'constants/index'; -import { - ACTIONS_WITH_EGLD_VALUE, - ACTIONS_WITH_MANDATORY_OPERATIONS, - ACTIONS_WITH_VALUE_IN_ACTION_FIELD, - ACTIONS_WITH_VALUE_IN_DATA_FIELD -} from '../../constants'; - +import { NftEnumType, InterpretedTransactionType } from 'types'; import { getValueFromActions, getValueFromDataField, @@ -14,15 +8,20 @@ import { getEgldValueData, getTitleText } from './helpers'; +import { getTransactionActionNftText } from '../../../getTransactionActionNftText'; +import { getTransactionActionTokenText } from '../../../getTransactionActionTokenText'; +import { + ACTIONS_WITH_EGLD_VALUE, + ACTIONS_WITH_MANDATORY_OPERATIONS, + ACTIONS_WITH_VALUE_IN_ACTION_FIELD, + ACTIONS_WITH_VALUE_IN_DATA_FIELD +} from '../../constants'; +import { getTransactionTokens } from '../getTransactionTokens'; import { EgldValueDataType, NFTValueDataType, TokenValueDataType } from '../types'; -import { NftEnumType, InterpretedTransactionType } from 'types'; -import { getTransactionTokens } from '../getTransactionTokens'; -import { getTransactionActionNftText } from '../../../getTransactionActionNftText'; -import { getTransactionActionTokenText } from '../../../getTransactionActionTokenText'; export interface GetTransactionValueReturnType { egldValueData?: EgldValueDataType; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts index 5e0ebf3..1d908ec 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getTitleText.ts @@ -1,12 +1,12 @@ +import { TokenArgumentType } from 'types'; +import { getIdentifierType } from 'utils/validation'; +import { getTransactionActionNftText } from '../../../../getTransactionActionNftText'; +import { getTransactionActionTokenText } from '../../../../getTransactionActionTokenText'; import { EgldValueDataType, NFTValueDataType, TokenValueDataType } from '../../types'; -import { TokenArgumentType } from 'types'; -import { getIdentifierType } from 'utils'; -import { getTransactionActionNftText } from '../../../../getTransactionActionNftText'; -import { getTransactionActionTokenText } from '../../../../getTransactionActionTokenText'; export interface GetTransactionValueReturnType { egldValueData?: EgldValueDataType; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromDataField.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromDataField.ts index 2cbb266..c63d053 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromDataField.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromDataField.ts @@ -1,5 +1,5 @@ import BigNumber from 'bignumber.js'; -import { InterpretedTransactionType } from 'types'; +import { InterpretedTransactionType } from 'types/serverTransactions.types'; import { decodeBase64 } from 'utils/decoders'; import { getEgldValueData } from './getEgldValueData'; @@ -13,7 +13,7 @@ export function getValueFromDataField(transaction: InterpretedTransactionType) { if (!value.isNaN()) { return getEgldValueData(value.toString(10)); } - } catch (err) { + } catch { if (!warningLogged) { console.error( `Unable to extract value for txHash: ${transaction.txHash}` diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromOperations.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromOperations.ts index 043bad0..8bae791 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromOperations.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/helpers/getValueFromOperations.ts @@ -1,4 +1,4 @@ -import { InterpretedTransactionType } from 'types'; +import { InterpretedTransactionType } from 'types/serverTransactions.types'; import { getEgldValueData } from './getEgldValueData'; import { getVisibleOperations } from '../../../../getVisibleOperations'; @@ -24,8 +24,9 @@ export function getValueFromOperations( } else { logError(transaction.txHash); } - } catch (err) { + } catch { logError(transaction.txHash); } + return getEgldValueData(transaction.value); } diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/tests/getTransactionValue.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/tests/getTransactionValue.test.ts index 6409b1c..fbc7b4b 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/tests/getTransactionValue.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getTransactionValue/tests/getTransactionValue.test.ts @@ -1,11 +1,12 @@ -import { NftEnumType, TransactionActionsEnum } from 'types'; import { DECIMALS } from 'constants/index'; -import { getTransactionValue } from '../getTransactionValue'; +import { NftEnumType } from 'types'; +import { TransactionActionsEnum } from 'types/serverTransactions.types'; import { ACTIONS_WITH_EGLD_VALUE, ACTIONS_WITH_MANDATORY_OPERATIONS, ACTIONS_WITH_VALUE_IN_DATA_FIELD } from '../../../constants'; +import { getTransactionValue } from '../getTransactionValue'; describe('getTransactionValue', () => { const baseTransaction = { @@ -74,7 +75,7 @@ describe('getTransactionValue', () => { test('handles NFT transactions', () => { const mockNftToken = { - type: NftEnumType.NonFungibleESDT, + type: NftEnumType.SemiFungibleESDT, value: '1', decimals: 0, identifier: 'TEST-123-01' @@ -86,13 +87,9 @@ describe('getTransactionValue', () => { ...baseAction, category: 'nft', arguments: { - tokenId: mockNftToken.identifier, - amount: mockNftToken.value + token: mockNftToken } - }, - tokens: [mockNftToken], - tokenIdentifier: mockNftToken.identifier, - tokenValue: mockNftToken.value + } }; const result = getTransactionValue({ transaction }); @@ -117,13 +114,9 @@ describe('getTransactionValue', () => { ...baseAction, category: 'esdt', arguments: { - tokenId: mockToken.identifier, - amount: mockToken.value + token: mockToken } }, - tokens: [mockToken], - tokenIdentifier: mockToken.identifier, - tokenValue: mockToken.value, function: 'transfer', operations: [], results: [] @@ -143,26 +136,33 @@ describe('getTransactionValue', () => { type: 'FungibleESDT', value: '1000000000000000000', decimals: 18, - identifier: 'TEST-123' + identifier: 'TEST-123', + amount: '1000000000000000000', + tokenId: 'TEST-123' }, { type: 'FungibleESDT', value: '2000000000000000000', decimals: 18, - identifier: 'TEST-456' + identifier: 'TEST-456', + amount: '2000000000000000000', + tokenId: 'TEST-456' } ]; + const transfers = mockTokens.map((token) => ({ + ...token, + tokenId: token.identifier, + amount: token.value + })); + const transaction = { ...baseTransaction, action: { ...baseAction, category: 'multiToken', arguments: { - tokens: mockTokens.map((token) => ({ - tokenId: token.identifier, - amount: token.value - })) + transfers } }, tokens: mockTokens @@ -171,7 +171,7 @@ describe('getTransactionValue', () => { const result = getTransactionValue({ transaction }); expect(result.tokenValueData?.titleText).toBeDefined(); - expect(result.tokenValueData?.transactionTokens).toEqual(mockTokens); + expect(result.tokenValueData?.transactionTokens).toEqual(transfers); }); test('handles actions with value in data field', () => { diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/base-transaction-mock.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/base-transaction-mock.ts index 98a406f..23c9285 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/base-transaction-mock.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/base-transaction-mock.ts @@ -1,4 +1,4 @@ -import { TransactionActionsEnum } from 'types'; +import { TransactionActionsEnum } from 'types/serverTransactions.types'; export const baseTransactionMock = { blockHash: '', diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getOperationsMessages.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getOperationsMessages.test.ts index 0e68bd1..bda15cf 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getOperationsMessages.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getOperationsMessages.test.ts @@ -2,7 +2,7 @@ import { OperationType, TransactionOperationActionTypeEnum, VisibleTransactionOperationType -} from 'types'; +} from 'types/serverTransactions.types'; import { getOperationsMessages } from '../getOperationsMessages'; import { baseTransactionMock } from './base-transaction-mock'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionMethod.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionMethod.test.ts index c3ce93b..541e5ce 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionMethod.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionMethod.test.ts @@ -2,12 +2,12 @@ import { ServerTransactionType, TransactionActionCategoryEnum, TransactionActionsEnum -} from 'types'; +} from 'types/serverTransactions.types'; import { getTransactionMethod } from '../getTransactionMethod'; import { baseTransactionMock } from './base-transaction-mock'; describe('getTransactionMethod', () => { - it('returns default value "Transaction" in case of missing "action" field ', () => { + it('returns default value "Transaction" in case of missing "action" field', () => { const transaction: ServerTransactionType = { ...baseTransactionMock, action: undefined diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiver.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiver.test.ts index 242e1ac..60de128 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiver.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionReceiver.test.ts @@ -2,7 +2,7 @@ import { ServerTransactionType, TransactionActionCategoryEnum, TransactionActionsEnum -} from 'types'; +} from 'types/serverTransactions.types'; import { getTransactionReceiver } from '../getTransactionReceiver'; import { baseTransactionMock } from './base-transaction-mock'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTokens.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTokens.test.ts index ab24f1b..cdbce40 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTokens.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTokens.test.ts @@ -2,7 +2,7 @@ import { ServerTransactionType, TransactionActionCategoryEnum, TransactionActionsEnum -} from 'types'; +} from 'types/serverTransactions.types'; import { getTransactionTokens } from '../getTransactionTokens'; import { baseTransactionMock } from './base-transaction-mock'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTransferType.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTransferType.test.ts index 2d3ab66..a078f62 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTransferType.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getTransactionTransferType.test.ts @@ -1,5 +1,8 @@ -import { TransactionDirectionEnum } from 'types'; -import { ServerTransactionType, TransferTypeEnum } from 'types'; +import { + ServerTransactionType, + TransactionDirectionEnum, + TransferTypeEnum +} from 'types/serverTransactions.types'; import { getTransactionTransferType } from '../getTransactionTransferType'; import { baseTransactionMock } from './base-transaction-mock'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/tests/extended-transaction-mock.ts b/src/utils/transactions/parsers/getInterpretedTransaction/tests/extended-transaction-mock.ts index d74738b..33dece5 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/tests/extended-transaction-mock.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/tests/extended-transaction-mock.ts @@ -4,7 +4,7 @@ import { TransactionActionCategoryEnum, TransactionActionsEnum, VisibleTransactionOperationType -} from 'types'; +} from 'types/serverTransactions.types'; import { EsdtEnumType } from 'types/tokens.types'; import { baseTransactionMock } from '../helpers/tests/base-transaction-mock'; diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/tests/getInterpretedTransaction.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/tests/getInterpretedTransaction.test.ts index fe4a4d9..14fabd6 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/tests/getInterpretedTransaction.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/tests/getInterpretedTransaction.test.ts @@ -1,8 +1,11 @@ -import { InterpretedTransactionType, TransactionDirectionEnum } from 'types'; -import { ServerTransactionType, TransactionActionsEnum } from 'types'; +import { + ServerTransactionType, + TransactionActionsEnum, + InterpretedTransactionType, + TransactionDirectionEnum +} from 'types/serverTransactions.types'; import { getInterpretedTransaction } from '../getInterpretedTransaction'; - -import { explorerUrlBuilder } from '../helpers/explorerUrlBuilder'; +import { explorerUrlBuilder } from '../helpers'; import { transactionMock } from './extended-transaction-mock'; diff --git a/src/utils/transactions/parsers/getTransactionLink.ts b/src/utils/transactions/parsers/getTransactionLink.ts index e208ffe..28584e1 100644 --- a/src/utils/transactions/parsers/getTransactionLink.ts +++ b/src/utils/transactions/parsers/getTransactionLink.ts @@ -1,9 +1,9 @@ -import { explorerAddressSelector } from 'reduxStore/selectors'; -import { store } from 'reduxStore/store'; +import { explorerAddressSelector } from 'store/selectors'; +import { getState } from 'store/store'; export function getTransactionLink( transactionHash: string, - explorerAddress: string = explorerAddressSelector(store.getState()) + explorerAddress: string = explorerAddressSelector(getState()) ) { return `${explorerAddress}/transactions/${transactionHash}`; } diff --git a/src/utils/transactions/parsers/getTransactionMessages.ts b/src/utils/transactions/parsers/getTransactionMessages.ts index fafdaa1..cf8a528 100644 --- a/src/utils/transactions/parsers/getTransactionMessages.ts +++ b/src/utils/transactions/parsers/getTransactionMessages.ts @@ -1,7 +1,9 @@ -import { InterpretedTransactionType } from 'types'; -import { getOperationsMessages } from '../getInterpretedTransaction/helpers/getOperationsMessages'; -import { getReceiptMessage } from '../getInterpretedTransaction/helpers/getReceiptMessage'; -import getScResultsMessages from '../getInterpretedTransaction/helpers/getScResultsMessages'; +import { InterpretedTransactionType } from 'types/serverTransactions.types'; +import { + getOperationsMessages, + getReceiptMessage +} from './getInterpretedTransaction'; +import getScResultsMessages from './getInterpretedTransaction/helpers/getScResultsMessages'; export function getTransactionMessages( transaction: InterpretedTransactionType diff --git a/src/utils/transactions/parsers/getTransactionStatus.ts b/src/utils/transactions/parsers/getTransactionStatus.ts index 315380b..73be8a4 100644 --- a/src/utils/transactions/parsers/getTransactionStatus.ts +++ b/src/utils/transactions/parsers/getTransactionStatus.ts @@ -1,5 +1,5 @@ import { TransactionServerStatusesEnum } from 'types/enums.types'; -import { InterpretedTransactionType } from 'types'; +import { InterpretedTransactionType } from 'types/serverTransactions.types'; export function getTransactionStatus(transaction: InterpretedTransactionType) { const statusIs = (compareTo: string) => diff --git a/src/utils/transactions/parsers/getTransactionStatusText.ts b/src/utils/transactions/parsers/getTransactionStatusText.ts index cc9d4b0..97062bb 100644 --- a/src/utils/transactions/parsers/getTransactionStatusText.ts +++ b/src/utils/transactions/parsers/getTransactionStatusText.ts @@ -1,5 +1,5 @@ import { TransactionServerStatusesEnum } from 'types/enums.types'; -import { InterpretedTransactionType } from 'types'; +import { InterpretedTransactionType } from 'types/serverTransactions.types'; export function getTransactionStatusText( transaction: InterpretedTransactionType diff --git a/src/utils/transactions/parsers/getTransactionsDetails.ts b/src/utils/transactions/parsers/getTransactionsDetails.ts index e79aa18..88b1ede 100644 --- a/src/utils/transactions/parsers/getTransactionsDetails.ts +++ b/src/utils/transactions/parsers/getTransactionsDetails.ts @@ -1,5 +1,7 @@ -import { getTransactionByHashPromise } from 'apiCalls'; -import { ServerTransactionType } from 'types'; +import { TransactionStatus } from '@multiversx/sdk-core/out'; +import { getTransactionByHash } from 'apiCalls'; +import { ServerTransactionType } from 'types/serverTransactions.types'; +import { sleep } from '../../asyncActions'; export async function getTransactionsDetails(txHashes: string[]) { const delayMs = 3000; @@ -12,10 +14,10 @@ export async function getTransactionsDetails(txHashes: string[]) { while (transactions === undefined && retries > 0) { try { - await delayWithPromise(delayMs); + await sleep(delayMs); const promiseResponse = await Promise.allSettled( - txHashes.map((hash) => getTransactionByHashPromise(hash)) + txHashes.map((hash) => getTransactionByHash(hash)) ); const apiTransactions = promiseResponse @@ -43,7 +45,7 @@ export async function getTransactionsDetails(txHashes: string[]) { } else { retries -= 1; } - } catch (e) { + } catch { retries -= 1; } } diff --git a/src/utils/transactions/parsers/getVisibleOperations.ts b/src/utils/transactions/parsers/getVisibleOperations.ts index 32bb1b8..650dd2c 100644 --- a/src/utils/transactions/parsers/getVisibleOperations.ts +++ b/src/utils/transactions/parsers/getVisibleOperations.ts @@ -1,7 +1,7 @@ import { InterpretedTransactionType, VisibleTransactionOperationType -} from 'types'; +} from 'types/serverTransactions.types'; export function getVisibleOperations(transaction: InterpretedTransactionType) { const operations = diff --git a/src/utils/transactions/parsers/parseMultiEsdtTransferData.ts b/src/utils/transactions/parsers/parseMultiEsdtTransferData.ts index 8a5b668..8b6b798 100644 --- a/src/utils/transactions/parsers/parseMultiEsdtTransferData.ts +++ b/src/utils/transactions/parsers/parseMultiEsdtTransferData.ts @@ -1,17 +1,23 @@ import BigNumber from 'bignumber.js'; import { MultiEsdtTransactionType, TransactionTypesEnum } from 'types'; import { decodePart } from 'utils/decoders/decodePart'; -import { getAllStringOccurrences } from '../getAllStringOccurrences'; +import { getAllStringOccurrences } from '../../misc'; export function parseMultiEsdtTransferData(data?: string) { const transactions: MultiEsdtTransactionType[] = []; + + if (!data) { + return transactions; + } + let contractCallDataIndex = 0; + try { if ( - data?.startsWith(TransactionTypesEnum.MultiESDTNFTTransfer) && - data?.includes('@') + data.startsWith(TransactionTypesEnum.MultiESDTNFTTransfer) && + data.includes('@') ) { - const [, receiver, encodedTxCount, ...rest] = data?.split('@'); + const [, receiver, encodedTxCount, ...rest] = data.split('@'); if (receiver) { const txCount = new BigNumber(encodedTxCount, 16).toNumber(); @@ -90,7 +96,7 @@ export function parseMultiEsdtTransferData(data?: string) { } } catch (err) { console.error('failed parsing tx', err); - return transactions; } + return transactions; } diff --git a/src/utils/transactions/parsers/parseMultiEsdtTransferDataForMultipleTransactions.ts b/src/utils/transactions/parsers/parseMultiEsdtTransferDataForMultipleTransactions.ts index b171748..c25497f 100644 --- a/src/utils/transactions/parsers/parseMultiEsdtTransferDataForMultipleTransactions.ts +++ b/src/utils/transactions/parsers/parseMultiEsdtTransferDataForMultipleTransactions.ts @@ -1,4 +1,5 @@ import type { Transaction } from '@multiversx/sdk-core'; + import { MultiSignTransactionType, TransactionDataTokenType, diff --git a/src/utils/transactions/parsers/parseTransactionAfterSigning.ts b/src/utils/transactions/parsers/parseTransactionAfterSigning.ts index ecf82fa..e527229 100644 --- a/src/utils/transactions/parsers/parseTransactionAfterSigning.ts +++ b/src/utils/transactions/parsers/parseTransactionAfterSigning.ts @@ -1,9 +1,8 @@ -import { Transaction } from '@multiversx/sdk-core/out'; +import { Transaction, TransactionPayload } from '@multiversx/sdk-core/out'; import { PlainSignedTransaction } from '@multiversx/sdk-web-wallet-provider/out/plainSignedTransaction'; -import { newTransaction } from 'models'; import { SignedTransactionType } from 'types'; import { TransactionServerStatusesEnum } from 'types/enums.types'; -import { isGuardianTx } from '../validation/isGuardianTx'; +import { isGuardianTx } from '../validation'; export function parseTransactionAfterSigning( signedTransaction: Transaction | PlainSignedTransaction @@ -11,9 +10,17 @@ export function parseTransactionAfterSigning( const isComplexTransaction = Object.getPrototypeOf(signedTransaction).toPlainObject != null; + const plainSignedTx = signedTransaction as PlainSignedTransaction; + const encoder = new TextEncoder(); + const transaction = isComplexTransaction ? (signedTransaction as Transaction) - : newTransaction(signedTransaction as PlainSignedTransaction); + : new Transaction({ + ...plainSignedTx, + data: new TransactionPayload(plainSignedTx.data), + signature: encoder.encode(plainSignedTx.signature), + guardianSignature: encoder.encode(plainSignedTx.guardianSignature) + }); const parsedTransaction: SignedTransactionType = { ...transaction.toPlainObject(), diff --git a/src/utils/transactions/parsers/tests/getOperationsDetails.test.ts b/src/utils/transactions/parsers/tests/getOperationsDetails.test.ts index 19eef2d..030b1db 100644 --- a/src/utils/transactions/parsers/tests/getOperationsDetails.test.ts +++ b/src/utils/transactions/parsers/tests/getOperationsDetails.test.ts @@ -5,7 +5,7 @@ import { TransactionActionsEnum, TransactionOperationActionTypeEnum, VisibleTransactionOperationType -} from 'types'; +} from 'types/serverTransactions.types'; import { NftEnumType } from 'types/tokens.types'; import { getOperationsDetails } from '../getOperationsDetails'; @@ -187,29 +187,25 @@ describe('getOperationsDetails tests', () => { expect(getOperationsDetails({ transaction: TRANSACTION }).length).toBe(4); }); - test('returns operations without hidden operation type and with create action type', () => { + test('returns operations filtered by create action type', () => { expect( getOperationsDetails({ transaction: TRANSACTION, - filterBy: { - action: TransactionOperationActionTypeEnum.create - } + filterBy: { action: TransactionOperationActionTypeEnum.create } }).length ).toBe(3); }); - test('returns operations without hidden operation type and with transfer action type', () => { + test('returns operations filtered by transfer action type', () => { expect( getOperationsDetails({ transaction: TRANSACTION, - filterBy: { - action: TransactionOperationActionTypeEnum.transfer - } + filterBy: { action: TransactionOperationActionTypeEnum.transfer } }).length ).toBe(1); }); - test('returns operations without hidden operation type and with transfer action type', () => { + test('returns operations filtered by transfer action type and receiver', () => { expect( getOperationsDetails({ transaction: TRANSACTION, @@ -221,7 +217,7 @@ describe('getOperationsDetails tests', () => { ).toBe(1); }); - test('returns operations without hidden operation type and with create action type', () => { + test('returns operations filtered by create action type and receiver', () => { expect( getOperationsDetails({ transaction: TRANSACTION, @@ -233,7 +229,7 @@ describe('getOperationsDetails tests', () => { ).toBe(0); }); - test('returns operations without hidden operation type and with create action type, sender and receiver', () => { + test('returns operations filtered by create action type with matching sender and receiver', () => { expect( getOperationsDetails({ transaction: TRANSACTION, diff --git a/src/utils/transactions/parsers/tests/getTransactionLinkWithLabel.test.ts b/src/utils/transactions/parsers/tests/getTransactionLinkWithLabel.test.ts index d1a8217..4b77dbc 100644 --- a/src/utils/transactions/parsers/tests/getTransactionLinkWithLabel.test.ts +++ b/src/utils/transactions/parsers/tests/getTransactionLinkWithLabel.test.ts @@ -1,6 +1,9 @@ -import { ServerTransactionType, TransactionDirectionEnum } from 'types'; -import { getInterpretedTransaction } from 'utils/transactions/getInterpretedTransaction'; -import { transactionMock } from '../../getInterpretedTransaction/tests/extended-transaction-mock'; +import { + ServerTransactionType, + TransactionDirectionEnum +} from 'types/serverTransactions.types'; +import { getInterpretedTransaction } from '../getInterpretedTransaction'; +import { transactionMock } from '../getInterpretedTransaction/tests/extended-transaction-mock'; import { getTransactionLinkWithLabel } from '../getTransactionLinkWithLabel'; const explorerAddress = 'https://testing.devnet.com'; diff --git a/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/mexUnwrapper.ts b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/mexUnwrapper.ts index 1b2c0b8..60fa9b9 100644 --- a/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/mexUnwrapper.ts +++ b/src/utils/transactions/parsers/transactionActionUnwrapper/helpers/mexUnwrapper.ts @@ -1,14 +1,16 @@ import BigNumber from 'bignumber.js'; import { ZERO } from 'constants/index'; import { + TransactionActionsEnum, TransactionActionType, - UnwrapperType, - TransactionActionsEnum -} from 'types'; + UnwrapperType +} from 'types/serverTransactions.types'; export function mexUnwrapper( action: TransactionActionType ): Array { + let value = ZERO; + switch (action.name) { // distribution case TransactionActionsEnum.claimLockedAssets: @@ -54,7 +56,6 @@ export function mexUnwrapper( { token: action.arguments?.transfers } ]; case TransactionActionsEnum.mergeLockedAssetTokens: - let value = ZERO; if (action.arguments?.transfers) { const values = action.arguments.transfers.map( ({ value }: { value: string }) => value diff --git a/src/utils/transactions/parsers/transactionActionUnwrapper/transactionActionUnwrapper.ts b/src/utils/transactions/parsers/transactionActionUnwrapper/transactionActionUnwrapper.ts index 1b51ca5..f732cd9 100644 --- a/src/utils/transactions/parsers/transactionActionUnwrapper/transactionActionUnwrapper.ts +++ b/src/utils/transactions/parsers/transactionActionUnwrapper/transactionActionUnwrapper.ts @@ -1,9 +1,9 @@ +import { esdtNftUnwrapper, mexUnwrapper, stakeUnwrapper } from './helpers'; import { TransactionActionCategoryEnum, TransactionActionType, UnwrapperType } from '../../../../types'; -import { esdtNftUnwrapper, mexUnwrapper, stakeUnwrapper } from './helpers'; export function transactionActionUnwrapper( action: TransactionActionType diff --git a/src/utils/transactions/validation/isCrossShardTransaction.ts b/src/utils/transactions/validation/isCrossShardTransaction.ts index ba724dd..cace4e2 100644 --- a/src/utils/transactions/validation/isCrossShardTransaction.ts +++ b/src/utils/transactions/validation/isCrossShardTransaction.ts @@ -20,7 +20,7 @@ export function isCrossShardTransaction({ return getShardOfAddress(sender) === receiverShard; } return receiverShard === senderShard; - } catch (err) { + } catch { return false; } } diff --git a/src/utils/transactions/validation/tests/isGuardianTx.test.ts b/src/utils/transactions/validation/tests/isGuardianTx.test.ts index 6defcd9..f2da869 100644 --- a/src/utils/transactions/validation/tests/isGuardianTx.test.ts +++ b/src/utils/transactions/validation/tests/isGuardianTx.test.ts @@ -1,4 +1,4 @@ -import { GuardianActionsEnum } from 'constants'; +import { GuardianActionsEnum } from 'types'; import { isGuardianTx } from '../isGuardianTx'; describe('isGuardianTx Function', () => { diff --git a/src/utils/validation/addressIsValid.ts b/src/utils/validation/addressIsValid.ts index 6b877bc..f3f885c 100644 --- a/src/utils/validation/addressIsValid.ts +++ b/src/utils/validation/addressIsValid.ts @@ -9,6 +9,7 @@ function canTransformToPublicKey(address: string) { } } +// TODO: Move to utils export function addressIsValid(destinationAddress: string) { const isValidBach = destinationAddress?.startsWith('erd') && diff --git a/src/utils/validation/maxDecimals.ts b/src/utils/validation/maxDecimals.ts index b56d2a7..695d52e 100644 --- a/src/utils/validation/maxDecimals.ts +++ b/src/utils/validation/maxDecimals.ts @@ -1,4 +1,7 @@ -export const maxDecimals = (amount: string, decimals = 18) => { +// TODO: Move to utils +import { DECIMALS } from 'constants/index'; + +export function maxDecimals(amount: string, decimals = DECIMALS) { if ( amount != null && amount.toString().indexOf('.') >= 0 && @@ -7,4 +10,4 @@ export const maxDecimals = (amount: string, decimals = 18) => { return false; } return true; -}; +} diff --git a/src/utils/validation/stringIsFloat.ts b/src/utils/validation/stringIsFloat.ts index c752883..88ab54e 100755 --- a/src/utils/validation/stringIsFloat.ts +++ b/src/utils/validation/stringIsFloat.ts @@ -1,7 +1,8 @@ import BigNumber from 'bignumber.js'; import { ZERO } from 'constants/index'; -export const stringIsFloat = (amount: string) => { +// TODO: Move to utils +export function stringIsFloat(amount: string) { if (isNaN(amount as any)) { return false; } @@ -21,4 +22,4 @@ export const stringIsFloat = (amount: string) => { const number = decimals ? [wholes, decimals].join('.') : wholes; const bNparsed = new BigNumber(number); return bNparsed.toString(10) === number && bNparsed.comparedTo(0) >= 0; -}; +} diff --git a/src/utils/validation/stringIsInteger.ts b/src/utils/validation/stringIsInteger.ts index 990c232..983c2d2 100644 --- a/src/utils/validation/stringIsInteger.ts +++ b/src/utils/validation/stringIsInteger.ts @@ -1,9 +1,7 @@ import BigNumber from 'bignumber.js'; -export const stringIsInteger = ( - integer: string, - positiveNumbersOnly = true -) => { +// TODO: Move to utils +export function stringIsInteger(integer: string, positiveNumbersOnly = true) { const stringInteger = String(integer); if (!stringInteger.match(/^[-]?\d+$/)) { return false; @@ -13,4 +11,4 @@ export const stringIsInteger = ( return ( bNparsed.toString(10) === stringInteger && bNparsed.comparedTo(0) >= limit ); -}; +} diff --git a/src/utils/window/index.ts b/src/utils/window/index.ts index dfead60..742a502 100644 --- a/src/utils/window/index.ts +++ b/src/utils/window/index.ts @@ -5,5 +5,5 @@ export * from './getIsAuthRoute'; export * from './getWindowLocation'; export * from './isWindowAvailable'; export * from './parseNavigationParams'; -export * from './removeSearchParamsFromUrl' +export * from './removeSearchParamsFromUrl'; export * from './sanitizeCallbackUrl'; diff --git a/src/utils/window/tests/clearNavigationHistory.test.ts b/src/utils/window/tests/clearNavigationHistory.test.ts index e585e71..04cac7e 100644 --- a/src/utils/window/tests/clearNavigationHistory.test.ts +++ b/src/utils/window/tests/clearNavigationHistory.test.ts @@ -9,7 +9,7 @@ describe('clearNavigationHistory', () => { jest.spyOn(window.history, 'replaceState').mockImplementation(); (getWindowLocation as jest.Mock).mockReturnValue({ pathname: '/test', - hash: '#section1', + hash: '#section1' }); }); @@ -46,7 +46,7 @@ describe('clearNavigationHistory', () => { test('should use default path when pathname is empty', () => { (getWindowLocation as jest.Mock).mockReturnValue({ pathname: '', - hash: '', + hash: '' }); clearNavigationHistory({}); @@ -72,4 +72,4 @@ describe('clearNavigationHistory', () => { '/test?filter=active#section1' ); }); -}); \ No newline at end of file +}); diff --git a/yarn.lock b/yarn.lock index 954867e..1abfdcc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24,7 +24,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz" integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== -"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.8.0", "@babel/core@>=7.0.0-beta.0 <8": +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": version "7.26.0" resolved "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz" integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== @@ -273,19 +273,129 @@ resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@esbuild/aix-ppc64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.1.tgz#044268dc9ca4dc67f8d4aad8f51cfb894bfd7114" + integrity sha512-O7yppwipkXvnEPjzkSXJRk2g4bS8sUx9p9oXHq9MU/U7lxUzZVsnFZMDTmeeX9bfQxrFcvOacl/ENgOh0WP9pA== + +"@esbuild/android-arm64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.1.tgz#76aacd934449e541f05b66d5ec8cbff96ec2ae81" + integrity sha512-jXhccq6es+onw7x8MxoFnm820mz7sGa9J14kLADclmiEUH4fyj+FjR6t0M93RgtlI/awHWhtF0Wgfhqgf9gDZA== + +"@esbuild/android-arm@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.1.tgz#8247c5aef933a212bca261290f6e43a9dca07cc5" + integrity sha512-hh3jKWikdnTtHCglDAeVO3Oyh8MaH8xZUaWMiCCvJ9/c3NtPqZq+CACOlGTxhddypXhl+8B45SeceYBfB/e8Ow== + +"@esbuild/android-x64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.1.tgz#80cbfa35412299edefbc4ab78064f0b66e448008" + integrity sha512-NPObtlBh4jQHE01gJeucqEhdoD/4ya2owSIS8lZYS58aR0x7oZo9lB2lVFxgTANSa5MGCBeoQtr+yA9oKCGPvA== + "@esbuild/darwin-arm64@0.21.1": version "0.21.1" resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.1.tgz" integrity sha512-BLT7TDzqsVlQRmJfO/FirzKlzmDpBWwmCUlyggfzUwg1cAxVxeA4O6b1XkMInlxISdfPAOunV9zXjvh5x99Heg== -"@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": +"@esbuild/darwin-x64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.1.tgz#db971502c9fa204906b89e489810c902bf6d9afb" + integrity sha512-D3h3wBQmeS/vp93O4B+SWsXB8HvRDwMyhTNhBd8yMbh5wN/2pPWRW5o/hM3EKgk9bdKd9594lMGoTCTiglQGRQ== + +"@esbuild/freebsd-arm64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.1.tgz#f0f3bc20c23af999bd696099a324dceb66d77761" + integrity sha512-/uVdqqpNKXIxT6TyS/oSK4XE4xWOqp6fh4B5tgAwozkyWdylcX+W4YF2v6SKsL4wCQ5h1bnaSNjWPXG/2hp8AQ== + +"@esbuild/freebsd-x64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.1.tgz#d36af9085edb34244b41e5a57640e6b4452cbec2" + integrity sha512-paAkKN1n1jJitw+dAoR27TdCzxRl1FOEITx3h201R6NoXUojpMzgMLdkXVgCvaCSCqwYkeGLoe9UVNRDKSvQgw== + +"@esbuild/linux-arm64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.1.tgz#9d2ad42eea33b2a9571f13e7ecc39ee9d3ff0c6d" + integrity sha512-G65d08YoH00TL7Xg4LaL3gLV21bpoAhQ+r31NUu013YB7KK0fyXIt05VbsJtpqh/6wWxoLJZOvQHYnodRrnbUQ== + +"@esbuild/linux-arm@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.1.tgz#d6f7c5873479dd97148bef3e3a7f09d486642883" + integrity sha512-tRHnxWJnvNnDpNVnsyDhr1DIQZUfCXlHSCDohbXFqmg9W4kKR7g8LmA3kzcwbuxbRMKeit8ladnCabU5f2traA== + +"@esbuild/linux-ia32@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.1.tgz#8f2aef34a31c8d16dbce0b8679021f4881f38efe" + integrity sha512-tt/54LqNNAqCz++QhxoqB9+XqdsaZOtFD/srEhHYwBd3ZUOepmR1Eeot8bS+Q7BiEvy9vvKbtpHf+r6q8hF5UA== + +"@esbuild/linux-loong64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.1.tgz#44461ea2388efbafa6cf12b2bc1407a5388da066" + integrity sha512-MhNalK6r0nZD0q8VzUBPwheHzXPr9wronqmZrewLfP7ui9Fv1tdPmg6e7A8lmg0ziQCziSDHxh3cyRt4YMhGnQ== + +"@esbuild/linux-mips64el@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.1.tgz#754d533a4fef4b0790d82bfe1e82d6876f18370e" + integrity sha512-YCKVY7Zen5rwZV+nZczOhFmHaeIxR4Zn3jcmNH53LbgF6IKRwmrMywqDrg4SiSNApEefkAbPSIzN39FC8VsxPg== + +"@esbuild/linux-ppc64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.1.tgz#2aafcfe2826c7d5d2e3c41eb8934e6368a7cada5" + integrity sha512-bw7bcQ+270IOzDV4mcsKAnDtAFqKO0jVv3IgRSd8iM0ac3L8amvCrujRVt1ajBTJcpDaFhIX+lCNRKteoDSLig== + +"@esbuild/linux-riscv64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.1.tgz#481ceaf5939d14fb25da62a385b5e6c2096a3370" + integrity sha512-ARmDRNkcOGOm1AqUBSwRVDfDeD9hGYRfkudP2QdoonBz1ucWVnfBPfy7H4JPI14eYtZruRSczJxyu7SRYDVOcg== + +"@esbuild/linux-s390x@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.1.tgz#e25b97005e4c82540d1bc7af88e333fb55142570" + integrity sha512-o73TcUNMuoTZlhwFdsgr8SfQtmMV58sbgq6gQq9G1xUiYnHMTmJbwq65RzMx89l0iya69lR4bxBgtWiiOyDQZA== + +"@esbuild/linux-x64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.1.tgz#a05a61d0a0cbb03baa6db12cd8164c1e5265ffb2" + integrity sha512-da4/1mBJwwgJkbj4fMH7SOXq2zapgTo0LKXX1VUZ0Dxr+e8N0WbS80nSZ5+zf3lvpf8qxrkZdqkOqFfm57gXwA== + +"@esbuild/netbsd-x64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.1.tgz#e298f854e8999563f2e4668bd542678c46be4b53" + integrity sha512-CPWs0HTFe5woTJN5eKPvgraUoRHrCtzlYIAv9wBC+FAyagBSaf+UdZrjwYyTGnwPGkThV4OCI7XibZOnPvONVw== + +"@esbuild/openbsd-x64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.1.tgz#640d34de1e3c6bc3ff64e0379aae00ede3608f14" + integrity sha512-xxhTm5QtzNLc24R0hEkcH+zCx/o49AsdFZ0Cy5zSd/5tOj4X2g3/2AJB625NoadUuc4A8B3TenLJoYdWYOYCew== + +"@esbuild/sunos-x64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.1.tgz#f53cb1cdcbf05b3320e147ddb85ec2b1cf2b6cfc" + integrity sha512-CWibXszpWys1pYmbr9UiKAkX6x+Sxw8HWtw1dRESK1dLW5fFJ6rMDVw0o8MbadusvVQx1a8xuOxnHXT941Hp1A== + +"@esbuild/win32-arm64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.1.tgz#df4f44f9b4fec9598c0ec2c34fec9c568c8ab85d" + integrity sha512-jb5B4k+xkytGbGUS4T+Z89cQJ9DJ4lozGRSV+hhfmCPpfJ3880O31Q1srPCimm+V6UCbnigqD10EgDNgjvjerQ== + +"@esbuild/win32-ia32@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.1.tgz#a57edbd9905db9f957327ae0facfbf406a80a4e4" + integrity sha512-PgyFvjJhXqHn1uxPhyN1wZ6dIomKjiLUQh1LjFvjiV1JmnkZ/oMPrfeEAZg5R/1ftz4LZWZr02kefNIQ5SKREQ== + +"@esbuild/win32-x64@0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.1.tgz#eb86553d90e86a8c174b96650fdb4c60f2de16a7" + integrity sha512-W9NttRZQR5ehAiqHGDnvfDaGmQOm6Fi4vSlce8mjM75x//XKuVAByohlEX6N17yZnVXxQFuh4fDRunP8ca6bfA== + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.1" resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz" integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== dependencies: eslint-visitor-keys "^3.4.3" -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.11.0", "@eslint-community/regexpp@^4.12.1": +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": version "4.12.1" resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== @@ -611,7 +721,7 @@ slash "^3.0.0" write-file-atomic "^4.0.2" -"@jest/types@^29.0.0", "@jest/types@^29.6.3": +"@jest/types@^29.6.3": version "29.6.3" resolved "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz" integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== @@ -655,17 +765,17 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@ledgerhq/devices@^8.0.7": - version "8.4.4" - resolved "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.4.4.tgz" - integrity sha512-sz/ryhe/R687RHtevIE9RlKaV8kkKykUV4k29e7GAVwzHX1gqG+O75cu1NCJUHLbp3eABV5FdvZejqRUlLis9A== +"@ledgerhq/devices@8.0.3": + version "8.0.3" + resolved "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.0.3.tgz" + integrity sha512-Q7/vqkGELSBuwFafFoTqlHIRyOjw8JqbSgiQwe2R38xN0RKtKIh+5E6UfMKyoExiq+SrQg0IC8P2LS+XdjOHLw== dependencies: - "@ledgerhq/errors" "^6.19.1" - "@ledgerhq/logs" "^6.12.0" - rxjs "^7.8.1" + "@ledgerhq/errors" "^6.12.6" + "@ledgerhq/logs" "^6.10.1" + rxjs "6" semver "^7.3.5" -"@ledgerhq/devices@^8.3.0", "@ledgerhq/devices@^8.4.4": +"@ledgerhq/devices@^8.0.7", "@ledgerhq/devices@^8.3.0", "@ledgerhq/devices@^8.4.4": version "8.4.4" resolved "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.4.4.tgz" integrity sha512-sz/ryhe/R687RHtevIE9RlKaV8kkKykUV4k29e7GAVwzHX1gqG+O75cu1NCJUHLbp3eABV5FdvZejqRUlLis9A== @@ -675,31 +785,16 @@ rxjs "^7.8.1" semver "^7.3.5" -"@ledgerhq/devices@8.0.3": - version "8.0.3" - resolved "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.0.3.tgz" - integrity sha512-Q7/vqkGELSBuwFafFoTqlHIRyOjw8JqbSgiQwe2R38xN0RKtKIh+5E6UfMKyoExiq+SrQg0IC8P2LS+XdjOHLw== - dependencies: - "@ledgerhq/errors" "^6.12.6" - "@ledgerhq/logs" "^6.10.1" - rxjs "6" - semver "^7.3.5" - -"@ledgerhq/errors@^6.12.6": - version "6.19.1" - resolved "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.19.1.tgz" - integrity sha512-75yK7Nnit/Gp7gdrJAz0ipp31CCgncRp+evWt6QawQEtQKYEDfGo10QywgrrBBixeRxwnMy1DP6g2oCWRf1bjw== - -"@ledgerhq/errors@^6.14.0", "@ledgerhq/errors@^6.16.4", "@ledgerhq/errors@^6.19.1": - version "6.19.1" - resolved "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.19.1.tgz" - integrity sha512-75yK7Nnit/Gp7gdrJAz0ipp31CCgncRp+evWt6QawQEtQKYEDfGo10QywgrrBBixeRxwnMy1DP6g2oCWRf1bjw== - "@ledgerhq/errors@6.12.6": version "6.12.6" resolved "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.12.6.tgz" integrity sha512-D+r2B09vaRO06wfGoss+rNgwqWSoK0bCtsaJWzlD2hv1zxTtucqVtSztbRFypIqxWTCb3ix5Nh2dWHEJVTp2Xw== +"@ledgerhq/errors@^6.12.6", "@ledgerhq/errors@^6.14.0", "@ledgerhq/errors@^6.16.4", "@ledgerhq/errors@^6.19.1": + version "6.19.1" + resolved "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.19.1.tgz" + integrity sha512-75yK7Nnit/Gp7gdrJAz0ipp31CCgncRp+evWt6QawQEtQKYEDfGo10QywgrrBBixeRxwnMy1DP6g2oCWRf1bjw== + "@ledgerhq/hw-transport-web-ble@6.28.6": version "6.28.6" resolved "https://registry.npmjs.org/@ledgerhq/hw-transport-web-ble/-/hw-transport-web-ble-6.28.6.tgz" @@ -731,6 +826,15 @@ "@ledgerhq/hw-transport" "^6.30.6" "@ledgerhq/logs" "^6.12.0" +"@ledgerhq/hw-transport@6.28.8": + version "6.28.8" + resolved "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.28.8.tgz" + integrity sha512-XxQVl4htd018u/M66r0iu5nlHi+J6QfdPsORzDF6N39jaz+tMqItb7tUlXM/isggcuS5lc7GJo7NOuJ8rvHZaQ== + dependencies: + "@ledgerhq/devices" "^8.0.7" + "@ledgerhq/errors" "^6.14.0" + events "^3.3.0" + "@ledgerhq/hw-transport@^6.30.6": version "6.31.4" resolved "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.31.4.tgz" @@ -741,15 +845,6 @@ "@ledgerhq/logs" "^6.12.0" events "^3.3.0" -"@ledgerhq/hw-transport@6.28.8": - version "6.28.8" - resolved "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.28.8.tgz" - integrity sha512-XxQVl4htd018u/M66r0iu5nlHi+J6QfdPsORzDF6N39jaz+tMqItb7tUlXM/isggcuS5lc7GJo7NOuJ8rvHZaQ== - dependencies: - "@ledgerhq/devices" "^8.0.7" - "@ledgerhq/errors" "^6.14.0" - events "^3.3.0" - "@ledgerhq/logs@^6.10.1", "@ledgerhq/logs@^6.12.0": version "6.12.0" resolved "https://registry.npmjs.org/@ledgerhq/logs/-/logs-6.12.0.tgz" @@ -882,7 +977,7 @@ resolved "https://registry.npmjs.org/@multiversx/sdk-bls-wasm/-/sdk-bls-wasm-0.3.5.tgz" integrity sha512-c0tIdQUnbBLSt6NYU+OpeGPYdL0+GV547HeHT8Xc0BKQ7Cj0v82QUoA2QRtWrR1G4MNZmLsIacZSsf6DrIS2Bw== -"@multiversx/sdk-core@>= 12.18.0", "@multiversx/sdk-core@>= 12.5.0", "@multiversx/sdk-core@>= 13.0.0", "@multiversx/sdk-core@>= 13.5.0": +"@multiversx/sdk-core@>= 13.5.0": version "13.15.0" resolved "https://registry.npmjs.org/@multiversx/sdk-core/-/sdk-core-13.15.0.tgz" integrity sha512-5RRLMxSDd0XZGopIrPsWLbA8nWxC7WQYjea8/jPvkRApLyggheQU8gaC6ZSgSE0EBrSHl+oC3+YH8nbVayZ2gw== @@ -907,7 +1002,8 @@ "@multiversx/sdk-dapp-core-ui@file:../mx-sdk-dapp-core-ui": version "0.0.0" - resolved "file:../mx-sdk-dapp-core-ui" + dependencies: + bignumber.js "9.x" "@multiversx/sdk-dapp-utils@1.0.0": version "1.0.0" @@ -1009,7 +1105,7 @@ dependencies: qs "6.10.3" -"@noble/curves@~1.4.0", "@noble/curves@1.4.2": +"@noble/curves@1.4.2", "@noble/curves@~1.4.0": version "1.4.2" resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz" integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw== @@ -1021,25 +1117,20 @@ resolved "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz" integrity sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ== -"@noble/hashes@^1.2.0": - version "1.6.1" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz" - integrity sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w== - -"@noble/hashes@^1.3.1": - version "1.6.1" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz" - integrity sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w== +"@noble/hashes@1.3.0": + version "1.3.0" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz" + integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== -"@noble/hashes@~1.4.0", "@noble/hashes@1.4.0": +"@noble/hashes@1.4.0", "@noble/hashes@~1.4.0": version "1.4.0" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== -"@noble/hashes@1.3.0": - version "1.3.0" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz" - integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== +"@noble/hashes@^1.2.0", "@noble/hashes@^1.3.1": + version "1.6.1" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz" + integrity sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -1049,7 +1140,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -1072,11 +1163,56 @@ resolved "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz" integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q== +"@parcel/watcher-android-arm64@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz#e32d3dda6647791ee930556aee206fcd5ea0fb7a" + integrity sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ== + "@parcel/watcher-darwin-arm64@2.5.0": version "2.5.0" resolved "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz" integrity sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw== +"@parcel/watcher-darwin-x64@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz#f9f1d5ce9d5878d344f14ef1856b7a830c59d1bb" + integrity sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA== + +"@parcel/watcher-freebsd-x64@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz#2b77f0c82d19e84ff4c21de6da7f7d096b1a7e82" + integrity sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw== + +"@parcel/watcher-linux-arm-glibc@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz#92ed322c56dbafa3d2545dcf2803334aee131e42" + integrity sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA== + +"@parcel/watcher-linux-arm-musl@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz#cd48e9bfde0cdbbd2ecd9accfc52967e22f849a4" + integrity sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA== + +"@parcel/watcher-linux-arm64-glibc@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz#7b81f6d5a442bb89fbabaf6c13573e94a46feb03" + integrity sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA== + +"@parcel/watcher-linux-arm64-musl@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz#dcb8ff01077cdf59a18d9e0a4dff7a0cfe5fd732" + integrity sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q== + +"@parcel/watcher-linux-x64-glibc@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz#2e254600fda4e32d83942384d1106e1eed84494d" + integrity sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw== + +"@parcel/watcher-linux-x64-musl@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz#01fcea60fedbb3225af808d3f0a7b11229792eef" + integrity sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA== + "@parcel/watcher-wasm@^2.4.1": version "2.5.0" resolved "https://registry.npmjs.org/@parcel/watcher-wasm/-/watcher-wasm-2.5.0.tgz" @@ -1086,6 +1222,21 @@ micromatch "^4.0.5" napi-wasm "^1.1.0" +"@parcel/watcher-win32-arm64@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz#87cdb16e0783e770197e52fb1dc027bb0c847154" + integrity sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig== + +"@parcel/watcher-win32-ia32@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz#778c39b56da33e045ba21c678c31a9f9d7c6b220" + integrity sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA== + +"@parcel/watcher-win32-x64@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz#33873876d0bbc588aacce38e90d1d7480ce81cb7" + integrity sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw== + "@parcel/watcher@^2.4.1": version "2.5.0" resolved "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz" @@ -1246,14 +1397,6 @@ resolved "https://registry.npmjs.org/@stablelib/bytes/-/bytes-1.0.1.tgz" integrity sha512-Kre4Y4kdwuqL8BR2E9hV/R5sOrUj6NanZaZis0V6lX5yzqC3hBuVSDXUIBqQv/sCpmuWRiHLwqiT1pqqjuBXoQ== -"@stablelib/chacha@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@stablelib/chacha/-/chacha-1.0.1.tgz" - integrity sha512-Pmlrswzr0pBzDofdFuVe1q7KdsHKhhU24e8gkEwnTGOmlC7PADzLVxGdn2PoNVBBabdg0l/IfLKg6sHAbTQugg== - dependencies: - "@stablelib/binary" "^1.0.1" - "@stablelib/wipe" "^1.0.1" - "@stablelib/chacha20poly1305@1.0.1": version "1.0.1" resolved "https://registry.npmjs.org/@stablelib/chacha20poly1305/-/chacha20poly1305-1.0.1.tgz" @@ -1266,6 +1409,14 @@ "@stablelib/poly1305" "^1.0.1" "@stablelib/wipe" "^1.0.1" +"@stablelib/chacha@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@stablelib/chacha/-/chacha-1.0.1.tgz" + integrity sha512-Pmlrswzr0pBzDofdFuVe1q7KdsHKhhU24e8gkEwnTGOmlC7PADzLVxGdn2PoNVBBabdg0l/IfLKg6sHAbTQugg== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + "@stablelib/constant-time@^1.0.1": version "1.0.1" resolved "https://registry.npmjs.org/@stablelib/constant-time/-/constant-time-1.0.1.tgz" @@ -1368,7 +1519,52 @@ resolved "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.9.3.tgz" integrity sha512-hGfl/KTic/QY4tB9DkTbNuxy5cV4IeejpPD4zo+Lzt4iLlDWIeANL4Fkg67FiVceNJboqg48CUX+APhDHO5G1w== -"@swc/core@*", "@swc/core@^1.4.17": +"@swc/core-darwin-x64@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.9.3.tgz#01376c6c2caea5dd0c235f21ebc7e41238153c86" + integrity sha512-IaRq05ZLdtgF5h9CzlcgaNHyg4VXuiStnOFpfNEMuI5fm5afP2S0FHq8WdakUz5WppsbddTdplL+vpeApt/WCQ== + +"@swc/core-linux-arm-gnueabihf@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.9.3.tgz#4a9705903cebfc8e3e2bee71a42f7c88896e61df" + integrity sha512-Pbwe7xYprj/nEnZrNBvZfjnTxlBIcfApAGdz2EROhjpPj+FBqBa3wOogqbsuGGBdCphf8S+KPprL1z+oDWkmSQ== + +"@swc/core-linux-arm64-gnu@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.9.3.tgz#722aefc00a7abfb031fae7539226c7d7683f5c8d" + integrity sha512-AQ5JZiwNGVV/2K2TVulg0mw/3LYfqpjZO6jDPtR2evNbk9Yt57YsVzS+3vHSlUBQDRV9/jqMuZYVU3P13xrk+g== + +"@swc/core-linux-arm64-musl@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.9.3.tgz#6643f683311cc1dcee00970e3d6b4872225bdbd8" + integrity sha512-tzVH480RY6RbMl/QRgh5HK3zn1ZTFsThuxDGo6Iuk1MdwIbdFYUY034heWUTI4u3Db97ArKh0hNL0xhO3+PZdg== + +"@swc/core-linux-x64-gnu@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.9.3.tgz#e6f5cefa244409abe1451fbb4575696a870cbd7a" + integrity sha512-ivXXBRDXDc9k4cdv10R21ccBmGebVOwKXT/UdH1PhxUn9m/h8erAWjz5pcELwjiMf27WokqPgaWVfaclDbgE+w== + +"@swc/core-linux-x64-musl@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.9.3.tgz#4d45399f7a01389add61febd02da9b12f16abc81" + integrity sha512-ILsGMgfnOz1HwdDz+ZgEuomIwkP1PHT6maigZxaCIuC6OPEhKE8uYna22uU63XvYcLQvZYDzpR3ms47WQPuNEg== + +"@swc/core-win32-arm64-msvc@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.9.3.tgz#8c352bcea558b9a950877cd724f132d7d51a4d80" + integrity sha512-e+XmltDVIHieUnNJHtspn6B+PCcFOMYXNJB1GqoCcyinkEIQNwC8KtWgMqUucUbEWJkPc35NHy9k8aCXRmw9Kg== + +"@swc/core-win32-ia32-msvc@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.9.3.tgz#656f78b9c56413dbd590ac259dbe0d563cd8e166" + integrity sha512-rqpzNfpAooSL4UfQnHhkW8aL+oyjqJniDP0qwZfGnjDoJSbtPysHg2LpcOBEdSnEH+uIZq6J96qf0ZFD8AGfXA== + +"@swc/core-win32-x64-msvc@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.9.3.tgz#9595c177d2c11909558da93b18f37e7c5ae1909c" + integrity sha512-3YJJLQ5suIEHEKc1GHtqVq475guiyqisKSoUnoaRtxkDaW5g1yvPt9IoSLOe2mRs7+FFhGGU693RsBUSwOXSdQ== + +"@swc/core@^1.4.17": version "1.9.3" resolved "https://registry.npmjs.org/@swc/core/-/core-1.9.3.tgz" integrity sha512-oRj0AFePUhtatX+BscVhnzaAmWjpfAeySpM1TCbxA1rtBDeH/JDhi5yYzAKneDYtVtBvA7ApfeuzhMC9ye4xSg== @@ -1489,14 +1685,6 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^29.5.11": - version "29.5.14" - resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz" - integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== - dependencies: - expect "^29.0.0" - pretty-format "^29.0.0" - "@types/jest@29.5.13": version "29.5.13" resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.13.tgz" @@ -1505,6 +1693,14 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jest@^29.5.11": + version "29.5.14" + resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + "@types/js-levenshtein@^1.1.1": version "1.1.3" resolved "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.3.tgz" @@ -1547,14 +1743,7 @@ "@types/node" "*" form-data "^4.0.0" -"@types/node@*": - version "22.10.1" - resolved "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz" - integrity sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ== - dependencies: - undici-types "~6.20.0" - -"@types/node@>=13.7.0": +"@types/node@*", "@types/node@>=13.7.0": version "22.10.1" resolved "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz" integrity sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ== @@ -1607,7 +1796,7 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/eslint-plugin@8.15.0": +"@typescript-eslint/eslint-plugin@8.15.0": version "8.15.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz" integrity sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg== @@ -1622,7 +1811,7 @@ natural-compare "^1.4.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@^8.0.0 || ^8.0.0-alpha.0", "@typescript-eslint/parser@8.15.0": +"@typescript-eslint/parser@8.15.0": version "8.15.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.15.0.tgz" integrity sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A== @@ -1697,16 +1886,6 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0": - version "8.17.0" - resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz" - integrity sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "8.17.0" - "@typescript-eslint/types" "8.17.0" - "@typescript-eslint/typescript-estree" "8.17.0" - "@typescript-eslint/utils@8.15.0": version "8.15.0" resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.15.0.tgz" @@ -1717,6 +1896,16 @@ "@typescript-eslint/types" "8.15.0" "@typescript-eslint/typescript-estree" "8.15.0" +"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0": + version "8.17.0" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz" + integrity sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.17.0" + "@typescript-eslint/types" "8.17.0" + "@typescript-eslint/typescript-estree" "8.17.0" + "@typescript-eslint/visitor-keys@8.15.0": version "8.15.0" resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz" @@ -1789,15 +1978,15 @@ "@walletconnect/safe-json" "^1.0.2" tslib "1.14.1" -"@walletconnect/jsonrpc-types@^1.0.2": - version "1.0.4" - resolved "https://registry.npmjs.org/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.4.tgz" - integrity sha512-P6679fG/M+wuWg9TY8mh6xFSdYnFyFjwFelxyISxMDrlbXokorEVXYOxiqEbrU3x1BmBoCAJJ+vtEaEoMlpCBQ== +"@walletconnect/jsonrpc-types@1.0.3": + version "1.0.3" + resolved "https://registry.npmjs.org/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.3.tgz" + integrity sha512-iIQ8hboBl3o5ufmJ8cuduGad0CQm3ZlsHtujv9Eu16xq89q+BG7Nh5VLxxUgmtpnrePgFkTwXirCTkwJH1v+Yw== dependencies: - events "^3.3.0" keyvaluestorage-interface "^1.0.0" + tslib "1.14.1" -"@walletconnect/jsonrpc-types@^1.0.3": +"@walletconnect/jsonrpc-types@^1.0.2", "@walletconnect/jsonrpc-types@^1.0.3": version "1.0.4" resolved "https://registry.npmjs.org/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.4.tgz" integrity sha512-P6679fG/M+wuWg9TY8mh6xFSdYnFyFjwFelxyISxMDrlbXokorEVXYOxiqEbrU3x1BmBoCAJJ+vtEaEoMlpCBQ== @@ -1805,15 +1994,7 @@ events "^3.3.0" keyvaluestorage-interface "^1.0.0" -"@walletconnect/jsonrpc-types@1.0.3": - version "1.0.3" - resolved "https://registry.npmjs.org/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.3.tgz" - integrity sha512-iIQ8hboBl3o5ufmJ8cuduGad0CQm3ZlsHtujv9Eu16xq89q+BG7Nh5VLxxUgmtpnrePgFkTwXirCTkwJH1v+Yw== - dependencies: - keyvaluestorage-interface "^1.0.0" - tslib "1.14.1" - -"@walletconnect/jsonrpc-utils@^1.0.6", "@walletconnect/jsonrpc-utils@^1.0.8", "@walletconnect/jsonrpc-utils@1.0.8": +"@walletconnect/jsonrpc-utils@1.0.8", "@walletconnect/jsonrpc-utils@^1.0.6", "@walletconnect/jsonrpc-utils@^1.0.8": version "1.0.8" resolved "https://registry.npmjs.org/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.8.tgz" integrity sha512-vdeb03bD8VzJUL6ZtzRYsFMq1eZQcM3EAzT0a3st59dyLfJ0wq+tKMpmGH7HlB7waD858UWgfIcudbPFsbzVdw== @@ -1986,7 +2167,7 @@ acorn-walk@^8.0.2: dependencies: acorn "^8.11.0" -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.1.0, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.8.1: +acorn@^8.1.0, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.8.1: version "8.14.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== @@ -2015,6 +2196,11 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.21.3" +ansi-regex@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" @@ -2178,7 +2364,7 @@ axios-mock-adapter@1.22.0: fast-deep-equal "^3.1.3" is-buffer "^2.0.5" -axios@^1.7.4, "axios@>= 0.17.0", axios@>=1.6.5: +axios@>=1.6.5, axios@^1.7.4: version "1.7.8" resolved "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz" integrity sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw== @@ -2187,7 +2373,7 @@ axios@^1.7.4, "axios@>= 0.17.0", axios@>=1.6.5: form-data "^4.0.0" proxy-from-env "^1.1.0" -babel-jest@^29.0.0, babel-jest@^29.7.0: +babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz" integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== @@ -2260,17 +2446,17 @@ base64-js@^1.3.1: resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -bech32@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz" - integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== - bech32@1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -bignumber.js@^9.0.0, bignumber.js@^9.0.1, bignumber.js@9.x: +bech32@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz" + integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== + +bignumber.js@9.x, bignumber.js@^9.0.0: version "9.1.2" resolved "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz" integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== @@ -2428,7 +2614,7 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.24.0, "browserslist@>= 4.21.0": +browserslist@^4.24.0: version "4.24.2" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz" integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== @@ -2462,15 +2648,15 @@ buffer-xor@^1.0.3: resolved "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz" integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== +buffer@6.0.3, buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== dependencies: base64-js "^1.3.1" - ieee754 "^1.1.13" + ieee754 "^1.2.1" -buffer@^5.7.1: +buffer@^5.5.0, buffer@^5.7.1: version "5.7.1" resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -2478,31 +2664,11 @@ buffer@^5.7.1: base64-js "^1.3.1" ieee754 "^1.1.13" -buffer@^6.0.3, buffer@6.0.3: - version "6.0.3" - resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" - integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.2.1" - -builtin-modules@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz" - integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== - builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz" integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ== -builtins@^5.0.1: - version "5.1.0" - resolved "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz" - integrity sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg== - dependencies: - semver "^7.0.0" - call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" @@ -2735,7 +2901,7 @@ create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: ripemd160 "^2.0.1" sha.js "^2.4.0" -create-hmac@^1.1.4, create-hmac@^1.1.7, create-hmac@1.1.7: +create-hmac@1.1.7, create-hmac@^1.1.4, create-hmac@^1.1.7: version "1.1.7" resolved "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz" integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== @@ -2852,6 +3018,13 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5, debug@~4.3.1, debug@~4.3.2: + version "4.3.7" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + debug@^3.2.7: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" @@ -2859,13 +3032,6 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5, debug@~4.3.1, debug@~4.3.2, debug@4: - version "4.3.7" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" - integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== - dependencies: - ms "^2.1.3" - decimal.js@^10.4.2: version "10.4.3" resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz" @@ -2939,7 +3105,7 @@ destr@^2.0.3: resolved "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz" integrity sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ== -detect-browser@^5.2.0, detect-browser@5.3.0: +detect-browser@5.3.0, detect-browser@^5.2.0: version "5.3.0" resolved "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz" integrity sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w== @@ -3004,11 +3170,6 @@ duplexify@^4.1.2: readable-stream "^3.1.1" stream-shift "^1.0.2" -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - ed25519-hd-key@1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/ed25519-hd-key/-/ed25519-hd-key-1.1.2.tgz" @@ -3053,11 +3214,6 @@ emoji-regex@^8.0.0: resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" @@ -3205,7 +3361,7 @@ esbuild-node-externals@1.14.0: find-up "^5.0.0" tslib "^2.4.1" -"esbuild@0.12 - 0.23", esbuild@0.21.1: +esbuild@0.21.1: version "0.21.1" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.21.1.tgz" integrity sha512-GPqx+FX7mdqulCeQ4TsGZQ3djBJkx5k7zBGtqt9ycVlWNg8llJ4RO9n2vciu8BN2zAEs6lPbPl0asZsAh7oWzg== @@ -3265,14 +3421,7 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-compat-utils@^0.5.1: - version "0.5.1" - resolved "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz" - integrity sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q== - dependencies: - semver "^7.5.4" - -eslint-config-prettier@*, eslint-config-prettier@9.1.0: +eslint-config-prettier@9.1.0: version "9.1.0" resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz" integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== @@ -3312,15 +3461,6 @@ eslint-module-utils@^2.12.0, eslint-module-utils@^2.8.1: dependencies: debug "^3.2.7" -eslint-plugin-es-x@^7.5.0: - version "7.8.0" - resolved "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz" - integrity sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ== - dependencies: - "@eslint-community/eslint-utils" "^4.1.2" - "@eslint-community/regexpp" "^4.11.0" - eslint-compat-utils "^0.5.1" - eslint-plugin-es@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz" @@ -3329,7 +3469,7 @@ eslint-plugin-es@^3.0.0: eslint-utils "^2.0.0" regexpp "^3.0.0" -eslint-plugin-import@*, eslint-plugin-import@^2.25.2, eslint-plugin-import@2.31.0: +eslint-plugin-import@2.31.0: version "2.31.0" resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz" integrity sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A== @@ -3361,23 +3501,6 @@ eslint-plugin-jest@28.9.0: dependencies: "@typescript-eslint/utils" "^6.0.0 || ^7.0.0 || ^8.0.0" -"eslint-plugin-n@^15.0.0 || ^16.0.0 ": - version "16.6.2" - resolved "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz" - integrity sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - builtins "^5.0.1" - eslint-plugin-es-x "^7.5.0" - get-tsconfig "^4.7.0" - globals "^13.24.0" - ignore "^5.2.4" - is-builtin-module "^3.2.1" - is-core-module "^2.12.1" - minimatch "^3.1.2" - resolve "^1.22.2" - semver "^7.5.3" - eslint-plugin-node@11.1.0: version "11.1.0" resolved "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz" @@ -3398,7 +3521,7 @@ eslint-plugin-prettier@5.2.1: prettier-linter-helpers "^1.0.0" synckit "^0.9.1" -eslint-plugin-promise@^6.0.0, eslint-plugin-promise@7.1.0: +eslint-plugin-promise@7.1.0: version "7.1.0" resolved "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-7.1.0.tgz" integrity sha512-8trNmPxdAy3W620WKDpaS65NlM5yAumod6XeC4LOb+jxlkG4IVcp68c6dXY2ev+uT4U1PtG57YDV6EGAXN0GbQ== @@ -3433,7 +3556,7 @@ eslint-visitor-keys@^4.2.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz" integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== -eslint@*, "eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0 || ^9.0.0", eslint@^8.0.1, "eslint@^8.57.0 || ^9.0.0", eslint@>=4.19.1, eslint@>=5.16.0, eslint@>=6.0.0, eslint@>=7.0.0, eslint@>=8, eslint@>=8.0.0, eslint@9.15.0: +eslint@9.15.0: version "9.15.0" resolved "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz" integrity sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw== @@ -3623,7 +3746,7 @@ fast-glob@^3.2.9, fast-glob@^3.3.2: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0, fast-json-stable-stringify@2.x: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -3683,15 +3806,7 @@ filter-obj@^1.1.0: resolved "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz" integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^4.1.0: +find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -3829,7 +3944,7 @@ get-symbol-description@^1.0.2: es-errors "^1.3.0" get-intrinsic "^1.2.4" -get-tsconfig@^4.7.0, get-tsconfig@^4.7.5: +get-tsconfig@^4.7.5: version "4.8.1" resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz" integrity sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg== @@ -3850,6 +3965,17 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob@10.3.14: + version "10.3.14" + resolved "https://registry.npmjs.org/glob/-/glob-10.3.14.tgz" + integrity sha512-4fkAqu93xe9Mk7le9v0y3VrPDqLKHarNi2s4Pv7f2yOvfhWfhc7hRPHC/JyqMqb8B/Dt/eGS4n7ykwf3fOsl8g== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.3.6" + minimatch "^9.0.1" + minipass "^7.0.4" + path-scurry "^1.11.0" + glob@^10.3.7: version "10.4.5" resolved "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz" @@ -3862,7 +3988,7 @@ glob@^10.3.7: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^7.1.3: +glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -3874,41 +4000,11 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.4: - version "7.2.3" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@10.3.14: - version "10.3.14" - resolved "https://registry.npmjs.org/glob/-/glob-10.3.14.tgz" - integrity sha512-4fkAqu93xe9Mk7le9v0y3VrPDqLKHarNi2s4Pv7f2yOvfhWfhc7hRPHC/JyqMqb8B/Dt/eGS4n7ykwf3fOsl8g== - dependencies: - foreground-child "^3.1.0" - jackspeak "^2.3.6" - minimatch "^9.0.1" - minipass "^7.0.4" - path-scurry "^1.11.0" - globals@^11.1.0: version "11.12.0" resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.24.0: - version "13.24.0" - resolved "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz" - integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== - dependencies: - type-fest "^0.20.2" - globals@^14.0.0: version "14.0.0" resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz" @@ -4103,13 +4199,6 @@ human-signals@^5.0.0: resolved "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz" integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== -iconv-lite@^0.4.24: - version "0.4.24" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - iconv-lite@0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" @@ -4117,6 +4206,13 @@ iconv-lite@0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + idb-keyval@^6.2.1: version "6.2.1" resolved "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz" @@ -4127,12 +4223,12 @@ ieee754@^1.1.13, ieee754@^1.2.1: resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1: +ignore@^5.1.1, ignore@^5.2.0, ignore@^5.3.1: version "5.3.2" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== -immer@^10.1.1, immer@>=9.0: +immer@^10.1.1: version "10.1.1" resolved "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz" integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw== @@ -4166,7 +4262,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4, inherits@2: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4261,13 +4357,6 @@ is-buffer@^2.0.5: resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== -is-builtin-module@^3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz" - integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== - dependencies: - builtin-modules "^3.3.0" - is-bun-module@^1.0.2: version "1.3.0" resolved "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.3.0.tgz" @@ -4280,7 +4369,7 @@ is-callable@^1.1.3, is-callable@^1.2.7: resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.12.1, is-core-module@^2.13.0, is-core-module@^2.15.1: +is-core-module@^2.13.0, is-core-module@^2.15.1: version "2.15.1" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz" integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== @@ -4815,7 +4904,7 @@ jest-resolve-dependencies@^29.7.0: jest-regex-util "^29.6.3" jest-snapshot "^29.7.0" -jest-resolve@*, jest-resolve@^29.7.0: +jest-resolve@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz" integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== @@ -4959,7 +5048,7 @@ jest-worker@^29.7.0: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@*, jest@^29.0.0, jest@29.7.0: +jest@29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz" integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== @@ -4969,7 +5058,7 @@ jest@*, jest@^29.0.0, jest@29.7.0: import-local "^3.0.2" jest-cli "^29.7.0" -jiti@*, jiti@^2.1.2: +jiti@^2.1.2: version "2.4.1" resolved "https://registry.npmjs.org/jiti/-/jiti-2.4.1.tgz" integrity sha512-yPBThwecp1wS9DmoA4x4KR2h3QoslacnDR8ypuFM962kI4/456Iy1oHx2RAgh4jfZNdn0bctsdadceiBUgpU1g== @@ -5185,7 +5274,7 @@ lodash.merge@^4.6.2: resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash@^4.17.21, lodash@4.17.21: +lodash@4.17.21, lodash@^4.17.21: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -5325,14 +5414,7 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^9.0.1: - version "9.0.5" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.4: +minimatch@^9.0.1, minimatch@^9.0.4: version "9.0.5" resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== @@ -5410,7 +5492,9 @@ nanoassert@^1.0.0: integrity sha512-C40jQ3NzfkP53NsO8kEOFd79p4b9kDXQMwgiY1z8ZwrDZgUyom0AHwGegF4Dm99L+YoYhuaB0ceerUcXmqr1rQ== napi-wasm@^1.1.0: - version "1.1.0" + version "1.1.3" + resolved "https://registry.yarnpkg.com/napi-wasm/-/napi-wasm-1.1.3.tgz#7bb95c88e6561f84880bb67195437b1cfbe99224" + integrity sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg== natural-compare@^1.4.0: version "1.4.0" @@ -5899,7 +5983,7 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@>=3.0.0, prettier@3.2.5: +prettier@3.2.5: version "3.2.5" resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz" integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== @@ -5936,7 +6020,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -protobufjs@^7.2.6, protobufjs@7.3.0: +protobufjs@7.3.0: version "7.3.0" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.0.tgz" integrity sha512-YWD03n3shzV9ImZRX3ccbjqLxj7NokGN0V/ESiBV5xWqrommYHYiihuIyavq03pWSGqlyvYUFmfoMKd+1rPA/g== @@ -5993,13 +6077,6 @@ pure-rand@^6.0.0: resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz" integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== -qs@^6.12.3: - version "6.13.1" - resolved "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz" - integrity sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg== - dependencies: - side-channel "^1.0.6" - qs@6.10.3: version "6.10.3" resolved "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz" @@ -6014,6 +6091,13 @@ qs@6.11.2: dependencies: side-channel "^1.0.4" +qs@^6.12.3: + version "6.13.1" + resolved "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz" + integrity sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg== + dependencies: + side-channel "^1.0.6" + query-string@7.1.3: version "7.1.3" resolved "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz" @@ -6074,7 +6158,7 @@ react-is@^18.0.0: resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== -"react@^16.8.0 || ^17.0.0 || ^18.0.0", react@^18.3.1, react@>=16.8: +react@^18.3.1: version "18.3.1" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== @@ -6191,7 +6275,7 @@ resolve.exports@^2.0.0: resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz" integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== -resolve@^1.10.1, resolve@^1.17.0, resolve@^1.20.0, resolve@^1.22.2, resolve@^1.22.4: +resolve@^1.10.1, resolve@^1.17.0, resolve@^1.20.0, resolve@^1.22.4: version "1.22.8" resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -6240,13 +6324,6 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^7.5.5, rxjs@^7.8.1: - version "7.8.1" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== - dependencies: - tslib "^2.1.0" - rxjs@6: version "6.6.7" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" @@ -6254,6 +6331,13 @@ rxjs@6: dependencies: tslib "^1.9.0" +rxjs@^7.5.5, rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-array-concat@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz" @@ -6269,12 +6353,7 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@~5.1.1: +safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -6310,22 +6389,12 @@ scryptsy@2.1.0: resolved "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz" integrity sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w== -semver@^6.1.0: +semver@^6.1.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^6.3.0: - version "6.3.1" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^6.3.1: - version "6.3.1" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.0.0, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: +semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: version "7.6.3" resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -6397,12 +6466,7 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -signal-exit@^4.1.0: +signal-exit@^4.0.1, signal-exit@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== @@ -6522,20 +6586,6 @@ strict-uri-encode@^2.0.0: resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz" integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== -string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - string-length@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" @@ -6553,23 +6603,14 @@ string-length@^4.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== +string-width@4.1.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3, string-width@^5.0.1, string-width@^5.1.2: + version "4.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.1.0.tgz#ba846d1daa97c3c596155308063e075ed1c99aff" + integrity sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" + strip-ansi "^5.2.0" string.prototype.trim@^1.2.9: version "1.2.9" @@ -6599,6 +6640,20 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" +string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + "strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -6606,6 +6661,13 @@ string.prototype.trimstart@^1.0.8: dependencies: ansi-regex "^5.0.1" +strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -6797,22 +6859,12 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.9.0, tslib@1.14.1: +tslib@1.14.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.1.0: - version "2.8.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -tslib@^2.4.1: - version "2.8.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -tslib@^2.6.2: +tslib@^2.1.0, tslib@^2.4.1, tslib@^2.6.2: version "2.8.1" resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -6839,11 +6891,6 @@ type-detect@4.0.8: resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - type-fest@^0.21.3: version "0.21.3" resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" @@ -6908,7 +6955,7 @@ typescript-eslint@8.15.0: "@typescript-eslint/parser" "8.15.0" "@typescript-eslint/utils" "8.15.0" -"typescript@>= 4.4.x <= 5.2.x", typescript@>=4.2.0, "typescript@>=4.3 <6", typescript@5.4.5: +typescript@5.4.5: version "5.4.5" resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz" integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== @@ -7053,16 +7100,16 @@ util@^0.12.3, util@^0.12.4, util@^0.12.5: is-typed-array "^1.1.3" which-typed-array "^1.1.2" -uuid@^9.0.1: - version "9.0.1" - resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz" - integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== - uuid@8.3.2: version "8.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.1: + version "9.0.1" + resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-to-istanbul@^9.0.1: version "9.3.0" resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz" @@ -7107,16 +7154,16 @@ web-encoding@^1.1.5: optionalDependencies: "@zxing/text-encoding" "0.9.0" -webextension-polyfill@^0.10.0: - version "0.10.0" - resolved "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz" - integrity sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g== - "webextension-polyfill@>=0.10.0 <1.0": version "0.12.0" resolved "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.12.0.tgz" integrity sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q== +webextension-polyfill@^0.10.0: + version "0.10.0" + resolved "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz" + integrity sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" From 293ca886b2e0bac009f6fc5b817b8b2b17d07351 Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Wed, 4 Dec 2024 18:33:36 +0200 Subject: [PATCH 12/23] refactor --- .../getInterpretedTransaction/helpers/getScResultsMessages.ts | 4 +--- .../helpers/tests/getScResultsMessages.test.ts | 2 +- src/utils/transactions/parsers/getTransactionMessages.ts | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getScResultsMessages.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getScResultsMessages.ts index 9e9d8a9..d5e8ae4 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getScResultsMessages.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/getScResultsMessages.ts @@ -1,8 +1,6 @@ import { ServerTransactionType } from 'types'; -export default function getScResultsMessages( - transaction: ServerTransactionType -) { +export function getScResultsMessages(transaction: ServerTransactionType) { return ( transaction?.results ?.map((result) => result.returnMessage) diff --git a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getScResultsMessages.test.ts b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getScResultsMessages.test.ts index 15c08c9..f16558c 100644 --- a/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getScResultsMessages.test.ts +++ b/src/utils/transactions/parsers/getInterpretedTransaction/helpers/tests/getScResultsMessages.test.ts @@ -1,5 +1,5 @@ import { ResultType, ServerTransactionType } from 'types'; -import getScResultsMessages from '../getScResultsMessages'; +import { getScResultsMessages } from '../getScResultsMessages'; import { baseTransactionMock } from './base-transaction-mock'; describe('getScResultsMessages', () => { diff --git a/src/utils/transactions/parsers/getTransactionMessages.ts b/src/utils/transactions/parsers/getTransactionMessages.ts index cf8a528..c466be4 100644 --- a/src/utils/transactions/parsers/getTransactionMessages.ts +++ b/src/utils/transactions/parsers/getTransactionMessages.ts @@ -1,9 +1,9 @@ import { InterpretedTransactionType } from 'types/serverTransactions.types'; import { getOperationsMessages, - getReceiptMessage + getReceiptMessage, + getScResultsMessages } from './getInterpretedTransaction'; -import getScResultsMessages from './getInterpretedTransaction/helpers/getScResultsMessages'; export function getTransactionMessages( transaction: InterpretedTransactionType From 7d7723d5e8bf4e6843d209d83508f01a9bdab81d Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Wed, 4 Dec 2024 18:41:01 +0200 Subject: [PATCH 13/23] refactor --- ...apper.test.tsx => transactionActionUnwrapper.test.ts} | 0 src/utils/window/addOriginToLocationPath.ts | 4 ++-- src/utils/window/clearNavigationHistory.ts | 4 ++-- src/utils/window/getDefaultCallbackUrl.ts | 4 ++-- src/utils/window/getIsAuthRoute.ts | 9 +++------ src/utils/window/getWindowLocation.ts | 4 ++-- src/utils/window/isWindowAvailable copy.ts | 5 +++-- src/utils/window/isWindowAvailable.ts | 5 +++-- src/utils/window/parseNavigationParams.ts | 6 +++--- src/utils/window/removeSearchParamsFromUrl.ts | 6 +++--- src/utils/window/sanitizeCallbackUrl.ts | 6 +++--- 11 files changed, 26 insertions(+), 27 deletions(-) rename src/utils/transactions/parsers/transactionActionUnwrapper/tests/{transactionActionUnwrapper.test.tsx => transactionActionUnwrapper.test.ts} (100%) diff --git a/src/utils/transactions/parsers/transactionActionUnwrapper/tests/transactionActionUnwrapper.test.tsx b/src/utils/transactions/parsers/transactionActionUnwrapper/tests/transactionActionUnwrapper.test.ts similarity index 100% rename from src/utils/transactions/parsers/transactionActionUnwrapper/tests/transactionActionUnwrapper.test.tsx rename to src/utils/transactions/parsers/transactionActionUnwrapper/tests/transactionActionUnwrapper.test.ts diff --git a/src/utils/window/addOriginToLocationPath.ts b/src/utils/window/addOriginToLocationPath.ts index ffd408d..d838333 100644 --- a/src/utils/window/addOriginToLocationPath.ts +++ b/src/utils/window/addOriginToLocationPath.ts @@ -1,6 +1,6 @@ import { getWindowLocation } from './getWindowLocation'; -export const addOriginToLocationPath = (path = '') => { +export function addOriginToLocationPath(path = '') { const location = getWindowLocation(); const isHrefUrl = path.startsWith('http') || path.startsWith('www.'); @@ -12,4 +12,4 @@ export const addOriginToLocationPath = (path = '') => { } return `${location.origin}/${path.replace('/', '')}`; -}; +} diff --git a/src/utils/window/clearNavigationHistory.ts b/src/utils/window/clearNavigationHistory.ts index e6a1ab8..8759363 100644 --- a/src/utils/window/clearNavigationHistory.ts +++ b/src/utils/window/clearNavigationHistory.ts @@ -1,6 +1,6 @@ import { getWindowLocation } from './getWindowLocation'; -export const clearNavigationHistory = (remainingParams: any) => { +export function clearNavigationHistory(remainingParams: any) { const newUrlParams = new URLSearchParams(remainingParams).toString(); const { pathname, hash } = getWindowLocation(); const newSearch = newUrlParams ? `?${newUrlParams}` : ''; @@ -9,4 +9,4 @@ export const clearNavigationHistory = (remainingParams: any) => { setTimeout(() => { window?.history.replaceState({}, document?.title, fullPath); }); -}; +} diff --git a/src/utils/window/getDefaultCallbackUrl.ts b/src/utils/window/getDefaultCallbackUrl.ts index d5e149b..f72c9cc 100644 --- a/src/utils/window/getDefaultCallbackUrl.ts +++ b/src/utils/window/getDefaultCallbackUrl.ts @@ -1,7 +1,7 @@ import { getWindowLocation } from './index'; -export const getDefaultCallbackUrl = () => { +export function getDefaultCallbackUrl() { const { pathname, search, hash } = getWindowLocation(); return `${pathname ?? ''}${search ?? ''}${hash ?? ''}`; -}; +} diff --git a/src/utils/window/getIsAuthRoute.ts b/src/utils/window/getIsAuthRoute.ts index e74cc4c..a56d5c2 100644 --- a/src/utils/window/getIsAuthRoute.ts +++ b/src/utils/window/getIsAuthRoute.ts @@ -11,15 +11,12 @@ import { matchPath } from './matchPath'; } ] */ -export const getIsAuthRoute = < +export function getIsAuthRoute< T extends { authenticatedRoute: boolean; path: string; } ->( - routes: Array, - pathname: string -) => { +>(routes: Array, pathname: string) { const authenticatedRoutes = routes.filter(({ authenticatedRoute }) => Boolean(authenticatedRoute) ); @@ -29,4 +26,4 @@ export const getIsAuthRoute = < ); return isOnAuthenticatedRoute; -}; +} diff --git a/src/utils/window/getWindowLocation.ts b/src/utils/window/getWindowLocation.ts index e5a415d..ab2d05e 100644 --- a/src/utils/window/getWindowLocation.ts +++ b/src/utils/window/getWindowLocation.ts @@ -8,7 +8,7 @@ type GetWindowLocationType = { search: string; }; -export const getWindowLocation = (): GetWindowLocationType => { +export function getWindowLocation(): GetWindowLocationType { const isAvailable = isWindowAvailable(); if (!isAvailable) { @@ -32,4 +32,4 @@ export const getWindowLocation = (): GetWindowLocationType => { href, search }; -}; +} diff --git a/src/utils/window/isWindowAvailable copy.ts b/src/utils/window/isWindowAvailable copy.ts index 46354e5..cd0f964 100644 --- a/src/utils/window/isWindowAvailable copy.ts +++ b/src/utils/window/isWindowAvailable copy.ts @@ -1,2 +1,3 @@ -export const isWindowAvailable = () => - typeof window != 'undefined' && typeof window?.location != 'undefined'; +export function isWindowAvailable() { + return typeof window != 'undefined' && typeof window?.location != 'undefined'; +} diff --git a/src/utils/window/isWindowAvailable.ts b/src/utils/window/isWindowAvailable.ts index 46354e5..cd0f964 100644 --- a/src/utils/window/isWindowAvailable.ts +++ b/src/utils/window/isWindowAvailable.ts @@ -1,2 +1,3 @@ -export const isWindowAvailable = () => - typeof window != 'undefined' && typeof window?.location != 'undefined'; +export function isWindowAvailable() { + return typeof window != 'undefined' && typeof window?.location != 'undefined'; +} diff --git a/src/utils/window/parseNavigationParams.ts b/src/utils/window/parseNavigationParams.ts index 6ec47e0..6949d20 100644 --- a/src/utils/window/parseNavigationParams.ts +++ b/src/utils/window/parseNavigationParams.ts @@ -16,10 +16,10 @@ const defaultOptions: ParseNavigationParamsOptionsType = { * @param options.removeParams allows removing params from URL search object * @returns the selected params, search object with removed params, and the `clearNavigationHistory` helper */ -export const parseNavigationParams = ( +export function parseNavigationParams( preserveParams: string[], options = defaultOptions -) => { +) { let params: Record = {}; const defaultSearch = isWindowAvailable() ? window.location.search : ''; const search = options.search ?? defaultSearch; @@ -54,4 +54,4 @@ export const parseNavigationParams = ( params, clearNavigationHistory: () => clearNavigationHistory(params) }; -}; +} diff --git a/src/utils/window/removeSearchParamsFromUrl.ts b/src/utils/window/removeSearchParamsFromUrl.ts index 5240867..bb5fee1 100644 --- a/src/utils/window/removeSearchParamsFromUrl.ts +++ b/src/utils/window/removeSearchParamsFromUrl.ts @@ -8,10 +8,10 @@ interface RemoveSearchParamsFromUrlParamsType { search?: string; } -export const removeSearchParamsFromUrl = ({ +export function removeSearchParamsFromUrl({ removeParams, search -}: RemoveSearchParamsFromUrlParamsType) => { +}: RemoveSearchParamsFromUrlParamsType) { const windowSearch = isWindowAvailable() ? window.location.search : ''; const defaultSearch = search ?? windowSearch; @@ -33,4 +33,4 @@ export const removeSearchParamsFromUrl = ({ clearNavigationHistory(remainingParams); return remainingParams; -}; +} diff --git a/src/utils/window/sanitizeCallbackUrl.ts b/src/utils/window/sanitizeCallbackUrl.ts index bea5ea4..577f4f8 100644 --- a/src/utils/window/sanitizeCallbackUrl.ts +++ b/src/utils/window/sanitizeCallbackUrl.ts @@ -1,7 +1,7 @@ -export const sanitizeCallbackUrl = ( +export function sanitizeCallbackUrl( targetURL: string, vulnerableItems: string[] = ['address'] -) => { +) { const url = new URL(targetURL); const params = new URLSearchParams(url.search); @@ -19,4 +19,4 @@ export const sanitizeCallbackUrl = ( return `${url.origin}${pathname}${questionMark}${params.toString()}${ url.hash }`; -}; +} From d5d96f234f980b11ae5b5119c1d040ea9f45371c Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Wed, 4 Dec 2024 18:41:16 +0200 Subject: [PATCH 14/23] CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bc0405..77b764d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- [Added transaction interpretation utils](https://github.com/multiversx/mx-sdk-dapp-core/pull/38) - [Added custom web socket url support](https://github.com/multiversx/mx-sdk-dapp-core/pull/35) - [Metamask integration](https://github.com/multiversx/mx-sdk-dapp-core/pull/27) - [Extension integration](https://github.com/multiversx/mx-sdk-dapp-core/pull/26) From 025f6e576aba1872f7886e28a69fc4c6bf9d5ae5 Mon Sep 17 00:00:00 2001 From: "razvan.tomegea" Date: Wed, 4 Dec 2024 18:41:59 +0200 Subject: [PATCH 15/23] pre-merge-unit-tests.yml --- .github/workflows/pre-merge-unit-tests.yml | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/pre-merge-unit-tests.yml diff --git a/.github/workflows/pre-merge-unit-tests.yml b/.github/workflows/pre-merge-unit-tests.yml new file mode 100644 index 0000000..fbdac28 --- /dev/null +++ b/.github/workflows/pre-merge-unit-tests.yml @@ -0,0 +1,40 @@ +name: "Jest Unit Tests" +on: + pull_request: + branches: [main] + paths: + - 'src/**' + - '**.js' + - '**.ts' + - '**.json' + repository_dispatch: + types: run-unit-tests + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + run-unit-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.ref }} + if: ${{ !github.event.pull_request.draft }} + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Setup yarn + run: npm install -g yarn + - name: Clean up + run: | + rm -rf node_modules build + - name: Install Dependencies + run: yarn install + - name: Build + run: yarn build + - name: Run unit tests + run: yarn test --silent From ef82ddfe485e47bc4e48e2958fc162cc2db45ac5 Mon Sep 17 00:00:00 2001 From: Tudor Morar Date: Thu, 5 Dec 2024 11:27:52 +0200 Subject: [PATCH 16/23] Fix config 1 --- src/core/providers/helpers/getConfig.ts | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/core/providers/helpers/getConfig.ts b/src/core/providers/helpers/getConfig.ts index 70a2b92..20f561e 100644 --- a/src/core/providers/helpers/getConfig.ts +++ b/src/core/providers/helpers/getConfig.ts @@ -2,13 +2,26 @@ import type { LedgerConnectModal } from '@multiversx/sdk-dapp-core-ui/dist/compo import { defineCustomElements } from '@multiversx/sdk-dapp-core-ui/loader'; import { safeWindow } from 'constants/index'; import { + IEventBus, IProviderConfig, + IProviderConfigUI, ProviderTypeEnum } from '../types/providerFactory.types'; -export const getConfig = async (config: IProviderConfig = {}) => { +const UI: IProviderConfigUI = { + [ProviderTypeEnum.ledger]: { + eventBus: {} as IEventBus, + mount: () => { + throw new Error('mount not implemented'); + } + } +}; + +const defaultConfig = { UI }; + +export const getConfig = async (config: IProviderConfig = defaultConfig) => { if (!safeWindow.document) { - return config; + return { ...defaultConfig, ...config }; } defineCustomElements(safeWindow); @@ -19,7 +32,7 @@ export const getConfig = async (config: IProviderConfig = {}) => { await customElements.whenDefined('ledger-connect-modal'); const eventBus = await ledgerModalElement.getEventBus(); - const ui = { + const UI = { [ProviderTypeEnum.ledger]: { eventBus, mount: () => { @@ -30,9 +43,9 @@ export const getConfig = async (config: IProviderConfig = {}) => { return { ...config, - ui: { - ...ui, - ...config.ui + UI: { + ...UI, + ...config.UI } }; }; From da7adaaf45c403919ad23cb1c29befac47069d30 Mon Sep 17 00:00:00 2001 From: Tudor Morar Date: Thu, 5 Dec 2024 11:28:19 +0200 Subject: [PATCH 17/23] Fix config 2 --- .../providers/types/providerFactory.types.ts | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/core/providers/types/providerFactory.types.ts b/src/core/providers/types/providerFactory.types.ts index e95466c..a51b0bc 100644 --- a/src/core/providers/types/providerFactory.types.ts +++ b/src/core/providers/types/providerFactory.types.ts @@ -1,7 +1,8 @@ import type { IDAppProviderBase } from '@multiversx/sdk-dapp-utils'; // @ts-ignore -export interface IProvider extends IDAppProviderBase { +export interface IProvider + extends IDAppProviderBase { init: () => Promise; login: (options?: { callbackUrl?: string; token?: string }) => Promise<{ address: string; @@ -12,7 +13,7 @@ export interface IProvider extends IDAppProviderBase { }>; logout: () => Promise; setShouldShowConsentPopup?: (shouldShow: boolean) => void; - getType: () => ProviderTypeEnum; + getType: () => T; getAddress(): Promise; // TODO will be removed as soon as the new login method is implemented in the same way for all providers getTokenLoginSignature(): string | undefined; @@ -26,16 +27,18 @@ export interface IEventBus { unsubscribe(event: string, callback: Function): void; } +export interface IProviderConfigUI { + ledger: { + eventBus: IEventBus; + mount: () => void; + }; +} + export interface IProviderConfig { account?: { address: string; }; - ui?: { - ledger: { - eventBus: IEventBus; - mount: () => void; - }; - }; + UI?: IProviderConfigUI; } export enum ProviderTypeEnum { @@ -47,13 +50,6 @@ export enum ProviderTypeEnum { opera = 'opera', metamask = 'metamask', passkey = 'passkey', - webhook = 'webhook', custom = 'custom', none = '' } - -export interface IProviderFactory { - type: ProviderTypeEnum; - config?: IProviderConfig; - customProvider?: IProvider; -} From 4d53f813fce4382ed5b47431e50b48973ea82c60 Mon Sep 17 00:00:00 2001 From: Tudor Morar Date: Thu, 5 Dec 2024 11:29:17 +0200 Subject: [PATCH 18/23] Fix config 3 --- src/core/providers/ProviderFactory.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/core/providers/ProviderFactory.ts b/src/core/providers/ProviderFactory.ts index d7427be..72397c6 100644 --- a/src/core/providers/ProviderFactory.ts +++ b/src/core/providers/ProviderFactory.ts @@ -10,11 +10,7 @@ import { createExtensionProvider } from './helpers/extension/createExtensionProv import { getConfig } from './helpers/getConfig'; import { createIframeProvider } from './helpers/iframe/createIframeProvider'; import { createLedgerProvider } from './helpers/ledger/createLedgerProvider'; -import { - IProvider, - IProviderFactory, - ProviderTypeEnum -} from './types/providerFactory.types'; +import { IProvider, ProviderTypeEnum } from './types/providerFactory.types'; export class ProviderFactory { public async create({ @@ -23,7 +19,7 @@ export class ProviderFactory { customProvider }: IProviderFactory): Promise { let createdProvider: IProvider | null = null; - const { account, ui } = await getConfig(userConfig); + const { account, UI } = await getConfig(userConfig); switch (type) { case ProviderTypeEnum.extension: { @@ -48,8 +44,8 @@ export class ProviderFactory { case ProviderTypeEnum.ledger: { const ledgerProvider = await createLedgerProvider( - ui.ledger.eventBus, - ui.ledger.mount + UI.ledger.eventBus, + UI.ledger.mount ); if (!ledgerProvider) { From 13eaebdf3240aceb5ee050d85140ad36b1604abf Mon Sep 17 00:00:00 2001 From: Tudor Morar Date: Thu, 5 Dec 2024 12:22:37 +0200 Subject: [PATCH 19/23] Work on custom providers --- src/core/methods/initApp/initApp.ts | 6 ++- src/core/methods/initApp/initApp.types.ts | 6 +++ src/core/providers/ProviderFactory.ts | 45 ++++++++++++------- src/core/providers/helpers/getConfig.ts | 25 +++++------ .../helpers/ledger/createLedgerProvider.ts | 14 +++--- src/core/providers/helpers/restoreProvider.ts | 4 +- src/core/providers/helpers/utils.ts | 3 -- .../providers/types/providerFactory.types.ts | 4 +- 8 files changed, 60 insertions(+), 47 deletions(-) diff --git a/src/core/methods/initApp/initApp.ts b/src/core/methods/initApp/initApp.ts index c83d67e..6829f98 100644 --- a/src/core/methods/initApp/initApp.ts +++ b/src/core/methods/initApp/initApp.ts @@ -1,4 +1,5 @@ import { restoreProvider } from 'core/providers/helpers/restoreProvider'; +import { ProviderFactory } from 'core/providers/ProviderFactory'; import { getDefaultNativeAuthConfig } from 'services/nativeAuth/methods/getDefaultNativeAuthConfig'; import { NativeAuthConfigType } from 'services/nativeAuth/nativeAuth.types'; import { initializeNetwork } from 'store/actions'; @@ -30,7 +31,8 @@ const defaultInitAppProps = { * */ export async function initApp({ storage = defaultInitAppProps.storage, - dAppConfig + dAppConfig, + customProviders }: InitAppType) { initStore(storage.getStorageCallback); @@ -57,6 +59,8 @@ export async function initApp({ const isLoggedIn = getIsLoggedIn(); + ProviderFactory.customProviders(customProviders || []); + if (isLoggedIn) { await restoreProvider(); await registerWebsocketListener(); diff --git a/src/core/methods/initApp/initApp.types.ts b/src/core/methods/initApp/initApp.types.ts index d53d8e3..1ef6bab 100644 --- a/src/core/methods/initApp/initApp.types.ts +++ b/src/core/methods/initApp/initApp.types.ts @@ -1,3 +1,4 @@ +import { IProvider } from 'core/providers/types/providerFactory.types'; import { NativeAuthConfigType } from 'services/nativeAuth/nativeAuth.types'; import { StorageCallback } from 'store/storage'; import { EnvironmentsEnum } from 'types/enums.types'; @@ -49,4 +50,9 @@ export type InitAppType = { getStorageCallback: StorageCallback; }; dAppConfig: DappConfigType; + customProviders?: Array<{ + name: string; + icon: string; + class: IProvider; + }>; }; diff --git a/src/core/providers/ProviderFactory.ts b/src/core/providers/ProviderFactory.ts index 72397c6..7eef767 100644 --- a/src/core/providers/ProviderFactory.ts +++ b/src/core/providers/ProviderFactory.ts @@ -13,11 +13,28 @@ import { createLedgerProvider } from './helpers/ledger/createLedgerProvider'; import { IProvider, ProviderTypeEnum } from './types/providerFactory.types'; export class ProviderFactory { - public async create({ + private static _customProviders: Array<{ + name: string; + icon: string; + class: IProvider; + }> = []; + + public static customProviders( + providers: { + name: string; + icon: string; + class: IProvider; + }[] + ) { + this._customProviders = providers; + } + + public static async create({ type, - config: userConfig, - customProvider - }: IProviderFactory): Promise { + config: userConfig + }: any): Promise { + // IProviderFactory + let createdProvider: IProvider | null = null; const { account, UI } = await getConfig(userConfig); @@ -43,10 +60,7 @@ export class ProviderFactory { } case ProviderTypeEnum.ledger: { - const ledgerProvider = await createLedgerProvider( - UI.ledger.eventBus, - UI.ledger.mount - ); + const ledgerProvider = await createLedgerProvider(UI.ledger.mount); if (!ledgerProvider) { throw new Error('Unable to create ledger provider'); @@ -102,16 +116,15 @@ export class ProviderFactory { break; } - case ProviderTypeEnum.custom: { - if (!customProvider) { - throw new Error('Unable to create custom provider provider'); - } - createdProvider = customProvider; + default: { + this._customProviders.forEach((customProvider) => { + if (customProvider.name === type) { + createdProvider = customProvider.class; + createdProvider.getType = () => type; + } + }); break; } - - default: - break; } if (!createdProvider) { diff --git a/src/core/providers/helpers/getConfig.ts b/src/core/providers/helpers/getConfig.ts index 20f561e..e937da4 100644 --- a/src/core/providers/helpers/getConfig.ts +++ b/src/core/providers/helpers/getConfig.ts @@ -2,7 +2,6 @@ import type { LedgerConnectModal } from '@multiversx/sdk-dapp-core-ui/dist/compo import { defineCustomElements } from '@multiversx/sdk-dapp-core-ui/loader'; import { safeWindow } from 'constants/index'; import { - IEventBus, IProviderConfig, IProviderConfigUI, ProviderTypeEnum @@ -10,7 +9,6 @@ import { const UI: IProviderConfigUI = { [ProviderTypeEnum.ledger]: { - eventBus: {} as IEventBus, mount: () => { throw new Error('mount not implemented'); } @@ -24,19 +22,18 @@ export const getConfig = async (config: IProviderConfig = defaultConfig) => { return { ...defaultConfig, ...config }; } - defineCustomElements(safeWindow); - const ledgerModalElement = document.createElement( - 'ledger-connect-modal' - ) as LedgerConnectModal; - document.body.appendChild(ledgerModalElement); - await customElements.whenDefined('ledger-connect-modal'); - const eventBus = await ledgerModalElement.getEventBus(); - const UI = { [ProviderTypeEnum.ledger]: { - eventBus, - mount: () => { + mount: async () => { + defineCustomElements(safeWindow); + const ledgerModalElement = document.createElement( + 'ledger-connect-modal' + ) as LedgerConnectModal; + document.body.appendChild(ledgerModalElement); + + const eventBus = await ledgerModalElement.getEventBus(); + return eventBus; } } }; @@ -44,8 +41,8 @@ export const getConfig = async (config: IProviderConfig = defaultConfig) => { return { ...config, UI: { - ...UI, - ...config.UI + ...defaultConfig.UI, + ...UI } }; }; diff --git a/src/core/providers/helpers/ledger/createLedgerProvider.ts b/src/core/providers/helpers/ledger/createLedgerProvider.ts index c200e7a..d2e476c 100644 --- a/src/core/providers/helpers/ledger/createLedgerProvider.ts +++ b/src/core/providers/helpers/ledger/createLedgerProvider.ts @@ -18,17 +18,17 @@ import { ILedgerAccount } from './ledger.types'; const failInitializeErrorText = 'Check if the MultiversX App is open on Ledger'; export async function createLedgerProvider( - eventBus: IEventBus, - mount: () => void + mount: () => Promise ): Promise { - if (!eventBus) { - throw new Error('Event bus not provided for Ledger provider'); - } - const shouldInitiateLogin = !getIsLoggedIn(); + let eventBus: IEventBus | undefined; if (shouldInitiateLogin) { - mount?.(); + eventBus = await mount?.(); + } + + if (!eventBus) { + throw new Error('Event bus not provided for Ledger provider'); } const manager = LedgerConnectStateManager.getInstance(eventBus); diff --git a/src/core/providers/helpers/restoreProvider.ts b/src/core/providers/helpers/restoreProvider.ts index 3e352ef..9a4ff0c 100644 --- a/src/core/providers/helpers/restoreProvider.ts +++ b/src/core/providers/helpers/restoreProvider.ts @@ -19,9 +19,7 @@ export async function restoreProvider() { } }; - const factory = new ProviderFactory(); - - const provider = await factory.create({ + const provider = await ProviderFactory.create({ type, config }); diff --git a/src/core/providers/helpers/utils.ts b/src/core/providers/helpers/utils.ts index 89e50f3..2935c6a 100644 --- a/src/core/providers/helpers/utils.ts +++ b/src/core/providers/helpers/utils.ts @@ -2,7 +2,6 @@ import { ExtensionProvider } from '@multiversx/sdk-extension-provider'; import { HWProvider } from '@multiversx/sdk-hw-provider'; import { MetamaskProvider } from '@multiversx/sdk-metamask-provider/out/metamaskProvider'; import { OperaProvider } from '@multiversx/sdk-opera-provider'; -import { WalletProvider } from '@multiversx/sdk-web-wallet-provider'; import { ProviderTypeEnum } from 'core/providers/types/providerFactory.types'; import { CrossWindowProvider } from 'lib/sdkWebWalletCrossWindowProvider'; import { WalletConnectV2Provider } from 'utils/walletconnect/__sdkWalletconnectProvider'; @@ -12,8 +11,6 @@ export function getProviderType( provider?: TProvider | null ): ProviderTypeEnum { switch (provider?.constructor) { - case WalletProvider: - return ProviderTypeEnum.webhook; // TODO: remove? case WalletConnectV2Provider: return ProviderTypeEnum.walletConnect; case HWProvider: diff --git a/src/core/providers/types/providerFactory.types.ts b/src/core/providers/types/providerFactory.types.ts index a51b0bc..9484268 100644 --- a/src/core/providers/types/providerFactory.types.ts +++ b/src/core/providers/types/providerFactory.types.ts @@ -29,8 +29,7 @@ export interface IEventBus { export interface IProviderConfigUI { ledger: { - eventBus: IEventBus; - mount: () => void; + mount: () => Promise; }; } @@ -50,6 +49,5 @@ export enum ProviderTypeEnum { opera = 'opera', metamask = 'metamask', passkey = 'passkey', - custom = 'custom', none = '' } From bcfa723464ee355834248a4776960235870b1c09 Mon Sep 17 00:00:00 2001 From: Tudor Morar Date: Thu, 5 Dec 2024 13:40:51 +0200 Subject: [PATCH 20/23] Code compiling --- src/core/methods/initApp/initApp.ts | 8 +++++++- src/core/providers/ProviderFactory.ts | 10 ++++++---- src/core/providers/types/providerFactory.types.ts | 5 +++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/core/methods/initApp/initApp.ts b/src/core/methods/initApp/initApp.ts index 6829f98..83ae383 100644 --- a/src/core/methods/initApp/initApp.ts +++ b/src/core/methods/initApp/initApp.ts @@ -1,3 +1,4 @@ +import { safeWindow } from 'constants/index'; import { restoreProvider } from 'core/providers/helpers/restoreProvider'; import { ProviderFactory } from 'core/providers/ProviderFactory'; import { getDefaultNativeAuthConfig } from 'services/nativeAuth/methods/getDefaultNativeAuthConfig'; @@ -59,7 +60,12 @@ export async function initApp({ const isLoggedIn = getIsLoggedIn(); - ProviderFactory.customProviders(customProviders || []); + const usedProviders = [ + ...((safeWindow as any)?.multiversx?.providers || []), + ...(customProviders || []) + ]; + + ProviderFactory.customProviders(usedProviders || []); if (isLoggedIn) { await restoreProvider(); diff --git a/src/core/providers/ProviderFactory.ts b/src/core/providers/ProviderFactory.ts index 7eef767..30f4230 100644 --- a/src/core/providers/ProviderFactory.ts +++ b/src/core/providers/ProviderFactory.ts @@ -10,7 +10,11 @@ import { createExtensionProvider } from './helpers/extension/createExtensionProv import { getConfig } from './helpers/getConfig'; import { createIframeProvider } from './helpers/iframe/createIframeProvider'; import { createLedgerProvider } from './helpers/ledger/createLedgerProvider'; -import { IProvider, ProviderTypeEnum } from './types/providerFactory.types'; +import { + IProvider, + IProviderFactory, + ProviderTypeEnum +} from './types/providerFactory.types'; export class ProviderFactory { private static _customProviders: Array<{ @@ -32,9 +36,7 @@ export class ProviderFactory { public static async create({ type, config: userConfig - }: any): Promise { - // IProviderFactory - + }: IProviderFactory): Promise { let createdProvider: IProvider | null = null; const { account, UI } = await getConfig(userConfig); diff --git a/src/core/providers/types/providerFactory.types.ts b/src/core/providers/types/providerFactory.types.ts index 9484268..eee3d8d 100644 --- a/src/core/providers/types/providerFactory.types.ts +++ b/src/core/providers/types/providerFactory.types.ts @@ -51,3 +51,8 @@ export enum ProviderTypeEnum { passkey = 'passkey', none = '' } + +export interface IProviderFactory { + type: ProviderTypeEnum; + config?: IProviderConfig; +} From d1b1d938ef0f09cfc207b7320e490fee0ce1e02b Mon Sep 17 00:00:00 2001 From: Tudor Morar Date: Thu, 5 Dec 2024 13:44:51 +0200 Subject: [PATCH 21/23] Improve custom providers --- src/core/methods/initApp/initApp.types.ts | 8 ++------ src/core/providers/ProviderFactory.ts | 17 ++++------------- .../providers/types/providerFactory.types.ts | 6 ++++++ 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/core/methods/initApp/initApp.types.ts b/src/core/methods/initApp/initApp.types.ts index 1ef6bab..69dbc89 100644 --- a/src/core/methods/initApp/initApp.types.ts +++ b/src/core/methods/initApp/initApp.types.ts @@ -1,4 +1,4 @@ -import { IProvider } from 'core/providers/types/providerFactory.types'; +import { ICustomProvider } from 'core/providers/types/providerFactory.types'; import { NativeAuthConfigType } from 'services/nativeAuth/nativeAuth.types'; import { StorageCallback } from 'store/storage'; import { EnvironmentsEnum } from 'types/enums.types'; @@ -50,9 +50,5 @@ export type InitAppType = { getStorageCallback: StorageCallback; }; dAppConfig: DappConfigType; - customProviders?: Array<{ - name: string; - icon: string; - class: IProvider; - }>; + customProviders?: ICustomProvider[]; }; diff --git a/src/core/providers/ProviderFactory.ts b/src/core/providers/ProviderFactory.ts index 30f4230..3214974 100644 --- a/src/core/providers/ProviderFactory.ts +++ b/src/core/providers/ProviderFactory.ts @@ -11,25 +11,16 @@ import { getConfig } from './helpers/getConfig'; import { createIframeProvider } from './helpers/iframe/createIframeProvider'; import { createLedgerProvider } from './helpers/ledger/createLedgerProvider'; import { + ICustomProvider, IProvider, IProviderFactory, ProviderTypeEnum } from './types/providerFactory.types'; export class ProviderFactory { - private static _customProviders: Array<{ - name: string; - icon: string; - class: IProvider; - }> = []; - - public static customProviders( - providers: { - name: string; - icon: string; - class: IProvider; - }[] - ) { + private static _customProviders: ICustomProvider[] = []; + + public static customProviders(providers: ICustomProvider[]) { this._customProviders = providers; } diff --git a/src/core/providers/types/providerFactory.types.ts b/src/core/providers/types/providerFactory.types.ts index eee3d8d..0d0e73d 100644 --- a/src/core/providers/types/providerFactory.types.ts +++ b/src/core/providers/types/providerFactory.types.ts @@ -56,3 +56,9 @@ export interface IProviderFactory { type: ProviderTypeEnum; config?: IProviderConfig; } + +export interface ICustomProvider { + name: string; + icon: string; + class: IProvider; +} From 1966b1b67e977d2522675d6e6427985418b59f0a Mon Sep 17 00:00:00 2001 From: Tudor Morar Date: Thu, 5 Dec 2024 14:18:49 +0200 Subject: [PATCH 22/23] Call with config --- .../DappProvider/helpers/logout/logout.ts | 4 +-- .../helpers/signMessage/signMessage.ts | 4 +-- src/core/providers/ProviderFactory.ts | 9 +++--- src/core/providers/helpers/utils.ts | 30 ------------------- .../providers/types/providerFactory.types.ts | 7 +++-- 5 files changed, 12 insertions(+), 42 deletions(-) delete mode 100644 src/core/providers/helpers/utils.ts diff --git a/src/core/providers/DappProvider/helpers/logout/logout.ts b/src/core/providers/DappProvider/helpers/logout/logout.ts index 2e6476c..73f3cc9 100644 --- a/src/core/providers/DappProvider/helpers/logout/logout.ts +++ b/src/core/providers/DappProvider/helpers/logout/logout.ts @@ -1,5 +1,4 @@ import { getAddress } from 'core/methods/account/getAddress'; -import { getProviderType } from 'core/providers/helpers/utils'; import { IProvider, ProviderTypeEnum @@ -48,7 +47,6 @@ export async function logout({ } }: IProviderLogout) { let address = getAddress(); - const providerType = getProviderType(provider); if (options.shouldBroadcastLogoutAcrossTabs) { broadcastLogoutAcrossTabs(address); @@ -59,7 +57,7 @@ export async function logout({ if ( options.hasConsentPopup && - providerType === ProviderTypeEnum.crossWindow + provider.getType() === ProviderTypeEnum.crossWindow ) { (provider as unknown as CrossWindowProvider).setShouldShowConsentPopup( true diff --git a/src/core/providers/DappProvider/helpers/signMessage/signMessage.ts b/src/core/providers/DappProvider/helpers/signMessage/signMessage.ts index 7cf3e8e..ed21f05 100644 --- a/src/core/providers/DappProvider/helpers/signMessage/signMessage.ts +++ b/src/core/providers/DappProvider/helpers/signMessage/signMessage.ts @@ -1,6 +1,5 @@ import { Message, Address } from '@multiversx/sdk-core'; import { getAddress } from 'core/methods/account/getAddress'; -import { getProviderType } from 'core/providers/helpers/utils'; import { IProvider, ProviderTypeEnum @@ -22,7 +21,6 @@ export async function signMessage({ options }: SignMessageType): Promise> { const address = getAddress(); - const providerType = getProviderType(provider); const messageToSign = new Message({ address: new Address(address), @@ -31,7 +29,7 @@ export async function signMessage({ if ( options?.hasConsentPopup && - providerType === ProviderTypeEnum.crossWindow + provider.getType() === ProviderTypeEnum.crossWindow ) { (provider as unknown as CrossWindowProvider).setShouldShowConsentPopup( true diff --git a/src/core/providers/ProviderFactory.ts b/src/core/providers/ProviderFactory.ts index 3214974..9c3c2a5 100644 --- a/src/core/providers/ProviderFactory.ts +++ b/src/core/providers/ProviderFactory.ts @@ -29,7 +29,8 @@ export class ProviderFactory { config: userConfig }: IProviderFactory): Promise { let createdProvider: IProvider | null = null; - const { account, UI } = await getConfig(userConfig); + const config = await getConfig(userConfig); + const { account, UI } = config; switch (type) { case ProviderTypeEnum.extension: { @@ -110,9 +111,9 @@ export class ProviderFactory { } default: { - this._customProviders.forEach((customProvider) => { - if (customProvider.name === type) { - createdProvider = customProvider.class; + this._customProviders.forEach(async (customProvider) => { + if (customProvider.name !== type) { + createdProvider = await customProvider.constructor(config); createdProvider.getType = () => type; } }); diff --git a/src/core/providers/helpers/utils.ts b/src/core/providers/helpers/utils.ts deleted file mode 100644 index 2935c6a..0000000 --- a/src/core/providers/helpers/utils.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ExtensionProvider } from '@multiversx/sdk-extension-provider'; -import { HWProvider } from '@multiversx/sdk-hw-provider'; -import { MetamaskProvider } from '@multiversx/sdk-metamask-provider/out/metamaskProvider'; -import { OperaProvider } from '@multiversx/sdk-opera-provider'; -import { ProviderTypeEnum } from 'core/providers/types/providerFactory.types'; -import { CrossWindowProvider } from 'lib/sdkWebWalletCrossWindowProvider'; -import { WalletConnectV2Provider } from 'utils/walletconnect/__sdkWalletconnectProvider'; -import { EmptyProvider } from './emptyProvider'; - -export function getProviderType( - provider?: TProvider | null -): ProviderTypeEnum { - switch (provider?.constructor) { - case WalletConnectV2Provider: - return ProviderTypeEnum.walletConnect; - case HWProvider: - return ProviderTypeEnum.ledger; - case ExtensionProvider: - return ProviderTypeEnum.extension; - case MetamaskProvider: - return ProviderTypeEnum.metamask; - case OperaProvider: - return ProviderTypeEnum.opera; - case CrossWindowProvider: - return ProviderTypeEnum.crossWindow; - case EmptyProvider: - default: - return ProviderTypeEnum.none; - } -} diff --git a/src/core/providers/types/providerFactory.types.ts b/src/core/providers/types/providerFactory.types.ts index 0d0e73d..8b91ba3 100644 --- a/src/core/providers/types/providerFactory.types.ts +++ b/src/core/providers/types/providerFactory.types.ts @@ -57,8 +57,11 @@ export interface IProviderFactory { config?: IProviderConfig; } -export interface ICustomProvider { +export interface ICustomProvider< + T extends ProviderTypeEnum = ProviderTypeEnum +> { name: string; + type: T[keyof T]; icon: string; - class: IProvider; + constructor: (config: IProviderConfig) => Promise; } From ed1ea422b621ebf716b32d9f63e3318273498547 Mon Sep 17 00:00:00 2001 From: Tudor Morar Date: Thu, 5 Dec 2024 14:59:28 +0200 Subject: [PATCH 23/23] Project building --- src/core/providers/ProviderFactory.ts | 4 ++-- src/core/providers/types/providerFactory.types.ts | 8 +++++--- src/store/actions/loginInfo/loginInfoActions.ts | 4 +++- src/store/actions/sharedActions/sharedActions.ts | 6 ++++-- src/store/slices/loginInfo/loginInfo.types.ts | 6 ++++-- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/core/providers/ProviderFactory.ts b/src/core/providers/ProviderFactory.ts index 9c3c2a5..aa37f76 100644 --- a/src/core/providers/ProviderFactory.ts +++ b/src/core/providers/ProviderFactory.ts @@ -112,7 +112,7 @@ export class ProviderFactory { default: { this._customProviders.forEach(async (customProvider) => { - if (customProvider.name !== type) { + if (customProvider.type === type) { createdProvider = await customProvider.constructor(config); createdProvider.getType = () => type; } @@ -128,7 +128,7 @@ export class ProviderFactory { const dappProvider = new DappProvider(createdProvider); setAccountProvider(dappProvider); - setProviderType(type); + setProviderType(type as ProviderTypeEnum); return dappProvider; } diff --git a/src/core/providers/types/providerFactory.types.ts b/src/core/providers/types/providerFactory.types.ts index 8b91ba3..2063592 100644 --- a/src/core/providers/types/providerFactory.types.ts +++ b/src/core/providers/types/providerFactory.types.ts @@ -13,7 +13,7 @@ export interface IProvider }>; logout: () => Promise; setShouldShowConsentPopup?: (shouldShow: boolean) => void; - getType: () => T; + getType: () => T[keyof T] | string; getAddress(): Promise; // TODO will be removed as soon as the new login method is implemented in the same way for all providers getTokenLoginSignature(): string | undefined; @@ -52,8 +52,10 @@ export enum ProviderTypeEnum { none = '' } -export interface IProviderFactory { - type: ProviderTypeEnum; +export interface IProviderFactory< + T extends ProviderTypeEnum = ProviderTypeEnum +> { + type: T[keyof T]; config?: IProviderConfig; } diff --git a/src/store/actions/loginInfo/loginInfoActions.ts b/src/store/actions/loginInfo/loginInfoActions.ts index e9ae8ff..c8a610f 100644 --- a/src/store/actions/loginInfo/loginInfoActions.ts +++ b/src/store/actions/loginInfo/loginInfoActions.ts @@ -7,7 +7,9 @@ import { import { getStore } from 'store/store'; import { TokenLoginType } from 'types/login.types'; -export const setProviderType = (providerType: ProviderTypeEnum) => +export const setProviderType = ( + providerType: T +) => getStore().setState(({ loginInfo: state }) => { state.providerType = providerType; }); diff --git a/src/store/actions/sharedActions/sharedActions.ts b/src/store/actions/sharedActions/sharedActions.ts index 530925f..fcda2aa 100644 --- a/src/store/actions/sharedActions/sharedActions.ts +++ b/src/store/actions/sharedActions/sharedActions.ts @@ -4,9 +4,11 @@ import { resetStore } from 'store/middleware/logoutMiddleware'; import { getStore } from '../../store'; export const logoutAction = () => getStore().setState(resetStore); -export interface LoginActionPayloadType { +export interface LoginActionPayloadType< + T extends ProviderTypeEnum = ProviderTypeEnum +> { address: string; - providerType: ProviderTypeEnum; + providerType: T[keyof T]; } export const loginAction = ({ diff --git a/src/store/slices/loginInfo/loginInfo.types.ts b/src/store/slices/loginInfo/loginInfo.types.ts index 118b475..4560b12 100644 --- a/src/store/slices/loginInfo/loginInfo.types.ts +++ b/src/store/slices/loginInfo/loginInfo.types.ts @@ -17,8 +17,10 @@ export interface LoginInfoType { expires: number; } -export interface LoginInfoSliceType { - providerType: ProviderTypeEnum | null; +export interface LoginInfoSliceType< + T extends ProviderTypeEnum = ProviderTypeEnum +> { + providerType: T[keyof T] | null; walletConnectLogin: WalletConnectLoginType | null; ledgerLogin: LedgerLoginType | null; tokenLogin: TokenLoginType | null;