diff --git a/package.json b/package.json
index 047ee00a27d..dfb58cb1641 100644
--- a/package.json
+++ b/package.json
@@ -135,7 +135,7 @@
"@dlc-link/dlc-tools": "1.1.1",
"@fungible-systems/zone-file": "2.0.0",
"@hirosystems/token-metadata-api-client": "1.2.0",
- "@leather-wallet/models": "0.4.5",
+ "@leather-wallet/models": "0.6.1",
"@leather-wallet/tokens": "0.0.15",
"@ledgerhq/hw-transport-webusb": "6.27.19",
"@noble/hashes": "1.3.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6f92a7ee96f..96bc1bd5907 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -30,8 +30,8 @@ dependencies:
specifier: 1.2.0
version: 1.2.0
'@leather-wallet/models':
- specifier: 0.4.5
- version: 0.4.5
+ specifier: 0.6.1
+ version: 0.6.1
'@leather-wallet/tokens':
specifier: 0.0.15
version: 0.0.15
@@ -3702,8 +3702,8 @@ packages:
bignumber.js: 9.1.2
dev: true
- /@leather-wallet/models@0.4.5:
- resolution: {integrity: sha512-H0NhxYy1C2Byrj1wswL9Zf6bWOejvgVuwgVDT6xh7zqvoPotiAtrfEe3hiBdSeUtUsTh70ha1ZMuGwf/rph67w==}
+ /@leather-wallet/models@0.6.1:
+ resolution: {integrity: sha512-PVbsCypJaVY7W6pOqDfFlZuvx8pZhpBo+8mMo2wddDNSnSGoIhxQtZVTzZPHTb4d++UWPuxrDx0WVcu7G2Y9gA==}
dependencies:
bignumber.js: 9.1.2
dev: false
diff --git a/src/app/common/hooks/balance/use-total-balance.tsx b/src/app/common/hooks/balance/use-total-balance.tsx
index 2126e3046a0..60031645bf9 100644
--- a/src/app/common/hooks/balance/use-total-balance.tsx
+++ b/src/app/common/hooks/balance/use-total-balance.tsx
@@ -28,7 +28,7 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs)
// get btc balance
const {
- btcCryptoAssetBalance,
+ balance: btcBalance,
isLoading: isLoadingBtcBalance,
isFetching: isFetchingBtcBalance,
isInitialLoading: isInititalLoadingBtcBalance,
@@ -37,10 +37,7 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs)
return useMemo(() => {
// calculate total balance
const stxUsdAmount = baseCurrencyAmountInQuote(stxBalance, stxMarketData);
- const btcUsdAmount = baseCurrencyAmountInQuote(
- btcCryptoAssetBalance.availableBalance,
- btcMarketData
- );
+ const btcUsdAmount = baseCurrencyAmountInQuote(btcBalance.availableBalance, btcMarketData);
const totalBalance = { ...stxUsdAmount, amount: stxUsdAmount.amount.plus(btcUsdAmount.amount) };
return {
@@ -56,7 +53,7 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs)
}, [
stxBalance,
stxMarketData,
- btcCryptoAssetBalance.availableBalance,
+ btcBalance.availableBalance,
btcMarketData,
isLoading,
isLoadingBtcBalance,
diff --git a/src/app/components/balance/btc-balance.tsx b/src/app/components/balance/btc-balance.tsx
index a35c5af4ec3..f6fad708482 100644
--- a/src/app/components/balance/btc-balance.tsx
+++ b/src/app/components/balance/btc-balance.tsx
@@ -2,15 +2,15 @@ import { formatMoney } from '@app/common/money/format-money';
import { Caption } from '@app/ui/components/typography/caption';
import { BitcoinNativeSegwitAccountLoader } from '../loaders/bitcoin-account-loader';
-import { BtcCryptoAssetLoader } from '../loaders/btc-crypto-asset-loader';
+import { BtcBalanceLoader } from '../loaders/btc-balance-loader';
export function BtcBalance() {
return (
{signer => (
-
- {asset => {formatMoney(asset.balance.availableBalance)}}
-
+
+ {balance => {formatMoney(balance.availableBalance)}}
+
)}
);
diff --git a/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx b/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx
index 8555cf70fa7..3bcf528f288 100644
--- a/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx
+++ b/src/app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee.tsx
@@ -10,7 +10,7 @@ import {
determineUtxosForSpendAll,
} from '@app/common/transactions/bitcoin/coinselect/local-coin-selection';
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
-import { useCurrentBtcAvailableBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
+import { useCurrentBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks';
export const MAX_FEE_RATE_MULTIPLIER = 50;
@@ -22,7 +22,7 @@ interface UseBitcoinCustomFeeArgs {
}
export function useBitcoinCustomFee({ amount, isSendingMax, recipients }: UseBitcoinCustomFeeArgs) {
- const { balance } = useCurrentBtcAvailableBalanceNativeSegwit();
+ const { balance } = useCurrentBtcCryptoAssetBalanceNativeSegwit();
const { data: utxos = [] } = useCurrentNativeSegwitUtxos();
const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC');
@@ -30,7 +30,9 @@ export function useBitcoinCustomFee({ amount, isSendingMax, recipients }: UseBit
(feeRate: number) => {
if (!feeRate || !utxos.length) return { fee: 0, fiatFeeValue: '' };
- const satAmount = isSendingMax ? balance.amount.toNumber() : amount.amount.toNumber();
+ const satAmount = isSendingMax
+ ? balance.availableBalance.amount.toNumber()
+ : amount.amount.toNumber();
const determineUtxosArgs = {
amount: satAmount,
@@ -49,6 +51,6 @@ export function useBitcoinCustomFee({ amount, isSendingMax, recipients }: UseBit
)}`,
};
},
- [utxos, isSendingMax, balance.amount, amount.amount, recipients, btcMarketData]
+ [utxos, isSendingMax, balance.availableBalance.amount, amount.amount, recipients, btcMarketData]
);
}
diff --git a/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list.ts b/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list.ts
index 672fb10ee7f..3cd9b45741e 100644
--- a/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list.ts
+++ b/src/app/components/bitcoin-fees-list/use-bitcoin-fees-list.ts
@@ -10,7 +10,7 @@ import {
determineUtxosForSpend,
determineUtxosForSpendAll,
} from '@app/common/transactions/bitcoin/coinselect/local-coin-selection';
-import { useCurrentBtcAvailableBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
+import { useCurrentBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
import { UtxoResponseItem } from '@app/query/bitcoin/bitcoin-client';
import { useAverageBitcoinFeeRates } from '@app/query/bitcoin/fees/fee-estimates.hooks';
import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks';
@@ -43,7 +43,7 @@ export function useBitcoinFeesList({
recipient,
utxos,
}: UseBitcoinFeesListArgs) {
- const { balance } = useCurrentBtcAvailableBalanceNativeSegwit();
+ const { balance } = useCurrentBtcCryptoAssetBalanceNativeSegwit();
const btcMarketData = useCryptoCurrencyMarketDataMeanAverage('BTC');
const { data: feeRates, isLoading } = useAverageBitcoinFeeRates();
@@ -57,7 +57,9 @@ export function useBitcoinFeesList({
if (!feeRates || !utxos.length) return [];
const determineUtxosDefaultArgs = {
- recipients: [{ address: recipient, amount: isSendingMax ? balance : amount }],
+ recipients: [
+ { address: recipient, amount: isSendingMax ? balance.availableBalance : amount },
+ ],
utxos,
};
@@ -116,7 +118,7 @@ export function useBitcoinFeesList({
}
return feesArr;
- }, [feeRates, utxos, isSendingMax, balance, amount, recipient, btcMarketData]);
+ }, [feeRates, utxos, recipient, isSendingMax, balance.availableBalance, amount, btcMarketData]);
return {
feesList,
diff --git a/src/app/components/crypto-asset-item/crypto-asset-item.layout.tsx b/src/app/components/crypto-asset-item/crypto-asset-item.layout.tsx
index efe8e0bd245..a422d15b714 100644
--- a/src/app/components/crypto-asset-item/crypto-asset-item.layout.tsx
+++ b/src/app/components/crypto-asset-item/crypto-asset-item.layout.tsx
@@ -1,9 +1,7 @@
-import { ReactNode } from 'react';
-
+import type { CryptoAssetBalance } from '@leather-wallet/models';
import { Box, Flex, styled } from 'leather-styles/jsx';
import { spamFilter } from '@app/common/utils/spam-filter';
-import type { AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model';
import { BulletSeparator } from '@app/ui/components/bullet-separator/bullet-separator';
import { ItemLayout } from '@app/ui/components/item-layout/item-layout';
import { SkeletonLoader } from '@app/ui/components/skeleton-loader/skeleton-loader';
@@ -14,73 +12,66 @@ import { Pressable } from '@app/ui/pressable/pressable';
import { parseCryptoAssetBalance } from './crypto-asset-item.layout.utils';
interface CryptoAssetItemLayoutProps {
- additionalBalanceInfo?: ReactNode;
- additionalBalanceInfoAsFiat?: ReactNode;
- asset: AccountCryptoAssetWithDetails;
- caption?: string;
+ balance: CryptoAssetBalance;
+ captionLeft: string;
+ captionRightBulletInfo?: React.ReactNode;
+ contractId?: string;
fiatBalance?: string;
icon: React.ReactNode;
isLoading?: boolean;
- name: string;
- onClick?(asset: AccountCryptoAssetWithDetails): void;
- rightElement?: React.ReactNode;
+ onSelectAsset?(symbol: string, contractId?: string): void;
+ titleLeft: string;
+ titleRightBulletInfo?: React.ReactNode;
}
export function CryptoAssetItemLayout({
- additionalBalanceInfo,
- additionalBalanceInfoAsFiat,
- asset,
- caption,
+ balance,
+ captionLeft,
+ captionRightBulletInfo,
+ contractId,
fiatBalance,
icon,
isLoading = false,
- name,
- onClick,
- rightElement,
+ onSelectAsset,
+ titleLeft,
+ titleRightBulletInfo,
}: CryptoAssetItemLayoutProps) {
- const { dataTestId, formattedBalance } = parseCryptoAssetBalance(asset.balance);
- const { availableBalance } = asset.balance;
- const title = spamFilter(name);
+ const { availableBalance, dataTestId, formattedBalance } = parseCryptoAssetBalance(balance);
const titleRight = (
- {rightElement ? (
- rightElement
- ) : (
-
-
- {formattedBalance.value} {additionalBalanceInfo}
-
-
- )}
+
+
+
+ {formattedBalance.value}
+ {titleRightBulletInfo}
+
+
+
);
const captionRight = (
- {!rightElement && (
-
-
-
- {availableBalance.amount.toNumber() > 0 ? fiatBalance : null}
- {additionalBalanceInfoAsFiat}
-
-
-
- )}
+
+
+ {availableBalance.amount.toNumber() > 0 ? fiatBalance : null}
+ {captionRightBulletInfo}
+
+
);
- const isInteractive = !!onClick;
+ const isInteractive = !!onSelectAsset;
const content = (
@@ -88,7 +79,11 @@ export function CryptoAssetItemLayout({
if (isInteractive)
return (
- onClick(asset)} my="space.02">
+ onSelectAsset(availableBalance.symbol, contractId)}
+ my="space.02"
+ >
{content}
);
diff --git a/src/app/components/crypto-asset-item/crypto-asset-item.layout.utils.ts b/src/app/components/crypto-asset-item/crypto-asset-item.layout.utils.ts
index 6cc1c622b67..2b5ef2cd0c2 100644
--- a/src/app/components/crypto-asset-item/crypto-asset-item.layout.utils.ts
+++ b/src/app/components/crypto-asset-item/crypto-asset-item.layout.utils.ts
@@ -2,12 +2,12 @@ import type { CryptoAssetBalance } from '@leather-wallet/models';
import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors';
import { formatBalance } from '@app/common/format-balance';
-import { ftDecimals } from '@app/common/stacks-utils';
+import { formatMoneyWithoutSymbol } from '@app/common/money/format-money';
export function parseCryptoAssetBalance(balance: CryptoAssetBalance) {
const { availableBalance } = balance;
- const amount = ftDecimals(availableBalance.amount, availableBalance.decimals);
+ const amount = formatMoneyWithoutSymbol(availableBalance);
const dataTestId = CryptoAssetSelectors.CryptoAssetListItem.replace(
'{symbol}',
availableBalance.symbol.toLowerCase()
@@ -15,6 +15,7 @@ export function parseCryptoAssetBalance(balance: CryptoAssetBalance) {
const formattedBalance = formatBalance(amount);
return {
+ availableBalance,
dataTestId,
formattedBalance,
};
diff --git a/src/app/components/loaders/brc20-tokens-loader.tsx b/src/app/components/loaders/brc20-tokens-loader.tsx
index b7f152d2e61..405740da048 100644
--- a/src/app/components/loaders/brc20-tokens-loader.tsx
+++ b/src/app/components/loaders/brc20-tokens-loader.tsx
@@ -1,10 +1,18 @@
-import { useBrc20AccountCryptoAssetsWithDetails } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks';
-import type { Brc20AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model';
+import type { Brc20CryptoAssetInfo, CryptoAssetBalance, MarketData } from '@leather-wallet/models';
+
+import { useBrc20Tokens } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks';
interface Brc20TokensLoaderProps {
- children(tokens: Brc20AccountCryptoAssetWithDetails[]): React.ReactNode;
+ children(
+ tokens: {
+ balance: CryptoAssetBalance;
+ info: Brc20CryptoAssetInfo;
+ holderAddress: string;
+ marketData: MarketData;
+ }[]
+ ): React.ReactNode;
}
export function Brc20TokensLoader({ children }: Brc20TokensLoaderProps) {
- const tokens = useBrc20AccountCryptoAssetsWithDetails();
+ const tokens = useBrc20Tokens();
return children(tokens);
}
diff --git a/src/app/components/loaders/btc-balance-loader.tsx b/src/app/components/loaders/btc-balance-loader.tsx
new file mode 100644
index 00000000000..a5b3a4e86a8
--- /dev/null
+++ b/src/app/components/loaders/btc-balance-loader.tsx
@@ -0,0 +1,12 @@
+import type { BtcCryptoAssetBalance } from '@leather-wallet/models';
+
+import { useBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
+
+interface BtcBalanceLoaderProps {
+ address: string;
+ children(balance: BtcCryptoAssetBalance, isInitialLoading: boolean): React.ReactNode;
+}
+export function BtcBalanceLoader({ address, children }: BtcBalanceLoaderProps) {
+ const { balance, isInitialLoading } = useBtcCryptoAssetBalanceNativeSegwit(address);
+ return children(balance, isInitialLoading);
+}
diff --git a/src/app/components/loaders/btc-crypto-asset-loader.tsx b/src/app/components/loaders/btc-crypto-asset-loader.tsx
deleted file mode 100644
index 3ed06ddb8bf..00000000000
--- a/src/app/components/loaders/btc-crypto-asset-loader.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import { useBtcAccountCryptoAssetWithDetails } from '@app/query/bitcoin/btc/btc-crypto-asset.hooks';
-import type { BtcAccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model';
-
-interface BtcCryptoAssetLoaderProps {
- address: string;
- children(asset: BtcAccountCryptoAssetWithDetails, isInitialLoading: boolean): React.ReactNode;
-}
-export function BtcCryptoAssetLoader({ address, children }: BtcCryptoAssetLoaderProps) {
- const { asset, isInitialLoading } = useBtcAccountCryptoAssetWithDetails(address);
- return children(asset, isInitialLoading);
-}
diff --git a/src/app/components/loaders/runes-loader.tsx b/src/app/components/loaders/runes-loader.tsx
index ee78d0287e8..9b4eb8ae036 100644
--- a/src/app/components/loaders/runes-loader.tsx
+++ b/src/app/components/loaders/runes-loader.tsx
@@ -1,11 +1,12 @@
-import type { RuneToken } from '@app/query/bitcoin/bitcoin-client';
+import type { CryptoAssetBalance, RuneCryptoAssetInfo } from '@leather-wallet/models';
+
import { useRuneTokens } from '@app/query/bitcoin/runes/runes.hooks';
interface RunesLoaderProps {
addresses: string[];
- children(runes: RuneToken[]): React.ReactNode;
+ children(runes: { balance: CryptoAssetBalance; info: RuneCryptoAssetInfo }[]): React.ReactNode;
}
export function RunesLoader({ addresses, children }: RunesLoaderProps) {
- const runes = useRuneTokens(addresses);
+ const { runes = [] } = useRuneTokens(addresses);
return children(runes);
}
diff --git a/src/app/components/loaders/sip10-tokens-loader.tsx b/src/app/components/loaders/sip10-tokens-loader.tsx
new file mode 100644
index 00000000000..e547e4b751c
--- /dev/null
+++ b/src/app/components/loaders/sip10-tokens-loader.tsx
@@ -0,0 +1,15 @@
+import {
+ type Sip10TokenAssetDetails,
+ useFilteredSip10Tokens,
+} from '@app/query/stacks/sip10/sip10-tokens.hooks';
+import type { Sip10CryptoAssetFilter } from '@app/query/stacks/sip10/sip10-tokens.utils';
+
+interface Sip10TokensLoaderProps {
+ address: string;
+ filter: Sip10CryptoAssetFilter;
+ children(isLoading: boolean, tokens: Sip10TokenAssetDetails[]): React.ReactNode;
+}
+export function Sip10TokensLoader({ address, filter, children }: Sip10TokensLoaderProps) {
+ const { isInitialLoading, tokens = [] } = useFilteredSip10Tokens({ address, filter });
+ return children(isInitialLoading, tokens);
+}
diff --git a/src/app/components/loaders/src20-tokens-loader.tsx b/src/app/components/loaders/src20-tokens-loader.tsx
index 1ce829f6beb..47f568f0089 100644
--- a/src/app/components/loaders/src20-tokens-loader.tsx
+++ b/src/app/components/loaders/src20-tokens-loader.tsx
@@ -1,9 +1,15 @@
+import type { CryptoAssetBalance, Src20CryptoAssetInfo } from '@leather-wallet/models';
+
import { useSrc20TokensByAddress } from '@app/query/bitcoin/stamps/stamps-by-address.hooks';
-import type { Src20Token } from '@app/query/bitcoin/stamps/stamps-by-address.query';
+
+export interface Src20TokenAssetDetails {
+ balance: CryptoAssetBalance;
+ info: Src20CryptoAssetInfo;
+}
interface Src20TokensLoaderProps {
address: string;
- children(tokens: Src20Token[]): React.ReactNode;
+ children(tokens: Src20TokenAssetDetails[]): React.ReactNode;
}
export function Src20TokensLoader({ address, children }: Src20TokensLoaderProps) {
const { data: tokens = [] } = useSrc20TokensByAddress(address);
diff --git a/src/app/components/loaders/stx-balance-loader.tsx b/src/app/components/loaders/stx-balance-loader.tsx
new file mode 100644
index 00000000000..a132572c385
--- /dev/null
+++ b/src/app/components/loaders/stx-balance-loader.tsx
@@ -0,0 +1,15 @@
+import type { StxCryptoAssetBalance } from '@leather-wallet/models';
+
+import { isFetchedWithSuccess } from '@app/query/query-config';
+import { useStxCryptoAssetBalance } from '@app/query/stacks/balance/account-balance.hooks';
+
+interface StxBalanceLoaderProps {
+ address: string;
+ children(balance: StxCryptoAssetBalance, isInitialLoading: boolean): React.ReactNode;
+}
+export function StxBalanceLoader({ address, children }: StxBalanceLoaderProps) {
+ const result = useStxCryptoAssetBalance(address);
+ if (!isFetchedWithSuccess(result)) return null;
+ const { data: balance, isInitialLoading } = result;
+ return children(balance, isInitialLoading);
+}
diff --git a/src/app/components/loaders/stx-crypto-asset-loader.tsx b/src/app/components/loaders/stx-crypto-asset-loader.tsx
deleted file mode 100644
index 72824eb4770..00000000000
--- a/src/app/components/loaders/stx-crypto-asset-loader.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import type { StxAccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model';
-import { useStxAccountCryptoAssetWithDetails } from '@app/query/stacks/stx/stx-crypto-asset.hooks';
-
-interface StxCryptoAssetLoaderProps {
- address: string;
- children(asset: StxAccountCryptoAssetWithDetails, isInitialLoading: boolean): React.ReactNode;
-}
-export function StxCryptoAssetLoader({ address, children }: StxCryptoAssetLoaderProps) {
- const { asset, isInitialLoading } = useStxAccountCryptoAssetWithDetails(address);
- return children(asset, isInitialLoading);
-}
diff --git a/src/app/components/loaders/stx20-tokens-loader.tsx b/src/app/components/loaders/stx20-tokens-loader.tsx
index 986d214f6e6..796f649320d 100644
--- a/src/app/components/loaders/stx20-tokens-loader.tsx
+++ b/src/app/components/loaders/stx20-tokens-loader.tsx
@@ -1,9 +1,15 @@
-import type { Stx20Token } from '@app/query/stacks/stacks-client';
+import type { CryptoAssetBalance, Stx20CryptoAssetInfo } from '@leather-wallet/models';
+
import { useStx20Tokens } from '@app/query/stacks/stx20/stx20-tokens.hooks';
interface Stx20TokensLoaderProps {
address: string;
- children(tokens: Stx20Token[]): React.ReactNode;
+ children(
+ tokens: {
+ balance: CryptoAssetBalance;
+ info: Stx20CryptoAssetInfo;
+ }[]
+ ): React.ReactNode;
}
export function Stx20TokensLoader({ address, children }: Stx20TokensLoaderProps) {
const { data: tokens = [] } = useStx20Tokens(address);
diff --git a/src/app/features/asset-list/components/connect-ledger-asset-button.tsx b/src/app/features/asset-list/_components/connect-ledger-asset-button.tsx
similarity index 99%
rename from src/app/features/asset-list/components/connect-ledger-asset-button.tsx
rename to src/app/features/asset-list/_components/connect-ledger-asset-button.tsx
index 9e596ba057a..81a4dc28b97 100644
--- a/src/app/features/asset-list/components/connect-ledger-asset-button.tsx
+++ b/src/app/features/asset-list/_components/connect-ledger-asset-button.tsx
@@ -25,6 +25,7 @@ export function ConnectLedgerButton({ chain }: ConnectLedgerButtonProps) {
},
});
};
+
return (
+ );
+}
diff --git a/src/app/features/asset-list/asset-list.tsx b/src/app/features/asset-list/asset-list.tsx
index 6a1a32c4699..ff8c8b188e5 100644
--- a/src/app/features/asset-list/asset-list.tsx
+++ b/src/app/features/asset-list/asset-list.tsx
@@ -7,33 +7,34 @@ import {
BitcoinTaprootAccountLoader,
} from '@app/components/loaders/bitcoin-account-loader';
import { Brc20TokensLoader } from '@app/components/loaders/brc20-tokens-loader';
-import { BtcCryptoAssetLoader } from '@app/components/loaders/btc-crypto-asset-loader';
+import { BtcBalanceLoader } from '@app/components/loaders/btc-balance-loader';
import { RunesLoader } from '@app/components/loaders/runes-loader';
+import { Sip10TokensLoader } from '@app/components/loaders/sip10-tokens-loader';
import { Src20TokensLoader } from '@app/components/loaders/src20-tokens-loader';
import { CurrentStacksAccountLoader } from '@app/components/loaders/stacks-account-loader';
import { Stx20TokensLoader } from '@app/components/loaders/stx20-tokens-loader';
-import { StxCryptoAssetLoader } from '@app/components/loaders/stx-crypto-asset-loader';
+import { StxBalanceLoader } from '@app/components/loaders/stx-balance-loader';
import { Brc20TokenAssetList } from '@app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list';
import { RunesAssetList } from '@app/features/asset-list/bitcoin/runes-asset-list/runes-asset-list';
import { Src20TokenAssetList } from '@app/features/asset-list/bitcoin/src20-token-asset-list/src20-token-asset-list';
import { Stx20TokenAssetList } from '@app/features/asset-list/stacks/stx20-token-asset-list/stx20-token-asset-list';
import { StxCryptoAssetItem } from '@app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item';
-import { StxCryptoAssetItemFallback } from '@app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item-fallback';
-import type { AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
+import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon';
+import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon';
+import { ConnectLedgerAssetItemFallback } from './_components/connect-ledger-asset-item-fallback';
import { BtcCryptoAssetItem } from './bitcoin/btc-crypto-asset-item/btc-crypto-asset-item';
-import { BtcCryptoAssetItemFallback } from './bitcoin/btc-crypto-asset-item/btc-crypto-asset-item-fallback';
import { Sip10TokenAssetList } from './stacks/sip10-token-asset-list/sip10-token-asset-list';
import { Sip10TokenAssetListUnsupported } from './stacks/sip10-token-asset-list/sip10-token-asset-list-unsupported';
export type AssetListVariant = 'interactive' | 'read-only';
interface AssetListProps {
- onClick?(asset: AccountCryptoAssetWithDetails): void;
+ onSelectAsset?(symbol: string, contractId?: string): void;
variant?: AssetListVariant;
}
-export function AssetList({ onClick, variant = 'read-only' }: AssetListProps) {
+export function AssetList({ onSelectAsset, variant = 'read-only' }: AssetListProps) {
const network = useCurrentNetwork();
const { whenWallet } = useWalletType();
@@ -41,41 +42,29 @@ export function AssetList({ onClick, variant = 'read-only' }: AssetListProps) {
return (
- {whenWallet({
- software: (
-
- {nativeSegwitAccount => (
-
- {(asset, isInitialLoading) => (
-
- )}
-
- )}
-
- ),
- ledger: (
- }
- >
- {nativeSegwitAccount => (
-
- {(asset, isInitialLoading) => (
-
- )}
-
+ }
+ symbol="BTC"
+ variant={variant}
+ />
+ }
+ >
+ {nativeSegwitAccount => (
+
+ {(balance, isInitialLoading) => (
+
)}
-
- ),
- })}
+
+ )}
+
{/* Temporary duplication during Ledger Bitcoin feature dev */}
{isReadOnly &&
@@ -85,15 +74,39 @@ export function AssetList({ onClick, variant = 'read-only' }: AssetListProps) {
ledger: null,
})}
- }>
+ }
+ symbol="STX"
+ variant={variant}
+ />
+ }
+ >
{account => (
<>
-
- {(asset, isInitialLoading) => (
-
+
+ {(balance, isInitialLoading) => (
+
)}
-
-
+
+
+ {(isInitialLoading, tokens) => (
+
+ )}
+
{isReadOnly && (
{tokens => }
@@ -111,7 +124,7 @@ export function AssetList({ onClick, variant = 'read-only' }: AssetListProps) {
{whenWallet({
software: (
- {tokens => }
+ {tokens => }
),
ledger: null,
@@ -134,7 +147,13 @@ export function AssetList({ onClick, variant = 'read-only' }: AssetListProps) {
{isReadOnly && (
- {account => }
+ {account => (
+
+ {(isInitialLoading, tokens) => (
+
+ )}
+
+ )}
)}
diff --git a/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx b/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx
index 875048d35af..56aaf127505 100644
--- a/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx
+++ b/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx
@@ -1,45 +1,59 @@
import { useNavigate } from 'react-router-dom';
+import type { Brc20CryptoAssetInfo, CryptoAssetBalance, MarketData } from '@leather-wallet/models';
import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors';
import { Stack } from 'leather-styles/jsx';
import { RouteUrls } from '@shared/route-urls';
-import { capitalize } from '@app/common/utils';
import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout';
import type { AssetListVariant } from '@app/features/asset-list/asset-list';
-import { useCurrentBtcAvailableBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
-import type { Brc20AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model';
+import { useCurrentBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
import { Brc20AvatarIcon } from '@app/ui/components/avatar/brc20-avatar-icon';
+interface Brc20TokenAssetDetails {
+ balance: CryptoAssetBalance;
+ holderAddress: string;
+ info: Brc20CryptoAssetInfo;
+ marketData: MarketData;
+}
+
interface Brc20TokenAssetListProps {
- assets: Brc20AccountCryptoAssetWithDetails[];
+ tokens: Brc20TokenAssetDetails[];
variant?: AssetListVariant;
}
-export function Brc20TokenAssetList({ assets, variant }: Brc20TokenAssetListProps) {
+export function Brc20TokenAssetList({ tokens, variant }: Brc20TokenAssetListProps) {
const navigate = useNavigate();
- const { balance, isInitialLoading } = useCurrentBtcAvailableBalanceNativeSegwit();
+ const { balance, isInitialLoading } = useCurrentBtcCryptoAssetBalanceNativeSegwit();
- const hasPositiveBtcBalanceForFees = variant === 'interactive' && balance.amount.isGreaterThan(0);
+ const hasPositiveBtcBalanceForFees =
+ variant === 'interactive' && balance.availableBalance.amount.isGreaterThan(0);
- function navigateToBrc20SendForm(asset: Brc20AccountCryptoAssetWithDetails) {
- const { balance, holderAddress, info, marketData } = asset;
+ function navigateToBrc20SendForm(token: Brc20TokenAssetDetails) {
+ const { balance, holderAddress, info, marketData } = token;
navigate(RouteUrls.SendBrc20SendForm.replace(':ticker', info.symbol), {
- state: { balance: balance.availableBalance, holderAddress, marketData, ticker: info.symbol },
+ state: {
+ balance: balance.availableBalance,
+ holderAddress,
+ marketData,
+ ticker: info.symbol,
+ },
});
}
return (
- {assets.map(asset => (
+ {tokens.map(token => (
}
isLoading={isInitialLoading}
- key={asset.info.symbol}
- name={asset.info.symbol}
- onClick={hasPositiveBtcBalanceForFees ? () => navigateToBrc20SendForm(asset) : undefined}
+ key={token.info.symbol}
+ onSelectAsset={
+ hasPositiveBtcBalanceForFees ? () => navigateToBrc20SendForm(token) : undefined
+ }
+ titleLeft={token.info.symbol}
/>
))}
diff --git a/src/app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item-fallback.tsx b/src/app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item-fallback.tsx
deleted file mode 100644
index f7e7c71e797..00000000000
--- a/src/app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item-fallback.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout';
-import { btcCryptoAssetPlaceholder } from '@app/query/bitcoin/btc/btc-crypto-asset.hooks';
-import { useCheckLedgerBlockchainAvailable } from '@app/store/accounts/blockchain/utils';
-import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon';
-
-import type { AssetListVariant } from '../../asset-list';
-import { ConnectLedgerButton } from '../../components/connect-ledger-asset-button';
-
-interface StxCryptoAssetItemFallbackProps {
- variant: AssetListVariant;
-}
-export function BtcCryptoAssetItemFallback({ variant }: StxCryptoAssetItemFallbackProps) {
- const checkBlockchainAvailable = useCheckLedgerBlockchainAvailable();
- if (variant === 'interactive' && !checkBlockchainAvailable('bitcoin')) return null;
- return (
- }
- name={btcCryptoAssetPlaceholder.info.name}
- rightElement={}
- />
- );
-}
diff --git a/src/app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item.tsx b/src/app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item.tsx
index 1128f53945e..c3b2e25dc60 100644
--- a/src/app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item.tsx
+++ b/src/app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item.tsx
@@ -1,40 +1,31 @@
+import type { BtcCryptoAssetBalance } from '@leather-wallet/models';
+
import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money';
import { i18nFormatCurrency } from '@app/common/money/format-money';
-import { capitalize } from '@app/common/utils';
import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout';
import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks';
-import type {
- AccountCryptoAssetWithDetails,
- BtcAccountCryptoAssetWithDetails,
-} from '@app/query/models/crypto-asset.model';
import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon';
interface BtcCryptoAssetItemProps {
- asset: BtcAccountCryptoAssetWithDetails;
+ balance: BtcCryptoAssetBalance;
isLoading: boolean;
- onClick?(asset: AccountCryptoAssetWithDetails): void;
- rightElement?: React.ReactNode;
+ onSelectAsset?(symbol: string): void;
}
-export function BtcCryptoAssetItem({
- asset,
- isLoading,
- onClick,
- rightElement,
-}: BtcCryptoAssetItemProps) {
+export function BtcCryptoAssetItem({ balance, isLoading, onSelectAsset }: BtcCryptoAssetItemProps) {
const marketData = useCryptoCurrencyMarketDataMeanAverage('BTC');
- const availableBalanceAsFiat = i18nFormatCurrency(
- baseCurrencyAmountInQuote(asset.balance.availableBalance, marketData)
+ const fiatAvailableBalance = i18nFormatCurrency(
+ baseCurrencyAmountInQuote(balance.availableBalance, marketData)
);
return (
}
isLoading={isLoading}
- name={capitalize(asset.info.name)}
- onClick={onClick}
- rightElement={rightElement}
+ onSelectAsset={onSelectAsset}
+ titleLeft="Bitcoin"
/>
);
}
diff --git a/src/app/features/asset-list/bitcoin/runes-asset-list/runes-asset-item.layout.tsx b/src/app/features/asset-list/bitcoin/runes-asset-list/runes-asset-item.layout.tsx
deleted file mode 100644
index 09780d9adf4..00000000000
--- a/src/app/features/asset-list/bitcoin/runes-asset-list/runes-asset-item.layout.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { styled } from 'leather-styles/jsx';
-
-import { formatBalance } from '@app/common/format-balance';
-import { convertAmountToBaseUnit } from '@app/common/money/calculate-money';
-import type { RuneToken } from '@app/query/bitcoin/bitcoin-client';
-import { RunesAvatarIcon } from '@app/ui/components/avatar/runes-avatar-icon';
-import { ItemLayout } from '@app/ui/components/item-layout/item-layout';
-import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip';
-import { Pressable } from '@app/ui/pressable/pressable';
-
-interface RunesAssetItemLayoutProps {
- rune: RuneToken;
-}
-export function RunesAssetItemLayout({ rune }: RunesAssetItemLayoutProps) {
- const { balance, tokenData } = rune;
- const balanceAsString = convertAmountToBaseUnit(balance).toString();
- const formattedBalance = formatBalance(balanceAsString);
-
- return (
-
- }
- titleLeft={tokenData.spaced_rune_name ?? tokenData.rune_name}
- captionLeft="Runes"
- titleRight={
-
-
- {formattedBalance.value} {tokenData.symbol}
-
-
- }
- />
-
- );
-}
diff --git a/src/app/features/asset-list/bitcoin/runes-asset-list/runes-asset-list.tsx b/src/app/features/asset-list/bitcoin/runes-asset-list/runes-asset-list.tsx
index d89fb52692d..f585ff622cf 100644
--- a/src/app/features/asset-list/bitcoin/runes-asset-list/runes-asset-list.tsx
+++ b/src/app/features/asset-list/bitcoin/runes-asset-list/runes-asset-list.tsx
@@ -1,12 +1,24 @@
-import type { RuneToken } from '@app/query/bitcoin/bitcoin-client';
+import type { CryptoAssetBalance, RuneCryptoAssetInfo } from '@leather-wallet/models';
-import { RunesAssetItemLayout } from './runes-asset-item.layout';
+import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout';
+import { RunesAvatarIcon } from '@app/ui/components/avatar/runes-avatar-icon';
+
+interface RuneTokenAssetDetails {
+ balance: CryptoAssetBalance;
+ info: RuneCryptoAssetInfo;
+}
interface RunesAssetListProps {
- runes: RuneToken[];
+ runes: RuneTokenAssetDetails[];
}
export function RunesAssetList({ runes }: RunesAssetListProps) {
return runes.map((rune, i) => (
-
+ }
+ key={`${rune.info.symbol}${i}`}
+ titleLeft={rune.info.spacedRuneName ?? rune.info.runeName}
+ />
));
}
diff --git a/src/app/features/asset-list/bitcoin/src20-token-asset-list/src20-token-asset-item.layout.tsx b/src/app/features/asset-list/bitcoin/src20-token-asset-list/src20-token-asset-item.layout.tsx
deleted file mode 100644
index e2e669f1e92..00000000000
--- a/src/app/features/asset-list/bitcoin/src20-token-asset-list/src20-token-asset-item.layout.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { styled } from 'leather-styles/jsx';
-
-import { createMoney } from '@shared/models/money.model';
-
-import { formatBalance } from '@app/common/format-balance';
-import type { Src20Token } from '@app/query/bitcoin/stamps/stamps-by-address.query';
-import { Src20AvatarIcon } from '@app/ui/components/avatar/src20-avatar-icon';
-import { ItemLayout } from '@app/ui/components/item-layout/item-layout';
-import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip';
-import { Pressable } from '@app/ui/pressable/pressable';
-
-interface Src20TokenAssetItemLayoutProps {
- token: Src20Token;
-}
-export function Src20TokenAssetItemLayout({ token }: Src20TokenAssetItemLayoutProps) {
- const balance = createMoney(Number(token.amt), token.tick, 0).amount.toString();
- const formattedBalance = formatBalance(balance);
-
- return (
-
- }
- titleLeft={token.tick.toUpperCase()}
- captionLeft="SRC-20"
- titleRight={
-
-
- {formattedBalance.value}
-
-
- }
- />
-
- );
-}
diff --git a/src/app/features/asset-list/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx b/src/app/features/asset-list/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx
index 706537735a8..fcdac2a8c5e 100644
--- a/src/app/features/asset-list/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx
+++ b/src/app/features/asset-list/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx
@@ -1,12 +1,18 @@
-import type { Src20Token } from '@app/query/bitcoin/stamps/stamps-by-address.query';
-
-import { Src20TokenAssetItemLayout } from './src20-token-asset-item.layout';
+import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout';
+import type { Src20TokenAssetDetails } from '@app/components/loaders/src20-tokens-loader';
+import { Src20AvatarIcon } from '@app/ui/components/avatar/src20-avatar-icon';
interface Src20TokenAssetListProps {
- tokens: Src20Token[];
+ tokens: Src20TokenAssetDetails[];
}
export function Src20TokenAssetList({ tokens }: Src20TokenAssetListProps) {
return tokens.map((token, i) => (
-
+ }
+ titleLeft={token.info.symbol.toUpperCase()}
+ />
));
}
diff --git a/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-item.tsx b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-item.tsx
index e7b421e56ac..01713734ad6 100644
--- a/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-item.tsx
+++ b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-item.tsx
@@ -1,36 +1,49 @@
+import type { CryptoAssetBalance, MarketData, Sip10CryptoAssetInfo } from '@leather-wallet/models';
+
+import { convertAssetBalanceToFiat } from '@app/common/asset-utils';
+import { getSafeImageCanonicalUri } from '@app/common/stacks-utils';
+import { spamFilter } from '@app/common/utils/spam-filter';
import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout';
import { StacksAssetAvatar } from '@app/components/stacks-asset-avatar';
-import type {
- AccountCryptoAssetWithDetails,
- Sip10AccountCryptoAssetWithDetails,
-} from '@app/query/models/crypto-asset.model';
-
-import { parseSip10TokenCryptoAssetBalance } from './sip10-token-asset-item.utils';
interface Sip10TokenAssetItemProps {
- asset: Sip10AccountCryptoAssetWithDetails;
- onClick?(asset: AccountCryptoAssetWithDetails): void;
+ balance: CryptoAssetBalance;
+ info: Sip10CryptoAssetInfo;
+ isLoading: boolean;
+ marketData: MarketData;
+ onSelectAsset?(symbol: string, contractId?: string): void;
}
-export function Sip10TokenAssetItem({ asset, onClick }: Sip10TokenAssetItemProps) {
- const { avatar, fiatBalance, imageCanonicalUri, title } =
- parseSip10TokenCryptoAssetBalance(asset);
+export function Sip10TokenAssetItem({
+ balance,
+ info,
+ isLoading,
+ marketData,
+ onSelectAsset,
+}: Sip10TokenAssetItemProps) {
+ const name = spamFilter(info.name);
+ const fiatBalance = convertAssetBalanceToFiat({
+ balance: balance.availableBalance,
+ marketData,
+ });
return (
- {title[0]}
+ {name[0]}
}
- name={asset.info.name}
- onClick={onClick}
+ isLoading={isLoading}
+ onSelectAsset={onSelectAsset}
+ titleLeft={name}
/>
);
}
diff --git a/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-item.utils.ts b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-item.utils.ts
deleted file mode 100644
index e1b09175109..00000000000
--- a/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-item.utils.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors';
-
-import { convertAssetBalanceToFiat } from '@app/common/asset-utils';
-import { formatBalance } from '@app/common/format-balance';
-import { ftDecimals, getSafeImageCanonicalUri } from '@app/common/stacks-utils';
-import { spamFilter } from '@app/common/utils/spam-filter';
-import type { Sip10AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model';
-
-export function parseSip10TokenCryptoAssetBalance(asset: Sip10AccountCryptoAssetWithDetails) {
- const { balance, info } = asset;
- const { contractId, decimals, imageCanonicalUri, name, symbol } = info;
-
- const amount = ftDecimals(balance.availableBalance.amount, decimals);
- const avatar = contractId;
- const dataTestId =
- symbol && CryptoAssetSelectors.CryptoAssetListItem.replace('{symbol}', symbol.toLowerCase());
- const formattedBalance = formatBalance(amount);
- const safeImageCanonicalUri = getSafeImageCanonicalUri(imageCanonicalUri, name);
- const title = spamFilter(name);
- const fiatBalance = convertAssetBalanceToFiat({
- ...asset,
- balance: asset.balance.availableBalance,
- });
-
- return {
- amount,
- avatar,
- fiatBalance,
- dataTestId,
- formattedBalance,
- imageCanonicalUri: safeImageCanonicalUri,
- title,
- };
-}
diff --git a/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list-unsupported.tsx b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list-unsupported.tsx
index 72b52332198..db02882b6fd 100644
--- a/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list-unsupported.tsx
+++ b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list-unsupported.tsx
@@ -2,25 +2,30 @@ import { useState } from 'react';
import { Stack, styled } from 'leather-styles/jsx';
-import { useFilteredSip10AccountCryptoAssetsWithDetails } from '@app/query/stacks/sip10/sip10-tokens.hooks';
+import { useAlexCurrencyPriceAsMarketData } from '@app/query/common/alex-sdk/alex-sdk.hooks';
+import type { Sip10TokenAssetDetails } from '@app/query/stacks/sip10/sip10-tokens.hooks';
import { Accordion } from '@app/ui/components/accordion/accordion';
import { Sip10TokenAssetItem } from './sip10-token-asset-item';
const accordionValue = 'accordion-unsupported-token-asset-list';
-export function Sip10TokenAssetListUnsupported({ address }: { address: string }) {
+interface Sip10TokenAssetListUnsupportedProps {
+ isLoading: boolean;
+ tokens: Sip10TokenAssetDetails[];
+}
+export function Sip10TokenAssetListUnsupported({
+ isLoading,
+ tokens,
+}: Sip10TokenAssetListUnsupportedProps) {
const [isOpen, setIsOpen] = useState(false);
- const assets = useFilteredSip10AccountCryptoAssetsWithDetails({
- address,
- filter: 'unsupported',
- });
+ const priceAsMarketData = useAlexCurrencyPriceAsMarketData();
function onValueChange(value: string) {
setIsOpen(value === accordionValue);
}
- if (!assets.length) return null;
+ if (!tokens.length) return null;
return (
@@ -30,8 +35,17 @@ export function Sip10TokenAssetListUnsupported({ address }: { address: string })
- {assets.map(asset => (
-
+ {tokens.map(token => (
+
))}
diff --git a/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list.tsx b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list.tsx
index 28e2b49c71e..ab2f490bdc4 100644
--- a/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list.tsx
+++ b/src/app/features/asset-list/stacks/sip10-token-asset-list/sip10-token-asset-list.tsx
@@ -1,28 +1,38 @@
import { Stack } from 'leather-styles/jsx';
-import { isDefined } from '@shared/utils';
-
-import type { AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model';
-import { useFilteredSip10AccountCryptoAssetsWithDetails } from '@app/query/stacks/sip10/sip10-tokens.hooks';
+import { useAlexCurrencyPriceAsMarketData } from '@app/query/common/alex-sdk/alex-sdk.hooks';
+import type { Sip10TokenAssetDetails } from '@app/query/stacks/sip10/sip10-tokens.hooks';
import { Sip10TokenAssetItem } from './sip10-token-asset-item';
interface Sip10TokenAssetListProps {
- address: string;
- onClick?(asset: AccountCryptoAssetWithDetails): void;
+ isLoading: boolean;
+ tokens: Sip10TokenAssetDetails[];
+ onSelectAsset?(symbol: string, contractId?: string): void;
}
-export function Sip10TokenAssetList({ address, onClick }: Sip10TokenAssetListProps) {
- const assets = useFilteredSip10AccountCryptoAssetsWithDetails({
- address,
- filter: isDefined(onClick) ? 'all' : 'supported',
- });
+export function Sip10TokenAssetList({
+ isLoading,
+ tokens,
+ onSelectAsset,
+}: Sip10TokenAssetListProps) {
+ const priceAsMarketData = useAlexCurrencyPriceAsMarketData();
- if (!assets.length) return null;
+ if (!tokens.length) return null;
return (
- {assets.map(asset => (
-
+ {tokens.map(token => (
+
))}
);
diff --git a/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item-fallback.tsx b/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item-fallback.tsx
deleted file mode 100644
index 1eb4c256dcd..00000000000
--- a/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item-fallback.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout';
-import { stxCryptoAssetPlaceholder } from '@app/query/stacks/stx/stx-crypto-asset.hooks';
-import { useCheckLedgerBlockchainAvailable } from '@app/store/accounts/blockchain/utils';
-import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon';
-
-import type { AssetListVariant } from '../../asset-list';
-import { ConnectLedgerButton } from '../../components/connect-ledger-asset-button';
-
-interface StxCryptoAssetItemFallbackProps {
- variant: AssetListVariant;
-}
-export function StxCryptoAssetItemFallback({ variant }: StxCryptoAssetItemFallbackProps) {
- const checkBlockchainAvailable = useCheckLedgerBlockchainAvailable();
- if (variant === 'interactive' && !checkBlockchainAvailable('stacks')) return null;
- return (
- }
- name={stxCryptoAssetPlaceholder.info.name}
- rightElement={}
- />
- );
-}
diff --git a/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item.stories.tsx b/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item.stories.tsx
index 7fe04fcfecb..5db83074eea 100644
--- a/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item.stories.tsx
+++ b/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item.stories.tsx
@@ -42,40 +42,20 @@ const stxCryptoAssetBalance = {
export const StxCryptoAssetItem: Story = {
args: {
- asset: {
- info: {
- decimals: 6,
- hasMemo: true,
- name: 'stacks',
- symbol: 'STX',
- },
- balance: {
- ...stxCryptoAssetBalance,
- lockedBalance: { amount: new BigNumber(0), decimals: 6, symbol },
- },
- chain: 'stacks',
- marketData: null,
- type: 'stx',
+ balance: {
+ ...stxCryptoAssetBalance,
+ lockedBalance: { amount: new BigNumber(0), decimals: 6, symbol },
},
+ isLoading: false,
},
};
export const StxCryptoAssetItemWithLockedBalance: Story = {
args: {
- asset: {
- info: {
- decimals: 6,
- hasMemo: true,
- name: 'stacks',
- symbol: 'STX',
- },
- balance: {
- ...stxCryptoAssetBalance,
- lockedBalance: { amount: new BigNumber(1000000000), decimals: 6, symbol },
- },
- chain: 'stacks',
- marketData: null,
- type: 'stx',
+ balance: {
+ ...stxCryptoAssetBalance,
+ lockedBalance: { amount: new BigNumber(1000000000), decimals: 6, symbol },
},
+ isLoading: false,
},
};
diff --git a/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item.tsx b/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item.tsx
index 55092bab5f1..1473ad0cf0d 100644
--- a/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item.tsx
+++ b/src/app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item.tsx
@@ -1,54 +1,46 @@
+import type { StxCryptoAssetBalance } from '@leather-wallet/models';
import { styled } from 'leather-styles/jsx';
import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money';
-import { i18nFormatCurrency } from '@app/common/money/format-money';
-import { ftDecimals } from '@app/common/stacks-utils';
-import { capitalize } from '@app/common/utils';
+import { formatMoneyWithoutSymbol, i18nFormatCurrency } from '@app/common/money/format-money';
import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout';
import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks';
-import type {
- AccountCryptoAssetWithDetails,
- StxAccountCryptoAssetWithDetails,
-} from '@app/query/models/crypto-asset.model';
import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon';
-import { BulletOperator } from '@app/ui/components/bullet-separator/bullet-separator';
import { Caption } from '@app/ui/components/typography/caption';
interface StxCryptoAssetItemProps {
- asset: StxAccountCryptoAssetWithDetails;
+ balance: StxCryptoAssetBalance;
isLoading: boolean;
- onClick?(asset: AccountCryptoAssetWithDetails): void;
+ onSelectAsset?(symbol: string): void;
}
-export function StxCryptoAssetItem({ asset, isLoading, onClick }: StxCryptoAssetItemProps) {
+export function StxCryptoAssetItem({ balance, isLoading, onSelectAsset }: StxCryptoAssetItemProps) {
const marketData = useCryptoCurrencyMarketDataMeanAverage('STX');
- const { availableBalance, lockedBalance } = asset.balance;
- const showAdditionalInfo = lockedBalance.amount.isGreaterThan(0);
+ const { availableBalance, lockedBalance } = balance;
+ const showLockedBalance = lockedBalance.amount.isGreaterThan(0);
- const lockedBalanceAsFiat = i18nFormatCurrency(
+ const fiatLockedBalance = i18nFormatCurrency(
baseCurrencyAmountInQuote(lockedBalance, marketData)
);
- const availableBalanceAsFiat = i18nFormatCurrency(
+ const fiatAvailableBalance = i18nFormatCurrency(
baseCurrencyAmountInQuote(availableBalance, marketData)
);
- const additionalBalanceInfo = (
-
-
- {ftDecimals(lockedBalance.amount, lockedBalance.decimals)} locked
-
+ const titleRightBulletInfo = (
+ {formatMoneyWithoutSymbol(lockedBalance)} locked
);
- const additionalBalanceInfoAsFiat = {lockedBalanceAsFiat} locked;
+ const captionRightBulletInfo = {fiatLockedBalance} locked;
return (
}
isLoading={isLoading}
- name={capitalize(asset.info.name)}
- onClick={onClick}
+ onSelectAsset={onSelectAsset}
+ titleLeft="Stacks"
+ titleRightBulletInfo={showLockedBalance && titleRightBulletInfo}
/>
);
}
diff --git a/src/app/features/asset-list/stacks/stx20-token-asset-list/stx20-token-asset-item.layout.tsx b/src/app/features/asset-list/stacks/stx20-token-asset-list/stx20-token-asset-item.layout.tsx
deleted file mode 100644
index 6cc4b7d2f1d..00000000000
--- a/src/app/features/asset-list/stacks/stx20-token-asset-list/stx20-token-asset-item.layout.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { styled } from 'leather-styles/jsx';
-
-import { formatBalance } from '@app/common/format-balance';
-import type { Stx20Token } from '@app/query/stacks/stacks-client';
-import { Stx20AvatarIcon } from '@app/ui/components/avatar/stx20-avatar-icon';
-import { ItemLayout } from '@app/ui/components/item-layout/item-layout';
-import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip';
-import { Pressable } from '@app/ui/pressable/pressable';
-
-interface Stx20TokenAssetItemLayoutProps {
- token: Stx20Token;
-}
-export function Stx20TokenAssetItemLayout({ token }: Stx20TokenAssetItemLayoutProps) {
- const balanceAsString = token.balance.amount.toString();
- const formattedBalance = formatBalance(balanceAsString);
-
- return (
-
- }
- titleLeft={token.tokenData.ticker}
- captionLeft="STX-20"
- titleRight={
-
-
- {formattedBalance.value}
-
-
- }
- />
-
- );
-}
diff --git a/src/app/features/asset-list/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx b/src/app/features/asset-list/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx
index 18462051c5c..144ffc8f068 100644
--- a/src/app/features/asset-list/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx
+++ b/src/app/features/asset-list/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx
@@ -1,12 +1,24 @@
-import type { Stx20Token } from '@app/query/stacks/stacks-client';
+import type { CryptoAssetBalance, Stx20CryptoAssetInfo } from '@leather-wallet/models';
-import { Stx20TokenAssetItemLayout } from './stx20-token-asset-item.layout';
+import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout';
+import { Stx20AvatarIcon } from '@app/ui/components/avatar/stx20-avatar-icon';
+
+interface Stx20TokenAssetDetails {
+ balance: CryptoAssetBalance;
+ info: Stx20CryptoAssetInfo;
+}
interface Stx20TokenAssetListProps {
- tokens: Stx20Token[];
+ tokens: Stx20TokenAssetDetails[];
}
export function Stx20TokenAssetList({ tokens }: Stx20TokenAssetListProps) {
return tokens.map((token, i) => (
-
+ }
+ key={`${token.info.symbol}${i}`}
+ titleLeft={token.info.symbol}
+ />
));
}
diff --git a/src/app/features/bitcoin-choose-fee/bitcoin-choose-fee.tsx b/src/app/features/bitcoin-choose-fee/bitcoin-choose-fee.tsx
index ca8d932c055..e0e2eecaa42 100644
--- a/src/app/features/bitcoin-choose-fee/bitcoin-choose-fee.tsx
+++ b/src/app/features/bitcoin-choose-fee/bitcoin-choose-fee.tsx
@@ -10,7 +10,7 @@ import { formatMoney } from '@app/common/money/format-money';
import { BitcoinCustomFee } from '@app/components/bitcoin-custom-fee/bitcoin-custom-fee';
import { MAX_FEE_RATE_MULTIPLIER } from '@app/components/bitcoin-custom-fee/hooks/use-bitcoin-custom-fee';
import { OnChooseFeeArgs } from '@app/components/bitcoin-fees-list/bitcoin-fees-list';
-import { useCurrentBtcAvailableBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
+import { useCurrentBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
import { AvailableBalance } from '@app/ui/components/containers/footers/available-balance';
import { BitcoinChooseFeeLayout } from './components/bitcoin-choose-fee.layout';
@@ -47,7 +47,7 @@ export function BitcoinChooseFee({
maxRecommendedFeeRate = 0,
...rest
}: BitcoinChooseFeeProps) {
- const { balance } = useCurrentBtcAvailableBalanceNativeSegwit();
+ const { balance } = useCurrentBtcCryptoAssetBalanceNativeSegwit();
const hasAmount = amount.amount.isGreaterThan(0);
const [customFeeInitialValue, setCustomFeeInitialValue] = useState(recommendedFeeRate);
@@ -86,7 +86,7 @@ export function BitcoinChooseFee({
feesList={feesList}
/>
-
+
diff --git a/src/app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend.ts b/src/app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend.ts
index 01a3764ae79..5a5c22ab7c7 100644
--- a/src/app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend.ts
+++ b/src/app/features/bitcoin-choose-fee/hooks/use-validate-bitcoin-spend.ts
@@ -3,18 +3,18 @@ import { useState } from 'react';
import { Money, createMoney } from '@shared/models/money.model';
import { subtractMoney, sumMoney } from '@app/common/money/calculate-money';
-import { useCurrentBtcAvailableBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
+import { useCurrentBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
export function useValidateBitcoinSpend(amount?: Money, isSendingMax?: boolean) {
const [showInsufficientBalanceError, setShowInsufficientBalanceError] = useState(false);
- const { balance } = useCurrentBtcAvailableBalanceNativeSegwit();
+ const { balance } = useCurrentBtcCryptoAssetBalanceNativeSegwit();
return {
showInsufficientBalanceError,
onValidateBitcoinFeeSpend(feeValue: number) {
const feeAsMoney = createMoney(feeValue, 'BTC');
- if (feeAsMoney.amount.isGreaterThan(balance.amount)) {
+ if (feeAsMoney.amount.isGreaterThan(balance.availableBalance.amount)) {
setShowInsufficientBalanceError(true);
return false;
}
@@ -28,10 +28,10 @@ export function useValidateBitcoinSpend(amount?: Money, isSendingMax?: boolean)
}
const totalSpend = isSendingMax
- ? subtractMoney(balance, feeAsMoney)
+ ? subtractMoney(balance.availableBalance, feeAsMoney)
: sumMoney([amount, feeAsMoney]);
- if (totalSpend.amount.isGreaterThan(balance.amount)) {
+ if (totalSpend.amount.isGreaterThan(balance.availableBalance.amount)) {
setShowInsufficientBalanceError(true);
return false;
}
diff --git a/src/app/features/dialogs/increase-fee-dialog/hooks/use-btc-increase-fee.ts b/src/app/features/dialogs/increase-fee-dialog/hooks/use-btc-increase-fee.ts
index a2df7a106db..1505ee9f863 100644
--- a/src/app/features/dialogs/increase-fee-dialog/hooks/use-btc-increase-fee.ts
+++ b/src/app/features/dialogs/increase-fee-dialog/hooks/use-btc-increase-fee.ts
@@ -51,7 +51,7 @@ export function useBtcIncreaseFee(btcTx: BitcoinTx) {
[btcTx.vin.length, btcTx.vout.length, recipient]
);
- const { btcCryptoAssetBalance } = useBtcCryptoAssetBalanceNativeSegwit(currentBitcoinAddress);
+ const { balance } = useBtcCryptoAssetBalanceNativeSegwit(currentBitcoinAddress);
const sendingAmount = getBitcoinTxValue(currentBitcoinAddress, btcTx);
const { feesList } = useBitcoinFeesList({
amount: createMoney(btcToSat(sendingAmount), 'BTC'),
@@ -164,7 +164,7 @@ export function useBtcIncreaseFee(btcTx: BitcoinTx) {
}
// check if fee is higher than the available balance
- return bnValue.isLessThanOrEqualTo(btcCryptoAssetBalance.availableBalance.amount);
+ return bnValue.isLessThanOrEqualTo(balance.availableBalance.amount);
},
}),
});
diff --git a/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx b/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx
index 674651b3ba7..01fd1678cec 100644
--- a/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx
+++ b/src/app/features/dialogs/increase-fee-dialog/increase-btc-fee-dialog.tsx
@@ -33,11 +33,11 @@ export function IncreaseBtcFeeDialog() {
const btcTx = tx;
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
const currentBitcoinAddress = nativeSegwitSigner.address;
- const { btcCryptoAssetBalance } = useBtcCryptoAssetBalanceNativeSegwit(currentBitcoinAddress);
+ const { balance } = useBtcCryptoAssetBalanceNativeSegwit(currentBitcoinAddress);
const { isBroadcasting, sizeInfo, onSubmit, validationSchema, recipient } =
useBtcIncreaseFee(btcTx);
- const balance = formatMoney(btcCryptoAssetBalance.availableBalance);
+ const btcBalance = formatMoney(balance.availableBalance);
const recipients = [
{
@@ -106,7 +106,7 @@ export function IncreaseBtcFeeDialog() {
/>
- {btcCryptoAssetBalance && Balance: {balance}}
+ {balance && Balance: {btcBalance}}
diff --git a/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx b/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx
index dc344607006..7602638ec3c 100644
--- a/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx
+++ b/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx
@@ -8,7 +8,7 @@ import { noop } from '@shared/utils';
import { usePressable } from '@app/components/item-hover';
import { StatusPending } from '@app/components/status-pending';
import { StatusReady } from '@app/components/status-ready';
-import { useCurrentBtcAvailableBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
+import { useCurrentBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
import { useCheckOrderStatuses } from '@app/query/bitcoin/ordinals/brc20/use-check-order-status';
import { fetchInscripionById } from '@app/query/bitcoin/ordinals/inscription-by-id.query';
import { convertInscriptionToSupportedInscriptionType } from '@app/query/bitcoin/ordinals/inscription.hooks';
@@ -90,9 +90,9 @@ function PendingBrcTransfer({ order }: PendingBrcTransferProps) {
const [component, bind] = usePressable(order.status === 'ready');
const navigate = useNavigate();
const ordinalsbotClient = useOrdinalsbotClient();
- const { balance } = useCurrentBtcAvailableBalanceNativeSegwit();
+ const { balance } = useCurrentBtcCryptoAssetBalanceNativeSegwit();
- const hasPositiveBtcBalanceForFees = balance.amount.isGreaterThan(0);
+ const hasPositiveBtcBalanceForFees = balance.availableBalance.amount.isGreaterThan(0);
return (
diff --git a/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx b/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx
index 58eb7a9cb2b..89df7aa848e 100644
--- a/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx
+++ b/src/app/pages/fund/choose-asset-to-fund/choose-asset-to-fund.tsx
@@ -5,15 +5,12 @@ import { Stack, styled } from 'leather-styles/jsx';
import { RouteUrls } from '@shared/route-urls';
-import { capitalize } from '@app/common/utils';
-import { CryptoAssetItemLayout } from '@app/components/crypto-asset-item/crypto-asset-item.layout';
import { BitcoinNativeSegwitAccountLoader } from '@app/components/loaders/bitcoin-account-loader';
-import { BtcCryptoAssetLoader } from '@app/components/loaders/btc-crypto-asset-loader';
+import { BtcBalanceLoader } from '@app/components/loaders/btc-balance-loader';
import { CurrentStacksAccountLoader } from '@app/components/loaders/stacks-account-loader';
-import { StxCryptoAssetLoader } from '@app/components/loaders/stx-crypto-asset-loader';
+import { StxBalanceLoader } from '@app/components/loaders/stx-balance-loader';
+import { BtcCryptoAssetItem } from '@app/features/asset-list/bitcoin/btc-crypto-asset-item/btc-crypto-asset-item';
import { StxCryptoAssetItem } from '@app/features/asset-list/stacks/stx-crypo-asset-item/stx-crypto-asset-item';
-import type { AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model';
-import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon';
import { Card } from '@app/ui/layout/card/card';
import { Page } from '@app/ui/layout/page/page.layout';
@@ -21,8 +18,7 @@ export function ChooseCryptoAssetToFund() {
const navigate = useNavigate();
const navigateToFund = useCallback(
- (asset: AccountCryptoAssetWithDetails) =>
- navigate(RouteUrls.Fund.replace(':currency', asset.info.symbol)),
+ (symbol: string) => navigate(RouteUrls.Fund.replace(':currency', symbol)),
[navigate]
);
@@ -39,31 +35,29 @@ export function ChooseCryptoAssetToFund() {
{signer => (
-
- {(asset, isLoading) => (
- }
- isLoading={isLoading}
- name={capitalize(asset.info.name)}
- onClick={() => navigateToFund(asset)}
+
+ {(balance, isInitialLoading) => (
+ navigateToFund('BTC')}
/>
)}
-
+
)}
{account => (
-
- {(asset, isInitialLoading) => (
+
+ {(balance, isInitialLoading) => (
navigateToFund(asset)}
+ onSelectAsset={() => navigateToFund('STX')}
/>
)}
-
+
)}
diff --git a/src/app/pages/home/components/send-button.tsx b/src/app/pages/home/components/send-button.tsx
index 33da083cc91..80a4b4930b0 100644
--- a/src/app/pages/home/components/send-button.tsx
+++ b/src/app/pages/home/components/send-button.tsx
@@ -10,7 +10,7 @@ import { whenPageMode } from '@app/common/utils';
import { openIndexPageInNewTab } from '@app/common/utils/open-in-new-tab';
import { useBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
import { useStxCryptoAssetBalance } from '@app/query/stacks/balance/account-balance.hooks';
-import { useTransferableSip10CryptoAssetsWithDetails } from '@app/query/stacks/sip10/sip10-tokens.hooks';
+import { useTransferableSip10Tokens } from '@app/query/stacks/sip10/sip10-tokens.hooks';
import { useCurrentAccountNativeSegwitIndexZeroSignerNullable } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
import { IconButton } from '@app/ui/components/icon-button/icon-button';
@@ -19,14 +19,13 @@ import { SendIcon } from '@app/ui/icons';
function SendButtonSuspense() {
const navigate = useNavigate();
const { whenWallet } = useWalletType();
- const address = useCurrentStacksAccountAddress();
const btcAddress = useCurrentAccountNativeSegwitIndexZeroSignerNullable()?.address;
- const { btcCryptoAssetBalance } = useBtcCryptoAssetBalanceNativeSegwit(btcAddress ?? '');
- const { data: stxCryptoAssetBalance } = useStxCryptoAssetBalance(address);
- const stacksFtAssets = useTransferableSip10CryptoAssetsWithDetails(address);
+ const { balance: btcBalance } = useBtcCryptoAssetBalanceNativeSegwit(btcAddress ?? '');
+ const stxAddress = useCurrentStacksAccountAddress();
+ const { data: stxBalance } = useStxCryptoAssetBalance(stxAddress);
+ const stacksFtAssets = useTransferableSip10Tokens(stxAddress);
- const isDisabled =
- !btcCryptoAssetBalance && !stxCryptoAssetBalance && stacksFtAssets?.length === 0;
+ const isDisabled = !btcBalance && !stxBalance && stacksFtAssets?.length === 0;
return (
-
+
);
diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx
index 57eec8dabd2..298774c3371 100644
--- a/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/form/btc/btc-send-form.tsx
@@ -1,17 +1,15 @@
import { Outlet } from 'react-router-dom';
+import type { CryptoCurrencies } from '@leather-wallet/models';
import { SendCryptoAssetSelectors } from '@tests/selectors/send.selectors';
import { Form, Formik } from 'formik';
import { Box } from 'leather-styles/jsx';
import { HIGH_FEE_WARNING_LEARN_MORE_URL_BTC } from '@shared/constants';
-import { CryptoCurrencies } from '@shared/models/currencies.model';
import { formatMoney } from '@app/common/money/format-money';
import { HighFeeDialog } from '@app/features/dialogs/high-fee-dialog/high-fee-dialog';
-import { useBtcAccountCryptoAssetWithDetails } from '@app/query/bitcoin/btc/btc-crypto-asset.hooks';
import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks';
-import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon';
import { Button } from '@app/ui/components/button/button';
import { Callout } from '@app/ui/components/callout/callout';
@@ -34,13 +32,10 @@ const symbol: CryptoCurrencies = 'BTC';
export function BtcSendForm() {
const routeState = useSendFormRouteState();
- const btcMarketData = useCryptoCurrencyMarketDataMeanAverage(symbol);
-
- const { address } = useCurrentAccountNativeSegwitIndexZeroSigner();
- const { asset } = useBtcAccountCryptoAssetWithDetails(address);
- const { balance, info } = asset;
+ const marketData = useCryptoCurrencyMarketDataMeanAverage('BTC');
const {
+ balance,
calcMaxSpend,
chooseTransactionFee,
currentNetwork,
@@ -101,10 +96,10 @@ export function BtcSendForm() {
onSetIsSendingMax={onSetIsSendingMax}
isSendingMax={isSendingMax}
switchableAmount={
-
+
}
/>
- } name={info.name} symbol={symbol} />
+ } name="Bitcoin" symbol={symbol} />
{currentNetwork.chain.bitcoin.bitcoinNetwork === 'testnet' && (
diff --git a/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-send-form.tsx
index 9ee3743d782..7513f71abed 100644
--- a/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-send-form.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/form/btc/use-btc-send-form.tsx
@@ -24,7 +24,7 @@ import {
} from '@app/common/validation/forms/currency-validators';
import { useUpdatePersistedSendFormValues } from '@app/features/popup-send-form-restoration/use-update-persisted-send-form-values';
import { useCurrentNativeSegwitUtxos } from '@app/query/bitcoin/address/utxos-by-address.hooks';
-import { useCurrentBtcAvailableBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
+import { useCurrentBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks';
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
@@ -37,7 +37,7 @@ export function useBtcSendForm() {
const currentNetwork = useCurrentNetwork();
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
const { data: utxos = [], refetch } = useCurrentNativeSegwitUtxos();
- const { balance } = useCurrentBtcAvailableBalanceNativeSegwit();
+ const { balance } = useCurrentBtcCryptoAssetBalanceNativeSegwit();
const sendFormNavigate = useSendFormNavigate();
const calcMaxSpend = useCalculateMaxBitcoinSpend();
const { onFormStateChange } = useUpdatePersistedSendFormValues();
@@ -46,6 +46,7 @@ export function useBtcSendForm() {
useOnMount(() => refetch());
return {
+ balance,
calcMaxSpend,
currentNetwork,
formRef,
@@ -59,7 +60,7 @@ export function useBtcSendForm() {
amount: yup
.number()
.concat(btcMinimumSpendValidator())
- .concat(btcAmountPrecisionValidator(formatPrecisionError(balance)))
+ .concat(btcAmountPrecisionValidator(formatPrecisionError(balance.availableBalance)))
.concat(currencyAmountValidator())
.concat(
btcInsufficientBalanceValidator({
diff --git a/src/app/pages/send/send-crypto-asset-form/form/sip10/sip10-token-send-form-container.tsx b/src/app/pages/send/send-crypto-asset-form/form/sip10/sip10-token-send-form-container.tsx
index fbee8ba9b37..657596792cc 100644
--- a/src/app/pages/send/send-crypto-asset-form/form/sip10/sip10-token-send-form-container.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/form/sip10/sip10-token-send-form-container.tsx
@@ -1,5 +1,6 @@
+import type { CryptoAssetBalance, MarketData, Sip10CryptoAssetInfo } from '@leather-wallet/models';
+
import { StacksAssetAvatar } from '@app/components/stacks-asset-avatar';
-import type { Sip10AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model';
import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon';
import { AmountField } from '../../components/amount-field';
@@ -10,9 +11,15 @@ import { StacksCommonSendForm } from '../stacks/stacks-common-send-form';
import { useSip10SendForm } from './use-sip10-send-form';
interface Sip10TokenSendFormContainerProps {
- asset: Sip10AccountCryptoAssetWithDetails;
+ info: Sip10CryptoAssetInfo;
+ balance: CryptoAssetBalance;
+ marketData: MarketData;
}
-export function Sip10TokenSendFormContainer({ asset }: Sip10TokenSendFormContainerProps) {
+export function Sip10TokenSendFormContainer({
+ info,
+ balance,
+ marketData,
+}: Sip10TokenSendFormContainerProps) {
const {
availableTokenBalance,
initialValues,
@@ -21,10 +28,9 @@ export function Sip10TokenSendFormContainer({ asset }: Sip10TokenSendFormContain
stacksFtFees: fees,
validationSchema,
avatar,
- marketData,
decimals,
symbol,
- } = useSip10SendForm({ asset });
+ } = useSip10SendForm({ info, balance });
const amountField = (
;
}
- return children({ asset });
+ return children({
+ ...token,
+ marketData: priceAsMarketData(token.info.contractId, token.balance.availableBalance.symbol),
+ });
}
export function Sip10TokenSendForm() {
return (
- {({ asset }) => }
+ {token => (
+
+ )}
);
}
diff --git a/src/app/pages/send/send-crypto-asset-form/form/sip10/use-sip10-send-form.tsx b/src/app/pages/send/send-crypto-asset-form/form/sip10/use-sip10-send-form.tsx
index 41c3b66c600..4176cbfc2d7 100644
--- a/src/app/pages/send/send-crypto-asset-form/form/sip10/use-sip10-send-form.tsx
+++ b/src/app/pages/send/send-crypto-asset-form/form/sip10/use-sip10-send-form.tsx
@@ -1,5 +1,6 @@
import { useMemo } from 'react';
+import type { CryptoAssetBalance, Sip10CryptoAssetInfo } from '@leather-wallet/models';
import { FormikHelpers } from 'formik';
import * as yup from 'yup';
@@ -9,7 +10,6 @@ import { StacksSendFormValues } from '@shared/models/form.model';
import { convertAmountToBaseUnit } from '@app/common/money/calculate-money';
import { getSafeImageCanonicalUri } from '@app/common/stacks-utils';
import { stacksFungibleTokenAmountValidator } from '@app/common/validation/forms/amount-validators';
-import type { Sip10AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model';
import { useCalculateStacksTxFees } from '@app/query/stacks/fees/fees.hooks';
import {
useFtTokenTransferUnsignedTx,
@@ -20,31 +20,32 @@ import { useSendFormNavigate } from '../../hooks/use-send-form-navigate';
import { useStacksCommonSendForm } from '../stacks/use-stacks-common-send-form';
interface UseSip10SendFormArgs {
- asset: Sip10AccountCryptoAssetWithDetails;
+ balance: CryptoAssetBalance;
+ info: Sip10CryptoAssetInfo;
}
-export function useSip10SendForm({ asset }: UseSip10SendFormArgs) {
- const generateTx = useGenerateFtTokenTransferUnsignedTx(asset.info);
+export function useSip10SendForm({ balance, info }: UseSip10SendFormArgs) {
+ const generateTx = useGenerateFtTokenTransferUnsignedTx(info);
const sendFormNavigate = useSendFormNavigate();
- const unsignedTx = useFtTokenTransferUnsignedTx(asset.info);
+ const unsignedTx = useFtTokenTransferUnsignedTx(info);
const { data: stacksFtFees } = useCalculateStacksTxFees(unsignedTx);
- const availableTokenBalance = asset.balance.availableBalance;
+ const availableTokenBalance = balance.availableBalance;
const sendMaxBalance = useMemo(
() => convertAmountToBaseUnit(availableTokenBalance),
[availableTokenBalance]
);
const { initialValues, checkFormValidation, recipient, memo, nonce } = useStacksCommonSendForm({
- symbol: asset.info.symbol,
+ symbol: info.symbol,
availableTokenBalance,
});
function createFtAvatar() {
return {
- avatar: asset.info.contractId,
- imageCanonicalUri: getSafeImageCanonicalUri(asset.info.imageCanonicalUri, asset.info.name),
+ avatar: info.contractId,
+ imageCanonicalUri: getSafeImageCanonicalUri(info.imageCanonicalUri, info.name),
};
}
@@ -53,9 +54,8 @@ export function useSip10SendForm({ asset }: UseSip10SendFormArgs) {
initialValues,
sendMaxBalance,
stacksFtFees,
- symbol: asset.info.symbol,
- decimals: asset.info.decimals,
- marketData: asset.marketData,
+ symbol: info.symbol,
+ decimals: info.decimals,
avatar: createFtAvatar(),
validationSchema: yup.object({
amount: stacksFungibleTokenAmountValidator(availableTokenBalance),
@@ -75,8 +75,8 @@ export function useSip10SendForm({ asset }: UseSip10SendFormArgs) {
if (!tx) return logger.error('Attempted to generate unsigned tx, but tx is undefined');
sendFormNavigate.toConfirmAndSignStacksSip10Transaction({
- decimals: asset.info.decimals,
- name: asset.info.name,
+ decimals: info.decimals,
+ name: info.name,
tx,
});
},
diff --git a/src/app/query/bitcoin/balance/btc-balance-native-segwit.hooks.ts b/src/app/query/bitcoin/balance/btc-balance-native-segwit.hooks.ts
index 84f43815ec3..e962da88c77 100644
--- a/src/app/query/bitcoin/balance/btc-balance-native-segwit.hooks.ts
+++ b/src/app/query/bitcoin/balance/btc-balance-native-segwit.hooks.ts
@@ -1,37 +1,58 @@
import { useMemo } from 'react';
import type { BtcCryptoAssetBalance, Money } from '@leather-wallet/models';
+import BigNumber from 'bignumber.js';
import { createMoney } from '@shared/models/money.model';
+import { isUndefined } from '@shared/utils';
+import { sumNumbers } from '@app/common/math/helpers';
import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';
-import { useGetBitcoinBalanceByAddress } from './btc-balance.hooks';
+import { useNativeSegwitUtxosByAddress } from '../address/utxos-by-address.hooks';
+import { useRunesEnabled } from '../runes/runes.hooks';
function createBtcCryptoAssetBalance(balance: Money): BtcCryptoAssetBalance {
return {
availableBalance: balance,
- // TODO: Asset refactor: can we determine these here or are they nec?
+ // TODO: Can we determine these here or are they nec?
protectedBalance: createMoney(0, 'BTC'),
uneconomicalBalance: createMoney(0, 'BTC'),
};
}
-// Balance is derived from a single query in address reuse mode
export function useBtcCryptoAssetBalanceNativeSegwit(address: string) {
- const { balance, isInitialLoading, isLoading, isFetching } =
- useGetBitcoinBalanceByAddress(address);
- const btcCryptoAssetBalance = useMemo(() => createBtcCryptoAssetBalance(balance), [balance]);
+ const runesEnabled = useRunesEnabled();
+
+ const {
+ data: utxos,
+ isInitialLoading,
+ isLoading,
+ isFetching,
+ } = useNativeSegwitUtxosByAddress({
+ address,
+ filterInscriptionUtxos: true,
+ filterPendingTxsUtxos: true,
+ filterRunesUtxos: runesEnabled,
+ });
+
+ const balance = useMemo(() => {
+ if (isUndefined(utxos))
+ return createBtcCryptoAssetBalance(createMoney(new BigNumber(0), 'BTC'));
+ return createBtcCryptoAssetBalance(
+ createMoney(sumNumbers(utxos.map(utxo => utxo.value)), 'BTC')
+ );
+ }, [utxos]);
return {
- btcCryptoAssetBalance,
+ balance,
isInitialLoading,
isLoading,
isFetching,
};
}
-export function useCurrentBtcAvailableBalanceNativeSegwit() {
+export function useCurrentBtcCryptoAssetBalanceNativeSegwit() {
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
- return useGetBitcoinBalanceByAddress(nativeSegwitSigner.address);
+ return useBtcCryptoAssetBalanceNativeSegwit(nativeSegwitSigner.address);
}
diff --git a/src/app/query/bitcoin/balance/btc-balance.hooks.ts b/src/app/query/bitcoin/balance/btc-balance.hooks.ts
deleted file mode 100644
index d7b5f85a609..00000000000
--- a/src/app/query/bitcoin/balance/btc-balance.hooks.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { useMemo } from 'react';
-
-import BigNumber from 'bignumber.js';
-
-import { createMoney } from '@shared/models/money.model';
-import { isUndefined } from '@shared/utils';
-
-import { sumNumbers } from '@app/common/math/helpers';
-
-import { useNativeSegwitUtxosByAddress } from '../address/utxos-by-address.hooks';
-import { useRunesEnabled } from '../runes/runes.hooks';
-
-export function useGetBitcoinBalanceByAddress(address: string) {
- const runesEnabled = useRunesEnabled();
-
- const {
- data: utxos,
- isInitialLoading,
- isLoading,
- isFetching,
- } = useNativeSegwitUtxosByAddress({
- address,
- filterInscriptionUtxos: true,
- filterPendingTxsUtxos: true,
- filterRunesUtxos: runesEnabled,
- });
-
- const balance = useMemo(() => {
- if (isUndefined(utxos)) return createMoney(new BigNumber(0), 'BTC');
- return createMoney(sumNumbers(utxos.map(utxo => utxo.value)), 'BTC');
- }, [utxos]);
-
- return {
- balance,
- isInitialLoading,
- isLoading,
- isFetching,
- };
-}
diff --git a/src/app/query/bitcoin/bitcoin-client.ts b/src/app/query/bitcoin/bitcoin-client.ts
index 8b9f127bed5..eb6d74d3cb1 100644
--- a/src/app/query/bitcoin/bitcoin-client.ts
+++ b/src/app/query/bitcoin/bitcoin-client.ts
@@ -6,7 +6,6 @@ import {
BESTINSLOT_API_BASE_URL_TESTNET,
type BitcoinNetworkModes,
} from '@shared/constants';
-import type { Money } from '@shared/models/money.model';
import type { BitcoinTx } from '@shared/models/transactions/bitcoin-transaction.model';
import { getBlockstreamRatelimiter } from './blockstream-rate-limiter';
@@ -142,11 +141,6 @@ interface RunesTickerInfoResponse {
data: RuneTickerInfo;
}
-export interface RuneToken {
- balance: Money;
- tokenData: RuneBalance & RuneTickerInfo;
-}
-
export interface RunesOutputsByAddress {
pkscript: string;
wallet_addr: string;
diff --git a/src/app/query/bitcoin/btc/btc-crypto-asset.hooks.ts b/src/app/query/bitcoin/btc/btc-crypto-asset.hooks.ts
deleted file mode 100644
index 52cc752989d..00000000000
--- a/src/app/query/bitcoin/btc/btc-crypto-asset.hooks.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import type { BtcCryptoAssetInfo } from '@leather-wallet/models';
-
-import { BTC_DECIMALS } from '@shared/constants';
-import { createMoney } from '@shared/models/money.model';
-
-import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks';
-import { createAccountCryptoAssetWithDetailsFactory } from '@app/query/models/crypto-asset.model';
-
-import type { BtcAccountCryptoAssetWithDetails } from '../../models/crypto-asset.model';
-import { useBtcCryptoAssetBalanceNativeSegwit } from '../balance/btc-balance-native-segwit.hooks';
-
-const btcCryptoAssetInfo: BtcCryptoAssetInfo = {
- decimals: BTC_DECIMALS,
- hasMemo: false,
- name: 'bitcoin',
- symbol: 'BTC',
-};
-
-const btcCryptoAssetBalancePlaceholder = {
- availableBalance: createMoney(0, 'BTC'),
- protectedBalance: createMoney(0, 'BTC'),
- uneconomicalBalance: createMoney(0, 'BTC'),
-};
-
-export const btcCryptoAssetPlaceholder =
- createAccountCryptoAssetWithDetailsFactory({
- balance: btcCryptoAssetBalancePlaceholder,
- chain: 'bitcoin',
- info: btcCryptoAssetInfo,
- marketData: null,
- type: 'btc',
- });
-
-export function useBtcAccountCryptoAssetWithDetails(address: string) {
- const { btcCryptoAssetBalance, isInitialLoading } = useBtcCryptoAssetBalanceNativeSegwit(address);
- const marketData = useCryptoCurrencyMarketDataMeanAverage('BTC');
-
- return {
- asset: createAccountCryptoAssetWithDetailsFactory({
- balance: btcCryptoAssetBalance,
- chain: 'bitcoin',
- info: btcCryptoAssetInfo,
- marketData,
- type: 'btc',
- }),
- isInitialLoading,
- };
-}
diff --git a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts
index d0dff8e26b9..ba39c62dd25 100644
--- a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts
+++ b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts
@@ -1,3 +1,4 @@
+import type { Brc20CryptoAssetInfo } from '@leather-wallet/models';
import BigNumber from 'bignumber.js';
import { createMarketData, createMarketPair } from '@shared/models/market.model';
@@ -6,11 +7,9 @@ import { createMoney } from '@shared/models/money.model';
import { unitToFractionalUnit } from '@app/common/money/unit-conversion';
import { useGetBrc20TokensQuery } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query';
import { useCalculateBitcoinFiatValue } from '@app/query/common/market-data/market-data.hooks';
+import { createCryptoAssetBalance } from '@app/query/common/models';
import { useConfigOrdinalsbot } from '@app/query/common/remote-config/remote-config.query';
-import {
- type Brc20AccountCryptoAssetWithDetails,
- createAccountCryptoAssetWithDetailsFactory,
-} from '@app/query/models/crypto-asset.model';
+import { isFetchedWithSuccess } from '@app/query/query-config';
import { useAppDispatch } from '@app/store';
import { useCurrentAccountIndex } from '@app/store/accounts/account';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
@@ -88,44 +87,44 @@ export function useBrc20Transfers(holderAddress: string) {
};
}
-export function useBrc20AccountCryptoAssetsWithDetails() {
+function createBrc20CryptoAssetInfo(decimals: number, ticker: string): Brc20CryptoAssetInfo {
+ return {
+ decimals,
+ hasMemo: false,
+ name: 'brc-20',
+ symbol: ticker,
+ };
+}
+
+export function useBrc20Tokens() {
const calculateBitcoinFiatValue = useCalculateBitcoinFiatValue();
- const { data: allBrc20TokensResponse } = useGetBrc20TokensQuery();
+ const result = useGetBrc20TokensQuery();
+
+ if (!isFetchedWithSuccess(result)) return [];
- const tokens = allBrc20TokensResponse?.pages
+ const tokens = result.data.pages
.flatMap(page => page.brc20Tokens)
.filter(token => token.length > 0)
.flatMap(token => token);
- return (
- tokens?.map(token => {
- const priceAsFiat = calculateBitcoinFiatValue(
- createMoney(new BigNumber(token.balance.min_listed_unit_price ?? 0), 'BTC')
- );
- return createAccountCryptoAssetWithDetailsFactory({
- balance: {
- availableBalance: createMoney(
- unitToFractionalUnit(token.info.decimals)(new BigNumber(token.balance.overall_balance)),
- token.balance.ticker,
- token.info.decimals
- ),
- },
- chain: 'bitcoin',
- holderAddress: token.holderAddress,
- info: {
- decimals: token.info.decimals,
- hasMemo: false,
- name: 'brc-20',
- symbol: token.info.ticker,
- },
- marketData: token.balance.min_listed_unit_price
- ? createMarketData(
- createMarketPair(token.balance.ticker, 'USD'),
- createMoney(priceAsFiat.amount, 'USD')
- )
- : null,
- type: 'brc-20',
- });
- }) ?? []
- );
+ return tokens.map(token => {
+ const fiatPrice = calculateBitcoinFiatValue(
+ createMoney(new BigNumber(token.balance.min_listed_unit_price ?? 0), 'BTC')
+ );
+ return {
+ balance: createCryptoAssetBalance(
+ createMoney(
+ unitToFractionalUnit(token.info.decimals)(new BigNumber(token.balance.overall_balance)),
+ token.balance.ticker,
+ token.info.decimals
+ )
+ ),
+ info: createBrc20CryptoAssetInfo(token.info.decimals, token.balance.ticker),
+ holderAddress: token.holderAddress,
+ marketData: createMarketData(
+ createMarketPair(token.balance.ticker, 'USD'),
+ createMoney(fiatPrice.amount, 'USD')
+ ),
+ };
+ });
}
diff --git a/src/app/query/bitcoin/runes/runes-ticker-info.query.ts b/src/app/query/bitcoin/runes/runes-ticker-info.query.ts
index ed9d2804d5e..25096223f0b 100644
--- a/src/app/query/bitcoin/runes/runes-ticker-info.query.ts
+++ b/src/app/query/bitcoin/runes/runes-ticker-info.query.ts
@@ -1,25 +1,31 @@
-import { type UseQueryResult, useQueries } from '@tanstack/react-query';
+import { useQueries } from '@tanstack/react-query';
import { useConfigRunesEnabled } from '@app/query/common/remote-config/remote-config.query';
import { useBitcoinClient } from '@app/store/common/api-clients.hooks';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
-import type { RuneTickerInfo } from '../bitcoin-client';
+import type { RuneBalance, RuneTickerInfo } from '../bitcoin-client';
+import { createRuneCryptoAssetDetails } from './runes.utils';
const queryOptions = { staleTime: 5 * 60 * 1000 };
-export function useGetRunesTickerInfoQuery(runeNames: string[]): UseQueryResult[] {
+export function useGetRunesTickerInfoQuery(runesBalances: RuneBalance[]) {
const client = useBitcoinClient();
const network = useCurrentNetwork();
const runesEnabled = useConfigRunesEnabled();
return useQueries({
- queries: runeNames.map(runeName => {
+ queries: runesBalances.map(runeBalance => {
return {
- enabled: !!runeName && (network.chain.bitcoin.bitcoinNetwork === 'testnet' || runesEnabled),
- queryKey: ['runes-ticker-info', runeName],
+ enabled:
+ !runeBalance && (network.chain.bitcoin.bitcoinNetwork === 'testnet' || runesEnabled),
+ queryKey: ['runes-ticker-info', runeBalance.rune_name],
queryFn: () =>
- client.bestinSlotApi.getRunesTickerInfo(runeName, network.chain.bitcoin.bitcoinNetwork),
+ client.bestinSlotApi.getRunesTickerInfo(
+ runeBalance.rune_name,
+ network.chain.bitcoin.bitcoinNetwork
+ ),
+ select: (resp: RuneTickerInfo) => createRuneCryptoAssetDetails(runeBalance, resp),
...queryOptions,
};
}),
diff --git a/src/app/query/bitcoin/runes/runes.hooks.ts b/src/app/query/bitcoin/runes/runes.hooks.ts
index 68ae4966d9e..2a6f88ecff7 100644
--- a/src/app/query/bitcoin/runes/runes.hooks.ts
+++ b/src/app/query/bitcoin/runes/runes.hooks.ts
@@ -1,31 +1,14 @@
-import { createMoney } from '@shared/models/money.model';
+import { useMemo } from 'react';
+
import { isDefined } from '@shared/utils';
import { useConfigRunesEnabled } from '@app/query/common/remote-config/remote-config.query';
import { useCurrentNetwork } from '@app/store/networks/networks.selectors';
-import type { RuneBalance, RuneTickerInfo, RuneToken } from '../bitcoin-client';
import { useGetRunesOutputsByAddressQuery } from './runes-outputs-by-address.query';
import { useGetRunesTickerInfoQuery } from './runes-ticker-info.query';
import { useGetRunesWalletBalancesByAddressesQuery } from './runes-wallet-balances.query';
-const defaultRunesSymbol = '¤';
-
-function makeRuneToken(runeBalance: RuneBalance, tickerInfo: RuneTickerInfo): RuneToken {
- return {
- balance: createMoney(
- Number(runeBalance.total_balance),
- tickerInfo.rune_name,
- tickerInfo.decimals
- ),
- tokenData: {
- ...runeBalance,
- ...tickerInfo,
- symbol: tickerInfo.symbol ?? defaultRunesSymbol,
- },
- };
-}
-
export function useRunesEnabled() {
const runesEnabled = useConfigRunesEnabled();
const network = useCurrentNetwork();
@@ -38,17 +21,16 @@ export function useRuneTokens(addresses: string[]) {
.flatMap(query => query.data)
.filter(isDefined);
- const runesTickerInfo = useGetRunesTickerInfoQuery(runesBalances.map(r => r.rune_name))
- .flatMap(query => query.data)
- .filter(isDefined);
+ const results = useGetRunesTickerInfoQuery(runesBalances);
- return runesBalances
- .map(r => {
- const tickerInfo = runesTickerInfo.find(t => t.rune_name === r.rune_name);
- if (!tickerInfo) return;
- return makeRuneToken(r, tickerInfo);
- })
- .filter(isDefined);
+ return useMemo(() => {
+ // We can potentially use the 'combine' option in react-query v5 to replace this?
+ // https://tanstack.com/query/latest/docs/framework/react/reference/useQueries#combine
+ const isInitialLoading = results.some(query => query.isInitialLoading);
+ const runes = results.map(query => query.data).filter(isDefined);
+
+ return { isInitialLoading, runes };
+ }, [results]);
}
export function useRunesOutputsByAddress(address: string) {
diff --git a/src/app/query/bitcoin/runes/runes.utils.ts b/src/app/query/bitcoin/runes/runes.utils.ts
new file mode 100644
index 00000000000..7849c3bd300
--- /dev/null
+++ b/src/app/query/bitcoin/runes/runes.utils.ts
@@ -0,0 +1,29 @@
+import type { RuneCryptoAssetInfo } from '@leather-wallet/models';
+
+import { createMoney } from '@shared/models/money.model';
+
+import { createCryptoAssetBalance } from '@app/query/common/models';
+
+import type { RuneBalance, RuneTickerInfo } from '../bitcoin-client';
+
+const defaultRunesSymbol = '¤';
+
+function createRuneCryptoAssetInfo(tickerInfo: RuneTickerInfo): RuneCryptoAssetInfo {
+ return {
+ decimals: tickerInfo.decimals,
+ hasMemo: false,
+ name: 'rune',
+ runeName: tickerInfo.rune_name,
+ spacedRuneName: tickerInfo.spaced_rune_name,
+ symbol: tickerInfo.symbol ?? defaultRunesSymbol,
+ };
+}
+
+export function createRuneCryptoAssetDetails(runeBalance: RuneBalance, tickerInfo: RuneTickerInfo) {
+ return {
+ balance: createCryptoAssetBalance(
+ createMoney(Number(runeBalance.total_balance), tickerInfo.rune_name, tickerInfo.decimals)
+ ),
+ info: createRuneCryptoAssetInfo(tickerInfo),
+ };
+}
diff --git a/src/app/query/bitcoin/stamps/stamps-by-address.hooks.ts b/src/app/query/bitcoin/stamps/stamps-by-address.hooks.ts
index 1eeb7efca3b..aec9723c0d0 100644
--- a/src/app/query/bitcoin/stamps/stamps-by-address.hooks.ts
+++ b/src/app/query/bitcoin/stamps/stamps-by-address.hooks.ts
@@ -1,17 +1,36 @@
-import { useStampsByAddressQuery } from './stamps-by-address.query';
+import type { Src20CryptoAssetInfo } from '@leather-wallet/models';
+import BigNumber from 'bignumber.js';
+
+import { createMoney } from '@shared/models/money.model';
+
+import { createCryptoAssetBalance } from '@app/query/common/models';
+
+import { type Src20Token, useStampsByAddressQuery } from './stamps-by-address.query';
export function useStampsByAddress(address: string) {
return useStampsByAddressQuery(address, {
- select(data) {
- return data.data?.stamps;
- },
+ select: resp => resp.data?.stamps,
});
}
+function createSrc20CryptoAssetInfo(src20: Src20Token): Src20CryptoAssetInfo {
+ return {
+ decimals: 0,
+ hasMemo: false,
+ id: src20.id ?? '',
+ name: 'src-20',
+ symbol: src20.tick,
+ };
+}
+
export function useSrc20TokensByAddress(address: string) {
return useStampsByAddressQuery(address, {
- select(data) {
- return data.data?.src20;
- },
+ select: resp =>
+ resp.data.src20.map(token => ({
+ balance: createCryptoAssetBalance(
+ createMoney(new BigNumber(token.amt ?? 0), token.tick, 0)
+ ),
+ info: createSrc20CryptoAssetInfo(token),
+ })),
});
}
diff --git a/src/app/query/common/alex-sdk/alex-sdk.hooks.ts b/src/app/query/common/alex-sdk/alex-sdk.hooks.ts
index 6d3b95fcb92..d9c1f3ca3f4 100644
--- a/src/app/query/common/alex-sdk/alex-sdk.hooks.ts
+++ b/src/app/query/common/alex-sdk/alex-sdk.hooks.ts
@@ -12,7 +12,7 @@ import { sortAssetsByName } from '@app/common/asset-utils';
import { convertAmountToFractionalUnit } from '@app/common/money/calculate-money';
import { pullContractIdFromIdentity } from '@app/common/utils';
import { useCurrentStxAvailableUnlockedBalance } from '@app/query/stacks/balance/account-balance.hooks';
-import { useTransferableSip10CryptoAssetsWithDetails } from '@app/query/stacks/sip10/sip10-tokens.hooks';
+import { useTransferableSip10Tokens } from '@app/query/stacks/sip10/sip10-tokens.hooks';
import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
import { getAvatarFallback } from '@app/ui/components/avatar/avatar';
@@ -38,11 +38,12 @@ export function useAlexCurrencyPriceAsMarketData() {
const { data: prices } = useAlexSdkLatestPricesQuery();
return useCallback(
- (principal: string, symbol?: string) => {
+ (principal: string, symbol: string) => {
const tokenInfo = supportedCurrencies
.filter(isDefined)
.find(token => pullContractIdFromIdentity(token.contractAddress) === principal);
- if (!symbol || !prices || !tokenInfo) return null;
+ if (!prices || !tokenInfo)
+ return createMarketData(createMarketPair(symbol, 'USD'), createMoney(0, 'USD'));
const currency = tokenInfo.id as Currency;
const price = convertAmountToFractionalUnit(new BigNumber(prices[currency] ?? 0), 2);
return createMarketData(createMarketPair(symbol, 'USD'), createMoney(price, 'USD'));
@@ -56,7 +57,7 @@ function useCreateSwapAsset() {
const { data: prices } = useAlexSdkLatestPricesQuery();
const priceAsMarketData = useAlexCurrencyPriceAsMarketData();
const availableUnlockedBalance = useCurrentStxAvailableUnlockedBalance();
- const assets = useTransferableSip10CryptoAssetsWithDetails(address);
+ const sip10Tokens = useTransferableSip10Tokens(address);
return useCallback(
(tokenInfo?: TokenInfo): SwapAsset | undefined => {
@@ -68,8 +69,8 @@ function useCreateSwapAsset() {
const currency = tokenInfo.id as Currency;
const principal = pullContractIdFromIdentity(tokenInfo.contractAddress);
- const availableBalance = assets.find(a => a.info.contractId === principal)?.balance
- .availableBalance;
+ const availableBalance = sip10Tokens.find(token => token.info.contractId === principal)
+ ?.balance.availableBalance;
const swapAsset = {
currency,
@@ -96,7 +97,7 @@ function useCreateSwapAsset() {
: priceAsMarketData(principal, tokenInfo.name),
};
},
- [assets, availableUnlockedBalance, priceAsMarketData, prices]
+ [availableUnlockedBalance, priceAsMarketData, prices, sip10Tokens]
);
}
diff --git a/src/app/query/common/models.ts b/src/app/query/common/models.ts
new file mode 100644
index 00000000000..1a0b1224404
--- /dev/null
+++ b/src/app/query/common/models.ts
@@ -0,0 +1,6 @@
+import type { BaseCryptoAssetBalance, Money } from '@leather-wallet/models';
+
+// TODO: Move to models pkg
+export function createCryptoAssetBalance(balance: Money): BaseCryptoAssetBalance {
+ return { availableBalance: balance };
+}
diff --git a/src/app/query/models/crypto-asset.model.ts b/src/app/query/models/crypto-asset.model.ts
deleted file mode 100644
index ac2ecaa1389..00000000000
--- a/src/app/query/models/crypto-asset.model.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import type {
- BaseCryptoAssetInfo,
- Blockchains,
- Brc20CryptoAssetInfo,
- BtcCryptoAssetBalance,
- BtcCryptoAssetInfo,
- CryptoAssetBalance,
- CryptoAssetType,
- MarketData,
- Sip10CryptoAssetInfo,
- StxCryptoAssetBalance,
- StxCryptoAssetInfo,
-} from '@leather-wallet/models';
-
-interface AccountCryptoAssetDetails {
- balance: CryptoAssetBalance;
- chain: Blockchains;
- info: BaseCryptoAssetInfo;
- marketData: MarketData | null;
- type: CryptoAssetType;
-}
-
-export function createAccountCryptoAssetWithDetailsFactory(
- args: T
-): T {
- const { balance, chain, info, marketData, type } = args;
- return {
- balance,
- chain,
- info,
- marketData,
- type,
- } as T;
-}
-
-export interface BtcAccountCryptoAssetWithDetails extends AccountCryptoAssetDetails {
- balance: BtcCryptoAssetBalance;
- info: BtcCryptoAssetInfo;
-}
-
-export interface StxAccountCryptoAssetWithDetails extends AccountCryptoAssetDetails {
- balance: StxCryptoAssetBalance;
- info: StxCryptoAssetInfo;
-}
-
-export interface Brc20AccountCryptoAssetWithDetails extends AccountCryptoAssetDetails {
- holderAddress: string;
- info: Brc20CryptoAssetInfo;
-}
-
-export interface Sip10AccountCryptoAssetWithDetails extends AccountCryptoAssetDetails {
- info: Sip10CryptoAssetInfo;
-}
-
-export type AccountCryptoAssetWithDetails =
- | BtcAccountCryptoAssetWithDetails
- | StxAccountCryptoAssetWithDetails
- | Brc20AccountCryptoAssetWithDetails
- | Sip10AccountCryptoAssetWithDetails;
diff --git a/src/app/query/query-config.ts b/src/app/query/query-config.ts
index a5ede1495ab..ec1998617d7 100644
--- a/src/app/query/query-config.ts
+++ b/src/app/query/query-config.ts
@@ -1,4 +1,8 @@
-import { UseQueryOptions } from '@tanstack/react-query';
+import {
+ type QueryObserverSuccessResult,
+ UseQueryOptions,
+ type UseQueryResult,
+} from '@tanstack/react-query';
type AllowedReactQueryConfigOptions = keyof Pick<
UseQueryOptions,
@@ -9,3 +13,9 @@ export type AppUseQueryConfig = Pick<
UseQueryOptions,
AllowedReactQueryConfigOptions
>;
+
+export function isFetchedWithSuccess(
+ query: UseQueryResult
+): query is QueryObserverSuccessResult {
+ return !query.isError && !query.isLoading && query.data !== undefined;
+}
diff --git a/src/app/query/stacks/sip10/sip10-tokens.hooks.ts b/src/app/query/stacks/sip10/sip10-tokens.hooks.ts
index d50030f12e1..64f21e13b4a 100644
--- a/src/app/query/stacks/sip10/sip10-tokens.hooks.ts
+++ b/src/app/query/stacks/sip10/sip10-tokens.hooks.ts
@@ -1,124 +1,94 @@
import { useMemo } from 'react';
-import type { FtMetadataResponse } from '@hirosystems/token-metadata-api-client';
-import type { Sip10CryptoAssetInfo } from '@leather-wallet/models';
-import BigNumber from 'bignumber.js';
+import type { BaseCryptoAssetBalance, Sip10CryptoAssetInfo } from '@leather-wallet/models';
-import { createMoney } from '@shared/models/money.model';
import { isDefined, isUndefined } from '@shared/utils';
-import { getTicker, pullContractIdFromIdentity } from '@app/common/utils';
-import {
- useAlexCurrencyPriceAsMarketData,
- useAlexSwappableAssets,
-} from '@app/query/common/alex-sdk/alex-sdk.hooks';
-import {
- type AccountCryptoAssetWithDetails,
- type Sip10AccountCryptoAssetWithDetails,
- createAccountCryptoAssetWithDetailsFactory,
-} from '@app/query/models/crypto-asset.model';
+import { useAlexSwappableAssets } from '@app/query/common/alex-sdk/alex-sdk.hooks';
import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
-import { getAssetStringParts } from '@app/ui/utils/get-asset-string-parts';
import { useStacksAccountBalanceFungibleTokens } from '../balance/account-balance.hooks';
-import { useStacksAccountFungibleTokenMetadata } from '../token-metadata/fungible-tokens/fungible-token-metadata.hooks';
-import { isFtAsset } from '../token-metadata/token-metadata.utils';
import {
- type Sip10CryptoAssetFilter,
- filterSip10AccountCryptoAssetsWithDetails,
-} from './sip10-tokens.utils';
+ useGetFungibleTokensBalanceMetadataQuery,
+ useGetFungibleTokensMetadataQuery,
+} from '../token-metadata/fungible-tokens/fungible-token-metadata.query';
+import { type Sip10CryptoAssetFilter, filterSip10Tokens } from './sip10-tokens.utils';
-export function isTransferableSip10Token(asset: Partial) {
- return !isUndefined(asset.decimals) && !isUndefined(asset.name) && !isUndefined(asset.symbol);
+function useSip10TokensCryptoAssetBalance(address: string) {
+ const { data: tokens = {} } = useStacksAccountBalanceFungibleTokens(address);
+ return useGetFungibleTokensBalanceMetadataQuery(tokens);
}
-export function getSip10InfoFromAsset(asset: AccountCryptoAssetWithDetails) {
- if ('contractId' in asset.info) return asset.info;
- return;
+function useSip10TokensCryptoAssetInfo(address: string) {
+ const { data: tokens = {} } = useStacksAccountBalanceFungibleTokens(address);
+ return useGetFungibleTokensMetadataQuery(Object.keys(tokens));
}
-function createSip10CryptoAssetInfo(
- contractId: string,
- key: string,
- token: FtMetadataResponse
-): Sip10CryptoAssetInfo {
- const { assetName, contractName } = getAssetStringParts(key);
- const name = token.name ? token.name : assetName;
-
- return {
- canTransfer: isTransferableSip10Token(token),
- contractId,
- contractName,
- decimals: token.decimals ?? 0,
- hasMemo: isTransferableSip10Token(token),
- imageCanonicalUri: token.image_canonical_uri ?? '',
- name,
- symbol: token.symbol ?? getTicker(name),
- };
+export interface Sip10TokenAssetDetails {
+ balance: BaseCryptoAssetBalance;
+ info: Sip10CryptoAssetInfo;
}
-function useSip10AccountCryptoAssetsWithDetails(address: string) {
- const { data: tokens = {} } = useStacksAccountBalanceFungibleTokens(address);
- const tokenMetadata = useStacksAccountFungibleTokenMetadata(tokens);
- const priceAsMarketData = useAlexCurrencyPriceAsMarketData();
+function useSip10Tokens(address: string): {
+ isInitialLoading: boolean;
+ tokens: Sip10TokenAssetDetails[];
+} {
+ const balancesResults = useSip10TokensCryptoAssetBalance(address);
+ const infoResults = useSip10TokensCryptoAssetInfo(address);
- return useMemo(
- () =>
- Object.entries(tokens)
- .map(([key, value], i) => {
- const token = tokenMetadata[i].data;
- if (!(token && isFtAsset(token))) return;
- const contractId = pullContractIdFromIdentity(key);
+ return useMemo(() => {
+ // We can potentially use the 'combine' option in react-query v5 to replace this?
+ // https://tanstack.com/query/latest/docs/framework/react/reference/useQueries#combine
+ const isInitialLoading =
+ balancesResults.some(query => query.isInitialLoading) ||
+ infoResults.some(query => query.isInitialLoading);
+ const tokenBalances = balancesResults
+ .map(query => query.data)
+ .filter(isDefined)
+ .filter(token => token.balance.availableBalance.amount.isGreaterThan(0));
+ const tokenInfo = infoResults.map(query => query.data).filter(isDefined);
+ const tokens = tokenInfo.map(info => {
+ const tokenBalance = tokenBalances.find(
+ balance => balance.contractId === info.contractId
+ )?.balance;
+ if (isUndefined(tokenBalance)) return;
+ return {
+ balance: tokenBalance,
+ info,
+ };
+ });
- return createAccountCryptoAssetWithDetailsFactory({
- balance: {
- availableBalance: createMoney(
- new BigNumber(value.balance),
- token.symbol ?? getTicker(token.name ?? ''),
- token.decimals ?? 0
- ),
- },
- chain: 'stacks',
- info: createSip10CryptoAssetInfo(contractId, key, token),
- marketData: priceAsMarketData(contractId, token.symbol),
- type: 'sip-10',
- });
- })
- .filter(isDefined)
- .filter(asset => asset.balance.availableBalance.amount.isGreaterThan(0)),
- [priceAsMarketData, tokenMetadata, tokens]
- );
+ return {
+ isInitialLoading,
+ tokens: tokens.filter(isDefined),
+ };
+ }, [balancesResults, infoResults]);
}
-export function useSip10CryptoAssetWithDetails(contractId: string) {
+export function useSip10Token(contractId: string) {
const address = useCurrentStacksAccountAddress();
- const assets = useSip10AccountCryptoAssetsWithDetails(address);
+ const { tokens = [] } = useSip10Tokens(address);
return useMemo(
- () => assets.find(asset => asset.info.contractId === contractId),
- [assets, contractId]
+ () => tokens.find(token => token.info.contractId === contractId),
+ [contractId, tokens]
);
}
-interface UseFilteredSip10AccountCryptoAssetsWithDetailsArgs {
+interface UseSip10TokensArgs {
address: string;
filter?: Sip10CryptoAssetFilter;
}
-export function useFilteredSip10AccountCryptoAssetsWithDetails({
- address,
- filter = 'all',
-}: UseFilteredSip10AccountCryptoAssetsWithDetailsArgs) {
- const assets = useSip10AccountCryptoAssetsWithDetails(address);
+export function useFilteredSip10Tokens({ address, filter = 'all' }: UseSip10TokensArgs) {
+ const { isInitialLoading, tokens = [] } = useSip10Tokens(address);
const { data: swapAssets = [] } = useAlexSwappableAssets();
-
- return useMemo(
- () => filterSip10AccountCryptoAssetsWithDetails(assets, swapAssets, filter),
- [assets, swapAssets, filter]
+ const filteredTokens = useMemo(
+ () => filterSip10Tokens(swapAssets, tokens, filter),
+ [swapAssets, tokens, filter]
);
+ return { isInitialLoading, tokens: filteredTokens };
}
-export function useTransferableSip10CryptoAssetsWithDetails(
- address: string
-): Sip10AccountCryptoAssetWithDetails[] {
- const assets = useSip10AccountCryptoAssetsWithDetails(address);
- return useMemo(() => assets.filter(asset => asset.info.canTransfer), [assets]);
+export function useTransferableSip10Tokens(address: string) {
+ const { tokens = [] } = useSip10Tokens(address);
+ return useMemo(() => tokens.filter(token => token.info.canTransfer), [tokens]);
}
diff --git a/src/app/query/stacks/sip10/sip10-tokens.spec.ts b/src/app/query/stacks/sip10/sip10-tokens.spec.ts
index 6e92dd062b2..e62c56ecff6 100644
--- a/src/app/query/stacks/sip10/sip10-tokens.spec.ts
+++ b/src/app/query/stacks/sip10/sip10-tokens.spec.ts
@@ -1,6 +1,6 @@
import type { FtMetadataResponse } from '@hirosystems/token-metadata-api-client';
-import { isTransferableSip10Token } from './sip10-tokens.hooks';
+import { isTransferableSip10Token } from './sip10-tokens.utils';
describe(isTransferableSip10Token.name, () => {
test('assets with a name, symbol and decimals are allowed to be transferred', () => {
diff --git a/src/app/query/stacks/sip10/sip10-tokens.utils.ts b/src/app/query/stacks/sip10/sip10-tokens.utils.ts
index 015f5154f60..96058a1fb18 100644
--- a/src/app/query/stacks/sip10/sip10-tokens.utils.ts
+++ b/src/app/query/stacks/sip10/sip10-tokens.utils.ts
@@ -1,22 +1,53 @@
+import type { FtMetadataResponse } from '@hirosystems/token-metadata-api-client';
+import type { CryptoAssetBalance, Sip10CryptoAssetInfo } from '@leather-wallet/models';
+
+import { isUndefined } from '@shared/utils';
+
+import { getTicker } from '@app/common/utils';
import type { SwapAsset } from '@app/query/common/alex-sdk/alex-sdk.hooks';
-import type { Sip10AccountCryptoAssetWithDetails } from '@app/query/models/crypto-asset.model';
import { getAssetStringParts } from '@app/ui/utils/get-asset-string-parts';
+export function isTransferableSip10Token(token: Partial) {
+ return !isUndefined(token.decimals) && !isUndefined(token.name) && !isUndefined(token.symbol);
+}
+
+export function createSip10CryptoAssetInfo(
+ contractId: string,
+ key: string,
+ ftAsset: FtMetadataResponse
+): Sip10CryptoAssetInfo {
+ const { assetName, contractName } = getAssetStringParts(key);
+ const name = ftAsset.name ? ftAsset.name : assetName;
+
+ return {
+ canTransfer: isTransferableSip10Token(ftAsset),
+ contractId,
+ contractName,
+ decimals: ftAsset.decimals ?? 0,
+ hasMemo: isTransferableSip10Token(ftAsset),
+ imageCanonicalUri: ftAsset.image_canonical_uri ?? '',
+ name,
+ symbol: ftAsset.symbol ?? getTicker(name),
+ };
+}
+
export type Sip10CryptoAssetFilter = 'all' | 'supported' | 'unsupported';
-export function filterSip10AccountCryptoAssetsWithDetails(
- assets: Sip10AccountCryptoAssetWithDetails[],
+export function filterSip10Tokens(
swapAssets: SwapAsset[],
+ tokens: {
+ balance: CryptoAssetBalance;
+ info: Sip10CryptoAssetInfo;
+ }[],
filter: Sip10CryptoAssetFilter
) {
- return assets.filter(asset => {
- const { address: contractAddress } = getAssetStringParts(asset.info.contractId);
+ return tokens.filter(token => {
if (filter === 'supported') {
- return swapAssets.some(swapAsset => swapAsset.principal.includes(contractAddress));
+ return swapAssets.some(swapAsset => swapAsset.principal === token.info.contractId);
}
if (filter === 'unsupported') {
- return !swapAssets.some(swapAsset => swapAsset.principal.includes(contractAddress));
+ return !swapAssets.some(swapAsset => swapAsset.principal === token.info.contractId);
}
- return assets;
+ return true;
});
}
diff --git a/src/app/query/stacks/stacks-client.ts b/src/app/query/stacks/stacks-client.ts
index a06231c957f..802a6d2ce3f 100644
--- a/src/app/query/stacks/stacks-client.ts
+++ b/src/app/query/stacks/stacks-client.ts
@@ -18,7 +18,6 @@ import {
import axios from 'axios';
import { STX20_API_BASE_URL_MAINNET } from '@shared/constants';
-import type { Money } from '@shared/models/money.model';
export interface Stx20Balance {
ticker: string;
@@ -31,12 +30,6 @@ interface Stx20BalanceResponse {
balances: Stx20Balance[];
}
-export interface Stx20Token {
- balance: Money;
- marketData: null;
- tokenData: Stx20Balance;
-}
-
class Stx20Api {
url = STX20_API_BASE_URL_MAINNET;
diff --git a/src/app/query/stacks/stx/stx-crypto-asset.hooks.ts b/src/app/query/stacks/stx/stx-crypto-asset.hooks.ts
deleted file mode 100644
index 6c89c853988..00000000000
--- a/src/app/query/stacks/stx/stx-crypto-asset.hooks.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import type { StxCryptoAssetInfo } from '@leather-wallet/models';
-
-import { STX_DECIMALS } from '@shared/constants';
-import { createMoney } from '@shared/models/money.model';
-import { isUndefined } from '@shared/utils';
-
-import { useCryptoCurrencyMarketDataMeanAverage } from '@app/query/common/market-data/market-data.hooks';
-import {
- type StxAccountCryptoAssetWithDetails,
- createAccountCryptoAssetWithDetailsFactory,
-} from '@app/query/models/crypto-asset.model';
-
-import { useStxCryptoAssetBalance } from '../balance/account-balance.hooks';
-
-const stxCryptoAssetInfo: StxCryptoAssetInfo = {
- decimals: STX_DECIMALS,
- hasMemo: true,
- name: 'stacks',
- symbol: 'STX',
-};
-
-const stxCryptoAssetBalancePlaceholder = {
- availableBalance: createMoney(0, 'STX'),
- availableUnlockedBalance: createMoney(0, 'STX'),
- inboundBalance: createMoney(0, 'STX'),
- lockedBalance: createMoney(0, 'STX'),
- outboundBalance: createMoney(0, 'STX'),
- pendingBalance: createMoney(0, 'STX'),
- totalBalance: createMoney(0, 'STX'),
- unlockedBalance: createMoney(0, 'STX'),
-};
-
-export const stxCryptoAssetPlaceholder =
- createAccountCryptoAssetWithDetailsFactory({
- balance: stxCryptoAssetBalancePlaceholder,
- chain: 'stacks',
- info: stxCryptoAssetInfo,
- marketData: null,
- type: 'stx',
- });
-
-export function useStxAccountCryptoAssetWithDetails(address: string) {
- const { data: balance, isInitialLoading } = useStxCryptoAssetBalance(address);
- const marketData = useCryptoCurrencyMarketDataMeanAverage('STX');
-
- if (isUndefined(balance)) return { asset: stxCryptoAssetPlaceholder, isInitialLoading };
-
- return {
- asset: createAccountCryptoAssetWithDetailsFactory({
- balance,
- chain: 'stacks',
- info: stxCryptoAssetInfo,
- marketData,
- type: 'stx',
- }),
- isInitialLoading,
- };
-}
diff --git a/src/app/query/stacks/stx20/stx20-tokens.hooks.ts b/src/app/query/stacks/stx20/stx20-tokens.hooks.ts
index ce3231a5b8b..7f054caabb5 100644
--- a/src/app/query/stacks/stx20/stx20-tokens.hooks.ts
+++ b/src/app/query/stacks/stx20/stx20-tokens.hooks.ts
@@ -1,20 +1,28 @@
+import type { Stx20CryptoAssetInfo } from '@leather-wallet/models';
import BigNumber from 'bignumber.js';
import { createMoney } from '@shared/models/money.model';
-import type { Stx20Balance, Stx20Token } from '../stacks-client';
+import { createCryptoAssetBalance } from '@app/query/common/models';
+
+import type { Stx20Balance } from '../stacks-client';
import { useStx20BalancesQuery } from './stx20-tokens.query';
-function createStx20Token(token: Stx20Balance): Stx20Token {
+function createStx20CryptoAssetInfo(stx20Balance: Stx20Balance): Stx20CryptoAssetInfo {
return {
- balance: createMoney(new BigNumber(token.balance), token.ticker, 0),
- marketData: null,
- tokenData: token,
+ name: 'stx-20',
+ symbol: stx20Balance.ticker,
};
}
export function useStx20Tokens(address: string) {
return useStx20BalancesQuery(address, {
- select: resp => resp.map(balance => createStx20Token(balance)),
+ select: resp =>
+ resp.map(stx20Balance => ({
+ balance: createCryptoAssetBalance(
+ createMoney(new BigNumber(stx20Balance.balance), stx20Balance.ticker, 0)
+ ),
+ info: createStx20CryptoAssetInfo(stx20Balance),
+ })),
});
}
diff --git a/src/app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.hooks.ts b/src/app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.hooks.ts
deleted file mode 100644
index ee8966d866c..00000000000
--- a/src/app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.hooks.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { AddressBalanceResponse } from '@shared/models/account.model';
-
-import { pullContractIdFromIdentity } from '@app/common/utils';
-
-import { useGetFungibleTokenMetadataListQuery } from './fungible-token-metadata.query';
-
-export function useStacksAccountFungibleTokenMetadata(
- tokens: AddressBalanceResponse['fungible_tokens']
-) {
- const tokenContractIds = Object.keys(tokens).map(key => pullContractIdFromIdentity(key));
- return useGetFungibleTokenMetadataListQuery(tokenContractIds);
-}
diff --git a/src/app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.query.ts b/src/app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.query.ts
index 8bdaa6e8b7a..eaa4855f5d4 100644
--- a/src/app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.query.ts
+++ b/src/app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.query.ts
@@ -1,12 +1,19 @@
-import { UseQueryResult, useQueries, useQuery } from '@tanstack/react-query';
+import { useQueries, useQuery } from '@tanstack/react-query';
+import BigNumber from 'bignumber.js';
import PQueue from 'p-queue';
+import type { AddressBalanceResponse } from '@shared/models/account.model';
+import { createMoney } from '@shared/models/money.model';
+
+import { getTicker, pullContractIdFromIdentity } from '@app/common/utils';
+import { createCryptoAssetBalance } from '@app/query/common/models';
import { useStacksClient } from '@app/store/common/api-clients.hooks';
import { useCurrentNetworkState } from '@app/store/networks/networks.hooks';
import { useHiroApiRateLimiter } from '../../hiro-rate-limiter';
+import { createSip10CryptoAssetInfo } from '../../sip10/sip10-tokens.utils';
import type { StacksClient } from '../../stacks-client';
-import { FtAssetResponse } from '../token-metadata.utils';
+import { FtAssetResponse, isFtAsset } from '../token-metadata.utils';
const staleTime = 12 * 60 * 60 * 1000;
@@ -25,13 +32,11 @@ function fetchFungibleTokenMetadata(client: StacksClient, limiter: PQueue) {
return (principal: string) => async () => {
return limiter.add(() => client.tokensApi.getFtMetadata(principal), {
throwOnTimeout: true,
- });
+ }) as unknown as FtAssetResponse;
};
}
-export function useGetFungibleTokenMetadataQuery(
- principal: string
-): UseQueryResult {
+export function useGetFungibleTokenMetadataQuery(principal: string) {
const client = useStacksClient();
const network = useCurrentNetworkState();
const limiter = useHiroApiRateLimiter();
@@ -43,18 +48,54 @@ export function useGetFungibleTokenMetadataQuery(
});
}
-export function useGetFungibleTokenMetadataListQuery(
- principals: string[]
-): UseQueryResult[] {
+export function useGetFungibleTokensBalanceMetadataQuery(
+ ftBalances: AddressBalanceResponse['fungible_tokens']
+) {
+ const client = useStacksClient();
+ const network = useCurrentNetworkState();
+ const limiter = useHiroApiRateLimiter();
+
+ return useQueries({
+ queries: Object.entries(ftBalances).map(([key, value]) => {
+ const contractId = pullContractIdFromIdentity(key);
+ return {
+ enabled: !!contractId,
+ queryKey: ['get-ft-metadata', contractId, network.chain.stacks.url],
+ queryFn: fetchFungibleTokenMetadata(client, limiter)(contractId),
+ select: (resp: FtAssetResponse) => {
+ if (!(resp && isFtAsset(resp))) return;
+ const symbol = resp.symbol ?? getTicker(resp.name ?? '');
+ return {
+ contractId,
+ balance: createCryptoAssetBalance(
+ createMoney(new BigNumber(value.balance), symbol, resp.decimals ?? 0)
+ ),
+ };
+ },
+ ...queryOptions,
+ };
+ }),
+ });
+}
+
+export function useGetFungibleTokensMetadataQuery(keys: string[]) {
const client = useStacksClient();
const network = useCurrentNetworkState();
const limiter = useHiroApiRateLimiter();
return useQueries({
- queries: principals.map(principal => ({
- queryKey: ['get-ft-metadata', principal, network.chain.stacks.url],
- queryFn: fetchFungibleTokenMetadata(client, limiter)(principal),
- ...queryOptions,
- })),
+ queries: keys.map(key => {
+ const contractId = pullContractIdFromIdentity(key);
+ return {
+ enabled: !!contractId,
+ queryKey: ['get-ft-metadata', contractId, network.chain.stacks.url],
+ queryFn: fetchFungibleTokenMetadata(client, limiter)(contractId),
+ select: (resp: FtAssetResponse) => {
+ if (!(resp && isFtAsset(resp))) return;
+ return createSip10CryptoAssetInfo(contractId, key, resp);
+ },
+ ...queryOptions,
+ };
+ }),
});
}
diff --git a/src/app/store/transactions/token-transfer.hooks.ts b/src/app/store/transactions/token-transfer.hooks.ts
index e60c6138d6b..9eff1ed34c6 100644
--- a/src/app/store/transactions/token-transfer.hooks.ts
+++ b/src/app/store/transactions/token-transfer.hooks.ts
@@ -79,7 +79,7 @@ export function useStxTokenTransferUnsignedTxState(values?: StacksSendFormValues
return tx.result;
}
-export function useGenerateFtTokenTransferUnsignedTx(assetInfo: Sip10CryptoAssetInfo) {
+export function useGenerateFtTokenTransferUnsignedTx(info: Sip10CryptoAssetInfo) {
const { data: nextNonce } = useNextNonce();
const account = useCurrentStacksAccount();
const network = useCurrentStacksNetworkState();
@@ -99,12 +99,12 @@ export function useGenerateFtTokenTransferUnsignedTx(assetInfo: Sip10CryptoAsset
? someCV(bufferCVFromString(values.memo || ''))
: noneCV();
- const amountAsFractionalUnit = ftUnshiftDecimals(amount, assetInfo.decimals || 0);
+ const amountAsFractionalUnit = ftUnshiftDecimals(amount, info.decimals || 0);
const {
address: contractAddress,
contractName,
assetName,
- } = getAssetStringParts(assetInfo.contractId);
+ } = getAssetStringParts(info.contractId);
const postConditionOptions = {
amount: amountAsFractionalUnit,
@@ -123,7 +123,7 @@ export function useGenerateFtTokenTransferUnsignedTx(assetInfo: Sip10CryptoAsset
standardPrincipalCVFromAddress(recipient),
];
- if (assetInfo.hasMemo) {
+ if (info.hasMemo) {
functionArgs.push(memo);
}
@@ -146,20 +146,13 @@ export function useGenerateFtTokenTransferUnsignedTx(assetInfo: Sip10CryptoAsset
return generateUnsignedTransaction(options);
},
- [
- account,
- assetInfo.contractId,
- assetInfo.decimals,
- assetInfo.hasMemo,
- network,
- nextNonce?.nonce,
- ]
+ [account, info.contractId, info.decimals, info.hasMemo, network, nextNonce?.nonce]
);
}
-export function useFtTokenTransferUnsignedTx(assetInfo: Sip10CryptoAssetInfo) {
+export function useFtTokenTransferUnsignedTx(info: Sip10CryptoAssetInfo) {
const account = useCurrentStacksAccount();
- const generateTx = useGenerateFtTokenTransferUnsignedTx(assetInfo);
+ const generateTx = useGenerateFtTokenTransferUnsignedTx(info);
const tx = useAsync(async () => generateTx(), [account]);
return tx.result;
diff --git a/src/app/ui/components/bullet-separator/bullet-separator.tsx b/src/app/ui/components/bullet-separator/bullet-separator.tsx
index a403ed1e79e..87e214737ea 100644
--- a/src/app/ui/components/bullet-separator/bullet-separator.tsx
+++ b/src/app/ui/components/bullet-separator/bullet-separator.tsx
@@ -2,7 +2,7 @@ import { cloneElement, isValidElement } from 'react';
import { Circle, CircleProps } from 'leather-styles/jsx';
-export function BulletOperator(props: CircleProps) {
+function BulletOperator(props: CircleProps) {
return (
= {
export default meta;
type Story = StoryObj;
-function ExampleInteractiveItemContent() {
+function ExamplePressableContent() {
return (
{},
- children: ,
+ children: ,
},
};
@@ -105,7 +105,7 @@ export const AlignmentExample: Story = {
],
args: {
onClick: () => {},
- children: ,
+ children: ,
},
};
@@ -127,6 +127,6 @@ export const WithPadding: Story = {
],
args: {
onClick: () => {},
- children: ,
+ children: ,
},
};
diff --git a/tests/page-object-models/home.page.ts b/tests/page-object-models/home.page.ts
index 8854dac5bc7..587d29a556d 100644
--- a/tests/page-object-models/home.page.ts
+++ b/tests/page-object-models/home.page.ts
@@ -96,7 +96,7 @@ export class HomePage {
return displayerAddress.replaceAll('\n', '');
}
- async selectTestNet() {
+ async selectTestnet() {
await this.page.getByTestId(SettingsSelectors.SettingsMenuBtn).click();
await this.page.getByTestId(SettingsSelectors.ChangeNetworkAction).click();
await this.page
diff --git a/tests/specs/send/send-btc.spec.ts b/tests/specs/send/send-btc.spec.ts
index 4e40a7371b4..b40a4b91631 100644
--- a/tests/specs/send/send-btc.spec.ts
+++ b/tests/specs/send/send-btc.spec.ts
@@ -12,7 +12,7 @@ test.describe('send btc', () => {
test.beforeEach(async ({ extensionId, globalPage, homePage, onboardingPage, sendPage }) => {
await globalPage.setupAndUseApiCalls(extensionId);
await onboardingPage.signInWithTestAccount(extensionId);
- await homePage.selectTestNet();
+ await homePage.selectTestnet();
await homePage.sendButton.click();
await sendPage.selectBtcAndGoToSendForm();
await sendPage.waitForSendPageReady();
@@ -29,6 +29,7 @@ test.describe('send btc', () => {
const details = await sendPage.confirmationDetails.allInnerTexts();
test.expect(details).toBeTruthy();
});
+
test('that recipient input is trimmed correctly', async ({ sendPage }) => {
await sendPage.amountInput.fill('0.00006');
await sendPage.recipientInput.fill(' ' + TEST_TESTNET_ACCOUNT_2_BTC_ADDRESS + ' ');
diff --git a/tests/specs/send/send-stx.spec.ts b/tests/specs/send/send-stx.spec.ts
index 4a019d31904..df68f306bad 100644
--- a/tests/specs/send/send-stx.spec.ts
+++ b/tests/specs/send/send-stx.spec.ts
@@ -18,7 +18,7 @@ test.describe('send stx: tests on testnet', () => {
test.beforeEach(async ({ extensionId, globalPage, homePage, onboardingPage, sendPage }) => {
await globalPage.setupAndUseApiCalls(extensionId);
await onboardingPage.signInWithTestAccount(extensionId);
- await homePage.selectTestNet();
+ await homePage.selectTestnet();
await homePage.sendButton.click();
await sendPage.selectStxAndGoToSendForm();
});