├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── pull_request_template.md └── workflows │ ├── changelog.yml │ ├── npm-publish.yml │ ├── pre-merge-unit-tests.yml │ └── run-template-dapps-integration.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── esbuild.js ├── eslint.config.mjs ├── jest.config.js ├── package.json ├── scripts └── template-dapps-integration.sh ├── src ├── __mocks__ │ ├── accountConfig.ts │ ├── data │ │ ├── account.ts │ │ ├── blocks.ts │ │ ├── dappConfig.ts │ │ ├── index.ts │ │ ├── networkConfig.ts │ │ ├── socketResponse.ts │ │ ├── websocketConfig.ts │ │ └── wrapEgldContract.ts │ ├── index.ts │ ├── server.ts │ └── utils │ │ ├── index.ts │ │ ├── mockWindowHistory.ts │ │ └── mockWindowLocation.ts ├── apiCalls │ ├── account │ │ ├── getAccountFromApi.ts │ │ ├── getScamAddressData.ts │ │ └── index.ts │ ├── configuration │ │ ├── getCleanApiAddress.ts │ │ ├── getGasStationMetadata.ts │ │ ├── getNetworkConfigFromApi.ts │ │ ├── getServerConfiguration.ts │ │ └── index.ts │ ├── economics │ │ └── getEconomics.ts │ ├── endpoints.ts │ ├── index.ts │ ├── tokens │ │ ├── getPersistedToken.ts │ │ ├── getPersistedTokenDetails.ts │ │ ├── getTokenDetails.ts │ │ ├── index.ts │ │ └── tokenDataStorage.ts │ ├── transactions │ │ ├── getServerTransactionsByHashes.ts │ │ ├── getTransactionByHash.ts │ │ ├── getTransactions.ts │ │ └── getTransactionsByHashes.ts │ ├── utils │ │ ├── axiosFetcher.ts │ │ ├── axiosInstance.ts │ │ ├── getCleanApiAddress.ts │ │ └── index.ts │ └── websocket │ │ ├── getWebsocketUrl.ts │ │ └── index.ts ├── constants │ ├── UITags.enum.ts │ ├── browser.constants.ts │ ├── index.ts │ ├── ledger.constants.ts │ ├── mvx.constants.ts │ ├── network.constants.ts │ ├── placeholders.constants.ts │ ├── providerFactory.constants.ts │ ├── storage.constants.ts │ ├── transactions.constants.ts │ ├── walletConnect.constants.ts │ ├── webWalletProvider.constants.ts │ ├── websocket.constants.ts │ └── window.constants.ts ├── controllers │ ├── FormatAmountController │ │ ├── FormatAmountController.ts │ │ ├── index.ts │ │ ├── tests │ │ │ └── formatAmountController.test.ts │ │ └── types.ts │ ├── TransactionsHistoryController │ │ ├── TransactionsHistoryController.ts │ │ └── index.ts │ ├── TransactionsTableController │ │ ├── TransactionsTableController.ts │ │ ├── index.ts │ │ ├── tests │ │ │ └── transactionsTableController.test.ts │ │ └── transactionsTableController.types.ts │ └── index.ts ├── core │ └── providers │ │ └── DappProvider │ │ └── helpers │ │ └── login │ │ └── helpers │ │ └── refreshNativeAuthTokenLogin.ts ├── index.ts ├── lib │ ├── sdkCore.ts │ ├── sdkDappUi.ts │ ├── sdkDappUtils.ts │ ├── sdkWebWalletCrossWindowProvider.ts │ └── sdkWebWalletIframeProvider.ts ├── managers │ ├── NotificationsFeedManager │ │ ├── NotificationsFeedManager.ts │ │ ├── index.ts │ │ └── types │ │ │ ├── index.ts │ │ │ └── notifications.types.ts │ ├── TransactionManager │ │ ├── TransactionManager.ts │ │ ├── helpers │ │ │ ├── getAreTransactionsCorssShards.ts │ │ │ ├── getToastDuration.ts │ │ │ ├── getTransactionsStatus.ts │ │ │ ├── isBatchTransaction.ts │ │ │ └── isCrossShardTransaction.ts │ │ ├── index.ts │ │ └── tests │ │ │ ├── getAreTransactionsOnCrossShards.test.ts │ │ │ ├── getToastDuration.test.ts │ │ │ ├── getTransactionsStatus.test.ts │ │ │ └── isCrossShardTransaction.test.ts │ ├── UnlockPanelManager │ │ ├── UnlockPanelManager.ts │ │ ├── UnlockPanelManager.types.ts │ │ └── index.ts │ ├── index.ts │ └── internal │ │ ├── LedgerConnectStateManager │ │ ├── LedgerConnectStateManager.ts │ │ ├── index.ts │ │ └── types │ │ │ ├── index.ts │ │ │ └── ledgerConnectEvents.enum.ts │ │ ├── LedgerIdleStateManager │ │ ├── LEDGER_STATE_MANAGER_README.md │ │ └── LedgerIdleStateManager.ts │ │ ├── PendingTransactionsStateManager │ │ ├── PendingTransactionsStateManager.ts │ │ ├── index.ts │ │ └── types │ │ │ ├── index.ts │ │ │ └── pendingTransactions.types.ts │ │ ├── SidePanelBaseManager │ │ ├── SidePanelBaseManager.ts │ │ └── index.ts │ │ ├── SignTransactionsStateManager │ │ ├── SignTransactionsStateManager.ts │ │ ├── index.ts │ │ └── types │ │ │ ├── index.ts │ │ │ └── signTransactionsPanel.types.ts │ │ ├── ToastManager │ │ ├── ToastManager.ts │ │ ├── helpers │ │ │ ├── LifetimeManager.ts │ │ │ ├── createToastsFromTransactions.ts │ │ │ ├── createTransactionToast.ts │ │ │ ├── getToastDataStateByStatus.ts │ │ │ ├── getToastTransactionsStatus.ts │ │ │ └── tests │ │ │ │ ├── baseTransactionMock.ts │ │ │ │ ├── createToastsFromTransactions.test.ts │ │ │ │ ├── createTransactionToast.test.ts │ │ │ │ ├── getToastDataStateByStatus.test.ts │ │ │ │ └── getToastTransactionsStatus.test.ts │ │ ├── tests │ │ │ └── getToastProceededStatus.test.ts │ │ └── types │ │ │ ├── index.ts │ │ │ └── toast.types.ts │ │ ├── UIBaseManager │ │ └── UIBaseManager.ts │ │ ├── WalletConnectStateManager │ │ ├── WalletConnectStateManager.ts │ │ └── index.ts │ │ └── index.ts ├── methods │ ├── account │ │ ├── getAccount.ts │ │ ├── getAccountInfo.ts │ │ ├── getAddress.ts │ │ ├── getIsLoggedIn.ts │ │ ├── getLatestNonce.ts │ │ └── tests │ │ │ └── getLatestNonce.test.ts │ ├── initApp │ │ ├── gastStationMetadata │ │ │ └── setGasStationMetadata.ts │ │ ├── initApp.ts │ │ ├── initApp.types.ts │ │ └── websocket │ │ │ ├── initializeWebsocketConnection.ts │ │ │ └── registerWebsocket.ts │ ├── loginInfo │ │ └── getLoginInfo.ts │ ├── network │ │ ├── getEgldLabel.ts │ │ ├── getExplorerAddress.ts │ │ └── getNetworkConfig.ts │ ├── trackTransactions │ │ ├── helpers │ │ │ ├── checkTransactionStatus │ │ │ │ ├── checkBatch.ts │ │ │ │ ├── checkTransactionStatus.ts │ │ │ │ ├── getPendingTransactions.ts │ │ │ │ ├── index.ts │ │ │ │ └── manageFailedTransactions.ts │ │ │ └── getPollingInterval.ts │ │ └── trackTransactions.ts │ └── transactions │ │ ├── getPendingTransactions.ts │ │ ├── getPendingTransactionsSessions.ts │ │ └── getTransactionSessions.ts ├── providers │ ├── DappProvider │ │ ├── DappProvider.ts │ │ ├── helpers │ │ │ ├── computeNonces │ │ │ │ ├── computeNonce.ts │ │ │ │ └── computeNonces.ts │ │ │ ├── login │ │ │ │ ├── helpers │ │ │ │ │ ├── accountLogin.ts │ │ │ │ │ ├── extractAddressFromToken.ts │ │ │ │ │ ├── getAccountFromToken.ts │ │ │ │ │ ├── getModifiedLoginToken.ts │ │ │ │ │ └── tests │ │ │ │ │ │ └── getModifiedLoginToken.test.ts │ │ │ │ └── login.ts │ │ │ ├── logout │ │ │ │ └── logout.ts │ │ │ ├── signErrors │ │ │ │ └── handleSignError.ts │ │ │ ├── signMessage │ │ │ │ ├── getVerifier.ts │ │ │ │ ├── signMessageWithProvider.ts │ │ │ │ ├── tests │ │ │ │ │ └── verifyMessage.test.ts │ │ │ │ └── verifyMessage.ts │ │ │ └── signTransactions │ │ │ │ └── signTransactionsWithProvider.ts │ │ └── index.ts │ ├── ProviderFactory.ts │ ├── helpers │ │ ├── accountProvider.ts │ │ ├── cancelCrossWindowAction.ts │ │ ├── clearInitiatedLogins.ts │ │ ├── emptyProvider.ts │ │ └── restoreProvider.ts │ ├── index.ts │ ├── strategies │ │ ├── BaseProviderStrategy │ │ │ └── BaseProviderStrategy.ts │ │ ├── CrossWindowProviderStrategy │ │ │ ├── CrossWindowProviderStrategy.ts │ │ │ ├── index.ts │ │ │ └── types │ │ │ │ ├── crossWindow.types.ts │ │ │ │ └── index.ts │ │ ├── ExtensionProviderStrategy │ │ │ ├── ExtensionProviderStrategy.ts │ │ │ └── index.ts │ │ ├── IframeProviderStrategy │ │ │ ├── IframeProviderStrategy.ts │ │ │ ├── index.ts │ │ │ └── types │ │ │ │ ├── iframe.types.ts │ │ │ │ └── index.ts │ │ ├── LedgerProviderStrategy │ │ │ ├── LedgerProviderStrategy.ts │ │ │ ├── helpers │ │ │ │ ├── authenticateLedgerAccount.ts │ │ │ │ ├── getAuthTokenText.ts │ │ │ │ ├── getLedgerConfiguration.ts │ │ │ │ ├── getLedgerErrorCodes.ts │ │ │ │ ├── getLedgerProvider.ts │ │ │ │ ├── getLedgerVersionOptions.ts │ │ │ │ ├── index.ts │ │ │ │ ├── initializeLedgerProvider.ts │ │ │ │ ├── secondsToTimeString.ts │ │ │ │ ├── signLedgerMessage.ts │ │ │ │ └── updateAccountsList │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── updateAccountsList.ts │ │ │ │ │ └── updateAccountsList.types.ts │ │ │ ├── index.ts │ │ │ ├── tests │ │ │ │ ├── getLedgerVersionOptions.test.ts │ │ │ │ └── secondsToTimeString.test.ts │ │ │ └── types │ │ │ │ ├── index.ts │ │ │ │ ├── ledger.types.ts │ │ │ │ └── ledgerProvider.types.ts │ │ ├── WalletConnectProviderStrategy │ │ │ ├── WalletConnectProviderStrategy.ts │ │ │ ├── index.ts │ │ │ └── types │ │ │ │ ├── index.ts │ │ │ │ └── walletConnect.types.ts │ │ ├── WebviewProviderStrategy │ │ │ ├── WebviewProviderStrategy.ts │ │ │ ├── index.ts │ │ │ └── tests │ │ │ │ └── webview.test.ts │ │ ├── helpers │ │ │ ├── getPendingTransactionsHandlers.ts │ │ │ ├── index.ts │ │ │ ├── signMessage │ │ │ │ └── signMessage.ts │ │ │ └── signTransactions │ │ │ │ ├── helpers │ │ │ │ ├── calculateFeeInFiat.ts │ │ │ │ ├── calculateFeeLimit.ts │ │ │ │ ├── getCommonData │ │ │ │ │ ├── getCommonData.ts │ │ │ │ │ ├── helpers │ │ │ │ │ │ ├── checkIsValidSender.ts │ │ │ │ │ │ ├── decodeDataField.ts │ │ │ │ │ │ ├── getExtractTransactionsInfo.ts │ │ │ │ │ │ ├── getHighlight.ts │ │ │ │ │ │ ├── getPpuOptions.ts │ │ │ │ │ │ ├── getRecommendedGasPrice.ts │ │ │ │ │ │ ├── getScCall.ts │ │ │ │ │ │ ├── getTokenType.ts │ │ │ │ │ │ ├── getTxInfoByDataField.ts │ │ │ │ │ │ └── tests │ │ │ │ │ │ │ ├── decodeDataField.test.ts │ │ │ │ │ │ │ └── getGasPriceDetails.test.ts │ │ │ │ │ └── tests │ │ │ │ │ │ ├── getCommonData.test.ts │ │ │ │ │ │ └── mockGetCommonDataInput.ts │ │ │ │ ├── getFeeData.ts │ │ │ │ ├── getMultiEsdtTransferData │ │ │ │ │ ├── getMultiEsdtTransferData.ts │ │ │ │ │ ├── helpers │ │ │ │ │ │ ├── getAllStringOccurrences.ts │ │ │ │ │ │ ├── getTokenFromData.ts │ │ │ │ │ │ ├── parseMultiEsdtTransferData.ts │ │ │ │ │ │ └── parseMultiEsdtTransferDataForMultipleTransactions.ts │ │ │ │ │ └── tests │ │ │ │ │ │ └── getMultiEsdtTransferData.test.ts │ │ │ │ ├── guardTransactions │ │ │ │ │ ├── getCrossWindowProvider.ts │ │ │ │ │ ├── getTransactionsNeedGuardianSigning.ts │ │ │ │ │ └── guardTransactions.ts │ │ │ │ ├── isTokenTransfer.ts │ │ │ │ └── tests │ │ │ │ │ ├── calculateFeeInFiat.test.ts │ │ │ │ │ └── calculateFeeLimit.test.tsx │ │ │ │ └── signTransactions.ts │ │ └── index.ts │ └── types │ │ └── providerFactory.types.ts ├── react │ ├── account │ │ ├── useGetAccount.ts │ │ ├── useGetAccountInfo.ts │ │ ├── useGetIsLoggedIn.ts │ │ └── useGetLatestNonce.ts │ ├── index.ts │ ├── loginInfo │ │ └── useGetLoginInfo.ts │ ├── network │ │ └── useGetNetworkConfig.ts │ ├── store │ │ ├── createBoundedStore.ts │ │ ├── getReactStore.ts │ │ └── useSelector.ts │ └── transactions │ │ ├── useGetPendingTransactions.ts │ │ ├── useGetPendingTransactionsSessions.ts │ │ └── useGetTransactionSessions.ts ├── services │ ├── index.ts │ └── nativeAuth │ │ ├── helpers │ │ ├── decodeLoginToken.ts │ │ ├── decodeNativeAuthToken.ts │ │ ├── getLatestBlockHash.ts │ │ ├── index.ts │ │ └── tests │ │ │ ├── decodeLoginToken.test.ts │ │ │ └── decodeNativeAuthToken.test.ts │ │ ├── index.ts │ │ ├── methods │ │ ├── buildNativeAuthConfig.ts │ │ ├── getDefaultNativeAuthConfig.ts │ │ ├── getTokenExpiration.ts │ │ ├── index.ts │ │ └── tests │ │ │ └── getTokenExpiration.test.ts │ │ ├── nativeAuth.ts │ │ ├── nativeAuth.types.ts │ │ └── tests │ │ └── nativeAuth.test.ts ├── setupTests.js ├── store │ ├── actions │ │ ├── account │ │ │ ├── accountActions.ts │ │ │ └── index.ts │ │ ├── cache │ │ │ ├── cacheActions.ts │ │ │ └── index.ts │ │ ├── config │ │ │ ├── configActions.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── loginInfo │ │ │ └── loginInfoActions.ts │ │ ├── network │ │ │ ├── index.ts │ │ │ ├── initializeNetwork.ts │ │ │ └── networkActions.ts │ │ ├── sharedActions │ │ │ ├── index.ts │ │ │ └── sharedActions.ts │ │ ├── toasts │ │ │ ├── index.ts │ │ │ └── toastsActions.ts │ │ ├── transactions │ │ │ ├── transactionStateByStatus.ts │ │ │ └── transactionsActions.ts │ │ └── ui │ │ │ ├── index.ts │ │ │ └── uiActions.ts │ ├── middleware │ │ ├── applyMiddlewares.ts │ │ ├── index.ts │ │ └── logoutMiddleware.ts │ ├── selectors │ │ ├── accountSelectors.ts │ │ ├── cacheSelector.ts │ │ ├── configSelectors.ts │ │ ├── index.ts │ │ ├── loginInfoSelectors.ts │ │ ├── networkSelectors.ts │ │ ├── storeSelector.ts │ │ ├── toastsSelectors.ts │ │ ├── transactionsSelector.ts │ │ └── uiSelectors.ts │ ├── slices │ │ ├── account │ │ │ ├── account.types.ts │ │ │ ├── accountSlice.ts │ │ │ ├── emptyAccount.ts │ │ │ └── index.ts │ │ ├── cache │ │ │ ├── cacheSlice.ts │ │ │ ├── cacheSlice.types.ts │ │ │ └── index.ts │ │ ├── config │ │ │ ├── config.types.ts │ │ │ ├── configSlice.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── loginInfo │ │ │ ├── index.ts │ │ │ ├── loginInfo.types.ts │ │ │ └── loginInfoSlice.ts │ │ ├── network │ │ │ ├── emptyNetwork.ts │ │ │ ├── index.ts │ │ │ ├── network.types.ts │ │ │ ├── networkSlice.ts │ │ │ └── networkSlice.types.ts │ │ ├── toast │ │ │ ├── index.ts │ │ │ ├── toastSlice.ts │ │ │ └── toastSlice.types.ts │ │ ├── transactions │ │ │ ├── index.ts │ │ │ ├── transactionsSlice.ts │ │ │ └── transactionsSlice.types.ts │ │ └── ui │ │ │ ├── index.ts │ │ │ ├── ui.types.ts │ │ │ └── uiSlice.ts │ ├── storage │ │ ├── inMemoryStorage.ts │ │ ├── index.ts │ │ └── storageCallback.ts │ ├── store.ts │ └── store.types.ts ├── types │ ├── account.types.ts │ ├── enums.types.ts │ ├── index.ts │ ├── login.types.ts │ ├── manager.types.ts │ ├── network.types.ts │ ├── provider.types.ts │ ├── serverTransactions.types.ts │ ├── subscriptions.type.ts │ ├── suspiciousLink.types.ts │ ├── tokens.types.ts │ ├── transaction-list-item.types.ts │ ├── transactions.types.ts │ └── websocket.types.ts └── utils │ ├── account │ ├── fetchAccount.ts │ ├── index.ts │ ├── refreshAccount.ts │ └── trimUsernameDomain.ts │ ├── asyncActions │ ├── index.ts │ └── sleep.ts │ ├── createUIElement.ts │ ├── dateTime │ ├── getUnixTimestamp.ts │ ├── getUnixTimestampWithAddedMilliseconds.ts │ ├── getUnixTimestampWithAddedSeconds.ts │ └── index.ts │ ├── decoders │ ├── base64Utils.ts │ ├── decodePart.ts │ ├── index.ts │ ├── isAscii.ts │ ├── isUtf8.ts │ ├── stringContainsNumbers.ts │ └── tests │ │ ├── base64Utils.test.ts │ │ ├── decodePart.test.ts │ │ ├── isAscii.test.ts │ │ └── isUtf8.test.ts │ ├── index.ts │ ├── network │ ├── index.ts │ └── setAxiosInterceptors.ts │ ├── operations │ ├── capitalize.ts │ ├── getUsdValue.ts │ ├── pluralize.ts │ ├── tests │ │ ├── capitalize.test.ts │ │ ├── getUsdValue.test.ts │ │ ├── pluralize.spec.ts │ │ ├── timeRemaining.test.ts │ │ └── trimAmountDecimals.spec.ts │ ├── timeRemaining.ts │ └── trimAmountDecimals.ts │ ├── retryMultipleTimes.ts │ ├── transactions │ ├── explorerUrlBuilder.ts │ ├── getActiveTransactionsStatus.ts │ ├── getExplorerLink.ts │ ├── getHumanReadableTimeFormat.ts │ ├── getInterpretedTransaction │ │ ├── getInterpretedTransaction.ts │ │ ├── getTransactionValue │ │ │ ├── constants.ts │ │ │ ├── getTransactionValue.ts │ │ │ ├── helpers │ │ │ │ ├── getEgldValueData.ts │ │ │ │ ├── getTitleText.ts │ │ │ │ ├── getTransactionActionNftText.ts │ │ │ │ ├── getTransactionTokens.ts │ │ │ │ ├── getValueFromActions.ts │ │ │ │ ├── getValueFromDataField.ts │ │ │ │ ├── getValueFromOperations.ts │ │ │ │ ├── getVisibleOperations.ts │ │ │ │ ├── index.ts │ │ │ │ └── tests │ │ │ │ │ └── getTransactionTokens.test.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── helpers │ │ │ ├── getLockedAccountName.ts │ │ │ ├── getOperationsMessages.ts │ │ │ ├── getReceiptMessage.ts │ │ │ ├── getScResultsMessages.ts │ │ │ ├── getShardText.ts │ │ │ ├── getTokenFromData.ts │ │ │ ├── getTransactionIconInfo.ts │ │ │ ├── getTransactionMessages.ts │ │ │ ├── getTransactionMethod.ts │ │ │ ├── getTransactionReceiver.ts │ │ │ ├── getTransactionReceiverAssets.ts │ │ │ ├── getTransactionStatus.ts │ │ │ ├── getTransactionTokens.ts │ │ │ ├── getTransactionTransferType.ts │ │ │ ├── index.ts │ │ │ └── tests │ │ │ │ ├── base-transaction-mock.ts │ │ │ │ ├── extended-transaction-mock.ts │ │ │ │ ├── getOperationsMessages.test.ts │ │ │ │ ├── getReceiptMessage.test.ts │ │ │ │ ├── getScResultsMessages.test.ts │ │ │ │ ├── getShardText.test.ts │ │ │ │ ├── getTokenFromData.test.ts │ │ │ │ ├── getTransactionIconInfo.test.ts │ │ │ │ ├── getTransactionMethod.test.ts │ │ │ │ ├── getTransactionReceiver.test.ts │ │ │ │ ├── getTransactionReceiverAssets.test.ts │ │ │ │ ├── getTransactionTokens.test.ts │ │ │ │ └── getTransactionTransferType.test.ts │ │ └── index.ts │ ├── getScamFlag.ts │ ├── getTransactionsHistory │ │ ├── getTransactionsHistory.ts │ │ ├── helpers │ │ │ ├── createTransactionsHistoryFromSessions.ts │ │ │ ├── getAssetAmount.ts │ │ │ ├── getAssetPrice.ts │ │ │ ├── getCachedTransactionListItem.ts │ │ │ ├── getIsTransactionInvalidOrFailed.ts │ │ │ ├── getReceiverData.ts │ │ │ ├── getTransactionAction.ts │ │ │ ├── getTransactionActionDirectionLabel.ts │ │ │ ├── getTransactionActionTransferLabel.ts │ │ │ ├── getTransactionAmount.ts │ │ │ ├── getTransactionAsset.ts │ │ │ ├── getTransactionAssets.ts │ │ │ ├── getTransactionAvatar.ts │ │ │ ├── index.ts │ │ │ ├── isTransferNftOrSft.ts │ │ │ ├── mapServerTransactionsToListItems.ts │ │ │ ├── mapTransactionToListItem.ts │ │ │ └── tests │ │ │ │ └── createTransactionsHistoryFromSessions.test.ts │ │ └── index.ts │ ├── index.ts │ ├── isGuardianTx.ts │ └── tests │ │ ├── getExplorerkLink.test.ts │ │ └── getHumanReadableTimeFormat.test.ts │ ├── validation │ ├── addressIsValid.ts │ ├── getIdentifierType.ts │ ├── hex.ts │ ├── index.ts │ ├── isContract.ts │ ├── maxDecimals.ts │ └── tests │ │ └── isContract.test.ts │ ├── walletconnect │ ├── __mocks__ │ │ └── sdkWalletconnectProvider.ts │ └── __sdkWalletconnectProvider.ts │ └── window │ ├── buildUrlParams.ts │ ├── getIsAuthRoute.ts │ ├── getIsInIframe.ts │ ├── getWindowLocation.ts │ ├── getWindowParentOrigin.ts │ ├── index.ts │ ├── isWindowAvailable.ts │ ├── matchPath.ts │ ├── sanitizeCallbackUrl.ts │ └── tests │ ├── buildUrlParams.test.ts │ ├── getIsAuthRoute.test.ts │ ├── getIsInIframe.test.ts │ ├── getWindowLocation.test.ts │ ├── getWindowParentOrigin.test.ts │ └── sanitizeCallbackUrl.test.ts ├── tsconfig.base.json ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "current" 8 | }, 9 | "modules": "commonjs" 10 | } 11 | ], 12 | "@babel/preset-typescript", 13 | "@babel/preset-react" 14 | ], 15 | "env": { 16 | "test": { 17 | "plugins": ["@babel/plugin-transform-modules-commonjs"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | node_modules/ 4 | .snapshots/ 5 | *.min.js -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Issue/Feature 2 | 3 | ### Reproduce 4 | 5 | Issue exists on version `2.` of sdk-dapp. 6 | 7 | ### Root cause 8 | 9 | ### Fix 10 | 11 | ### Additional changes 12 | 13 | ### Contains breaking changes 14 | 15 | - [x] No 16 | - [ ] Yes 17 | 18 | ### Updated CHANGELOG 19 | 20 | - [ ] No 21 | - [x] Yes 22 | 23 | ### Testing 24 | 25 | - [x] User testing 26 | - [ ] Unit tests 27 | -------------------------------------------------------------------------------- /.github/workflows/changelog.yml: -------------------------------------------------------------------------------- 1 | name: "CHANGELOG entry secretary" 2 | on: 3 | pull_request: 4 | branches: [main, development] 5 | # The specific activity types are listed here to include "labeled" and "unlabeled" 6 | # (which are not included by default for the "pull_request" trigger). 7 | # This is needed to allow skipping enforcement of the changelog in PRs with specific labels, 8 | # as defined in the (optional) "skipLabels" property. 9 | types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] 10 | 11 | jobs: 12 | # Enforces the update of a changelog file on every pull request 13 | changelog: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: dangoslen/changelog-enforcer@v3 17 | -------------------------------------------------------------------------------- /.github/workflows/pre-merge-unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: "Jest Unit Tests" 2 | on: 3 | pull_request: 4 | branches: [main] 5 | paths: 6 | - 'src/**' 7 | - '**.js' 8 | - '**.ts' 9 | - '**.json' 10 | repository_dispatch: 11 | types: run-unit-tests 12 | workflow_dispatch: 13 | 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | run-unit-tests: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v3 23 | with: 24 | ref: ${{ github.ref }} 25 | if: ${{ !github.event.pull_request.draft }} 26 | - name: Set up Node.js 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: 18 30 | - name: Setup yarn 31 | run: npm install -g yarn 32 | - name: Clean up 33 | run: | 34 | rm -rf node_modules build 35 | - name: Install Dependencies 36 | run: yarn install 37 | - name: Build 38 | run: yarn build 39 | - name: Run unit tests 40 | run: yarn test --silent 41 | -------------------------------------------------------------------------------- /.github/workflows/run-template-dapps-integration.yml: -------------------------------------------------------------------------------- 1 | name: run-template-dapps-integration 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | repository_dispatch: 7 | types: run-template-dapps-integration 8 | workflow_dispatch: 9 | 10 | jobs: 11 | run_template_dapps_integration_script: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | ref: ${{ github.ref }} 17 | - name: Run script file 18 | run: | 19 | chmod +x ./scripts/template-dapps-integration.sh 20 | ./scripts/template-dapps-integration.sh 21 | shell: bash 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | .cache 7 | *.log 8 | .DS_Store 9 | 10 | # builds 11 | build 12 | out 13 | .idea 14 | .idea/workspace.xml 15 | .rpt2_cache 16 | .yalc 17 | .husky 18 | 19 | # misc 20 | .DS_Store 21 | .env 22 | .env.local 23 | .env.development.local 24 | .env.test.local 25 | .env.production.local 26 | 27 | 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | coverage 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | 3 | .travis.yml 4 | .idea 5 | .eslintrc 6 | .eslintignore 7 | .prettierrc 8 | 9 | tsconfig.json 10 | tsconfig.test.json 11 | tsconfig.base.json 12 | jest.config.ts 13 | babel.config.js 14 | esbuild.js 15 | jest.config.js 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true, 4 | "semi": true, 5 | "tabWidth": 2, 6 | "bracketSpacing": true, 7 | "jsxBracketSameLine": false, 8 | "arrowParens": "always", 9 | "trailingComma": "none", 10 | "printWidth": 80 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ## License 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) MultiversX 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | testEnvironment: 'jsdom', 4 | testTimeout: 10000, 5 | moduleDirectories: ['node_modules', 'src'], 6 | modulePaths: ['/src'], 7 | roots: ['/src'], 8 | transform: { 9 | '^.+\\.(ts|js|tsx|jsx)$': ['@swc/jest'] 10 | }, 11 | setupFilesAfterEnv: ['/src/setupTests.js'], 12 | transformIgnorePatterns: ['node_modules/(^.+\\\\.(ts|js|tsx|jsx)$)'], 13 | testMatch: [ 14 | '**/__tests__/**/*.[jt]s?(x)', 15 | '**/?(*.)+(spec|test|bgTest).[jt]s?(x)' 16 | ], 17 | moduleFileExtensions: [ 18 | // Place tsx and ts to beginning as suggestion from Jest team 19 | // https://jestjs.io/docs/configuration#modulefileextensions-arraystring 20 | 'tsx', 21 | 'ts', 22 | 'web.js', 23 | 'js', 24 | 'web.ts', 25 | 'web.tsx', 26 | 'json', 27 | 'node' 28 | ] 29 | }; 30 | -------------------------------------------------------------------------------- /src/__mocks__/accountConfig.ts: -------------------------------------------------------------------------------- 1 | import { fallbackNetworkConfigurations } from 'constants/network.constants'; 2 | import { EnvironmentsEnum } from 'types'; 3 | 4 | export const testAddress = 5 | 'erd1dm9uxpf5awkn7uhju7zjn9lde0dhahy0qaxqqlu26xcuuw27qqrsqfmej3'; 6 | 7 | export const testNetwork = 8 | fallbackNetworkConfigurations[EnvironmentsEnum.devnet]; 9 | 10 | export const testReceiver = 11 | 'erd1qqqqqqqqqqqqqpgqp699jngundfqw07d8jzkepucvpzush6k3wvqyc44rx'; 12 | -------------------------------------------------------------------------------- /src/__mocks__/data/account.ts: -------------------------------------------------------------------------------- 1 | export const account = { 2 | address: 'erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx', 3 | balance: '116893786890813785912', 4 | nonce: 12320, 5 | shard: 0, 6 | rootHash: 'wICKVeNpCg/TsBRyRyZMMMhcW1KENpAbopfinRVyENQ=', 7 | txCount: 12655, 8 | scrCount: 14084, 9 | developerReward: '0' 10 | }; 11 | -------------------------------------------------------------------------------- /src/__mocks__/data/blocks.ts: -------------------------------------------------------------------------------- 1 | export const blocks = [ 2 | { 3 | hash: 'fff67d31476ad920d53093a3a4c2178e198179b35656eeefa419107fa718b780', 4 | timestamp: 1671204768 5 | } 6 | ]; 7 | -------------------------------------------------------------------------------- /src/__mocks__/data/dappConfig.ts: -------------------------------------------------------------------------------- 1 | export const dappConfig = { 2 | id: 'devnet', 3 | name: 'Devnet', 4 | egldLabel: 'xEGLD', 5 | decimals: '4', 6 | egldDenomination: '18', 7 | gasPerDataByte: '1500', 8 | apiTimeout: '4000', 9 | walletConnectDeepLink: 10 | 'https://maiar.page.link/?apn=com.elrond.maiar.wallet&isi=1519405832&ibi=com.elrond.maiar.wallet&link=https://xportal.com/', 11 | walletConnectBridgeAddresses: ['https://bridge.walletconnect.org'], 12 | walletAddress: 'https://devnet-wallet.multiversx.com', 13 | apiAddress: 'https://devnet-api.multiversx.com', 14 | explorerAddress: 'http://devnet-explorer.multiversx.com', 15 | chainId: 'D' 16 | }; 17 | -------------------------------------------------------------------------------- /src/__mocks__/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './account'; 2 | export * from './blocks'; 3 | export * from './networkConfig'; 4 | export * from './dappConfig'; 5 | export * from './socketResponse'; 6 | export * from './websocketConfig'; 7 | export * from './blocks'; 8 | -------------------------------------------------------------------------------- /src/__mocks__/data/networkConfig.ts: -------------------------------------------------------------------------------- 1 | export const networkConfig = { 2 | data: { 3 | config: { 4 | erd_adaptivity: 'false', 5 | erd_chain_id: 'D', 6 | erd_denomination: 18, 7 | erd_gas_per_data_byte: 1500, 8 | erd_gas_price_modifier: '0.01', 9 | erd_hysteresis: '0.200000', 10 | erd_latest_tag_software_version: 'D1.3.50.0-hf01', 11 | erd_max_gas_per_transaction: 600000000, 12 | erd_meta_consensus_group_size: 58, 13 | erd_min_gas_limit: 50000, 14 | erd_min_gas_price: 1000000000, 15 | erd_min_transaction_version: 1, 16 | erd_num_metachain_nodes: 58, 17 | erd_num_nodes_in_shard: 58, 18 | erd_num_shards_without_meta: 3, 19 | erd_rewards_top_up_gradient_point: '2000000000000000000000000', 20 | erd_round_duration: 6000, 21 | erd_rounds_per_epoch: 1200, 22 | erd_shard_consensus_group_size: 21, 23 | erd_start_time: 1648551600, 24 | erd_top_up_factor: '0.500000' 25 | } 26 | }, 27 | code: 'successful' 28 | }; 29 | -------------------------------------------------------------------------------- /src/__mocks__/data/socketResponse.ts: -------------------------------------------------------------------------------- 1 | export const socketResponse = { 2 | sid: 'RlV7upxKjiJRyIhRAKb5', 3 | upgrades: ['websocket'], 4 | pingInterval: 25000, 5 | pingTimeout: 20000, 6 | maxPayload: 1000000 7 | }; 8 | -------------------------------------------------------------------------------- /src/__mocks__/data/websocketConfig.ts: -------------------------------------------------------------------------------- 1 | export const websocketConfig = { url: 'devnet-socket-api.multiversx.com' }; 2 | -------------------------------------------------------------------------------- /src/__mocks__/index.ts: -------------------------------------------------------------------------------- 1 | export * from './accountConfig'; 2 | export * from './server'; 3 | export * from './utils'; 4 | -------------------------------------------------------------------------------- /src/__mocks__/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mockWindowLocation'; 2 | export * from './mockWindowHistory'; 3 | -------------------------------------------------------------------------------- /src/__mocks__/utils/mockWindowHistory.ts: -------------------------------------------------------------------------------- 1 | export const mockWindowHistory = () => { 2 | if (!window) { 3 | return; 4 | } 5 | 6 | const history = window.history; 7 | 8 | // @ts-ignore 9 | delete window.history; 10 | 11 | // @ts-ignore 12 | window.history = Object.defineProperties( 13 | {}, 14 | { 15 | ...Object.getOwnPropertyDescriptors(history), 16 | pushState: { 17 | configurable: true, 18 | value: jest.fn() 19 | } 20 | } 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/__mocks__/utils/mockWindowLocation.ts: -------------------------------------------------------------------------------- 1 | export const mockWindowLocation = () => { 2 | if (!window) { 3 | return; 4 | } 5 | 6 | const location = window.location; 7 | 8 | // @ts-ignore 9 | delete window.location; 10 | 11 | // @ts-ignore 12 | window.location = Object.defineProperties( 13 | {}, 14 | { 15 | ...Object.getOwnPropertyDescriptors(location), 16 | assign: { 17 | configurable: true, 18 | value: jest.fn() 19 | } 20 | } 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/apiCalls/account/getAccountFromApi.ts: -------------------------------------------------------------------------------- 1 | import { ACCOUNTS_ENDPOINT } from 'apiCalls/endpoints'; 2 | import { axiosInstance } from 'apiCalls/utils/axiosInstance'; 3 | import { getCleanApiAddress } from 'apiCalls/utils/getCleanApiAddress'; 4 | import { TIMEOUT } from 'constants/network.constants'; 5 | import { AccountType } from 'types/account.types'; 6 | 7 | export const accountFetcher = ({ 8 | address, 9 | baseURL 10 | }: { 11 | address: string | null; 12 | baseURL: string; 13 | }) => { 14 | const apiAddress = getCleanApiAddress(baseURL); 15 | const url = `${apiAddress}/${ACCOUNTS_ENDPOINT}/${address}?withGuardianInfo=true`; 16 | // we need to get it with an axios instance because of cross-window user interaction issues 17 | return axiosInstance.get(url, { 18 | baseURL: apiAddress, 19 | timeout: TIMEOUT 20 | }); 21 | }; 22 | 23 | export const getAccountFromApi = async ({ 24 | address, 25 | baseURL 26 | }: { 27 | address?: string; 28 | baseURL: string; 29 | }) => { 30 | if (!address) { 31 | return null; 32 | } 33 | 34 | try { 35 | const { data } = await accountFetcher({ address, baseURL }); 36 | return data as AccountType; 37 | } catch (_err) { 38 | console.error('error fetching configuration for ', address); 39 | } 40 | 41 | return null; 42 | }; 43 | -------------------------------------------------------------------------------- /src/apiCalls/account/getScamAddressData.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { getCleanApiAddress } from 'apiCalls/utils'; 3 | import { TIMEOUT } from 'constants/index'; 4 | import { ScamInfoType } from 'types/account.types'; 5 | import { ACCOUNTS_ENDPOINT } from '../endpoints'; 6 | 7 | export async function getScamAddressData({ 8 | addressToVerify, 9 | baseURL 10 | }: { 11 | addressToVerify: string; 12 | baseURL: string; 13 | }) { 14 | const apiAddress = getCleanApiAddress(baseURL); 15 | 16 | const { data } = await axios.get<{ 17 | scamInfo?: ScamInfoType; 18 | code?: string; 19 | }>(`${apiAddress}/${ACCOUNTS_ENDPOINT}/${addressToVerify}`, { 20 | timeout: TIMEOUT 21 | }); 22 | 23 | return data; 24 | } 25 | -------------------------------------------------------------------------------- /src/apiCalls/account/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getAccountFromApi'; 2 | -------------------------------------------------------------------------------- /src/apiCalls/configuration/getCleanApiAddress.ts: -------------------------------------------------------------------------------- 1 | import { networkSelector } from 'store/selectors/networkSelectors'; 2 | import { getState } from 'store/store'; 3 | 4 | export const getCleanApiAddress = (customApiAddress?: string) => { 5 | const network = networkSelector(getState()); 6 | const apiAddress = customApiAddress ?? network.apiAddress; 7 | return apiAddress.endsWith('/') ? apiAddress.slice(0, -1) : apiAddress; 8 | }; 9 | -------------------------------------------------------------------------------- /src/apiCalls/configuration/getGasStationMetadata.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { getCleanApiAddress } from 'apiCalls/configuration/getCleanApiAddress'; 3 | import { NetworkType } from 'types/network.types'; 4 | 5 | interface IGasStationApiResponse { 6 | lastBlock: number; 7 | fast: number; 8 | faster: number; 9 | } 10 | 11 | const GAS_STATION_ENDPOINT = 'transactions/ppu'; 12 | 13 | export async function getGasStationMetadataFromApi( 14 | shard: number, 15 | customApiAddress?: string 16 | ): Promise { 17 | const apiAddress = getCleanApiAddress(customApiAddress); 18 | const gasStationUrl = `${apiAddress}/${GAS_STATION_ENDPOINT}/${shard}`; 19 | 20 | try { 21 | const { data } = await axios.get(gasStationUrl); 22 | 23 | if (data) { 24 | return { 25 | [shard]: { 26 | lastBlock: data.lastBlock, 27 | fast: data.fast, 28 | faster: data.faster 29 | } 30 | }; 31 | } 32 | return null; 33 | } catch (err) { 34 | console.error( 35 | 'Error fetching gas station metadata from:', 36 | gasStationUrl, 37 | err 38 | ); 39 | return null; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/apiCalls/configuration/getNetworkConfigFromApi.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { ApiNetworkConfigType } from 'types/network.types'; 3 | import { NETWORK_CONFIG_ENDPOINT } from '../endpoints'; 4 | 5 | const urlIsValid = (url: string) => { 6 | try { 7 | return Boolean(new URL(url)); 8 | } catch { 9 | return false; 10 | } 11 | }; 12 | 13 | export async function getNetworkConfigFromApi(apiAddress: string) { 14 | if (!urlIsValid(apiAddress)) { 15 | return null; 16 | } 17 | 18 | const configUrl = `${apiAddress}/${NETWORK_CONFIG_ENDPOINT}`; 19 | 20 | try { 21 | const { data } = await axios.get<{ 22 | data: { config: ApiNetworkConfigType }; 23 | }>(configUrl); 24 | 25 | if (data != null) { 26 | return data?.data?.config; 27 | } 28 | } catch (_err) { 29 | console.error('error fetching configuration for ', configUrl); 30 | } 31 | return null; 32 | } 33 | -------------------------------------------------------------------------------- /src/apiCalls/configuration/getServerConfiguration.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { NetworkType } from 'types/network.types'; 3 | import { CONFIG_ENDPOINT } from '../endpoints'; 4 | 5 | export async function getServerConfiguration(apiAddress: string) { 6 | const configUrl = `${apiAddress}/${CONFIG_ENDPOINT}`; 7 | 8 | try { 9 | const { data } = await axios.get(configUrl); 10 | if (data != null) { 11 | // egldDenomination will be removed from API when dapp-core v1 will be discontinued 12 | const egldDenomination = 'egldDenomination'; 13 | if (egldDenomination in data) { 14 | const { 15 | [egldDenomination]: decimals, 16 | decimals: digits, 17 | ...rest 18 | } = data as NetworkType & { 19 | [egldDenomination]: string; 20 | }; 21 | const networkConfig: NetworkType = { 22 | ...rest, 23 | decimals, 24 | digits 25 | }; 26 | return networkConfig; 27 | } 28 | return data; 29 | } 30 | } catch (_err) { 31 | console.error('error fetching configuration for ', configUrl); 32 | } 33 | return null; 34 | } 35 | -------------------------------------------------------------------------------- /src/apiCalls/configuration/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getServerConfiguration'; 2 | export * from './getNetworkConfigFromApi'; 3 | -------------------------------------------------------------------------------- /src/apiCalls/economics/getEconomics.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { ECONOMICS_ENDPOINT } from 'apiCalls/endpoints'; 3 | import { getCleanApiAddress } from 'apiCalls/utils/getCleanApiAddress'; 4 | 5 | export interface EconomicsInfoType { 6 | totalSupply: number; 7 | circulatingSupply: number; 8 | staked: number; 9 | price: number; 10 | marketCap: number; 11 | apr: number; 12 | topUpApr: number; 13 | } 14 | 15 | export async function getEconomics({ 16 | url = ECONOMICS_ENDPOINT, 17 | baseURL 18 | }: { 19 | url?: string; 20 | baseURL: string; 21 | }) { 22 | const apiAddress = getCleanApiAddress(baseURL); 23 | const configUrl = `${apiAddress}/${url}`; 24 | try { 25 | const { data } = await axios.get(configUrl); 26 | return data; 27 | } catch (err) { 28 | console.error('err fetching economics info', err); 29 | return null; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/apiCalls/endpoints.ts: -------------------------------------------------------------------------------- 1 | export const ACCOUNTS_ENDPOINT = 'accounts'; 2 | export const ADDRESS_ENDPOINT = 'address'; 3 | export const BLOCKS_ENDPOINT = 'blocks'; 4 | export const CODE_ENDPOINT = 'code'; 5 | export const COLLECTIONS_ENDPOINT = 'collections'; 6 | export const CONFIG_ENDPOINT = 'dapp/config'; 7 | export const CONTRACTS_ENDPOINT = 'contracts'; 8 | export const ECONOMICS_ENDPOINT = 'economics'; 9 | export const IDENTITIES_ENDPOINT = 'identities'; 10 | export const KEYS_ENDPOINT = 'keys'; 11 | export const LOCKED_ACCOUNTS_ENDPOINT = 'locked-accounts'; 12 | export const LOGS_ENDPOINT = 'logs'; 13 | export const MINIBLOCKS_ENDPOINT = 'miniblocks'; 14 | export const NETWORK_CONFIG_ENDPOINT = 'network/config'; 15 | export const NFTS_ENDPOINT = 'nfts'; 16 | export const NODES_ENDPOINT = 'nodes'; 17 | export const PROVIDERS_ENDPOINT = 'providers'; 18 | export const ROLES_ENDPOINT = 'roles'; 19 | export const SC_RESULTS_ENDPOINT = 'sc-results'; 20 | export const STAKE_ENDPOINT = 'stake'; 21 | export const TOKENS_ENDPOINT = 'tokens'; 22 | export const TRANSACTIONS_COUNT_ENDPOINT = 'transactions/count'; 23 | export const TRANSACTIONS_BATCH = 'batch'; 24 | export const TRANSACTIONS_ENDPOINT = 'transactions'; 25 | export const TRANSFERS_ENDPOINT = 'transfers'; 26 | -------------------------------------------------------------------------------- /src/apiCalls/index.ts: -------------------------------------------------------------------------------- 1 | export * from './account'; 2 | export * from './configuration'; 3 | export * from './endpoints'; 4 | export * from './tokens'; 5 | export * from './utils'; 6 | export * from './websocket'; 7 | -------------------------------------------------------------------------------- /src/apiCalls/tokens/getPersistedToken.ts: -------------------------------------------------------------------------------- 1 | import { axiosInstance } from 'apiCalls/utils/axiosInstance'; 2 | import { TIMEOUT } from 'constants/network.constants'; 3 | import { tokenDataStorage } from './tokenDataStorage'; 4 | 5 | export async function getPersistedToken(url: string): Promise { 6 | const config = { 7 | timeout: TIMEOUT 8 | }; 9 | 10 | const cachedToken: T | null = await tokenDataStorage.getItem(url); 11 | 12 | if (cachedToken) { 13 | return cachedToken; 14 | } 15 | 16 | const response = await axiosInstance.get(url, config); 17 | 18 | await tokenDataStorage.setItem(url, response.data); 19 | 20 | return response.data; 21 | } 22 | -------------------------------------------------------------------------------- /src/apiCalls/tokens/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getTokenDetails'; 2 | -------------------------------------------------------------------------------- /src/apiCalls/tokens/tokenDataStorage.ts: -------------------------------------------------------------------------------- 1 | let memoryCache: Record = {}; 2 | 3 | export let tokenDataStorage = { 4 | setItem: async (key: string, tokenData: T) => { 5 | try { 6 | memoryCache[key] = JSON.stringify(tokenData); 7 | } catch (e) { 8 | console.error('tokenDataStorage unable to serialize', e); 9 | } 10 | }, 11 | getItem: async (key: string) => { 12 | if (!memoryCache[key]) { 13 | return null; 14 | } 15 | try { 16 | return JSON.parse(memoryCache[key]); 17 | } catch (e) { 18 | console.error('tokenDataStorage unable to parse', e); 19 | } 20 | }, 21 | clear: async () => { 22 | memoryCache = {}; 23 | }, 24 | removeItem: async (key: string) => { 25 | delete memoryCache[key]; 26 | } 27 | }; 28 | 29 | export const setTokenDataStorage = ( 30 | tokenDataCacheStorage: typeof tokenDataStorage 31 | ) => { 32 | tokenDataStorage = tokenDataCacheStorage; 33 | }; 34 | -------------------------------------------------------------------------------- /src/apiCalls/transactions/getServerTransactionsByHashes.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { TRANSACTIONS_ENDPOINT } from 'apiCalls/endpoints'; 3 | import { networkSelector } from 'store/selectors/networkSelectors'; 4 | import { getState } from 'store/store'; 5 | import { ServerTransactionType } from 'types/serverTransactions.types'; 6 | 7 | export const getServerTransactionsByHashes = async ( 8 | hashes: string[] 9 | ): Promise => { 10 | const { apiAddress } = networkSelector(getState()); 11 | const { data } = await axios.get( 12 | `${apiAddress}/${TRANSACTIONS_ENDPOINT}`, 13 | { 14 | params: { 15 | hashes: hashes.join(','), 16 | withScResults: true 17 | } 18 | } 19 | ); 20 | 21 | return data; 22 | }; 23 | -------------------------------------------------------------------------------- /src/apiCalls/transactions/getTransactionByHash.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { TRANSACTIONS_ENDPOINT } from 'apiCalls/endpoints'; 3 | import { networkSelector } from 'store/selectors'; 4 | import { getState } from 'store/store'; 5 | import { ServerTransactionType } from 'types/serverTransactions.types'; 6 | 7 | export const getTransactionByHash = (hash: string) => { 8 | const { apiAddress } = networkSelector(getState()); 9 | 10 | return axios.get( 11 | `${apiAddress}/${TRANSACTIONS_ENDPOINT}/${hash}`, 12 | { 13 | timeout: 10000 // 10sec 14 | } 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/apiCalls/transactions/getTransactionsByHashes.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TransactionBatchStatusesEnum, 3 | TransactionServerStatusesEnum 4 | } from 'types/enums.types'; 5 | import { ServerTransactionType } from 'types/serverTransactions.types'; 6 | import { 7 | TrackedTransactionResultType, 8 | SignedTransactionType 9 | } from 'types/transactions.types'; 10 | import { getServerTransactionsByHashes } from './getServerTransactionsByHashes'; 11 | 12 | export const getTransactionsByHashes = async ( 13 | pendingTransactions: SignedTransactionType[] 14 | ): Promise => { 15 | const hashes = pendingTransactions.map((tx) => tx.hash); 16 | 17 | const responseData = await getServerTransactionsByHashes(hashes); 18 | 19 | return pendingTransactions.map((transaction) => { 20 | const txOnNetwork = responseData.find( 21 | (txResponse: ServerTransactionType) => 22 | txResponse?.txHash === transaction.hash 23 | ); 24 | 25 | return { 26 | ...transaction, 27 | status: txOnNetwork?.status as 28 | | TransactionServerStatusesEnum 29 | | TransactionBatchStatusesEnum, 30 | invalidTransaction: txOnNetwork == null, 31 | results: txOnNetwork?.results ?? [], 32 | previousStatus: transaction.status?.toString() || '', 33 | hasStatusChanged: Boolean( 34 | txOnNetwork && txOnNetwork.status !== transaction.status 35 | ) 36 | }; 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /src/apiCalls/utils/axiosFetcher.ts: -------------------------------------------------------------------------------- 1 | import { axiosInstance } from 'apiCalls/utils/axiosInstance'; 2 | 3 | export const axiosFetcher = (url: string) => 4 | axiosInstance.get(url).then((response) => response.data); 5 | -------------------------------------------------------------------------------- /src/apiCalls/utils/getCleanApiAddress.ts: -------------------------------------------------------------------------------- 1 | export const getCleanApiAddress = (apiAddress: string) => { 2 | return apiAddress.endsWith('/') ? apiAddress.slice(0, -1) : apiAddress; 3 | }; 4 | -------------------------------------------------------------------------------- /src/apiCalls/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './axiosFetcher'; 2 | export * from './axiosInstance'; 3 | export * from './getCleanApiAddress'; 4 | -------------------------------------------------------------------------------- /src/apiCalls/websocket/getWebsocketUrl.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export async function getWebsocketUrl(apiAddress: string) { 4 | try { 5 | const { data } = await axios.get<{ url: string }>( 6 | `${apiAddress}/websocket/config` 7 | ); 8 | return `wss://${data.url}`; 9 | } catch (err) { 10 | console.error(err); 11 | throw new Error('Can not get websocket url'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/apiCalls/websocket/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getWebsocketUrl'; 2 | -------------------------------------------------------------------------------- /src/constants/UITags.enum.ts: -------------------------------------------------------------------------------- 1 | export enum UITagsEnum { 2 | LEDGER_CONNECT = 'mvx-ledger-connect', 3 | NOTIFICATIONS_FEED = 'mvx-notifications-feed', 4 | PENDING_TRANSACTIONS_PANEL = 'mvx-pending-transactions-panel', 5 | SIGN_TRANSACTIONS_PANEL = 'mvx-sign-transactions-panel', 6 | TOAST_LIST = 'mvx-toast-list', 7 | WALLET_CONNECT = 'mvx-wallet-connect', 8 | UNLOCK_PANEL = 'mvx-unlock-panel' 9 | } 10 | -------------------------------------------------------------------------------- /src/constants/browser.constants.ts: -------------------------------------------------------------------------------- 1 | import { safeWindow } from './window.constants'; 2 | 3 | const userAgent = String(safeWindow?.navigator?.userAgent); 4 | 5 | export const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent); 6 | 7 | const isFirefoxOnWindows = 8 | /firefox/i.test(userAgent) && /windows/i.test(userAgent); 9 | 10 | export const isBrowserWithPopupConfirmation = isSafari || isFirefoxOnWindows; 11 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './browser.constants'; 2 | export * from './ledger.constants'; 3 | export * from './mvx.constants'; 4 | export * from './network.constants'; 5 | export * from './placeholders.constants'; 6 | export * from './storage.constants'; 7 | export * from './webWalletProvider.constants'; 8 | export * from './window.constants'; 9 | export * from './websocket.constants'; 10 | export * from './providerFactory.constants'; 11 | -------------------------------------------------------------------------------- /src/constants/mvx.constants.ts: -------------------------------------------------------------------------------- 1 | export const GAS_PRICE_MODIFIER = 0.01; 2 | export const GAS_PER_DATA_BYTE = 1500; 3 | export const GAS_LIMIT = 50000; 4 | /** 5 | * Extra gas limit for guarded transactions 6 | */ 7 | export const EXTRA_GAS_LIMIT_GUARDED_TX = 50000; 8 | export const GAS_PRICE = 1000000000; 9 | export const VERSION = 1; 10 | export const LEDGER_CONTRACT_DATA_ENABLED_VALUE = 1; 11 | export const METACHAIN_SHARD_ID = 4294967295; 12 | export const ALL_SHARDS_SHARD_ID = 4294967280; 13 | export const REFUNDED_GAS = 'refundedGas'; 14 | export const MULTI_TRANSFER_EGLD_TOKEN = 'EGLD-000000'; 15 | -------------------------------------------------------------------------------- /src/constants/placeholders.constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Not Applicable 3 | * @value N/A 4 | */ 5 | export const N_A = 'N/A'; 6 | export const ELLIPSIS = '...'; 7 | export const EMPTY_PPU = 0; 8 | -------------------------------------------------------------------------------- /src/constants/providerFactory.constants.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ProviderTypeEnum, 3 | ProviderType 4 | } from 'providers/types/providerFactory.types'; 5 | 6 | export const providerLabels: Record = { 7 | [ProviderTypeEnum.crossWindow]: 'MultiversX Web Wallet', 8 | [ProviderTypeEnum.extension]: 'MultiversX Wallet Extension', 9 | [ProviderTypeEnum.walletConnect]: 'xPortal App', 10 | [ProviderTypeEnum.ledger]: 'Ledger', 11 | [ProviderTypeEnum.metamask]: 'MetaMask Snap', 12 | [ProviderTypeEnum.passkey]: 'Passkey', 13 | [ProviderTypeEnum.webview]: 'Webview', 14 | [ProviderTypeEnum.none]: '' 15 | }; 16 | -------------------------------------------------------------------------------- /src/constants/storage.constants.ts: -------------------------------------------------------------------------------- 1 | import { createJSONStorage } from 'zustand/middleware'; 2 | import { SubscriptionsEnum } from 'types'; 3 | import { safeWindow } from './window.constants'; 4 | 5 | export const persistConfig: { 6 | persistReducersStorageType: 'localStorage' | 'sessionStorage'; 7 | } = { 8 | persistReducersStorageType: 'localStorage' 9 | }; 10 | 11 | export const storage = safeWindow 12 | ? createJSONStorage( 13 | () => safeWindow[persistConfig.persistReducersStorageType] 14 | ) 15 | : undefined; 16 | 17 | export const subscriptions = new Map void>(); 18 | -------------------------------------------------------------------------------- /src/constants/transactions.constants.ts: -------------------------------------------------------------------------------- 1 | export const CANCEL_TRANSACTION_TOAST_ID = 'cancel-transaction-toast'; 2 | export const ERROR_SIGNING_TOAST_ID = 'error-signing-toast'; 3 | export const AVERAGE_TX_DURATION_MS = 6000; 4 | export const CROSS_SHARD_ROUNDS = 5; 5 | export const TRANSACTIONS_STATUS_POLLING_INTERVAL_MS = 90 * 1000; // 90sec 6 | export const TRANSACTIONS_STATUS_DROP_INTERVAL_MS = 10 * 60 * 1000; // 10min 7 | export const CANCEL_TRANSACTION_TOAST_DEFAULT_DURATION = 10000; 8 | export const BATCH_TRANSACTIONS_ID_SEPARATOR = '-'; 9 | export const SAME_SHARD_MAX_TOAST_WIDTH_PERCENT_DECREASE = 75; 10 | export const CROSS_SHARD_MAX_TOAST_WIDTH_PERCENT_DECREASE = 25; 11 | export const PROGRESS_INTERVAL_DURATION_MS = 100; 12 | -------------------------------------------------------------------------------- /src/constants/walletConnect.constants.ts: -------------------------------------------------------------------------------- 1 | import { WalletConnectConfig } from 'providers/strategies/WalletConnectProviderStrategy/types/walletConnect.types'; 2 | 3 | export const fallbackWalletConnectConfigurations: WalletConnectConfig = { 4 | walletConnectV2ProjectId: '', 5 | walletConnectDeepLink: 6 | 'https://maiar.page.link/?apn=com.elrond.maiar.wallet&isi=1519405832&ibi=com.elrond.maiar.wallet&link=https://xportal.com/', 7 | walletConnectV2RelayAddress: 'wss://relay.walletconnect.com' 8 | }; 9 | -------------------------------------------------------------------------------- /src/constants/webWalletProvider.constants.ts: -------------------------------------------------------------------------------- 1 | export { 2 | WALLET_PROVIDER_MAINNET, 3 | WALLET_PROVIDER_DEVNET, 4 | WALLET_PROVIDER_TESTNET, 5 | WALLET_PROVIDER_CONNECT_URL, 6 | WALLET_PROVIDER_DISCONNECT_URL, 7 | WALLET_PROVIDER_SEND_TRANSACTION_URL, 8 | WALLET_PROVIDER_SIGN_TRANSACTION_URL, 9 | WALLET_PROVIDER_SIGN_MESSAGE_URL, 10 | WALLET_PROVIDER_CALLBACK_PARAM, 11 | WALLET_PROVIDER_CALLBACK_PARAM_TX_SIGNED 12 | } from '@multiversx/sdk-web-wallet-provider'; 13 | -------------------------------------------------------------------------------- /src/constants/websocket.constants.ts: -------------------------------------------------------------------------------- 1 | import { Socket } from 'socket.io-client'; 2 | 3 | export enum WebsocketConnectionStatusEnum { 4 | NOT_INITIALIZED = 'not_initialized', 5 | PENDING = 'pending', 6 | COMPLETED = 'completed' 7 | } 8 | 9 | export const websocketConnection: { 10 | instance: Socket | null; 11 | // Use the connection status to avoid multiple websocket connections 12 | status: WebsocketConnectionStatusEnum; 13 | } = { 14 | instance: null, 15 | status: WebsocketConnectionStatusEnum.NOT_INITIALIZED 16 | }; 17 | -------------------------------------------------------------------------------- /src/constants/window.constants.ts: -------------------------------------------------------------------------------- 1 | export const safeWindow: Window = 2 | typeof window !== 'undefined' ? window : ({} as Window); 3 | -------------------------------------------------------------------------------- /src/controllers/FormatAmountController/index.ts: -------------------------------------------------------------------------------- 1 | export * from './FormatAmountController'; 2 | -------------------------------------------------------------------------------- /src/controllers/FormatAmountController/types.ts: -------------------------------------------------------------------------------- 1 | import { FormatAmountPropsType } from 'lib/sdkDappUtils'; 2 | 3 | export interface FormatAmountControllerPropsType extends FormatAmountPropsType { 4 | egldLabel?: string; 5 | token?: string; 6 | } 7 | 8 | export interface FormatedAmountType { 9 | isValid: boolean; 10 | label?: string; 11 | valueDecimal: string; 12 | valueInteger: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/controllers/TransactionsHistoryController/TransactionsHistoryController.ts: -------------------------------------------------------------------------------- 1 | import { IGetHistoricalTransactionsParams } from 'types/transaction-list-item.types'; 2 | import { getTransactionsHistory } from 'utils/transactions'; 3 | 4 | // TODO: Will replace TransactionsTableController in the future 5 | export const TransactionsHistoryController = { 6 | async getTransactionsHistory(params: IGetHistoricalTransactionsParams) { 7 | const transactions = await getTransactionsHistory(params); 8 | return transactions; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/controllers/TransactionsHistoryController/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TransactionsHistoryController'; 2 | -------------------------------------------------------------------------------- /src/controllers/TransactionsTableController/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TransactionsTableController'; 2 | -------------------------------------------------------------------------------- /src/controllers/TransactionsTableController/transactionsTableController.types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TransactionAgeType, 3 | TransactionIconInfoType, 4 | TransactionMethodType 5 | } from 'types/serverTransactions.types'; 6 | 7 | export type TransactionsRowType = { 8 | age: TransactionAgeType; 9 | direction?: string; 10 | method: TransactionMethodType; 11 | iconInfo: TransactionIconInfoType; 12 | link: string; 13 | receiver: TransactionAccountType; 14 | sender: TransactionAccountType; 15 | txHash: string; 16 | value: TransactionValueType; 17 | }; 18 | 19 | type TransactionAccountType = { 20 | address: string; 21 | description: string; 22 | isContract: boolean; 23 | isTokenLocked: boolean; 24 | link: string; 25 | name: string; 26 | shard?: string; 27 | shardLink?: string; 28 | showLink: boolean; 29 | }; 30 | 31 | export type TransactionValueType = { 32 | badge?: string; 33 | collection?: string; 34 | egldLabel: string; 35 | link?: string; 36 | linkText?: string; 37 | name?: string; 38 | showFormattedAmount?: boolean; 39 | svgUrl?: string; 40 | ticker?: string; 41 | titleText?: string; 42 | valueDecimal: string; 43 | valueInteger: string; 44 | }; 45 | -------------------------------------------------------------------------------- /src/controllers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './FormatAmountController'; 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './apiCalls'; 2 | export * from './controllers'; 3 | export * from './constants'; 4 | export * from './services'; 5 | export * from './types'; 6 | export * from './utils'; 7 | -------------------------------------------------------------------------------- /src/lib/sdkCore.ts: -------------------------------------------------------------------------------- 1 | export { 2 | Transaction, 3 | TransactionVersion, 4 | Address, 5 | AddressComputer, 6 | TransactionComputer, 7 | TransactionOptions, 8 | Message, 9 | UserPublicKey, 10 | IPlainTransactionObject, 11 | MessageComputer, 12 | UserVerifier 13 | } from '@multiversx/sdk-core'; 14 | -------------------------------------------------------------------------------- /src/lib/sdkDappUtils.ts: -------------------------------------------------------------------------------- 1 | export { 2 | DIGITS, 3 | ZERO, 4 | DECIMALS 5 | } from '@multiversx/sdk-dapp-utils/out/constants'; 6 | export { stringIsInteger } from '@multiversx/sdk-dapp-utils/out/helpers/stringIsInteger'; 7 | export { stringIsFloat } from '@multiversx/sdk-dapp-utils/out/helpers/stringIsFloat'; 8 | export { 9 | formatAmount, 10 | FormatAmountPropsType 11 | } from '@multiversx/sdk-dapp-utils/out/helpers/formatAmount'; 12 | export { recommendGasPrice } from '@multiversx/sdk-dapp-utils/out/helpers/recommendGasPrice'; 13 | export type { IDAppProviderOptions } from '@multiversx/sdk-dapp-utils/out/models/dappProviderBase'; 14 | -------------------------------------------------------------------------------- /src/lib/sdkWebWalletCrossWindowProvider.ts: -------------------------------------------------------------------------------- 1 | export { CrossWindowProvider } from '@multiversx/sdk-web-wallet-cross-window-provider/out/CrossWindowProvider/CrossWindowProvider'; 2 | -------------------------------------------------------------------------------- /src/lib/sdkWebWalletIframeProvider.ts: -------------------------------------------------------------------------------- 1 | export { IframeProvider } from '@multiversx/sdk-web-wallet-iframe-provider/out'; 2 | -------------------------------------------------------------------------------- /src/managers/NotificationsFeedManager/index.ts: -------------------------------------------------------------------------------- 1 | export * from './NotificationsFeedManager'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/managers/NotificationsFeedManager/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './notifications.types'; 2 | -------------------------------------------------------------------------------- /src/managers/NotificationsFeedManager/types/notifications.types.ts: -------------------------------------------------------------------------------- 1 | export enum NotificationsFeedEventsEnum { 2 | CLOSE = 'CLOSE_NOTIFICATIONS_FEED', 3 | CLEAR = 'CLEAR_NOTIFICATIONS_FEED_HISTORY', 4 | OPEN = 'OPEN_NOTIFICATIONS_FEED', 5 | // Event to update the pending transactions list 6 | PENDING_TRANSACTIONS_UPDATE = 'PENDING_TRANSACTIONS_UPDATE', 7 | // Event to update the transactions history list 8 | TRANSACTIONS_HISTORY_UPDATE = 'TRANSACTIONS_HISTORY_UPDATE' 9 | } 10 | -------------------------------------------------------------------------------- /src/managers/TransactionManager/helpers/getAreTransactionsCorssShards.ts: -------------------------------------------------------------------------------- 1 | import { SignedTransactionType } from 'types/transactions.types'; 2 | import { getAddressFromDataField } from 'utils'; 3 | import { isCrossShardTransaction } from './isCrossShardTransaction'; 4 | 5 | export const getAreTransactionsCrossShards = ( 6 | transactions?: SignedTransactionType[], 7 | accountShard = 1 8 | ): boolean => { 9 | if (!transactions?.length) { 10 | return true; 11 | } 12 | 13 | return transactions.reduce( 14 | (prevTxIsSameShard: boolean, { receiver, data }: SignedTransactionType) => { 15 | const receiverAddress = getAddressFromDataField({ 16 | receiver, 17 | data: data ?? '' 18 | }); 19 | if (receiverAddress == null) { 20 | return prevTxIsSameShard; 21 | } 22 | return ( 23 | prevTxIsSameShard && 24 | isCrossShardTransaction({ 25 | receiverAddress, 26 | senderShard: accountShard 27 | }) 28 | ); 29 | }, 30 | true 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /src/managers/TransactionManager/helpers/getToastDuration.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AVERAGE_TX_DURATION_MS, 3 | CROSS_SHARD_ROUNDS 4 | } from 'constants/transactions.constants'; 5 | import { accountSelector } from 'store/selectors'; 6 | import { getState } from 'store/store'; 7 | import { SignedTransactionType } from 'types/transactions.types'; 8 | import { getAreTransactionsCrossShards } from './getAreTransactionsCorssShards'; 9 | import { isBatchTransaction } from './isBatchTransaction'; 10 | 11 | export const getToastDuration = ( 12 | transactions: SignedTransactionType[] | SignedTransactionType[][] 13 | ) => { 14 | let totalDuration = 0; 15 | const accountShard = accountSelector(getState())?.shard; 16 | 17 | if (isBatchTransaction(transactions)) { 18 | transactions.forEach((transactionGroup) => { 19 | const isCrossShard = getAreTransactionsCrossShards( 20 | transactionGroup, 21 | accountShard 22 | ); 23 | totalDuration += isCrossShard 24 | ? CROSS_SHARD_ROUNDS * AVERAGE_TX_DURATION_MS 25 | : AVERAGE_TX_DURATION_MS; 26 | }); 27 | return totalDuration; 28 | } 29 | 30 | const isCrossShard = getAreTransactionsCrossShards( 31 | transactions, 32 | accountShard 33 | ); 34 | totalDuration = isCrossShard 35 | ? CROSS_SHARD_ROUNDS * AVERAGE_TX_DURATION_MS 36 | : AVERAGE_TX_DURATION_MS; 37 | 38 | return totalDuration; 39 | }; 40 | -------------------------------------------------------------------------------- /src/managers/TransactionManager/helpers/getTransactionsStatus.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getIsTransactionFailed, 3 | getIsTransactionNotExecuted, 4 | getIsTransactionSuccessful 5 | } from 'store/actions/transactions/transactionStateByStatus'; 6 | import { TransactionBatchStatusesEnum } from 'types'; 7 | import { SignedTransactionType } from 'types/transactions.types'; 8 | 9 | export function getTransactionsSessionStatus( 10 | transactions: SignedTransactionType[] 11 | ): TransactionBatchStatusesEnum | null { 12 | if (!transactions || transactions.length === 0) { 13 | return TransactionBatchStatusesEnum.invalid; 14 | } 15 | 16 | const areAllSuccessful = transactions.every((transaction) => 17 | getIsTransactionSuccessful(transaction.status) 18 | ); 19 | 20 | if (areAllSuccessful) { 21 | return TransactionBatchStatusesEnum.success; 22 | } 23 | 24 | const areAnyFailed = transactions.some((transaction) => 25 | getIsTransactionFailed(transaction.status) 26 | ); 27 | 28 | if (areAnyFailed) { 29 | return TransactionBatchStatusesEnum.fail; 30 | } 31 | 32 | const areAllNotExecuted = transactions.every((transaction) => 33 | getIsTransactionNotExecuted(transaction.status) 34 | ); 35 | 36 | if (areAllNotExecuted) { 37 | return TransactionBatchStatusesEnum.invalid; 38 | } 39 | 40 | return null; 41 | } 42 | -------------------------------------------------------------------------------- /src/managers/TransactionManager/helpers/isBatchTransaction.ts: -------------------------------------------------------------------------------- 1 | export const isBatchTransaction = ( 2 | transactions: T[] | T[][] 3 | ): transactions is T[][] => { 4 | return Array.isArray(transactions[0]); 5 | }; 6 | -------------------------------------------------------------------------------- /src/managers/TransactionManager/helpers/isCrossShardTransaction.ts: -------------------------------------------------------------------------------- 1 | import { Address, AddressComputer } from 'lib/sdkCore'; 2 | 3 | export interface IsCrossShardTransactionPropsType { 4 | receiverAddress: string; 5 | senderShard?: number; 6 | senderAddress?: string; 7 | } 8 | export function isCrossShardTransaction({ 9 | receiverAddress, 10 | senderShard, 11 | senderAddress 12 | }: IsCrossShardTransactionPropsType) { 13 | const addressComputer = new AddressComputer(); 14 | try { 15 | const receiver = new Address(receiverAddress); 16 | const computedReceiverShard = addressComputer.getShardOfAddress(receiver); 17 | if (senderShard == null && senderAddress != null) { 18 | const sender = new Address(senderAddress); 19 | const computedSenderShard = addressComputer.getShardOfAddress(sender); 20 | return computedSenderShard !== computedReceiverShard; 21 | } 22 | return computedReceiverShard !== senderShard; 23 | } catch (_err) { 24 | return false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/managers/TransactionManager/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TransactionManager'; 2 | -------------------------------------------------------------------------------- /src/managers/UnlockPanelManager/index.ts: -------------------------------------------------------------------------------- 1 | export * from './UnlockPanelManager'; 2 | -------------------------------------------------------------------------------- /src/managers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TransactionManager'; 2 | export * from './NotificationsFeedManager'; 3 | -------------------------------------------------------------------------------- /src/managers/internal/LedgerConnectStateManager/index.ts: -------------------------------------------------------------------------------- 1 | export * from './LedgerConnectStateManager'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/managers/internal/LedgerConnectStateManager/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ledgerConnectEvents.enum'; 2 | -------------------------------------------------------------------------------- /src/managers/internal/LedgerConnectStateManager/types/ledgerConnectEvents.enum.ts: -------------------------------------------------------------------------------- 1 | export enum LedgerConnectEventsEnum { 2 | CONNECT_DEVICE = 'CONNECT_DEVICE', 3 | ACCESS_WALLET = 'ACCESS_WALLET', 4 | GO_TO_PAGE = 'GO_TO_PAGE', 5 | CLOSE = 'CLOSE', 6 | DATA_UPDATE = 'DATA_UPDATE', 7 | UI_DISCONNECTED = 'UI_DISCONNECTED' 8 | } 9 | -------------------------------------------------------------------------------- /src/managers/internal/PendingTransactionsStateManager/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PendingTransactionsStateManager'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/managers/internal/PendingTransactionsStateManager/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './pendingTransactions.types'; 2 | -------------------------------------------------------------------------------- /src/managers/internal/PendingTransactionsStateManager/types/pendingTransactions.types.ts: -------------------------------------------------------------------------------- 1 | // types here need to be synced with the types in sdk-dapp-ui 2 | export enum PendingTransactionsEventsEnum { 3 | CLOSE = 'CLOSE_PENDING_TRANSACTIONS', 4 | DATA_UPDATE = 'DATA_UPDATE_PENDING_TRANSACTIONS' 5 | } 6 | -------------------------------------------------------------------------------- /src/managers/internal/SidePanelBaseManager/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SidePanelBaseManager'; 2 | -------------------------------------------------------------------------------- /src/managers/internal/SignTransactionsStateManager/index.ts: -------------------------------------------------------------------------------- 1 | export * from './SignTransactionsStateManager'; 2 | -------------------------------------------------------------------------------- /src/managers/internal/SignTransactionsStateManager/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './signTransactionsPanel.types'; 2 | -------------------------------------------------------------------------------- /src/managers/internal/ToastManager/helpers/getToastTransactionsStatus.ts: -------------------------------------------------------------------------------- 1 | import { ITransactionListItem } from 'lib/sdkDappUi'; 2 | import { isServerTransactionPending } from 'store/actions/transactions/transactionStateByStatus'; 3 | import { TransactionServerStatusesEnum } from 'types'; 4 | 5 | export const getToastTransactionsStatus = ( 6 | transactions: ITransactionListItem[] 7 | ) => { 8 | const processedTransactions = transactions.filter( 9 | (tx) => 10 | !isServerTransactionPending(tx.status as TransactionServerStatusesEnum) 11 | ).length; 12 | 13 | const totalTransactions = transactions.length; 14 | 15 | if (totalTransactions === 1 && processedTransactions === 1) { 16 | return isServerTransactionPending( 17 | transactions[0].status as TransactionServerStatusesEnum 18 | ) 19 | ? 'Processing transaction' 20 | : 'Transaction processed'; 21 | } 22 | 23 | return `${processedTransactions} / ${totalTransactions} transactions processed`; 24 | }; 25 | -------------------------------------------------------------------------------- /src/managers/internal/ToastManager/helpers/tests/baseTransactionMock.ts: -------------------------------------------------------------------------------- 1 | import { testAddress } from '__mocks__'; 2 | import { ITransactionListItem } from 'lib/sdkDappUi'; 3 | import { TransactionServerStatusesEnum } from 'types'; 4 | 5 | export const baseTransactionMock: ITransactionListItem = { 6 | status: TransactionServerStatusesEnum.success, 7 | asset: null, 8 | action: { name: 'Transfer' }, 9 | link: 'https://explorer.example.com/tx/123', 10 | hash: '123', 11 | interactor: testAddress, 12 | directionLabel: 'To', 13 | amount: '1 EGLD', 14 | timestamp: Date.now() 15 | }; 16 | -------------------------------------------------------------------------------- /src/managers/internal/ToastManager/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './toast.types'; 2 | -------------------------------------------------------------------------------- /src/managers/internal/WalletConnectStateManager/index.ts: -------------------------------------------------------------------------------- 1 | export * from './WalletConnectStateManager'; 2 | -------------------------------------------------------------------------------- /src/managers/internal/index.ts: -------------------------------------------------------------------------------- 1 | export { PendingTransactionsStateManager } from './PendingTransactionsStateManager/PendingTransactionsStateManager'; 2 | export { SignTransactionsStateManager } from './SignTransactionsStateManager/SignTransactionsStateManager'; 3 | export { ToastManager } from './ToastManager/ToastManager'; 4 | export { WalletConnectStateManager as WalletConnectManager } from './WalletConnectStateManager/WalletConnectStateManager'; 5 | -------------------------------------------------------------------------------- /src/methods/account/getAccount.ts: -------------------------------------------------------------------------------- 1 | import { accountSelector } from 'store/selectors'; 2 | import { getState } from 'store/store'; 3 | 4 | export function getAccount(state = getState()) { 5 | return accountSelector(state); 6 | } 7 | -------------------------------------------------------------------------------- /src/methods/account/getAccountInfo.ts: -------------------------------------------------------------------------------- 1 | import { accountInfoSelector } from 'store/selectors'; 2 | import { getState } from 'store/store'; 3 | 4 | export function getAccountInfo(state = getState()) { 5 | return accountInfoSelector(state); 6 | } 7 | -------------------------------------------------------------------------------- /src/methods/account/getAddress.ts: -------------------------------------------------------------------------------- 1 | import { addressSelector } from 'store/selectors'; 2 | import { getState } from 'store/store'; 3 | 4 | export function getAddress(state = getState()) { 5 | return addressSelector(state); 6 | } 7 | -------------------------------------------------------------------------------- /src/methods/account/getIsLoggedIn.ts: -------------------------------------------------------------------------------- 1 | import { isLoggedInSelector } from 'store/selectors/accountSelectors'; 2 | import { getState } from 'store/store'; 3 | 4 | export function getIsLoggedIn(state = getState()) { 5 | return isLoggedInSelector(state); 6 | } 7 | -------------------------------------------------------------------------------- /src/methods/account/getLatestNonce.ts: -------------------------------------------------------------------------------- 1 | import { accountNonceSelector } from '../../store/selectors/accountSelectors'; 2 | import { transactionsSliceSelector } from '../../store/selectors/transactionsSelector'; 3 | import { getState } from '../../store/store'; 4 | import { AccountType } from '../../types/account.types'; 5 | 6 | export function getLatestNonce(apiAccount: AccountType | null) { 7 | const state = getState(); 8 | const transactionsSessions = transactionsSliceSelector(state); 9 | const currentAccountNonce = accountNonceSelector(state); 10 | 11 | // Get the max transactions nonce 12 | let lastTransactionNonce = Object.keys(transactionsSessions) 13 | .map(Number) // Convert keys to numbers (timestamps) 14 | .reduce((maxNonce, timestamp) => { 15 | const transactions = transactionsSessions[timestamp]?.transactions || []; 16 | return Math.max(maxNonce, ...transactions.map((tx) => tx.nonce), 0); 17 | }, 0); 18 | 19 | if (lastTransactionNonce > 0) { 20 | lastTransactionNonce += 1; // Increment only if there's at least one pending transaction 21 | } 22 | 23 | if (apiAccount == null) { 24 | const currentStoreNonce = Math.max( 25 | lastTransactionNonce, 26 | currentAccountNonce 27 | ); 28 | return currentStoreNonce; 29 | } 30 | 31 | const currentNonce = Math.max( 32 | lastTransactionNonce || apiAccount.nonce, 33 | currentAccountNonce 34 | ); 35 | 36 | return currentNonce; 37 | } 38 | -------------------------------------------------------------------------------- /src/methods/initApp/websocket/registerWebsocket.ts: -------------------------------------------------------------------------------- 1 | import { initializeWebsocketConnection } from './initializeWebsocketConnection'; 2 | 3 | /** 4 | * Manages the WebSocket connection lifecycle. 5 | * 6 | * Holds a reference to the current WebSocket connection's `closeConnection` method, 7 | * allowing other parts of the application to close the connection on demand (e.g., on logout). 8 | * 9 | * This pattern avoids exporting mutable bindings directly by encapsulating 10 | * the reference within a stable object. 11 | * 12 | * @example 13 | * ```ts 14 | * await registerWebsocketListener(address); 15 | * websocketManager.closeConnectionRef?.(); 16 | * ``` 17 | */ 18 | export const websocketManager = { 19 | closeConnectionRef: undefined as (() => void) | undefined 20 | }; 21 | 22 | export async function registerWebsocketListener(address: string) { 23 | const { closeConnection } = await initializeWebsocketConnection(address); 24 | 25 | websocketManager.closeConnectionRef = closeConnection; 26 | } 27 | -------------------------------------------------------------------------------- /src/methods/loginInfo/getLoginInfo.ts: -------------------------------------------------------------------------------- 1 | import { getState } from 'store/store'; 2 | import { isLoggedInSelector } from '../../store/selectors/accountSelectors'; 3 | import { loginInfoSelector } from '../../store/selectors/loginInfoSelectors'; 4 | 5 | export const getLoginInfo = (state = getState()) => { 6 | return { 7 | ...loginInfoSelector(state), 8 | isLoggedIn: isLoggedInSelector(state) 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /src/methods/network/getEgldLabel.ts: -------------------------------------------------------------------------------- 1 | import { networkSelector } from 'store/selectors/networkSelectors'; 2 | import { getState } from 'store/store'; 3 | 4 | export function getEgldLabel(state = getState()) { 5 | return networkSelector(state).egldLabel; 6 | } 7 | -------------------------------------------------------------------------------- /src/methods/network/getExplorerAddress.ts: -------------------------------------------------------------------------------- 1 | import { networkSelector } from 'store/selectors/networkSelectors'; 2 | import { getState } from 'store/store'; 3 | 4 | export function getExplorerAddress(state = getState()) { 5 | return networkSelector(state).explorerAddress; 6 | } 7 | -------------------------------------------------------------------------------- /src/methods/network/getNetworkConfig.ts: -------------------------------------------------------------------------------- 1 | import { networkConfigSelector } from 'store/selectors/networkSelectors'; 2 | import { getState } from 'store/store'; 3 | 4 | export function getNetworkConfig(state = getState()) { 5 | return networkConfigSelector(state); 6 | } 7 | -------------------------------------------------------------------------------- /src/methods/trackTransactions/helpers/checkTransactionStatus/checkTransactionStatus.ts: -------------------------------------------------------------------------------- 1 | import { pendingTransactionsSessionsSelector } from 'store/selectors/transactionsSelector'; 2 | import { getState } from 'store/store'; 3 | import { checkBatch } from './checkBatch'; 4 | 5 | export async function checkTransactionStatus() { 6 | const pendingSessions = pendingTransactionsSessionsSelector(getState()); 7 | if (Object.keys(pendingSessions).length > 0) { 8 | for (const [sessionId, { transactions }] of Object.entries( 9 | pendingSessions 10 | )) { 11 | await checkBatch({ 12 | sessionId, 13 | transactionBatch: transactions 14 | }); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/methods/trackTransactions/helpers/checkTransactionStatus/getPendingTransactions.ts: -------------------------------------------------------------------------------- 1 | import { getIsTransactionPending } from 'store/actions/transactions/transactionStateByStatus'; 2 | import { SignedTransactionType } from 'types/transactions.types'; 3 | 4 | export function getPendingTransactions( 5 | transactions: SignedTransactionType[] 6 | ): SignedTransactionType[] { 7 | const pendingTransactions = transactions.reduce( 8 | (acc: SignedTransactionType[], transaction) => { 9 | if ( 10 | transaction.hash != null && 11 | getIsTransactionPending(transaction.status) 12 | ) { 13 | acc.push(transaction); 14 | } 15 | return acc; 16 | }, 17 | [] 18 | ); 19 | return pendingTransactions; 20 | } 21 | -------------------------------------------------------------------------------- /src/methods/trackTransactions/helpers/checkTransactionStatus/index.ts: -------------------------------------------------------------------------------- 1 | export * from './checkTransactionStatus'; 2 | -------------------------------------------------------------------------------- /src/methods/trackTransactions/helpers/checkTransactionStatus/manageFailedTransactions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | updateTransactionStatus, 3 | updateTransactionsSession 4 | } from 'store/actions/transactions/transactionsActions'; 5 | import { 6 | TransactionBatchStatusesEnum, 7 | TransactionServerStatusesEnum 8 | } from 'types/enums.types'; 9 | import { ResultType } from 'types/serverTransactions.types'; 10 | import { SignedTransactionType } from 'types/transactions.types'; 11 | 12 | export function manageFailedTransactions({ 13 | results, 14 | hash, 15 | sessionId 16 | }: { 17 | results: ResultType[]; 18 | hash: string; 19 | sessionId: string; 20 | }) { 21 | const resultWithError = results?.find( 22 | (scResult) => scResult?.returnMessage !== '' 23 | ); 24 | 25 | updateTransactionStatus({ 26 | sessionId, 27 | transaction: { 28 | ...(resultWithError as unknown as SignedTransactionType), 29 | hash, 30 | status: TransactionServerStatusesEnum.fail, 31 | inTransit: false 32 | } 33 | }); 34 | 35 | updateTransactionsSession({ 36 | sessionId, 37 | status: TransactionBatchStatusesEnum.fail, 38 | errorMessage: resultWithError?.returnMessage 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /src/methods/trackTransactions/helpers/getPollingInterval.ts: -------------------------------------------------------------------------------- 1 | import { TRANSACTIONS_STATUS_POLLING_INTERVAL_MS } from 'constants/transactions.constants'; 2 | import { roundDurationSelectorSelector } from 'store/selectors'; 3 | import { getState } from 'store/store'; 4 | 5 | export function getPollingInterval() { 6 | const roundDuration = roundDurationSelectorSelector(getState()); 7 | 8 | if (!roundDuration) { 9 | return TRANSACTIONS_STATUS_POLLING_INTERVAL_MS; 10 | } 11 | 12 | // Polling interval should not be less than 1s 13 | return Math.max(1000, roundDuration / 2); 14 | } 15 | -------------------------------------------------------------------------------- /src/methods/transactions/getPendingTransactions.ts: -------------------------------------------------------------------------------- 1 | import { getState } from 'store/store'; 2 | import { pendingTransactionsSelector } from '../../store/selectors/transactionsSelector'; 3 | 4 | export function getPendingTransactions(state = getState()) { 5 | return pendingTransactionsSelector(state); 6 | } 7 | -------------------------------------------------------------------------------- /src/methods/transactions/getPendingTransactionsSessions.ts: -------------------------------------------------------------------------------- 1 | import { getState } from 'store/store'; 2 | import { pendingTransactionsSessionsSelector } from '../../store/selectors/transactionsSelector'; 3 | 4 | export function getPendingTransactionsSessions(state = getState()) { 5 | return pendingTransactionsSessionsSelector(state); 6 | } 7 | -------------------------------------------------------------------------------- /src/methods/transactions/getTransactionSessions.ts: -------------------------------------------------------------------------------- 1 | import { getState } from 'store/store'; 2 | import { transactionsSliceSelector } from '../../store/selectors/transactionsSelector'; 3 | 4 | export function getTransactionSessions(state = getState()) { 5 | return transactionsSliceSelector(state); 6 | } 7 | -------------------------------------------------------------------------------- /src/providers/DappProvider/helpers/computeNonces/computeNonce.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the higher nonce between the latest nonce of the account and the transaction nonce 3 | * Used to set the correct nonce for a transaction in the batch 4 | */ 5 | export const computeNonce = ({ 6 | accountNonce, 7 | transactionNonce 8 | }: { 9 | accountNonce: number; 10 | transactionNonce?: number; 11 | }) => { 12 | if (!transactionNonce) { 13 | return accountNonce; 14 | } 15 | 16 | return transactionNonce > accountNonce ? transactionNonce : accountNonce; 17 | }; 18 | -------------------------------------------------------------------------------- /src/providers/DappProvider/helpers/computeNonces/computeNonces.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from 'lib/sdkCore'; 2 | import { computeNonce } from './computeNonce'; 3 | 4 | export const computeNonces = ({ 5 | latestNonce, 6 | transactions 7 | }: { 8 | latestNonce: number; 9 | transactions: Array; 10 | }): Array => { 11 | if (transactions.length === 0) { 12 | return transactions; 13 | } 14 | 15 | return transactions.map((tx: Transaction, index: number) => { 16 | const nextNonce = latestNonce + index; 17 | 18 | const transactionNonce = tx.toPlainObject().nonce; 19 | 20 | // stop replacing nonce if transaction is configured with a higher nonce than the existing one 21 | const computedNonce = computeNonce({ 22 | accountNonce: nextNonce, 23 | transactionNonce 24 | }); 25 | 26 | tx.nonce = BigInt(computedNonce); 27 | 28 | return tx; 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /src/providers/DappProvider/helpers/login/helpers/extractAddressFromToken.ts: -------------------------------------------------------------------------------- 1 | import { setLoginToken } from 'store/actions/loginInfo/loginInfoActions'; 2 | import { getAccountFromToken } from './getAccountFromToken'; 3 | 4 | interface IExtractAccountFromTokenProps { 5 | loginToken: string; 6 | extraInfoData: { 7 | multisig?: string; 8 | impersonate?: string; 9 | }; 10 | address: string; 11 | } 12 | 13 | export async function extractAddressFromToken({ 14 | loginToken, 15 | extraInfoData, 16 | address 17 | }: IExtractAccountFromTokenProps): Promise { 18 | const accountDetails = await getAccountFromToken({ 19 | originalLoginToken: loginToken, 20 | extraInfoData, 21 | address 22 | }); 23 | 24 | if (accountDetails.modifiedLoginToken) { 25 | setLoginToken(accountDetails.modifiedLoginToken); 26 | } 27 | 28 | return accountDetails.address; 29 | } 30 | -------------------------------------------------------------------------------- /src/providers/DappProvider/helpers/login/helpers/getAccountFromToken.ts: -------------------------------------------------------------------------------- 1 | import { getModifiedLoginToken } from './getModifiedLoginToken'; 2 | 3 | interface GetAccountFromTokenType { 4 | address: string; 5 | originalLoginToken?: string; 6 | extraInfoData: { 7 | multisig?: string; 8 | impersonate?: string; 9 | }; 10 | } 11 | 12 | export async function getAccountFromToken({ 13 | originalLoginToken, 14 | extraInfoData, 15 | address 16 | }: GetAccountFromTokenType) { 17 | const modifiedLoginToken = await getModifiedLoginToken({ 18 | loginToken: originalLoginToken, 19 | extraInfoData 20 | }); 21 | 22 | const tokenAddress = 23 | extraInfoData.multisig || extraInfoData.impersonate || address; 24 | 25 | const accountAddress = modifiedLoginToken != null ? tokenAddress : address; 26 | 27 | return { 28 | address: accountAddress, 29 | modifiedLoginToken 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/providers/DappProvider/helpers/login/helpers/getModifiedLoginToken.ts: -------------------------------------------------------------------------------- 1 | import { decodeLoginToken } from 'services/nativeAuth/helpers/decodeLoginToken'; 2 | import { LatestBlockHashType } from 'services/nativeAuth/helpers/getLatestBlockHash'; 3 | import { nativeAuth } from 'services/nativeAuth/nativeAuth'; 4 | 5 | export interface GetMultiSigLoginTokenType { 6 | loginToken?: string; 7 | extraInfoData: { 8 | multisig?: string; 9 | impersonate?: string; 10 | }; 11 | } 12 | 13 | export async function getModifiedLoginToken({ 14 | loginToken, 15 | extraInfoData 16 | }: GetMultiSigLoginTokenType) { 17 | if (loginToken == null || Object.keys(extraInfoData).length === 0) { 18 | return null; 19 | } 20 | 21 | const data = decodeLoginToken(String(loginToken)); 22 | const { timestamp, ...rest } = data?.extraInfo || {}; 23 | 24 | const isValidData = data && timestamp != null; 25 | 26 | if (!isValidData) { 27 | return null; 28 | } 29 | const latestBlockInfo: LatestBlockHashType = { 30 | hash: String(data?.blockHash), 31 | timestamp: Number(timestamp) 32 | }; 33 | 34 | const tokenLogin = await nativeAuth({ 35 | extraInfo: { ...rest, ...extraInfoData }, 36 | expirySeconds: data?.ttl, 37 | origin: data?.origin 38 | }).initialize({ 39 | latestBlockInfo 40 | }); 41 | 42 | return tokenLogin; 43 | } 44 | -------------------------------------------------------------------------------- /src/providers/DappProvider/helpers/signMessage/getVerifier.ts: -------------------------------------------------------------------------------- 1 | import { Address, UserPublicKey, UserVerifier } from 'lib/sdkCore'; 2 | 3 | export function getVerifier(address: string) { 4 | const publicKey = new UserPublicKey(new Address(address).getPublicKey()); 5 | 6 | return new UserVerifier(publicKey); 7 | } 8 | -------------------------------------------------------------------------------- /src/providers/DappProvider/helpers/signMessage/signMessageWithProvider.ts: -------------------------------------------------------------------------------- 1 | import { Message, Address } from 'lib/sdkCore'; 2 | import { CrossWindowProvider } from 'lib/sdkWebWalletCrossWindowProvider'; 3 | import { getAddress } from 'methods/account/getAddress'; 4 | import { 5 | IProvider, 6 | ProviderTypeEnum 7 | } from 'providers/types/providerFactory.types'; 8 | 9 | export type SignMessageType = { 10 | provider: IProvider; 11 | message: Message; 12 | options?: { 13 | hasConsentPopup?: boolean; 14 | }; 15 | }; 16 | 17 | export async function signMessageWithProvider({ 18 | message, 19 | provider, 20 | options 21 | }: SignMessageType): Promise { 22 | const address = getAddress(); 23 | 24 | const messageToSign = new Message({ 25 | address: new Address(address), 26 | data: message.data 27 | }); 28 | 29 | if ( 30 | options?.hasConsentPopup && 31 | provider.getType() === ProviderTypeEnum.crossWindow 32 | ) { 33 | (provider as unknown as CrossWindowProvider).setShouldShowConsentPopup( 34 | true 35 | ); // TODO: is this still needed ? 36 | } 37 | 38 | const signedMessage = await provider.signMessage(messageToSign, options); 39 | 40 | return signedMessage; 41 | } 42 | -------------------------------------------------------------------------------- /src/providers/DappProvider/helpers/signMessage/tests/verifyMessage.test.ts: -------------------------------------------------------------------------------- 1 | import { verifyMessage } from '../verifyMessage'; 2 | 3 | jest.mock('../getVerifier', () => ({ 4 | getVerifier: jest.fn().mockImplementation(() => ({ 5 | verify: jest.fn().mockImplementation(() => true) 6 | })) 7 | })); 8 | 9 | describe('Verify Message test', () => { 10 | const signature = 11 | '{"address":"erd1wh9c0sjr2xn8hzf02lwwcr4jk2s84tat9ud2kaq6zr7xzpvl9l5q8awmex","message":"0x54455354","signature":"0xfd7578037cdaed106e04c437821828a78ac0eb93d42118b6c8a11510520400cfa4a06dfa446cd6437c0f91264675bd554cbcb2e0b08622e9f210772890f12d01","version":1,"signer":"ErdJS"}'; 12 | 13 | it('should verify message successfully', async () => { 14 | const { address, isVerified, message } = await verifyMessage(signature); 15 | 16 | expect(address).toStrictEqual( 17 | 'erd1wh9c0sjr2xn8hzf02lwwcr4jk2s84tat9ud2kaq6zr7xzpvl9l5q8awmex' 18 | ); 19 | 20 | expect(isVerified).toStrictEqual(true); 21 | expect(message).toStrictEqual('TEST'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/providers/DappProvider/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DappProvider'; 2 | -------------------------------------------------------------------------------- /src/providers/helpers/accountProvider.ts: -------------------------------------------------------------------------------- 1 | import { IProvider } from 'providers/types/providerFactory.types'; 2 | import { DappProvider } from '../DappProvider'; 3 | import { emptyProvider } from './emptyProvider'; 4 | 5 | export type ProvidersType = IProvider; 6 | 7 | let accountProvider: DappProvider | null = null; 8 | 9 | export function setAccountProvider( 10 | provider: TProvider 11 | ) { 12 | accountProvider = provider; 13 | } 14 | 15 | export function getAccountProvider(): DappProvider { 16 | return accountProvider || new DappProvider(emptyProvider); 17 | } 18 | -------------------------------------------------------------------------------- /src/providers/helpers/cancelCrossWindowAction.ts: -------------------------------------------------------------------------------- 1 | import { CrossWindowProvider } from 'lib/sdkWebWalletCrossWindowProvider'; 2 | import { getAccountProvider } from './accountProvider'; 3 | import { ProviderTypeEnum } from '../types/providerFactory.types'; 4 | 5 | export const cancelCrossWindowAction = async () => { 6 | const provider = getAccountProvider(); 7 | 8 | if (provider.getType() === ProviderTypeEnum.crossWindow) { 9 | const crossWindowProvider = provider as unknown as CrossWindowProvider; 10 | await crossWindowProvider.cancelAction(); 11 | await crossWindowProvider.dispose(); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/providers/helpers/clearInitiatedLogins.ts: -------------------------------------------------------------------------------- 1 | import { CrossWindowProvider } from 'lib/sdkWebWalletCrossWindowProvider'; 2 | import { IframeProvider } from 'lib/sdkWebWalletIframeProvider'; 3 | import { ProviderTypeEnum, ProviderType } from '../types/providerFactory.types'; 4 | 5 | export const clearInitiatedLogins = ( 6 | props?: { 7 | skipLoginMethod: ProviderType; 8 | } | null 9 | ) => { 10 | Object.values(ProviderTypeEnum).forEach((method) => { 11 | if (method === props?.skipLoginMethod) { 12 | return; 13 | } 14 | 15 | switch (method) { 16 | case ProviderTypeEnum.crossWindow: { 17 | const crossWindowProvider = CrossWindowProvider.getInstance(); 18 | if (crossWindowProvider.isInitialized()) { 19 | crossWindowProvider.dispose(); 20 | } 21 | break; 22 | } 23 | 24 | case ProviderTypeEnum.metamask: 25 | case ProviderTypeEnum.passkey: { 26 | const iframeProvider = IframeProvider.getInstance(); 27 | if (iframeProvider.isInitialized()) { 28 | iframeProvider.dispose(); 29 | } 30 | break; 31 | } 32 | 33 | default: 34 | break; 35 | } 36 | }); 37 | 38 | return null; 39 | }; 40 | -------------------------------------------------------------------------------- /src/providers/helpers/restoreProvider.ts: -------------------------------------------------------------------------------- 1 | import { providerTypeSelector } from 'store/selectors'; 2 | import { getState } from 'store/store'; 3 | import { setAccountProvider } from './accountProvider'; 4 | import { ProviderFactory } from '../ProviderFactory'; 5 | 6 | export async function restoreProvider() { 7 | const type = providerTypeSelector(getState()); 8 | 9 | if (!type) { 10 | return; 11 | } 12 | 13 | const provider = await ProviderFactory.create({ 14 | type 15 | }); 16 | 17 | if (!provider) { 18 | throw new Error('Provider not found'); 19 | } 20 | 21 | setAccountProvider(provider); 22 | } 23 | -------------------------------------------------------------------------------- /src/providers/index.ts: -------------------------------------------------------------------------------- 1 | export { getAccountProvider } from './helpers/accountProvider'; 2 | export * from './strategies'; 3 | -------------------------------------------------------------------------------- /src/providers/strategies/CrossWindowProviderStrategy/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CrossWindowProviderStrategy'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/providers/strategies/CrossWindowProviderStrategy/types/crossWindow.types.ts: -------------------------------------------------------------------------------- 1 | export interface CrossWindowConfig { 2 | /** 3 | * default: `false` 4 | */ 5 | isBrowserWithPopupConfirmation?: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /src/providers/strategies/CrossWindowProviderStrategy/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './crossWindow.types'; 2 | -------------------------------------------------------------------------------- /src/providers/strategies/ExtensionProviderStrategy/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ExtensionProviderStrategy'; 2 | -------------------------------------------------------------------------------- /src/providers/strategies/IframeProviderStrategy/index.ts: -------------------------------------------------------------------------------- 1 | export * from './IframeProviderStrategy'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/providers/strategies/IframeProviderStrategy/types/iframe.types.ts: -------------------------------------------------------------------------------- 1 | import { IframeLoginTypes } from '@multiversx/sdk-web-wallet-iframe-provider/out/constants'; 2 | 3 | export type IframeProviderType = { 4 | type: IframeLoginTypes; 5 | address?: string; 6 | walletUrl?: string; 7 | }; 8 | -------------------------------------------------------------------------------- /src/providers/strategies/IframeProviderStrategy/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './iframe.types'; 2 | -------------------------------------------------------------------------------- /src/providers/strategies/LedgerProviderStrategy/helpers/getAuthTokenText.ts: -------------------------------------------------------------------------------- 1 | import { decodeLoginToken } from 'services/nativeAuth/helpers/decodeLoginToken'; 2 | import getLedgerVersionOptions from './getLedgerVersionOptions'; 3 | import { secondsToTimeString } from './secondsToTimeString'; 4 | 5 | export const getAuthTokenText = ({ 6 | loginToken, 7 | version 8 | }: { 9 | loginToken?: string; 10 | version?: string; 11 | }) => { 12 | if (!loginToken || !version) { 13 | return null; 14 | } 15 | 16 | const { ledgerWithUsernames } = getLedgerVersionOptions(version); 17 | const nativeAuthInfo = decodeLoginToken(loginToken); 18 | if (nativeAuthInfo == null) { 19 | return null; 20 | } 21 | 22 | const confirmAddressText = 'Confirm Ledger Address'; 23 | const authText = 'Authorise Authentication Token'; 24 | 25 | if (ledgerWithUsernames) { 26 | const time = secondsToTimeString(nativeAuthInfo.ttl); 27 | 28 | return { 29 | data: `${nativeAuthInfo.origin} for ${time}.`, 30 | confirmAddressText, 31 | authText 32 | }; 33 | } 34 | 35 | return { 36 | data: loginToken, 37 | confirmAddressText, 38 | authText 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /src/providers/strategies/LedgerProviderStrategy/helpers/getLedgerConfiguration.ts: -------------------------------------------------------------------------------- 1 | import { HWProvider } from '@multiversx/sdk-hw-provider'; 2 | import { IHWWalletApp } from '@multiversx/sdk-hw-provider/out/interface'; 3 | 4 | import { LEDGER_CONTRACT_DATA_ENABLED_VALUE } from 'constants/index'; 5 | 6 | export async function getLedgerConfiguration(initializedHwWalletP: HWProvider) { 7 | if (!initializedHwWalletP.isInitialized()) { 8 | throw new Error('Unable to get version. Provider not initialized'); 9 | } 10 | const hwApp: IHWWalletApp = (initializedHwWalletP as any).hwApp; 11 | const { contractData, version } = await hwApp.getAppConfiguration(); 12 | const dataEnabled = contractData === LEDGER_CONTRACT_DATA_ENABLED_VALUE; 13 | return { version, dataEnabled }; 14 | } 15 | -------------------------------------------------------------------------------- /src/providers/strategies/LedgerProviderStrategy/helpers/getLedgerErrorCodes.ts: -------------------------------------------------------------------------------- 1 | import { ledgerErrorCodes } from 'constants/ledger.constants'; 2 | 3 | const ledgerAppErrorText = 'Check if the MultiversX app is open on Ledger'; 4 | const notConnectedCode = 0x6e01; 5 | const wrongClaCode = 0x6e00; 6 | const inactiveAppCodes = [notConnectedCode, wrongClaCode]; 7 | 8 | export function getLedgerErrorCodes(err?: any) { 9 | let errorMessage: string | null = null; 10 | if (err?.statusCode in ledgerErrorCodes) { 11 | const statusCode: keyof typeof ledgerErrorCodes = err?.statusCode; 12 | const { message } = ledgerErrorCodes[statusCode]; 13 | errorMessage = inactiveAppCodes.includes(statusCode) 14 | ? ledgerAppErrorText 15 | : message; 16 | } 17 | 18 | if (!errorMessage && String(err).includes('The device was disconnected.')) { 19 | errorMessage = 'The device was disconnected'; 20 | } 21 | 22 | return { 23 | errorMessage, 24 | defaultErrorMessage: ledgerAppErrorText 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/providers/strategies/LedgerProviderStrategy/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getAuthTokenText'; 2 | export * from './getLedgerConfiguration'; 3 | export * from './getLedgerErrorCodes'; 4 | export * from './getLedgerProvider'; 5 | export * from './getLedgerVersionOptions'; 6 | export * from './secondsToTimeString'; 7 | -------------------------------------------------------------------------------- /src/providers/strategies/LedgerProviderStrategy/helpers/secondsToTimeString.ts: -------------------------------------------------------------------------------- 1 | import isString from 'lodash.isstring'; 2 | 3 | export const secondsToTimeString = (seconds: number) => { 4 | if (seconds <= 0 || isNaN(seconds) || !seconds || isString(seconds)) { 5 | return 'N/A time'; 6 | } 7 | 8 | if (seconds >= 86400) { 9 | return 'more than one day'; 10 | } 11 | 12 | const hours = Math.floor(seconds / 3600); 13 | const remainingSeconds = seconds % 3600; 14 | const minutes = Math.floor(remainingSeconds / 60); 15 | const remainingSecondsAfterMinutes = remainingSeconds % 60; 16 | 17 | const parts = []; 18 | if (hours > 0) { 19 | parts.push(`${hours}h`); 20 | } 21 | if (minutes > 0) { 22 | parts.push(`${minutes}min`); 23 | } 24 | if (remainingSecondsAfterMinutes > 0) { 25 | parts.push(`${remainingSecondsAfterMinutes}sec`); 26 | } 27 | 28 | return parts.join(' '); 29 | }; 30 | -------------------------------------------------------------------------------- /src/providers/strategies/LedgerProviderStrategy/helpers/signLedgerMessage.ts: -------------------------------------------------------------------------------- 1 | import { providerLabels } from 'constants/providerFactory.constants'; 2 | import { Message } from 'lib/sdkCore'; 3 | import { getLedgerErrorCodes } from './getLedgerErrorCodes'; 4 | import { signMessage } from '../../helpers/signMessage/signMessage'; 5 | 6 | export async function signLedgerMessage({ 7 | message, 8 | handleSignMessage 9 | }: { 10 | message: Message; 11 | handleSignMessage: (msg: Message) => Promise; 12 | }): Promise { 13 | try { 14 | const signedMessage = await signMessage({ 15 | message, 16 | handleSignMessage: handleSignMessage, 17 | providerType: providerLabels.ledger 18 | }); 19 | return signedMessage; 20 | } catch (error) { 21 | const { errorMessage } = getLedgerErrorCodes(error); 22 | throw errorMessage ? { message: errorMessage } : error; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/providers/strategies/LedgerProviderStrategy/helpers/updateAccountsList/index.ts: -------------------------------------------------------------------------------- 1 | export * from './updateAccountsList'; 2 | -------------------------------------------------------------------------------- /src/providers/strategies/LedgerProviderStrategy/helpers/updateAccountsList/updateAccountsList.types.ts: -------------------------------------------------------------------------------- 1 | import { HWProvider } from '@multiversx/sdk-hw-provider/out'; 2 | 3 | import { LedgerConnectStateManager } from 'managers/internal/LedgerConnectStateManager'; 4 | 5 | import { ILedgerAccount } from '../../types/ledger.types'; 6 | 7 | export type UpdateAccountObjectType = Record; 8 | export interface IUpdateAccountsList { 9 | manager: LedgerConnectStateManager | null; 10 | provider: HWProvider | null; 11 | } 12 | -------------------------------------------------------------------------------- /src/providers/strategies/LedgerProviderStrategy/index.ts: -------------------------------------------------------------------------------- 1 | export * from './LedgerProviderStrategy'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/providers/strategies/LedgerProviderStrategy/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ledgerProvider.types'; 2 | -------------------------------------------------------------------------------- /src/providers/strategies/LedgerProviderStrategy/types/ledger.types.ts: -------------------------------------------------------------------------------- 1 | // types here need to be synced with the types in sdk-dapp-ui ledger-connect.types 2 | 3 | export interface IConnectScreenData { 4 | customContentMarkup?: string; 5 | disabled?: boolean; 6 | error?: string; 7 | } 8 | 9 | export interface IAccountScreenData { 10 | accounts: ILedgerAccount[]; 11 | startIndex: number; 12 | addressesPerPage: number; 13 | isLoading: boolean; 14 | } 15 | 16 | export interface IConfirmScreenData { 17 | data?: string; 18 | selectedAddress: string; 19 | confirmAddressText?: string; 20 | authText?: string; 21 | explorerLink: string; 22 | } 23 | 24 | export interface ILedgerConnectPanelData { 25 | connectScreenData: IConnectScreenData | null; 26 | accountScreenData: IAccountScreenData | null; 27 | shouldClose?: true; 28 | confirmScreenData: IConfirmScreenData | null; 29 | } 30 | 31 | export interface ILedgerAccount { 32 | address: string; 33 | balance: string; 34 | usdValue?: string; 35 | index: number; 36 | } 37 | 38 | export enum LedgerConnectEventsEnum { 39 | CONNECT_DEVICE = 'CONNECT_DEVICE', 40 | ACCESS_WALLET = 'ACCESS_WALLET', 41 | NEXT_PAGE = 'NEXT_PAGE', 42 | PREV_PAGE = 'PREV_PAGE', 43 | CLOSE_LEDGER_CONNECT_PANEL = 'CLOSE_LEDGER_CONNECT_PANEL', 44 | OPEN_LEDGER_CONNECT_PANEL = 'OPEN_LEDGER_CONNECT_PANEL', 45 | DATA_UPDATE = 'DATA_UPDATE' 46 | } 47 | -------------------------------------------------------------------------------- /src/providers/strategies/LedgerProviderStrategy/types/ledgerProvider.types.ts: -------------------------------------------------------------------------------- 1 | import { IProviderAccount } from '@multiversx/sdk-wallet-connect-provider/out'; 2 | 3 | export type LedgerConfigType = { 4 | version: string; 5 | dataEnabled: boolean; 6 | }; 7 | 8 | export type LedgerLoginType = (options?: { 9 | addressIndex: number; 10 | }) => Promise; 11 | -------------------------------------------------------------------------------- /src/providers/strategies/WalletConnectProviderStrategy/index.ts: -------------------------------------------------------------------------------- 1 | export * from './WalletConnectProviderStrategy'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /src/providers/strategies/WalletConnectProviderStrategy/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './walletConnect.types'; 2 | -------------------------------------------------------------------------------- /src/providers/strategies/WalletConnectProviderStrategy/types/walletConnect.types.ts: -------------------------------------------------------------------------------- 1 | import { WalletConnectV2ProviderOptionsType } from '@multiversx/sdk-wallet-connect-provider/out'; 2 | 3 | export enum WalletConnectV2Error { 4 | invalidAddress = 'Invalid address', 5 | invalidConfig = 'Invalid WalletConnect setup', 6 | invalidTopic = 'Expired connection', 7 | sessionExpired = 'Unable to connect to existing session', 8 | connectError = 'Unable to connect', 9 | userRejected = 'User rejected connection proposal', 10 | userRejectedExisting = 'User rejected existing connection proposal', 11 | errorLogout = 'Unable to remove existing pairing', 12 | invalidChainID = 'Invalid chainID', 13 | actionError = 'Unable to send event' 14 | } 15 | 16 | // types here need to be synced with the types in sdk-dapp-ui 17 | export enum WalletConnectEventsEnum { 18 | CLOSE = 'CLOSE', 19 | DATA_UPDATE = 'DATA_UPDATE', 20 | UI_DISCONNECTED = 'UI_DISCONNECTED' 21 | } 22 | 23 | export interface IWalletConnectModalData { 24 | wcURI: string; 25 | } 26 | 27 | export interface WalletConnectConfig { 28 | walletConnectV2ProjectId: string; 29 | walletConnectV2RelayAddress?: string; 30 | walletConnectV2Options?: WalletConnectV2ProviderOptionsType; 31 | customRequestMethods?: string[]; 32 | walletConnectDeepLink?: string; 33 | } 34 | -------------------------------------------------------------------------------- /src/providers/strategies/WebviewProviderStrategy/index.ts: -------------------------------------------------------------------------------- 1 | export * from './WebviewProviderStrategy'; 2 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/getPendingTransactionsHandlers.ts: -------------------------------------------------------------------------------- 1 | import { PendingTransactionsStateManager } from 'managers/internal/PendingTransactionsStateManager/PendingTransactionsStateManager'; 2 | 3 | export async function getPendingTransactionsHandlers(props?: { 4 | cancelAction?: () => Promise | undefined; 5 | }) { 6 | const pendingTransactionsStateManager = 7 | PendingTransactionsStateManager.getInstance(); 8 | await pendingTransactionsStateManager.openUI(); 9 | 10 | const onClose = async ({ 11 | shouldCancelAction = true 12 | }: { shouldCancelAction?: boolean } = {}) => { 13 | pendingTransactionsStateManager.closeUI(); 14 | 15 | if (shouldCancelAction && props?.cancelAction) { 16 | await props.cancelAction(); 17 | } 18 | }; 19 | 20 | return { manager: pendingTransactionsStateManager, onClose }; 21 | } 22 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export { getPendingTransactionsHandlers } from './getPendingTransactionsHandlers'; 2 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/calculateFeeInFiat.ts: -------------------------------------------------------------------------------- 1 | import { DECIMALS, DIGITS, formatAmount } from 'lib/sdkDappUtils'; 2 | import { getUsdValue } from 'utils/operations/getUsdValue'; 3 | 4 | export interface CalculateFeeInFiatType { 5 | feeLimit: string; 6 | egldPriceInUsd: number; 7 | hideEqualSign?: boolean; 8 | } 9 | 10 | export const calculateFeeInFiat = ({ 11 | feeLimit, 12 | egldPriceInUsd, 13 | hideEqualSign 14 | }: CalculateFeeInFiatType) => { 15 | const amount = formatAmount({ 16 | input: feeLimit, 17 | decimals: DECIMALS, 18 | digits: DIGITS, 19 | showLastNonZeroDecimal: true 20 | }); 21 | 22 | const feeAsUsdValue = getUsdValue({ 23 | amount, 24 | usd: egldPriceInUsd, 25 | decimals: DIGITS 26 | }); 27 | 28 | if (hideEqualSign) { 29 | return feeAsUsdValue; 30 | } 31 | 32 | return `≈ ${feeAsUsdValue}`; 33 | }; 34 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/getCommonData/helpers/checkIsValidSender.ts: -------------------------------------------------------------------------------- 1 | import { AccountType } from 'types/account.types'; 2 | 3 | // Don't allow signing if the logged in account's address 4 | // is neither the sender or the sender account's active guardian 5 | export const checkIsValidSender = ( 6 | senderAccount: Partial | null, 7 | address: string | string[] 8 | ) => { 9 | if (!senderAccount) { 10 | return true; 11 | } 12 | 13 | if (Array.isArray(address)) { 14 | return address.some( 15 | (addr) => 16 | senderAccount.address === addr || 17 | senderAccount.activeGuardianAddress === addr 18 | ); 19 | } 20 | 21 | return ( 22 | senderAccount.address === address || 23 | senderAccount.activeGuardianAddress === address 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/getCommonData/helpers/getHighlight.ts: -------------------------------------------------------------------------------- 1 | import { TransactionDataTokenType } from 'types/transactions.types'; 2 | 3 | export const getHighlight = (txInfoToken?: TransactionDataTokenType) => { 4 | if (!txInfoToken?.multiTxData) { 5 | return null; 6 | } 7 | 8 | return txInfoToken.multiTxData; 9 | }; 10 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/getCommonData/helpers/getRecommendedGasPrice.ts: -------------------------------------------------------------------------------- 1 | import { IPlainTransactionObject } from 'lib/sdkCore'; 2 | import { recommendGasPrice } from 'lib/sdkDappUtils'; 3 | import { ISignTransactionsPanelCommonData } from 'managers/internal/SignTransactionsStateManager/types/signTransactionsPanel.types'; 4 | 5 | interface GetRecommendedGasPricePropsType { 6 | transaction: IPlainTransactionObject; 7 | gasPriceData?: { 8 | initialGasPrice: number; 9 | ppu: ISignTransactionsPanelCommonData['ppu']; 10 | }; 11 | } 12 | 13 | export function getRecommendedGasPrice({ 14 | transaction, 15 | gasPriceData 16 | }: GetRecommendedGasPricePropsType) { 17 | if (!gasPriceData) { 18 | throw new Error('Gas price not found for nonce: ' + transaction.nonce); 19 | } 20 | 21 | const { initialGasPrice, ppu } = gasPriceData; 22 | 23 | const newGasPrice = ppu 24 | ? recommendGasPrice({ 25 | transactionDataLength: String(transaction?.data || '').length, 26 | transactionGasLimit: transaction.gasLimit, 27 | ppu 28 | }) 29 | : initialGasPrice; 30 | 31 | return newGasPrice; 32 | } 33 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/getCommonData/helpers/getScCall.ts: -------------------------------------------------------------------------------- 1 | import { TransactionDataTokenType } from 'types/transactions.types'; 2 | import { decodePart } from 'utils/decoders/decodePart'; 3 | 4 | export const getScCall = (txInfoToken?: TransactionDataTokenType) => { 5 | if (!txInfoToken?.multiTxData) { 6 | return null; 7 | } 8 | 9 | const { multiTxData, tokenId } = txInfoToken; 10 | 11 | if (tokenId) { 12 | return null; 13 | } 14 | 15 | const scCall = decodePart(multiTxData); 16 | return scCall; 17 | }; 18 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/getCommonData/helpers/getTokenType.ts: -------------------------------------------------------------------------------- 1 | import { EsdtEnumType, NftEnumType } from 'types/tokens.types'; 2 | 3 | export const getTokenType = (type?: NftEnumType) => { 4 | switch (type) { 5 | case NftEnumType.NonFungibleESDT: 6 | case NftEnumType.SemiFungibleESDT: 7 | case NftEnumType.MetaESDT: 8 | return type; 9 | default: 10 | return EsdtEnumType.FungibleESDT; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/getCommonData/helpers/getTxInfoByDataField.ts: -------------------------------------------------------------------------------- 1 | import { TransactionDataTokenType } from 'types/transactions.types'; 2 | const defaultTransactionInfo: TransactionDataTokenType = { 3 | tokenId: '', 4 | amount: '', 5 | type: '', 6 | multiTxData: '', 7 | receiver: '' 8 | }; 9 | 10 | export function getTxInfoByDataField({ 11 | data, 12 | multiTransactionData, 13 | parsedTransactionsByDataField 14 | }: { 15 | data: string; 16 | multiTransactionData?: string; 17 | parsedTransactionsByDataField: Record; 18 | }): TransactionDataTokenType { 19 | if (parsedTransactionsByDataField == null) { 20 | return defaultTransactionInfo; 21 | } 22 | 23 | if (data in parsedTransactionsByDataField) { 24 | return parsedTransactionsByDataField[data]; 25 | } 26 | 27 | if ( 28 | multiTransactionData != null && 29 | String(multiTransactionData) in parsedTransactionsByDataField 30 | ) { 31 | return parsedTransactionsByDataField[multiTransactionData]; 32 | } 33 | 34 | return defaultTransactionInfo; 35 | } 36 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/getFeeData.ts: -------------------------------------------------------------------------------- 1 | import { GAS_PER_DATA_BYTE, GAS_PRICE_MODIFIER } from 'constants/mvx.constants'; 2 | import { Transaction } from 'lib/sdkCore'; 3 | import { formatAmount } from 'lib/sdkDappUtils'; 4 | import { calculateFeeInFiat } from './calculateFeeInFiat'; 5 | import { calculateFeeLimit } from './calculateFeeLimit'; 6 | 7 | export const getFeeData = ({ 8 | transaction, 9 | price 10 | }: { 11 | transaction: Transaction; 12 | price?: number; 13 | }) => { 14 | const feeLimit = calculateFeeLimit({ 15 | gasPerDataByte: String(GAS_PER_DATA_BYTE), 16 | gasPriceModifier: String(GAS_PRICE_MODIFIER), 17 | gasLimit: transaction.getGasLimit().valueOf().toString(), 18 | gasPrice: transaction.getGasPrice().valueOf().toString(), 19 | data: transaction.getData().toString(), 20 | chainId: transaction.getChainID().valueOf() 21 | }); 22 | 23 | const feeLimitFormatted = formatAmount({ 24 | input: feeLimit, 25 | showLastNonZeroDecimal: true 26 | }); 27 | 28 | const feeInFiatLimit = price 29 | ? calculateFeeInFiat({ 30 | feeLimit, 31 | egldPriceInUsd: price, 32 | hideEqualSign: true 33 | }) 34 | : null; 35 | 36 | return { feeLimitFormatted, feeInFiatLimit, feeLimit }; 37 | }; 38 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/getMultiEsdtTransferData/getMultiEsdtTransferData.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '@multiversx/sdk-core'; 2 | import { 3 | MultiSignTransactionType, 4 | TransactionDataTokenType 5 | } from 'types/transactions.types'; 6 | import { parseMultiEsdtTransferDataForMultipleTransactions } from './helpers/parseMultiEsdtTransferDataForMultipleTransactions'; 7 | 8 | export type MultiEsdtTransferDataReturnType = ReturnType< 9 | typeof getMultiEsdtTransferData 10 | >; 11 | 12 | export function getMultiEsdtTransferData(transactions?: Transaction[]): { 13 | parsedTransactionsByDataField: Record; 14 | allTransactions: MultiSignTransactionType[]; 15 | } { 16 | const { allTransactions, parsedTransactionsByDataField } = 17 | parseMultiEsdtTransferDataForMultipleTransactions({ transactions }); 18 | 19 | return { 20 | parsedTransactionsByDataField, 21 | allTransactions 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/getMultiEsdtTransferData/helpers/getAllStringOccurrences.ts: -------------------------------------------------------------------------------- 1 | export const getAllStringOccurrences = ( 2 | sourceStr: string, 3 | searchStr: string 4 | ) => { 5 | const startingIndices = []; 6 | 7 | let indexOccurence = sourceStr.indexOf(searchStr, 0); 8 | 9 | while (indexOccurence >= 0) { 10 | startingIndices.push(indexOccurence); 11 | indexOccurence = sourceStr.indexOf(searchStr, indexOccurence + 1); 12 | } 13 | 14 | return startingIndices; 15 | }; 16 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/getMultiEsdtTransferData/tests/getMultiEsdtTransferData.test.ts: -------------------------------------------------------------------------------- 1 | import { getMultiEsdtTransferData } from '../getMultiEsdtTransferData'; 2 | 3 | describe('getMultiEsdtTransferData', () => { 4 | it('should extract transaction information', async () => { 5 | const data = getMultiEsdtTransferData([]); 6 | // Assert the result is correct based on your mock data 7 | expect(data).toBeTruthy(); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/guardTransactions/getCrossWindowProvider.ts: -------------------------------------------------------------------------------- 1 | import { CrossWindowProvider } from 'lib/sdkWebWalletCrossWindowProvider'; 2 | 3 | export async function getCrossWindowProvider({ 4 | address, 5 | walletUrl 6 | }: { 7 | address: string; 8 | walletUrl: string; 9 | }) { 10 | try { 11 | const provider = CrossWindowProvider.getInstance(); 12 | const success = await provider.init(); 13 | provider.setAddress(address).setWalletUrl(walletUrl); 14 | 15 | if (success) { 16 | return provider; 17 | } else { 18 | console.error('Could not initialize CrossWindowWallet Provider'); 19 | } 20 | } catch (err) { 21 | console.error('Unable to login to CrossWindowWallet Provider', err); 22 | } 23 | return null; 24 | } 25 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/guardTransactions/getTransactionsNeedGuardianSigning.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from 'lib/sdkCore'; 2 | 3 | export const getTransactionsNeedGuardianSigning = ({ 4 | transactions, 5 | isGuarded 6 | }: { 7 | transactions: Transaction[]; 8 | isGuarded?: boolean; 9 | }) => { 10 | if (!isGuarded || transactions.length === 0) { 11 | return false; 12 | } 13 | 14 | const allSigned = transactions.every((tx) => 15 | Boolean(tx.getGuardianSignature().toString('hex')) 16 | ); 17 | 18 | return !allSigned; 19 | }; 20 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/guardTransactions/guardTransactions.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from 'lib/sdkCore'; 2 | import { getAccount } from 'methods/account/getAccount'; 3 | import { networkSelector } from 'store/selectors'; 4 | import { getState } from 'store/store'; 5 | import { getCrossWindowProvider } from './getCrossWindowProvider'; 6 | import { getTransactionsNeedGuardianSigning } from './getTransactionsNeedGuardianSigning'; 7 | 8 | /* 9 | Performs guard transactions if needed 10 | */ 11 | export const guardTransactions = async (transactions: Transaction[]) => { 12 | const { isGuarded } = getAccount(); 13 | 14 | const needs2FAsigning = getTransactionsNeedGuardianSigning({ 15 | isGuarded, 16 | transactions 17 | }); 18 | 19 | if (!needs2FAsigning) { 20 | return transactions; 21 | } 22 | 23 | const sender = transactions[0].getSender().bech32().toString(); 24 | const { walletAddress } = networkSelector(getState()); 25 | 26 | const provider = await getCrossWindowProvider({ 27 | address: sender, 28 | walletUrl: walletAddress 29 | }); 30 | 31 | provider?.setShouldShowConsentPopup(false); 32 | const guardedTransactions = await provider?.guardTransactions(transactions); 33 | return guardedTransactions || []; 34 | }; 35 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/isTokenTransfer.ts: -------------------------------------------------------------------------------- 1 | export function isTokenTransfer({ 2 | tokenId, 3 | egldLabel 4 | }: { 5 | tokenId: string | undefined; 6 | egldLabel: string; 7 | }) { 8 | return Boolean(tokenId && tokenId !== egldLabel); 9 | } 10 | -------------------------------------------------------------------------------- /src/providers/strategies/helpers/signTransactions/helpers/tests/calculateFeeInFiat.test.ts: -------------------------------------------------------------------------------- 1 | import { calculateFeeInFiat } from '../calculateFeeInFiat'; 2 | 3 | describe('calculateFeeInFiat tests', () => { 4 | it('computes correct fee in fiat', () => { 5 | const fee = calculateFeeInFiat({ 6 | feeLimit: '50000000000000', 7 | egldPriceInUsd: 135.78 8 | }); 9 | expect(fee).toBe('≈ $0.0068'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/providers/strategies/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CrossWindowProviderStrategy'; 2 | export * from './ExtensionProviderStrategy'; 3 | export * from './IframeProviderStrategy'; 4 | export * from './LedgerProviderStrategy'; 5 | export * from './WalletConnectProviderStrategy'; 6 | -------------------------------------------------------------------------------- /src/react/account/useGetAccount.ts: -------------------------------------------------------------------------------- 1 | import { accountSelector } from '../../store/selectors/accountSelectors'; 2 | import { useSelector } from '../store/useSelector'; 3 | 4 | export const useGetAccount = () => { 5 | return useSelector(accountSelector); 6 | }; 7 | -------------------------------------------------------------------------------- /src/react/account/useGetAccountInfo.ts: -------------------------------------------------------------------------------- 1 | import { accountInfoSelector } from '../../store/selectors/accountSelectors'; 2 | import { useSelector } from '../store/useSelector'; 3 | 4 | export const useGetAccountInfo = () => { 5 | return useSelector(accountInfoSelector); 6 | }; 7 | -------------------------------------------------------------------------------- /src/react/account/useGetIsLoggedIn.ts: -------------------------------------------------------------------------------- 1 | import { isLoggedInSelector } from '../../store/selectors/accountSelectors'; 2 | import { useSelector } from '../store/useSelector'; 3 | 4 | export const useGetIsLoggedIn = () => { 5 | const isLoggedIn = useSelector(isLoggedInSelector); 6 | return isLoggedIn; 7 | }; 8 | -------------------------------------------------------------------------------- /src/react/account/useGetLatestNonce.ts: -------------------------------------------------------------------------------- 1 | import { getLatestNonce } from '../../methods/account/getLatestNonce'; 2 | import { accountSelector } from '../../store/selectors/accountSelectors'; 3 | import { useSelector } from '../store/useSelector'; 4 | 5 | export const useGetLatestNonce = () => { 6 | const account = useSelector(accountSelector); 7 | return getLatestNonce(account); 8 | }; 9 | -------------------------------------------------------------------------------- /src/react/index.ts: -------------------------------------------------------------------------------- 1 | export * from './store/useSelector'; 2 | -------------------------------------------------------------------------------- /src/react/loginInfo/useGetLoginInfo.ts: -------------------------------------------------------------------------------- 1 | import { isLoggedInSelector } from '../../store/selectors/accountSelectors'; 2 | import { loginInfoSelector } from '../../store/selectors/loginInfoSelectors'; 3 | import { useSelector } from '../store/useSelector'; 4 | 5 | export const useGetLoginInfo = () => { 6 | const loginInfo = useSelector(loginInfoSelector); 7 | const isLoggedIn = useSelector(isLoggedInSelector); 8 | 9 | return { ...loginInfo, isLoggedIn }; 10 | }; 11 | -------------------------------------------------------------------------------- /src/react/network/useGetNetworkConfig.ts: -------------------------------------------------------------------------------- 1 | import { networkConfigSelector } from '../../store/selectors/networkSelectors'; 2 | import { useSelector } from '../store/useSelector'; 3 | 4 | export const useGetNetworkConfig = () => { 5 | return useSelector(networkConfigSelector); 6 | }; 7 | -------------------------------------------------------------------------------- /src/react/store/createBoundedStore.ts: -------------------------------------------------------------------------------- 1 | import { useStore } from 'zustand'; 2 | import { StoreApi } from 'zustand/vanilla'; 3 | 4 | type ExtractState = S extends { getState: () => infer X } ? X : never; 5 | 6 | /** 7 | * 8 | * @param store 9 | * @returns a hook that can be used to access the store in ReactJS context 10 | * */ 11 | export const createBoundedUseStore = ((store) => (selector) => 12 | useStore(store, selector)) as >( 13 | store: S 14 | ) => { 15 | (): ExtractState; 16 | (selector: (state: ExtractState) => T): T; 17 | }; 18 | -------------------------------------------------------------------------------- /src/react/store/getReactStore.ts: -------------------------------------------------------------------------------- 1 | import { createBoundedUseStore } from './createBoundedStore'; 2 | import { getStore } from '../../store/store'; 3 | 4 | export const getReactStore = () => createBoundedUseStore(getStore()); 5 | -------------------------------------------------------------------------------- /src/react/store/useSelector.ts: -------------------------------------------------------------------------------- 1 | import { getReactStore } from './getReactStore'; 2 | import { StoreType } from '../../store/store.types'; 3 | 4 | type ExtractState = S extends { getState: () => infer T } ? T : StoreType; 5 | 6 | export function useSelector( 7 | selector: (state: ExtractState) => T 8 | ) { 9 | const useStore = getReactStore(); 10 | return useStore(selector); 11 | } 12 | -------------------------------------------------------------------------------- /src/react/transactions/useGetPendingTransactions.ts: -------------------------------------------------------------------------------- 1 | import { pendingTransactionsSelector } from '../../store/selectors/transactionsSelector'; 2 | import { useSelector } from '../store/useSelector'; 3 | 4 | export function useGetPendingTransactions() { 5 | const pendingTransactions = useSelector(pendingTransactionsSelector); 6 | return pendingTransactions; 7 | } 8 | -------------------------------------------------------------------------------- /src/react/transactions/useGetPendingTransactionsSessions.ts: -------------------------------------------------------------------------------- 1 | import { pendingTransactionsSessionsSelector } from '../../store/selectors/transactionsSelector'; 2 | import { useSelector } from '../store/useSelector'; 3 | 4 | export function useGetPendingTransactionsSessions() { 5 | const pendingSessions = useSelector(pendingTransactionsSessionsSelector); 6 | return pendingSessions; 7 | } 8 | -------------------------------------------------------------------------------- /src/react/transactions/useGetTransactionSessions.ts: -------------------------------------------------------------------------------- 1 | import { transactionsSliceSelector } from '../../store/selectors/transactionsSelector'; 2 | import { useSelector } from '../store/useSelector'; 3 | 4 | export function useGetTransactionSessions() { 5 | const sessions = useSelector(transactionsSliceSelector); 6 | return sessions; 7 | } 8 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './nativeAuth'; 2 | -------------------------------------------------------------------------------- /src/services/nativeAuth/helpers/decodeLoginToken.ts: -------------------------------------------------------------------------------- 1 | import isString from 'lodash.isstring'; 2 | import { decodeBase64 } from 'utils/decoders/base64Utils'; 3 | 4 | export interface DecodedLoginTokenType { 5 | blockHash: string; 6 | extraInfo?: { timestamp: number }; 7 | origin: string; 8 | ttl: number; 9 | } 10 | 11 | export const decodeLoginToken = ( 12 | loginToken: string 13 | ): DecodedLoginTokenType | null => { 14 | if (!loginToken || !isString(loginToken)) { 15 | return null; 16 | } 17 | 18 | const parts = loginToken.split('.'); 19 | 20 | if (parts.length !== 4) { 21 | return null; 22 | } 23 | 24 | try { 25 | const [origin, blockHash, ttl, extraInfo] = parts; 26 | const parsedExtraInfo = JSON.parse(decodeBase64(extraInfo)); 27 | const parsedOrigin = decodeBase64(origin); 28 | 29 | return { 30 | ttl: Number(ttl), 31 | extraInfo: parsedExtraInfo, 32 | origin: parsedOrigin, 33 | blockHash 34 | }; 35 | } catch (e) { 36 | console.error(`Error trying to decode ${loginToken}:`, e); 37 | 38 | return null; 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /src/services/nativeAuth/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './decodeLoginToken'; 2 | export * from './decodeNativeAuthToken'; 3 | export * from './getLatestBlockHash'; 4 | -------------------------------------------------------------------------------- /src/services/nativeAuth/helpers/tests/decodeLoginToken.test.ts: -------------------------------------------------------------------------------- 1 | import { decodeLoginToken } from '../decodeLoginToken'; 2 | 3 | const loginToken = 4 | 'bG9jYWxob3N0.f2d0897d0ca185a884349b10842a9fe4c749472d75cee30e311a82de29acd52b.86400.eyJ0aW1lc3RhbXAiOjE2Nzc1ODg3ODZ9'; 5 | 6 | describe('decodeLoginToken tests', () => { 7 | it('decodes loginToken token', () => { 8 | const parsed = decodeLoginToken(loginToken); 9 | expect(parsed).toStrictEqual({ 10 | blockHash: 11 | 'f2d0897d0ca185a884349b10842a9fe4c749472d75cee30e311a82de29acd52b', 12 | extraInfo: { 13 | timestamp: 1677588786 14 | }, 15 | origin: 'localhost', 16 | ttl: 86400 17 | }); 18 | }); 19 | it('decodes empty', () => { 20 | const parsed = decodeLoginToken(''); 21 | expect(parsed).toStrictEqual(null); 22 | }); 23 | it('decodes invalid', () => { 24 | const parsed = decodeLoginToken('invalid'); 25 | expect(parsed).toStrictEqual(null); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/services/nativeAuth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './nativeAuth'; 2 | -------------------------------------------------------------------------------- /src/services/nativeAuth/methods/buildNativeAuthConfig.ts: -------------------------------------------------------------------------------- 1 | import { NativeAuthConfigType } from '../nativeAuth.types'; 2 | import { getDefaultNativeAuthConfig } from './getDefaultNativeAuthConfig'; 3 | 4 | export const buildNativeAuthConfig = (config?: NativeAuthConfigType) => { 5 | const defaultNativeAuthConfig = getDefaultNativeAuthConfig(); 6 | 7 | return { 8 | origin: config?.origin ?? defaultNativeAuthConfig.origin, 9 | blockHashShard: config?.blockHashShard, 10 | expirySeconds: 11 | config?.expirySeconds ?? defaultNativeAuthConfig.expirySeconds, 12 | apiAddress: config?.apiAddress ?? defaultNativeAuthConfig.apiAddress, 13 | tokenExpirationToastWarningSeconds: 14 | config?.tokenExpirationToastWarningSeconds ?? 15 | defaultNativeAuthConfig.tokenExpirationToastWarningSeconds, 16 | extraInfo: config?.extraInfo ?? {}, 17 | gatewayUrl: config?.gatewayUrl, 18 | extraRequestHeaders: config?.extraRequestHeaders ?? {} 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /src/services/nativeAuth/methods/getDefaultNativeAuthConfig.ts: -------------------------------------------------------------------------------- 1 | import { getWindowLocation } from 'utils/window/getWindowLocation'; 2 | 3 | export const getDefaultNativeAuthConfig = ( 4 | apiAddress = 'https://api.multiversx.com' 5 | ) => { 6 | return { 7 | origin: getWindowLocation().origin, 8 | apiAddress, 9 | expirySeconds: 60 * 60 * 24, // one day 10 | tokenExpirationToastWarningSeconds: 5 * 60 // five minutes 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /src/services/nativeAuth/methods/getTokenExpiration.ts: -------------------------------------------------------------------------------- 1 | import { getUnixTimestamp } from 'utils/dateTime/getUnixTimestamp'; 2 | import { decodeNativeAuthToken } from '../helpers/decodeNativeAuthToken'; 3 | 4 | export interface GetTokenExpirationReturnType { 5 | isExpired: boolean; 6 | expiresAt?: number; 7 | secondsUntilExpires?: number; 8 | } 9 | 10 | const notFound = { 11 | isExpired: false 12 | }; 13 | 14 | export const getTokenExpiration = ( 15 | token?: string 16 | ): GetTokenExpirationReturnType => { 17 | if (!token) { 18 | return notFound; 19 | } 20 | 21 | const decodedToken = decodeNativeAuthToken(token); 22 | 23 | if (!decodedToken) { 24 | return notFound; 25 | } 26 | 27 | const unixNow = getUnixTimestamp(); 28 | const { ttl, extraInfo } = decodedToken; 29 | 30 | const timestamp = extraInfo?.timestamp; 31 | 32 | if (!timestamp) { 33 | return notFound; 34 | } 35 | 36 | const expiresAt = timestamp + ttl; 37 | 38 | const isExpired = unixNow > expiresAt; 39 | 40 | const secondsUntilExpires = expiresAt - unixNow; 41 | 42 | return { isExpired, expiresAt, secondsUntilExpires }; 43 | }; 44 | -------------------------------------------------------------------------------- /src/services/nativeAuth/methods/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getTokenExpiration'; 2 | export * from './buildNativeAuthConfig'; 3 | -------------------------------------------------------------------------------- /src/services/nativeAuth/nativeAuth.types.ts: -------------------------------------------------------------------------------- 1 | export interface NativeAuthConfigType { 2 | origin?: string; 3 | apiAddress?: string; 4 | expirySeconds?: number; 5 | blockHashShard?: number; 6 | gatewayUrl?: string; 7 | extraRequestHeaders?: { [key: string]: string }; 8 | extraInfo?: { 9 | [key: string]: string; 10 | }; 11 | /** 12 | * Displays a logout toast warning before token expiration. Defaults to 5 minutes. 13 | * 14 | * If set to `null`, will not trigger any warning. 15 | */ 16 | tokenExpirationToastWarningSeconds?: number | null; 17 | } 18 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | /************** 2 | * MSW config code 3 | ***************/ 4 | import 'isomorphic-fetch'; 5 | import { server } from './__mocks__/server'; 6 | 7 | // Establish API mocking before all tests. 8 | beforeAll(() => server.listen()); 9 | 10 | // Reset any request handlers that we may add during the tests, 11 | // so they don't affect other tests. 12 | afterEach(() => server.resetHandlers()); 13 | 14 | // Clean up after the tests are finished. 15 | afterAll(() => server.close()); 16 | 17 | /************** 18 | * files 19 | ***************/ 20 | 21 | window.scrollTo = jest.fn(); 22 | 23 | jest.retryTimes(3); 24 | 25 | /************** 26 | * window 27 | ***************/ 28 | 29 | Object.defineProperty(window, 'matchMedia', { 30 | writable: true, 31 | value: jest.fn().mockImplementation((query) => ({ 32 | matches: false, 33 | media: query, 34 | onchange: null, 35 | addListener: jest.fn(), // deprecated 36 | removeListener: jest.fn(), // deprecated 37 | addEventListener: jest.fn(), 38 | removeEventListener: jest.fn(), 39 | dispatchEvent: jest.fn() 40 | })) 41 | }); 42 | -------------------------------------------------------------------------------- /src/store/actions/account/index.ts: -------------------------------------------------------------------------------- 1 | export * from './accountActions'; 2 | -------------------------------------------------------------------------------- /src/store/actions/cache/cacheActions.ts: -------------------------------------------------------------------------------- 1 | import { getStore } from 'store/store'; 2 | 3 | export const saveToCache = ({ key, value }: { key: string; value: T }) => { 4 | getStore().setState( 5 | ({ cache: state }) => { 6 | state[key] = value; 7 | }, 8 | false, 9 | 'saveToCache' 10 | ); 11 | }; 12 | 13 | export const removeFromCache = (key: string) => { 14 | getStore().setState( 15 | ({ cache: state }) => { 16 | delete state[key]; 17 | }, 18 | false, 19 | 'removeFromCache' 20 | ); 21 | }; 22 | 23 | export const clearCache = () => { 24 | getStore().setState( 25 | ({ cache: state }) => { 26 | Object.keys(state).forEach((key) => { 27 | delete state[key]; 28 | }); 29 | }, 30 | false, 31 | 'clearCache' 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/store/actions/cache/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cacheActions'; 2 | -------------------------------------------------------------------------------- /src/store/actions/config/index.ts: -------------------------------------------------------------------------------- 1 | export * from './configActions'; 2 | -------------------------------------------------------------------------------- /src/store/actions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './account'; 2 | export * from './network'; 3 | export * from './sharedActions'; 4 | export * from './toasts'; 5 | export * from './cache'; 6 | export * from './config'; 7 | export * from './ui'; 8 | -------------------------------------------------------------------------------- /src/store/actions/network/index.ts: -------------------------------------------------------------------------------- 1 | export * from './networkActions'; 2 | -------------------------------------------------------------------------------- /src/store/actions/network/networkActions.ts: -------------------------------------------------------------------------------- 1 | import { NetworkType } from 'types/network.types'; 2 | import { getStore } from '../../store'; 3 | 4 | export const initializeNetworkConfig = (newNetwork: NetworkType) => 5 | getStore().setState( 6 | ({ network: state }) => { 7 | state.network = { 8 | ...state.network, 9 | ...newNetwork 10 | }; 11 | }, 12 | false, 13 | 'initializeNetworkConfig' 14 | ); 15 | 16 | export { initializeNetwork } from './initializeNetwork'; 17 | -------------------------------------------------------------------------------- /src/store/actions/sharedActions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sharedActions'; 2 | -------------------------------------------------------------------------------- /src/store/actions/sharedActions/sharedActions.ts: -------------------------------------------------------------------------------- 1 | import { Address } from 'lib/sdkCore'; 2 | import { 3 | ProviderTypeEnum, 4 | ProviderType 5 | } from 'providers/types/providerFactory.types'; 6 | import { resetStore } from 'store/middleware/logoutMiddleware'; 7 | import { getStore } from 'store/store'; 8 | 9 | export const logoutAction = () => getStore().setState(resetStore); 10 | export interface LoginActionPayloadType { 11 | address: string; 12 | providerType: T; 13 | } 14 | 15 | export const loginAction = ({ 16 | address, 17 | providerType 18 | }: LoginActionPayloadType) => { 19 | getStore().setState( 20 | ({ account, loginInfo }) => { 21 | account.address = address; 22 | account.publicKey = new Address(address).hex(); 23 | 24 | if (loginInfo) { 25 | loginInfo.providerType = providerType; 26 | } 27 | }, 28 | false, 29 | 'loginAction' 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/store/actions/toasts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './toastsActions'; 2 | -------------------------------------------------------------------------------- /src/store/actions/ui/index.ts: -------------------------------------------------------------------------------- 1 | export * from './uiActions'; 2 | -------------------------------------------------------------------------------- /src/store/actions/ui/uiActions.ts: -------------------------------------------------------------------------------- 1 | import { getStore } from 'store/store'; 2 | 3 | export const setIsSidePanelOpen = (isOpen: boolean) => 4 | getStore().setState( 5 | ({ ui: state }) => { 6 | state.isSidePanelOpen = isOpen; 7 | }, 8 | false, 9 | 'setIsSidePanelOpen' 10 | ); 11 | -------------------------------------------------------------------------------- /src/store/middleware/applyMiddlewares.ts: -------------------------------------------------------------------------------- 1 | import { StoreType } from '../store.types'; 2 | import { logoutMiddleware } from './logoutMiddleware'; 3 | 4 | export const applyMiddlewares = (state: StoreType, _prevState: StoreType) => { 5 | logoutMiddleware(state); 6 | }; 7 | -------------------------------------------------------------------------------- /src/store/middleware/index.ts: -------------------------------------------------------------------------------- 1 | export * from './applyMiddlewares'; 2 | -------------------------------------------------------------------------------- /src/store/selectors/accountSelectors.ts: -------------------------------------------------------------------------------- 1 | import { emptyAccount } from 'store/slices/account/emptyAccount'; 2 | import { StoreType } from 'store/store.types'; 3 | 4 | const privateAccountInfoSelector = ({ account }: StoreType) => account; 5 | 6 | export const accountSelector = ({ account }: StoreType) => 7 | account.address && account.address in account.accounts 8 | ? account.accounts[account.address] 9 | : emptyAccount; 10 | 11 | export const accountInfoSelector = (state: StoreType) => { 12 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 13 | const { accounts, ...info } = privateAccountInfoSelector(state); 14 | const account = accountSelector(state); 15 | 16 | return { ...info, address: account.address, account }; 17 | }; 18 | 19 | export const addressSelector = ({ account: { address } }: StoreType) => address; 20 | 21 | export const websocketEventSelector = ({ 22 | account: { websocketEvent } 23 | }: StoreType) => websocketEvent; 24 | 25 | export const accountNonceSelector = (store: StoreType) => 26 | accountSelector(store)?.nonce || 0; 27 | 28 | export const isLoggedInSelector = (store: StoreType) => { 29 | const address = addressSelector(store); 30 | const account = accountSelector(store); 31 | return Boolean(address && account?.address === address); 32 | }; 33 | 34 | export const ledgerAccountSelector = ({ 35 | account: { ledgerAccount } 36 | }: StoreType) => ledgerAccount; 37 | -------------------------------------------------------------------------------- /src/store/selectors/cacheSelector.ts: -------------------------------------------------------------------------------- 1 | import { StoreType } from 'store/store.types'; 2 | 3 | export const cacheSliceSelector = ({ cache }: StoreType) => cache; 4 | 5 | export const getCachedItemSelector = 6 | (key: string) => 7 | ({ cache }: StoreType): T | undefined => 8 | cache[key] as T; 9 | -------------------------------------------------------------------------------- /src/store/selectors/configSelectors.ts: -------------------------------------------------------------------------------- 1 | import { StoreType } from 'store/store.types'; 2 | 3 | export const configSelector = ({ config }: StoreType) => config; 4 | 5 | export const nativeAuthConfigSelector = ({ config }: StoreType) => 6 | config.nativeAuthConfig; 7 | 8 | export const walletConnectConfigSelector = ({ config }: StoreType) => { 9 | return config.walletConnectConfig; 10 | }; 11 | 12 | export const crossWindowConfigSelector = ({ config }: StoreType) => { 13 | return config.crossWindowConfig; 14 | }; 15 | -------------------------------------------------------------------------------- /src/store/selectors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './accountSelectors'; 2 | export * from './networkSelectors'; 3 | export * from './storeSelector'; 4 | export * from './loginInfoSelectors'; 5 | export * from './configSelectors'; 6 | export * from './cacheSelector'; 7 | -------------------------------------------------------------------------------- /src/store/selectors/loginInfoSelectors.ts: -------------------------------------------------------------------------------- 1 | import { StoreType } from 'store/store.types'; 2 | 3 | export const loginInfoSelector = ({ loginInfo }: StoreType) => loginInfo; 4 | 5 | export const tokenLoginSelector = ({ loginInfo }: StoreType) => 6 | loginInfo.tokenLogin; 7 | 8 | export const walletConnectLoginSelector = ({ loginInfo }: StoreType) => 9 | loginInfo.walletConnectLogin; 10 | 11 | export const providerTypeSelector = ({ loginInfo }: StoreType) => 12 | loginInfo.providerType; 13 | 14 | export const ledgerLoginSelector = ({ loginInfo }: StoreType) => 15 | loginInfo.ledgerLogin; 16 | 17 | export const loginExpiresAtSelector = ({ loginInfo }: StoreType) => 18 | loginInfo.loginExpiresAt; 19 | -------------------------------------------------------------------------------- /src/store/selectors/networkSelectors.ts: -------------------------------------------------------------------------------- 1 | import { StoreType } from 'store/store.types'; 2 | 3 | export const networkConfigSelector = ({ network }: StoreType) => network; 4 | 5 | export const networkSelector = ({ network }: StoreType) => network.network; 6 | 7 | export const chainIdSelector = ({ network: { network } }: StoreType) => 8 | network.chainId; 9 | 10 | export const walletAddressSelector = ({ network: { network } }: StoreType) => 11 | network.walletAddress; 12 | 13 | export const roundDurationSelectorSelector = ({ 14 | network: { network } 15 | }: StoreType) => network.roundDuration; 16 | -------------------------------------------------------------------------------- /src/store/selectors/storeSelector.ts: -------------------------------------------------------------------------------- 1 | import { StoreType } from 'store/store.types'; 2 | 3 | export const stateSelector = (state: StoreType) => state; 4 | -------------------------------------------------------------------------------- /src/store/selectors/toastsSelectors.ts: -------------------------------------------------------------------------------- 1 | import { StoreType } from 'store/store.types'; 2 | 3 | export const toastsSliceSelector = ({ toasts }: StoreType) => toasts; 4 | 5 | export const customToastsSelector = ({ toasts }: StoreType) => 6 | toasts.customToasts; 7 | 8 | export const transactionToastsSelector = ({ toasts }: StoreType) => 9 | toasts.transactionToasts; 10 | -------------------------------------------------------------------------------- /src/store/selectors/uiSelectors.ts: -------------------------------------------------------------------------------- 1 | import { StoreType } from 'store/store.types'; 2 | 3 | export const uiSelector = ({ ui }: StoreType) => ui; 4 | 5 | export const isSidePanelOpenSelector = ({ ui }: StoreType) => 6 | ui.isSidePanelOpen; 7 | -------------------------------------------------------------------------------- /src/store/slices/account/account.types.ts: -------------------------------------------------------------------------------- 1 | import { AccountType } from 'types/account.types'; 2 | import { BatchTransactionsWSResponseType } from 'types/websocket.types'; 3 | 4 | export interface LedgerAccountType { 5 | index: number; 6 | address: string; 7 | hasContractDataEnabled: boolean; 8 | version: string; 9 | } 10 | 11 | export type AccountSliceType = { 12 | address: string; 13 | accounts: { [address: string]: AccountType }; 14 | publicKey: string; 15 | ledgerAccount: LedgerAccountType | null; 16 | walletConnectAccount: string | null; 17 | websocketEvent: { 18 | timestamp: number; 19 | message: string; 20 | } | null; 21 | websocketBatchEvent: { 22 | timestamp: number; 23 | data: BatchTransactionsWSResponseType; 24 | } | null; 25 | }; 26 | -------------------------------------------------------------------------------- /src/store/slices/account/accountSlice.ts: -------------------------------------------------------------------------------- 1 | import { StateCreator } from 'zustand/vanilla'; 2 | import { StoreType, MutatorsIn } from 'store/store.types'; 3 | import { AccountSliceType } from './account.types'; 4 | import { emptyAccount } from './emptyAccount'; 5 | 6 | export const initialState: AccountSliceType = { 7 | address: '', 8 | websocketEvent: null, 9 | websocketBatchEvent: null, 10 | accounts: { '': emptyAccount }, 11 | ledgerAccount: null, 12 | publicKey: '', 13 | walletConnectAccount: null 14 | }; 15 | 16 | function getAccountSlice(): StateCreator< 17 | StoreType, 18 | MutatorsIn, 19 | [], 20 | AccountSliceType 21 | > { 22 | return () => initialState; 23 | } 24 | 25 | export const accountSlice = getAccountSlice(); 26 | -------------------------------------------------------------------------------- /src/store/slices/account/emptyAccount.ts: -------------------------------------------------------------------------------- 1 | import { ELLIPSIS } from 'constants/placeholders.constants'; 2 | import { ZERO } from 'lib/sdkDappUtils'; 3 | import { AccountType } from 'types/account.types'; 4 | 5 | export const emptyAccount: AccountType = { 6 | balance: ELLIPSIS, 7 | address: '', 8 | isGuarded: false, 9 | nonce: 0, 10 | txCount: 0, 11 | scrCount: 0, 12 | claimableRewards: ZERO 13 | }; 14 | -------------------------------------------------------------------------------- /src/store/slices/account/index.ts: -------------------------------------------------------------------------------- 1 | export { accountSlice } from './accountSlice'; 2 | -------------------------------------------------------------------------------- /src/store/slices/cache/cacheSlice.ts: -------------------------------------------------------------------------------- 1 | import { StateCreator } from 'zustand/vanilla'; 2 | import { StoreType, MutatorsIn } from 'store/store.types'; 3 | import { CacheSliceType } from './cacheSlice.types'; 4 | 5 | export const initialState: CacheSliceType = {}; 6 | 7 | function getCacheSlice(): StateCreator< 8 | StoreType, 9 | MutatorsIn, 10 | [], 11 | CacheSliceType 12 | > { 13 | return () => initialState; 14 | } 15 | 16 | export const cacheSlice = getCacheSlice(); 17 | -------------------------------------------------------------------------------- /src/store/slices/cache/cacheSlice.types.ts: -------------------------------------------------------------------------------- 1 | export type CacheSliceType = { 2 | [key: string]: unknown; 3 | }; 4 | -------------------------------------------------------------------------------- /src/store/slices/cache/index.ts: -------------------------------------------------------------------------------- 1 | export { cacheSlice } from './cacheSlice'; 2 | -------------------------------------------------------------------------------- /src/store/slices/config/config.types.ts: -------------------------------------------------------------------------------- 1 | import { WebsocketConnectionStatusEnum } from 'constants/websocket.constants'; 2 | import { CrossWindowConfig } from 'providers/strategies/CrossWindowProviderStrategy/types'; 3 | import { WalletConnectConfig } from 'providers/strategies/WalletConnectProviderStrategy/types'; 4 | import { NativeAuthConfigType } from 'services/nativeAuth/nativeAuth.types'; 5 | 6 | export interface ConfigSliceType { 7 | nativeAuthConfig: NativeAuthConfigType | null; 8 | walletConnectConfig: WalletConnectConfig | null; 9 | crossWindowConfig: CrossWindowConfig | null; 10 | websocketStatus: WebsocketConnectionStatusEnum; 11 | } 12 | -------------------------------------------------------------------------------- /src/store/slices/config/configSlice.ts: -------------------------------------------------------------------------------- 1 | import { StateCreator } from 'zustand/vanilla'; 2 | import { WebsocketConnectionStatusEnum } from 'constants/websocket.constants'; 3 | import { StoreType, MutatorsIn } from 'store/store.types'; 4 | import { ConfigSliceType } from './config.types'; 5 | 6 | // Do not export initial state for the config slice. 7 | // This will be permanently defined by the dApp at dApp initialization. 8 | // The config should be changed by using the `setNativeAuthConfig` action in some specific cases. 9 | // Preferably, the config should be set at the dApp initialization and not changed during the dApp lifecycle. (e.g. when the user logs in/log out) 10 | const initialState: ConfigSliceType = { 11 | nativeAuthConfig: null, 12 | walletConnectConfig: null, 13 | crossWindowConfig: null, 14 | websocketStatus: WebsocketConnectionStatusEnum.NOT_INITIALIZED 15 | }; 16 | 17 | function getConfigSlice(): StateCreator< 18 | StoreType, 19 | MutatorsIn, 20 | [], 21 | ConfigSliceType 22 | > { 23 | return () => initialState; 24 | } 25 | 26 | export const configSlice = getConfigSlice(); 27 | -------------------------------------------------------------------------------- /src/store/slices/config/index.ts: -------------------------------------------------------------------------------- 1 | export { configSlice } from './configSlice'; 2 | -------------------------------------------------------------------------------- /src/store/slices/index.ts: -------------------------------------------------------------------------------- 1 | export * from './account'; 2 | export * from './network'; 3 | export * from './loginInfo'; 4 | export * from './config'; 5 | export * from './toast'; 6 | export * from './cache'; 7 | export * from './ui'; 8 | -------------------------------------------------------------------------------- /src/store/slices/loginInfo/index.ts: -------------------------------------------------------------------------------- 1 | export { loginInfoSlice } from './loginInfoSlice'; 2 | -------------------------------------------------------------------------------- /src/store/slices/loginInfo/loginInfo.types.ts: -------------------------------------------------------------------------------- 1 | import { ProviderType } from 'providers/types/providerFactory.types'; 2 | import { TokenLoginType } from 'types/login.types'; 3 | 4 | export interface WalletConnectLoginType { 5 | loginType: string; 6 | callbackRoute: string; 7 | logoutRoute: string; 8 | } 9 | 10 | export interface LedgerLoginType { 11 | index: number; 12 | loginType: string; 13 | } 14 | 15 | export interface LoginInfoType { 16 | data: any; 17 | expires: number; 18 | } 19 | 20 | export interface LoginInfoSliceType { 21 | providerType: T | null; 22 | walletConnectLogin: WalletConnectLoginType | null; 23 | ledgerLogin: LedgerLoginType | null; 24 | tokenLogin: TokenLoginType | null; 25 | walletLogin: LoginInfoType | null; 26 | extensionLogin: LoginInfoType | null; 27 | operaLogin: LoginInfoType | null; 28 | crossWindowLogin: LoginInfoType | null; 29 | logoutRoute?: string; 30 | isWalletConnectV2Initialized?: boolean; 31 | loginExpiresAt: number | null; 32 | } 33 | -------------------------------------------------------------------------------- /src/store/slices/loginInfo/loginInfoSlice.ts: -------------------------------------------------------------------------------- 1 | import { StateCreator } from 'zustand/vanilla'; 2 | import { StoreType, MutatorsIn } from 'store/store.types'; 3 | import { LoginInfoSliceType } from './loginInfo.types'; 4 | 5 | export const initialState: LoginInfoSliceType = { 6 | providerType: null, 7 | walletConnectLogin: null, 8 | ledgerLogin: null, 9 | tokenLogin: null, 10 | walletLogin: null, 11 | extensionLogin: null, 12 | operaLogin: null, 13 | crossWindowLogin: null, 14 | loginExpiresAt: null 15 | }; 16 | 17 | function getTokenInfoSlice(): StateCreator< 18 | StoreType, 19 | MutatorsIn, 20 | [], 21 | LoginInfoSliceType 22 | > { 23 | return () => initialState; 24 | } 25 | 26 | export const loginInfoSlice = getTokenInfoSlice(); 27 | -------------------------------------------------------------------------------- /src/store/slices/network/emptyNetwork.ts: -------------------------------------------------------------------------------- 1 | import { NetworkType } from 'types/network.types'; 2 | 3 | export const emptyNetwork: NetworkType = { 4 | id: 'not-configured', 5 | chainId: '', 6 | name: 'NOT CONFIGURED', 7 | egldLabel: '', 8 | decimals: '18', 9 | digits: '4', 10 | gasPerDataByte: '1500', 11 | walletAddress: '', 12 | apiAddress: '', 13 | explorerAddress: '', 14 | apiTimeout: '4000', 15 | roundDuration: 60000 16 | }; 17 | -------------------------------------------------------------------------------- /src/store/slices/network/index.ts: -------------------------------------------------------------------------------- 1 | export { networkSlice } from './networkSlice'; 2 | -------------------------------------------------------------------------------- /src/store/slices/network/network.types.ts: -------------------------------------------------------------------------------- 1 | import { NetworkType } from 'types/network.types'; 2 | 3 | export interface NetworkSliceType { 4 | network: NetworkType; 5 | customWalletAddress: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/store/slices/network/networkSlice.ts: -------------------------------------------------------------------------------- 1 | import { StateCreator } from 'zustand/vanilla'; 2 | import { StoreType, MutatorsIn } from 'store/store.types'; 3 | import { emptyNetwork } from './emptyNetwork'; 4 | import { NetworkSliceType } from './networkSlice.types'; 5 | 6 | const initialState: NetworkSliceType = { 7 | network: emptyNetwork 8 | }; 9 | 10 | function getNetworkSlice(): StateCreator< 11 | StoreType, 12 | MutatorsIn, 13 | [], 14 | NetworkSliceType 15 | > { 16 | return () => initialState; 17 | } 18 | 19 | export const networkSlice = getNetworkSlice(); 20 | -------------------------------------------------------------------------------- /src/store/slices/network/networkSlice.types.ts: -------------------------------------------------------------------------------- 1 | import { NetworkType } from 'types/network.types'; 2 | 3 | export interface NetworkSliceType { 4 | network: NetworkType; 5 | } 6 | -------------------------------------------------------------------------------- /src/store/slices/toast/index.ts: -------------------------------------------------------------------------------- 1 | export { toastSlice } from './toastSlice'; 2 | -------------------------------------------------------------------------------- /src/store/slices/toast/toastSlice.ts: -------------------------------------------------------------------------------- 1 | import { StateCreator } from 'zustand/vanilla'; 2 | import { StoreType, MutatorsIn } from 'store/store.types'; 3 | import { ToastsSliceType } from './toastSlice.types'; 4 | 5 | export const initialState: ToastsSliceType = { 6 | customToasts: [], 7 | transactionToasts: [] 8 | }; 9 | 10 | function getToastSlice(): StateCreator< 11 | StoreType, 12 | MutatorsIn, 13 | [], 14 | ToastsSliceType 15 | > { 16 | return () => initialState; 17 | } 18 | 19 | export const toastSlice = getToastSlice(); 20 | -------------------------------------------------------------------------------- /src/store/slices/transactions/index.ts: -------------------------------------------------------------------------------- 1 | export { transactionsSlice } from './transactionsSlice'; 2 | -------------------------------------------------------------------------------- /src/store/slices/transactions/transactionsSlice.ts: -------------------------------------------------------------------------------- 1 | import { StateCreator } from 'zustand/vanilla'; 2 | import { StoreType, MutatorsIn } from 'store/store.types'; 3 | import { TransactionsSliceType } from './transactionsSlice.types'; 4 | 5 | export const initialState: TransactionsSliceType = {}; 6 | 7 | function getTransactionsSlice(): StateCreator< 8 | StoreType, 9 | MutatorsIn, 10 | [], 11 | TransactionsSliceType 12 | > { 13 | return () => initialState; 14 | } 15 | 16 | export const transactionsSlice = getTransactionsSlice(); 17 | -------------------------------------------------------------------------------- /src/store/slices/transactions/transactionsSlice.types.ts: -------------------------------------------------------------------------------- 1 | import { SessionTransactionType } from 'types/transactions.types'; 2 | 3 | export type TransactionsSliceType = { 4 | [sessionId: string]: SessionTransactionType; 5 | }; 6 | -------------------------------------------------------------------------------- /src/store/slices/ui/index.ts: -------------------------------------------------------------------------------- 1 | export { uiSlice } from './uiSlice'; 2 | -------------------------------------------------------------------------------- /src/store/slices/ui/ui.types.ts: -------------------------------------------------------------------------------- 1 | export interface UiSliceType { 2 | isSidePanelOpen: boolean; 3 | } 4 | -------------------------------------------------------------------------------- /src/store/slices/ui/uiSlice.ts: -------------------------------------------------------------------------------- 1 | import { StateCreator } from 'zustand/vanilla'; 2 | import { StoreType, MutatorsIn } from 'store/store.types'; 3 | import { UiSliceType } from './ui.types'; 4 | 5 | const initialState: UiSliceType = { 6 | isSidePanelOpen: false 7 | }; 8 | 9 | function getUiSlice(): StateCreator { 10 | return () => initialState; 11 | } 12 | 13 | export const uiSlice = getUiSlice(); 14 | -------------------------------------------------------------------------------- /src/store/storage/inMemoryStorage.ts: -------------------------------------------------------------------------------- 1 | interface InMemoryStorageType { 2 | [key: string]: string; 3 | } 4 | 5 | export class InMemoryStorage { 6 | private storage: InMemoryStorageType = {}; 7 | 8 | setItem(key: string, value: string) { 9 | this.storage[key] = value; 10 | } 11 | 12 | getItem(key: string): string | null { 13 | return this.storage.hasOwnProperty(key) ? this.storage[key] : null; 14 | } 15 | 16 | removeItem(key: string) { 17 | delete this.storage[key]; 18 | } 19 | 20 | clear() { 21 | this.storage = {} as Storage; 22 | } 23 | 24 | get length() { 25 | return Object.keys(this.storage).length; 26 | } 27 | 28 | key(index: number): string | null { 29 | const keys = Object.keys(this.storage); 30 | return keys[index] || null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/store/storage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './storageCallback'; 2 | export * from './inMemoryStorage'; 3 | -------------------------------------------------------------------------------- /src/store/storage/storageCallback.ts: -------------------------------------------------------------------------------- 1 | import { StateStorage } from 'zustand/middleware'; 2 | import { safeWindow } from 'constants/window.constants'; 3 | 4 | export type StorageCallback = () => StateStorage; 5 | 6 | export const defaultStorageCallback: StorageCallback = () => 7 | safeWindow.localStorage; 8 | -------------------------------------------------------------------------------- /src/store/store.types.ts: -------------------------------------------------------------------------------- 1 | import { AccountSliceType } from './slices/account/account.types'; 2 | import { CacheSliceType } from './slices/cache/cacheSlice.types'; 3 | import { ConfigSliceType } from './slices/config/config.types'; 4 | import { LoginInfoSliceType } from './slices/loginInfo/loginInfo.types'; 5 | import { NetworkSliceType } from './slices/network/networkSlice.types'; 6 | import { ToastsSliceType } from './slices/toast/toastSlice.types'; 7 | import { TransactionsSliceType } from './slices/transactions/transactionsSlice.types'; 8 | import { UiSliceType } from './slices/ui/ui.types'; 9 | 10 | export type StoreType = { 11 | network: NetworkSliceType; 12 | account: AccountSliceType; 13 | loginInfo: LoginInfoSliceType; 14 | config: ConfigSliceType; 15 | toasts: ToastsSliceType; 16 | transactions: TransactionsSliceType; 17 | cache: CacheSliceType; 18 | ui: UiSliceType; 19 | }; 20 | 21 | export type MutatorsIn = [ 22 | ['zustand/devtools', never], 23 | ['zustand/persist', unknown], 24 | ['zustand/immer', never] 25 | ]; 26 | 27 | export type MutatorsOut = [ 28 | ['zustand/devtools', never], 29 | ['zustand/persist', StoreType], 30 | ['zustand/immer', never] 31 | ]; 32 | -------------------------------------------------------------------------------- /src/types/account.types.ts: -------------------------------------------------------------------------------- 1 | export interface AssetType { 2 | name: string; 3 | description: string; 4 | tags: string[]; 5 | iconPng?: string; 6 | iconSvg?: string; 7 | } 8 | 9 | export interface AccountType { 10 | address: string; 11 | balance: string; 12 | nonce: number; 13 | txCount: number; 14 | scrCount: number; 15 | claimableRewards: string; 16 | code?: string; 17 | username?: string; 18 | shard?: number; 19 | ownerAddress?: string; 20 | developerReward?: string; 21 | deployedAt?: number; 22 | scamInfo?: ScamInfoType; 23 | isUpgradeable?: boolean; 24 | isReadable?: boolean; 25 | isPayable?: boolean; 26 | isPayableBySmartContract?: boolean; 27 | assets?: AssetType; 28 | isGuarded: boolean; 29 | activeGuardianActivationEpoch?: number; 30 | activeGuardianAddress?: string; 31 | activeGuardianServiceUid?: string; 32 | pendingGuardianActivationEpoch?: number; 33 | pendingGuardianAddress?: string; 34 | pendingGuardianServiceUid?: string; 35 | } 36 | 37 | export interface ScamInfoType { 38 | type: string; 39 | info: string; 40 | } 41 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './enums.types'; 2 | export * from './network.types'; 3 | export * from './provider.types'; 4 | export * from './subscriptions.type'; 5 | export * from './websocket.types'; 6 | export * from './suspiciousLink.types'; 7 | -------------------------------------------------------------------------------- /src/types/login.types.ts: -------------------------------------------------------------------------------- 1 | import { NativeAuthConfigType } from 'services/nativeAuth/nativeAuth.types'; 2 | 3 | export interface OnProviderLoginType { 4 | token?: string; 5 | /** 6 | * If set to `true`, will fallback on default configuration 7 | */ 8 | nativeAuth?: NativeAuthConfigType | boolean; 9 | } 10 | 11 | export interface TokenLoginType { 12 | loginToken: string; 13 | signature?: string; 14 | nativeAuthToken?: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/types/manager.types.ts: -------------------------------------------------------------------------------- 1 | export interface IEventBus { 2 | subscribe(event: string, callback: Function): void; 3 | publish(event: string, data: T): void; 4 | unsubscribe(event: string, callback: Function): void; 5 | } 6 | -------------------------------------------------------------------------------- /src/types/provider.types.ts: -------------------------------------------------------------------------------- 1 | export enum ProviderErrorsEnum { 2 | notInitialized = 'Provider is not initialized.', 3 | signTransactionsNotInitialized = 'Sign transactions method is not initialized.', 4 | invalidProviderType = 'Invalid provider type.', 5 | eventBusError = 'eventBus is not initialized' 6 | } 7 | -------------------------------------------------------------------------------- /src/types/subscriptions.type.ts: -------------------------------------------------------------------------------- 1 | export enum SubscriptionsEnum { 2 | websocketStatusChanged = 'websocketStatusChanged', 3 | websocketEventReceived = 'websocketEventReceived', 4 | websocketCleanup = 'websocketCleanup' 5 | } 6 | -------------------------------------------------------------------------------- /src/types/suspiciousLink.types.ts: -------------------------------------------------------------------------------- 1 | import { ScamInfoType } from './account.types'; 2 | 3 | export interface SuspiciousLinkType { 4 | isSuspicious: boolean; 5 | message: string; 6 | textWithLinks: string; 7 | } 8 | 9 | export interface SuspiciousLinkPropsType { 10 | message: string; 11 | messagePrefix?: string; 12 | scamInfo?: ScamInfoType; 13 | isNsfw?: boolean; 14 | verified?: boolean; 15 | } 16 | 17 | export interface TextWithLinksType { 18 | textWithLinks: string; 19 | hasLinks: boolean; 20 | } 21 | -------------------------------------------------------------------------------- /src/types/websocket.types.ts: -------------------------------------------------------------------------------- 1 | export type BatchTransactionsWSResponseType = { 2 | batchId: string; 3 | txHashes: string[]; 4 | }; 5 | -------------------------------------------------------------------------------- /src/utils/account/fetchAccount.ts: -------------------------------------------------------------------------------- 1 | import { getAccountFromApi } from 'apiCalls/account/getAccountFromApi'; 2 | 3 | export const fetchAccount = (props: { address?: string; baseURL: string }) => 4 | getAccountFromApi(props); 5 | -------------------------------------------------------------------------------- /src/utils/account/index.ts: -------------------------------------------------------------------------------- 1 | export * from './fetchAccount'; 2 | export * from './refreshAccount'; 3 | export * from './trimUsernameDomain'; 4 | -------------------------------------------------------------------------------- /src/utils/account/trimUsernameDomain.ts: -------------------------------------------------------------------------------- 1 | export const trimUsernameDomain = (username?: string) => { 2 | if (!username) { 3 | return; 4 | } 5 | 6 | const elrondSuffixExists = username.lastIndexOf('.elrond') > 0; 7 | const trimmedPartBeforeLastDot = elrondSuffixExists 8 | ? username.substring(0, username.lastIndexOf('.')) 9 | : username; 10 | 11 | return trimmedPartBeforeLastDot; 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/asyncActions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sleep'; 2 | -------------------------------------------------------------------------------- /src/utils/asyncActions/sleep.ts: -------------------------------------------------------------------------------- 1 | export async function sleep(time: number) { 2 | return await new Promise((resolve) => { 3 | //timeouts are broken in jest, if this is a test env 4 | //exit the sleep state immediately 5 | if (process.env.NODE_ENV === 'test') { 6 | resolve(); 7 | } 8 | setTimeout(() => resolve(), time); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/createUIElement.ts: -------------------------------------------------------------------------------- 1 | import { IEventBus } from '@multiversx/sdk-dapp-ui/dist/loader'; 2 | import { safeWindow } from 'constants/index'; 3 | import { UITagsEnum } from 'constants/UITags.enum'; 4 | import { defineCustomElements } from 'lib/sdkDappUi'; 5 | 6 | export interface CreateEventBusUIElementType extends HTMLElement { 7 | getEventBus: () => Promise; 8 | } 9 | 10 | export const createUIElement = async ({ 11 | name, 12 | anchor 13 | }: { 14 | name: UITagsEnum; 15 | anchor?: HTMLElement; 16 | }) => { 17 | await defineCustomElements(safeWindow); 18 | 19 | if (!safeWindow.document) { 20 | return {} as T; 21 | } 22 | 23 | const element = safeWindow.document.createElement(name); 24 | const rootElement = anchor || safeWindow.document.body; 25 | rootElement.appendChild(element); 26 | await customElements.whenDefined(name); 27 | 28 | return element as T; 29 | }; 30 | -------------------------------------------------------------------------------- /src/utils/dateTime/getUnixTimestamp.ts: -------------------------------------------------------------------------------- 1 | export const getUnixTimestamp = () => { 2 | return Date.now() / 1000; 3 | }; 4 | -------------------------------------------------------------------------------- /src/utils/dateTime/getUnixTimestampWithAddedMilliseconds.ts: -------------------------------------------------------------------------------- 1 | export const getUnixTimestampWithAddedMilliseconds = ( 2 | addedMilliseconds: number 3 | ) => { 4 | return ( 5 | new Date().setMilliseconds( 6 | new Date().getMilliseconds() + addedMilliseconds 7 | ) / 1000 8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/dateTime/getUnixTimestampWithAddedSeconds.ts: -------------------------------------------------------------------------------- 1 | export const getUnixTimestampWithAddedSeconds = (addedSeconds: number) => { 2 | return new Date().setSeconds(new Date().getSeconds() + addedSeconds); 3 | }; 4 | -------------------------------------------------------------------------------- /src/utils/dateTime/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getUnixTimestamp'; 2 | export * from './getUnixTimestampWithAddedMilliseconds'; 3 | export * from './getUnixTimestampWithAddedSeconds'; 4 | -------------------------------------------------------------------------------- /src/utils/decoders/decodePart.ts: -------------------------------------------------------------------------------- 1 | import { isUtf8 } from './isUtf8'; 2 | 3 | export function decodePart(part: string) { 4 | let decodedPart = part; 5 | 6 | try { 7 | const hexPart = Buffer.from(part, 'hex').toString(); 8 | 9 | if (isUtf8(hexPart) && hexPart.length > 1) { 10 | decodedPart = hexPart; 11 | } 12 | } catch (_error) { 13 | /* empty */ 14 | } 15 | 16 | return decodedPart; 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/decoders/index.ts: -------------------------------------------------------------------------------- 1 | export * from './base64Utils'; 2 | export * from './decodePart'; 3 | export * from './isAscii'; 4 | export * from './isUtf8'; 5 | export * from './stringContainsNumbers'; 6 | -------------------------------------------------------------------------------- /src/utils/decoders/isAscii.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-control-regex 2 | export const isAscii = (str: string) => !/[^\x00-\x7F]/gm.test(str); 3 | -------------------------------------------------------------------------------- /src/utils/decoders/isUtf8.ts: -------------------------------------------------------------------------------- 1 | export function isUtf8(str: string) { 2 | for (let i = 0; i < str.length; i++) { 3 | if (str.charCodeAt(i) > 127) { 4 | return false; 5 | } 6 | } 7 | return true; 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/decoders/stringContainsNumbers.ts: -------------------------------------------------------------------------------- 1 | export const stringContainsNumbers = (str: string) => /\d/.test(str); 2 | -------------------------------------------------------------------------------- /src/utils/decoders/tests/decodePart.test.ts: -------------------------------------------------------------------------------- 1 | import { decodePart } from '../decodePart'; 2 | 3 | describe('decodePart', () => { 4 | it('should decode hex to utf-8 string', async () => { 5 | const result = decodePart('6f6b'); 6 | expect(result).toBe('ok'); 7 | }); 8 | 9 | it('should return input value if not utf-8 string and not a number', async () => { 10 | const result = decodePart('oÉD'); 11 | expect(result).toBe('oÉD'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/utils/decoders/tests/isAscii.test.ts: -------------------------------------------------------------------------------- 1 | import { isAscii } from '../isAscii'; 2 | 3 | describe('isAscii', () => { 4 | it('should return false for strings with unknown characters', async () => { 5 | const result = isAscii( 6 | '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~��������������������������������������������������������������������������������������������������������������������������������' 7 | ); 8 | expect(result).toStrictEqual(false); 9 | }); 10 | 11 | it('should return false for strings with emojis', async () => { 12 | const result = isAscii('This ❌ h🅰s some 😱🙀 emojis inside'); 13 | expect(result).toStrictEqual(false); 14 | }); 15 | 16 | it('should return true for strings with symbols, letters, and numbers', async () => { 17 | const result = isAscii('ESDTTransfer@a129asnas98d@a9s8h98h9'); 18 | expect(result).toStrictEqual(true); 19 | }); 20 | 21 | it('should return true for simple strings with letters', async () => { 22 | const result = isAscii('Some example'); 23 | expect(result).toStrictEqual(true); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/utils/decoders/tests/isUtf8.test.ts: -------------------------------------------------------------------------------- 1 | import { isUtf8 } from '../isUtf8'; 2 | 3 | describe('isUtf8', () => { 4 | it('should return valid UTF-8', async () => { 5 | const result = isUtf8('dGVzdCB0cmFuc2FjdGlvbiDwn5mA'); 6 | expect(result).toStrictEqual(true); 7 | }); 8 | 9 | it('should also return valid UTF-8', async () => { 10 | const result = isUtf8('ZEdWemRDQjBjbUZ1YzJGamRHbHZiaUR3bjVtQQ=='); 11 | expect(result).toStrictEqual(true); 12 | }); 13 | 14 | it('should return invalid UTF-8', async () => { 15 | const result = isUtf8('test transaction 🙀'); 16 | expect(result).toStrictEqual(false); 17 | }); 18 | 19 | it('should return also invalid UTF-8', async () => { 20 | const result = isUtf8('��-'); 21 | expect(result).toStrictEqual(false); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './account'; 2 | export * from './asyncActions'; 3 | export * from './dateTime'; 4 | export * from './decoders'; 5 | export * from './network'; 6 | export * from './retryMultipleTimes'; 7 | export * from './transactions'; 8 | export * from './validation'; 9 | export * from './window'; 10 | -------------------------------------------------------------------------------- /src/utils/network/index.ts: -------------------------------------------------------------------------------- 1 | export * from './setAxiosInterceptors'; 2 | -------------------------------------------------------------------------------- /src/utils/network/setAxiosInterceptors.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios'; 2 | 3 | interface InterceptAxiosCallsConfig { 4 | authenticatedDomains: string[]; 5 | bearerToken?: string; 6 | } 7 | 8 | export const setAxiosInterceptors = ({ 9 | authenticatedDomains, 10 | bearerToken 11 | }: InterceptAxiosCallsConfig): void => { 12 | axios.interceptors.request.clear(); 13 | axios.interceptors.response.clear(); 14 | 15 | axios.interceptors.response.use( 16 | (response) => response, 17 | (error: AxiosError) => { 18 | let url = error.config?.url; 19 | if (error.config?.params) { 20 | const queryString = new URLSearchParams(error.config.params); 21 | url += `?${queryString.toString()}`; 22 | } 23 | console.error('Axios error for: ', url); 24 | return Promise.reject(error); 25 | } 26 | ); 27 | 28 | axios.interceptors.request.use( 29 | async (config: InternalAxiosRequestConfig) => { 30 | if ( 31 | authenticatedDomains.includes(String(config?.baseURL)) && 32 | bearerToken 33 | ) { 34 | config.headers.Authorization = `Bearer ${bearerToken}`; 35 | } 36 | return config; 37 | }, 38 | (error) => Promise.reject(error) 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /src/utils/operations/capitalize.ts: -------------------------------------------------------------------------------- 1 | export const capitalize = (str = ''): string => 2 | `${str.charAt(0).toUpperCase()}${str.slice(1)}`; 3 | -------------------------------------------------------------------------------- /src/utils/operations/getUsdValue.ts: -------------------------------------------------------------------------------- 1 | export const getUsdValue = ({ 2 | amount, 3 | usd, 4 | decimals = 2, 5 | addEqualSign 6 | }: { 7 | amount: string; 8 | usd: number; 9 | decimals?: number; 10 | addEqualSign?: boolean; 11 | }) => { 12 | let sum = (parseFloat(amount) * usd).toFixed(decimals); 13 | if (isNaN(Number(sum))) { 14 | sum = '0'; 15 | } 16 | 17 | const formattedValue = parseFloat(sum).toLocaleString('en', { 18 | maximumFractionDigits: decimals, 19 | minimumFractionDigits: decimals 20 | }); 21 | const equalSign = parseFloat(amount) > 0 ? '≈' : '='; 22 | const equalSignPrefix = addEqualSign ? `${equalSign} ` : ''; 23 | return `${equalSignPrefix}$${formattedValue}`; 24 | }; 25 | -------------------------------------------------------------------------------- /src/utils/operations/pluralize.ts: -------------------------------------------------------------------------------- 1 | export const pluralize = (str: string, itemCount: number): string => 2 | itemCount === 1 ? str : `${str}s`; 3 | -------------------------------------------------------------------------------- /src/utils/operations/tests/capitalize.test.ts: -------------------------------------------------------------------------------- 1 | import { capitalize } from '../capitalize'; 2 | 3 | describe('capitalize tests', () => { 4 | const str = 'multiversX'; 5 | 6 | it('capitalizes the word correctly', () => { 7 | expect(capitalize(str)).toStrictEqual('MultiversX'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/utils/operations/tests/getUsdValue.test.ts: -------------------------------------------------------------------------------- 1 | import { getUsdValue } from '../getUsdValue'; 2 | 3 | describe('getUsdValue tests', () => { 4 | it('formats amount', () => { 5 | expect( 6 | getUsdValue({ 7 | amount: '2', 8 | usd: 40, 9 | decimals: 4, 10 | addEqualSign: true 11 | }) 12 | ).toBe('≈ $80.0000'); 13 | }); 14 | it('shows = for 0', () => { 15 | expect( 16 | getUsdValue({ 17 | amount: '0', 18 | usd: 40, 19 | addEqualSign: true 20 | }) 21 | ).toBe('= $0.00'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/utils/operations/tests/pluralize.spec.ts: -------------------------------------------------------------------------------- 1 | import { pluralize } from '../pluralize'; 2 | 3 | describe('pluralize tests', () => { 4 | const str = 'item'; 5 | 6 | it('pluralizes correctly when no items are present', () => { 7 | const count = 0; 8 | expect(pluralize(str, count)).toStrictEqual('items'); 9 | }); 10 | 11 | it('pluralizes correctly when 1 item is present', () => { 12 | const count = 1; 13 | expect(pluralize(str, count)).toStrictEqual('item'); 14 | }); 15 | 16 | it('pluralizes correctly when more items are present', () => { 17 | const count = 2; 18 | expect(pluralize(str, count)).toStrictEqual('items'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/utils/operations/tests/timeRemaining.test.ts: -------------------------------------------------------------------------------- 1 | import { timeRemaining } from '../timeRemaining'; 2 | 3 | describe('timeRemaining tests - short time format', () => { 4 | const entries: [number, string][] = [ 5 | [1076, '17 min'], 6 | [3976, '1 hr'], 7 | [5286, '1 hr'] 8 | ]; 9 | 10 | for (let i = 0; i < entries.length; i++) { 11 | const [input, output] = entries[i]; 12 | test(`parse ${input} -> ${output}`, () => { 13 | const result = timeRemaining(input); 14 | expect(result).toStrictEqual(output); 15 | }); 16 | } 17 | }); 18 | 19 | describe('timeRemaining tests - long time format', () => { 20 | const entries: [number, string][] = [ 21 | [1076, '17 min 56 sec'], 22 | [3976, '1 hr 6 min'], 23 | [5286, '1 hr 28 min'] 24 | ]; 25 | 26 | for (let i = 0; i < entries.length; i++) { 27 | const [input, output] = entries[i]; 28 | test(`parse ${input} -> ${output}`, () => { 29 | const result = timeRemaining(input, false); 30 | expect(result).toStrictEqual(output); 31 | }); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /src/utils/operations/trimAmountDecimals.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | 3 | import { ZERO } from 'lib/sdkDappUtils'; 4 | 5 | interface TrimPriceAmountType { 6 | amount: string; 7 | minimumPositiveDecimals: number; 8 | } 9 | 10 | export const trimAmountDecimals = ({ 11 | amount, 12 | minimumPositiveDecimals 13 | }: TrimPriceAmountType): string => { 14 | const [mainPrice, decimalPrice] = amount.split('.'); 15 | 16 | const decimalPriceArray = decimalPrice ? decimalPrice.split('') : []; 17 | const trimmedDecimals = decimalPriceArray.reduce((total, decimal, index) => { 18 | const decimalAboveZero = new BigNumber(total).isGreaterThan(ZERO); 19 | const minimumDecimalsReached = index > minimumPositiveDecimals - 1; 20 | 21 | const shouldReturnTotal = 22 | decimalAboveZero && minimumDecimalsReached && minimumPositiveDecimals > 0; 23 | 24 | if (shouldReturnTotal) { 25 | return total; 26 | } 27 | 28 | return total.concat(decimal); 29 | }, ''); 30 | 31 | if (!trimmedDecimals) { 32 | return mainPrice; 33 | } 34 | 35 | return `${mainPrice}.${trimmedDecimals}`; 36 | }; 37 | -------------------------------------------------------------------------------- /src/utils/retryMultipleTimes.ts: -------------------------------------------------------------------------------- 1 | import { sleep } from './asyncActions'; 2 | 3 | interface Options { 4 | retries: number; 5 | delay?: number; 6 | } 7 | 8 | const executeAsyncCall = async ( 9 | cb: (..._args: any[]) => any, 10 | options: Options, 11 | args: any[], 12 | retries = 0 13 | ): Promise => { 14 | try { 15 | return await cb(...args); 16 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 17 | } catch (error) { 18 | if (retries < options.retries) { 19 | if (options?.delay != null) { 20 | await sleep(options.delay); 21 | } 22 | 23 | return await executeAsyncCall(cb, options, args, retries + 1); 24 | } 25 | 26 | return null; 27 | } 28 | }; 29 | 30 | export const retryMultipleTimes = 31 | ( 32 | cb: (..._args: any[]) => any, 33 | options: Options = { retries: 5, delay: 500 } 34 | ) => 35 | async (...args: any[]) => { 36 | return await executeAsyncCall(cb, options, args); 37 | }; 38 | -------------------------------------------------------------------------------- /src/utils/transactions/getActiveTransactionsStatus.ts: -------------------------------------------------------------------------------- 1 | import { 2 | failedTransactionsSelector, 3 | pendingTransactionsSelector, 4 | successfulTransactionsSelector, 5 | timedOutTransactionsSelector 6 | } from 'store/selectors/transactionsSelector'; 7 | import { getState } from 'store/store'; 8 | 9 | export interface GetActiveTransactionsStatusReturnType { 10 | timedOut: boolean; 11 | fail: boolean; 12 | success: boolean; 13 | pending: boolean; 14 | } 15 | 16 | export function getActiveTransactionsStatus(): GetActiveTransactionsStatusReturnType { 17 | const state = getState(); 18 | const timedOutTransactions = timedOutTransactionsSelector(state); 19 | const failedTransactions = failedTransactionsSelector(state); 20 | const successfulTransactions = successfulTransactionsSelector(state); 21 | const pendingTransactions = pendingTransactionsSelector(state); 22 | const pending = Object.keys(pendingTransactions)?.length > 0; 23 | const timedOut = !pending && Object.keys(timedOutTransactions)?.length > 0; 24 | 25 | const fail = 26 | !pending && !timedOut && Object.keys(failedTransactions)?.length > 0; 27 | 28 | const success = 29 | !pending && 30 | !timedOut && 31 | !fail && 32 | Object.keys(successfulTransactions).length > 0; 33 | 34 | return { 35 | pending, 36 | timedOut, 37 | fail, 38 | success 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/transactions/getExplorerLink.ts: -------------------------------------------------------------------------------- 1 | let errorMessageDisplayed = false; 2 | 3 | function logError(error: string) { 4 | if (!errorMessageDisplayed) { 5 | console.error(error); 6 | errorMessageDisplayed = true; 7 | } 8 | } 9 | 10 | export function getExplorerLink({ 11 | explorerAddress, 12 | to 13 | }: { 14 | explorerAddress: string; 15 | to: string; 16 | }) { 17 | try { 18 | // if a valid url is sent, return the original url 19 | const url = new URL(to); 20 | return url.href; 21 | } catch { 22 | if (!to.startsWith('/')) { 23 | logError(`Link not prepended by / : ${to}`); 24 | to = `/${to}`; 25 | } 26 | return explorerAddress ? `${explorerAddress}${to}` : to; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/transactions/getHumanReadableTimeFormat.ts: -------------------------------------------------------------------------------- 1 | export interface GetHumanReadableTimeFormatType { 2 | value: number; 3 | noSeconds?: boolean; 4 | utc?: boolean; 5 | meridiem?: boolean; 6 | } 7 | 8 | /** 9 | * @param value - UNIX timestamp 10 | * */ 11 | export function getHumanReadableTimeFormat({ 12 | value, 13 | noSeconds, 14 | utc, 15 | meridiem = true 16 | }: GetHumanReadableTimeFormatType) { 17 | const utcDate = new Date(value * 1000); 18 | const [, AmPm] = utcDate 19 | .toLocaleString('en-US', { hour: 'numeric', hour12: meridiem }) 20 | .split(' '); 21 | const formatted = utcDate.toUTCString(); 22 | const [, date] = formatted.split(','); 23 | const [day, month, year, time] = date.trim().split(' '); 24 | const [hours, minutes, sec] = time.split(':'); 25 | const seconds = `:${sec}`; 26 | const timeFormatted = `${hours}:${minutes}${noSeconds ? '' : seconds}`; 27 | const utcSuffix = utc ? 'UTC' : ''; 28 | const meridiemSuffix = meridiem ? AmPm : ''; 29 | const suffix = `${meridiemSuffix} ${utcSuffix}`.trim(); 30 | 31 | return `${month} ${day}, ${year} ${timeFormatted} ${suffix}`.trim(); 32 | } 33 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/getTransactionValue/constants.ts: -------------------------------------------------------------------------------- 1 | import { TransactionActionsEnum } from 'types/serverTransactions.types'; 2 | 3 | /** 4 | * If `action.name` or `function` is in `ACTIONS_WITH_MANDATORY_OPERATIONS[]`, transaction value will be computed based `operations` field 5 | */ 6 | export const ACTIONS_WITH_MANDATORY_OPERATIONS = [ 7 | TransactionActionsEnum.reDelegateRewards, 8 | TransactionActionsEnum.claimRewards, 9 | TransactionActionsEnum.unBond 10 | ]; 11 | 12 | /** 13 | * If `action.name` is in `ACTIONS_WITH_EGLD_VALUE[]`, transaction value will be returned directly 14 | */ 15 | export const ACTIONS_WITH_EGLD_VALUE = [ 16 | TransactionActionsEnum.wrapEgld, 17 | TransactionActionsEnum.unwrapEgld 18 | ]; 19 | 20 | /** 21 | * If `action.name` is in `ACTIONS_WITH_VALUE_IN_DATA_FIELD[]`, transaction value will be computed based `data` field 22 | */ 23 | export const ACTIONS_WITH_VALUE_IN_DATA_FIELD = [ 24 | TransactionActionsEnum.unStake 25 | ]; 26 | 27 | /** 28 | * If `action.name` is in `ACTIONS_WITH_VALUE_IN_ACTION_FIELD[]`, transaction value will be computed based `action` field 29 | */ 30 | export const ACTIONS_WITH_VALUE_IN_ACTION_FIELD = [ 31 | TransactionActionsEnum.unDelegate 32 | ]; 33 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/getTransactionValue/helpers/getEgldValueData.ts: -------------------------------------------------------------------------------- 1 | import { DECIMALS, formatAmount } from 'lib/sdkDappUtils'; 2 | 3 | export const getEgldValueData = (value: string) => ({ 4 | egldValueData: { 5 | value, 6 | formattedValue: formatAmount({ input: value }), 7 | decimals: DECIMALS 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/getTransactionValue/helpers/getTransactionTokens.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ServerTransactionType, 3 | TokenArgumentType 4 | } from 'types/serverTransactions.types'; 5 | 6 | export const getTransactionTokens = ( 7 | transaction: ServerTransactionType 8 | ): TokenArgumentType[] => { 9 | if (transaction.action) { 10 | const merged = [ 11 | transaction.action.arguments?.token, 12 | transaction.action.arguments?.token1, 13 | transaction.action.arguments?.token2, 14 | transaction.action.arguments?.transfers // array of tokens 15 | ].filter((x) => x != null); 16 | 17 | return [].concat(...merged); 18 | } 19 | 20 | return []; 21 | }; 22 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/getTransactionValue/helpers/getValueFromActions.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { InterpretedTransactionType } from 'types/serverTransactions.types'; 3 | import { getEgldValueData } from './getEgldValueData'; 4 | 5 | let warningLogged = false; 6 | 7 | export function getValueFromActions(transaction: InterpretedTransactionType) { 8 | const value = new BigNumber(transaction.action?.arguments?.value); 9 | 10 | if (!value.isNaN()) { 11 | return getEgldValueData(transaction.action?.arguments?.value); 12 | } 13 | 14 | if (!warningLogged) { 15 | console.error( 16 | `Unable to interpret ${transaction.action?.name} data for txHash: ${transaction.txHash}` 17 | ); 18 | warningLogged = true; 19 | } 20 | 21 | // fallback on transaction value 22 | return getEgldValueData(transaction.value); 23 | } 24 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/getTransactionValue/helpers/getValueFromDataField.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { InterpretedTransactionType } from 'types/serverTransactions.types'; 3 | import { decodeBase64 } from 'utils/decoders'; 4 | import { getEgldValueData } from './getEgldValueData'; 5 | 6 | let warningLogged = false; 7 | 8 | export function getValueFromDataField(transaction: InterpretedTransactionType) { 9 | try { 10 | const data = decodeBase64(transaction.data); 11 | const encodedValue = data.replace(`${transaction.action?.name}@`, ''); 12 | const value = new BigNumber(encodedValue, 16); 13 | if (!value.isNaN()) { 14 | return getEgldValueData(value.toString(10)); 15 | } 16 | } catch { 17 | if (!warningLogged) { 18 | console.error( 19 | `Unable to extract value for txHash: ${transaction.txHash}` 20 | ); 21 | warningLogged = true; 22 | } 23 | } 24 | 25 | // fallback on transaction value 26 | return getEgldValueData(transaction.value); 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/getTransactionValue/helpers/getValueFromOperations.ts: -------------------------------------------------------------------------------- 1 | import { InterpretedTransactionType } from 'types/serverTransactions.types'; 2 | import { getEgldValueData } from './getEgldValueData'; 3 | import { getVisibleOperations } from './getVisibleOperations'; 4 | 5 | let warningLogged = false; 6 | 7 | const logError = (hash: string) => { 8 | if (!warningLogged) { 9 | console.error( 10 | `Operations field missing for txHash: ${hash}. 11 | Unable to compute value field.` 12 | ); 13 | warningLogged = true; 14 | } 15 | }; 16 | 17 | export function getValueFromOperations( 18 | transaction: InterpretedTransactionType 19 | ) { 20 | try { 21 | if (transaction.operations) { 22 | const [operation] = getVisibleOperations(transaction); 23 | return getEgldValueData(operation?.value); 24 | } else { 25 | logError(transaction.txHash); 26 | } 27 | } catch { 28 | logError(transaction.txHash); 29 | } 30 | return getEgldValueData(transaction.value); 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/getTransactionValue/helpers/getVisibleOperations.ts: -------------------------------------------------------------------------------- 1 | import { 2 | InterpretedTransactionType, 3 | VisibleTransactionOperationType 4 | } from 'types/serverTransactions.types'; 5 | 6 | export const getVisibleOperations = ( 7 | transaction: InterpretedTransactionType 8 | ) => { 9 | const operations = 10 | transaction?.operations?.filter((operation) => 11 | Object.values(VisibleTransactionOperationType).includes( 12 | operation.type 13 | ) 14 | ) ?? []; 15 | 16 | return operations; 17 | }; 18 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/getTransactionValue/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getEgldValueData'; 2 | export * from './getTitleText'; 3 | export * from './getValueFromActions'; 4 | export * from './getValueFromDataField'; 5 | export * from './getValueFromOperations'; 6 | export * from './getTransactionActionNftText'; 7 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/getTransactionValue/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getTransactionValue'; 2 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/getTransactionValue/types.ts: -------------------------------------------------------------------------------- 1 | import { TokenArgumentType } from 'types/serverTransactions.types'; 2 | 3 | export interface ESDTValueDataType { 4 | tokenFormattedAmount: string | null; 5 | tokenExplorerLink: string; 6 | tokenLinkText: string; 7 | transactionTokens: TokenArgumentType[]; 8 | token: TokenArgumentType; 9 | value: string | null; 10 | decimals: number | null; 11 | titleText: string; 12 | } 13 | 14 | export interface NFTValueDataType extends ESDTValueDataType { 15 | badgeText: string | null; 16 | } 17 | 18 | export interface TokenValueDataType extends ESDTValueDataType { 19 | showFormattedAmount: boolean; 20 | } 21 | 22 | export interface EgldValueDataType { 23 | value: string; 24 | formattedValue: string; 25 | decimals: number; 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/getOperationsMessages.ts: -------------------------------------------------------------------------------- 1 | import { ServerTransactionType } from 'types/serverTransactions.types'; 2 | 3 | export function getOperationsMessages(transaction: ServerTransactionType) { 4 | return ( 5 | transaction?.operations 6 | ?.map((operation) => operation.message) 7 | .filter((messages): messages is string => Boolean(messages)) ?? [] 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/getReceiptMessage.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { REFUNDED_GAS } from 'constants/index'; 3 | import { DECIMALS, DIGITS, formatAmount } from 'lib/sdkDappUtils'; 4 | import { ServerTransactionType } from 'types/serverTransactions.types'; 5 | 6 | const getReceiptValue = (transaction: ServerTransactionType) => { 7 | if (!transaction.receipt?.value) { 8 | return ''; 9 | } 10 | 11 | if (transaction.receipt?.data === REFUNDED_GAS) { 12 | const formattedGas = formatAmount({ 13 | input: transaction.receipt.value, 14 | decimals: DECIMALS, 15 | digits: DIGITS, 16 | showLastNonZeroDecimal: true 17 | }); 18 | 19 | const gasRefunded = new BigNumber(formattedGas) 20 | .times(transaction.gasPrice) 21 | .times(100); 22 | 23 | return gasRefunded.toFixed(); 24 | } 25 | 26 | return transaction.receipt.value; 27 | }; 28 | 29 | export function getReceiptMessage(transaction: ServerTransactionType) { 30 | const message = transaction.receipt?.data; 31 | 32 | if (!message) { 33 | return ''; 34 | } 35 | 36 | const receiptValue = getReceiptValue(transaction); 37 | const value = receiptValue ? `: ${receiptValue}` : ''; 38 | 39 | return `${message}${value}`; 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/getScResultsMessages.ts: -------------------------------------------------------------------------------- 1 | import { ServerTransactionType } from 'types/serverTransactions.types'; 2 | 3 | export function getScResultsMessages(transaction: ServerTransactionType) { 4 | return ( 5 | transaction?.results 6 | ?.map((result) => result.returnMessage) 7 | .filter((messages): messages is string => Boolean(messages)) ?? [] 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/getShardText.ts: -------------------------------------------------------------------------------- 1 | import { ALL_SHARDS_SHARD_ID, METACHAIN_SHARD_ID } from 'constants/index'; 2 | 3 | export const getShardText = (shard: number | string) => { 4 | let shardText = shard; 5 | 6 | if (typeof shardText === 'string' && shardText.includes('Shard')) { 7 | shardText = shardText.replace('Shard', '').replace(' ', ''); 8 | } 9 | 10 | const isMetachain = 11 | METACHAIN_SHARD_ID.toString() === String(shardText).toString() || 12 | String(shardText) === 'metachain'; 13 | 14 | const isAllShards = 15 | ALL_SHARDS_SHARD_ID.toString() === String(shardText).toString(); 16 | 17 | if (isMetachain) { 18 | return 'Metachain'; 19 | } 20 | if (isAllShards) { 21 | return 'All Shards'; 22 | } 23 | return `Shard ${shardText}`; 24 | }; 25 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/getTransactionIconInfo.ts: -------------------------------------------------------------------------------- 1 | import { ServerTransactionType } from 'types/serverTransactions.types'; 2 | import { capitalize } from 'utils/operations/capitalize'; 3 | import { getTransactionMessages } from './getTransactionMessages'; 4 | import { getTransactionStatus } from './getTransactionStatus'; 5 | 6 | export const getTransactionIconInfo = (transaction: ServerTransactionType) => { 7 | const transactionMessages = getTransactionMessages(transaction); 8 | 9 | const { failed, invalid, pending } = getTransactionStatus(transaction); 10 | 11 | let icon: string = ''; 12 | 13 | if (failed) { 14 | icon = 'faTimes'; 15 | } else if (invalid) { 16 | icon = 'faBan'; 17 | } else if (pending) { 18 | icon = 'faHourglass'; 19 | } 20 | 21 | const showErrorText = (failed || invalid) && transactionMessages.length > 0; 22 | const errorText = showErrorText ? transactionMessages.join(',') : ''; 23 | 24 | const tooltip = `${capitalize(transaction.status)} ${errorText}`; 25 | 26 | return { icon, tooltip }; 27 | }; 28 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/getTransactionMessages.ts: -------------------------------------------------------------------------------- 1 | import { ServerTransactionType } from 'types/serverTransactions.types'; 2 | import { getOperationsMessages } from './getOperationsMessages'; 3 | import { getReceiptMessage } from './getReceiptMessage'; 4 | import { getScResultsMessages } from './getScResultsMessages'; 5 | 6 | export function getTransactionMessages(transaction: ServerTransactionType) { 7 | return Array.from( 8 | new Set([ 9 | ...getScResultsMessages(transaction), 10 | ...getOperationsMessages(transaction), 11 | getReceiptMessage(transaction) 12 | ]) 13 | ).filter((el) => Boolean(el)); 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/getTransactionMethod.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TransactionActionCategoryEnum, 3 | TransactionActionsEnum, 4 | ServerTransactionType 5 | } from 'types/serverTransactions.types'; 6 | 7 | export const getTransactionMethod = (transaction: ServerTransactionType) => { 8 | let transactionAction = 'Transaction'; 9 | const transactionHasAction = 10 | transaction.action?.name && transaction.action?.category; 11 | 12 | if (transactionHasAction) { 13 | if ( 14 | transaction.action?.category === TransactionActionCategoryEnum.esdtNft && 15 | transaction.action?.name === TransactionActionsEnum.transfer 16 | ) { 17 | transactionAction = 'Transaction'; 18 | } else if (transaction.action) { 19 | transactionAction = transaction.action.name; 20 | } 21 | 22 | if (transaction.action?.arguments?.functionName) { 23 | transactionAction = transaction.action.arguments.functionName; 24 | } 25 | } 26 | 27 | return transactionAction; 28 | }; 29 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/getTransactionReceiver.ts: -------------------------------------------------------------------------------- 1 | import { ServerTransactionType } from 'types/serverTransactions.types'; 2 | 3 | export function getTransactionReceiver(transaction: ServerTransactionType) { 4 | let receiver = transaction.receiver; 5 | if (transaction.action?.arguments?.receiver) { 6 | receiver = transaction.action.arguments.receiver; 7 | } 8 | 9 | return receiver; 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/getTransactionReceiverAssets.ts: -------------------------------------------------------------------------------- 1 | import { ServerTransactionType } from 'types/serverTransactions.types'; 2 | import { getTransactionReceiver } from './getTransactionReceiver'; 3 | 4 | /** 5 | * The information about an account like name, icon, etc... 6 | * */ 7 | export function getTransactionReceiverAssets( 8 | transaction: ServerTransactionType 9 | ) { 10 | const receiver = getTransactionReceiver(transaction); 11 | 12 | return transaction.receiver === receiver 13 | ? transaction.receiverAssets 14 | : undefined; 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/getTransactionStatus.ts: -------------------------------------------------------------------------------- 1 | import { TransactionServerStatusesEnum } from 'types/enums.types'; 2 | import { ServerTransactionType } from 'types/serverTransactions.types'; 3 | 4 | export function getTransactionStatus(transaction: ServerTransactionType) { 5 | const statusIs = (compareTo: string) => 6 | transaction.status.toLowerCase() === compareTo.toLowerCase(); 7 | 8 | const failed = 9 | statusIs(TransactionServerStatusesEnum.fail) || 10 | statusIs(TransactionServerStatusesEnum.rewardReverted); 11 | const success = statusIs(TransactionServerStatusesEnum.success); 12 | const invalid = 13 | statusIs(TransactionServerStatusesEnum.notExecuted) || 14 | statusIs(TransactionServerStatusesEnum.invalid); 15 | const pending = 16 | statusIs(TransactionServerStatusesEnum.pending) || 17 | transaction.pendingResults; 18 | return { 19 | failed, 20 | success, 21 | invalid, 22 | pending 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/getTransactionTokens.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ServerTransactionType, 3 | TokenArgumentType 4 | } from 'types/serverTransactions.types'; 5 | 6 | export const getTransactionTokens = ( 7 | transaction: ServerTransactionType 8 | ): TokenArgumentType[] => { 9 | if (transaction.action) { 10 | const merged = [ 11 | transaction.action.arguments?.token, 12 | transaction.action.arguments?.token1, 13 | transaction.action.arguments?.token2, 14 | transaction.action.arguments?.transfers // array of tokens 15 | ].filter((x) => x != null); 16 | 17 | return [].concat(...merged); 18 | } 19 | 20 | return []; 21 | }; 22 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/getTransactionTransferType.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TransferTypeEnum, 3 | TransactionDirectionEnum, 4 | ServerTransactionType 5 | } from 'types/serverTransactions.types'; 6 | 7 | interface IGetTransactionTransferTypeParams { 8 | address: string; 9 | transaction: ServerTransactionType; 10 | receiver: string; 11 | } 12 | 13 | export function getTransactionTransferType({ 14 | address, 15 | transaction, 16 | receiver 17 | }: IGetTransactionTransferTypeParams): TransactionDirectionEnum { 18 | const directionOut = address === transaction.sender; 19 | const directionIn = address === receiver; 20 | const directionSelf = directionOut && directionIn; 21 | const isScResult = transaction?.type === TransferTypeEnum.SmartContractResult; 22 | 23 | switch (true) { 24 | case isScResult: 25 | return TransactionDirectionEnum.INTERNAL; 26 | case directionSelf: 27 | return TransactionDirectionEnum.SELF; 28 | case directionIn: 29 | return TransactionDirectionEnum.IN; 30 | case directionOut: 31 | default: 32 | return TransactionDirectionEnum.OUT; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../../getExplorerLink'; 2 | export * from './getTokenFromData'; 3 | export * from './getTransactionMethod'; 4 | export * from './getTransactionReceiver'; 5 | export * from './getTransactionReceiverAssets'; 6 | export * from './getTransactionTokens'; 7 | export * from './getTransactionTransferType'; 8 | export * from './getTransactionIconInfo'; 9 | export * from './getTransactionMessages'; 10 | export * from './getTransactionStatus'; 11 | export * from './getReceiptMessage'; 12 | export * from './getOperationsMessages'; 13 | export * from './getScResultsMessages'; 14 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/tests/base-transaction-mock.ts: -------------------------------------------------------------------------------- 1 | import { TransactionServerStatusesEnum } from 'types'; 2 | import { TransactionActionsEnum } from 'types/serverTransactions.types'; 3 | 4 | export const baseTransactionMock = { 5 | blockHash: '', 6 | price: 60, 7 | txHash: 'c747f6ea467fb68e2f152a5baa57edbc7e04d297954878084363cbdb961eed7e', 8 | gasLimit: 60000000, 9 | gasPrice: 1000000000, 10 | gasUsed: 60000000, 11 | miniBlockHash: 12 | 'd7ad7c1f8f57e3a370b12092604c42157cb5228ab598e9bbca641ee2e4ee7bd2', 13 | nonce: 965, 14 | receiver: 'erd1qqqqqqqqqqqqqpgq4gdcg0k83u7lpv4s4532w3au9y9h0vm70eqq6m8qk2', 15 | receiverShard: 0, 16 | round: 2044988, 17 | sender: 'erd1axhx4kenjlae6sknq7zjg2g4fvzavv979r2fg425p62wkl84avtqsf7vvv', 18 | senderShard: 0, 19 | signature: 'transaction-signature-hash', 20 | status: TransactionServerStatusesEnum.fail, 21 | value: '1234', 22 | fee: '655440000000000', 23 | timestamp: 1660821528, 24 | data: 'cGluZw==', 25 | action: { 26 | category: 'scCall', 27 | name: TransactionActionsEnum.ping 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/tests/getShardText.test.ts: -------------------------------------------------------------------------------- 1 | import { getShardText } from '../getShardText'; 2 | 3 | describe('getShardText tests', () => { 4 | it('formats metachain', () => { 5 | expect(getShardText(4294967295)).toBe('Metachain'); 6 | }); 7 | it('formats Shard 1', () => { 8 | expect(getShardText(1)).toBe('Shard 1'); 9 | }); 10 | it('formats Shard 2', () => { 11 | expect(getShardText('Shard 2')).toBe('Shard 2'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/helpers/tests/getTransactionReceiver.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ServerTransactionType, 3 | TransactionActionCategoryEnum, 4 | TransactionActionsEnum 5 | } from 'types/serverTransactions.types'; 6 | import { getTransactionReceiver } from '../getTransactionReceiver'; 7 | import { baseTransactionMock } from './base-transaction-mock'; 8 | 9 | describe('getTransactionReceiver', () => { 10 | it('returns receiver address from transaction body', () => { 11 | const transaction: ServerTransactionType = { 12 | ...baseTransactionMock, 13 | receiver: 'receiver-hash' 14 | }; 15 | 16 | const result = getTransactionReceiver(transaction); 17 | 18 | expect(result).toEqual(transaction.receiver); 19 | }); 20 | 21 | it('returns receiver address from the transaction action arguments if exists', () => { 22 | const transaction: ServerTransactionType = { 23 | ...baseTransactionMock, 24 | action: { 25 | category: TransactionActionCategoryEnum.esdtNft, 26 | name: TransactionActionsEnum.transfer, 27 | arguments: { 28 | receiver: 'receiver-hash' 29 | } 30 | } 31 | }; 32 | 33 | const result = getTransactionReceiver(transaction); 34 | 35 | expect(result).toEqual(transaction.action?.arguments?.receiver); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/utils/transactions/getInterpretedTransaction/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getInterpretedTransaction'; 2 | export * from './helpers'; 3 | -------------------------------------------------------------------------------- /src/utils/transactions/getTransactionsHistory/getTransactionsHistory.ts: -------------------------------------------------------------------------------- 1 | import isEmpty from 'lodash.isempty'; 2 | import { ITransactionListItem } from 'lib/sdkDappUi'; 3 | import type { IGetHistoricalTransactionsParams } from 'types/transaction-list-item.types'; 4 | import { 5 | createTransactionsHistoryFromSessions, 6 | mapServerTransactionsToListItems 7 | } from './helpers'; 8 | 9 | export const getTransactionsHistory = async ({ 10 | transactionsSessions, 11 | address, 12 | explorerAddress, 13 | egldLabel 14 | }: IGetHistoricalTransactionsParams): Promise => { 15 | if (isEmpty(transactionsSessions)) { 16 | return []; 17 | } 18 | 19 | const signedTransactions = 20 | createTransactionsHistoryFromSessions(transactionsSessions); 21 | 22 | return mapServerTransactionsToListItems({ 23 | transactions: signedTransactions, 24 | address, 25 | explorerAddress, 26 | egldLabel 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /src/utils/transactions/getTransactionsHistory/helpers/createTransactionsHistoryFromSessions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SessionTransactionType, 3 | SignedTransactionType 4 | } from 'types/transactions.types'; 5 | 6 | export const createTransactionsHistoryFromSessions = ( 7 | sessions: Record 8 | ): SignedTransactionType[] => { 9 | const sortedSessionKeys = Object.keys(sessions).sort( 10 | (a, b) => Number(b) - Number(a) 11 | ); 12 | 13 | return sortedSessionKeys.reduce( 14 | (allTransactions, sessionKey: string) => { 15 | const sessionTransactions = sessions[sessionKey]?.transactions || []; 16 | return [...allTransactions, ...sessionTransactions]; 17 | }, 18 | [] 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/transactions/getTransactionsHistory/helpers/getAssetAmount.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import trimEnd from 'lodash.trimend'; 3 | import { formatAmount, DECIMALS, DIGITS, ZERO } from 'lib/sdkDappUtils'; 4 | 5 | import { trimAmountDecimals } from 'utils/operations/trimAmountDecimals'; 6 | import { isTransferNftOrSft } from './isTransferNftOrSft'; 7 | 8 | export const getAssetAmount = (transactionTransfer: Record) => { 9 | if (isTransferNftOrSft(transactionTransfer)) { 10 | return parseFloat(transactionTransfer.value).toLocaleString(); 11 | } 12 | 13 | const formattedAmount = formatAmount({ 14 | input: String(transactionTransfer.value ?? ZERO), 15 | decimals: Number(transactionTransfer.decimals ?? DECIMALS), 16 | showLastNonZeroDecimal: true, 17 | addCommas: true 18 | }); 19 | 20 | const trimmedAmount = trimAmountDecimals({ 21 | minimumPositiveDecimals: DIGITS, 22 | amount: formattedAmount 23 | }); 24 | 25 | const [price, decimals] = trimmedAmount.split('.'); 26 | const trimmedDecimals = trimEnd(decimals, ZERO); 27 | 28 | if (decimals && new BigNumber(decimals).isGreaterThan(ZERO)) { 29 | return `${price}.${trimmedDecimals}`; 30 | } 31 | 32 | return trimmedAmount; 33 | }; 34 | -------------------------------------------------------------------------------- /src/utils/transactions/getTransactionsHistory/helpers/getAssetPrice.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | 3 | import { formatAmount, DECIMALS, ZERO } from 'lib/sdkDappUtils'; 4 | 5 | import { isTransferNftOrSft } from './isTransferNftOrSft'; 6 | 7 | export const getAssetPrice = (transactionTransfer: Record) => { 8 | if (isTransferNftOrSft(transactionTransfer)) { 9 | return; 10 | } 11 | 12 | const formattedAmount = formatAmount({ 13 | input: String(transactionTransfer.value ?? ZERO), 14 | decimals: Number(transactionTransfer.decimals ?? DECIMALS), 15 | showLastNonZeroDecimal: true 16 | }); 17 | 18 | const assetPrice = new BigNumber(transactionTransfer.usdPrice ?? '1') 19 | .times(formattedAmount) 20 | .toString(); 21 | 22 | return assetPrice; 23 | }; 24 | -------------------------------------------------------------------------------- /src/utils/transactions/getTransactionsHistory/helpers/getCachedTransactionListItem.ts: -------------------------------------------------------------------------------- 1 | import { ITransactionListItem } from 'lib/sdkDappUi'; 2 | import { getCachedItemSelector } from 'store/selectors/cacheSelector'; 3 | import { getStore } from 'store/store'; 4 | 5 | export const getCachedTransactionListItem = ( 6 | hash: string 7 | ): ITransactionListItem | null => { 8 | const cachedTransaction = getCachedItemSelector( 9 | `transaction-${hash}` 10 | )(getStore().getState()); 11 | 12 | return cachedTransaction ?? null; 13 | }; 14 | -------------------------------------------------------------------------------- /src/utils/transactions/getTransactionsHistory/helpers/getIsTransactionInvalidOrFailed.ts: -------------------------------------------------------------------------------- 1 | import { TransactionServerStatusesEnum } from 'types/enums.types'; 2 | 3 | export const getIsTransactionInvalidOrFailed = ( 4 | status: TransactionServerStatusesEnum 5 | ): boolean => { 6 | const failedOrInvalidTransactionStatuses = [ 7 | TransactionServerStatusesEnum.fail, 8 | TransactionServerStatusesEnum.invalid 9 | ]; 10 | 11 | const isTransactionFailedOrInvalid = 12 | failedOrInvalidTransactionStatuses.includes( 13 | status as TransactionServerStatusesEnum 14 | ); 15 | 16 | return isTransactionFailedOrInvalid; 17 | }; 18 | -------------------------------------------------------------------------------- /src/utils/transactions/getTransactionsHistory/helpers/getReceiverData.ts: -------------------------------------------------------------------------------- 1 | import { AssetType } from 'types/account.types'; 2 | import { ServerTransactionType } from 'types/serverTransactions.types'; 3 | 4 | export const getReceiverData = (transaction: ServerTransactionType) => { 5 | const receiverAddress = transaction.receiver; 6 | const isSelfTransaction = receiverAddress === transaction.sender; 7 | 8 | const transactionArguments = 9 | transaction.action && transaction.action.arguments; 10 | 11 | const retrieveArgumentsReceiverData = 12 | isSelfTransaction && 13 | transactionArguments && 14 | transactionArguments.receiver && 15 | transactionArguments.receiver !== receiverAddress; 16 | 17 | const receiver: string = retrieveArgumentsReceiverData 18 | ? transactionArguments.receiver 19 | : receiverAddress; 20 | 21 | const receiverAssets: AssetType = retrieveArgumentsReceiverData 22 | ? transactionArguments.receiverAssets 23 | : transaction.receiverAssets; 24 | 25 | return { receiver, receiverAssets }; 26 | }; 27 | -------------------------------------------------------------------------------- /src/utils/transactions/getTransactionsHistory/helpers/getTransactionAmount.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { ITransactionAsset } from './getTransactionAssets'; 3 | 4 | interface IGetTransactionAmountParams { 5 | transactionAssets: ITransactionAsset[]; 6 | isIncomingTransaction: boolean; 7 | } 8 | 9 | export const getTransactionAmount = ({ 10 | transactionAssets, 11 | isIncomingTransaction 12 | }: IGetTransactionAmountParams): string => { 13 | if (transactionAssets.length > 1) { 14 | const firstAssetTicker = transactionAssets[0]?.assetTicker; 15 | const hasMultipleDifferentAssets = transactionAssets.some( 16 | (asset) => asset.assetTicker !== firstAssetTicker 17 | ); 18 | 19 | if (hasMultipleDifferentAssets) { 20 | return ''; 21 | } 22 | } 23 | 24 | if (!transactionAssets[0]?.assetAmount) { 25 | return ''; 26 | } 27 | 28 | const amount = transactionAssets[0].assetAmount; 29 | const ticker = transactionAssets[0].assetTicker; 30 | 31 | if (new BigNumber(amount).isZero()) { 32 | return `${amount} ${ticker}`; 33 | } 34 | 35 | const prefix = isIncomingTransaction ? '+' : '-'; 36 | 37 | return `${prefix}${amount} ${ticker}`; 38 | }; 39 | -------------------------------------------------------------------------------- /src/utils/transactions/getTransactionsHistory/helpers/getTransactionAvatar.ts: -------------------------------------------------------------------------------- 1 | interface IGetTransactionAvatarParams { 2 | senderAssets?: IAsset; 3 | receiverAssets?: IAsset; 4 | userIsReceiver: boolean; 5 | } 6 | 7 | interface IAsset { 8 | svgUrl?: string; 9 | iconSvg?: string; 10 | iconPng?: string; 11 | } 12 | 13 | export const getTransactionAvatar = ({ 14 | senderAssets, 15 | receiverAssets, 16 | userIsReceiver 17 | }: IGetTransactionAvatarParams): string | null => { 18 | if (userIsReceiver) { 19 | return ( 20 | senderAssets?.svgUrl ?? 21 | senderAssets?.iconSvg ?? 22 | senderAssets?.iconPng ?? 23 | null 24 | ); 25 | } 26 | 27 | return ( 28 | receiverAssets?.svgUrl ?? 29 | receiverAssets?.iconSvg ?? 30 | receiverAssets?.iconPng ?? 31 | null 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/utils/transactions/getTransactionsHistory/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export { mapTransactionToListItem } from './mapTransactionToListItem'; 2 | export { createTransactionsHistoryFromSessions } from './createTransactionsHistoryFromSessions'; 3 | export { getReceiverData } from './getReceiverData'; 4 | export { getTransactionAmount } from './getTransactionAmount'; 5 | export { getTransactionAction as processTransactionAction } from './getTransactionAction'; 6 | export { getTransactionAssets as processTransactionAssets } from './getTransactionAssets'; 7 | export { getTransactionAsset } from './getTransactionAsset'; 8 | export { mapServerTransactionsToListItems } from './mapServerTransactionsToListItems'; 9 | -------------------------------------------------------------------------------- /src/utils/transactions/getTransactionsHistory/helpers/isTransferNftOrSft.ts: -------------------------------------------------------------------------------- 1 | import { NftEnumType } from 'types/tokens.types'; 2 | 3 | export const isTransferNftOrSft = ( 4 | transactionTransfer: Record 5 | ) => { 6 | const assetTypesWithoutDenomination = [ 7 | NftEnumType.SemiFungibleESDT, 8 | NftEnumType.NonFungibleESDT 9 | ]; 10 | 11 | const transferType = transactionTransfer.type as NftEnumType; 12 | const isTransferTypeNftOrSFt = 13 | assetTypesWithoutDenomination.includes(transferType); 14 | 15 | return isTransferTypeNftOrSFt; 16 | }; 17 | -------------------------------------------------------------------------------- /src/utils/transactions/getTransactionsHistory/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getTransactionsHistory'; 2 | export * from './helpers'; 3 | -------------------------------------------------------------------------------- /src/utils/transactions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './explorerUrlBuilder'; 2 | export * from './getActiveTransactionsStatus'; 3 | export * from './getHumanReadableTimeFormat'; 4 | export * from './getTransactionsHistory'; 5 | -------------------------------------------------------------------------------- /src/utils/transactions/isGuardianTx.ts: -------------------------------------------------------------------------------- 1 | import { GuardianActionsEnum } from 'types/enums.types'; 2 | 3 | export const isGuardianTx = ({ 4 | data, 5 | onlySetGuardian 6 | }: { 7 | data?: string; 8 | onlySetGuardian?: boolean; 9 | }) => { 10 | if (!data) { 11 | return false; 12 | } 13 | 14 | if (onlySetGuardian) { 15 | return data.startsWith(GuardianActionsEnum.SetGuardian); 16 | } 17 | 18 | return Object.values(GuardianActionsEnum).some((action) => 19 | data.startsWith(action) 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/utils/transactions/tests/getExplorerkLink.test.ts: -------------------------------------------------------------------------------- 1 | import { getExplorerLink } from '../getExplorerLink'; 2 | 3 | describe('getNetworkLink', () => { 4 | it('return "/${to}" parameter when the explorerAddress is empty and log an error in console', () => { 5 | // prevent showing errors in Jest console 6 | jest.mock('console', () => ({ 7 | error: () => null 8 | })); 9 | 10 | const input = 'address'; 11 | const consoleErrorSpy = jest.spyOn(console, 'error'); 12 | 13 | const result = getExplorerLink({ explorerAddress: '', to: input }); 14 | 15 | expect(consoleErrorSpy).toHaveBeenCalled(); 16 | expect(result).toEqual(`/${input}`); 17 | }); 18 | 19 | it('compose link using explorer address and "to" parameter', () => { 20 | const explorerAddress = 'http://devnet-explorer.multiversx.com'; 21 | const to = '/address'; 22 | 23 | const result = getExplorerLink({ explorerAddress, to }); 24 | 25 | expect(result).toEqual(`${explorerAddress}${to}`); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/utils/validation/addressIsValid.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '@multiversx/sdk-core'; 2 | 3 | function canTransformToPublicKey(address: string) { 4 | try { 5 | return Address.isValid(address); 6 | } catch { 7 | return false; 8 | } 9 | } 10 | 11 | export function addressIsValid(destinationAddress: string) { 12 | const isValidString = /^\w+$/.test(destinationAddress); 13 | 14 | return isValidString && canTransformToPublicKey(destinationAddress); 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/validation/getIdentifierType.ts: -------------------------------------------------------------------------------- 1 | const esdtParts = 2; 2 | const nftParts = 3; 3 | 4 | const defaultResult = { 5 | isEsdt: false, 6 | isNft: false, 7 | isEgld: false 8 | }; 9 | 10 | export function getIdentifierType(identifier?: string): { 11 | isEsdt: boolean; 12 | isNft: boolean; 13 | isEgld: boolean; 14 | } { 15 | const parts = identifier?.split('-').length; 16 | 17 | if (parts === esdtParts) { 18 | return { 19 | ...defaultResult, 20 | isEsdt: true 21 | }; 22 | } 23 | if (parts === nftParts) { 24 | return { 25 | ...defaultResult, 26 | isNft: true 27 | }; 28 | } 29 | return { 30 | ...defaultResult, 31 | isEgld: true 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/validation/hex.ts: -------------------------------------------------------------------------------- 1 | export const isHexValidCharacters = (str: string) => { 2 | return str.toLowerCase().match(/[0-9a-f]/g); 3 | }; 4 | 5 | export const isHexValidLength = (str: string) => { 6 | return str.length % 2 === 0; 7 | }; 8 | -------------------------------------------------------------------------------- /src/utils/validation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './maxDecimals'; 2 | export * from './getIdentifierType'; 3 | export * from './addressIsValid'; 4 | export * from './isContract'; 5 | export * from './hex'; 6 | -------------------------------------------------------------------------------- /src/utils/validation/maxDecimals.ts: -------------------------------------------------------------------------------- 1 | export const maxDecimals = (amount: string, decimals = 18) => { 2 | if ( 3 | amount != null && 4 | amount.toString().indexOf('.') >= 0 && 5 | (amount as any).toString().split('.').pop().length > decimals 6 | ) { 7 | return false; 8 | } 9 | return true; 10 | }; 11 | -------------------------------------------------------------------------------- /src/utils/validation/tests/isContract.test.ts: -------------------------------------------------------------------------------- 1 | import { isContract } from '../isContract'; 2 | 3 | const userAddress = 4 | 'erd1a07ey0xj28u90mtk8858zsavs0cj7s3cy74ufgxdmcq3nslr0y2st2aaax'; 5 | const contractAddress = 6 | 'erd1qqqqqqqqqqqqqpgqv9gxgq8nurz754spjfck6rdwlg9etpcp0n4sjg2dhc'; 7 | 8 | describe('isContract tests', () => { 9 | it('returns false for user account', () => { 10 | const valid = isContract(userAddress); 11 | expect(valid).toBe(false); 12 | }); 13 | it('returns true for smart contract', () => { 14 | const valid = isContract(contractAddress); 15 | expect(valid).toBe(true); 16 | }); 17 | it('returns false for invalid address', () => { 18 | const valid = isContract('erd1qqqqqqqqqqqqqqqpq'); 19 | expect(valid).toBe(false); 20 | }); 21 | it('returns true for contract address in data', () => { 22 | const valid = isContract( 23 | userAddress, 24 | undefined, 25 | 'MultiESDTNFTTransfer@0000000000000000050061506400f3e0c5ea560192716d0daefa0b9587017ceb@02@5745474c442d383836303061@@8ac7230489e80000@555344432d613332393036@@464e61d6@6164644c6971756964697479@8963dd8c2c5e0000@459a65fa' 26 | ); 27 | expect(valid).toBe(true); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/utils/walletconnect/__mocks__/sdkWalletconnectProvider.ts: -------------------------------------------------------------------------------- 1 | import { EmptyProvider } from 'providers/helpers/emptyProvider'; 2 | 3 | type SessionEventTypes = any; 4 | type PairingTypes = any; 5 | type EngineTypes = any; 6 | const WalletConnectMethodsEnum = {}; 7 | const WalletConnectOptionalMethodsEnum = {}; 8 | const WalletConnectV2Provider = EmptyProvider; 9 | 10 | export { 11 | EngineTypes, 12 | PairingTypes, 13 | SessionEventTypes, 14 | WalletConnectMethodsEnum, 15 | WalletConnectOptionalMethodsEnum, 16 | WalletConnectV2Provider 17 | }; 18 | -------------------------------------------------------------------------------- /src/utils/walletconnect/__sdkWalletconnectProvider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SessionEventTypes, 3 | PairingTypes, 4 | EngineTypes 5 | } from '@multiversx/sdk-wallet-connect-provider'; 6 | import { 7 | Operation as WalletConnectMethodsEnum, 8 | OptionalOperation as WalletConnectOptionalMethodsEnum 9 | } from '@multiversx/sdk-wallet-connect-provider/out/operation'; 10 | import { WalletConnectV2Provider } from '@multiversx/sdk-wallet-connect-provider/out/walletConnectV2Provider'; 11 | 12 | /** 13 | * These members are ingnored when compiling to commonJS 14 | */ 15 | export { 16 | EngineTypes, 17 | PairingTypes, 18 | SessionEventTypes, 19 | WalletConnectMethodsEnum, 20 | WalletConnectOptionalMethodsEnum, 21 | WalletConnectV2Provider 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils/window/buildUrlParams.ts: -------------------------------------------------------------------------------- 1 | export function buildUrlParams( 2 | search: string, 3 | urlParams: { 4 | [key: string]: string; 5 | } 6 | ) { 7 | const urlSearchParams: any = new URLSearchParams(search); 8 | const params = Object.fromEntries(urlSearchParams); 9 | const nextUrlParams = new URLSearchParams({ 10 | ...params, 11 | ...urlParams 12 | }).toString(); 13 | return { nextUrlParams, params }; 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/window/getIsAuthRoute.ts: -------------------------------------------------------------------------------- 1 | import { matchPath } from './matchPath'; 2 | 3 | /** 4 | * Allow detecting authenticated routes with pattern parameters 5 | * @example 6 | * routes = [ 7 | * { 8 | path: "/users/:id", 9 | component: () => <>, 10 | authenticatedRoute: true 11 | } 12 | ] 13 | */ 14 | export const getIsAuthRoute = < 15 | T extends { 16 | authenticatedRoute: boolean; 17 | path: string; 18 | } 19 | >( 20 | routes: Array, 21 | pathname: string 22 | ) => { 23 | const authenticatedRoutes = routes.filter(({ authenticatedRoute }) => 24 | Boolean(authenticatedRoute) 25 | ); 26 | 27 | const isOnAuthenticatedRoute = authenticatedRoutes.some( 28 | ({ path }) => matchPath(path, pathname) !== null 29 | ); 30 | 31 | return isOnAuthenticatedRoute; 32 | }; 33 | -------------------------------------------------------------------------------- /src/utils/window/getIsInIframe.ts: -------------------------------------------------------------------------------- 1 | import { getWindowParentOrigin } from './getWindowParentOrigin'; 2 | import { isWindowAvailable } from './isWindowAvailable'; 3 | 4 | export const getIsInIframe = (): boolean => { 5 | try { 6 | if (!isWindowAvailable()) { 7 | return false; 8 | } 9 | 10 | const parentOrigin = getWindowParentOrigin(); 11 | 12 | if (!parentOrigin) { 13 | return false; 14 | } 15 | 16 | // Check if window.self is different from window.top 17 | // This is a standard way to detect if we're in an iframe 18 | return window.self !== window.top; 19 | } catch (_error) { 20 | // If we get a security error when trying to access window.top, 21 | // it means we're in a cross-origin iframe 22 | return true; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/utils/window/getWindowLocation.ts: -------------------------------------------------------------------------------- 1 | import { isWindowAvailable } from './isWindowAvailable'; 2 | 3 | type GetWindowLocationType = { 4 | pathname: string; 5 | hash: string; 6 | origin: string; 7 | href: string; 8 | search: string; 9 | }; 10 | 11 | export const getWindowLocation = (): GetWindowLocationType => { 12 | const isAvailable = isWindowAvailable(); 13 | 14 | if (!isAvailable) { 15 | return { 16 | pathname: '', 17 | hash: '', 18 | origin: '', 19 | href: '', 20 | search: '' 21 | }; 22 | } 23 | 24 | const { 25 | location: { pathname, hash, origin, href, search } 26 | } = window; 27 | 28 | return { 29 | pathname, 30 | hash, 31 | origin, 32 | href, 33 | search 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /src/utils/window/getWindowParentOrigin.ts: -------------------------------------------------------------------------------- 1 | import { safeWindow } from 'constants/window.constants'; 2 | 3 | export function getWindowParentOrigin() { 4 | if (!safeWindow) { 5 | return ''; 6 | } 7 | 8 | try { 9 | if (safeWindow.document.referrer) { 10 | return new URL(safeWindow.document.referrer).origin; 11 | } 12 | 13 | const ancestorOrigins = safeWindow.location.ancestorOrigins; 14 | 15 | if (ancestorOrigins.length < 1) { 16 | return ''; 17 | } 18 | 19 | return new URL(ancestorOrigins[ancestorOrigins.length - 1]).origin; 20 | } catch (e) { 21 | console.error(e); 22 | return ''; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/window/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getWindowLocation'; 2 | export * from './isWindowAvailable'; 3 | export * from './sanitizeCallbackUrl'; 4 | export * from './buildUrlParams'; 5 | export * from './getIsAuthRoute'; 6 | export * from './getIsInIframe'; 7 | -------------------------------------------------------------------------------- /src/utils/window/isWindowAvailable.ts: -------------------------------------------------------------------------------- 1 | export const isWindowAvailable = () => 2 | typeof window != 'undefined' && typeof window?.location != 'undefined'; 3 | -------------------------------------------------------------------------------- /src/utils/window/sanitizeCallbackUrl.ts: -------------------------------------------------------------------------------- 1 | export const sanitizeCallbackUrl = ( 2 | targetURL: string, 3 | vulnerableItems: string[] = ['address'] 4 | ) => { 5 | const url = new URL(targetURL); 6 | const params = new URLSearchParams(url.search); 7 | 8 | vulnerableItems.forEach((vulnerableItem) => params.delete(vulnerableItem)); 9 | 10 | const questionMark = Array.from(params.values()).length > 0 ? '?' : ''; 11 | 12 | const pathname = 13 | url.pathname === '/' && !targetURL.endsWith('/') ? '' : url.pathname; 14 | 15 | if (url.protocol === 'vscode:') { 16 | return targetURL; 17 | } 18 | 19 | return `${url.origin}${pathname}${questionMark}${params.toString()}${ 20 | url.hash 21 | }`; 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils/window/tests/getIsAuthRoute.test.ts: -------------------------------------------------------------------------------- 1 | import { getIsAuthRoute } from '../getIsAuthRoute'; 2 | 3 | const createRoutes = (path: string, authenticatedRoute = true) => [ 4 | { 5 | path, 6 | component: () => null, 7 | authenticatedRoute 8 | } 9 | ]; 10 | 11 | describe('matchRoute', () => { 12 | it('should return true for simple routes', () => { 13 | const result = getIsAuthRoute(createRoutes('/home'), '/home'); 14 | expect(result).toBe(true); 15 | }); 16 | it('should return true for pattern routes', () => { 17 | const result = getIsAuthRoute( 18 | createRoutes('/user/:id'), 19 | '/user/first-name' 20 | ); 21 | expect(result).toBe(true); 22 | }); 23 | it('should return false for non-matching pattern routes', () => { 24 | const result = getIsAuthRoute( 25 | createRoutes('/user/:id'), 26 | '/user/first-name/detail' 27 | ); 28 | expect(result).toBe(false); 29 | }); 30 | it('should return true for non-athenticated non-matching pattern routes', () => { 31 | const result = getIsAuthRoute( 32 | createRoutes('/user/:id', false), 33 | '/user/first-name' 34 | ); 35 | expect(result).toBe(false); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "out", 4 | "lib": [ 5 | "es2021", 6 | "dom" 7 | ], 8 | "target": "es2021", 9 | "sourceMap": true, 10 | "allowJs": true, 11 | "baseUrl": "./src", 12 | "strict": true, 13 | "strictPropertyInitialization": true, 14 | "strictNullChecks": true, 15 | "skipLibCheck": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "noUnusedParameters": true, 19 | "esModuleInterop": true, 20 | "declaration": true, 21 | "experimentalDecorators": true, 22 | "useDefineForClassFields": false, 23 | }, 24 | "include": [ 25 | "src/**/*" 26 | ], 27 | "exclude": [ 28 | "node_modules", 29 | "out" 30 | ], 31 | } 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | } 6 | } 7 | --------------------------------------------------------------------------------