From d5a20aadbdfe1d3b8a5b398708a665b9afa71573 Mon Sep 17 00:00:00 2001 From: heidar-stacks <233772527+heidar-stacks@users.noreply.github.com> Date: Tue, 10 Feb 2026 12:58:54 -0500 Subject: [PATCH] fix: sbtc balance on testnet --- .../_components/NewNavBar/MobileNavPage.tsx | 4 +- .../NewNavBar/PagesSlidingMenu.tsx | 13 +- src/app/_components/NewNavBar/consts.ts | 122 ++++++++++-------- .../[principal]/redesign/AddressOverview.tsx | 6 +- src/app/token/[tokenId]/consts.ts | 27 +++- src/app/token/[tokenId]/page-data.ts | 8 +- src/app/token/[tokenId]/page.tsx | 8 +- src/app/tokens/TokenRow/index.tsx | 6 +- src/app/tokens/useTokens.ts | 9 +- src/app/tokens/utils.ts | 7 +- .../redesign/tx-summary/TokensTransferred.tsx | 6 +- .../__tests__/useFungibleTokens.test.tsx | 6 +- .../useFungibleTokens.ts | 6 +- src/common/utils/fungible-token-utils.tsx | 61 ++++++++- src/common/utils/navbar-utils.tsx | 19 +++ src/common/utils/utils.ts | 2 +- 16 files changed, 223 insertions(+), 87 deletions(-) create mode 100644 src/common/utils/navbar-utils.tsx diff --git a/src/app/_components/NewNavBar/MobileNavPage.tsx b/src/app/_components/NewNavBar/MobileNavPage.tsx index ea42d7676..ce2431ef5 100644 --- a/src/app/_components/NewNavBar/MobileNavPage.tsx +++ b/src/app/_components/NewNavBar/MobileNavPage.tsx @@ -1,3 +1,4 @@ +import { usePrimaryPages } from '@/common/utils/navbar-utils'; import { Text } from '@/ui/Text'; import { Box, Flex, Icon, Stack, useDisclosure } from '@chakra-ui/react'; import { CaretLeft, CaretRight, X } from '@phosphor-icons/react'; @@ -8,7 +9,7 @@ import { SharedMobileNavBar } from './NavBar'; import { PrimaryPageLink, SecondaryPageLink } from './PagesLinks'; import { Prices } from './Prices'; import { SettingsPopoverContent } from './SettingsPopover'; -import { primaryPages, secondaryPages } from './consts'; +import { secondaryPages } from './consts'; const topOpacityDuration = 0.3; @@ -19,6 +20,7 @@ const MobileContentTop = ({ isSettingsMenuOpen: boolean; onClose: () => void; }) => { + const primaryPages = usePrimaryPages(); return ( diff --git a/src/app/_components/NewNavBar/PagesSlidingMenu.tsx b/src/app/_components/NewNavBar/PagesSlidingMenu.tsx index e49516c50..2018b66f5 100644 --- a/src/app/_components/NewNavBar/PagesSlidingMenu.tsx +++ b/src/app/_components/NewNavBar/PagesSlidingMenu.tsx @@ -1,3 +1,8 @@ +import { + SBTC_TOKEN_CONTRACT_ID_MAINNET, + SBTC_TOKEN_CONTRACT_ID_TESTNET, +} from '@/app/token/[tokenId]/consts'; +import { usePrimaryPages } from '@/common/utils/navbar-utils'; import { Text } from '@/ui/Text'; import { Flex, Icon, Separator, Stack } from '@chakra-ui/react'; import { CaretUpDown, List } from '@phosphor-icons/react'; @@ -6,12 +11,13 @@ import { useMemo, useState } from 'react'; import { SlidingMenu } from '../../../common/components/SlidingMenu'; import { PrimaryPageLink, SecondaryPageLink } from './PagesLinks'; -import { PrimaryPageLabel, primaryPages, secondaryPages } from './consts'; +import { PrimaryPageLabel, secondaryPages } from './consts'; const getPageLabelFromPath = (path: string): PrimaryPageLabel => { if (path === '/transactions') return 'Transactions'; if (path === '/tokens') return 'Tokens'; - if (path === '/token/SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token') return 'sBTC'; + if (path === `/token/${SBTC_TOKEN_CONTRACT_ID_MAINNET}`) return 'sBTC'; + if (path === `/token/${SBTC_TOKEN_CONTRACT_ID_TESTNET}`) return 'sBTC'; if (path === '/signers') return 'Signers'; if (path === '/blocks') return 'Blocks'; if (path === '/mempool') return 'Mempool'; @@ -29,6 +35,7 @@ export const PagesSlidingMenu = () => { const [isOpen, setIsOpen] = useState(false); const path = usePathname(); const pageLabel = getPageLabelFromPath(path); + const primaryPages = usePrimaryPages(); const menuContent = useMemo(() => { return ( @@ -48,7 +55,7 @@ export const PagesSlidingMenu = () => { ); - }, [pageLabel]); + }, [pageLabel, primaryPages]); return ( (tx) ? tx.block_time : undefined; diff --git a/src/app/tokens/TokenRow/index.tsx b/src/app/tokens/TokenRow/index.tsx index 1c8e79abb..b75ac7676 100644 --- a/src/app/tokens/TokenRow/index.tsx +++ b/src/app/tokens/TokenRow/index.tsx @@ -1,4 +1,5 @@ import { RISKY_TOKENS, VERIFIED_TOKENS } from '@/app/token/[tokenId]/consts'; +import { getHasSBTCInNameOrSymbol, useIsSBTC } from '@/common/utils/fungible-token-utils'; import { Flex, Icon, Table } from '@chakra-ui/react'; import { FtBasicMetadataResponse } from '@hirosystems/token-metadata-api-client'; import { SealCheck, Warning } from '@phosphor-icons/react'; @@ -8,7 +9,6 @@ import { TokenLink, TxLink } from '../../../common/components/ExplorerLinks'; import { abbreviateNumber, getFtDecimalAdjustedBalance } from '../../../common/utils/utils'; import { Text } from '../../../ui/Text'; import { TokenAvatar } from '../../address/[principal]/TokenBalanceCard/TokenAvatar'; -import { isSBTC, referencesSBTC } from '../utils'; export const TokenRow: FC<{ ftToken: FtBasicMetadataResponse; @@ -17,8 +17,8 @@ export const TokenRow: FC<{ const symbol = ftToken.symbol || ''; const contractId = ftToken.contract_principal; - const includesSbtc = referencesSBTC(name, symbol); - const isSbtc = isSBTC(contractId); + const includesSbtc = getHasSBTCInNameOrSymbol(name, ftToken.symbol ?? ''); + const isSbtc = useIsSBTC(ftToken.contract_principal); const tokenBadge = useMemo(() => { if (isSbtc || VERIFIED_TOKENS.includes(contractId)) { diff --git a/src/app/tokens/useTokens.ts b/src/app/tokens/useTokens.ts index 654c7f252..20b2bacf7 100644 --- a/src/app/tokens/useTokens.ts +++ b/src/app/tokens/useTokens.ts @@ -1,5 +1,6 @@ 'use client'; +import { useSbtcTokenContractId } from '@/common/utils/fungible-token-utils'; import { FtBasicMetadataResponse } from '@hirosystems/token-metadata-api-client'; import { useCallback, useMemo } from 'react'; @@ -8,7 +9,7 @@ import { useSuspenseInfiniteQueryResult, } from '../../common/hooks/useInfiniteQueryResult'; import { useFtTokens, useSuspenseFtTokens } from '../../common/queries/useFtTokens'; -import { sbtcContractAddress, usdcxContractAddress } from '../token/[tokenId]/consts'; +import { usdcxContractAddress } from '../token/[tokenId]/consts'; export const useSuspenseTokens = ( debouncedSearchTerm: string @@ -19,6 +20,7 @@ export const useSuspenseTokens = ( loadMore: () => void; } => { const searchByNameResponse = useSuspenseFtTokens({ name: debouncedSearchTerm || undefined }); + const sbtcContractId = useSbtcTokenContractId(); const searchBySymbol = !!debouncedSearchTerm; const searchByAddress = new RegExp('^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41}').test( @@ -35,10 +37,7 @@ export const useSuspenseTokens = ( ); const shouldAddPinnedTokens = useMemo(() => !debouncedSearchTerm, [debouncedSearchTerm]); // Only add pinned tokens if no search term is provided. If they are searched, they will be added by default. If a search term that is not a pinned token is provided, they should not be added. - const sbtcResponse = useFtTokens( - { address: sbtcContractAddress }, - { enabled: shouldAddPinnedTokens } - ); + const sbtcResponse = useFtTokens({ address: sbtcContractId }, { enabled: shouldAddPinnedTokens }); const usdcxResponse = useFtTokens( { address: usdcxContractAddress }, { enabled: shouldAddPinnedTokens } diff --git a/src/app/tokens/utils.ts b/src/app/tokens/utils.ts index 26b3b5f32..dac5f1184 100644 --- a/src/app/tokens/utils.ts +++ b/src/app/tokens/utils.ts @@ -3,7 +3,8 @@ import { FtBasicMetadataResponse } from '@hirosystems/token-metadata-api-client' import { LEGIT_SBTC_DERIVATIVES, RISKY_TOKENS, - sbtcContractAddress, + SBTC_TOKEN_CONTRACT_ID_MAINNET, + SBTC_TOKEN_CONTRACT_ID_TESTNET, } from '../token/[tokenId]/consts'; export const referencesSBTC = ( @@ -20,7 +21,9 @@ export const isSBTC = (contractId: string) => { if (!contractId) { return false; } - return contractId === sbtcContractAddress; + return ( + contractId === SBTC_TOKEN_CONTRACT_ID_MAINNET || contractId === SBTC_TOKEN_CONTRACT_ID_TESTNET + ); }; export function showSBTCTokenAlert(tokenName: string, tokenSymbol: string, contractId: string) { diff --git a/src/app/txid/[txId]/redesign/tx-summary/TokensTransferred.tsx b/src/app/txid/[txId]/redesign/tx-summary/TokensTransferred.tsx index 88653a5b2..15520d229 100644 --- a/src/app/txid/[txId]/redesign/tx-summary/TokensTransferred.tsx +++ b/src/app/txid/[txId]/redesign/tx-summary/TokensTransferred.tsx @@ -1,6 +1,7 @@ -import { SBTC_ASSET_ID, SBTC_DECIMALS } from '@/app/token/[tokenId]/consts'; +import { SBTC_DECIMALS } from '@/app/token/[tokenId]/consts'; import { AddressLink, TokenLink } from '@/common/components/ExplorerLinks'; import { useFtMetadata } from '@/common/queries/useFtMetadata'; +import { useSbtcTokenAssetId } from '@/common/utils/fungible-token-utils'; import { ftDecimals, getAssetNameParts, truncateMiddle } from '@/common/utils/utils'; import { Flex, Text } from '@chakra-ui/react'; import { FC } from 'react'; @@ -20,6 +21,7 @@ interface TokenTransferItemProps { } export const TokenTransferItem: FC = ({ event }) => { + const sbtcAssetId = useSbtcTokenAssetId(); const isStx = event.event_type === 'stx_asset'; const isFt = event.event_type === 'fungible_token_asset'; const isNft = event.event_type === 'non_fungible_token_asset'; @@ -29,7 +31,7 @@ export const TokenTransferItem: FC = ({ event }) => { const ftContractId = ftAssetParts ? `${ftAssetParts.address}.${ftAssetParts.contract}` : undefined; - const isSbtc = ftAssetId === SBTC_ASSET_ID; + const isSbtc = ftAssetId === sbtcAssetId; const { data: ftMetadata } = useFtMetadata(ftContractId, { enabled: isFt && !isSbtc }); diff --git a/src/common/components/table/fungible-tokens-table/__tests__/useFungibleTokens.test.tsx b/src/common/components/table/fungible-tokens-table/__tests__/useFungibleTokens.test.tsx index 93db52187..ad84bcb2f 100644 --- a/src/common/components/table/fungible-tokens-table/__tests__/useFungibleTokens.test.tsx +++ b/src/common/components/table/fungible-tokens-table/__tests__/useFungibleTokens.test.tsx @@ -1,4 +1,4 @@ -import { SBTC_ASSET_ID } from '@/app/token/[tokenId]/consts'; +import { SBTC_TOKEN_ASSET_ID_MAINNET } from '@/app/token/[tokenId]/consts'; import { isRiskyToken } from '@/common/utils/fungible-token-utils'; import { getAssetNameParts } from '@/common/utils/utils'; @@ -350,7 +350,7 @@ describe('putSBTCFirst (if exported)', () => { it.skip('should move SBTC to first position', () => { const mockBalancesWithSBTC = [ { ...mockFtBalance1, asset_identifier: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.token1' }, - { ...mockFtBalance2, asset_identifier: SBTC_ASSET_ID }, + { ...mockFtBalance2, asset_identifier: SBTC_TOKEN_ASSET_ID_MAINNET }, { ...mockFtBalanceZero, asset_identifier: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.token3', @@ -358,7 +358,7 @@ describe('putSBTCFirst (if exported)', () => { ]; const result = putSBTCFirst(mockBalancesWithSBTC); - expect(result[0].asset_identifier).toBe(SBTC_ASSET_ID); + expect(result[0].asset_identifier).toBe(SBTC_TOKEN_ASSET_ID_MAINNET); expect(result).toHaveLength(3); }); diff --git a/src/common/components/table/fungible-tokens-table/useFungibleTokens.ts b/src/common/components/table/fungible-tokens-table/useFungibleTokens.ts index 66ee077ad..4f48e2362 100644 --- a/src/common/components/table/fungible-tokens-table/useFungibleTokens.ts +++ b/src/common/components/table/fungible-tokens-table/useFungibleTokens.ts @@ -1,4 +1,4 @@ -import { SBTC_ASSET_ID } from '@/app/token/[tokenId]/consts'; +import { SBTC_TOKEN_ASSET_ID_MAINNET } from '@/app/token/[tokenId]/consts'; import { THIRTY_SECONDS } from '@/common/queries/query-stale-time'; import { useAccountBalance } from '@/common/queries/useAccountBalance'; import { useFungibleTokensMetadata } from '@/common/queries/useFtMetadata'; @@ -124,9 +124,9 @@ export function filterBalances( } export function putSBTCFirst(balances: FtBalanceWithAssetId[]): FtBalanceWithAssetId[] { - const sbtc = balances.find(balance => balance.asset_identifier === SBTC_ASSET_ID); + const sbtc = balances.find(balance => balance.asset_identifier === SBTC_TOKEN_ASSET_ID_MAINNET); if (sbtc) { - balances = balances.filter(balance => balance.asset_identifier !== SBTC_ASSET_ID); + balances = balances.filter(balance => balance.asset_identifier !== SBTC_TOKEN_ASSET_ID_MAINNET); balances.unshift(sbtc); } return balances; diff --git a/src/common/utils/fungible-token-utils.tsx b/src/common/utils/fungible-token-utils.tsx index 3b87309c4..9b15edb13 100644 --- a/src/common/utils/fungible-token-utils.tsx +++ b/src/common/utils/fungible-token-utils.tsx @@ -1,4 +1,17 @@ -import { RISKY_TOKENS, VERIFIED_TOKENS } from '@/app/token/[tokenId]/consts'; +import { + RISKY_TOKENS, + SBTC_DEPOSIT_CONTRACT_ID_MAINNET, + SBTC_DEPOSIT_CONTRACT_ID_TESTNET, + SBTC_TOKEN_ASSET_ID_MAINNET, + SBTC_TOKEN_ASSET_ID_TESTNET, + SBTC_TOKEN_CONTRACT_ID_MAINNET, + SBTC_TOKEN_CONTRACT_ID_TESTNET, + SBTC_WITHDRAWAL_CONTRACT_ID_MAINNET, + SBTC_WITHDRAWAL_CONTRACT_ID_TESTNET, + VERIFIED_TOKENS, +} from '@/app/token/[tokenId]/consts'; +import { useGlobalContext } from '@/common/context/useGlobalContext'; +import { NetworkModes } from '@/common/types/network'; import { Metadata } from '@hirosystems/token-metadata-api-client'; import { bigintPow } from './number-utils'; @@ -80,3 +93,49 @@ export function formatHoldingPercentage(percentage: number | undefined): string ? '<0.0001%' : `${percentage.toFixed(4)}%`; } + +export const getHasSBTCInNameOrSymbol = (name: string, symbol: string) => { + if (!name || !symbol) { + return false; + } + return name.toLowerCase().includes('sbtc') || symbol.toLowerCase().includes('sbtc'); +}; +export const getIsSBTC = (contractPrincipal: string, network: NetworkModes) => { + if (!contractPrincipal || !network) { + return false; + } + return network === 'mainnet' + ? contractPrincipal === SBTC_TOKEN_CONTRACT_ID_MAINNET + : contractPrincipal === SBTC_TOKEN_CONTRACT_ID_TESTNET; +}; + +export function useIsSBTC(tokenId: string) { + const network = useGlobalContext().activeNetwork; + return getIsSBTC(tokenId, network.mode); +} + +export const useSbtcTokenContractId = () => { + const network = useGlobalContext().activeNetwork; + return network.mode === 'mainnet' + ? SBTC_TOKEN_CONTRACT_ID_MAINNET + : SBTC_TOKEN_CONTRACT_ID_TESTNET; +}; + +export const useSbtcTokenAssetId = () => { + const network = useGlobalContext().activeNetwork; + return network.mode === 'mainnet' ? SBTC_TOKEN_ASSET_ID_MAINNET : SBTC_TOKEN_ASSET_ID_TESTNET; +}; + +export const useSbtcWithdrawalContractId = () => { + const network = useGlobalContext().activeNetwork; + return network.mode === 'mainnet' + ? SBTC_WITHDRAWAL_CONTRACT_ID_MAINNET + : SBTC_WITHDRAWAL_CONTRACT_ID_TESTNET; +}; + +export const useSbtcDepositContractId = () => { + const network = useGlobalContext().activeNetwork; + return network.mode === 'mainnet' + ? SBTC_DEPOSIT_CONTRACT_ID_MAINNET + : SBTC_DEPOSIT_CONTRACT_ID_TESTNET; +}; diff --git a/src/common/utils/navbar-utils.tsx b/src/common/utils/navbar-utils.tsx new file mode 100644 index 000000000..2644f19f5 --- /dev/null +++ b/src/common/utils/navbar-utils.tsx @@ -0,0 +1,19 @@ +import { + PrimaryPage, + blocksPage, + homePage, + mempoolPage, + sbtcMainnetPage, + sbtcTestnetPage, + signersPage, + tokensPage, + transactionsPage, +} from '@/app/_components/NewNavBar/consts'; + +import { useGlobalContext } from '../context/useGlobalContext'; + +export function usePrimaryPages(): PrimaryPage[] { + const network = useGlobalContext().activeNetwork; + const sbtcPage = network.mode === 'mainnet' ? sbtcMainnetPage : sbtcTestnetPage; + return [homePage, blocksPage, transactionsPage, mempoolPage, sbtcPage, signersPage, tokensPage]; +} diff --git a/src/common/utils/utils.ts b/src/common/utils/utils.ts index 378e98efb..9a1e2d9c3 100644 --- a/src/common/utils/utils.ts +++ b/src/common/utils/utils.ts @@ -364,7 +364,7 @@ export const getFtDecimalAdjustedBalance = ( return initBigNumber(value).shiftedBy(-decimals).toNumber(); }; -export const ftDecimals = (value: number | string | BigNumber, decimals: number) => { +export const ftDecimals = (value: number | string | BigNumber, decimals: number): string => { return getFtDecimalAdjustedBalance(value, decimals).toLocaleString('en-US', { maximumFractionDigits: decimals, });