├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug-template.md │ ├── chores-template.md │ ├── enhancement-template.md │ └── idea-template.md ├── PULL_REQUEST_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE │ ├── bump-version-template.md │ ├── changes-template.md │ └── pre-release-template.md └── workflows │ ├── build-rc.yml │ ├── build.yml │ ├── create-release-pr.yml │ ├── merge-repos.yml │ ├── playwright.yml │ ├── release-develop.yml │ └── release.yml ├── .gitignore ├── .husky ├── pre-commit └── pre-push ├── .prettierrc.json ├── LICENSE ├── README.md ├── knip.jsonc ├── package-lock.json ├── package.json ├── playwright.config.ts ├── scripts ├── .gitignore ├── build-named.js ├── create-release-pr.sh ├── find-tag.sh ├── merge-release-to-remote.sh ├── merge-to-remote.sh └── pin_all_deps.js ├── src ├── app │ ├── App.tsx │ ├── components │ │ ├── accountHeader │ │ │ └── index.tsx │ │ ├── accountRow │ │ │ ├── accountAvatar.tsx │ │ │ ├── index.styled.ts │ │ │ ├── index.tsx │ │ │ └── lazyAccountRow.tsx │ │ ├── alertMessage │ │ │ └── index.tsx │ │ ├── animatedScreenContainer │ │ │ └── index.tsx │ │ ├── assetModal │ │ │ └── index.tsx │ │ ├── barLoader │ │ │ └── index.tsx │ │ ├── batchPsbtSigning │ │ │ ├── index.styled.ts │ │ │ └── index.tsx │ │ ├── bottomModal │ │ │ └── index.tsx │ │ ├── btcAddressTypeLabel │ │ │ └── index.tsx │ │ ├── btcAmountText │ │ │ └── index.tsx │ │ ├── button │ │ │ └── index.tsx │ │ ├── checkBox │ │ │ └── index.tsx │ │ ├── collectibleCollage │ │ │ └── collectibleCollage.tsx │ │ ├── collectibleCollectionGridItem │ │ │ └── index.tsx │ │ ├── collectibleDetailTile │ │ │ └── index.tsx │ │ ├── confirmBtcTransaction │ │ │ ├── hooks │ │ │ │ ├── useExtractTxSummary.ts │ │ │ │ └── useTxSummaryContext.ts │ │ │ ├── index.tsx │ │ │ ├── indexBatch.tsx │ │ │ ├── itemRow │ │ │ │ ├── amount.tsx │ │ │ │ ├── amountWithInscriptionSatribute.tsx │ │ │ │ ├── inscription.tsx │ │ │ │ ├── inscriptionSatributeRow.tsx │ │ │ │ ├── rareSatRow.tsx │ │ │ │ ├── rareSats.tsx │ │ │ │ └── runeAmount.tsx │ │ │ ├── sections │ │ │ │ ├── feeSection.tsx │ │ │ │ ├── inscribeSection │ │ │ │ │ ├── ContentLabel │ │ │ │ │ │ ├── common.ts │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ ├── preview.tsx │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.tsx │ │ │ │ ├── receiveSection.tsx │ │ │ │ ├── runesSection │ │ │ │ │ ├── burnSection.tsx │ │ │ │ │ ├── delegateSection.tsx │ │ │ │ │ ├── etchSection.tsx │ │ │ │ │ ├── mintSection.tsx │ │ │ │ │ └── styles.tsx │ │ │ │ ├── sendSection.tsx │ │ │ │ └── transferSection.tsx │ │ │ ├── transactionSummary.tsx │ │ │ └── txInOutput │ │ │ │ ├── index.tsx │ │ │ │ ├── transactionInput.tsx │ │ │ │ └── transactionOutput.tsx │ │ ├── confirmScreen │ │ │ └── index.tsx │ │ ├── confirmStxTransactionComponent │ │ │ ├── index.styled.ts │ │ │ ├── index.tsx │ │ │ └── transferMemoView │ │ │ │ └── index.tsx │ │ ├── copyButton │ │ │ └── index.tsx │ │ ├── createPassword │ │ │ ├── index.styled.ts │ │ │ └── index.tsx │ │ ├── dots │ │ │ └── index.tsx │ │ ├── errorDisplay │ │ │ ├── index.styled.ts │ │ │ └── index.tsx │ │ ├── exoticSatsRow │ │ │ └── exoticSatsRow.tsx │ │ ├── explore │ │ │ ├── FeaturedCard.tsx │ │ │ ├── FeaturedCarousel.tsx │ │ │ ├── RecommendedApps.tsx │ │ │ └── SwiperNavigation.tsx │ │ ├── fiatAmountText │ │ │ └── index.tsx │ │ ├── formattedNumber │ │ │ └── index.tsx │ │ ├── guards │ │ │ ├── auth.tsx │ │ │ ├── onboarding │ │ │ │ ├── WalletExistsContext.ts │ │ │ │ └── index.tsx │ │ │ ├── session.tsx │ │ │ ├── singleTab.tsx │ │ │ └── walletCloseGuard.tsx │ │ ├── infoContainer │ │ │ └── index.tsx │ │ ├── inputScreen │ │ │ └── index.tsx │ │ ├── keystone │ │ │ └── connectKeystoneView │ │ │ │ └── index.tsx │ │ ├── keystoneSteps │ │ │ ├── index.tsx │ │ │ └── keystoneStepView.tsx │ │ ├── ledger │ │ │ ├── connectLedgerView │ │ │ │ └── index.tsx │ │ │ ├── failLedgerView │ │ │ │ └── index.tsx │ │ │ ├── ledgerAddressComponent │ │ │ │ └── index.tsx │ │ │ ├── ledgerAssetSelectCard │ │ │ │ └── index.tsx │ │ │ └── ledgerInput │ │ │ │ └── index.tsx │ │ ├── ledgerSteps │ │ │ ├── index.tsx │ │ │ └── ledgerStepView.tsx │ │ ├── loadingTransactionStatus │ │ │ ├── circularSvgAnimation.tsx │ │ │ └── index.tsx │ │ ├── messageSigning │ │ │ ├── index.styled.ts │ │ │ └── index.tsx │ │ ├── optionsDialog │ │ │ └── optionsDialog.tsx │ │ ├── passwordInput │ │ │ ├── index.styled.ts │ │ │ └── index.tsx │ │ ├── percentageChange │ │ │ └── index.tsx │ │ ├── permissionsManager │ │ │ ├── README.md │ │ │ ├── globalStore.ts │ │ │ ├── index.tsx │ │ │ └── types.ts │ │ ├── postCondition │ │ │ ├── ftPostConditionCard.tsx │ │ │ ├── nftPostConditionCard.tsx │ │ │ ├── postConditionView │ │ │ │ ├── helper.ts │ │ │ │ └── index.tsx │ │ │ └── stxPostConditionCard.tsx │ │ ├── preferredBtcAddress │ │ │ ├── addressTypeSelector.tsx │ │ │ └── index.tsx │ │ ├── preferredBtcAddressItem │ │ │ └── index.tsx │ │ ├── rareSatIcon │ │ │ └── rareSatIcon.tsx │ │ ├── receiveCardComponent │ │ │ └── index.tsx │ │ ├── recipientComponent │ │ │ └── index.tsx │ │ ├── recipientSelector │ │ │ └── index.tsx │ │ ├── requests │ │ │ └── requestError.tsx │ │ ├── resetWallet │ │ │ └── index.tsx │ │ ├── screenContainer │ │ │ └── index.tsx │ │ ├── seedPhraseInput │ │ │ ├── index.tsx │ │ │ └── seedWordInput.tsx │ │ ├── seedPhraseView │ │ │ ├── index.tsx │ │ │ └── word.tsx │ │ ├── separator │ │ │ └── index.tsx │ │ ├── speedUpTransaction │ │ │ ├── btc.tsx │ │ │ ├── index.styled.ts │ │ │ └── stx.tsx │ │ ├── squareButton │ │ │ └── index.tsx │ │ ├── startupLoadingScreen │ │ │ └── index.tsx │ │ ├── tabBar │ │ │ └── index.tsx │ │ ├── tabBarVertical │ │ │ └── index.tsx │ │ ├── tilesSkeletonLoader.tsx │ │ ├── toaster │ │ │ └── index.tsx │ │ ├── tokenImage │ │ │ └── index.tsx │ │ ├── tokenTile │ │ │ ├── index.styled.ts │ │ │ ├── index.tsx │ │ │ └── loader.tsx │ │ ├── tooltip │ │ │ ├── context.ts │ │ │ ├── index.tsx │ │ │ └── provider.tsx │ │ ├── topRow │ │ │ └── index.tsx │ │ ├── transactionDetailComponent │ │ │ └── index.tsx │ │ ├── transactionSetting │ │ │ ├── editNonce.tsx │ │ │ ├── editStxFee.tsx │ │ │ └── index.tsx │ │ ├── transactions │ │ │ ├── RuneTransaction.tsx │ │ │ ├── btcOrBrc20Transaction.tsx │ │ │ ├── stxTransaction.tsx │ │ │ ├── stxTransferTransaction.tsx │ │ │ ├── transactionAmount.tsx │ │ │ ├── transactionRecipient.tsx │ │ │ ├── transactionStatusIcon.tsx │ │ │ ├── transactionTitle.tsx │ │ │ └── txTransfers.tsx │ │ ├── transactionsRequests │ │ │ ├── ContractCallRequest.tsx │ │ │ ├── ContractDeployRequest.tsx │ │ │ ├── contractCallDetails.tsx │ │ │ └── utils.ts │ │ ├── transferAmountComponent │ │ │ └── index.tsx │ │ ├── transferDetailView │ │ │ └── index.tsx │ │ ├── transferFeeView │ │ │ └── index.tsx │ │ ├── webGalleryButton │ │ │ └── index.tsx │ │ └── wrenchErrorMessage │ │ │ └── index.tsx │ ├── hooks │ │ ├── apiClients │ │ │ ├── useBtcClient.ts │ │ │ ├── useOrdinalsApi.ts │ │ │ ├── useOrdinalsServiceApi.ts │ │ │ ├── useRunesApi.ts │ │ │ ├── useStacksApi.ts │ │ │ └── useXverseApi.ts │ │ ├── queries │ │ │ ├── ordinals │ │ │ │ ├── useAddressCollectionInscriptions.ts │ │ │ │ ├── useAddressInscription.ts │ │ │ │ ├── useAddressInscriptions.ts │ │ │ │ ├── useAddressRareSats.ts │ │ │ │ ├── useCollectionMarketData.ts │ │ │ │ ├── useGetBrc20FungibleTokens.ts │ │ │ │ └── useInscriptionDetails.ts │ │ │ ├── runes │ │ │ │ ├── useRuneFiatRateQuery.ts │ │ │ │ ├── useRuneFloorPricePerMarketplaceQuery.ts │ │ │ │ ├── useRuneFloorPriceQuery.ts │ │ │ │ ├── useRuneFungibleTokensQuery.ts │ │ │ │ ├── useRuneSellPsbtPerMarketplace.ts │ │ │ │ ├── useRuneUtxosQuery.ts │ │ │ │ ├── useRuneUtxosQueryPerMarketplace.ts │ │ │ │ └── useSubmitRuneSellPsbt.ts │ │ │ ├── stx │ │ │ │ ├── useGetSip10FungibleTokens.ts │ │ │ │ └── useGetSip10TokenInfo.ts │ │ │ ├── swaps │ │ │ │ ├── useGetQuotes.ts │ │ │ │ └── useGetUtxos.ts │ │ │ ├── useAccountBalance.ts │ │ │ ├── useAppConfig.ts │ │ │ ├── useBnsName.ts │ │ │ ├── useBtcWalletData.ts │ │ │ ├── useConfirmedBtcBalance.ts │ │ │ ├── useDelegationState.ts │ │ │ ├── useFeeMultipliers.ts │ │ │ ├── useGetCoinsMarketData.ts │ │ │ ├── useGetExchangeRate.ts │ │ │ ├── useGetHistoricalData.ts │ │ │ ├── useGetTopTokens.ts │ │ │ ├── useNftDetail.ts │ │ │ ├── usePendingOrdinalTx.ts │ │ │ ├── useSelectedAccountBtcBalance.tsx │ │ │ ├── useSpamTokens.ts │ │ │ ├── useStackingData.ts │ │ │ ├── useStacksCollectibles.ts │ │ │ ├── useStxPendingTxData.ts │ │ │ ├── useStxWalletData.ts │ │ │ ├── useSupportedCoinRates.ts │ │ │ ├── useTransaction.ts │ │ │ └── useTransactions.ts │ │ ├── stores │ │ │ ├── useHasRehydrated.ts │ │ │ ├── useNftDataSelector.ts │ │ │ └── useSatBundleReducer.ts │ │ ├── useAvatarCleanup.ts │ │ ├── useBtcAddressBalance.ts │ │ ├── useBtcFeeRate.ts │ │ ├── useCanUserSwitchPaymentType.ts │ │ ├── useCancellableEffect.ts │ │ ├── useChromeLocalStorage.ts │ │ ├── useDebounce.ts │ │ ├── useFeaturedDapps.ts │ │ ├── useHasFeature.ts │ │ ├── useIntersectionObserver.ts │ │ ├── useNetwork.ts │ │ ├── useNotificationBanners.ts │ │ ├── useOnTabClosed.ts │ │ ├── useOptionsDialog.ts │ │ ├── useOptionsSheet.ts │ │ ├── useOrdinalsByAddress.ts │ │ ├── useRbfTransactionData.ts │ │ ├── useResetUserFlow.ts │ │ ├── useSearchParamsState.ts │ │ ├── useSeedVault.ts │ │ ├── useSeedVaultMigration.ts │ │ ├── useSelectedAccount.ts │ │ ├── useSignatureRequest.ts │ │ ├── useStorage.ts │ │ ├── useStxAccountRequest.ts │ │ ├── useStxAddressRequest.ts │ │ ├── useTextOrdinalContent.ts │ │ ├── useToggleBalanceView.ts │ │ ├── useTrackMixPanelPageViewed.ts │ │ ├── useTransactionContext.ts │ │ ├── useWalletReducer.ts │ │ ├── useWalletSelector.ts │ │ └── useWalletSession.ts │ ├── layouts │ │ ├── confirmTxLayout.tsx │ │ └── sendLayout.tsx │ ├── mixpanelSetup.ts │ ├── routes │ │ ├── authenticatedRoutes.tsx │ │ ├── index.tsx │ │ ├── onboardingRoutes.tsx │ │ ├── openRoutes.tsx │ │ └── paths.ts │ ├── screens │ │ ├── accountList │ │ │ └── index.tsx │ │ ├── backupWallet │ │ │ └── index.tsx │ │ ├── backupWalletSteps │ │ │ ├── index.tsx │ │ │ ├── seedCheck.tsx │ │ │ └── verifySeed.tsx │ │ ├── btcSendRequest │ │ │ ├── index.tsx │ │ │ └── useBtcSendRequestPayload.ts │ │ ├── buy │ │ │ ├── index.tsx │ │ │ └── redirectButton.tsx │ │ ├── coinDashboard │ │ │ ├── coinHeader.styled.ts │ │ │ ├── coinHeader.tsx │ │ │ ├── coins │ │ │ │ ├── btc │ │ │ │ │ ├── addressBalance.tsx │ │ │ │ │ ├── balanceBreakdown.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── other.tsx │ │ │ ├── index.styled.ts │ │ │ ├── index.tsx │ │ │ ├── runes │ │ │ │ └── bundleRow.tsx │ │ │ ├── tokenHistoricalData │ │ │ │ ├── HistoricalDataChart.tsx │ │ │ │ └── index.tsx │ │ │ ├── tokenPrice.tsx │ │ │ └── transactionsHistoryList.tsx │ │ ├── confirmBrc20Transaction │ │ │ ├── amountRow.tsx │ │ │ ├── brc20FeesComponent.tsx │ │ │ ├── index.styled.ts │ │ │ ├── index.tsx │ │ │ └── recipientCard.tsx │ │ ├── confirmFtTransaction │ │ │ └── index.tsx │ │ ├── confirmNftTransaction │ │ │ └── index.tsx │ │ ├── confirmStxTransaction │ │ │ └── index.tsx │ │ ├── connect │ │ │ ├── addressPurposeBox.tsx │ │ │ ├── authenticationRequest │ │ │ │ └── index.tsx │ │ │ ├── btcSelectAddressScreen │ │ │ │ ├── index.styled.ts │ │ │ │ ├── index.tsx │ │ │ │ └── useBtcAddressRequest.ts │ │ │ ├── connectionRequest │ │ │ │ ├── dappLogo.tsx │ │ │ │ ├── hooks.ts │ │ │ │ ├── host.tsx │ │ │ │ ├── index.styles.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── permissions │ │ │ │ │ └── index.tsx │ │ │ │ ├── selectAccount.tsx │ │ │ │ └── title.tsx │ │ │ ├── permissionsList.tsx │ │ │ ├── selectAccount.tsx │ │ │ ├── stxSelectAccountScreen │ │ │ │ └── index.tsx │ │ │ └── stxSelectAddressScreen │ │ │ │ └── index.tsx │ │ ├── connectHardwareWallet │ │ │ └── index.tsx │ │ ├── createInscription │ │ │ ├── CompleteScreen.tsx │ │ │ ├── EditFee.tsx │ │ │ ├── ErrorModal │ │ │ │ └── index.tsx │ │ │ ├── feeRow.tsx │ │ │ ├── hardwareWalletStepView.tsx │ │ │ ├── index.styled.ts │ │ │ └── index.tsx │ │ ├── createPassword │ │ │ └── index.tsx │ │ ├── createWalletSuccess │ │ │ └── index.tsx │ │ ├── error │ │ │ └── index.tsx │ │ ├── etchRune │ │ │ ├── index.tsx │ │ │ └── useEtchRequest.ts │ │ ├── executeBrc20Transaction │ │ │ ├── index.tsx │ │ │ ├── keystoneStepView.tsx │ │ │ └── ledgerStepView.tsx │ │ ├── explore │ │ │ └── index.tsx │ │ ├── forgotPassword │ │ │ └── index.tsx │ │ ├── home │ │ │ ├── announcementModal │ │ │ │ ├── index.tsx │ │ │ │ └── nativeSegWit.tsx │ │ │ ├── balanceCard │ │ │ │ ├── index.styled.ts │ │ │ │ └── index.tsx │ │ │ ├── banner.tsx │ │ │ ├── bannerCarousel.tsx │ │ │ ├── coinSelectModal │ │ │ │ └── index.tsx │ │ │ ├── index.styled.ts │ │ │ ├── index.tsx │ │ │ └── receiveSheet.tsx │ │ ├── keystone │ │ │ └── importKeystoneAccount │ │ │ │ ├── index.styled.ts │ │ │ │ ├── index.tsx │ │ │ │ ├── stepControls.tsx │ │ │ │ ├── steps │ │ │ │ ├── index.styled.ts │ │ │ │ └── index.tsx │ │ │ │ └── types.ts │ │ ├── landing │ │ │ ├── index.styled.ts │ │ │ └── index.tsx │ │ ├── ledger │ │ │ ├── addStxAddress │ │ │ │ └── index.tsx │ │ │ ├── confirmLedgerStxTransaction │ │ │ │ ├── index.styled.ts │ │ │ │ └── index.tsx │ │ │ ├── importLedgerAccount │ │ │ │ ├── index.styled.ts │ │ │ │ ├── index.tsx │ │ │ │ ├── stepControls.tsx │ │ │ │ ├── steps │ │ │ │ │ ├── index.styled.ts │ │ │ │ │ └── index.tsx │ │ │ │ └── types.ts │ │ │ └── verifyLedgerAccountAddress │ │ │ │ ├── index.styled.ts │ │ │ │ └── index.tsx │ │ ├── legal │ │ │ └── index.tsx │ │ ├── listRune │ │ │ ├── floorComparisonLabel.tsx │ │ │ ├── index.styled.ts │ │ │ ├── index.tsx │ │ │ ├── listMarketplaceItem.tsx │ │ │ ├── listRuneItem.tsx │ │ │ ├── listRuneWrapper.tsx │ │ │ ├── reducer.tsx │ │ │ ├── setCustomPriceModal.tsx │ │ │ └── setRunePriceItem.tsx │ │ ├── login │ │ │ ├── index.styled.ts │ │ │ └── index.tsx │ │ ├── manageTokens │ │ │ ├── coinItem │ │ │ │ └── index.tsx │ │ │ ├── index.styled.ts │ │ │ └── index.tsx │ │ ├── migrationConfirmation │ │ │ └── index.tsx │ │ ├── mintRune │ │ │ ├── index.tsx │ │ │ └── useMintRequest.ts │ │ ├── nftCollection │ │ │ ├── index.styled.ts │ │ │ ├── index.tsx │ │ │ └── useNftCollection.ts │ │ ├── nftDashboard │ │ │ ├── collectiblesTabs │ │ │ │ ├── index.styled.ts │ │ │ │ ├── index.tsx │ │ │ │ └── skeletonLoader.tsx │ │ │ ├── hidden │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ ├── inscriptionsTabGridItem.tsx │ │ │ ├── nft.tsx │ │ │ ├── nftImage.tsx │ │ │ ├── nftTabGridItem.tsx │ │ │ ├── notice.tsx │ │ │ ├── rareSatsTabGridItem.tsx │ │ │ ├── receiveNft │ │ │ │ └── index.tsx │ │ │ ├── supportedRarities │ │ │ │ ├── index.tsx │ │ │ │ └── rarityTile.tsx │ │ │ └── useNftDashboard.tsx │ │ ├── nftDetail │ │ │ ├── index.styled.ts │ │ │ ├── index.tsx │ │ │ ├── nftAttribute.tsx │ │ │ └── useNftDetail.ts │ │ ├── ordinalDetail │ │ │ ├── index.styled.ts │ │ │ ├── index.tsx │ │ │ ├── ordinalAttributeComponent.tsx │ │ │ └── useOrdinalDetail.ts │ │ ├── ordinals │ │ │ ├── brc20Tile.tsx │ │ │ └── ordinalImage.tsx │ │ ├── ordinalsCollection │ │ │ ├── index.styled.ts │ │ │ └── index.tsx │ │ ├── rareSatsBundle │ │ │ ├── bundleContent.tsx │ │ │ ├── index.styled.ts │ │ │ ├── index.tsx │ │ │ ├── rareSatsBundleGridItem.tsx │ │ │ └── runeAmount.tsx │ │ ├── rareSatsDetail │ │ │ └── rareSatsDetail.tsx │ │ ├── receive │ │ │ ├── index.styled.ts │ │ │ ├── index.tsx │ │ │ └── qrCode.tsx │ │ ├── restoreWallet │ │ │ ├── enterSeedphrase.tsx │ │ │ ├── index.tsx │ │ │ └── paymentAddressTypeSelector.tsx │ │ ├── runeListingBatchSigning │ │ │ └── index.tsx │ │ ├── sendBrc20OneStep │ │ │ ├── brc20TransferForm.tsx │ │ │ └── index.tsx │ │ ├── sendBtc │ │ │ ├── accountSelector.tsx │ │ │ ├── amountSelector.tsx │ │ │ ├── helpers.ts │ │ │ ├── index.tsx │ │ │ ├── stepDisplay.tsx │ │ │ ├── steps.tsx │ │ │ └── title.tsx │ │ ├── sendInscriptionsRequest │ │ │ ├── index.tsx │ │ │ └── useSendInscriptions.ts │ │ ├── sendNft │ │ │ └── index.tsx │ │ ├── sendOrdinal │ │ │ ├── index.tsx │ │ │ ├── stepDisplay.tsx │ │ │ └── steps.tsx │ │ ├── sendRune │ │ │ ├── amountSelector.tsx │ │ │ ├── helpers.ts │ │ │ ├── index.tsx │ │ │ ├── runeAmountSelector.tsx │ │ │ ├── stepDisplay.tsx │ │ │ └── steps.tsx │ │ ├── sendStx │ │ │ ├── ftAmountSelector.tsx │ │ │ ├── index.tsx │ │ │ ├── stepResolver.tsx │ │ │ ├── steps │ │ │ │ ├── Step1SelectRecipient.tsx │ │ │ │ ├── Step2SelectAmount.tsx │ │ │ │ └── Step3Confirm.tsx │ │ │ └── stxAmountSelector.tsx │ │ ├── settings │ │ │ ├── about │ │ │ │ └── index.tsx │ │ │ ├── advanced │ │ │ │ ├── index.tsx │ │ │ │ ├── paymentAddressTypeSelector │ │ │ │ │ └── index.tsx │ │ │ │ └── recoverFunds │ │ │ │ │ ├── fundsRow.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── recoverOrdinals │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── ordinalRow.tsx │ │ │ │ │ └── recoverRunes │ │ │ │ │ └── index.tsx │ │ │ ├── changeNetwork │ │ │ │ ├── index.tsx │ │ │ │ ├── networkRow.tsx │ │ │ │ └── nodeInput.tsx │ │ │ ├── connectedAppsAndPermissions │ │ │ │ ├── components │ │ │ │ │ ├── client.tsx │ │ │ │ │ └── selectedApp.tsx │ │ │ │ ├── index.styles.ts │ │ │ │ ├── index.tsx │ │ │ │ └── types.ts │ │ │ ├── index.styles.ts │ │ │ ├── index.tsx │ │ │ ├── preferences │ │ │ │ ├── fiatCurrency │ │ │ │ │ ├── currencyRow.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── lockCountdown │ │ │ │ │ └── index.tsx │ │ │ │ └── privacyPreferences │ │ │ │ │ └── index.tsx │ │ │ ├── security │ │ │ │ ├── backupWallet │ │ │ │ │ └── index.tsx │ │ │ │ ├── changePassword │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── resetWallet │ │ │ │ │ └── index.tsx │ │ │ └── settingComponent │ │ │ │ └── index.tsx │ │ ├── signBatchPsbtRequest │ │ │ └── index.tsx │ │ ├── signMessageRequest │ │ │ ├── index.tsx │ │ │ └── useSignMessageRequest.ts │ │ ├── signPsbtRequest │ │ │ ├── index.tsx │ │ │ ├── useSignPsbt.ts │ │ │ └── useSignPsbtValidationGate.ts │ │ ├── signRuneDelistingMessage │ │ │ └── index.tsx │ │ ├── signatureRequest │ │ │ ├── clarityMessageView.tsx │ │ │ ├── collapsableContainer.tsx │ │ │ ├── index.styled.ts │ │ │ ├── index.tsx │ │ │ ├── signatureRequestMessage.tsx │ │ │ ├── signatureRequestStructuredData.tsx │ │ │ └── utils.ts │ │ ├── speedUpTransaction │ │ │ ├── customFee │ │ │ │ ├── index.styled.ts │ │ │ │ └── index.tsx │ │ │ ├── index.styled.ts │ │ │ └── index.tsx │ │ ├── stacking │ │ │ ├── index.tsx │ │ │ ├── stackingProgress │ │ │ │ ├── components │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── stackingStatusTile.tsx │ │ │ └── startStacking │ │ │ │ ├── components │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── stackInfoTile.tsx │ │ ├── stxSignTransactions │ │ │ ├── components │ │ │ │ ├── controls.tsx │ │ │ │ └── getPopupPayload │ │ │ │ │ ├── components │ │ │ │ │ ├── loader │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ └── signingFlow │ │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ │ ├── broadcastError.tsx │ │ │ │ │ │ │ │ ├── review │ │ │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ │ │ │ │ ├── feeEditor.tsx │ │ │ │ │ │ │ │ │ │ ├── header.tsx │ │ │ │ │ │ │ │ │ │ ├── transactionDetails │ │ │ │ │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ │ │ │ │ │ ├── card │ │ │ │ │ │ │ │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ │ │ │ │ │ ├── contractCallDetails │ │ │ │ │ │ │ │ │ │ │ │ │ ├── callSummary │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── callSummaryLayout │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── copyableText.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── hooks.ts │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── callSummaryLoader │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── edit.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── fee │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── feeLoader.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── feeLogic.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── sponsored.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ │ │ │ │ │ │ │ ├── hooks.ts │ │ │ │ │ │ │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ ├── noTransfersMessage.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ └── postConditions │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── stxPostCondition │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── stxPostCondition.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── fungibleTokenPostCondition.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── nftPostCondition.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── styles.tsx │ │ │ │ │ │ │ │ │ │ │ │ │ │ └── utils.ts │ │ │ │ │ │ │ │ │ │ │ │ ├── notYetSupported.tsx │ │ │ │ │ │ │ │ │ │ │ │ ├── smartContractDetails.tsx │ │ │ │ │ │ │ │ │ │ │ │ ├── tokenTransferDetails.tsx │ │ │ │ │ │ │ │ │ │ │ │ └── versionedSmartContractDetails.tsx │ │ │ │ │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ │ │ │ │ ├── styles.tsx │ │ │ │ │ │ │ │ │ │ │ ├── transactionTypeToComponentMap.ts │ │ │ │ │ │ │ │ │ │ │ ├── types.ts │ │ │ │ │ │ │ │ │ │ │ └── utils.ts │ │ │ │ │ │ │ │ │ │ └── utils.ts │ │ │ │ │ │ │ │ │ ├── hooks.ts │ │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ │ ├── shared │ │ │ │ │ │ │ │ │ ├── actions │ │ │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ │ │ └── final.tsx │ │ │ │ │ │ │ │ ├── signLedger │ │ │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ │ │ │ ├── connectLedger │ │ │ │ │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ │ │ │ │ │ ├── connecting.tsx │ │ │ │ │ │ │ │ │ │ │ │ ├── error.tsx │ │ │ │ │ │ │ │ │ │ │ │ ├── initial.tsx │ │ │ │ │ │ │ │ │ │ │ │ ├── shared │ │ │ │ │ │ │ │ │ │ │ │ │ └── layout.tsx │ │ │ │ │ │ │ │ │ │ │ │ └── success.tsx │ │ │ │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ │ │ │ └── signingTransactions.tsx │ │ │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ │ │ ├── signSuccess.tsx │ │ │ │ │ │ │ │ └── signingError.tsx │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── popupPayloadError.tsx │ │ │ │ │ └── index.tsx │ │ │ ├── hooks.ts │ │ │ ├── index.tsx │ │ │ ├── styles.ts │ │ │ └── utils.ts │ │ ├── swap │ │ │ ├── components │ │ │ │ ├── amountInput │ │ │ │ │ └── index.tsx │ │ │ │ ├── psbtConfirmation │ │ │ │ │ ├── psbtConfirmation.tsx │ │ │ │ │ ├── useExecuteOrder.tsx │ │ │ │ │ └── useExecuteUtxoOrder.tsx │ │ │ │ ├── routeItem │ │ │ │ │ └── index.tsx │ │ │ │ ├── tokenFromBottomSheet │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── useFromTokens.ts │ │ │ │ └── tokenToBottomSheet │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── useToTokens.ts │ │ │ ├── index.tsx │ │ │ ├── mixpanel.ts │ │ │ ├── quoteSummary │ │ │ │ ├── EditFee.tsx │ │ │ │ ├── index.styled.ts │ │ │ │ ├── index.tsx │ │ │ │ ├── quoteSummaryTile.tsx │ │ │ │ ├── usePlaceOrder.tsx │ │ │ │ └── usePlaceUtxoOrder.tsx │ │ │ ├── quotesModal │ │ │ │ ├── index.tsx │ │ │ │ └── quoteTile.tsx │ │ │ ├── slippageModal │ │ │ │ └── index.tsx │ │ │ ├── types.ts │ │ │ ├── useMasterCoinsList.tsx │ │ │ ├── useVisibleMasterCoinsList.tsx │ │ │ ├── utils.ts │ │ │ └── utxoSelection │ │ │ │ ├── index.styled.ts │ │ │ │ ├── index.tsx │ │ │ │ └── utxoItem.tsx │ │ ├── transactionRequest │ │ │ ├── index.tsx │ │ │ └── useStxTransactionRequest │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ ├── transactionStatus │ │ │ ├── index.tsx │ │ │ ├── marketplaceRuneListingResults.tsx │ │ │ └── multipleMarketplaceListingResult.tsx │ │ ├── transferRunesRequest │ │ │ ├── index.tsx │ │ │ └── useTransferRunesRequest.ts │ │ ├── unlistRune │ │ │ ├── index.styled.ts │ │ │ ├── index.tsx │ │ │ └── unlistRuneItemPerMarketplace.tsx │ │ ├── unlistRuneUtxo │ │ │ └── index.tsx │ │ └── walletExists │ │ │ └── index.tsx │ ├── stores │ │ ├── index.ts │ │ ├── nftData │ │ │ ├── actions │ │ │ │ ├── actionCreator.ts │ │ │ │ └── types.ts │ │ │ └── reducer.ts │ │ └── wallet │ │ │ ├── actions │ │ │ ├── actionCreators.ts │ │ │ ├── migrationTypes.ts │ │ │ └── types.ts │ │ │ └── reducer.ts │ ├── ui-components │ │ ├── btcAmountSelector.tsx │ │ └── selectFeeRate │ │ │ ├── feeItem.tsx │ │ │ ├── feeSelectPopup.tsx │ │ │ └── index.tsx │ ├── ui-library │ │ ├── avatar.tsx │ │ ├── button.tsx │ │ ├── callout.tsx │ │ ├── checkbox.tsx │ │ ├── common.styled.ts │ │ ├── crossButton.tsx │ │ ├── dialog.tsx │ │ ├── divider.tsx │ │ ├── input.tsx │ │ ├── inputFeedback.tsx │ │ ├── sheet.tsx │ │ ├── snackBar.tsx │ │ ├── spinner.tsx │ │ ├── tabs.tsx │ │ └── toggle.tsx │ └── utils │ │ ├── alertTracker.ts │ │ ├── brc20.ts │ │ ├── chromeLocalStorage.ts │ │ ├── chromeStorage.ts │ │ ├── constants.ts │ │ ├── currency.ts │ │ ├── date.ts │ │ ├── encryptionUtils.ts │ │ ├── gradient.ts │ │ ├── helper.ts │ │ ├── inscriptions.ts │ │ ├── keystone.ts │ │ ├── ledger.ts │ │ ├── localStorage.ts │ │ ├── mappers.ts │ │ ├── mixpanel.ts │ │ ├── nfts.ts │ │ ├── query.ts │ │ ├── rareSats.ts │ │ ├── runes.ts │ │ ├── tokens.ts │ │ └── transactions │ │ └── transactions.ts ├── assets │ ├── animation │ │ └── full_logo_horizontal.json │ ├── fonts │ │ ├── DMSans-Bold.ttf │ │ ├── DMSans-Medium.ttf │ │ ├── DMSans-Regular.ttf │ │ ├── IBMPlexSans-Bold.ttf │ │ ├── IBMPlexSans-Medium.ttf │ │ ├── IBMPlexSans-Regular.ttf │ │ ├── Satoshi-Black.otf │ │ ├── Satoshi-Bold.otf │ │ ├── Satoshi-Medium.otf │ │ └── Satoshi-Regular.otf │ └── img │ │ ├── Copy.svg │ │ ├── ErrorBoundary │ │ └── error.svg │ │ ├── Warning.svg │ │ ├── Warning_red.svg │ │ ├── arrow_left.svg │ │ ├── arrow_square_out.svg │ │ ├── backupWallet │ │ └── backup.svg │ │ ├── bottomTabBar │ │ ├── nft_tab.svg │ │ ├── setting_tab.svg │ │ ├── stacking_tab.svg │ │ ├── unselected_nft_tab.svg │ │ ├── unselected_setting_tab.svg │ │ ├── unselected_stacking_tab.svg │ │ ├── unselected_wallet_tab.svg │ │ └── wallet_tab.svg │ │ ├── btcFlashy.svg │ │ ├── checkmark-bold.svg │ │ ├── checkmarkIcon.svg │ │ ├── createPassword │ │ ├── Eye.svg │ │ ├── EyeSlash.svg │ │ └── Password.svg │ │ ├── createWalletSuccess │ │ ├── CheckCircle.svg │ │ ├── extension.svg │ │ ├── logo.svg │ │ └── pin.svg │ │ ├── dashboard-icon.svg │ │ ├── dashboard │ │ ├── Copy.svg │ │ ├── X.svg │ │ ├── add_token.svg │ │ ├── arrow_down.svg │ │ ├── arrow_down_left.svg │ │ ├── arrow_left.svg │ │ ├── arrow_up.svg │ │ ├── arrow_up_right.svg │ │ ├── binance.svg │ │ ├── bitcoin_icon.svg │ │ ├── bitcoin_token.svg │ │ ├── black_plus.svg │ │ ├── caret_right.svg │ │ ├── coin_token.svg │ │ ├── copy_black_icon.svg │ │ ├── credit_card.svg │ │ ├── faders_horizontal.svg │ │ ├── list.svg │ │ ├── mia_token.svg │ │ ├── moonpay.svg │ │ ├── nyc_token.svg │ │ ├── ordinalBRC20.svg │ │ ├── paypal.svg │ │ ├── stx_icon.svg │ │ ├── tick.svg │ │ ├── transak.svg │ │ └── xverse_swaps_logo.svg │ │ ├── dots_three_vertical.svg │ │ ├── full_logo_horizontal.svg │ │ ├── full_logo_vertical.svg │ │ ├── hw │ │ ├── account_switch.svg │ │ ├── connect_hw.svg │ │ ├── keystone │ │ │ ├── keystone_badge.svg │ │ │ ├── keystone_connect_default.svg │ │ │ ├── keystone_icon.svg │ │ │ ├── keystone_import_connect_btc.svg │ │ │ ├── keystone_import_connect_done.svg │ │ │ ├── keystone_import_connect_fail.svg │ │ │ └── keystone_import_start.svg │ │ └── ledger │ │ │ ├── arrow_left_icon.svg │ │ │ ├── btc_icon.svg │ │ │ ├── btc_ordinals_icon.svg │ │ │ ├── check_circle.svg │ │ │ ├── info_icon.svg │ │ │ ├── ledger_badge.svg │ │ │ ├── ledger_confirm_ordinals.svg │ │ │ ├── ledger_connect_default.svg │ │ │ ├── ledger_icon.svg │ │ │ ├── ledger_import_connect_btc.svg │ │ │ ├── ledger_import_connect_done.svg │ │ │ ├── ledger_import_connect_fail.svg │ │ │ ├── ledger_import_connect_stx.svg │ │ │ ├── ledger_import_end.svg │ │ │ ├── ledger_import_start.svg │ │ │ ├── ordinals_icon.svg │ │ │ └── ordinals_icon_big.svg │ │ ├── icons │ │ └── ArrowSwap.svg │ │ ├── info.svg │ │ ├── landing │ │ ├── onboarding1.svg │ │ └── onboarding2.svg │ │ ├── linkIcon.svg │ │ ├── listings │ │ ├── CheckCircle.svg │ │ └── XCircle.svg │ │ ├── manageTokens │ │ └── runes_coming_soon.svg │ │ ├── nftDashboard │ │ ├── ArrowUpRight.svg │ │ ├── Copy.svg │ │ ├── Printer.svg │ │ ├── QrCode.svg │ │ ├── RocketLaunch.svg │ │ ├── bns.svg │ │ ├── code.svg │ │ ├── globe.svg │ │ ├── nft_fallback.svg │ │ ├── ordinals_icon.svg │ │ ├── rareSats │ │ │ ├── 1Dpali.png │ │ │ ├── 1stx.png │ │ │ ├── 2Dpali.png │ │ │ ├── 3Dpali.png │ │ │ ├── alpha.png │ │ │ ├── b286.png │ │ │ ├── b78.png │ │ │ ├── b9.png │ │ │ ├── b9_450.png │ │ │ ├── black_epic.png │ │ │ ├── black_legendary.png │ │ │ ├── black_rare.png │ │ │ ├── black_uncommon.png │ │ │ ├── epic.png │ │ │ ├── fibonacci.png │ │ │ ├── hitman.png │ │ │ ├── jpeg.png │ │ │ ├── legacy.png │ │ │ ├── legendary.png │ │ │ ├── mythic.png │ │ │ ├── nakamoto.png │ │ │ ├── namepali.png │ │ │ ├── omega.png │ │ │ ├── pali.png │ │ │ ├── paliblock.png │ │ │ ├── perfectpaliception.png │ │ │ ├── pizza.png │ │ │ ├── rare.png │ │ │ ├── seqpali.png │ │ │ ├── silkroad.png │ │ │ ├── uncommon.png │ │ │ ├── unknown.png │ │ │ └── vintage.png │ │ ├── rune_icon.svg │ │ ├── share.svg │ │ ├── shareNft │ │ │ ├── Envelope.svg │ │ │ ├── Link.svg │ │ │ ├── Vector.svg │ │ │ └── facebook-f.svg │ │ ├── share_network.svg │ │ ├── squares_four.svg │ │ └── white_ordinals_icon.svg │ │ ├── rareSats │ │ ├── ic_ordinal_small.svg │ │ ├── ic_ordinal_small_over_card.svg │ │ ├── link.svg │ │ └── satBundle.svg │ │ ├── receive_btc_image.svg │ │ ├── receive_ordinals_image.svg │ │ ├── send │ │ ├── check_circle.svg │ │ ├── ic_sats_ticker.svg │ │ ├── info.svg │ │ ├── info_circle.svg │ │ ├── switch.svg │ │ └── x_circle.svg │ │ ├── settings │ │ ├── Timer15m.svg │ │ ├── Timer1h.svg │ │ ├── Timer30m.svg │ │ ├── Timer3h.svg │ │ ├── arrow.svg │ │ ├── check_circle.svg │ │ ├── check_square.svg │ │ ├── currencies │ │ │ ├── ars.svg │ │ │ ├── aus.svg │ │ │ ├── bra.svg │ │ │ ├── can.svg │ │ │ ├── che.svg │ │ │ ├── chn.svg │ │ │ ├── cop.svg │ │ │ ├── dop.svg │ │ │ ├── eu.svg │ │ │ ├── gbr.svg │ │ │ ├── gtq.svg │ │ │ ├── hkg.svg │ │ │ ├── hun.svg │ │ │ ├── idn.svg │ │ │ ├── ind.svg │ │ │ ├── jpn.svg │ │ │ ├── kor.svg │ │ │ ├── mex.svg │ │ │ ├── mys.svg │ │ │ ├── nga.svg │ │ │ ├── pak.svg │ │ │ ├── pen.svg │ │ │ ├── phl.svg │ │ │ ├── pol.svg │ │ │ ├── ron.svg │ │ │ ├── rus.svg │ │ │ ├── sgp.svg │ │ │ ├── tha.svg │ │ │ ├── tur.svg │ │ │ ├── twn.svg │ │ │ ├── usa.svg │ │ │ ├── vnm.svg │ │ │ └── zaf.svg │ │ ├── tick.svg │ │ └── x.svg │ │ ├── swap │ │ ├── address.svg │ │ ├── arrow_swap.svg │ │ ├── chevron.svg │ │ ├── copy.svg │ │ ├── fold_arrow_down.svg │ │ ├── fold_arrow_up.svg │ │ └── slippageEdit.svg │ │ ├── tick.svg │ │ ├── transactions │ │ ├── ArrowDown.svg │ │ ├── Assets.svg │ │ ├── CircleWavyCheck.svg │ │ ├── Copy.svg │ │ ├── Lock.svg │ │ ├── Minus.svg │ │ ├── Plus.svg │ │ ├── ScriptIcon.svg │ │ ├── address.svg │ │ ├── arrowUpRed.svg │ │ ├── burned.svg │ │ ├── contract.svg │ │ ├── dropDownIcon.svg │ │ ├── failed.svg │ │ ├── microBlock.svg │ │ ├── ordinal.svg │ │ ├── output.svg │ │ ├── pending.svg │ │ ├── received.svg │ │ ├── runes.svg │ │ ├── sent.svg │ │ └── wallet.svg │ │ ├── user_circle_slashed.svg │ │ ├── warningIcon.svg │ │ ├── warning_circle.svg │ │ ├── webInteractions │ │ ├── ArrowLineDown.svg │ │ ├── ShareNetwork.svg │ │ ├── bnsCall.svg │ │ ├── contractCall.svg │ │ ├── deploy_contract.svg │ │ ├── goto-explorer.svg │ │ ├── nftCall.svg │ │ ├── signatureIcon.svg │ │ └── swapCall.svg │ │ ├── xverse_icon.png │ │ └── xverse_logo.svg ├── background │ └── background.ts ├── common │ ├── types │ │ ├── inpage-types.ts │ │ ├── ledger.ts │ │ ├── message-types.ts │ │ └── messages.ts │ ├── utils │ │ ├── getEventSourceWindow.ts │ │ ├── getSelectedAccount.ts │ │ ├── index.ts │ │ ├── keystone.ts │ │ ├── ledger.ts │ │ ├── legacy-external-message-handler.ts │ │ ├── messageHandlers.ts │ │ ├── messages │ │ │ └── extensionToContentScript │ │ │ │ ├── README.md │ │ │ │ ├── dispatchEvent │ │ │ │ ├── README.md │ │ │ │ └── index.ts │ │ │ │ ├── message-flow.png │ │ │ │ ├── schemas.ts │ │ │ │ └── utils │ │ │ │ └── index.ts │ │ ├── permissionsStore.ts │ │ ├── popup-center.ts │ │ ├── popup │ │ │ ├── README.md │ │ │ └── index.ts │ │ ├── promises.ts │ │ ├── route-urls.ts │ │ ├── rpc │ │ │ ├── README.md │ │ │ ├── btc │ │ │ │ ├── getAccounts.ts │ │ │ │ ├── getAddresses │ │ │ │ │ ├── getAddresses.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── getBalance.ts │ │ │ │ ├── index.ts │ │ │ │ ├── sendTransfer.ts │ │ │ │ ├── signMessage.ts │ │ │ │ └── signPsbt.ts │ │ │ ├── getInfo.ts │ │ │ ├── handleInvalidMessage.ts │ │ │ ├── handlerRouter │ │ │ │ ├── bitcoin.ts │ │ │ │ ├── common.ts │ │ │ │ ├── index.ts │ │ │ │ ├── ordinals.ts │ │ │ │ ├── runes.ts │ │ │ │ ├── stacks.ts │ │ │ │ └── wallet.ts │ │ │ ├── helpers.ts │ │ │ ├── index.ts │ │ │ ├── messageMiddlewares.ts │ │ │ ├── ordinals │ │ │ │ ├── getInscriptions.ts │ │ │ │ └── sendInscriptions.ts │ │ │ ├── responseMessages │ │ │ │ ├── bitcoin.ts │ │ │ │ ├── errors.ts │ │ │ │ ├── stacks.ts │ │ │ │ ├── types.ts │ │ │ │ └── wallet.ts │ │ │ ├── runes │ │ │ │ ├── etch.ts │ │ │ │ ├── getBalance.ts │ │ │ │ ├── mint.ts │ │ │ │ └── transfer.ts │ │ │ ├── stx │ │ │ │ ├── callContract │ │ │ │ │ └── index.ts │ │ │ │ ├── deployContract │ │ │ │ │ └── index.ts │ │ │ │ ├── getAccounts │ │ │ │ │ └── index.ts │ │ │ │ ├── getAddresses │ │ │ │ │ └── index.ts │ │ │ │ ├── signMessage │ │ │ │ │ └── index.ts │ │ │ │ ├── signStructuredMessage │ │ │ │ │ └── index.ts │ │ │ │ ├── signTransaction │ │ │ │ │ ├── index.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── signTransactions │ │ │ │ │ └── index.ts │ │ │ │ └── transferStx │ │ │ │ │ └── index.ts │ │ │ └── wallet │ │ │ │ ├── connect.ts │ │ │ │ ├── disconnect.ts │ │ │ │ ├── getAccount.ts │ │ │ │ ├── getCurrentPermissions.ts │ │ │ │ ├── getNetwork.ts │ │ │ │ ├── getWalletType.ts │ │ │ │ ├── renouncePermissions.ts │ │ │ │ └── requestPermissions.ts │ │ └── safe.ts │ └── walletEvents.ts ├── content-scripts │ ├── README.md │ └── content-script.ts ├── inpage │ ├── eventHandlers.ts │ ├── index.ts │ ├── sats.inpage.ts │ ├── stacks.inpage.ts │ └── utils.ts ├── locales │ ├── en.json │ └── index.ts ├── manifest.json ├── pages │ ├── Options │ │ ├── index.css │ │ ├── index.html │ │ └── index.tsx │ └── Popup │ │ ├── index.css │ │ ├── index.html │ │ └── index.tsx ├── react-app-env.d.ts ├── styled.d.ts └── theme │ ├── font.css │ ├── global.ts │ └── index.ts ├── tests ├── fixtures │ ├── base.ts │ ├── helpers.ts │ └── passwordTestData.ts ├── pages │ ├── landing.ts │ ├── onboarding.ts │ └── wallet.ts └── specs │ ├── createWallet.spec.ts │ ├── healthcheck.spec.ts │ ├── managementAccount.spec.ts │ ├── managementToken.spec.ts │ ├── onboarding.spec.ts │ ├── runesList.spec.ts │ ├── runesSend.spec.ts │ ├── runesUnlisting.spec.ts │ ├── swapExchange.spec.ts │ ├── swapME.spec.ts │ ├── swapSip10Velar.spec.ts │ ├── swapUSCancel.spec.ts │ ├── swapVelar.spec.ts │ ├── swapVisuals.spec.ts │ ├── tabCollectiblesInscriptions.spec.ts │ ├── tabCollectiblesRareSats.spec.ts │ ├── tabExplore.spec.ts │ ├── tabSettings.spec.ts │ ├── tabStacking.spec.ts │ ├── transactionBRC20.spec.ts │ ├── transactionBTC.spec.ts │ ├── transactionHistory.spec.ts │ └── transactionSTX.spec.ts ├── tsconfig.json └── webpack ├── makeConfig.js ├── utils ├── build.js ├── devServer.js └── env.js └── webpack.config.js /.env.example: -------------------------------------------------------------------------------- 1 | # Behavior 2 | SKIP_ANIMATION_WALLET_STARTUP=false; 3 | 4 | # Providers 5 | TRANSAC_API_KEY= 6 | MOON_PAY_API_KEY= 7 | 8 | # Analytics 9 | MIX_PANEL_TOKEN= 10 | MIX_PANEL_EXPLORE_APP_TOKEN= 11 | 12 | # only needed for E2E test 13 | # SEED_WORDS1= [] 14 | # SEED_WORDS2= [] 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules/ 3 | **/*.js -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @secretkeylabs/reviewers-web-extension 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug template 3 | about: Template for bug report. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Component 11 | `Insert screen/component of this bug is related.` 12 | 13 | # Severity 14 | `Choose one`: 15 | - `Critical (Application crash, Loss of data)` 16 | - `Major (Unable to proceed on a journey)` 17 | - `Trivial (Some UI issue)` 18 | 19 | # Summary 20 | `Insert a TL;DR sentence describing the bug.` 21 | 22 | # Reproduce steps 23 | 1. 24 | 2. 25 | 3. 26 | 27 | # Expected result 28 | `Insert expected behaviour.` 29 | 30 | # Actual result 31 | `Insert actual behaviour.` 32 | 33 | # Remark 34 | `(Optional)` -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/chores-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Chores template 3 | about: Template for development chores. 4 | title: '' 5 | labels: chores 6 | assignees: '' 7 | 8 | --- 9 | 10 | # What to implement 11 | 12 | # How to implement 13 | 14 | # Resource 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement template 3 | about: Template for user story. 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Story 11 | **As a** `insert subject here` 12 | **I want to** `insert demand here` 13 | **so that** `insert benefit here`. 14 | # Acceptance Criteria 15 | ## Prerequisite 16 | ### Scenario `N` 17 | **Given** `insert pre requisites here` 18 | **When** `insert triggering point here` 19 | **Then** `insert result here` 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/idea-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Idea template 3 | about: Template for new ideas. 4 | title: '' 5 | labels: idea 6 | assignees: '' 7 | 8 | --- -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/bump-version-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bump version template 3 | about: Template for bumping version after release. 4 | title: 'Bumping to {version_number}' 5 | assignees: '' 6 | 7 | --- 8 | 9 | ## Checklist 10 | 11 | - [ ] Change version number 12 | - [ ] Reset build number -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pre-release-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Pre-release template 3 | about: Template for pre-release. Develop -> Master. 4 | title: 'Pre-releasing {version_number}' 5 | labels: pre-release 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Checklist 11 | 12 | - [ ] Bump version PR merged #`Insert PR number` 13 | - [ ] CHANGELOG Generated 14 | - [ ] CHANGELOG unreleased tag changed -------------------------------------------------------------------------------- /.github/workflows/merge-repos.yml: -------------------------------------------------------------------------------- 1 | name: Merge branch to remote 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | branch: 7 | description: 'Merge branch' 8 | required: true 9 | default: develop 10 | type: choice 11 | options: 12 | - develop 13 | - main 14 | 15 | jobs: 16 | merge-branch-to-remote: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | pull-requests: write 20 | contents: write 21 | steps: 22 | - uses: actions/checkout@v4 23 | - id: run-merge-script 24 | env: 25 | ORIGIN_BRANCH: ${{ inputs.branch }} 26 | REMOTE_REPO: ${{ secrets.REMOTE_REPO }} 27 | GH_TOKEN: ${{ secrets.GH_REPOS_RW_PAT }} 28 | run: | 29 | # git config 30 | git config user.name "GitHub Actions Bot" 31 | git config user.email "<>" 32 | # run shell script 33 | cd scripts 34 | ORIGIN_BRANCH=$ORIGIN_BRANCH REMOTE_REPO=$REMOTE_REPO ./merge-to-remote.sh 35 | -------------------------------------------------------------------------------- /.github/workflows/release-develop.yml: -------------------------------------------------------------------------------- 1 | name: Merge release to develop 2 | ## 3 | # This workflow triggers on merge of release branch back to develop 4 | # 5 | # It should push to public repo 6 | # 7 | on: 8 | pull_request: 9 | branches: 10 | - develop 11 | types: 12 | - closed 13 | jobs: 14 | publish-latest: 15 | if: ${{ github.event.pull_request.merged == true && startsWith(github.head_ref, 'release/')}} 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: write 19 | pull-requests: write 20 | env: 21 | GH_TOKEN: ${{ github.token }} 22 | steps: 23 | - uses: actions/checkout@v4 24 | - id: push-to-public 25 | name: Push to public remote 26 | if: ${{ contains(github.repositoryUrl, 'private') }} 27 | run: | 28 | # git config 29 | git config user.name "GitHub Actions Bot" 30 | git config user.email "<>" 31 | # run shell script 32 | cd scripts 33 | ORIGIN_BRANCH=develop REMOTE_REPO=xverse-web-extension ./merge-to-remote.sh 34 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run knip 5 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-organize-imports"], 3 | "organizeImportsSkipDestructiveCodeActions": true, 4 | "arrowParens": "always", 5 | "bracketSpacing": true, 6 | "jsxBracketSameLine": false, 7 | "jsxSingleQuote": false, 8 | "quoteProps": "as-needed", 9 | "singleQuote": true, 10 | "semi": true, 11 | "printWidth": 100, 12 | "useTabs": false, 13 | "tabWidth": 2, 14 | "trailingComma": "all" 15 | } 16 | -------------------------------------------------------------------------------- /knip.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/knip@5/schema.json", 3 | "entry": [ 4 | "src/background/background.ts", 5 | "src/content-scripts/content-script.ts", 6 | "src/inpage/index.ts", 7 | "src/pages/Options/index.tsx", 8 | "src/pages/Popup/index.tsx", 9 | "webpack/utils/build.js", 10 | "webpack/utils/devServer.js" 11 | ], 12 | "project": [ 13 | "src/**/*.{ts,tsx}", 14 | "webpack/**/*.js" 15 | ], 16 | "webpack": { 17 | "config": [ 18 | "webpack/webpack.config.js" 19 | ] 20 | }, 21 | "ignoreDependencies": [ 22 | // Used by webpack to polyfill references to Node.js's `path` module, yet 23 | // not explicitly imported anywhere in the project. 24 | "path", 25 | // Used by the extension, yet not explicitly imported anywhere in the 26 | // project. 27 | "@types/chrome", 28 | // Used for linking xverse core locally 29 | "process" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | release.json 2 | pr*.json 3 | body.md 4 | releases.json 5 | -------------------------------------------------------------------------------- /scripts/find-tag.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | ## 4 | # find-tag.sh 5 | # 6 | # a util for looking through a list of github releases and exporting the next tag 7 | # 8 | if [[ -z "$TAG" ]]; then 9 | echo "TAG is required. e.g. v0.26.0" 10 | exit 1 11 | fi 12 | 13 | if cat releases.json | jq -r '.[].tag_name' | grep $TAG; then 14 | echo found releases matching $TAG 15 | 16 | for i in $(cat releases.json | jq -r '.[].tag_name' | grep $TAG); do 17 | LATEST_RUNNING=$(echo $i | grep rc | sed 's/.*-rc.\(.*\)/\1/') 18 | 19 | if [[ -z "$LATEST_RC" || $LATEST_RUNNING -gt $LATEST_RC ]]; then 20 | LATEST_RC=$LATEST_RUNNING 21 | fi 22 | done 23 | 24 | if [[ -z "$LATEST_RC" ]]; then 25 | echo $TAG was already released 26 | exit 1; 27 | elif [[ -n "$LATEST_RC" ]]; then 28 | echo incrementing rc 29 | NEXT_TAG="$TAG-rc.$((LATEST_RC+1))" 30 | fi 31 | else 32 | echo no releases matching $TAG yet 33 | NEXT_TAG="$TAG-rc.0" 34 | fi 35 | 36 | echo next tag will be $NEXT_TAG 37 | export NEXT_TAG=$NEXT_TAG 38 | -------------------------------------------------------------------------------- /src/app/components/checkBox/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | interface CheckBoxProps { 4 | isChecked: boolean; 5 | checkBoxId: string; 6 | checkBoxLabel: string; 7 | onCheck: () => void; 8 | } 9 | 10 | const CheckBoxWrapper = styled.div((props) => ({ 11 | ...props.theme.typography.body_medium_m, 12 | color: props.theme.colors.white_0, 13 | label: { 14 | marginLeft: props.theme.spacing(5), 15 | }, 16 | '> input, > label': { 17 | cursor: 'pointer', 18 | }, 19 | })); 20 | 21 | function CheckBox(props: CheckBoxProps): JSX.Element { 22 | const { isChecked, checkBoxId, checkBoxLabel, onCheck } = props; 23 | return ( 24 | 25 | 26 | 27 | 28 | ); 29 | } 30 | 31 | export default CheckBox; 32 | -------------------------------------------------------------------------------- /src/app/components/confirmBtcTransaction/sections/inscribeSection/ContentLabel/common.ts: -------------------------------------------------------------------------------- 1 | export enum ContentType { 2 | SVG = 'SVG', 3 | IMAGE = 'IMAGE', 4 | HTML = 'HTML', 5 | TEXT = 'TEXT', 6 | MARKDOWN = 'MARKDOWN', 7 | JSON = 'JSON', 8 | VIDEO = 'VIDEO', 9 | AUDIO = 'AUDIO', 10 | OTHER = 'OTHER', 11 | } 12 | -------------------------------------------------------------------------------- /src/app/components/formattedNumber/index.tsx: -------------------------------------------------------------------------------- 1 | import type { FormattedBalance } from '@secretkeylabs/xverse-core'; 2 | import styled from 'styled-components'; 3 | 4 | const Container = styled.div` 5 | display: inline-flex; 6 | `; 7 | 8 | const Subscript = styled.span` 9 | padding-top: 8px; 10 | font-size: 0.7em; 11 | `; 12 | 13 | type FormattedNumberProps = { 14 | number: FormattedBalance; 15 | tokenSymbol?: string; 16 | }; 17 | 18 | function FormattedNumber({ number, tokenSymbol }: FormattedNumberProps) { 19 | const { prefix, suffix } = number; 20 | return ( 21 | 22 | {prefix} 23 | {suffix && ( 24 | <> 25 | {suffix.subscript} 26 | {suffix.value} 27 | 28 | )} 29 | {tokenSymbol && ` ${tokenSymbol}`} 30 | 31 | ); 32 | } 33 | 34 | export default FormattedNumber; 35 | -------------------------------------------------------------------------------- /src/app/components/guards/onboarding/WalletExistsContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react'; 2 | 3 | export type WalletExistsContextProps = { 4 | disableWalletExistsGuard?: () => void; 5 | }; 6 | 7 | export const WalletExistsContext = createContext({}); 8 | 9 | export const useWalletExistsContext = (): WalletExistsContextProps => { 10 | const context = useContext(WalletExistsContext); 11 | return context; 12 | }; 13 | -------------------------------------------------------------------------------- /src/app/components/guards/walletCloseGuard.tsx: -------------------------------------------------------------------------------- 1 | import { useSingleTabGuard } from '@components/guards/singleTab'; 2 | import type { PropsWithChildren } from 'react'; 3 | 4 | /** 5 | * This guard is used to close any open tabs when the wallet is locked or reset. 6 | * It should only be rendered at the root of the options page. 7 | */ 8 | function WalletCloseGuard({ children }: PropsWithChildren) { 9 | useSingleTabGuard('closeWallet', false); 10 | 11 | // fragment is required here because without it, the router thinks there could be more than 1 child node 12 | // eslint-disable-next-line react/jsx-no-useless-fragment 13 | return <>{children}; 14 | } 15 | 16 | export default WalletCloseGuard; 17 | -------------------------------------------------------------------------------- /src/app/components/inputScreen/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const Container = styled.div` 5 | flex: 1 1 100%; 6 | display: flex; 7 | flex-direction: column; 8 | justify-content: space-between; 9 | `; 10 | 11 | const ButtonsContainer = styled.div` 12 | margin: ${(props) => props.theme.space.l} 0; 13 | `; 14 | 15 | const InputsContainer = styled.div` 16 | display: flex; 17 | flex-direction: column; 18 | `; 19 | 20 | type InputScreenProps = { 21 | inputs: React.ReactNode; 22 | buttons: React.ReactNode; 23 | header?: React.ReactNode; 24 | }; 25 | 26 | function InputScreen({ inputs, buttons, header }: InputScreenProps) { 27 | return ( 28 | 29 |
30 | {header} 31 | {inputs} 32 |
33 | {buttons} 34 |
35 | ); 36 | } 37 | 38 | export default InputScreen; 39 | -------------------------------------------------------------------------------- /src/app/components/permissionsManager/globalStore.ts: -------------------------------------------------------------------------------- 1 | import { saveStore } from '@common/utils/permissionsStore'; 2 | import { type Permissions } from '@secretkeylabs/xverse-core'; 3 | 4 | let storeGlobal: Permissions.Store.PermissionsStore | null = null; 5 | 6 | export function setStoreGlobal(newStore: Permissions.Store.PermissionsStore) { 7 | storeGlobal = newStore; 8 | } 9 | 10 | export function saveStoreGlobal(newStore: Permissions.Store.PermissionsStore) { 11 | storeGlobal = newStore; 12 | saveStore(newStore); 13 | } 14 | 15 | export function getStoreGlobal(): Permissions.Store.PermissionsStore { 16 | if (!storeGlobal) { 17 | throw new Error('Store has not been initialized'); 18 | } 19 | 20 | return storeGlobal; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/components/postCondition/ftPostConditionCard.tsx: -------------------------------------------------------------------------------- 1 | import type { Coin } from '@secretkeylabs/xverse-core'; 2 | import type { PostConditionWire } from '@stacks/transactions'; 3 | import { ftDecimals } from '@utils/helper'; 4 | import PostConditionsView from './postConditionView'; 5 | import { getAmountFromPostCondition } from './postConditionView/helper'; 6 | 7 | interface Props { 8 | postCondition: PostConditionWire; 9 | ftMetaData?: Coin; 10 | } 11 | function FtPostConditionCard({ postCondition, ftMetaData }: Props) { 12 | const amount = ftDecimals( 13 | getAmountFromPostCondition(postCondition) ?? 0, 14 | ftMetaData?.decimals ?? 0, 15 | ); 16 | return ( 17 | 18 | ); 19 | } 20 | 21 | export default FtPostConditionCard; 22 | -------------------------------------------------------------------------------- /src/app/components/postCondition/nftPostConditionCard.tsx: -------------------------------------------------------------------------------- 1 | import AssetIcon from '@assets/img/transactions/Assets.svg'; 2 | import type { PostConditionWire } from '@stacks/transactions'; 3 | import PostConditionsView from './postConditionView'; 4 | import { getAmountFromPostCondition } from './postConditionView/helper'; 5 | 6 | interface Props { 7 | postCondition: PostConditionWire; 8 | } 9 | function NftPostConditionCard({ postCondition }: Props) { 10 | const amount = getAmountFromPostCondition(postCondition) ?? ''; 11 | return ; 12 | } 13 | 14 | export default NftPostConditionCard; 15 | -------------------------------------------------------------------------------- /src/app/components/postCondition/stxPostConditionCard.tsx: -------------------------------------------------------------------------------- 1 | import IconStacks from '@assets/img/dashboard/stx_icon.svg'; 2 | import type { PostConditionWire } from '@stacks/transactions'; 3 | import PostConditionsView from './postConditionView'; 4 | import { getAmountFromPostCondition } from './postConditionView/helper'; 5 | 6 | interface Props { 7 | postCondition: PostConditionWire; 8 | } 9 | function StxPostConditionCard({ postCondition }: Props) { 10 | const amount = getAmountFromPostCondition(postCondition) ?? ''; 11 | 12 | return ; 13 | } 14 | 15 | export default StxPostConditionCard; 16 | -------------------------------------------------------------------------------- /src/app/components/preferredBtcAddress/addressTypeSelector.tsx: -------------------------------------------------------------------------------- 1 | import PreferredBtcAddressItem from '@components/preferredBtcAddressItem'; 2 | import useBtcAddressBalance from '@hooks/useBtcAddressBalance'; 3 | 4 | type Props = { 5 | title: string; 6 | address?: string; 7 | onClick: () => void; 8 | isSelected: boolean; 9 | }; 10 | 11 | function AddressTypeSelector({ title, onClick, address, isSelected }: Props) { 12 | const { data } = useBtcAddressBalance(address ?? ''); 13 | 14 | return ( 15 | 22 | ); 23 | } 24 | 25 | export default AddressTypeSelector; 26 | -------------------------------------------------------------------------------- /src/app/components/seedPhraseView/word.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const SeedWord = styled.p((props) => ({ 4 | ...props.theme.body_medium_m, 5 | marginTop: props.theme.spacing(16), 6 | })); 7 | 8 | const OrdinalNumber = styled.p((props) => ({ 9 | ...props.theme.body_medium_m, 10 | marginLeft: props.theme.spacing(7), 11 | marginRight: props.theme.spacing(1.5), 12 | marginTop: props.theme.spacing(16), 13 | color: props.theme.colors.white_400, 14 | })); 15 | 16 | const Container = styled.div({ 17 | display: 'flex', 18 | flexDirection: 'row', 19 | }); 20 | 21 | interface Props { 22 | index: number; 23 | word: string; 24 | isVisible: boolean; 25 | } 26 | 27 | function SeedPhraseWord({ index, word, isVisible }: Props) { 28 | return ( 29 | 30 | {index + 1}. 31 | 32 | {isVisible ? word : '••••••••'} 33 | 34 | 35 | ); 36 | } 37 | 38 | export default SeedPhraseWord; 39 | -------------------------------------------------------------------------------- /src/app/components/separator/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Separator = styled.hr` 4 | margin-top: ${(props) => props.theme.space.m}; 5 | border: none; 6 | border-top: 1px solid ${(props) => props.theme.colors.elevation3}; 7 | `; 8 | 9 | export default Separator; 10 | -------------------------------------------------------------------------------- /src/app/components/toaster/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import toast, { Toaster, useToasterStore } from 'react-hot-toast'; 3 | 4 | // this solution is from https://github.com/timolins/react-hot-toast/issues/31 5 | function useMaxToasts(max: number) { 6 | const { toasts } = useToasterStore(); 7 | 8 | useEffect(() => { 9 | toasts 10 | .filter((t) => t.visible) // Only consider visible toasts 11 | .filter((_, i) => i >= max) // Is toast index over limit? 12 | .forEach((t) => toast.remove(t.id)); // Dismiss 13 | }, [toasts, max]); 14 | } 15 | 16 | export default function ToasterComponent({ 17 | max = 3, 18 | ...props 19 | }: React.ComponentProps & { 20 | max?: number; 21 | }) { 22 | useMaxToasts(max); 23 | 24 | return ; 25 | } 26 | -------------------------------------------------------------------------------- /src/app/components/tooltip/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export type TooltipContext = { 4 | targetDiv?: HTMLDivElement; 5 | initialised: boolean; 6 | }; 7 | 8 | export const tooltipContext = createContext({ initialised: false }); 9 | -------------------------------------------------------------------------------- /src/app/components/tooltip/provider.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react'; 2 | import styled from 'styled-components'; 3 | import { tooltipContext, type TooltipContext } from './context'; 4 | 5 | const TooltipContainer = styled.div(() => ({ 6 | width: 0, 7 | height: 0, 8 | overflow: 'hidden', 9 | })); 10 | 11 | type Props = { 12 | children: React.ReactNode; 13 | }; 14 | 15 | export default function TooltipProvider({ children }: Props) { 16 | const tooltipParentRef = useRef(null); 17 | const [contextValue, setContextValue] = useState({ initialised: false }); 18 | 19 | useEffect(() => { 20 | setContextValue({ initialised: true, targetDiv: tooltipParentRef.current ?? undefined }); 21 | }, []); 22 | 23 | return ( 24 | 25 | {children} 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/app/hooks/apiClients/useBtcClient.ts: -------------------------------------------------------------------------------- 1 | import { BitcoinEsploraApiProvider } from '@secretkeylabs/xverse-core'; 2 | import { useMemo } from 'react'; 3 | import useWalletSelector from '../useWalletSelector'; 4 | 5 | const useBtcClient = () => { 6 | const { network } = useWalletSelector(); 7 | const { type, btcApiUrl, fallbackBtcApiUrl } = network; 8 | const url = btcApiUrl; 9 | 10 | const esploraInstance = useMemo( 11 | () => 12 | new BitcoinEsploraApiProvider({ 13 | url, 14 | fallbackUrl: fallbackBtcApiUrl, 15 | network: type, 16 | }), 17 | [url, fallbackBtcApiUrl, type], 18 | ); 19 | 20 | return esploraInstance; 21 | }; 22 | 23 | export default useBtcClient; 24 | -------------------------------------------------------------------------------- /src/app/hooks/apiClients/useOrdinalsApi.ts: -------------------------------------------------------------------------------- 1 | import { OrdinalsApi } from '@secretkeylabs/xverse-core'; 2 | import { useMemo } from 'react'; 3 | import useWalletSelector from '../useWalletSelector'; 4 | 5 | const useOrdinalsApi = () => { 6 | const { network } = useWalletSelector(); 7 | return useMemo( 8 | () => 9 | new OrdinalsApi({ 10 | network: network.type, 11 | }), 12 | [network.type], 13 | ); 14 | }; 15 | 16 | export default useOrdinalsApi; 17 | -------------------------------------------------------------------------------- /src/app/hooks/apiClients/useOrdinalsServiceApi.ts: -------------------------------------------------------------------------------- 1 | import { getOrdinalsServiceApiClient } from '@secretkeylabs/xverse-core'; 2 | import { useMemo } from 'react'; 3 | import useWalletSelector from '../useWalletSelector'; 4 | 5 | const useOrdinalsServiceApi = () => { 6 | const { network } = useWalletSelector(); 7 | return useMemo(() => getOrdinalsServiceApiClient(network.type), [network.type]); 8 | }; 9 | 10 | export default useOrdinalsServiceApi; 11 | -------------------------------------------------------------------------------- /src/app/hooks/apiClients/useRunesApi.ts: -------------------------------------------------------------------------------- 1 | import { getRunesClient } from '@secretkeylabs/xverse-core'; 2 | import { useMemo } from 'react'; 3 | import useWalletSelector from '../useWalletSelector'; 4 | 5 | const useRunesApi = () => { 6 | const { network } = useWalletSelector(); 7 | return useMemo(() => getRunesClient(network.type), [network.type]); 8 | }; 9 | 10 | export default useRunesApi; 11 | -------------------------------------------------------------------------------- /src/app/hooks/apiClients/useStacksApi.ts: -------------------------------------------------------------------------------- 1 | import useNetworkSelector from '@hooks/useNetwork'; 2 | import { StacksApiProvider } from '@secretkeylabs/xverse-core'; 3 | import { useMemo } from 'react'; 4 | 5 | const useStacksAPI = () => { 6 | const network = useNetworkSelector(); 7 | 8 | const StacksAPI = useMemo( 9 | () => 10 | new StacksApiProvider({ 11 | network, 12 | }), 13 | [network], 14 | ); 15 | 16 | return StacksAPI; 17 | }; 18 | 19 | export default useStacksAPI; 20 | -------------------------------------------------------------------------------- /src/app/hooks/apiClients/useXverseApi.ts: -------------------------------------------------------------------------------- 1 | import { getXverseApiClient } from '@secretkeylabs/xverse-core'; 2 | import { useMemo } from 'react'; 3 | import useWalletSelector from '../useWalletSelector'; 4 | 5 | const useXverseApi = () => { 6 | const { network } = useWalletSelector(); 7 | return useMemo(() => getXverseApiClient(network.type), [network.type]); 8 | }; 9 | 10 | export default useXverseApi; 11 | -------------------------------------------------------------------------------- /src/app/hooks/queries/ordinals/useInscriptionDetails.ts: -------------------------------------------------------------------------------- 1 | import useOrdinalsApi from '@hooks/apiClients/useOrdinalsApi'; 2 | import { useQuery } from '@tanstack/react-query'; 3 | 4 | const useInscriptionDetails = (id: string) => { 5 | const OrdinalsApi = useOrdinalsApi(); 6 | return useQuery({ 7 | queryKey: [`inscription-details-${id}`], 8 | queryFn: () => OrdinalsApi.getInscription(id), 9 | }); 10 | }; 11 | 12 | export default useInscriptionDetails; 13 | -------------------------------------------------------------------------------- /src/app/hooks/queries/runes/useRuneFloorPriceQuery.ts: -------------------------------------------------------------------------------- 1 | import useRunesApi from '@hooks/apiClients/useRunesApi'; 2 | import useWalletSelector from '@hooks/useWalletSelector'; 3 | import { useQuery } from '@tanstack/react-query'; 4 | import { useCallback } from 'react'; 5 | 6 | export default function useRuneFloorPriceQuery(runeName: string, backgroundRefetch = true) { 7 | const { network } = useWalletSelector(); 8 | const runesApi = useRunesApi(); 9 | const queryFn = useCallback( 10 | async () => 11 | runesApi 12 | .getRuneMarketData(runeName) 13 | .then((res) => Number(res.floorUnitPrice?.formatted ?? 0)), 14 | [runeName, runesApi], 15 | ); 16 | return useQuery({ 17 | refetchOnWindowFocus: backgroundRefetch, 18 | refetchOnReconnect: backgroundRefetch, 19 | queryKey: ['get-rune-floor-price', runeName], 20 | enabled: Boolean(runeName) && network.type === 'Mainnet', 21 | queryFn, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /src/app/hooks/queries/swaps/useGetUtxos.ts: -------------------------------------------------------------------------------- 1 | import useWalletSelector from '@hooks/useWalletSelector'; 2 | import { 3 | getXverseApiClient, 4 | type GetUtxosRequest, 5 | type GetUtxosResponse, 6 | } from '@secretkeylabs/xverse-core'; 7 | import { useQuery } from '@tanstack/react-query'; 8 | 9 | const useGetUtxos = (body: GetUtxosRequest | null) => { 10 | const { network } = useWalletSelector(); 11 | 12 | return useQuery({ 13 | queryKey: ['getUtxos', body, network.type], 14 | queryFn: async () => { 15 | if (!body) { 16 | throw new Error('Request body is null'); 17 | } 18 | const client = getXverseApiClient(network.type); 19 | const response = await client.swaps.getUtxos(body); 20 | return response; 21 | }, 22 | enabled: !!body, 23 | refetchOnWindowFocus: false, 24 | refetchOnMount: true, 25 | refetchOnReconnect: true, 26 | }); 27 | }; 28 | 29 | export default useGetUtxos; 30 | -------------------------------------------------------------------------------- /src/app/hooks/queries/useAppConfig.ts: -------------------------------------------------------------------------------- 1 | import useWalletSelector from '@hooks/useWalletSelector'; 2 | import { getAppConfig } from '@secretkeylabs/xverse-core'; 3 | import { useQuery } from '@tanstack/react-query'; 4 | 5 | const useAppConfig = () => { 6 | const { network } = useWalletSelector(); 7 | 8 | return useQuery({ 9 | queryKey: ['app-config', network.type], 10 | queryFn: async () => { 11 | const response = await getAppConfig(network.type); 12 | return response; 13 | }, 14 | }); 15 | }; 16 | 17 | export default useAppConfig; 18 | -------------------------------------------------------------------------------- /src/app/hooks/queries/useBtcWalletData.ts: -------------------------------------------------------------------------------- 1 | import useBtcClient from '@hooks/apiClients/useBtcClient'; 2 | import useSelectedAccount from '@hooks/useSelectedAccount'; 3 | import type { BtcAddressData } from '@secretkeylabs/xverse-core'; 4 | import { useQuery } from '@tanstack/react-query'; 5 | 6 | const useBtcWalletData = () => { 7 | const { btcAddress } = useSelectedAccount(); 8 | const btcClient = useBtcClient(); 9 | 10 | const fetchBtcWalletData = async () => { 11 | const btcData: BtcAddressData = await btcClient.getBalance(btcAddress); 12 | return btcData.finalBalance; 13 | }; 14 | 15 | return useQuery({ 16 | queryKey: ['btc-wallet-data', btcAddress], 17 | queryFn: fetchBtcWalletData, 18 | enabled: !!btcAddress, 19 | staleTime: 10 * 1000, // 10 secs 20 | keepPreviousData: true, 21 | }); 22 | }; 23 | 24 | export default useBtcWalletData; 25 | -------------------------------------------------------------------------------- /src/app/hooks/queries/useDelegationState.ts: -------------------------------------------------------------------------------- 1 | import useNetworkSelector from '@hooks/useNetwork'; 2 | import useSelectedAccount from '@hooks/useSelectedAccount'; 3 | import { fetchDelegationState } from '@secretkeylabs/xverse-core'; 4 | import { useQuery } from '@tanstack/react-query'; 5 | 6 | const useDelegationState = () => { 7 | const { stxAddress } = useSelectedAccount(); 8 | const selectedNetwork = useNetworkSelector(); 9 | 10 | return useQuery({ 11 | queryKey: ['stacking-delegation-state', selectedNetwork, stxAddress], 12 | queryFn: () => fetchDelegationState(stxAddress, selectedNetwork), 13 | enabled: Boolean(stxAddress), 14 | }); 15 | }; 16 | 17 | export default useDelegationState; 18 | -------------------------------------------------------------------------------- /src/app/hooks/queries/useFeeMultipliers.ts: -------------------------------------------------------------------------------- 1 | import useWalletSelector from '@hooks/useWalletSelector'; 2 | import type { AppInfo } from '@secretkeylabs/xverse-core'; 3 | import { getXverseApiClient } from '@secretkeylabs/xverse-core'; 4 | import { setFeeMultiplierAction } from '@stores/wallet/actions/actionCreators'; 5 | import { useQuery } from '@tanstack/react-query'; 6 | import { useDispatch } from 'react-redux'; 7 | 8 | const useFeeMultipliers = () => { 9 | const { network } = useWalletSelector(); 10 | const xverseApi = getXverseApiClient(network.type); 11 | const dispatch = useDispatch(); 12 | 13 | const fetchFeeMultiplierData = async (): Promise => { 14 | const response = await xverseApi.fetchAppInfo(); 15 | if (!response) throw new Error('Failed to fetch fee multipliers'); 16 | 17 | dispatch(setFeeMultiplierAction(response)); 18 | return response; 19 | }; 20 | 21 | return useQuery({ 22 | queryKey: ['fee_multipliers'], 23 | queryFn: fetchFeeMultiplierData, 24 | }); 25 | }; 26 | 27 | export default useFeeMultipliers; 28 | -------------------------------------------------------------------------------- /src/app/hooks/queries/useGetCoinsMarketData.ts: -------------------------------------------------------------------------------- 1 | import useXverseApi from '@hooks/apiClients/useXverseApi'; 2 | import { useQuery } from '@tanstack/react-query'; 3 | 4 | const useGetCoinsMarketData = (searchId: string) => { 5 | const xverseApi = useXverseApi(); 6 | const ids = ['bitcoin', 'blockstack'].includes(searchId) ? ['bitcoin', 'blockstack'] : [searchId]; 7 | 8 | const queryFn = async () => { 9 | const response = await xverseApi.getCoinsMarketData(ids); 10 | return response; 11 | }; 12 | 13 | return useQuery({ 14 | queryKey: ['get-coins-market-data', ids.join(',')], 15 | staleTime: 5 * 60 * 1000, // 5 minutes 16 | queryFn, 17 | select: (data) => data.find(({ id }) => searchId === id), 18 | }); 19 | }; 20 | 21 | export default useGetCoinsMarketData; 22 | -------------------------------------------------------------------------------- /src/app/hooks/queries/useGetExchangeRate.ts: -------------------------------------------------------------------------------- 1 | import useXverseApi from '@hooks/apiClients/useXverseApi'; 2 | import type { ExchangeRateAvailableCurrencies } from '@secretkeylabs/xverse-core'; 3 | import { useQuery } from '@tanstack/react-query'; 4 | 5 | const useGetExchangeRate = (currency: ExchangeRateAvailableCurrencies) => { 6 | const xverseApi = useXverseApi(); 7 | 8 | const queryFn = async () => { 9 | const response = await xverseApi.getExchangeRate(currency); 10 | return response; 11 | }; 12 | 13 | return useQuery({ 14 | queryKey: ['get-exchange-rate', currency], 15 | staleTime: 5 * 60 * 1000, // 5 minutes 16 | queryFn, 17 | }); 18 | }; 19 | 20 | export default useGetExchangeRate; 21 | -------------------------------------------------------------------------------- /src/app/hooks/queries/useGetHistoricalData.ts: -------------------------------------------------------------------------------- 1 | import useXverseApi from '@hooks/apiClients/useXverseApi'; 2 | import useWalletSelector from '@hooks/useWalletSelector'; 3 | import type { HistoricalDataParamsPeriod } from '@secretkeylabs/xverse-core'; 4 | import { useQuery } from '@tanstack/react-query'; 5 | import useGetExchangeRate from './useGetExchangeRate'; 6 | 7 | const useGetHistoricalData = (id: string, period: HistoricalDataParamsPeriod) => { 8 | const xverseApi = useXverseApi(); 9 | 10 | const { fiatCurrency } = useWalletSelector(); 11 | const { data: exchangeRates } = useGetExchangeRate('USD'); 12 | const exchangeRate = exchangeRates ? Number(exchangeRates[fiatCurrency]) : 1; 13 | 14 | return useQuery({ 15 | // eslint-disable-next-line 16 | queryKey: ['get-historical-data', id, period, fiatCurrency], 17 | queryFn: () => xverseApi.getHistoricalData(id, period, exchangeRate), 18 | staleTime: 10 * 60 * 1000, // 10 minutes 19 | }); 20 | }; 21 | 22 | export default useGetHistoricalData; 23 | -------------------------------------------------------------------------------- /src/app/hooks/queries/useGetTopTokens.ts: -------------------------------------------------------------------------------- 1 | import useXverseApi from '@hooks/apiClients/useXverseApi'; 2 | import { useQuery } from '@tanstack/react-query'; 3 | 4 | const useGetTopTokens = () => { 5 | const xverseApi = useXverseApi(); 6 | 7 | const queryFn = async () => { 8 | const response = await xverseApi.getTopTokens(); 9 | return response; 10 | }; 11 | 12 | return useQuery({ 13 | queryKey: ['top-tokens'], 14 | staleTime: 1000 * 60 * 60, // 1 hour 15 | queryFn, 16 | }); 17 | }; 18 | 19 | export default useGetTopTokens; 20 | -------------------------------------------------------------------------------- /src/app/hooks/queries/useStxPendingTxData.ts: -------------------------------------------------------------------------------- 1 | import useSelectedAccount from '@hooks/useSelectedAccount'; 2 | import { fetchStxPendingTxData, type StxPendingTxData } from '@secretkeylabs/xverse-core'; 3 | import { useQuery } from '@tanstack/react-query'; 4 | import useNetworkSelector from '../useNetwork'; 5 | 6 | const useStxPendingTxData = () => { 7 | const { stxAddress } = useSelectedAccount(); 8 | const selectedNetwork = useNetworkSelector(); 9 | const result = useQuery({ 10 | queryKey: ['stx-pending-transaction', { stxAddress, selectedNetwork }], 11 | queryFn: (): Promise => fetchStxPendingTxData(stxAddress, selectedNetwork), 12 | }); 13 | return result; 14 | }; 15 | 16 | export default useStxPendingTxData; 17 | -------------------------------------------------------------------------------- /src/app/hooks/queries/useStxWalletData.ts: -------------------------------------------------------------------------------- 1 | import useStacksAPI from '@hooks/apiClients/useStacksApi'; 2 | import useSelectedAccount from '@hooks/useSelectedAccount'; 3 | import type { StxAddressData } from '@secretkeylabs/xverse-core'; 4 | import { useQuery } from '@tanstack/react-query'; 5 | 6 | const useStxWalletData = () => { 7 | const { stxAddress } = useSelectedAccount(); 8 | const StacksAPI = useStacksAPI(); 9 | const fetchStxWalletData = async (): Promise => { 10 | const response = await StacksAPI.getAddressBalance(stxAddress); 11 | return { 12 | ...response, 13 | balance: response.totalBalance, 14 | locked: response.lockedBalance, 15 | transactions: [], 16 | }; 17 | }; 18 | 19 | return useQuery({ 20 | queryKey: ['stx-wallet-data', stxAddress], 21 | queryFn: fetchStxWalletData, 22 | enabled: !!stxAddress, 23 | staleTime: 10 * 1000, // 10 secs 24 | keepPreviousData: true, 25 | }); 26 | }; 27 | 28 | export default useStxWalletData; 29 | -------------------------------------------------------------------------------- /src/app/hooks/queries/useTransaction.ts: -------------------------------------------------------------------------------- 1 | import useBtcClient from '@hooks/apiClients/useBtcClient'; 2 | import useSelectedAccount from '@hooks/useSelectedAccount'; 3 | import { fetchBtcTransaction } from '@secretkeylabs/xverse-core'; 4 | import { useQuery } from '@tanstack/react-query'; 5 | 6 | export default function useTransaction(id?: string) { 7 | const selectedAccount = useSelectedAccount(); 8 | const btcClient = useBtcClient(); 9 | 10 | const fetchTransaction = async () => { 11 | if (!selectedAccount || !id) { 12 | return; 13 | } 14 | 15 | const transaction = await fetchBtcTransaction( 16 | id, 17 | selectedAccount.btcAddress, 18 | selectedAccount.ordinalsAddress, 19 | btcClient, 20 | ); 21 | 22 | return transaction; 23 | }; 24 | 25 | return useQuery({ 26 | queryKey: ['transaction', id], 27 | queryFn: fetchTransaction, 28 | enabled: id !== undefined, 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/app/hooks/stores/useHasRehydrated.ts: -------------------------------------------------------------------------------- 1 | import type { StoreState } from '@stores/index'; 2 | import { useSelector } from 'react-redux'; 3 | import type { PersistPartial } from 'redux-persist/es/persistReducer'; 4 | 5 | // eslint-disable-next-line no-underscore-dangle 6 | const selectHasRehydrated = (state: StoreState & PersistPartial) => state._persist.rehydrated; 7 | 8 | export default function useHasStateRehydrated() { 9 | return useSelector(selectHasRehydrated); 10 | } 11 | -------------------------------------------------------------------------------- /src/app/hooks/stores/useNftDataSelector.ts: -------------------------------------------------------------------------------- 1 | import type { StoreState } from '@stores/index'; 2 | import { useSelector } from 'react-redux'; 3 | 4 | const useNftDataSelector = () => { 5 | const nftDataState = useSelector((state: StoreState) => ({ 6 | ...state.nftDataState, 7 | })); 8 | 9 | return { 10 | ...nftDataState, 11 | }; 12 | }; 13 | 14 | export default useNftDataSelector; 15 | -------------------------------------------------------------------------------- /src/app/hooks/stores/useSatBundleReducer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | setSelectedSatBundleAction, 3 | setSelectedSatBundleItemIndexAction, 4 | } from '@stores/nftData/actions/actionCreator'; 5 | import type { NftDataState } from '@stores/nftData/actions/types'; 6 | import { useDispatch } from 'react-redux'; 7 | 8 | const useSatBundleDataReducer = () => { 9 | const dispatch = useDispatch(); 10 | 11 | const setSelectedSatBundleDetails = (data: NftDataState['selectedSatBundle']) => { 12 | dispatch(setSelectedSatBundleAction(data)); 13 | }; 14 | 15 | const setSelectedSatBundleItemIndex = (data: NftDataState['selectedSatBundleItemIndex']) => { 16 | dispatch(setSelectedSatBundleItemIndexAction(data)); 17 | }; 18 | 19 | return { 20 | setSelectedSatBundleDetails, 21 | setSelectedSatBundleItemIndex, 22 | }; 23 | }; 24 | 25 | export default useSatBundleDataReducer; 26 | -------------------------------------------------------------------------------- /src/app/hooks/useBtcAddressBalance.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from '@tanstack/react-query'; 2 | import BigNumber from 'bignumber.js'; 3 | import useBtcClient from './apiClients/useBtcClient'; 4 | 5 | export default function useBtcAddressBalance(address: string) { 6 | const btcClient = useBtcClient(); 7 | 8 | const fetchBalance = async () => { 9 | if (!address) { 10 | return null; 11 | } 12 | 13 | const addressData = await btcClient.getAddressData(address); 14 | const confirmedBalance = BigNumber(addressData.chain_stats.funded_txo_sum) 15 | .minus(addressData.chain_stats.spent_txo_sum) 16 | .toNumber(); 17 | const unconfirmedBalance = BigNumber(addressData.mempool_stats.funded_txo_sum) 18 | .minus(addressData.mempool_stats.spent_txo_sum) 19 | .toNumber(); 20 | 21 | return { address, confirmedBalance, unconfirmedBalance }; 22 | }; 23 | 24 | return useQuery({ 25 | queryKey: ['btc-address-balance', address], 26 | queryFn: fetchBalance, 27 | staleTime: 10 * 1000, // 10 secs 28 | keepPreviousData: true, 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/app/hooks/useBtcFeeRate.ts: -------------------------------------------------------------------------------- 1 | import { type BtcFeeResponse } from '@secretkeylabs/xverse-core'; 2 | import { useQuery } from '@tanstack/react-query'; 3 | import useXverseApi from './apiClients/useXverseApi'; 4 | import useWalletSelector from './useWalletSelector'; 5 | 6 | function useBtcFeeRate() { 7 | const { network } = useWalletSelector(); 8 | const xverseApi = useXverseApi(); 9 | 10 | return useQuery({ 11 | queryKey: ['btcFeeRate', network.type], 12 | queryFn: () => xverseApi.fetchBtcFeeRate(), 13 | staleTime: 5 * 60 * 1000, // 5 mins 14 | }); 15 | } 16 | 17 | export default useBtcFeeRate; 18 | -------------------------------------------------------------------------------- /src/app/hooks/useCanUserSwitchPaymentType.ts: -------------------------------------------------------------------------------- 1 | import useSelectedAccount from './useSelectedAccount'; 2 | import useWalletSelector from './useWalletSelector'; 3 | 4 | export default function useCanUserSwitchPaymentType() { 5 | const selectedAccount = useSelectedAccount(); 6 | const { selectedAccountType } = useWalletSelector(); 7 | return !!( 8 | selectedAccountType !== 'ledger' && 9 | selectedAccount.btcAddresses.native && 10 | selectedAccount.btcAddresses.nested 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/app/hooks/useCancellableEffect.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, type DependencyList } from 'react'; 2 | 3 | type EffectCallback = (isEffectActive: () => boolean) => Promise | void; 4 | 5 | export default function useCancellableEffect(effectFn: EffectCallback, deps: DependencyList) { 6 | useEffect(() => { 7 | let isEffectActive = true; 8 | 9 | const wrappedEffect = async () => { 10 | await effectFn(() => isEffectActive); 11 | }; 12 | 13 | wrappedEffect(); 14 | 15 | return () => { 16 | isEffectActive = false; 17 | }; 18 | // eslint-disable-next-line react-hooks/exhaustive-deps 19 | }, deps); 20 | } 21 | -------------------------------------------------------------------------------- /src/app/hooks/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export default function useDebounce(value: string, delay: number, immediate = false) { 4 | const [debouncedValue, setDebouncedValue] = useState(immediate ? value : ''); 5 | 6 | useEffect(() => { 7 | if (immediate && debouncedValue === '') { 8 | setDebouncedValue(value); 9 | return; 10 | } 11 | 12 | const handler = setTimeout(() => { 13 | setDebouncedValue(value); 14 | }, delay); 15 | 16 | return () => { 17 | clearTimeout(handler); 18 | }; 19 | }, [value, delay, immediate, debouncedValue]); 20 | 21 | return debouncedValue; 22 | } 23 | -------------------------------------------------------------------------------- /src/app/hooks/useHasFeature.ts: -------------------------------------------------------------------------------- 1 | import { FeatureId, getXverseApiClient } from '@secretkeylabs/xverse-core'; 2 | import { useQuery } from '@tanstack/react-query'; 3 | import useSelectedAccount from './useSelectedAccount'; 4 | import useWalletSelector from './useWalletSelector'; 5 | 6 | const useAppFeatures = () => { 7 | const { masterPubKey } = useSelectedAccount(); 8 | const { network } = useWalletSelector(); 9 | 10 | return useQuery({ 11 | queryKey: ['appFeatures', network.type, masterPubKey], 12 | queryFn: () => getXverseApiClient(network.type).getAppFeatures(), 13 | staleTime: 1000 * 60 * 5, // 5 minutes 14 | cacheTime: 1000 * 60 * 60 * 24, // 24 hours 15 | }); 16 | }; 17 | 18 | export default function useHasFeature(feature: FeatureId): boolean { 19 | const { data } = useAppFeatures(); 20 | return data?.[feature]?.enabled ?? false; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/hooks/useIntersectionObserver.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | const useIntersectionObserver = (ref, callback, options) => { 4 | useEffect(() => { 5 | const observer = new IntersectionObserver((entries) => { 6 | entries.forEach((entry) => { 7 | if (entry.isIntersecting) { 8 | callback(); 9 | } 10 | }); 11 | }, options); 12 | 13 | if (ref.current) { 14 | observer.observe(ref.current); 15 | } 16 | 17 | return () => { 18 | if (ref.current) { 19 | observer.unobserve(ref.current); 20 | } 21 | }; 22 | }, [ref, callback, options]); 23 | }; 24 | 25 | export default useIntersectionObserver; 26 | -------------------------------------------------------------------------------- /src/app/hooks/useNetwork.ts: -------------------------------------------------------------------------------- 1 | import { StacksMainnet, StacksTestnet } from '@secretkeylabs/xverse-core'; 2 | import { useMemo } from 'react'; 3 | import useWalletSelector from './useWalletSelector'; 4 | 5 | const useNetworkSelector = () => { 6 | const { network } = useWalletSelector(); 7 | 8 | const selectedNetwork = useMemo( 9 | () => 10 | network.type === 'Mainnet' 11 | ? { 12 | ...StacksMainnet, 13 | client: { 14 | baseUrl: network.address, 15 | }, 16 | } 17 | : { 18 | ...StacksTestnet, 19 | client: { 20 | baseUrl: network.address, 21 | }, 22 | }, 23 | [network.type, network.address], 24 | ); 25 | return selectedNetwork; 26 | }; 27 | 28 | export default useNetworkSelector; 29 | -------------------------------------------------------------------------------- /src/app/hooks/useNotificationBanners.ts: -------------------------------------------------------------------------------- 1 | import { getXverseApiClient } from '@secretkeylabs/xverse-core'; 2 | import { useQuery } from '@tanstack/react-query'; 3 | import useWalletSelector from './useWalletSelector'; 4 | 5 | function useNotificationBanners() { 6 | const { network } = useWalletSelector(); 7 | 8 | const fetchNotificationBanners = async () => { 9 | const response = await getXverseApiClient(network.type).getNotificationBanners(); 10 | 11 | return response; 12 | }; 13 | 14 | const { data, isLoading, isFetching, refetch } = useQuery({ 15 | queryKey: ['notificationBanners'], 16 | queryFn: fetchNotificationBanners, 17 | staleTime: 60 * 60 * 1000, // 1 hour 18 | }); 19 | 20 | return { 21 | data, 22 | isLoading, 23 | isFetching, 24 | refetch, 25 | }; 26 | } 27 | 28 | export default useNotificationBanners; 29 | -------------------------------------------------------------------------------- /src/app/hooks/useOnTabClosed.ts: -------------------------------------------------------------------------------- 1 | import { InternalMethods } from '@common/types/message-types'; 2 | import type { BackgroundMessages } from '@common/types/messages'; 3 | import { useEffect } from 'react'; 4 | 5 | export default function useOnOriginTabClose(tabId: number, handler: () => void) { 6 | useEffect(() => { 7 | const messageHandler = (message: BackgroundMessages) => { 8 | if (message.method !== InternalMethods.OriginatingTabClosed) return; 9 | if (message.payload.tabId === Number(tabId)) { 10 | handler(); 11 | } 12 | }; 13 | 14 | chrome.runtime.onMessage.addListener(messageHandler); 15 | 16 | return () => chrome.runtime.onMessage.removeListener(messageHandler); 17 | }, []); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/hooks/useOptionsDialog.ts: -------------------------------------------------------------------------------- 1 | import { OPTIONS_DIALOG_WIDTH } from '@utils/constants'; 2 | import { useState } from 'react'; 3 | 4 | export default function useOptionsDialog(dialogWidth = OPTIONS_DIALOG_WIDTH) { 5 | const [isVisible, setIsVisible] = useState(false); 6 | const [indents, setIndents] = useState<{ top: string; left: string } | undefined>(); 7 | 8 | const handleOpen = (event: React.MouseEvent) => { 9 | const parentBoundingRect = (event.target as HTMLElement).parentElement?.getBoundingClientRect(); 10 | 11 | setIsVisible(true); 12 | setIndents({ 13 | top: `${parentBoundingRect?.top}px`, 14 | left: `calc(${parentBoundingRect?.right}px - ${dialogWidth}px)`, 15 | }); 16 | }; 17 | 18 | const handleClose = () => { 19 | setIsVisible(false); 20 | }; 21 | 22 | return { 23 | isVisible, 24 | setIsVisible, 25 | indents, 26 | setIndents, 27 | open: handleOpen, 28 | close: handleClose, 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/app/hooks/useOptionsSheet.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | export default function useOptionsSheet(initialVisibility = false) { 4 | const [isVisible, setIsVisible] = useState(initialVisibility); 5 | 6 | const handleOpen = () => { 7 | setIsVisible(true); 8 | }; 9 | 10 | const handleClose = () => { 11 | setIsVisible(false); 12 | }; 13 | 14 | return { 15 | isVisible, 16 | setIsVisible, 17 | open: handleOpen, 18 | close: handleClose, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/app/hooks/useOrdinalsByAddress.ts: -------------------------------------------------------------------------------- 1 | import { getOrdinalsByAddress, type BtcOrdinal } from '@secretkeylabs/xverse-core'; 2 | import { useQuery } from '@tanstack/react-query'; 3 | import useBtcClient from './apiClients/useBtcClient'; 4 | import useWalletSelector from './useWalletSelector'; 5 | 6 | const useOrdinalsByAddress = (address: string) => { 7 | const { network } = useWalletSelector(); 8 | const btcClient = useBtcClient(); 9 | 10 | const fetchOrdinals = async (): Promise => { 11 | const ordinals = await getOrdinalsByAddress(btcClient, network.type, address); 12 | return ordinals.filter((item) => item.id !== undefined); 13 | }; 14 | 15 | const { 16 | data: ordinals, 17 | isLoading, 18 | refetch, 19 | } = useQuery({ 20 | queryKey: [`ordinals-${address}`], 21 | queryFn: fetchOrdinals, 22 | }); 23 | 24 | return { 25 | ordinals, 26 | isLoading, 27 | refetch, 28 | }; 29 | }; 30 | 31 | export default useOrdinalsByAddress; 32 | -------------------------------------------------------------------------------- /src/app/hooks/useSearchParamsState.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { useSearchParams } from 'react-router-dom'; 3 | 4 | const useSearchParamsState = (key: string, defaultValue: T): [T, (newValue: T) => void] => { 5 | const [searchParams, setSearchParamsInternal] = useSearchParams(); 6 | const setSearchParam = (newValue: T) => { 7 | searchParams.set(key, JSON.stringify(newValue)); 8 | setSearchParamsInternal(searchParams); 9 | }; 10 | 11 | const paramValue = searchParams.get(key); 12 | const initialValue = paramValue !== null ? (JSON.parse(paramValue) as T) : defaultValue; 13 | 14 | const [value, setValue] = useState(initialValue); 15 | const customSetter = (newValue: T) => { 16 | setValue(newValue); 17 | setSearchParam(newValue); 18 | }; 19 | 20 | useEffect(() => { 21 | setSearchParam(initialValue); 22 | }, []); 23 | 24 | return [value, customSetter]; 25 | }; 26 | 27 | export default useSearchParamsState; 28 | -------------------------------------------------------------------------------- /src/app/hooks/useTextOrdinalContent.ts: -------------------------------------------------------------------------------- 1 | import type { CondensedInscription, Inscription } from '@secretkeylabs/xverse-core'; 2 | import { getTextOrdinalContent } from '@secretkeylabs/xverse-core'; 3 | import { useQuery } from '@tanstack/react-query'; 4 | import PQueue from 'p-queue'; 5 | import useWalletSelector from './useWalletSelector'; 6 | 7 | const queue = new PQueue({ concurrency: 1 }); 8 | 9 | const useTextOrdinalContent = (ordinal?: Inscription | CondensedInscription) => { 10 | const { network } = useWalletSelector(); 11 | const { data: textContent } = useQuery({ 12 | enabled: !!ordinal?.id, 13 | queryKey: ['ordinal-text', ordinal?.id, network.type], 14 | queryFn: async () => { 15 | if (!ordinal?.id) return; 16 | return queue.add(() => getTextOrdinalContent(network.type, ordinal?.id)); 17 | }, 18 | staleTime: 5 * 60 * 1000, // 5 min 19 | }); 20 | 21 | return textContent?.toString(); 22 | }; 23 | 24 | export default useTextOrdinalContent; 25 | -------------------------------------------------------------------------------- /src/app/hooks/useTrackMixPanelPageViewed.ts: -------------------------------------------------------------------------------- 1 | import { isKeystoneAccount, isLedgerAccount } from '@utils/helper'; 2 | import { getMixpanelInstance } from 'app/mixpanelSetup'; 3 | import { useEffect } from 'react'; 4 | import { useLocation } from 'react-router-dom'; 5 | import useSelectedAccount from './useSelectedAccount'; 6 | 7 | declare const VERSION: string; 8 | 9 | const useTrackMixPanelPageViewed = (properties?: any, deps: any[] = []) => { 10 | const selectedAccount = useSelectedAccount(); 11 | const location = useLocation(); 12 | 13 | useEffect(() => { 14 | getMixpanelInstance('web-extension').track_pageview({ 15 | path: location.pathname, 16 | wallet_type: isLedgerAccount(selectedAccount) 17 | ? 'ledger' 18 | : isKeystoneAccount(selectedAccount) 19 | ? 'keystone' 20 | : 'software', 21 | client_version: VERSION, 22 | ...properties, 23 | }); 24 | }, deps); // eslint-disable-line react-hooks/exhaustive-deps 25 | }; 26 | 27 | export default useTrackMixPanelPageViewed; 28 | -------------------------------------------------------------------------------- /src/app/hooks/useWalletSelector.ts: -------------------------------------------------------------------------------- 1 | import type { StoreState } from '@stores/index'; 2 | import { useSelector } from 'react-redux'; 3 | 4 | const useWalletSelector = () => { 5 | const walletState = useSelector((state: StoreState) => state.walletState); 6 | 7 | return { 8 | ...walletState, 9 | }; 10 | }; 11 | 12 | export default useWalletSelector; 13 | -------------------------------------------------------------------------------- /src/app/mixpanelSetup.ts: -------------------------------------------------------------------------------- 1 | import { MIX_PANEL_EXPLORE_APP_TOKEN, MIX_PANEL_TOKEN } from '@utils/constants'; 2 | import mixpanel, { type Mixpanel } from 'mixpanel-browser'; 3 | 4 | export const mixpanelInstances: Record = { 5 | 'web-extension': { 6 | token: MIX_PANEL_TOKEN, 7 | }, 8 | 'explore-app': { 9 | token: MIX_PANEL_EXPLORE_APP_TOKEN, 10 | }, 11 | }; 12 | 13 | // lazy load the mixpanel instances 14 | export const getMixpanelInstance = (instanceKey: keyof typeof mixpanelInstances): Mixpanel => { 15 | if (mixpanel[instanceKey]) { 16 | return mixpanel[instanceKey]; 17 | } 18 | 19 | const token = mixpanelInstances[instanceKey]?.token; 20 | if (!token) { 21 | throw new Error(`Mixpanel instance ${instanceKey} token not found`); 22 | } 23 | 24 | mixpanel.init( 25 | token, 26 | { 27 | debug: process.env.NODE_ENV === 'development', 28 | ip: false, 29 | persistence: 'localStorage', 30 | }, 31 | instanceKey, 32 | ); 33 | return mixpanel[instanceKey]; 34 | }; 35 | -------------------------------------------------------------------------------- /src/app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import AnimatedScreenContainer from '@components/animatedScreenContainer'; 2 | import ScreenContainer from '@components/screenContainer'; 3 | import ErrorBoundary from '@screens/error'; 4 | import { createHashRouter } from 'react-router-dom'; 5 | 6 | import { authedRoutes, authedRoutesAnimated, authedRoutesWithSidebar } from './authenticatedRoutes'; 7 | import onboardingRoutes from './onboardingRoutes'; 8 | import openRoutes from './openRoutes'; 9 | 10 | const router = createHashRouter([ 11 | { 12 | path: '/', 13 | element: , 14 | errorElement: , 15 | children: [authedRoutes, onboardingRoutes, ...openRoutes], 16 | }, 17 | { 18 | path: '/', 19 | element: , 20 | errorElement: , 21 | children: [authedRoutesAnimated], 22 | }, 23 | { 24 | path: '/', 25 | element: , 26 | errorElement: , 27 | children: [authedRoutesWithSidebar], 28 | }, 29 | ]); 30 | 31 | export default router; 32 | -------------------------------------------------------------------------------- /src/app/routes/openRoutes.tsx: -------------------------------------------------------------------------------- 1 | import { type RouteObject } from 'react-router-dom'; 2 | 3 | import CreateWalletSuccess from '@screens/createWalletSuccess'; 4 | import ForgotPassword from '@screens/forgotPassword'; 5 | import Landing from '@screens/landing'; 6 | import Login from '@screens/login'; 7 | import WalletExists from '@screens/walletExists'; 8 | 9 | // any open routes should be placed here 10 | // these are routes that don't require authentication and don't fit in the other routers 11 | const openRoutes: RouteObject[] = [ 12 | { 13 | path: 'landing', 14 | element: , 15 | }, 16 | { 17 | path: 'login', 18 | element: , 19 | }, 20 | { 21 | path: 'forgotPassword', 22 | element: , 23 | }, 24 | { 25 | path: 'wallet-success/:action', 26 | element: , 27 | }, 28 | { 29 | path: 'wallet-exists', 30 | element: , 31 | }, 32 | ]; 33 | 34 | export default openRoutes; 35 | -------------------------------------------------------------------------------- /src/app/screens/coinDashboard/index.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from 'react-router-dom'; 2 | import Btc from './coins/btc'; 3 | import Other from './coins/other'; 4 | 5 | export type Tab = 'first' | 'second' | 'third'; 6 | 7 | export default function CoinDashboard() { 8 | const { currency } = useParams(); 9 | 10 | switch (currency) { 11 | case 'BTC': 12 | return ; 13 | default: 14 | // TODO: split this more. Other is currently doing too much 15 | return ; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/screens/connect/connectionRequest/dappLogo.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import styled from 'styled-components'; 3 | 4 | const ImgContainer = styled('div')({ 5 | display: 'flex', 6 | justifyContent: 'center', 7 | }); 8 | 9 | const Img = styled('img')((props) => ({ 10 | height: 48, 11 | width: 48, 12 | alignSelf: 'center', 13 | objectFit: 'contain', 14 | borderRadius: props.theme.radius(1), 15 | })); 16 | 17 | type Props = { 18 | /** 19 | * Any source that can be used as an image source. 20 | */ 21 | src?: string | null; 22 | }; 23 | export function DappLogo({ src }: Props) { 24 | if (!src) { 25 | return null; 26 | } 27 | 28 | return ( 29 | 30 | Dapp Logo 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/app/screens/connect/connectionRequest/selectAccount.tsx: -------------------------------------------------------------------------------- 1 | import useSelectedAccount from '@hooks/useSelectedAccount'; 2 | import SelectAccount from '@screens/connect/selectAccount'; 3 | import RoutePaths from 'app/routes/paths'; 4 | import { useNavigate } from 'react-router-dom'; 5 | 6 | /* eslint-disable import/prefer-default-export */ 7 | export function SelectAccountPrompt() { 8 | const selectedAccount = useSelectedAccount(); 9 | const navigate = useNavigate(); 10 | 11 | const handleSwitchAccount = () => { 12 | navigate(`${RoutePaths.AccountList}?hideListActions=true`); 13 | }; 14 | 15 | return ; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/screens/connect/connectionRequest/title.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import { useTranslation } from 'react-i18next'; 3 | import styled from 'styled-components'; 4 | 5 | const TitleInner = styled.h1((props) => ({ 6 | ...props.theme.typography.headline_xs, 7 | color: props.theme.colors.white_0, 8 | textAlign: 'center', 9 | })); 10 | 11 | export function Title() { 12 | const { t } = useTranslation('translation', { keyPrefix: 'AUTH_REQUEST_SCREEN' }); 13 | return {t('TITLE')}; 14 | } 15 | -------------------------------------------------------------------------------- /src/app/screens/error/index.tsx: -------------------------------------------------------------------------------- 1 | import ErrorDisplay from '@components/errorDisplay'; 2 | import { useRouteError } from 'react-router-dom'; 3 | 4 | interface RouterError { 5 | status?: number; 6 | statusText?: string; 7 | error?: { 8 | message?: string; 9 | stack?: string; 10 | }; 11 | } 12 | 13 | function ErrorBoundary() { 14 | const error = useRouteError() as RouterError; 15 | return ( 16 | 24 | ); 25 | } 26 | 27 | export default ErrorBoundary; 28 | -------------------------------------------------------------------------------- /src/app/screens/keystone/importKeystoneAccount/index.styled.ts: -------------------------------------------------------------------------------- 1 | import { animated } from '@react-spring/web'; 2 | import styled from 'styled-components'; 3 | 4 | export const Container = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | flex: 1; 9 | &::-webkit-scrollbar { 10 | display: none; 11 | } 12 | `; 13 | 14 | export const OnBoardingContentContainer = styled(animated.div)((props) => ({ 15 | display: 'flex', 16 | width: '100%', 17 | flexDirection: 'column', 18 | flex: 1, 19 | justifyContent: props.className === 'center' ? 'center' : 'none', 20 | paddingLeft: props.theme.spacing(8), 21 | paddingRight: props.theme.spacing(8), 22 | })); 23 | 24 | export const OnBoardingActionsContainer = styled.div((props) => ({ 25 | width: '100%', 26 | display: 'flex', 27 | alignItems: 'flex-start', 28 | justifyContent: 'space-between', 29 | paddingLeft: props.theme.spacing(8), 30 | paddingRight: props.theme.spacing(8), 31 | marginBottom: props.theme.spacing(30), 32 | })); 33 | -------------------------------------------------------------------------------- /src/app/screens/keystone/importKeystoneAccount/types.ts: -------------------------------------------------------------------------------- 1 | export enum ImportKeystoneSteps { 2 | START = 0, 3 | CONNECT_KEYSTONE = 1, 4 | ADDRESS_ADDED = 2, 5 | ADD_ACCOUNT_NAME = 3, 6 | IMPORT_END = 4, 7 | } 8 | -------------------------------------------------------------------------------- /src/app/screens/ledger/importLedgerAccount/index.styled.ts: -------------------------------------------------------------------------------- 1 | import { animated } from '@react-spring/web'; 2 | import styled from 'styled-components'; 3 | 4 | export const Container = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | flex: 1; 9 | &::-webkit-scrollbar { 10 | display: none; 11 | } 12 | `; 13 | 14 | export const OnBoardingContentContainer = styled(animated.div)((props) => ({ 15 | display: 'flex', 16 | width: '100%', 17 | flexDirection: 'column', 18 | flex: 1, 19 | justifyContent: props.className === 'center' ? 'center' : 'none', 20 | paddingLeft: props.theme.spacing(8), 21 | paddingRight: props.theme.spacing(8), 22 | })); 23 | 24 | export const OnBoardingActionsContainer = styled.div((props) => ({ 25 | width: '100%', 26 | display: 'flex', 27 | alignItems: 'flex-start', 28 | justifyContent: 'space-between', 29 | paddingLeft: props.theme.spacing(8), 30 | paddingRight: props.theme.spacing(8), 31 | marginBottom: props.theme.spacing(30), 32 | })); 33 | -------------------------------------------------------------------------------- /src/app/screens/ledger/importLedgerAccount/types.ts: -------------------------------------------------------------------------------- 1 | export enum ImportLedgerSteps { 2 | START = 0, 3 | SELECT_ASSET = 1, 4 | BEFORE_START = 2, 5 | IMPORTANT_WARNING = 3, 6 | CONNECT_LEDGER = 4, 7 | ADD_MULTIPLE_ACCOUNTS = 4.5, 8 | ADD_ADDRESS = 5, 9 | ADD_ORDINALS_ADDRESS = 5.5, 10 | ADDRESS_ADDED = 6, 11 | ADD_ACCOUNT_NAME = 7, 12 | IMPORT_END = 8, 13 | } 14 | 15 | export enum LedgerLiveOptions { 16 | USING = 'using', 17 | NOT_USING = 'not using', 18 | } 19 | -------------------------------------------------------------------------------- /src/app/screens/nftDashboard/collectiblesTabs/skeletonLoader.tsx: -------------------------------------------------------------------------------- 1 | import { StyledBarLoader, TilesSkeletonLoader } from '@components/tilesSkeletonLoader'; 2 | import { CountLoaderContainer, GridContainer, LoaderContainer } from './index.styled'; 3 | 4 | function SkeletonLoader({ isGalleryOpen }: { isGalleryOpen: boolean }) { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | } 16 | 17 | export default SkeletonLoader; 18 | -------------------------------------------------------------------------------- /src/app/screens/nftDetail/nftAttribute.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Container = styled.div((props) => ({ 4 | display: 'flex', 5 | borderRadius: 12, 6 | border: `1px solid ${props.theme.colors.elevation3}`, 7 | flexDirection: 'column', 8 | padding: '8px 16px', 9 | alignItems: 'flex-start', 10 | })); 11 | 12 | const TypeText = styled.h1((props) => ({ 13 | ...props.theme.typography.body_m, 14 | color: props.theme.colors.white_400, 15 | })); 16 | 17 | const ValueText = styled.h1((props) => ({ 18 | ...props.theme.typography.body_medium_m, 19 | color: props.theme.colors.white_0, 20 | wordBreak: 'break-all', 21 | })); 22 | 23 | interface Props { 24 | type: string; 25 | value: string; 26 | } 27 | 28 | function NftAttribute({ type, value }: Props) { 29 | return ( 30 | 31 | {type} 32 | {value} 33 | 34 | ); 35 | } 36 | 37 | export default NftAttribute; 38 | -------------------------------------------------------------------------------- /src/app/screens/sendBtc/title.tsx: -------------------------------------------------------------------------------- 1 | import TokenImage from '@components/tokenImage'; 2 | import styled from 'styled-components'; 3 | 4 | const TitleContainer = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | flex: 0 0; 9 | `; 10 | 11 | const TitleText = styled.div` 12 | ${(props) => props.theme.typography.headline_xs} 13 | margin-top: ${(props) => props.theme.spacing(6)}px; 14 | margin-bottom: ${(props) => props.theme.spacing(12)}px; 15 | `; 16 | 17 | type Props = { 18 | title: string; 19 | showBtcIcon?: boolean; 20 | }; 21 | 22 | export default function Title({ title, showBtcIcon = false }: Props) { 23 | return ( 24 | 25 | {showBtcIcon && } 26 | {title} 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/app/screens/sendOrdinal/steps.tsx: -------------------------------------------------------------------------------- 1 | export enum Step { 2 | SelectRecipient = 0, 3 | Confirm = 1, 4 | } 5 | 6 | export const getNextStep = (currentStep: Step) => { 7 | switch (currentStep) { 8 | case Step.SelectRecipient: 9 | return Step.Confirm; 10 | case Step.Confirm: 11 | return Step.Confirm; 12 | default: 13 | throw new Error(`Unknown step: ${currentStep}`); 14 | } 15 | }; 16 | 17 | export const getPreviousStep = (currentStep: Step) => { 18 | switch (currentStep) { 19 | case Step.SelectRecipient: 20 | return Step.SelectRecipient; 21 | case Step.Confirm: 22 | return Step.SelectRecipient; 23 | default: 24 | throw new Error(`Unknown step: ${currentStep}`); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /src/app/screens/sendRune/steps.tsx: -------------------------------------------------------------------------------- 1 | export enum Step { 2 | SelectRecipient = 0, 3 | SelectAmount = 1, 4 | Confirm = 2, 5 | } 6 | 7 | export const getNextStep = (currentStep: Step) => { 8 | switch (currentStep) { 9 | case Step.SelectRecipient: 10 | return Step.SelectAmount; 11 | case Step.SelectAmount: 12 | return Step.Confirm; 13 | case Step.Confirm: 14 | return Step.Confirm; 15 | default: 16 | throw new Error(`Unknown step: ${currentStep}`); 17 | } 18 | }; 19 | 20 | export const getPreviousStep = (currentStep: Step) => { 21 | switch (currentStep) { 22 | case Step.SelectRecipient: 23 | return Step.SelectRecipient; 24 | case Step.SelectAmount: 25 | return Step.SelectRecipient; 26 | case Step.Confirm: 27 | return Step.SelectAmount; 28 | default: 29 | throw new Error(`Unknown step: ${currentStep}`); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/app/screens/sendStx/stepResolver.tsx: -------------------------------------------------------------------------------- 1 | export enum Step { 2 | SelectRecipient = 0, 3 | SelectAmount = 1, 4 | Confirm = 2, 5 | } 6 | 7 | export const getNextStep = (currentStep: Step, amountEditable: boolean) => { 8 | switch (currentStep) { 9 | case Step.SelectRecipient: 10 | return amountEditable ? Step.SelectAmount : Step.Confirm; 11 | case Step.SelectAmount: 12 | return Step.Confirm; 13 | case Step.Confirm: 14 | return Step.Confirm; 15 | default: 16 | throw new Error(`Unknown step: ${currentStep}`); 17 | } 18 | }; 19 | 20 | export const getPreviousStep = (currentStep: Step, amountEditable: boolean) => { 21 | switch (currentStep) { 22 | case Step.SelectRecipient: 23 | return Step.SelectRecipient; 24 | case Step.SelectAmount: 25 | return Step.SelectRecipient; 26 | case Step.Confirm: 27 | return amountEditable ? Step.SelectAmount : Step.SelectRecipient; 28 | default: 29 | throw new Error(`Unknown step: ${currentStep}`); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/app/screens/sendStx/steps/Step3Confirm.tsx: -------------------------------------------------------------------------------- 1 | import { stxToMicrostacks } from '@secretkeylabs/xverse-core'; 2 | import RoutePaths from 'app/routes/paths'; 3 | import BigNumber from 'bignumber.js'; 4 | import { useEffect } from 'react'; 5 | import { useNavigate } from 'react-router-dom'; 6 | 7 | type Props = { 8 | unsignedSendStxTx: string; 9 | fee: string; 10 | ftConfirmParams?: any; 11 | }; 12 | 13 | function Step3Confirm({ unsignedSendStxTx, fee, ftConfirmParams }: Props) { 14 | const navigate = useNavigate(); 15 | 16 | useEffect(() => { 17 | if (ftConfirmParams) { 18 | navigate('/confirm-ft-tx', { 19 | state: ftConfirmParams, 20 | }); 21 | return; 22 | } 23 | 24 | navigate(RoutePaths.ConfirmStacksTransaction, { 25 | state: { 26 | unsignedTx: unsignedSendStxTx, 27 | fee: stxToMicrostacks(new BigNumber(fee)).toString(), 28 | }, 29 | }); 30 | // eslint-disable-next-line react-hooks/exhaustive-deps 31 | }, []); 32 | 33 | return null; 34 | } 35 | export default Step3Confirm; 36 | -------------------------------------------------------------------------------- /src/app/screens/settings/connectedAppsAndPermissions/types.ts: -------------------------------------------------------------------------------- 1 | import { type Permissions } from '@secretkeylabs/xverse-core'; 2 | 3 | export type ConnectedApp = { client: Permissions.Store.Client; lastUsed: number }; 4 | -------------------------------------------------------------------------------- /src/app/screens/settings/index.styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Container = styled.div((props) => ({ 4 | display: 'flex', 5 | flexDirection: 'column', 6 | flex: 1, 7 | padding: `0 ${props.theme.space.xs}`, 8 | ...props.theme.scrollbar, 9 | })); 10 | 11 | export const Title = styled.h1((props) => ({ 12 | ...props.theme.typography.headline_xs, 13 | paddingTop: props.theme.space.xs, 14 | paddingBottom: props.theme.space.m, 15 | })); 16 | 17 | export const SubTitle = styled.p((props) => ({ 18 | ...props.theme.typography.body_m, 19 | color: props.theme.colors.white_200, 20 | marginBottom: props.theme.space.l, 21 | })); 22 | -------------------------------------------------------------------------------- /src/app/screens/signatureRequest/signatureRequestMessage.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import styled from 'styled-components'; 3 | import CollapsableContainer from './collapsableContainer'; 4 | 5 | const RequestMessage = styled.p((props) => ({ 6 | ...props.theme.typography.body_medium_m, 7 | textAlign: 'left', 8 | wordWrap: 'break-word', 9 | color: props.theme.colors.white_0, 10 | })); 11 | 12 | type Props = { 13 | message: string; 14 | }; 15 | 16 | export default function SignatureRequestMessage(props: Props) { 17 | const { t } = useTranslation('translation', { keyPrefix: 'SIGNATURE_REQUEST' }); 18 | const { message } = props; 19 | 20 | return ( 21 | 22 | {message.split(/\r?\n/).map((line) => ( 23 | {line} 24 | ))} 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/app/screens/signatureRequest/signatureRequestStructuredData.tsx: -------------------------------------------------------------------------------- 1 | import type { StructuredDataSignaturePayload } from '@stacks/connect'; 2 | import { deserializeCV } from '@stacks/transactions'; 3 | import { useTranslation } from 'react-i18next'; 4 | import ClarityMessageView from './clarityMessageView'; 5 | import CollapsableContainer from './collapsableContainer'; 6 | 7 | interface SignatureRequestStructuredDataProps { 8 | payload: StructuredDataSignaturePayload; 9 | } 10 | 11 | export default function SignatureRequestStructuredData(props: SignatureRequestStructuredDataProps) { 12 | const { t } = useTranslation('translation', { keyPrefix: 'SIGNATURE_REQUEST' }); 13 | const { payload } = props; 14 | return ( 15 | 16 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/app/screens/speedUpTransaction/index.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const LoaderContainer = styled.div({ 4 | display: 'flex', 5 | justifyContent: 'center', 6 | alignItems: 'center', 7 | height: 'inherit', 8 | }); 9 | 10 | export const SuccessActionsContainer = styled.div((props) => ({ 11 | width: '100%', 12 | display: 'flex', 13 | flexDirection: 'column', 14 | gap: props.theme.space.s, 15 | paddingLeft: props.theme.space.m, 16 | paddingRight: props.theme.space.m, 17 | marginBottom: props.theme.space.xxl, 18 | marginTop: props.theme.space.xxl, 19 | })); 20 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/broadcastError.tsx: -------------------------------------------------------------------------------- 1 | import xCircleIcon from '@assets/img/send/x_circle.svg'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { Final } from './shared/final'; 4 | 5 | export function BroadcastError() { 6 | const { t: tStatus } = useTranslation('translation', { keyPrefix: 'TRANSACTION_STATUS' }); 7 | const { t: tConfirm } = useTranslation('translation', { keyPrefix: 'CONFIRM_TRANSACTION' }); 8 | 9 | return ( 10 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/README.md: -------------------------------------------------------------------------------- 1 | # Fee Editor 2 | 3 | The fee editor fetches for fees on mount, even if it is not opened, so as to increase the chances that fee data will be available when the user opens the editor, thus helping avoid a momentary "blank" UI while data loads, since data-loading designs are not yet available. 4 | 5 | Once designs are available, the editor may be updated to better handle loading behavior and its UI behavior. 6 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/transactionDetails/components/contractCallDetails/callSummary/index.tsx: -------------------------------------------------------------------------------- 1 | import { isContractCallPayload, type StacksTransactionWire } from '@stacks/transactions'; 2 | import { TransactionDetailsLayout } from './callSummaryLoader'; 3 | 4 | type CallSummaryProps = { 5 | /** 6 | * Expectes a contract call transaction. 7 | */ 8 | transaction: StacksTransactionWire; 9 | }; 10 | 11 | function TransactionDetailsCheckPayloadType({ transaction }: CallSummaryProps) { 12 | const { payload } = transaction; 13 | if (!isContractCallPayload(payload)) { 14 | throw new Error('Expected a contract call payload', { cause: payload }); 15 | } 16 | 17 | return ; 18 | } 19 | 20 | export function TransactionDetails({ transaction }: CallSummaryProps) { 21 | return ; 22 | } 23 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/transactionDetails/components/contractCallDetails/components/fee/index.tsx: -------------------------------------------------------------------------------- 1 | import { AuthType } from '@stacks/transactions'; 2 | import { FeeLoader } from './feeLoader'; 3 | import { Sponsored } from './sponsored'; 4 | import type { Props } from './types'; 5 | 6 | function FeeSponsoredCheck(props: Props) { 7 | const { transaction } = props; 8 | 9 | const isSponsored = transaction.auth.authType === AuthType.Sponsored; 10 | if (isSponsored) return ; 11 | 12 | return ; 13 | } 14 | 15 | export function Fee(props: Props) { 16 | return ; 17 | } 18 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/transactionDetails/components/contractCallDetails/components/fee/types.ts: -------------------------------------------------------------------------------- 1 | import type { StacksTransactionWire } from '@stacks/transactions'; 2 | 3 | export type Props = { 4 | transaction: StacksTransactionWire; 5 | onEdit?: (transaction: StacksTransactionWire) => void; 6 | }; 7 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/transactionDetails/components/contractCallDetails/noTransfersMessage.tsx: -------------------------------------------------------------------------------- 1 | import { StyledP } from '@ui-library/common.styled'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { Card } from '../card'; 4 | 5 | export function NoTransfersMessage() { 6 | const { t } = useTranslation('translation'); 7 | return ( 8 | 9 | 10 | {t('CONTRACT_CALL_REQUEST.POST_CONDITION_ALERT')} 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/transactionDetails/components/contractCallDetails/postConditions/README.md: -------------------------------------------------------------------------------- 1 | # Post Conditions - Completion progress 2 | 3 | This feature is partially complete: components may not match the expected styles or work as expected. 4 | 5 | Initially, post conditions were to be released as part of the work for `stx_signTransactions`, and were later left outside of scope. 6 | 7 | The components have been left here for when we pick this up again. 8 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/transactionDetails/components/contractCallDetails/postConditions/fungibleTokenPostCondition.tsx: -------------------------------------------------------------------------------- 1 | export function FTPostCondition() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/transactionDetails/components/contractCallDetails/postConditions/nftPostCondition.tsx: -------------------------------------------------------------------------------- 1 | export function NFTPostCondition() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/transactionDetails/components/notYetSupported.tsx: -------------------------------------------------------------------------------- 1 | export function NotYetSupported() { 2 | return ( 3 |
4 |

Not Yet Supported

5 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/transactionDetails/components/smartContractDetails.tsx: -------------------------------------------------------------------------------- 1 | import { type SmartContractPayloadWire, type StacksTransactionWire } from '@stacks/transactions'; 2 | 3 | export function SmartContractDetails({ transaction }: { transaction: StacksTransactionWire }) { 4 | const payload = transaction.payload as SmartContractPayloadWire; 5 | 6 | return ( 7 |
8 |

Smart Contract

9 |

{payload.codeBody.content}

10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/transactionDetails/components/tokenTransferDetails.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | cvToValue, 3 | type StacksTransactionWire, 4 | type TokenTransferPayloadWire, 5 | } from '@stacks/transactions'; 6 | 7 | export function TokenTransferDetails({ transaction }: { transaction: StacksTransactionWire }) { 8 | const payload = transaction.payload as TokenTransferPayloadWire; 9 | 10 | return ( 11 |
12 |

Token Transfer

13 |

Amount: {Number(payload.amount)}

14 |

Recipient: {cvToValue(payload.recipient)}

15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/transactionDetails/components/versionedSmartContractDetails.tsx: -------------------------------------------------------------------------------- 1 | import { SmartContractDetails } from './smartContractDetails'; 2 | 3 | // There are no versioned smart contract specific features yet. 4 | export const VersionedSmartContractDetails = SmartContractDetails; 5 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/transactionDetails/index.tsx: -------------------------------------------------------------------------------- 1 | import { transactionTypeToDetailsComponentMap } from './transactionTypeToComponentMap'; 2 | import type { TransactionInfoProps } from './types'; 3 | 4 | export function TransactionDetails(props: TransactionInfoProps) { 5 | const { transaction } = props; 6 | const Component = transactionTypeToDetailsComponentMap[transaction.payload.payloadType]; 7 | return ; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/transactionDetails/styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Container = styled.div(({ theme }) => ({ 4 | display: 'flex', 5 | flexDirection: 'column', 6 | rowGap: theme.space.xs, 7 | })); 8 | 9 | export const Title = styled.div(({ theme }) => ({ 10 | ...theme.typography.body_medium_m, 11 | color: theme.colors.white_200, 12 | })); 13 | 14 | /** 15 | * This component exists to prevent icons or small buttons from registering any 16 | * height yet be vertically centered. This is needed since they may at times be 17 | * accidentally slightly taller than the row they're in (e.g., few extra px 18 | * padding) therefore vertically distorting the layout. 19 | */ 20 | export const VerticalCenteringNoHeight = styled.div({ 21 | height: 0, // This is the important bit! 22 | 23 | display: 'flex', 24 | flexDirection: 'row', 25 | alignItems: 'center', 26 | overflow: 'visible', 27 | }); 28 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/transactionDetails/types.ts: -------------------------------------------------------------------------------- 1 | import type { StacksTransactionWire } from '@stacks/transactions'; 2 | 3 | export type TransactionInfoProps = { 4 | transaction: StacksTransactionWire; 5 | onEditNonce?: () => void; 6 | onEditFee?: (transaction: StacksTransactionWire) => void; 7 | }; 8 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/components/utils.ts: -------------------------------------------------------------------------------- 1 | import { MICROSTX_IN_STX } from './transactionDetails/utils'; 2 | 3 | export function mapObjectValues( 4 | obj: Record, 5 | fn: (arg: V) => V2, 6 | ): Record { 7 | return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v as V)])) as Record; 8 | } 9 | 10 | export function microStxToStx(microStx: number | string | bigint) { 11 | const microStxNumber = Number(microStx); 12 | return Number(microStxNumber) / MICROSTX_IN_STX; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/review/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react'; 2 | 3 | /** 4 | * This hook is used to force a re-render of the component if necessary when 5 | * Stacks transactions are being manipulated. The issue is that the helpers 6 | * available from Stacks.js to modify Stacks transactions mutate the transaction 7 | * object. Mutating keeps its reference unchanged, React does not 8 | * detect a change and doesn't re-render with the new values. 9 | */ 10 | export function useRender() { 11 | const [, setState] = useState({}); 12 | 13 | return useCallback(() => setState({}), [setState]); 14 | } 15 | -------------------------------------------------------------------------------- /src/app/screens/stxSignTransactions/components/getPopupPayload/components/loader/components/signingFlow/components/shared/actions/index.tsx: -------------------------------------------------------------------------------- 1 | import Button from '@ui-library/button'; 2 | import styled from 'styled-components'; 3 | 4 | const Container = styled.div(({ theme }) => ({ 5 | display: 'flex', 6 | justifyContent: 'space-between', 7 | columnGap: theme.space.s, 8 | paddingTop: theme.space.l, 9 | paddingBottom: theme.space.l, 10 | })); 11 | 12 | type Action = { title: string; onClick?: () => void; disabled?: boolean; loading?: boolean }; 13 | 14 | export type Props = { 15 | main: Action; 16 | secondary?: Action; 17 | }; 18 | 19 | export function Actions({ main, secondary }: Props) { 20 | return ( 21 | 22 | {secondary && 41 | ); 42 | } 43 | 44 | export default CrossButton; 45 | -------------------------------------------------------------------------------- /src/app/ui-library/divider.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import Theme from 'theme'; 3 | 4 | const Divider = styled.div<{ 5 | $verticalMargin?: keyof typeof Theme.space; 6 | $color?: keyof typeof Theme.colors; 7 | }>((props) => ({ 8 | display: 'flex', 9 | width: '100%', 10 | height: 1, 11 | backgroundColor: props.$color 12 | ? String(props.theme.colors[props.$color]) 13 | : props.theme.colors.white_900, 14 | margin: props.$verticalMargin ? `${props.theme.space[props.$verticalMargin]} 0` : 0, 15 | })); 16 | export default Divider; 17 | -------------------------------------------------------------------------------- /src/app/utils/chromeLocalStorage.ts: -------------------------------------------------------------------------------- 1 | import chromeStorage from '@utils/chromeStorage'; 2 | 3 | export const chromeLocalStorageKeys = { 4 | isPriorityWallet: 'isPriorityWallet', 5 | }; 6 | 7 | export async function getIsPriorityWallet(): Promise { 8 | const isPriorityWallet = await chromeStorage.local.getItem( 9 | chromeLocalStorageKeys.isPriorityWallet, 10 | ); 11 | 12 | return isPriorityWallet ?? true; 13 | } 14 | -------------------------------------------------------------------------------- /src/app/utils/date.ts: -------------------------------------------------------------------------------- 1 | import { format, isToday, isYesterday } from 'date-fns'; 2 | 3 | // eslint-disable-next-line import/prefer-default-export 4 | export const formatDate = (date: Date) => { 5 | if (isToday(date)) { 6 | return 'Today'; 7 | } 8 | if (isYesterday(date)) { 9 | return 'Yesterday'; 10 | } 11 | return format(date, 'MMMM dd, yyyy'); 12 | }; 13 | 14 | export const formatDateKey = (date: Date) => format(date, 'yyyy-MM-dd'); 15 | -------------------------------------------------------------------------------- /src/app/utils/ledger.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MessageSigningProtocols, 3 | signMessageLedger, 4 | type LedgerTransport, 5 | type NetworkType, 6 | } from '@secretkeylabs/xverse-core'; 7 | 8 | export const handleLedgerMessageSigning = async ({ 9 | transport, 10 | addressIndex, 11 | address, 12 | networkType, 13 | message, 14 | protocol, 15 | }: { 16 | transport: LedgerTransport; 17 | addressIndex?: number; 18 | address: string; 19 | networkType: NetworkType; 20 | message: string; 21 | protocol?: MessageSigningProtocols; 22 | }) => { 23 | if (addressIndex === undefined) { 24 | throw new Error('Account not found'); 25 | } 26 | 27 | const signature = await signMessageLedger({ 28 | transport, 29 | networkType, 30 | addressIndex, 31 | address, 32 | message, 33 | protocol, 34 | }); 35 | 36 | return signature; 37 | }; 38 | 39 | export const signatureVrsToRsv = (sig: string): string => sig.slice(2) + sig.slice(0, 2); 40 | -------------------------------------------------------------------------------- /src/app/utils/localStorage.ts: -------------------------------------------------------------------------------- 1 | const isTermsAccepted = 'isTermsAccepted'; 2 | 3 | export function saveIsTermsAccepted(termsDisplayed: boolean) { 4 | localStorage.setItem(isTermsAccepted, termsDisplayed.toString()); 5 | } 6 | 7 | export function getIsTermsAccepted(): boolean { 8 | const accepted = localStorage.getItem(isTermsAccepted); 9 | return accepted !== null; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/utils/mappers.ts: -------------------------------------------------------------------------------- 1 | import type { FungibleToken, RuneBase } from '@secretkeylabs/xverse-core'; 2 | 3 | // eslint-disable-next-line import/prefer-default-export 4 | export const mapRuneBaseToFungibleToken = (rune: RuneBase): FungibleToken => ({ 5 | protocol: 'runes', 6 | name: rune.runeName, 7 | principal: rune.runeId, 8 | assetName: '', 9 | balance: '', 10 | total_received: '', 11 | total_sent: '', 12 | runeSymbol: rune.symbol, 13 | runeInscriptionId: rune.inscriptionId, 14 | }); 15 | -------------------------------------------------------------------------------- /src/app/utils/runes.ts: -------------------------------------------------------------------------------- 1 | import type { GetRunesUtxoItem } from '@secretkeylabs/xverse-core'; 2 | 3 | export const getFullTxId = (item: GetRunesUtxoItem) => `${item.txid}:${item.vout.toString()}`; 4 | 5 | export const getVoutFromFullTxId = (id: string) => id.split(':').pop() || ''; 6 | export const getTxIdFromFullTxId = (id: string): string => id.split(':')[0] || ''; 7 | 8 | export type RuneItem = { 9 | selected: boolean; 10 | amount: number; 11 | satAmount: number; 12 | priceSats: number; 13 | useIndividualCustomPrice: boolean; 14 | }; 15 | -------------------------------------------------------------------------------- /src/assets/fonts/DMSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/fonts/DMSans-Bold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/DMSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/fonts/DMSans-Medium.ttf -------------------------------------------------------------------------------- /src/assets/fonts/DMSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/fonts/DMSans-Regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/IBMPlexSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/fonts/IBMPlexSans-Bold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/IBMPlexSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/fonts/IBMPlexSans-Medium.ttf -------------------------------------------------------------------------------- /src/assets/fonts/IBMPlexSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/fonts/IBMPlexSans-Regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Satoshi-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/fonts/Satoshi-Black.otf -------------------------------------------------------------------------------- /src/assets/fonts/Satoshi-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/fonts/Satoshi-Bold.otf -------------------------------------------------------------------------------- /src/assets/fonts/Satoshi-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/fonts/Satoshi-Medium.otf -------------------------------------------------------------------------------- /src/assets/fonts/Satoshi-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/fonts/Satoshi-Regular.otf -------------------------------------------------------------------------------- /src/assets/img/Copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/img/arrow_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/arrow_square_out.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/bottomTabBar/nft_tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/img/bottomTabBar/stacking_tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/bottomTabBar/unselected_nft_tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/img/bottomTabBar/unselected_stacking_tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/img/bottomTabBar/unselected_wallet_tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/img/bottomTabBar/wallet_tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/checkmark-bold.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/checkmarkIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/createPassword/Eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/createPassword/EyeSlash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/createWalletSuccess/CheckCircle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/createWalletSuccess/extension.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/createWalletSuccess/pin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/Copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/X.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/add_token.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/arrow_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/arrow_down_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/arrow_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/arrow_up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/arrow_up_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/binance.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/black_plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/caret_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/copy_black_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/credit_card.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/faders_horizontal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/moonpay.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/img/dashboard/tick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/dots_three_vertical.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/img/hw/connect_hw.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/img/hw/keystone/keystone_connect_default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/img/hw/keystone/keystone_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/hw/keystone/keystone_import_start.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/hw/ledger/arrow_left_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/hw/ledger/check_circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/hw/ledger/info_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/hw/ledger/ledger_connect_default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/img/hw/ledger/ledger_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/hw/ledger/ledger_import_connect_done.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/img/hw/ledger/ledger_import_connect_fail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/img/hw/ledger/ordinals_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/hw/ledger/ordinals_icon_big.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/img/icons/ArrowSwap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/img/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/linkIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/ArrowUpRight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/Copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/Printer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/nft_fallback.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/ordinals_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/1Dpali.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/1Dpali.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/1stx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/1stx.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/2Dpali.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/2Dpali.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/3Dpali.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/3Dpali.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/alpha.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/b286.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/b286.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/b78.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/b78.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/b9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/b9.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/b9_450.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/b9_450.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/black_epic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/black_epic.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/black_legendary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/black_legendary.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/black_rare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/black_rare.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/black_uncommon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/black_uncommon.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/epic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/epic.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/fibonacci.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/fibonacci.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/hitman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/hitman.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/jpeg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/jpeg.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/legacy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/legacy.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/legendary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/legendary.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/mythic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/mythic.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/nakamoto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/nakamoto.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/namepali.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/namepali.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/omega.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/omega.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/pali.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/pali.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/paliblock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/paliblock.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/perfectpaliception.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/perfectpaliception.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/pizza.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/pizza.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/rare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/rare.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/seqpali.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/seqpali.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/silkroad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/silkroad.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/uncommon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/uncommon.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/unknown.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rareSats/vintage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/nftDashboard/rareSats/vintage.png -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/rune_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/share.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/shareNft/Envelope.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/shareNft/facebook-f.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/share_network.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/squares_four.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/img/nftDashboard/white_ordinals_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/rareSats/ic_ordinal_small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/img/rareSats/ic_ordinal_small_over_card.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/img/rareSats/satBundle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/receive_ordinals_image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/img/send/check_circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/send/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/send/switch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/img/send/x_circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/settings/arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/settings/check_circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/settings/check_square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/settings/currencies/che.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/img/settings/currencies/chn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/img/settings/currencies/cop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/img/settings/currencies/gbr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/assets/img/settings/currencies/hun.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/img/settings/currencies/idn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/img/settings/currencies/jpn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/img/settings/currencies/nga.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/img/settings/currencies/pol.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/img/settings/currencies/ron.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/img/settings/currencies/rus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/img/settings/currencies/tha.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/img/settings/currencies/vnm.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/img/settings/tick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/settings/x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/swap/arrow_swap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/swap/chevron.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/swap/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/swap/fold_arrow_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/swap/fold_arrow_up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/swap/slippageEdit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/img/tick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/transactions/ArrowDown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/transactions/Assets.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/img/transactions/Copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/transactions/Lock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/img/transactions/Minus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/transactions/Plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/transactions/ScriptIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/transactions/contract.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/transactions/dropDownIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/transactions/failed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/transactions/microBlock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/transactions/ordinal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/transactions/output.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/transactions/pending.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/transactions/received.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/transactions/runes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/webInteractions/ArrowLineDown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/webInteractions/goto-explorer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/img/xverse_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/assets/img/xverse_icon.png -------------------------------------------------------------------------------- /src/common/types/ledger.ts: -------------------------------------------------------------------------------- 1 | import type { StacksRecipient } from '@secretkeylabs/xverse-core'; 2 | import BigNumber from 'bignumber.js'; 3 | 4 | export type ConfirmStxTransactionState = { 5 | recipients: StacksRecipient[]; 6 | unsignedTx: Buffer; 7 | fee: BigNumber; 8 | }; 9 | -------------------------------------------------------------------------------- /src/common/types/messages.ts: -------------------------------------------------------------------------------- 1 | import type { ExtensionMethods, InternalMethods, Message } from './message-types'; 2 | 3 | /** 4 | * Popup <-> Background Script 5 | */ 6 | type BackgroundMessage = Omit< 7 | Message, 8 | 'source' 9 | >; 10 | 11 | type OriginatingTabClosed = BackgroundMessage< 12 | InternalMethods.OriginatingTabClosed, 13 | { tabId: number } 14 | >; 15 | 16 | export type BackgroundMessages = OriginatingTabClosed; 17 | 18 | export function sendMessage(message: BackgroundMessages) { 19 | return chrome.runtime.sendMessage(message); 20 | } 21 | -------------------------------------------------------------------------------- /src/common/utils/getEventSourceWindow.ts: -------------------------------------------------------------------------------- 1 | const getEventSourceWindow = (event: MessageEvent) => { 2 | const isWindow = 3 | !(event.source instanceof MessagePort) && !(event.source instanceof ServiceWorker); 4 | if (isWindow) { 5 | return event.source as Window; 6 | } 7 | return null; 8 | }; 9 | 10 | export default getEventSourceWindow; 11 | -------------------------------------------------------------------------------- /src/common/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { createUnsecuredToken } from 'jsontokens'; 2 | import { stringify } from 'superjson'; 3 | 4 | export function getTabIdFromPort(port: chrome.runtime.Port) { 5 | const tabId = port.sender?.tab?.id; 6 | 7 | if (!tabId) { 8 | throw new Error('Could not determine tab id from port.', { cause: port }); 9 | } 10 | 11 | return tabId; 12 | } 13 | 14 | export function getOriginFromPort(port: chrome.runtime.Port) { 15 | const origin = port.sender?.url ? new URL(port.sender.url).origin : port.sender?.origin; 16 | 17 | if (!origin) { 18 | throw new Error('Could not determine origin from port.', { cause: port }); 19 | } 20 | 21 | return origin; 22 | } 23 | 24 | export function stringifyData(data: unknown) { 25 | return createUnsecuredToken(stringify(data)); 26 | } 27 | -------------------------------------------------------------------------------- /src/common/utils/messageHandlers.ts: -------------------------------------------------------------------------------- 1 | import type { BackgroundMessages } from 'common/types/messages'; 2 | 3 | function validateMessagesAreFromExtension(sender: chrome.runtime.MessageSender) { 4 | // Only respond to internal messages from our UI, not content scripts in other applications 5 | return sender.url?.startsWith(chrome.runtime.getURL('')); 6 | } 7 | 8 | async function internalBackgroundMessageHandler( 9 | message: BackgroundMessages, 10 | sender: chrome.runtime.MessageSender, 11 | sendResponse: (response?: any) => void, 12 | ) { 13 | if (!validateMessagesAreFromExtension(sender)) { 14 | return; 15 | } 16 | sendResponse(); 17 | } 18 | 19 | export default internalBackgroundMessageHandler; 20 | -------------------------------------------------------------------------------- /src/common/utils/messages/extensionToContentScript/README.md: -------------------------------------------------------------------------------- 1 | # Sending messages to the content script 2 | 3 | To send messages to the content script, the extension environment provides [`chrome.tabs.sendMessage()`](https://developer.chrome.com/docs/extensions/reference/api/tabs#method-sendMessage). The methods for interacting with the content script or user applications build atop this method. 4 | 5 | Each feature provides its own wrappers for sending messages as required, 6 | 7 | ![Message flow](message-flow.png) 8 | 9 | The existing RPC methods have not yet been migrated to this area of the code. 10 | -------------------------------------------------------------------------------- /src/common/utils/messages/extensionToContentScript/dispatchEvent/README.md: -------------------------------------------------------------------------------- 1 | # Notify 2 | 3 | The `dispatchEvent*()` family of methods are used to notify pages that an event has taken place in the wallet. 4 | 5 | Since service workers can't directly communicate with page scripts, the `dispatchEvent*()` methods rely on intermediate code in the content script to relay the event to the page. A page is notified by having a wallet event dispatched on its `window` object. 6 | 7 | Some `dispatchEvent*()` methods are permisions aware. Only clients that meet the specified permissions will get notified of the event. 8 | 9 | Internally, the `dispatchEvent*()` methods use `chrome.tabs.sendMessage()` to send event data to the content script. Although it is techincally possible send events by calling `chrome.tabs.sendMessage()` directly, it is recommended to use the `dispatchEvent*()` methods so as to keep events easier to maintain. 10 | -------------------------------------------------------------------------------- /src/common/utils/messages/extensionToContentScript/message-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/secretkeylabs/xverse-web-extension/bdbd6a008fd3d8f04ea18eca0374fae67d90114c/src/common/utils/messages/extensionToContentScript/message-flow.png -------------------------------------------------------------------------------- /src/common/utils/promises.ts: -------------------------------------------------------------------------------- 1 | // a function that sleeps for a given amount of time (ms) 2 | export const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); 3 | -------------------------------------------------------------------------------- /src/common/utils/route-urls.ts: -------------------------------------------------------------------------------- 1 | enum RequestsRoutes { 2 | Home = '/', 3 | TransactionRequest = '/transaction-request', 4 | StxSignTransactions = '/stx-sign-transactions', 5 | AuthenticationRequest = '/authentication-request', 6 | SignatureRequest = '/signature-request', 7 | SignMessageRequest = '/sign-message-request', 8 | SignRuneDelistingMessage = '/sign-rune-delisting-message', 9 | AddressRequest = '/btc-select-address-request', 10 | StxAddressRequest = '/stx-select-address-request', 11 | StxAccountRequest = '/stx-select-account-request', 12 | SignBtcTx = '/psbt-signing-request', 13 | SignBatchBtcTx = '/batch-psbt-signing-request', 14 | RuneListingBatchSigning = '/rune-listing-batch-signing', 15 | SendBtcTx = '/btc-send-request', 16 | CreateInscription = '/create-inscription', 17 | CreateRepeatInscriptions = '/create-repeat-inscriptions', 18 | ConnectionRequest = '/connection-request', 19 | MintRune = '/mint-rune', 20 | EtchRune = '/etch-rune', 21 | } 22 | 23 | export default RequestsRoutes; 24 | -------------------------------------------------------------------------------- /src/common/utils/rpc/README.md: -------------------------------------------------------------------------------- 1 | # RPC message validation across contexts 2 | 3 | Incoming RPC request messages are validated with Valibot. The validation process can perform both structural checks (does the data have the right shape & types?) and logical checks (are the values sound?). 4 | 5 | The messages are checked prior to handing them off to the message handlers. The handlers may conclude the request flow by responding themselves, or may forward the message to other parts of the app. When the message is forwarded to other parts of the app across a context boundary (such as from the background script to the popup), validation information is lost. 6 | 7 | Although repetitive, until a better solution is found, messages need to be validated each time they cross a context boundary to ensure they are valid and their types inferred correctly. Given the messages are typically small, the added overhead seems worth the stability multiple validations provide. 8 | -------------------------------------------------------------------------------- /src/common/utils/rpc/btc/getAccounts.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import { getTabIdFromPort } from '@common/utils'; 3 | import { makeContext, openPopup } from '@common/utils/popup'; 4 | import { type GetAccountsRequestMessage } from '@sats-connect/core'; 5 | import RequestsRoutes from '../../route-urls'; 6 | import { makeSendPopupClosedUserRejectionMessage } from '../helpers'; 7 | 8 | export const handleGetAccounts = async ( 9 | message: GetAccountsRequestMessage, 10 | port: chrome.runtime.Port, 11 | ) => { 12 | await openPopup({ 13 | path: RequestsRoutes.AddressRequest, 14 | data: message, 15 | context: makeContext(port), 16 | onClose: makeSendPopupClosedUserRejectionMessage({ 17 | tabId: getTabIdFromPort(port), 18 | messageId: message.id, 19 | }), 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /src/common/utils/rpc/btc/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getAccounts'; 2 | export * from './getAddresses/getAddresses'; 3 | export * from './sendTransfer'; 4 | export * from './signMessage'; 5 | export * from './signPsbt'; 6 | -------------------------------------------------------------------------------- /src/common/utils/rpc/getInfo.ts: -------------------------------------------------------------------------------- 1 | import { type GetInfoRequestMessage, type Requests, type Return } from '@sats-connect/core'; 2 | import { keys } from 'ts-transformer-keys'; 3 | import { getTabIdFromPort } from '..'; 4 | import { makeRpcSuccessResponse, sendRpcResponse } from './helpers'; 5 | 6 | declare const VERSION: string; 7 | 8 | async function handleGetInfo(message: GetInfoRequestMessage, port: chrome.runtime.Port) { 9 | const response: Return<'getInfo'> = { 10 | version: VERSION, 11 | 12 | // TODO: migrate when all methods have been migrated. See 13 | // https://linear.app/xverseapp/issue/ENG-4623 14 | methods: keys(), 15 | supports: [], 16 | }; 17 | sendRpcResponse(getTabIdFromPort(port), makeRpcSuccessResponse(message.id, response)); 18 | } 19 | 20 | export default handleGetInfo; 21 | -------------------------------------------------------------------------------- /src/common/utils/rpc/handlerRouter/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import type { RpcRequestMessage } from '@sats-connect/core'; 3 | import { router as bitcoinRouter } from './bitcoin'; 4 | import { router as ordinalsRouter } from './ordinals'; 5 | import { router as runesRouter } from './runes'; 6 | import { router as stacksRouter } from './stacks'; 7 | import { router as walletRouter } from './wallet'; 8 | 9 | export type Handler = (message: RpcRequestMessage, port: chrome.runtime.Port) => Promise; 10 | 11 | export const router: Record = { 12 | ...walletRouter, 13 | ...bitcoinRouter, 14 | ...stacksRouter, 15 | ...runesRouter, 16 | ...ordinalsRouter, 17 | }; 18 | -------------------------------------------------------------------------------- /src/common/utils/rpc/ordinals/sendInscriptions.ts: -------------------------------------------------------------------------------- 1 | import { getTabIdFromPort } from '@common/utils'; 2 | import { makeContext, openPopup } from '@common/utils/popup'; 3 | import type { SendInscriptionsRequestMessage } from '@sats-connect/core'; 4 | import RoutePaths from 'app/routes/paths'; 5 | import { makeSendPopupClosedUserRejectionMessage } from '../helpers'; 6 | 7 | const handleSendInscriptions = async ( 8 | message: SendInscriptionsRequestMessage, 9 | port: chrome.runtime.Port, 10 | ) => { 11 | await openPopup({ 12 | path: RoutePaths.SendInscriptionsRequest, 13 | data: message, 14 | context: makeContext(port), 15 | onClose: makeSendPopupClosedUserRejectionMessage({ 16 | tabId: getTabIdFromPort(port), 17 | messageId: message.id, 18 | }), 19 | }); 20 | }; 21 | 22 | export default handleSendInscriptions; 23 | -------------------------------------------------------------------------------- /src/common/utils/rpc/responseMessages/types.ts: -------------------------------------------------------------------------------- 1 | import type { RpcId } from '@sats-connect/core'; 2 | 3 | export type BaseArgs = { 4 | tabId: NonNullable; 5 | messageId: RpcId; 6 | }; 7 | -------------------------------------------------------------------------------- /src/common/utils/rpc/runes/transfer.ts: -------------------------------------------------------------------------------- 1 | import { getTabIdFromPort } from '@common/utils'; 2 | import { makeContext, openPopup } from '@common/utils/popup'; 3 | import { type RunesTransferRequestMessage } from '@sats-connect/core'; 4 | import RoutePaths from 'app/routes/paths'; 5 | import { makeSendPopupClosedUserRejectionMessage } from '../helpers'; 6 | 7 | const handleTransferRunes = async ( 8 | message: RunesTransferRequestMessage, 9 | port: chrome.runtime.Port, 10 | ) => { 11 | await openPopup({ 12 | path: RoutePaths.TransferRunesRequest, 13 | data: message, 14 | context: makeContext(port), 15 | onClose: makeSendPopupClosedUserRejectionMessage({ 16 | tabId: getTabIdFromPort(port), 17 | messageId: message.id, 18 | }), 19 | }); 20 | }; 21 | 22 | export default handleTransferRunes; 23 | -------------------------------------------------------------------------------- /src/common/utils/rpc/stx/signTransaction/utils.ts: -------------------------------------------------------------------------------- 1 | import { deserializeTransaction } from '@stacks/transactions'; 2 | 3 | /* eslint-disable import/prefer-default-export */ 4 | export function isValidStacksTransaction(txHex: string) { 5 | try { 6 | deserializeTransaction(txHex); 7 | return true; 8 | } catch (e) { 9 | return false; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/common/utils/rpc/stx/signTransactions/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import { getTabIdFromPort } from '@common/utils'; 3 | import { makeContext, openPopup } from '@common/utils/popup'; 4 | import RequestsRoutes from '@common/utils/route-urls'; 5 | import { type StxSignTransactionsRequestMessage } from '@sats-connect/core'; 6 | import { makeSendPopupClosedUserRejectionMessage } from '../../helpers'; 7 | 8 | export async function signTransactions( 9 | message: StxSignTransactionsRequestMessage, 10 | port: chrome.runtime.Port, 11 | ) { 12 | await openPopup({ 13 | path: RequestsRoutes.StxSignTransactions, 14 | data: message, 15 | context: makeContext(port), 16 | onClose: makeSendPopupClosedUserRejectionMessage({ 17 | tabId: getTabIdFromPort(port), 18 | messageId: message.id, 19 | }), 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/common/utils/rpc/wallet/getNetwork.ts: -------------------------------------------------------------------------------- 1 | import { getTabIdFromPort } from '@common/utils'; 2 | import type { GetNetworkRequestMessage, GetNetworkResult } from '@sats-connect/core'; 3 | import rootStore from '@stores/index'; 4 | import { sendGetNetworkSuccessResponseMessage } from '../responseMessages/wallet'; 5 | 6 | export function handleGetNetwork(message: GetNetworkRequestMessage, port: chrome.runtime.Port) { 7 | const { network } = rootStore.store.getState().walletState; 8 | 9 | const result: GetNetworkResult = { 10 | bitcoin: { 11 | name: network.type, 12 | }, 13 | stacks: { 14 | // QUESTION: Do we need to add a stacks network names we could use here? 15 | name: network.type, 16 | }, 17 | }; 18 | 19 | sendGetNetworkSuccessResponseMessage({ 20 | tabId: getTabIdFromPort(port), 21 | messageId: message.id, 22 | result, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/common/utils/rpc/wallet/renouncePermissions.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import { type RenouncePermissionsRequestMessage } from '@sats-connect/core'; 3 | import { handleDisconnect } from './disconnect'; 4 | 5 | export const handleRenouncePermissions = async ( 6 | message: RenouncePermissionsRequestMessage, 7 | port: chrome.runtime.Port, 8 | ) => { 9 | // Renouncing individual permissions is not yet implemented, so all 10 | // permissions are renounced by disconnecting. 11 | handleDisconnect({ ...message, method: 'wallet_disconnect' }, port); 12 | }; 13 | -------------------------------------------------------------------------------- /src/common/walletEvents.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | export const walletEventName = 'xverse-wallet-event'; 3 | -------------------------------------------------------------------------------- /src/content-scripts/README.md: -------------------------------------------------------------------------------- 1 | # Processing incoming messages 2 | 3 | The content script listens for incomming messages using `chrome.runtime.onMessage()`. Messages may be 4 | 5 | - wallet events (e.g., account change, network change) or 6 | - RPC responses. 7 | 8 | The complete message list and schema definitions see [extensionToContentScript](../common/utils/messages/extensionToContentScript/). 9 | 10 | Messages that aren't events are assumed to be RPC responses and are forwarded to the tab using `window.postMessage()`. 11 | -------------------------------------------------------------------------------- /src/locales/index.ts: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { initReactI18next } from 'react-i18next'; 3 | import enJSON from './en.json'; 4 | 5 | const resources = { 6 | en: { translation: enJSON }, 7 | }; 8 | 9 | i18n.use(initReactI18next).init({ 10 | resources, 11 | lng: 'en', 12 | fallbackLng: 'en', 13 | react: { 14 | useSuspense: false, 15 | }, 16 | interpolation: { 17 | escapeValue: false, 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /src/pages/Options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Xverse Wallet 6 | 7 | 8 |
9 |
10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/pages/Options/index.tsx: -------------------------------------------------------------------------------- 1 | import WalletCloseGuard from '@components/guards/walletCloseGuard'; 2 | import { persistQueryClient } from '@tanstack/react-query-persist-client'; 3 | import { offlineStorage, queryClient } from '@utils/query'; 4 | import { createRoot } from 'react-dom/client'; 5 | import App from '../../app/App'; 6 | import './index.css'; 7 | 8 | declare const VERSION: string; 9 | 10 | const renderApp = async () => { 11 | persistQueryClient({ 12 | queryClient, 13 | persister: offlineStorage, 14 | buster: VERSION, 15 | }); 16 | const container = document.getElementById('app'); 17 | const root = createRoot(container!); 18 | return root.render( 19 | 20 | 21 | , 22 | ); 23 | }; 24 | 25 | renderApp(); 26 | -------------------------------------------------------------------------------- /src/pages/Popup/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | color: #ffffff; 5 | position: relative; 6 | background-color: #181818; 7 | } 8 | 9 | a { 10 | cursor: pointer; 11 | } 12 | 13 | #app { 14 | height: 600px; 15 | width: 360px; 16 | display: flex; 17 | flex-direction: column; 18 | justify-content: safe center; 19 | align-items: safe center; 20 | @media only screen and (min-width: 360px) { 21 | height: 100vh; 22 | width: 100vw; 23 | } 24 | } 25 | 26 | ::-webkit-scrollbar { 27 | display: none; 28 | } 29 | 30 | :focus-within, 31 | :focus-visible, 32 | :focus { 33 | outline: none; 34 | } 35 | 36 | :focus-visible { 37 | position: relative; 38 | border-radius: 4px; 39 | } 40 | 41 | :focus-visible::after { 42 | content: ''; 43 | position: absolute; 44 | inset: -2px; 45 | outline: 1px solid #ffffff; 46 | border: 2px solid #ee7a30; 47 | border-radius: inherit; 48 | pointer-events: none; 49 | } 50 | -------------------------------------------------------------------------------- /src/pages/Popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Xverse Wallet 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /src/pages/Popup/index.tsx: -------------------------------------------------------------------------------- 1 | import { persistQueryClient } from '@tanstack/react-query-persist-client'; 2 | import { offlineStorage, queryClient } from '@utils/query'; 3 | import { createRoot } from 'react-dom/client'; 4 | import App from '../../app/App'; 5 | import './index.css'; 6 | 7 | declare const VERSION: string; 8 | 9 | const renderApp = async () => { 10 | persistQueryClient({ 11 | queryClient, 12 | persister: offlineStorage, 13 | buster: VERSION, 14 | }); 15 | const container = document.getElementById('app'); 16 | 17 | const root = createRoot(container!); 18 | return root.render(); 19 | }; 20 | renderApp(); 21 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png'; 2 | declare module '*.svg' { 3 | const content: string; 4 | export default content; 5 | } 6 | declare module '*.jpeg'; 7 | declare module '*.jpg'; 8 | declare module '*.otf'; 9 | declare module '*.ttf'; 10 | declare module 'react-is-visible'; 11 | -------------------------------------------------------------------------------- /src/styled.d.ts: -------------------------------------------------------------------------------- 1 | import theme from './theme/index'; 2 | 3 | type CustomTheme = typeof theme; 4 | 5 | declare module 'styled-components' { 6 | export interface DefaultTheme extends CustomTheme {} 7 | } 8 | -------------------------------------------------------------------------------- /tests/fixtures/helpers.ts: -------------------------------------------------------------------------------- 1 | import { type Page } from '@playwright/test'; 2 | 3 | /** 4 | * Enables cross-chain swaps for the test environment. 5 | */ 6 | export async function enableCrossChainSwaps(page: Page): Promise { 7 | await page.route('https://api-3.xverse.app/v1/app-features', (route) => { 8 | route.fulfill({ 9 | status: 200, 10 | contentType: 'application/json', 11 | body: JSON.stringify({ 12 | CROSS_CHAIN_SWAPS: { enabled: true }, 13 | }), 14 | }); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /tests/fixtures/passwordTestData.ts: -------------------------------------------------------------------------------- 1 | // ToDo: fill with better passworddata 2 | export const passwordTestCases = [ 3 | { 4 | password: '123', 5 | expectations: { 6 | errorMessageVisible: true, 7 | securityLevel: 'Weak', 8 | continueButtonEnabled: false, 9 | }, 10 | }, 11 | { 12 | password: '123456789', 13 | expectations: { 14 | errorMessageVisible: true, 15 | securityLevel: 'Weak', 16 | continueButtonEnabled: false, 17 | }, 18 | }, 19 | { 20 | password: 'Admin@1234', 21 | expectations: { 22 | errorMessageVisible: false, 23 | securityLevel: 'Medium', 24 | continueButtonEnabled: true, 25 | }, 26 | }, 27 | { 28 | password: 'Admin@1234!!', 29 | expectations: { 30 | errorMessageVisible: false, 31 | securityLevel: 'Strong', 32 | continueButtonEnabled: true, 33 | }, 34 | }, 35 | ]; 36 | 37 | module.exports = { passwordTestCases }; 38 | -------------------------------------------------------------------------------- /tests/pages/landing.ts: -------------------------------------------------------------------------------- 1 | import { expect, type Locator, type Page } from '@playwright/test'; 2 | // Pageobject for landing page under options.html#/landing 3 | export default class Landing { 4 | readonly buttonCreateWallet: Locator; 5 | 6 | readonly buttonRestoreWallet: Locator; 7 | 8 | readonly landingTitle: Locator; 9 | 10 | constructor(readonly page: Page) { 11 | this.page = page; 12 | this.buttonCreateWallet = page.getByRole('button', { name: 'Create a new wallet' }); 13 | this.buttonRestoreWallet = page.getByRole('button', { name: 'Restore an existing wallet' }); 14 | this.landingTitle = page.getByText('The Bitcoin wallet for everyone'); 15 | } 16 | 17 | // Initialization Method for intial visual check of page object 18 | async initialize() { 19 | await expect(this.buttonCreateWallet).toBeVisible(); 20 | await expect(this.buttonRestoreWallet).toBeVisible(); 21 | await expect(this.landingTitle).toBeVisible(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/specs/healthcheck.spec.ts: -------------------------------------------------------------------------------- 1 | import { test } from '../fixtures/base'; 2 | import Landing from '../pages/landing'; 3 | 4 | test.describe('healthCheck', () => { 5 | test('healthCheck #smoketest', async ({ page, extensionId }) => { 6 | await page.goto(`chrome-extension://${extensionId}/options.html#/landing`); 7 | const landingPage = new Landing(page); 8 | await landingPage.initialize(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /webpack/utils/build.js: -------------------------------------------------------------------------------- 1 | // Do this as the first thing so that any code reading it knows the right env. 2 | process.env.BABEL_ENV = 'production'; 3 | process.env.NODE_ENV = 'production'; 4 | process.env.ASSET_PATH = '/'; 5 | 6 | var webpack = require('webpack'), 7 | config = require('../webpack.config'); 8 | 9 | config.mode = 'production'; 10 | 11 | webpack(config, function (err, stats) { 12 | if (err) throw err; 13 | console.log( 14 | stats.toString({ 15 | preset: 'minimal', 16 | colors: true, 17 | outputPath: true, 18 | env: true, 19 | }), 20 | ); 21 | }); 22 | -------------------------------------------------------------------------------- /webpack/utils/env.js: -------------------------------------------------------------------------------- 1 | // tiny wrapper with default env vars 2 | module.exports = { 3 | NODE_ENV: process.env.NODE_ENV || 'development', 4 | PORT: process.env.PORT || 3000, 5 | }; 6 | -------------------------------------------------------------------------------- /webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { makeConfig } = require('./makeConfig'); 2 | 3 | module.exports = makeConfig(); 4 | --------------------------------------------------------------------------------