From 1b31d72bdb216bb4bdca020c2c00eaaefd379502 Mon Sep 17 00:00:00 2001 From: David Totrashvili <8580261+0xDazzer@users.noreply.github.com> Date: Fri, 11 Jul 2025 20:24:39 +0500 Subject: [PATCH 1/6] feat: redesign (#1230) * feat: redesign * refactor: bump core-ui * fix: wallet-connector version * fix: remove neDesign flag * fix: e2e tests --- e2e/fixtures/wallet_balance.selectors.ts | 10 +- package-lock.json | 18 +- package.json | 4 +- src/main.tsx | 11 +- src/ui/common/api/apiWrapper.ts | 103 ++++ src/ui/common/api/error/index.ts | 13 + src/ui/common/api/getBsn.ts | 32 ++ src/ui/common/api/getDelegations.ts | 91 ++++ src/ui/common/api/getDelegationsV2.ts | 134 +++++ src/ui/common/api/getFinalityProviders.ts | 97 ++++ src/ui/common/api/getFinalityProvidersV2.ts | 110 ++++ src/ui/common/api/getNetworkInfo.ts | 165 ++++++ src/ui/common/api/getStats.ts | 35 ++ src/ui/common/api/getSystemStats.ts | 43 ++ src/ui/common/api/getUnbondingEligibility.ts | 17 + src/ui/common/api/healthCheckClient.ts | 15 + src/ui/common/api/httpStatusCodes.ts | 69 +++ src/ui/common/api/logTermAcceptance.ts | 32 ++ src/ui/common/api/postFilterOrdinals.ts | 46 ++ src/ui/common/api/postUnbonding.ts | 32 ++ src/ui/common/api/verifyBTCAddress.ts | 27 + src/ui/common/assets/404-error-character.svg | 438 ++++++++++++++++ src/ui/common/assets/babylon.svg | 29 ++ src/ui/common/assets/bbn.svg | 10 + src/ui/common/assets/bg-pattern.svg | 12 + src/ui/common/assets/bitcoin-block.svg | 98 ++++ src/ui/common/assets/bitcoin.png | Bin 0 -> 39289 bytes src/ui/common/assets/blue-check.svg | 11 + src/ui/common/assets/cancel-circle.svg | 3 + .../common/assets/chains/babylon-genesis.png | Bin 0 -> 8836 bytes src/ui/common/assets/chains/cosmos.png | Bin 0 -> 504325 bytes src/ui/common/assets/chains/ethereum.png | Bin 0 -> 2756 bytes src/ui/common/assets/chains/placeholder.svg | 6 + src/ui/common/assets/chains/sui.png | Bin 0 -> 3284 bytes src/ui/common/assets/check.svg | 3 + src/ui/common/assets/cog.svg | 5 + src/ui/common/assets/edit.svg | 3 + .../common/assets/generic-error-character.svg | 115 ++++ src/ui/common/assets/gold-check.svg | 11 + src/ui/common/assets/icon-black.svg | 10 + src/ui/common/assets/icon-white.svg | 10 + src/ui/common/assets/signet_bitcoin.svg | 1 + src/ui/common/assets/testnet-bg-pattern.svg | 12 + src/ui/common/assets/warning-octagon.svg | 3 + src/ui/common/assets/warning-triangle.svg | 3 + .../common/components/Activity/Activity.tsx | 16 + .../Activity/components/ActivityList.tsx | 344 ++++++++++++ .../components/ActivityCard/ActivityCard.tsx | 68 +++ .../components/ActivityCardActionSection.tsx | 27 + .../components/ActivityCardAmountSection.tsx | 48 ++ .../components/ActivityCardDetailsSection.tsx | 79 +++ .../components/ActivityListItem.tsx | 33 ++ .../common/components/ActivityCard/index.tsx | 14 + src/ui/common/components/Alerts/Alert.tsx | 65 +++ src/ui/common/components/Banner/Banner.tsx | 25 + .../components/Common/AuthGuard/index.tsx | 30 ++ .../common/components/Common/Body/index.tsx | 13 + .../Common/GridTable/components/TCell.tsx | 26 + .../Common/GridTable/components/THead.tsx | 48 ++ .../components/Common/GridTable/index.tsx | 129 +++++ .../components/Common/GridTable/types.ts | 52 ++ .../components/Common/GridTable/utils.ts | 17 + .../common/components/Common/Hint/index.tsx | 83 +++ .../common/components/Container/Container.tsx | 18 + .../components/Delegations/Delegation.tsx | 240 +++++++++ .../Delegations/DelegationActions.tsx | 204 ++++++++ .../DelegationList/NoDelegations.tsx | 18 + .../components/ActionButton.tsx | 154 ++++++ .../DelegationList/components/Amount.tsx | 22 + .../components/DelegationModal.tsx | 123 +++++ .../components/FinalityProviderMoniker.tsx | 65 +++ .../DelegationList/components/Inception.tsx | 15 + .../DelegationList/components/Overflow.tsx | 10 + .../components/SlashingContent.tsx | 100 ++++ .../DelegationList/components/Status.tsx | 193 +++++++ .../DelegationList/components/TxHash.tsx | 23 + .../Delegations/DelegationList/index.tsx | 161 ++++++ .../Delegations/DelegationList/utils.ts | 0 .../components/Delegations/Delegations.tsx | 487 +++++++++++++++++ .../Delegations/components/DelegationCell.tsx | 20 + .../common/components/Error/GenericError.tsx | 54 ++ src/ui/common/components/FAQ/FAQ.tsx | 25 + src/ui/common/components/FAQ/Section.tsx | 39 ++ .../common/components/FAQ/data/questions.tsx | 220 ++++++++ src/ui/common/components/FAQ/index.ts | 1 + src/ui/common/components/Footer/Footer.tsx | 116 +++++ src/ui/common/components/Hash/Hash.tsx | 88 ++++ src/ui/common/components/Header/Header.tsx | 24 + .../components/Header/SimplifiedHeader.tsx | 25 + src/ui/common/components/Icons/ThemedIcon.tsx | 55 ++ .../components/Icons/common/CopyIcon.tsx | 19 + src/ui/common/components/Icons/index.ts | 20 + .../Icons/wallet/BitcoinPublicKeyIcon.tsx | 20 + .../Icons/wallet/LinkWalletIcon.tsx | 20 + .../Icons/wallet/UsingInscriptionIcon.tsx | 21 + src/ui/common/components/Loading/Loading.tsx | 38 ++ src/ui/common/components/Logo/Icon.tsx | 33 ++ src/ui/common/components/Logo/Logo.tsx | 52 ++ src/ui/common/components/Logo/SmallLogo.tsx | 48 ++ .../components/SettingMenuButton.tsx | 32 ++ .../components/SettingMenuContainer.tsx | 42 ++ .../components/SettingMenuContent.tsx | 14 + .../components/Menu/SettingMenu/index.ts | 3 + .../Menu/WalletMenu/components/Toggle.tsx | 49 ++ .../components/WalletInfoSection.tsx | 106 ++++ .../components/WalletMenuContainer.tsx | 157 ++++++ .../components/WalletSettingsSection.tsx | 112 ++++ .../components/Menu/WalletMenu/index.ts | 4 + src/ui/common/components/Menu/index.ts | 3 + .../components/Modals/CancelFeedbackModal.tsx | 68 +++ .../components/Modals/ClaimRewardModal.tsx | 85 +++ .../ClaimStatusModal/ClaimStatusModal.tsx | 57 ++ .../ClaimStatusModal/SuccessContent.tsx | 38 ++ .../components/Modals/ConfirmationModal.tsx | 63 +++ .../common/components/Modals/ErrorModal.tsx | 154 ++++++ src/ui/common/components/Modals/InfoModal.tsx | 65 +++ .../MultistakingStartModal.tsx | 180 +++++++ .../common/components/Modals/PreviewModal.tsx | 243 +++++++++ .../Modals/PreviewMultistakingModal.tsx | 252 +++++++++ .../RegistrationEndModal.tsx | 49 ++ .../RegistrationStartModal.tsx | 40 ++ .../components/Modals/ResponsiveDialog.tsx | 16 + .../components/Modals/SignDetailsModal.tsx | 57 ++ .../components/Modals/SignModal/SignModal.tsx | 97 ++++ .../components/Modals/SignModal/Step.tsx | 99 ++++ .../components/Modals/SlashingModal.tsx | 31 ++ .../common/components/Modals/StakeModal.tsx | 39 ++ .../common/components/Modals/SubmitModal.tsx | 75 +++ .../Modals/SuccessFeedbackModal.tsx | 45 ++ .../common/components/Modals/UnbondModal.tsx | 54 ++ .../components/Modals/VerificationModal.tsx | 49 ++ .../Modals/WalletDisconnectModal.tsx | 75 +++ .../components/Modals/WithdrawModal.tsx | 25 + .../BsnFinalityProviderField.tsx | 131 +++++ .../BsnFinalityProviderItem.tsx | 79 +++ .../SelectedProvidersList.tsx | 30 ++ .../ChainSelectionModal.tsx | 178 +++++++ .../components/Multistaking/CounterButton.tsx | 42 ++ .../FinalityProviderModal.tsx | 78 +++ .../FinalityProviders.tsx | 25 + .../FinalityProviderFilter.tsx | 23 + .../FinalityProviderSearch.tsx | 43 ++ .../FinalityProviderTable.tsx | 147 ++++++ .../MultistakingForm/AmountBalanceInfo.tsx | 46 ++ .../MultistakingForm/AmountSubsection.tsx | 59 +++ .../MultistakingForm/BTCFeeRate.tsx | 125 +++++ .../MultistakingForm/ConnectButton.tsx | 15 + .../MultistakingForm/FeesSection.tsx | 24 + .../MultistakingForm/FormAlert.tsx | 46 ++ .../MultistakingForm/MultistakingForm.tsx | 89 ++++ .../MultistakingFormWrapper.tsx | 17 + .../MultistakingForm/SubSection.tsx | 22 + .../MultistakingForm/SubmitButton.tsx | 53 ++ .../MultistakingModal/MultistakingModal.tsx | 263 ++++++++++ .../components/NetworkBadge/NetworkBadge.tsx | 31 ++ .../components/NetworkBadge/testnet-icon.png | Bin 0 -> 40527 bytes .../components/Notification/DetailsButton.tsx | 10 + .../Notification/FloatingTopBar.tsx | 22 + .../components/Notification/IconWrapper.tsx | 24 + .../components/Notification/Notification.tsx | 52 ++ .../Notification/NotificationContainer.tsx | 34 ++ .../Notification/NotificationText.tsx | 9 + .../Notification/NotificationTitle.tsx | 9 + .../PersonalBalance/PersonalBalance.tsx | 135 +++++ src/ui/common/components/Section/Section.tsx | 33 ++ .../components/SignDetails/SignDetails.tsx | 149 ++++++ src/ui/common/components/Stakers/Staker.tsx | 46 ++ .../DelegationForm/components/AmountField.tsx | 33 ++ .../components/BBNFeeAmount.tsx | 24 + .../components/BTCFeeAmount.tsx | 30 ++ .../DelegationForm/components/BTCFeeRate.tsx | 112 ++++ .../DelegationForm/components/FeeItem.tsx | 40 ++ .../DelegationForm/components/FeeSection.tsx | 14 + .../DelegationForm/components/InfoAlert.tsx | 39 ++ .../DelegationForm/components/Overlay.tsx | 33 ++ .../components/SubmitButton.tsx | 47 ++ .../DelegationForm/components/TermField.tsx | 33 ++ .../DelegationForm/components/Total.tsx | 42 ++ .../Staking/DelegationForm/index.tsx | 170 ++++++ .../FeeModal/components/CustomLabel.tsx | 39 ++ .../Staking/FeeModal/components/Label.tsx | 28 + .../FeeModal/components/WarningTooltip.tsx | 25 + .../components/Staking/FeeModal/index.tsx | 166 ++++++ .../FinalityProviderColumns.tsx | 91 ++++ .../FinalityProviderFilter.tsx | 23 + .../FinalityProviderLogo.tsx | 68 +++ .../FinalityProviderSearch.tsx | 43 ++ .../FinalityProviderTable.tsx | 88 ++++ .../FinalityProviderTableStatusView.tsx | 36 ++ .../FinalityProviders/FinalityProviders.tsx | 46 ++ .../FinalityProviders/components/FPInfo.tsx | 33 ++ .../Staking/Form/States/Message.tsx | 31 ++ .../Form/States/WalletNotConnected.tsx | 36 ++ .../Staking/Form/States/api-not-available.svg | 7 + .../Staking/Form/States/connect-icon.svg | 4 + .../Staking/Form/States/geo-restricted.svg | 6 + .../Form/States/staking-cap-reached.svg | 6 + .../Form/States/staking-unavailable.svg | 9 + .../Staking/Form/States/staking-upgrading.svg | 5 + .../Staking/Form/States/wallet-icon.svg | 105 ++++ .../Staking/Form/validation/validation.ts | 18 + .../common/components/Staking/StakingForm.tsx | 55 ++ .../components/Staking/StakingModal/index.tsx | 139 +++++ .../components/Stats/ActionComponent.tsx | 29 ++ src/ui/common/components/Stats/StatItem.tsx | 115 ++++ src/ui/common/components/Stats/Stats.tsx | 61 +++ .../common/components/Stats/icons/index.tsx | 77 +++ src/ui/common/components/Tabs/Tabs.tsx | 82 +++ src/ui/common/components/Tabs/index.ts | 1 + .../components/ThemeToggle/ThemeToggle.tsx | 28 + src/ui/common/components/Wallet/Connect.tsx | 190 +++++++ src/ui/common/config/index.ts | 62 +++ src/ui/common/config/network/bbn.ts | 76 +++ .../common/config/network/bbn/bsn-devnet.ts | 69 +++ src/ui/common/config/network/bbn/canary.ts | 69 +++ src/ui/common/config/network/bbn/devnet.ts | 69 +++ .../common/config/network/bbn/edge-devnet.ts | 69 +++ src/ui/common/config/network/bbn/mainnet.ts | 69 +++ src/ui/common/config/network/bbn/testnet.ts | 69 +++ src/ui/common/config/network/bbn/urlUtils.ts | 23 + src/ui/common/config/network/btc.ts | 84 +++ src/ui/common/config/network/index.ts | 17 + src/ui/common/config/screen-breakpoints.ts | 7 + src/ui/common/constants.ts | 59 +++ src/ui/common/constants/endpoints.ts | 21 + src/ui/common/constants/errorMessages.ts | 33 ++ src/ui/common/constants/index.ts | 1 + src/ui/common/context/Error/ErrorProvider.tsx | 100 ++++ .../context/Error/errors/clientError.ts | 59 +++ src/ui/common/context/Error/errors/index.ts | 2 + .../context/Error/errors/serverError.ts | 60 +++ .../context/api/StakingStatsProvider.tsx | 78 +++ src/ui/common/context/rpc/BbnRpcProvider.tsx | 83 +++ .../context/wallet/BTCWalletProvider.tsx | 315 +++++++++++ .../context/wallet/CosmosWalletProvider.tsx | 234 +++++++++ .../wallet/WalletConnectionProvider.tsx | 90 ++++ src/ui/common/errors/codes.ts | 29 ++ src/ui/common/errors/index.ts | 11 + src/ui/common/global-error.tsx | 19 + .../common/hooks/client/api/useBTCBalance.ts | 19 + src/ui/common/hooks/client/api/useBsn.ts | 15 + .../common/hooks/client/api/useDelegations.ts | 106 ++++ .../hooks/client/api/useDelegationsV2.ts | 110 ++++ .../hooks/client/api/useFinalityProviders.ts | 69 +++ .../client/api/useFinalityProvidersV2.ts | 86 +++ .../common/hooks/client/api/useNetworkFees.ts | 14 + .../common/hooks/client/api/useNetworkInfo.ts | 13 + src/ui/common/hooks/client/api/useOrdinals.ts | 77 +++ src/ui/common/hooks/client/api/usePrices.ts | 19 + .../common/hooks/client/api/useSystemStats.ts | 14 + src/ui/common/hooks/client/api/useUTXOs.ts | 28 + .../client/rpc/mutation/useBbnTransaction.ts | 91 ++++ .../rpc/mutation/useSigningStargateClient.ts | 174 +++++++ .../hooks/client/rpc/queries/useBbnQuery.ts | 175 +++++++ .../hooks/client/rpc/useRpcErrorHandler.ts | 35 ++ src/ui/common/hooks/client/useClient.ts | 90 ++++ .../hooks/services/useDelegationService.ts | 364 +++++++++++++ .../hooks/services/useRegistrationService.ts | 131 +++++ .../hooks/services/useRewardsService.ts | 134 +++++ .../services/useStakingManagerService.ts | 85 +++ .../hooks/services/useStakingService.ts | 210 ++++++++ .../hooks/services/useTransactionService.ts | 490 ++++++++++++++++++ .../hooks/services/useV1TransactionService.ts | 255 +++++++++ .../hooks/storage/useDelegationStorage.ts | 134 +++++ src/ui/common/hooks/useBreakpoint.ts | 37 ++ src/ui/common/hooks/useCurrentTime.tsx | 17 + src/ui/common/hooks/useDI.ts | 7 + src/ui/common/hooks/useEventBus.ts | 10 + src/ui/common/hooks/useFormError.ts | 32 ++ src/ui/common/hooks/useHealthCheck.ts | 60 +++ src/ui/common/hooks/useLogger.ts | 49 ++ src/ui/common/hooks/useNotification.tsx | 28 + src/ui/common/hooks/useSentryUser.ts | 15 + src/ui/common/layout.tsx | 35 ++ src/ui/common/not-found.tsx | 13 + src/ui/common/page.tsx | 76 +++ src/ui/common/providers.tsx | 49 ++ src/ui/common/services/healthCheckService.ts | 27 + src/ui/common/state/BalanceState.tsx | 142 +++++ src/ui/common/state/DelegationState.tsx | 198 +++++++ src/ui/common/state/DelegationV2State.tsx | 146 ++++++ .../common/state/FinalityProviderBsnState.tsx | 247 +++++++++ src/ui/common/state/FinalityProviderState.tsx | 239 +++++++++ src/ui/common/state/MultistakingState.tsx | 191 +++++++ src/ui/common/state/RewardState.tsx | 122 +++++ src/ui/common/state/StakingState.tsx | 433 ++++++++++++++++ src/ui/common/state/index.tsx | 167 ++++++ src/ui/common/types/api.ts | 9 + src/ui/common/types/api/healthCheck.ts | 3 + src/ui/common/types/bsn.ts | 6 + src/ui/common/types/delegations.ts | 54 ++ src/ui/common/types/delegationsV2.ts | 121 +++++ src/ui/common/types/errors.ts | 63 +++ src/ui/common/types/fee.ts | 12 + src/ui/common/types/finalityProviders.ts | 51 ++ src/ui/common/types/network.ts | 6 + src/ui/common/types/networkInfo.ts | 45 ++ src/ui/common/types/services/healthCheck.ts | 15 + src/ui/common/types/stakingParams.ts | 7 + src/ui/common/types/stakingStats.ts | 8 + src/ui/common/utils/FeatureFlagService.ts | 59 +++ src/ui/common/utils/bbn.ts | 19 + src/ui/common/utils/btc.ts | 19 + src/ui/common/utils/buffer.ts | 17 + src/ui/common/utils/chunkArray.ts | 14 + src/ui/common/utils/createStateUtils.ts | 12 + src/ui/common/utils/delegations/fee.ts | 40 ++ src/ui/common/utils/delegations/index.ts | 127 +++++ src/ui/common/utils/delegations/slashing.ts | 31 ++ src/ui/common/utils/fetch.ts | 52 ++ src/ui/common/utils/formTransforms.ts | 23 + src/ui/common/utils/formatBTCTvl.ts | 21 + src/ui/common/utils/formatCurrency.ts | 50 ++ src/ui/common/utils/getFeeRateFromMempool.ts | 22 + src/ui/common/utils/getPrices.ts | 26 + src/ui/common/utils/getStakingTerm.ts | 14 + src/ui/common/utils/getState.ts | 66 +++ src/ui/common/utils/index.ts | 24 + src/ui/common/utils/isStakingSignReady.ts | 61 +++ .../local_storage/calculateDelegationsDiff.ts | 34 ++ .../filterDelegationsLocalStorage.ts | 71 +++ .../getDelegationsLocalStorageKey.ts | 11 + ...tIntermediateDelegationsLocalStorageKey.ts | 6 + .../toLocalStorageIntermediateDelegation.ts | 27 + src/ui/common/utils/maxDecimals.ts | 24 + src/ui/common/utils/mempool_api.ts | 301 +++++++++++ src/ui/common/utils/nextPowerOfTwo.ts | 6 + src/ui/common/utils/params/index.ts | 51 ++ src/ui/common/utils/time.ts | 109 ++++ src/ui/common/utils/trim.ts | 24 + src/ui/common/utils/url.ts | 5 + src/ui/common/utils/version.ts | 3 + src/ui/common/utils/wallet/amino.ts | 137 +++++ src/ui/common/utils/wallet/bbnRegistry.ts | 52 ++ src/ui/common/utils/wallet/icons/bitget.svg | 33 ++ .../common/utils/wallet/icons/cactuslink.svg | 3 + src/ui/common/utils/wallet/icons/keystone.svg | 4 + src/ui/common/utils/wallet/icons/okx.svg | 13 + src/ui/common/utils/wallet/icons/onekey.svg | 12 + src/ui/common/utils/wallet/icons/tomo.svg | 23 + src/ui/common/utils/wallet/index.ts | 43 ++ .../PersonalBalance/PersonalBalance.tsx | 2 +- src/ui/legacy/components/Stats/StatItem.tsx | 2 +- src/ui/legacy/components/Stats/Stats.tsx | 2 +- src/ui/legacy/layout.tsx | 27 +- src/ui/router.tsx | 23 +- tailwind.config.ts | 3 +- 347 files changed, 21906 insertions(+), 46 deletions(-) create mode 100644 src/ui/common/api/apiWrapper.ts create mode 100644 src/ui/common/api/error/index.ts create mode 100644 src/ui/common/api/getBsn.ts create mode 100644 src/ui/common/api/getDelegations.ts create mode 100644 src/ui/common/api/getDelegationsV2.ts create mode 100644 src/ui/common/api/getFinalityProviders.ts create mode 100644 src/ui/common/api/getFinalityProvidersV2.ts create mode 100644 src/ui/common/api/getNetworkInfo.ts create mode 100644 src/ui/common/api/getStats.ts create mode 100644 src/ui/common/api/getSystemStats.ts create mode 100644 src/ui/common/api/getUnbondingEligibility.ts create mode 100644 src/ui/common/api/healthCheckClient.ts create mode 100644 src/ui/common/api/httpStatusCodes.ts create mode 100644 src/ui/common/api/logTermAcceptance.ts create mode 100644 src/ui/common/api/postFilterOrdinals.ts create mode 100644 src/ui/common/api/postUnbonding.ts create mode 100644 src/ui/common/api/verifyBTCAddress.ts create mode 100644 src/ui/common/assets/404-error-character.svg create mode 100644 src/ui/common/assets/babylon.svg create mode 100644 src/ui/common/assets/bbn.svg create mode 100644 src/ui/common/assets/bg-pattern.svg create mode 100644 src/ui/common/assets/bitcoin-block.svg create mode 100644 src/ui/common/assets/bitcoin.png create mode 100644 src/ui/common/assets/blue-check.svg create mode 100644 src/ui/common/assets/cancel-circle.svg create mode 100644 src/ui/common/assets/chains/babylon-genesis.png create mode 100644 src/ui/common/assets/chains/cosmos.png create mode 100644 src/ui/common/assets/chains/ethereum.png create mode 100644 src/ui/common/assets/chains/placeholder.svg create mode 100644 src/ui/common/assets/chains/sui.png create mode 100644 src/ui/common/assets/check.svg create mode 100644 src/ui/common/assets/cog.svg create mode 100644 src/ui/common/assets/edit.svg create mode 100644 src/ui/common/assets/generic-error-character.svg create mode 100644 src/ui/common/assets/gold-check.svg create mode 100644 src/ui/common/assets/icon-black.svg create mode 100644 src/ui/common/assets/icon-white.svg create mode 100644 src/ui/common/assets/signet_bitcoin.svg create mode 100644 src/ui/common/assets/testnet-bg-pattern.svg create mode 100644 src/ui/common/assets/warning-octagon.svg create mode 100644 src/ui/common/assets/warning-triangle.svg create mode 100644 src/ui/common/components/Activity/Activity.tsx create mode 100644 src/ui/common/components/Activity/components/ActivityList.tsx create mode 100644 src/ui/common/components/ActivityCard/ActivityCard.tsx create mode 100644 src/ui/common/components/ActivityCard/components/ActivityCardActionSection.tsx create mode 100644 src/ui/common/components/ActivityCard/components/ActivityCardAmountSection.tsx create mode 100644 src/ui/common/components/ActivityCard/components/ActivityCardDetailsSection.tsx create mode 100644 src/ui/common/components/ActivityCard/components/ActivityListItem.tsx create mode 100644 src/ui/common/components/ActivityCard/index.tsx create mode 100644 src/ui/common/components/Alerts/Alert.tsx create mode 100644 src/ui/common/components/Banner/Banner.tsx create mode 100644 src/ui/common/components/Common/AuthGuard/index.tsx create mode 100644 src/ui/common/components/Common/Body/index.tsx create mode 100644 src/ui/common/components/Common/GridTable/components/TCell.tsx create mode 100644 src/ui/common/components/Common/GridTable/components/THead.tsx create mode 100644 src/ui/common/components/Common/GridTable/index.tsx create mode 100644 src/ui/common/components/Common/GridTable/types.ts create mode 100644 src/ui/common/components/Common/GridTable/utils.ts create mode 100644 src/ui/common/components/Common/Hint/index.tsx create mode 100644 src/ui/common/components/Container/Container.tsx create mode 100644 src/ui/common/components/Delegations/Delegation.tsx create mode 100644 src/ui/common/components/Delegations/DelegationActions.tsx create mode 100644 src/ui/common/components/Delegations/DelegationList/NoDelegations.tsx create mode 100644 src/ui/common/components/Delegations/DelegationList/components/ActionButton.tsx create mode 100644 src/ui/common/components/Delegations/DelegationList/components/Amount.tsx create mode 100644 src/ui/common/components/Delegations/DelegationList/components/DelegationModal.tsx create mode 100644 src/ui/common/components/Delegations/DelegationList/components/FinalityProviderMoniker.tsx create mode 100644 src/ui/common/components/Delegations/DelegationList/components/Inception.tsx create mode 100644 src/ui/common/components/Delegations/DelegationList/components/Overflow.tsx create mode 100644 src/ui/common/components/Delegations/DelegationList/components/SlashingContent.tsx create mode 100644 src/ui/common/components/Delegations/DelegationList/components/Status.tsx create mode 100644 src/ui/common/components/Delegations/DelegationList/components/TxHash.tsx create mode 100644 src/ui/common/components/Delegations/DelegationList/index.tsx create mode 100644 src/ui/common/components/Delegations/DelegationList/utils.ts create mode 100644 src/ui/common/components/Delegations/Delegations.tsx create mode 100644 src/ui/common/components/Delegations/components/DelegationCell.tsx create mode 100644 src/ui/common/components/Error/GenericError.tsx create mode 100644 src/ui/common/components/FAQ/FAQ.tsx create mode 100644 src/ui/common/components/FAQ/Section.tsx create mode 100644 src/ui/common/components/FAQ/data/questions.tsx create mode 100644 src/ui/common/components/FAQ/index.ts create mode 100644 src/ui/common/components/Footer/Footer.tsx create mode 100644 src/ui/common/components/Hash/Hash.tsx create mode 100644 src/ui/common/components/Header/Header.tsx create mode 100644 src/ui/common/components/Header/SimplifiedHeader.tsx create mode 100644 src/ui/common/components/Icons/ThemedIcon.tsx create mode 100644 src/ui/common/components/Icons/common/CopyIcon.tsx create mode 100644 src/ui/common/components/Icons/index.ts create mode 100644 src/ui/common/components/Icons/wallet/BitcoinPublicKeyIcon.tsx create mode 100644 src/ui/common/components/Icons/wallet/LinkWalletIcon.tsx create mode 100644 src/ui/common/components/Icons/wallet/UsingInscriptionIcon.tsx create mode 100644 src/ui/common/components/Loading/Loading.tsx create mode 100644 src/ui/common/components/Logo/Icon.tsx create mode 100644 src/ui/common/components/Logo/Logo.tsx create mode 100644 src/ui/common/components/Logo/SmallLogo.tsx create mode 100644 src/ui/common/components/Menu/SettingMenu/components/SettingMenuButton.tsx create mode 100644 src/ui/common/components/Menu/SettingMenu/components/SettingMenuContainer.tsx create mode 100644 src/ui/common/components/Menu/SettingMenu/components/SettingMenuContent.tsx create mode 100644 src/ui/common/components/Menu/SettingMenu/index.ts create mode 100644 src/ui/common/components/Menu/WalletMenu/components/Toggle.tsx create mode 100644 src/ui/common/components/Menu/WalletMenu/components/WalletInfoSection.tsx create mode 100644 src/ui/common/components/Menu/WalletMenu/components/WalletMenuContainer.tsx create mode 100644 src/ui/common/components/Menu/WalletMenu/components/WalletSettingsSection.tsx create mode 100644 src/ui/common/components/Menu/WalletMenu/index.ts create mode 100644 src/ui/common/components/Menu/index.ts create mode 100644 src/ui/common/components/Modals/CancelFeedbackModal.tsx create mode 100644 src/ui/common/components/Modals/ClaimRewardModal.tsx create mode 100644 src/ui/common/components/Modals/ClaimStatusModal/ClaimStatusModal.tsx create mode 100644 src/ui/common/components/Modals/ClaimStatusModal/SuccessContent.tsx create mode 100644 src/ui/common/components/Modals/ConfirmationModal.tsx create mode 100644 src/ui/common/components/Modals/ErrorModal.tsx create mode 100644 src/ui/common/components/Modals/InfoModal.tsx create mode 100644 src/ui/common/components/Modals/MultistakingModal/MultistakingStartModal.tsx create mode 100644 src/ui/common/components/Modals/PreviewModal.tsx create mode 100644 src/ui/common/components/Modals/PreviewMultistakingModal.tsx create mode 100644 src/ui/common/components/Modals/RegistrationModal/RegistrationEndModal.tsx create mode 100644 src/ui/common/components/Modals/RegistrationModal/RegistrationStartModal.tsx create mode 100644 src/ui/common/components/Modals/ResponsiveDialog.tsx create mode 100644 src/ui/common/components/Modals/SignDetailsModal.tsx create mode 100644 src/ui/common/components/Modals/SignModal/SignModal.tsx create mode 100644 src/ui/common/components/Modals/SignModal/Step.tsx create mode 100644 src/ui/common/components/Modals/SlashingModal.tsx create mode 100644 src/ui/common/components/Modals/StakeModal.tsx create mode 100644 src/ui/common/components/Modals/SubmitModal.tsx create mode 100644 src/ui/common/components/Modals/SuccessFeedbackModal.tsx create mode 100644 src/ui/common/components/Modals/UnbondModal.tsx create mode 100644 src/ui/common/components/Modals/VerificationModal.tsx create mode 100644 src/ui/common/components/Modals/WalletDisconnectModal.tsx create mode 100644 src/ui/common/components/Modals/WithdrawModal.tsx create mode 100644 src/ui/common/components/Multistaking/BsnFinalityProviderField/BsnFinalityProviderField.tsx create mode 100644 src/ui/common/components/Multistaking/BsnFinalityProviderField/BsnFinalityProviderItem.tsx create mode 100644 src/ui/common/components/Multistaking/BsnFinalityProviderField/SelectedProvidersList.tsx create mode 100644 src/ui/common/components/Multistaking/ChainSelectionModal/ChainSelectionModal.tsx create mode 100644 src/ui/common/components/Multistaking/CounterButton.tsx create mode 100644 src/ui/common/components/Multistaking/FinalityProviderField/FinalityProviderModal.tsx create mode 100644 src/ui/common/components/Multistaking/FinalityProviderField/FinalityProviders.tsx create mode 100644 src/ui/common/components/Multistaking/FinalityProviders/FinalityProviderFilter.tsx create mode 100644 src/ui/common/components/Multistaking/FinalityProviders/FinalityProviderSearch.tsx create mode 100644 src/ui/common/components/Multistaking/FinalityProviders/FinalityProviderTable.tsx create mode 100644 src/ui/common/components/Multistaking/MultistakingForm/AmountBalanceInfo.tsx create mode 100644 src/ui/common/components/Multistaking/MultistakingForm/AmountSubsection.tsx create mode 100644 src/ui/common/components/Multistaking/MultistakingForm/BTCFeeRate.tsx create mode 100644 src/ui/common/components/Multistaking/MultistakingForm/ConnectButton.tsx create mode 100644 src/ui/common/components/Multistaking/MultistakingForm/FeesSection.tsx create mode 100644 src/ui/common/components/Multistaking/MultistakingForm/FormAlert.tsx create mode 100644 src/ui/common/components/Multistaking/MultistakingForm/MultistakingForm.tsx create mode 100644 src/ui/common/components/Multistaking/MultistakingForm/MultistakingFormWrapper.tsx create mode 100644 src/ui/common/components/Multistaking/MultistakingForm/SubSection.tsx create mode 100644 src/ui/common/components/Multistaking/MultistakingForm/SubmitButton.tsx create mode 100644 src/ui/common/components/Multistaking/MultistakingModal/MultistakingModal.tsx create mode 100644 src/ui/common/components/NetworkBadge/NetworkBadge.tsx create mode 100644 src/ui/common/components/NetworkBadge/testnet-icon.png create mode 100644 src/ui/common/components/Notification/DetailsButton.tsx create mode 100644 src/ui/common/components/Notification/FloatingTopBar.tsx create mode 100644 src/ui/common/components/Notification/IconWrapper.tsx create mode 100644 src/ui/common/components/Notification/Notification.tsx create mode 100644 src/ui/common/components/Notification/NotificationContainer.tsx create mode 100644 src/ui/common/components/Notification/NotificationText.tsx create mode 100644 src/ui/common/components/Notification/NotificationTitle.tsx create mode 100644 src/ui/common/components/PersonalBalance/PersonalBalance.tsx create mode 100644 src/ui/common/components/Section/Section.tsx create mode 100644 src/ui/common/components/SignDetails/SignDetails.tsx create mode 100644 src/ui/common/components/Stakers/Staker.tsx create mode 100644 src/ui/common/components/Staking/DelegationForm/components/AmountField.tsx create mode 100644 src/ui/common/components/Staking/DelegationForm/components/BBNFeeAmount.tsx create mode 100644 src/ui/common/components/Staking/DelegationForm/components/BTCFeeAmount.tsx create mode 100644 src/ui/common/components/Staking/DelegationForm/components/BTCFeeRate.tsx create mode 100644 src/ui/common/components/Staking/DelegationForm/components/FeeItem.tsx create mode 100644 src/ui/common/components/Staking/DelegationForm/components/FeeSection.tsx create mode 100644 src/ui/common/components/Staking/DelegationForm/components/InfoAlert.tsx create mode 100644 src/ui/common/components/Staking/DelegationForm/components/Overlay.tsx create mode 100644 src/ui/common/components/Staking/DelegationForm/components/SubmitButton.tsx create mode 100644 src/ui/common/components/Staking/DelegationForm/components/TermField.tsx create mode 100644 src/ui/common/components/Staking/DelegationForm/components/Total.tsx create mode 100644 src/ui/common/components/Staking/DelegationForm/index.tsx create mode 100644 src/ui/common/components/Staking/FeeModal/components/CustomLabel.tsx create mode 100644 src/ui/common/components/Staking/FeeModal/components/Label.tsx create mode 100644 src/ui/common/components/Staking/FeeModal/components/WarningTooltip.tsx create mode 100644 src/ui/common/components/Staking/FeeModal/index.tsx create mode 100644 src/ui/common/components/Staking/FinalityProviders/FinalityProviderColumns.tsx create mode 100644 src/ui/common/components/Staking/FinalityProviders/FinalityProviderFilter.tsx create mode 100644 src/ui/common/components/Staking/FinalityProviders/FinalityProviderLogo.tsx create mode 100644 src/ui/common/components/Staking/FinalityProviders/FinalityProviderSearch.tsx create mode 100644 src/ui/common/components/Staking/FinalityProviders/FinalityProviderTable.tsx create mode 100644 src/ui/common/components/Staking/FinalityProviders/FinalityProviderTableStatusView.tsx create mode 100644 src/ui/common/components/Staking/FinalityProviders/FinalityProviders.tsx create mode 100644 src/ui/common/components/Staking/FinalityProviders/components/FPInfo.tsx create mode 100644 src/ui/common/components/Staking/Form/States/Message.tsx create mode 100644 src/ui/common/components/Staking/Form/States/WalletNotConnected.tsx create mode 100644 src/ui/common/components/Staking/Form/States/api-not-available.svg create mode 100644 src/ui/common/components/Staking/Form/States/connect-icon.svg create mode 100644 src/ui/common/components/Staking/Form/States/geo-restricted.svg create mode 100644 src/ui/common/components/Staking/Form/States/staking-cap-reached.svg create mode 100644 src/ui/common/components/Staking/Form/States/staking-unavailable.svg create mode 100644 src/ui/common/components/Staking/Form/States/staking-upgrading.svg create mode 100644 src/ui/common/components/Staking/Form/States/wallet-icon.svg create mode 100644 src/ui/common/components/Staking/Form/validation/validation.ts create mode 100644 src/ui/common/components/Staking/StakingForm.tsx create mode 100644 src/ui/common/components/Staking/StakingModal/index.tsx create mode 100644 src/ui/common/components/Stats/ActionComponent.tsx create mode 100644 src/ui/common/components/Stats/StatItem.tsx create mode 100644 src/ui/common/components/Stats/Stats.tsx create mode 100644 src/ui/common/components/Stats/icons/index.tsx create mode 100644 src/ui/common/components/Tabs/Tabs.tsx create mode 100644 src/ui/common/components/Tabs/index.ts create mode 100644 src/ui/common/components/ThemeToggle/ThemeToggle.tsx create mode 100644 src/ui/common/components/Wallet/Connect.tsx create mode 100644 src/ui/common/config/index.ts create mode 100644 src/ui/common/config/network/bbn.ts create mode 100644 src/ui/common/config/network/bbn/bsn-devnet.ts create mode 100644 src/ui/common/config/network/bbn/canary.ts create mode 100644 src/ui/common/config/network/bbn/devnet.ts create mode 100644 src/ui/common/config/network/bbn/edge-devnet.ts create mode 100644 src/ui/common/config/network/bbn/mainnet.ts create mode 100644 src/ui/common/config/network/bbn/testnet.ts create mode 100644 src/ui/common/config/network/bbn/urlUtils.ts create mode 100644 src/ui/common/config/network/btc.ts create mode 100644 src/ui/common/config/network/index.ts create mode 100644 src/ui/common/config/screen-breakpoints.ts create mode 100644 src/ui/common/constants.ts create mode 100644 src/ui/common/constants/endpoints.ts create mode 100644 src/ui/common/constants/errorMessages.ts create mode 100644 src/ui/common/constants/index.ts create mode 100644 src/ui/common/context/Error/ErrorProvider.tsx create mode 100644 src/ui/common/context/Error/errors/clientError.ts create mode 100644 src/ui/common/context/Error/errors/index.ts create mode 100644 src/ui/common/context/Error/errors/serverError.ts create mode 100644 src/ui/common/context/api/StakingStatsProvider.tsx create mode 100644 src/ui/common/context/rpc/BbnRpcProvider.tsx create mode 100644 src/ui/common/context/wallet/BTCWalletProvider.tsx create mode 100644 src/ui/common/context/wallet/CosmosWalletProvider.tsx create mode 100644 src/ui/common/context/wallet/WalletConnectionProvider.tsx create mode 100644 src/ui/common/errors/codes.ts create mode 100644 src/ui/common/errors/index.ts create mode 100644 src/ui/common/global-error.tsx create mode 100644 src/ui/common/hooks/client/api/useBTCBalance.ts create mode 100644 src/ui/common/hooks/client/api/useBsn.ts create mode 100644 src/ui/common/hooks/client/api/useDelegations.ts create mode 100644 src/ui/common/hooks/client/api/useDelegationsV2.ts create mode 100644 src/ui/common/hooks/client/api/useFinalityProviders.ts create mode 100644 src/ui/common/hooks/client/api/useFinalityProvidersV2.ts create mode 100644 src/ui/common/hooks/client/api/useNetworkFees.ts create mode 100644 src/ui/common/hooks/client/api/useNetworkInfo.ts create mode 100644 src/ui/common/hooks/client/api/useOrdinals.ts create mode 100644 src/ui/common/hooks/client/api/usePrices.ts create mode 100644 src/ui/common/hooks/client/api/useSystemStats.ts create mode 100644 src/ui/common/hooks/client/api/useUTXOs.ts create mode 100644 src/ui/common/hooks/client/rpc/mutation/useBbnTransaction.ts create mode 100644 src/ui/common/hooks/client/rpc/mutation/useSigningStargateClient.ts create mode 100644 src/ui/common/hooks/client/rpc/queries/useBbnQuery.ts create mode 100644 src/ui/common/hooks/client/rpc/useRpcErrorHandler.ts create mode 100644 src/ui/common/hooks/client/useClient.ts create mode 100644 src/ui/common/hooks/services/useDelegationService.ts create mode 100644 src/ui/common/hooks/services/useRegistrationService.ts create mode 100644 src/ui/common/hooks/services/useRewardsService.ts create mode 100644 src/ui/common/hooks/services/useStakingManagerService.ts create mode 100644 src/ui/common/hooks/services/useStakingService.ts create mode 100644 src/ui/common/hooks/services/useTransactionService.ts create mode 100644 src/ui/common/hooks/services/useV1TransactionService.ts create mode 100644 src/ui/common/hooks/storage/useDelegationStorage.ts create mode 100644 src/ui/common/hooks/useBreakpoint.ts create mode 100644 src/ui/common/hooks/useCurrentTime.tsx create mode 100644 src/ui/common/hooks/useDI.ts create mode 100644 src/ui/common/hooks/useEventBus.ts create mode 100644 src/ui/common/hooks/useFormError.ts create mode 100644 src/ui/common/hooks/useHealthCheck.ts create mode 100644 src/ui/common/hooks/useLogger.ts create mode 100644 src/ui/common/hooks/useNotification.tsx create mode 100644 src/ui/common/hooks/useSentryUser.ts create mode 100644 src/ui/common/layout.tsx create mode 100644 src/ui/common/not-found.tsx create mode 100644 src/ui/common/page.tsx create mode 100644 src/ui/common/providers.tsx create mode 100644 src/ui/common/services/healthCheckService.ts create mode 100644 src/ui/common/state/BalanceState.tsx create mode 100644 src/ui/common/state/DelegationState.tsx create mode 100644 src/ui/common/state/DelegationV2State.tsx create mode 100644 src/ui/common/state/FinalityProviderBsnState.tsx create mode 100644 src/ui/common/state/FinalityProviderState.tsx create mode 100644 src/ui/common/state/MultistakingState.tsx create mode 100644 src/ui/common/state/RewardState.tsx create mode 100644 src/ui/common/state/StakingState.tsx create mode 100644 src/ui/common/state/index.tsx create mode 100644 src/ui/common/types/api.ts create mode 100644 src/ui/common/types/api/healthCheck.ts create mode 100644 src/ui/common/types/bsn.ts create mode 100644 src/ui/common/types/delegations.ts create mode 100644 src/ui/common/types/delegationsV2.ts create mode 100644 src/ui/common/types/errors.ts create mode 100644 src/ui/common/types/fee.ts create mode 100644 src/ui/common/types/finalityProviders.ts create mode 100644 src/ui/common/types/network.ts create mode 100644 src/ui/common/types/networkInfo.ts create mode 100644 src/ui/common/types/services/healthCheck.ts create mode 100644 src/ui/common/types/stakingParams.ts create mode 100644 src/ui/common/types/stakingStats.ts create mode 100644 src/ui/common/utils/FeatureFlagService.ts create mode 100644 src/ui/common/utils/bbn.ts create mode 100644 src/ui/common/utils/btc.ts create mode 100644 src/ui/common/utils/buffer.ts create mode 100644 src/ui/common/utils/chunkArray.ts create mode 100644 src/ui/common/utils/createStateUtils.ts create mode 100644 src/ui/common/utils/delegations/fee.ts create mode 100644 src/ui/common/utils/delegations/index.ts create mode 100644 src/ui/common/utils/delegations/slashing.ts create mode 100644 src/ui/common/utils/fetch.ts create mode 100644 src/ui/common/utils/formTransforms.ts create mode 100644 src/ui/common/utils/formatBTCTvl.ts create mode 100644 src/ui/common/utils/formatCurrency.ts create mode 100644 src/ui/common/utils/getFeeRateFromMempool.ts create mode 100644 src/ui/common/utils/getPrices.ts create mode 100644 src/ui/common/utils/getStakingTerm.ts create mode 100644 src/ui/common/utils/getState.ts create mode 100644 src/ui/common/utils/index.ts create mode 100644 src/ui/common/utils/isStakingSignReady.ts create mode 100644 src/ui/common/utils/local_storage/calculateDelegationsDiff.ts create mode 100644 src/ui/common/utils/local_storage/filterDelegationsLocalStorage.ts create mode 100644 src/ui/common/utils/local_storage/getDelegationsLocalStorageKey.ts create mode 100644 src/ui/common/utils/local_storage/getIntermediateDelegationsLocalStorageKey.ts create mode 100644 src/ui/common/utils/local_storage/toLocalStorageIntermediateDelegation.ts create mode 100644 src/ui/common/utils/maxDecimals.ts create mode 100644 src/ui/common/utils/mempool_api.ts create mode 100644 src/ui/common/utils/nextPowerOfTwo.ts create mode 100644 src/ui/common/utils/params/index.ts create mode 100644 src/ui/common/utils/time.ts create mode 100644 src/ui/common/utils/trim.ts create mode 100644 src/ui/common/utils/url.ts create mode 100644 src/ui/common/utils/version.ts create mode 100644 src/ui/common/utils/wallet/amino.ts create mode 100644 src/ui/common/utils/wallet/bbnRegistry.ts create mode 100644 src/ui/common/utils/wallet/icons/bitget.svg create mode 100644 src/ui/common/utils/wallet/icons/cactuslink.svg create mode 100644 src/ui/common/utils/wallet/icons/keystone.svg create mode 100644 src/ui/common/utils/wallet/icons/okx.svg create mode 100644 src/ui/common/utils/wallet/icons/onekey.svg create mode 100644 src/ui/common/utils/wallet/icons/tomo.svg create mode 100644 src/ui/common/utils/wallet/index.ts diff --git a/e2e/fixtures/wallet_balance.selectors.ts b/e2e/fixtures/wallet_balance.selectors.ts index 602bef901..da17f4ec6 100644 --- a/e2e/fixtures/wallet_balance.selectors.ts +++ b/e2e/fixtures/wallet_balance.selectors.ts @@ -3,12 +3,12 @@ export const SPINNER_SELECTOR = '[data-testid="staked-balance"] .bbn-loader, [data-testid="stakable-balance"] .bbn-loader, [data-testid="baby-balance"] .bbn-loader, [data-testid="baby-rewards"] .bbn-loader'; export const STAKED_BALANCE_ITEM_SELECTOR = - '.bbn-list-item:has-text("Staked Balance")'; + '.bbn-list-item-legacy:has-text("Staked Balance")'; export const STAKED_BALANCE_VALUE_SELECTOR = - '.bbn-list-item:has-text("Staked Balance") .bbn-list-value'; + '.bbn-list-item-legacy:has-text("Staked Balance") .bbn-list-value-legacy'; export const STAKABLE_BALANCE_VALUE_SELECTOR = - '.bbn-list-item:has-text("Stakable Balance") .bbn-list-value'; + '.bbn-list-item-legacy:has-text("Stakable Balance") .bbn-list-value-legacy'; export const BABY_BALANCE_VALUE_SELECTOR = - '.bbn-list-item:has-text("BABY Balance") .bbn-list-value'; + '.bbn-list-item-legacy:has-text("BABY Balance") .bbn-list-value-legacy'; export const BABY_REWARDS_VALUE_SELECTOR = - '.bbn-list-item:has-text("BABY Rewards") .bbn-list-value'; + '.bbn-list-item-legacy:has-text("BABY Rewards") .bbn-list-value-legacy'; diff --git a/package-lock.json b/package-lock.json index 352e867ef..f1553311a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,8 @@ "dependencies": { "@babylonlabs-io/babylon-proto-ts": "1.1.0", "@babylonlabs-io/btc-staking-ts": "2.3.4", - "@babylonlabs-io/core-ui": "1.1.1", - "@babylonlabs-io/wallet-connector": "1.7.9", + "@babylonlabs-io/core-ui": "1.4.1", + "@babylonlabs-io/wallet-connector": "1.8.1", "@bitcoin-js/tiny-secp256k1-asmjs": "2.2.3", "@cosmjs/proto-signing": "^0.32.4", "@cosmjs/stargate": "^0.32.4", @@ -2217,9 +2217,9 @@ } }, "node_modules/@babylonlabs-io/core-ui": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@babylonlabs-io/core-ui/-/core-ui-1.1.1.tgz", - "integrity": "sha512-a7ZdVkfr/zlF2LNJgbUGjFgxEhLBA5psokTiOijNOA12pUmtWRQnis08pdmimJ7s/jNNewnJuOY2GzALyZEvVA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@babylonlabs-io/core-ui/-/core-ui-1.4.1.tgz", + "integrity": "sha512-z1Y5c45JB7P1y1SZEZGH9T3TG2lfYHeJPMuD9qoI4hb6fNRtX6R0JQ/wcXCidP98ngKQcULQgc/P3rEil/5nPQ==", "dependencies": { "@hookform/resolvers": "^3.9.1", "@popperjs/core": "^2.11.8", @@ -2235,9 +2235,9 @@ } }, "node_modules/@babylonlabs-io/wallet-connector": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/@babylonlabs-io/wallet-connector/-/wallet-connector-1.7.9.tgz", - "integrity": "sha512-dyuLegL8wd66GSNaMGo0oQqFxqW2/RCRRwRYtDvjTmslIaX1Zt+iEEvUGMNJonjmQJpEL0ghHfgX48ZwYCM2IA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@babylonlabs-io/wallet-connector/-/wallet-connector-1.8.1.tgz", + "integrity": "sha512-9xYy2Ba4p9YDfX73e7ETQy3nOAURczSxgLI4IeyOzphOtzjZHgv84VoeHVsR8bA811kpUlVhOiqOJSOW/D6IrA==", "dependencies": { "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", "@cosmjs/stargate": "^0.32.4", @@ -2257,7 +2257,7 @@ "uuid": "^11.1.0" }, "peerDependencies": { - "@babylonlabs-io/core-ui": "1.1.1", + "@babylonlabs-io/core-ui": "1.4.1", "react": "^18.3.1", "react-dom": "^18.3.1", "tailwind-merge": "^2.5.4" diff --git a/package.json b/package.json index e25b28d96..4e3875ca8 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,8 @@ "dependencies": { "@babylonlabs-io/babylon-proto-ts": "1.1.0", "@babylonlabs-io/btc-staking-ts": "2.3.4", - "@babylonlabs-io/core-ui": "1.1.1", - "@babylonlabs-io/wallet-connector": "1.7.9", + "@babylonlabs-io/core-ui": "1.4.1", + "@babylonlabs-io/wallet-connector": "1.8.1", "@bitcoin-js/tiny-secp256k1-asmjs": "2.2.3", "@cosmjs/proto-signing": "^0.32.4", "@cosmjs/stargate": "^0.32.4", diff --git a/src/main.tsx b/src/main.tsx index 4aa0c2133..b9de02b34 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,8 +3,7 @@ import { createRoot } from "react-dom/client"; import { ErrorBoundary } from "react-error-boundary"; import { BrowserRouter } from "react-router"; -import GlobalError from "@/ui/legacy/global-error"; -import Providers from "@/ui/legacy/providers"; +import GlobalError from "@/ui/common/global-error"; import { Router } from "@/ui/router"; import "@/ui/globals.css"; @@ -13,11 +12,9 @@ import "../sentry.client.config"; createRoot(document.getElementById("root")!).render( - - - - - + + + , ); diff --git a/src/ui/common/api/apiWrapper.ts b/src/ui/common/api/apiWrapper.ts new file mode 100644 index 000000000..a1712c93e --- /dev/null +++ b/src/ui/common/api/apiWrapper.ts @@ -0,0 +1,103 @@ +import { stringify } from "qs"; + +import { ClientError, ERROR_CODES } from "@/ui/common/errors"; + +import { getApiBaseUrl } from "../config"; + +type QueryParamValue = + | string + | number + | boolean + | Array; + +interface QueryParams { + [key: string]: QueryParamValue | undefined; +} + +interface RequestParams { + query?: QueryParams; + body?: any; +} + +export const apiWrapper = async ( + method: "GET" | "POST", + path: string, + generalErrorMessage: string, + params?: RequestParams, + timeout?: number, +): Promise<{ + data: TResponseData; + status: number; +}> => { + const queryString = params?.query + ? `?${stringify(params.query, { arrayFormat: "repeat" })}` + : ""; + + const baseUrl = getApiBaseUrl(); + const url = `${baseUrl}${path}${queryString}`; + + const options: RequestInit = { + method, + headers: + method === "POST" + ? { + "Content-Type": "application/json", + } + : {}, + }; + + if (params?.body) { + options.body = JSON.stringify(params.body); + } + + if (timeout) { + options.signal = AbortSignal.timeout(timeout); + } + + try { + const response = await fetch(url, options); + + if (!response.ok) { + const responseText = await response + .text() + .catch(() => generalErrorMessage); + const cause = new Error(responseText); + (cause as any).status = response.status; + const clientError = new ClientError( + ERROR_CODES.EXTERNAL_SERVICE_UNAVAILABLE, + "Error fetching data from the API", + { + cause, + }, + ); + throw clientError; + } + + const data = await response.json(); + return { data, status: response.status }; + } catch (error) { + if (error instanceof ClientError) { + throw error; + } + + if (error instanceof TypeError && error.message.includes("NetworkError")) { + const clientError = new ClientError( + ERROR_CODES.CONNECTION_ERROR, + "Network error occurred", + { + cause: error, + }, + ); + throw clientError; + } + + const clientError = new ClientError( + ERROR_CODES.EXTERNAL_SERVICE_UNAVAILABLE, + error instanceof Error ? error.message : generalErrorMessage, + { + cause: error as Error, + }, + ); + throw clientError; + } +}; diff --git a/src/ui/common/api/error/index.ts b/src/ui/common/api/error/index.ts new file mode 100644 index 000000000..db7938020 --- /dev/null +++ b/src/ui/common/api/error/index.ts @@ -0,0 +1,13 @@ +/** + * Checks if an error is a 451 HTTP status code error (Unavailable For Legal Reasons) + * @param error - The error to check + * @returns boolean indicating if the error has a 451 status code + */ +export const isError451 = (error: any): boolean => { + return ( + error && + (error.status === 451 || + (error.response && error.response.status === 451) || + (error.request && error.request.status === 451)) + ); +}; diff --git a/src/ui/common/api/getBsn.ts b/src/ui/common/api/getBsn.ts new file mode 100644 index 000000000..c85741778 --- /dev/null +++ b/src/ui/common/api/getBsn.ts @@ -0,0 +1,32 @@ +import { Bsn } from "../types/bsn"; + +import { apiWrapper } from "./apiWrapper"; + +interface BsnAPI { + id: string; + name: string; + description: string; + active_tvl: number; +} + +interface BsnDataResponse { + data: BsnAPI[]; +} + +const createBSN = (bsn: BsnAPI): Bsn => ({ + id: bsn.id, + name: bsn.name, + description: bsn.description, + activeTvl: bsn.active_tvl, +}); + +export const getBSNs = async (): Promise => { + const response = await apiWrapper( + "GET", + "/v2/bsn", + "Error getting BSN list", + ); + + const { data } = response.data; + return data.map(createBSN); +}; diff --git a/src/ui/common/api/getDelegations.ts b/src/ui/common/api/getDelegations.ts new file mode 100644 index 000000000..31040ad33 --- /dev/null +++ b/src/ui/common/api/getDelegations.ts @@ -0,0 +1,91 @@ +import { Pagination } from "../types/api"; +import { Delegation } from "../types/delegations"; + +import { apiWrapper } from "./apiWrapper"; + +export interface PaginatedDelegations { + delegations: Delegation[]; + pagination: Pagination; +} + +interface DelegationsAPIResponse { + data: DelegationAPI[]; + pagination: Pagination; +} + +interface DelegationAPI { + staking_tx_hash_hex: string; + staker_pk_hex: string; + finality_provider_pk_hex: string; + state: string; + staking_value: number; + staking_tx: StakingTxAPI; + unbonding_tx?: UnbondingTxAPI; + is_overflow: boolean; + is_eligible_for_transition: boolean; +} + +interface StakingTxAPI { + tx_hex: string; + output_index: number; + start_timestamp: string; + start_height: number; + timelock: number; +} + +interface UnbondingTxAPI { + tx_hex: string; + output_index: number; +} + +export const getDelegations = async ( + key: string, + publicKeyNoCoord: string, +): Promise => { + const params = { + pagination_key: key, + staker_btc_pk: publicKeyNoCoord, + // We only fetch for states that can have pending actions. + // We don't care terminal states such as "withdrawn" or "transitioned". + pending_action: true, + }; + + const response = await apiWrapper( + "GET", + "/v1/staker/delegations", + "Error getting delegations", + { query: params }, + ); + + const delegationsAPIResponse: DelegationsAPIResponse = response.data; + + const delegations: Delegation[] = delegationsAPIResponse.data.map( + (apiDelegation: DelegationAPI): Delegation => ({ + stakingTxHashHex: apiDelegation.staking_tx_hash_hex, + stakerPkHex: apiDelegation.staker_pk_hex, + finalityProviderPkHex: apiDelegation.finality_provider_pk_hex, + state: apiDelegation.state, + stakingValueSat: apiDelegation.staking_value, + stakingTx: { + txHex: apiDelegation.staking_tx.tx_hex, + outputIndex: apiDelegation.staking_tx.output_index, + startTimestamp: apiDelegation.staking_tx.start_timestamp, + startHeight: apiDelegation.staking_tx.start_height, + timelock: apiDelegation.staking_tx.timelock, + }, + isOverflow: apiDelegation.is_overflow, + unbondingTx: apiDelegation.unbonding_tx + ? { + txHex: apiDelegation.unbonding_tx.tx_hex, + outputIndex: apiDelegation.unbonding_tx.output_index, + } + : undefined, + isEligibleForTransition: apiDelegation.is_eligible_for_transition, + }), + ); + + const pagination: Pagination = { + next_key: delegationsAPIResponse.pagination.next_key, + }; + return { delegations: delegations, pagination }; +}; diff --git a/src/ui/common/api/getDelegationsV2.ts b/src/ui/common/api/getDelegationsV2.ts new file mode 100644 index 000000000..5b92b0c38 --- /dev/null +++ b/src/ui/common/api/getDelegationsV2.ts @@ -0,0 +1,134 @@ +import { Pagination } from "../types/api"; +import { + DelegationV2, + getDelegationV2StakingState, +} from "../types/delegationsV2"; + +import { apiWrapper } from "./apiWrapper"; + +export interface PaginatedDelegations { + delegations: DelegationV2[]; + pagination: Pagination; +} + +interface DelegationV2API { + finality_provider_btc_pks_hex: string[]; + params_version: number; + staker_btc_pk_hex: string; + delegation_staking: { + staking_tx_hex: string; + staking_tx_hash_hex: string; + staking_timelock: number; + staking_amount: number; + start_height: number; + end_height: number; + bbn_inception_height: number; + bbn_inception_time: string; + slashing: { + slashing_tx_hex: string; + spending_height: number; + }; + }; + delegation_unbonding: { + unbonding_timelock: number; + unbonding_tx: string; + covenant_unbonding_signatures?: { + covenant_btc_pk_hex: string; + signature_hex: string; + }[]; + slashing: { + unbonding_slashing_tx_hex: string; + spending_height: number; + }; + }; + state: string; +} + +export const getDelegationV2 = async ( + stakingTxHashHex: string, +): Promise => { + try { + const params = { + staking_tx_hash_hex: stakingTxHashHex, + }; + + const { data: delegationAPIResponse } = await apiWrapper<{ + data: DelegationV2API; + }>("GET", "/v2/delegation", "Error getting delegation v2", { + query: params, + }); + + return apiToDelegationV2(delegationAPIResponse.data); + } catch { + return null; + } +}; + +interface DelegationProps { + stakerPublicKey: string; + babylonAddress?: string; + pageKey?: string; +} + +export const getDelegationsV2 = async ({ + stakerPublicKey, + babylonAddress, + pageKey, +}: DelegationProps): Promise => { + const params = { + staker_pk_hex: stakerPublicKey, + babylon_address: babylonAddress, + pagination_key: pageKey ? pageKey : "", + }; + + const { data: delegationsAPIResponse } = await apiWrapper<{ + data: DelegationV2API[]; + pagination: Pagination; + }>("GET", "/v2/delegations", "Error getting delegations v2", { + query: params, + }); + + const pagination: Pagination = { + next_key: delegationsAPIResponse.pagination.next_key, + }; + return { + delegations: delegationsAPIResponse.data.map(apiToDelegationV2), + pagination, + }; +}; + +const apiToDelegationV2 = (apiDelegation: DelegationV2API): DelegationV2 => { + const state = getDelegationV2StakingState(apiDelegation.state); + + return { + finalityProviderBtcPksHex: apiDelegation.finality_provider_btc_pks_hex, + stakingTxHex: apiDelegation.delegation_staking.staking_tx_hex, + paramsVersion: apiDelegation.params_version, + stakerBtcPkHex: apiDelegation.staker_btc_pk_hex, + stakingAmount: apiDelegation.delegation_staking.staking_amount, + stakingTimelock: apiDelegation.delegation_staking.staking_timelock, + stakingTxHashHex: apiDelegation.delegation_staking.staking_tx_hash_hex, + startHeight: apiDelegation.delegation_staking.start_height, + endHeight: apiDelegation.delegation_staking.end_height, + bbnInceptionHeight: apiDelegation.delegation_staking.bbn_inception_height, + bbnInceptionTime: apiDelegation.delegation_staking.bbn_inception_time, + state, + unbondingTimelock: apiDelegation.delegation_unbonding.unbonding_timelock, + unbondingTxHex: apiDelegation.delegation_unbonding.unbonding_tx, + slashing: { + stakingSlashingTxHex: + apiDelegation.delegation_staking.slashing.slashing_tx_hex, + unbondingSlashingTxHex: + apiDelegation.delegation_unbonding.slashing.unbonding_slashing_tx_hex, + spendingHeight: + apiDelegation.delegation_unbonding.slashing.spending_height, + }, + covenantUnbondingSignatures: + apiDelegation.delegation_unbonding.covenant_unbonding_signatures?.map( + (signature) => ({ + covenantBtcPkHex: signature.covenant_btc_pk_hex, + signatureHex: signature.signature_hex, + }), + ), + }; +}; diff --git a/src/ui/common/api/getFinalityProviders.ts b/src/ui/common/api/getFinalityProviders.ts new file mode 100644 index 000000000..9fad8221e --- /dev/null +++ b/src/ui/common/api/getFinalityProviders.ts @@ -0,0 +1,97 @@ +import { API_ENDPOINTS } from "@/ui/common/constants/endpoints"; +import { isValidUrl } from "@/ui/common/utils/url"; + +import { Pagination } from "../types/api"; +import { FinalityProviderV1 } from "../types/finalityProviders"; + +import { apiWrapper } from "./apiWrapper"; + +export interface PaginatedFinalityProviders { + finalityProviders: FinalityProviderV1[]; + pagination: Pagination; +} + +interface FinalityProvidersAPIResponse { + data: FinalityProviderAPI[]; + pagination: Pagination; +} + +interface FinalityProviderAPI { + description: DescriptionAPI; + state: "active" | "standby"; + commission: string; + btc_pk: string; + active_tvl: number; + total_tvl: number; + active_delegations: number; + total_delegations: number; +} + +interface DescriptionAPI { + moniker: string; + identity: string; + website: string; + security_contact: string; + details: string; +} + +export const getFinalityProviders = async ({ + key, + pk, + sortBy, + order, + name, +}: { + key: string; + name?: string; + sortBy?: string; + order?: "asc" | "desc"; + pk?: string; +}): Promise => { + const params = { + pagination_key: key, + finality_provider_pk: pk, + sort_by: sortBy, + order, + name, + }; + + const response = await apiWrapper( + "GET", + API_ENDPOINTS.FINALITY_PROVIDERS, + "Error getting finality providers", + { query: params }, + ); + + const finalityProvidersAPIResponse: FinalityProvidersAPIResponse = + response.data; + const finalityProvidersAPI: FinalityProviderAPI[] = + finalityProvidersAPIResponse.data; + + const finalityProviders = finalityProvidersAPI.map( + (fp: FinalityProviderAPI): FinalityProviderV1 => ({ + description: { + moniker: fp.description.moniker, + identity: fp.description.identity, + website: isValidUrl(fp.description.website) + ? fp.description.website + : "", + securityContact: fp.description.security_contact, + details: fp.description.details, + }, + state: fp.state, + commission: fp.commission, + btcPk: fp.btc_pk, + activeTVLSat: fp.active_tvl, + totalTVLSat: fp.total_tvl, + activeDelegations: fp.active_delegations, + totalDelegations: fp.total_delegations, + }), + ); + + const pagination: Pagination = { + next_key: finalityProvidersAPIResponse.pagination.next_key, + }; + + return { finalityProviders, pagination }; +}; diff --git a/src/ui/common/api/getFinalityProvidersV2.ts b/src/ui/common/api/getFinalityProvidersV2.ts new file mode 100644 index 000000000..3788e6a44 --- /dev/null +++ b/src/ui/common/api/getFinalityProvidersV2.ts @@ -0,0 +1,110 @@ +import { isValidUrl } from "@/ui/common/utils/url"; + +import { Pagination } from "../types/api"; +import { + FinalityProvider, + FinalityProviderState, +} from "../types/finalityProviders"; + +import { apiWrapper } from "./apiWrapper"; + +export interface PaginatedFinalityProviders { + finalityProviders: FinalityProvider[]; + pagination: Pagination; +} + +interface FinalityProvidersAPIResponse { + data: FinalityProviderAPI[]; + pagination: Pagination; +} + +interface FinalityProviderAPI { + description: DescriptionAPI; + state: FinalityProviderState; + commission: string; + btc_pk: string; + active_tvl: number; + total_tvl: number; + active_delegations: number; + total_delegations: number; + logo_url?: string; + bsn_id?: string; + type: string; +} + +interface DescriptionAPI { + moniker: string; + identity: string; + website: string; + security_contact: string; + details: string; +} + +export const getFinalityProvidersV2 = async ({ + key, + pk, + sortBy, + order, + name, + bsnId, +}: { + key: string; + name?: string; + sortBy?: string; + order?: "asc" | "desc"; + pk?: string; + bsnId?: string; +}): Promise => { + const params = { + pagination_key: key, + finality_provider_pk: pk, + sort_by: sortBy, + order, + name, + bsn_id: bsnId, + }; + + const response = await apiWrapper( + "GET", + "/v2/finality-providers", + "Error getting finality providers", + { query: params }, + ); + + const finalityProvidersAPIResponse: FinalityProvidersAPIResponse = + response.data; + const finalityProvidersAPI: FinalityProviderAPI[] = + finalityProvidersAPIResponse.data; + + const finalityProviders = finalityProvidersAPI.map( + (fp: FinalityProviderAPI, i): FinalityProvider => ({ + description: { + moniker: fp.description.moniker, + identity: fp.description.identity, + website: isValidUrl(fp.description.website) + ? fp.description.website + : "", + securityContact: fp.description.security_contact, + details: fp.description.details, + }, + id: fp.btc_pk, + rank: i + 1, + state: fp.state, + commission: fp.commission, + btcPk: fp.btc_pk, + activeTVLSat: fp.active_tvl, + totalTVLSat: fp.total_tvl, + activeDelegations: fp.active_delegations, + totalDelegations: fp.total_delegations, + logo_url: fp.logo_url, + bsnId: fp.bsn_id, + chain: fp.type, + }), + ); + + const pagination: Pagination = { + next_key: finalityProvidersAPIResponse.pagination.next_key, + }; + + return { finalityProviders, pagination }; +}; diff --git a/src/ui/common/api/getNetworkInfo.ts b/src/ui/common/api/getNetworkInfo.ts new file mode 100644 index 000000000..cc3c895bd --- /dev/null +++ b/src/ui/common/api/getNetworkInfo.ts @@ -0,0 +1,165 @@ +import { getPublicKeyNoCoord } from "@babylonlabs-io/btc-staking-ts"; + +import { ClientError, ERROR_CODES } from "@/ui/common/errors"; + +import { NetworkInfo } from "../types/networkInfo"; + +import { apiWrapper } from "./apiWrapper"; + +interface NetworkInfoDataResponse { + data: NetworkInfoAPI; +} + +export interface BtcCheckpointParams { + version: number; + btc_confirmation_depth: number; +} + +interface StakingStatus { + is_staking_open: boolean; +} + +interface NetworkInfoAPI { + staking_status: StakingStatus; + params: { + bbn: BbnParams[]; + btc: BtcCheckpointParams[]; + max_bsn_fp_providers?: number; + }; +} + +export interface BbnParams { + version: number; + covenant_pks: string[]; + covenant_quorum: number; + min_staking_value_sat: number; + max_staking_value_sat: number; + min_staking_time_blocks: number; + max_staking_time_blocks: number; + slashing_pk_script: string; + min_slashing_tx_fee_sat: number; + slashing_rate: string; + unbonding_time_blocks: number; + unbonding_fee_sat: number; + min_commission_rate: string; + max_active_finality_providers: number; + delegation_creation_base_gas_fee: number; + btc_activation_height: number; + allow_list_expiration_height: number; + btcActivationHeight?: number; +} + +export const getNetworkInfo = async (): Promise => { + const response = await apiWrapper( + "GET", + "/v2/network-info", + "Error getting network info", + ); + const { data } = response; + const { params, staking_status } = data.data; + + const stakingVersions = (params.bbn || []) + .sort((a, b) => a.version - b.version) + .map((v) => ({ + version: v.version, + covenantNoCoordPks: v.covenant_pks.map((pk) => + String(getPublicKeyNoCoord(pk)), + ), + covenantQuorum: v.covenant_quorum, + minStakingValueSat: v.min_staking_value_sat, + maxStakingValueSat: v.max_staking_value_sat, + minStakingTimeBlocks: v.min_staking_time_blocks, + maxStakingTimeBlocks: v.max_staking_time_blocks, + unbondingTime: v.unbonding_time_blocks, + unbondingFeeSat: v.unbonding_fee_sat, + minCommissionRate: v.min_commission_rate, + maxActiveFinalityProviders: v.max_active_finality_providers, + delegationCreationBaseGasFee: v.delegation_creation_base_gas_fee, + slashing: { + slashingPkScriptHex: v.slashing_pk_script, + slashingRate: parseFloat(v.slashing_rate), + minSlashingTxFeeSat: v.min_slashing_tx_fee_sat, + }, + maxStakingAmountSat: v.max_staking_value_sat, + minStakingAmountSat: v.min_staking_value_sat, + btcActivationHeight: v.btc_activation_height, + allowListExpirationHeight: v.allow_list_expiration_height, + })); + + // Verify that version numbers and BTC activation heights are consistently ordered + const sortedByVersion = stakingVersions.toSorted( + (a, b) => b.version - a.version, + ); + const sortedByHeight = stakingVersions.toSorted( + (a, b) => b.btcActivationHeight - a.btcActivationHeight, + ); + + // Verify both sorts produce same order + const areEqual = sortedByVersion.every( + (param, index) => param === sortedByHeight[index], + ); + if (!areEqual) { + const clientError = new ClientError( + ERROR_CODES.EXTERNAL_SERVICE_UNAVAILABLE, + "Version numbers and BTC activation heights are not consistently ordered", + ); + throw clientError; + } + + const latestStakingParam = stakingVersions.reduce((prev, current) => + current.version > prev.version ? current : prev, + ); + + // Map the BTC checkpoint params to the expected format + const epochCheckVersions = (params.btc || []) + .sort((a, b) => a.version - b.version) + .map((v) => ({ + version: v.version, + btcConfirmationDepth: v.btc_confirmation_depth, + })); + + // Get the latest epoch check param + const latestEpochCheckParam = epochCheckVersions.reduce((prev, current) => + current.version > prev.version ? current : prev, + ); + + const genesisStakingParam = stakingVersions.find((v) => v.version === 0); + if (!genesisStakingParam) { + const clientError = new ClientError( + ERROR_CODES.EXTERNAL_SERVICE_UNAVAILABLE, + "Genesis staking params not found", + ); + throw clientError; + } + + // Find the genesis epoch check param (version 0) + const genesisEpochCheckParam = epochCheckVersions.find( + (v) => v.version === 0, + ); + if (!genesisEpochCheckParam) { + const clientError = new ClientError( + ERROR_CODES.EXTERNAL_SERVICE_UNAVAILABLE, + "Genesis epoch check params not found", + ); + throw clientError; + } + + return { + stakingStatus: { + isStakingOpen: staking_status.is_staking_open, + }, + params: { + bbnStakingParams: { + latestParam: latestStakingParam, + versions: stakingVersions, + genesisParam: genesisStakingParam, + }, + btcEpochCheckParams: { + latestParam: latestEpochCheckParam, + versions: epochCheckVersions, + genesisParam: genesisEpochCheckParam, + }, + maxBsnFpProviders: params.max_bsn_fp_providers, + }, + }; +}; diff --git a/src/ui/common/api/getStats.ts b/src/ui/common/api/getStats.ts new file mode 100644 index 000000000..feeb947df --- /dev/null +++ b/src/ui/common/api/getStats.ts @@ -0,0 +1,35 @@ +import { StakingStats } from "../types/stakingStats"; + +import { apiWrapper } from "./apiWrapper"; + +interface StatsAPIResponse { + data: StatsAPI; +} + +interface StatsAPI { + active_tvl: number; + total_tvl: number; + active_delegations: number; + total_delegations: number; + total_stakers: number; + unconfirmed_tvl: number; +} + +export const getStats = async (): Promise => { + const response = await apiWrapper( + "GET", + "/v1/stats", + "Error getting stats", + ); + const statsAPIResponse: StatsAPIResponse = response.data; + const statsAPI: StatsAPI = statsAPIResponse.data; + + return { + activeTVLSat: statsAPI.active_tvl, + totalTVLSat: statsAPI.total_tvl, + activeDelegations: statsAPI.active_delegations, + totalDelegations: statsAPI.total_delegations, + totalStakers: statsAPI.total_stakers, + unconfirmedTVLSat: statsAPI.unconfirmed_tvl, + }; +}; diff --git a/src/ui/common/api/getSystemStats.ts b/src/ui/common/api/getSystemStats.ts new file mode 100644 index 000000000..0dfbcfe96 --- /dev/null +++ b/src/ui/common/api/getSystemStats.ts @@ -0,0 +1,43 @@ +import { ClientError } from "@/ui/common/errors"; +import { ERROR_CODES } from "@/ui/common/errors/codes"; + +import { apiWrapper } from "./apiWrapper"; + +export interface SystemStats { + active_tvl: number; + total_tvl: number; + active_delegations: number; + total_delegations: number; + active_stakers: number; + total_stakers: number; + active_finality_providers: number; + total_finality_providers: number; + total_active_tvl: number; + btc_staking_apr: number; +} + +interface SystemStatsAPIResponse { + data: SystemStats; +} + +export const getSystemStats = async (): Promise => { + try { + const response = await apiWrapper( + "GET", + "/v2/stats", + "Error getting system stats", + ); + + const systemStatsAPIResponse: SystemStatsAPIResponse = response.data; + + return systemStatsAPIResponse.data; + } catch (error) { + throw new ClientError( + ERROR_CODES.EXTERNAL_SERVICE_UNAVAILABLE, + "Error getting system stats", + { + cause: error, + }, + ); + } +}; diff --git a/src/ui/common/api/getUnbondingEligibility.ts b/src/ui/common/api/getUnbondingEligibility.ts new file mode 100644 index 000000000..a4e55aa4b --- /dev/null +++ b/src/ui/common/api/getUnbondingEligibility.ts @@ -0,0 +1,17 @@ +import { apiWrapper } from "./apiWrapper"; + +export const getUnbondingEligibility = async (txID: string) => { + const params = { + staking_tx_hash_hex: txID, + }; + + const response = await apiWrapper( + "GET", + "/v1/unbonding/eligibility", + "Error checking unbonding eligibility", + { query: params }, + ); + + // If the response status is 200, the unbonding is eligible + return response.status === 200; +}; diff --git a/src/ui/common/api/healthCheckClient.ts b/src/ui/common/api/healthCheckClient.ts new file mode 100644 index 000000000..95bd9b156 --- /dev/null +++ b/src/ui/common/api/healthCheckClient.ts @@ -0,0 +1,15 @@ +import { apiWrapper } from "./apiWrapper"; + +type HealthCheckResponse = { + data: string; +}; + +export const fetchHealthCheck = async (): Promise => { + const response = await apiWrapper( + "GET", + "/healthcheck", + "Error getting healthcheck status", + ); + + return response.data; +}; diff --git a/src/ui/common/api/httpStatusCodes.ts b/src/ui/common/api/httpStatusCodes.ts new file mode 100644 index 000000000..5a60f395a --- /dev/null +++ b/src/ui/common/api/httpStatusCodes.ts @@ -0,0 +1,69 @@ +/** + * Standard HTTP status codes used throughout the application + * Replaces the Axios HttpStatusCode enum + */ +export enum HttpStatusCode { + Continue = 100, + SwitchingProtocols = 101, + Processing = 102, + EarlyHints = 103, + Ok = 200, + Created = 201, + Accepted = 202, + NonAuthoritativeInformation = 203, + NoContent = 204, + ResetContent = 205, + PartialContent = 206, + MultiStatus = 207, + AlreadyReported = 208, + ImUsed = 226, + MultipleChoices = 300, + MovedPermanently = 301, + Found = 302, + SeeOther = 303, + NotModified = 304, + UseProxy = 305, + Unused = 306, + TemporaryRedirect = 307, + PermanentRedirect = 308, + BadRequest = 400, + Unauthorized = 401, + PaymentRequired = 402, + Forbidden = 403, + NotFound = 404, + MethodNotAllowed = 405, + NotAcceptable = 406, + ProxyAuthenticationRequired = 407, + RequestTimeout = 408, + Conflict = 409, + Gone = 410, + LengthRequired = 411, + PreconditionFailed = 412, + PayloadTooLarge = 413, + UriTooLong = 414, + UnsupportedMediaType = 415, + RangeNotSatisfiable = 416, + ExpectationFailed = 417, + ImATeapot = 418, + MisdirectedRequest = 421, + UnprocessableEntity = 422, + Locked = 423, + FailedDependency = 424, + TooEarly = 425, + UpgradeRequired = 426, + PreconditionRequired = 428, + TooManyRequests = 429, + RequestHeaderFieldsTooLarge = 431, + UnavailableForLegalReasons = 451, + InternalServerError = 500, + NotImplemented = 501, + BadGateway = 502, + ServiceUnavailable = 503, + GatewayTimeout = 504, + HttpVersionNotSupported = 505, + VariantAlsoNegotiates = 506, + InsufficientStorage = 507, + LoopDetected = 508, + NotExtended = 510, + NetworkAuthenticationRequired = 511, +} diff --git a/src/ui/common/api/logTermAcceptance.ts b/src/ui/common/api/logTermAcceptance.ts new file mode 100644 index 000000000..06ee58fc6 --- /dev/null +++ b/src/ui/common/api/logTermAcceptance.ts @@ -0,0 +1,32 @@ +import { encode } from "url-safe-base64"; + +import { getPublicKeyNoCoord } from "@/ui/common/utils/wallet"; + +import { apiWrapper } from "./apiWrapper"; + +interface TermsPayload { + address: string; + public_key: string; +} + +// postLogTermsAcceptance is used to log the terms acceptance for a given address and public key +export const logTermsAcceptance = async ({ + address, + public_key, +}: TermsPayload) => { + try { + const payload: TermsPayload = { + address: address, + public_key: encode(getPublicKeyNoCoord(public_key).toString("hex")), + }; + + await apiWrapper( + "POST", + "/log-terms-acceptance", + "Error submitting terms acceptance request", + { body: payload }, + ); + } catch { + return; + } +}; diff --git a/src/ui/common/api/postFilterOrdinals.ts b/src/ui/common/api/postFilterOrdinals.ts new file mode 100644 index 000000000..ee15f6ea1 --- /dev/null +++ b/src/ui/common/api/postFilterOrdinals.ts @@ -0,0 +1,46 @@ +import { UTXO } from "@babylonlabs-io/btc-staking-ts"; + +import { chunkArray } from "@/ui/common/utils/chunkArray"; + +import { apiWrapper } from "./apiWrapper"; + +export interface UtxoInfo { + txid: string; + vout: number; + inscription: boolean; +} + +interface VerifyUtxosResponse { + data: UtxoInfo[]; +} + +const TIMEOUT_DURATION = 2000; // 2 seconds +const BATCH_SIZE = 30; + +export const postVerifyUtxoOrdinals = async ( + utxos: UTXO[], + address: string, +): Promise => { + const utxoChunks = chunkArray(utxos, BATCH_SIZE); + const responses = await Promise.all( + utxoChunks.map((chunk) => + apiWrapper( + "POST", + "/v1/ordinals/verify-utxos", + "Error verifying utxos ordinals", + { + body: { + address, + utxos: chunk.map((utxo) => ({ + txid: utxo.txid, + vout: utxo.vout, + })), + }, + }, + TIMEOUT_DURATION, + ), + ), + ); + + return responses.flatMap((response) => response.data.data); +}; diff --git a/src/ui/common/api/postUnbonding.ts b/src/ui/common/api/postUnbonding.ts new file mode 100644 index 000000000..a1a6d7cfa --- /dev/null +++ b/src/ui/common/api/postUnbonding.ts @@ -0,0 +1,32 @@ +import { apiWrapper } from "./apiWrapper"; + +interface UnbondingPayload { + staker_signed_signature_hex: string; + staking_tx_hash_hex: string; + unbonding_tx_hash_hex: string; + unbonding_tx_hex: string; +} + +export const postUnbonding = async ( + stakerSignedSignatureHex: string, + stakingTxHashHex: string, + unbondingTxHashHex: string, + unbondingTxHex: string, +) => { + const payload: UnbondingPayload = { + staker_signed_signature_hex: stakerSignedSignatureHex, + staking_tx_hash_hex: stakingTxHashHex, + unbonding_tx_hash_hex: unbondingTxHashHex, + unbonding_tx_hex: unbondingTxHex, + }; + + const response = await apiWrapper( + "POST", + "/v1/unbonding", + "Error submitting unbonding request", + { body: payload }, + ); + + // If the response status is 202, the request was accepted + return response.status === 202; +}; diff --git a/src/ui/common/api/verifyBTCAddress.ts b/src/ui/common/api/verifyBTCAddress.ts new file mode 100644 index 000000000..00ba7c2d7 --- /dev/null +++ b/src/ui/common/api/verifyBTCAddress.ts @@ -0,0 +1,27 @@ +import { apiWrapper } from "./apiWrapper"; + +const ALLOWED_STATUSES = ["medium", "low"]; + +export const verifyBTCAddress = async (address: string) => { + interface AddressScreeningResponse { + data: { + btc_address?: { + risk: string; + }; + }; + } + + try { + const { data: response } = await apiWrapper( + "GET", + "/address/screening", + "Error performing BTC address screening", + { query: { btc_address: address } }, + ); + + const risk = response.data?.btc_address?.risk; + return risk ? ALLOWED_STATUSES.includes(risk.toLowerCase()) : false; + } catch { + return false; + } +}; diff --git a/src/ui/common/assets/404-error-character.svg b/src/ui/common/assets/404-error-character.svg new file mode 100644 index 000000000..ce8eb154e --- /dev/null +++ b/src/ui/common/assets/404-error-character.svg @@ -0,0 +1,438 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ui/common/assets/babylon.svg b/src/ui/common/assets/babylon.svg new file mode 100644 index 000000000..bbfb55c9e --- /dev/null +++ b/src/ui/common/assets/babylon.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/ui/common/assets/bbn.svg b/src/ui/common/assets/bbn.svg new file mode 100644 index 000000000..cbd7e0e87 --- /dev/null +++ b/src/ui/common/assets/bbn.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/ui/common/assets/bg-pattern.svg b/src/ui/common/assets/bg-pattern.svg new file mode 100644 index 000000000..0ed801711 --- /dev/null +++ b/src/ui/common/assets/bg-pattern.svg @@ -0,0 +1,12 @@ + + + mainnet-bg-pattern + + + + + + diff --git a/src/ui/common/assets/bitcoin-block.svg b/src/ui/common/assets/bitcoin-block.svg new file mode 100644 index 000000000..0c0e3991b --- /dev/null +++ b/src/ui/common/assets/bitcoin-block.svg @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ui/common/assets/bitcoin.png b/src/ui/common/assets/bitcoin.png new file mode 100644 index 0000000000000000000000000000000000000000..865ffda19211619abee80463ae7c187e2c0d3b17 GIT binary patch literal 39289 zcmX_ncRbba`~NwIV;n>rD>D+3SymZGl$|{@L-vSla_pH%wm8V%dvj8u?6S8A*(H0N z->uL4`}?DZ$D_yV-1q&u?&oz~&+B2nYm1rl=sN34y@Sm;Z)>W+7gm@RVG}s z7}BGOvVs~A^o+Bu+vi!PQd*?aRclAyrux7(iw*MAr}^Yo1^*_-v-H3uUk-}EH$uTo zqa7Lv1IBAsi$F4_V*Xt%l#2KRY35Uqh(ig`!^a(<}AuBv) zfsnmyfhU+ZL$&om*;8-h)jT>}=8OoFgS0fzFevSAx3n2vd&KQ5nA25tDk|a0Adylt zq{t{T3}q{Ooh}Cx2B9N0LqBnKH)l40Y&~-$(#V4Tp?~Xl#n}HD-HEmEe$Dj5K7OI& zYzUl7y$QmA{Ypk32n(cCr}8AVuGva|Akd1bOht(V7xrTW9CQ+BxyT8lRq>9Dh@aUa z?j#nbDt!sqV`JGbb$+sD#+(raL+%Db7*H&?*_1J8CfNGRwbTQdbUMw6N0?XW!ZN5Z zEN&6P=F#^h#R|c78?qaJoJ@JFj=%F0?Kwf0^y`Z`k_Zh>0l6)t{2rwTTbXOsR&Xq^ zosglodr$P}mYLjDa@7~qX#pM0Fh{fxZQLPpH}*;^1AW|?Tvv|zY^~UR@E(p(5%j1D zbU+On4j*xpwJ6G)j8Dt=(8VgC7>EmbAt8*9L%-|Do59fyaz($wdhW`Y8@8HnuZ}8) z)@Y((qm*WN`n=u)%Fu9p&G9RY#V;H^Z6;LTPEp4(9Z{9b9+{+eoe>*Y-oxZa+I-Jh z?BQ`8ur~GO@Ly4cBHr?02`XRQiN&Dp;Uiu)Ol?%tzj6+smeTM|0yB&~)w8Eg)B
  • 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