Skip to content

Commit

Permalink
feat: add src-20 token balances, closes #3751
Browse files Browse the repository at this point in the history
  • Loading branch information
fbwoolf committed Apr 4, 2024
1 parent 5c1c284 commit 087e017
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,9 @@ interface Brc20TokenAssetItemLayoutProps {
token: Brc20Token;
onClick?(): void;
}
export function Brc20TokenAssetItemLayout({
onClick,

token,
}: Brc20TokenAssetItemLayoutProps) {
const balance = createMoney(Number(token.overall_balance), token.ticker, 0);
const formattedBalance = formatBalance(balance.amount.toString());
export function Brc20TokenAssetItemLayout({ onClick, token }: Brc20TokenAssetItemLayoutProps) {
const balance = createMoney(Number(token.overall_balance), token.ticker, 0).amount.toString();
const formattedBalance = formatBalance(balance);

return (
<Pressable onClick={onClick} my="space.02">
Expand All @@ -30,7 +26,7 @@ export function Brc20TokenAssetItemLayout({
titleRight={
<BasicTooltip
asChild
label={formattedBalance.isAbbreviated ? balance.amount.toString() : undefined}
label={formattedBalance.isAbbreviated ? balance : undefined}
side="left"
>
<styled.span data-testid={token.ticker} textStyle="label.02">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,25 @@ import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors';
import { Stack } from 'leather-styles/jsx';

import { RouteUrls } from '@shared/route-urls';
import { noop } from '@shared/utils';

import { useNativeSegwitBalance } from '@app/query/bitcoin/balance/btc-native-segwit-balance.hooks';
import { Brc20Token } from '@app/query/bitcoin/bitcoin-client';
import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';

import { Brc20TokenAssetItemLayout } from './components/brc20-token-asset-item.layout';
import { Brc20TokenAssetItemLayout } from './brc20-token-asset-item.layout';

export function Brc20TokenAssetList(props: { brc20Tokens?: Brc20Token[] }) {
interface Brc20TokenAssetListProps {
brc20Tokens?: Brc20Token[];
variant?: string;
}
export function Brc20TokenAssetList({ brc20Tokens = [], variant }: Brc20TokenAssetListProps) {
const navigate = useNavigate();
const currentAccountBtcAddress = useCurrentAccountNativeSegwitAddressIndexZero();
const { btcBalance: btcCryptoCurrencyAssetBalance } =
useNativeSegwitBalance(currentAccountBtcAddress);

const hasPositiveBtcBalanceForFees =
btcCryptoCurrencyAssetBalance.balance.amount.isGreaterThan(0);
variant === 'send' && btcCryptoCurrencyAssetBalance.balance.amount.isGreaterThan(0);

function navigateToBrc20SendForm(token: Brc20Token) {
const { ticker, available_balance, decimals, holderAddress } = token;
Expand All @@ -28,15 +31,13 @@ export function Brc20TokenAssetList(props: { brc20Tokens?: Brc20Token[] }) {
});
}

if (!props.brc20Tokens?.length) return null;

return (
<Stack data-testid={CryptoAssetSelectors.CryptoAssetList}>
{props.brc20Tokens?.map(token => (
{brc20Tokens.map(token => (
<Brc20TokenAssetItemLayout
key={token.ticker}
token={token}
onClick={hasPositiveBtcBalanceForFees ? () => navigateToBrc20SendForm(token) : noop}
onClick={hasPositiveBtcBalanceForFees ? () => navigateToBrc20SendForm(token) : undefined}
/>
))}
</Stack>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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 (
<Pressable my="space.02">
<ItemLayout
flagImg={<Src20AvatarIcon />}
titleLeft={token.tick.toUpperCase()}
captionLeft="SRC-20"
titleRight={
<BasicTooltip
asChild
label={formattedBalance.isAbbreviated ? balance : undefined}
side="left"
>
<styled.span data-testid={token.tick} fontWeight={500} textStyle="label.02">
{formattedBalance.value}
</styled.span>
</BasicTooltip>
}
/>
</Pressable>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Src20Token } from '@app/query/bitcoin/stamps/stamps-by-address.query';

import { Src20TokenAssetItemLayout } from './src20-token-asset-item.layout';

interface Src20TokenAssetListProps {
src20Tokens: Src20Token[];
}
export function Src20TokenAssetList({ src20Tokens = [] }: Src20TokenAssetListProps) {
return src20Tokens.map(token => <Src20TokenAssetItemLayout key={token.id} token={token} />);
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function CryptoAssetList({
<BitcoinNativeSegwitAccountLoader current>
{() => (
<Brc20TokensLoader>
{brc20Tokens => <Brc20TokenAssetList brc20Tokens={brc20Tokens} />}
{brc20Tokens => <Brc20TokenAssetList brc20Tokens={brc20Tokens} variant="send" />}
</Brc20TokensLoader>
)}
</BitcoinNativeSegwitAccountLoader>
Expand Down
16 changes: 16 additions & 0 deletions src/app/components/src20-tokens-loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { ReactNode } from 'react';

import { useSrc20TokensByAddress } from '@app/query/bitcoin/stamps/stamps-by-address.hooks';
import type { Src20Token } from '@app/query/bitcoin/stamps/stamps-by-address.query';

interface Src20TokensLoaderProps {
address: string;
children(src20Tokens: Src20Token[]): ReactNode;
}

export function Src20TokensLoader({ address, children }: Src20TokensLoaderProps) {
const { data: src20Tokens } = useSrc20TokensByAddress(address);

if (!src20Tokens) return null;
return children(src20Tokens);
}
7 changes: 1 addition & 6 deletions src/app/features/asset-list/asset-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { Stack } from 'leather-styles/jsx';
import { useBtcAssetBalance } from '@app/common/hooks/balance/btc/use-btc-balance';
import { useWalletType } from '@app/common/use-wallet-type';
import { BitcoinContractEntryPoint } from '@app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point';
import { Brc20TokensLoader } from '@app/components/brc20-tokens-loader';
import { CryptoCurrencyAssetItemLayout } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout';
import { CurrentStacksAccountLoader } from '@app/components/loaders/stacks-account-loader';
import { useHasBitcoinLedgerKeychain } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger';
Expand Down Expand Up @@ -75,11 +74,7 @@ export function AssetsList() {
</CurrentStacksAccountLoader>

{whenWallet({
software: (
<Brc20TokensLoader>
{brc20Tokens => <BitcoinFungibleTokenAssetList brc20Tokens={brc20Tokens} />}
</Brc20TokensLoader>
),
software: <BitcoinFungibleTokenAssetList btcAddress={btcAddress} />,
ledger: null,
})}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { Stack } from 'leather-styles/jsx';

import { Brc20TokenAssetItemLayout } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout';
import { Brc20Token } from '@app/query/bitcoin/bitcoin-client';
import { Brc20TokensLoader } from '@app/components/brc20-tokens-loader';
import { Brc20TokenAssetList } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list';
import { Src20TokenAssetList } from '@app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list';
import { Src20TokensLoader } from '@app/components/src20-tokens-loader';

interface BitcoinFungibleTokenAssetListProps {
brc20Tokens?: Brc20Token[];
btcAddress: string;
}
export function BitcoinFungibleTokenAssetList({ brc20Tokens }: BitcoinFungibleTokenAssetListProps) {
if (!brc20Tokens) return null;

export function BitcoinFungibleTokenAssetList({ btcAddress }: BitcoinFungibleTokenAssetListProps) {
return (
<Stack gap="space.05">
{brc20Tokens.map(token => (
<Brc20TokenAssetItemLayout key={token.ticker} token={token} />
))}
</Stack>
<>
<Brc20TokensLoader>
{brc20Tokens => <Brc20TokenAssetList brc20Tokens={brc20Tokens} />}
</Brc20TokensLoader>
<Src20TokensLoader address={btcAddress}>
{src20Tokens => <Src20TokenAssetList src20Tokens={src20Tokens} />}
</Src20TokensLoader>
</>
);
}
4 changes: 2 additions & 2 deletions src/app/features/collectibles/components/bitcoin/stamps.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { useEffect } from 'react';

import { useAnalytics } from '@app/common/hooks/analytics/use-analytics';
import { useStampsByAddressQuery } from '@app/query/bitcoin/stamps/stamps-by-address.query';
import { useStampsByAddress } from '@app/query/bitcoin/stamps/stamps-by-address.hooks';
import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';

import { Stamp } from './stamp';

export function Stamps() {
const currentAccountBtcAddress = useCurrentAccountNativeSegwitAddressIndexZero();
const { data: stamps = [] } = useStampsByAddressQuery(currentAccountBtcAddress);
const { data: stamps = [] } = useStampsByAddress(currentAccountBtcAddress);
const analytics = useAnalytics();

useEffect(() => {
Expand Down
17 changes: 17 additions & 0 deletions src/app/query/bitcoin/stamps/stamps-by-address.hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useStampsByAddressQuery } from './stamps-by-address.query';

export function useStampsByAddress(address: string) {
return useStampsByAddressQuery(address, {
select(data) {
return data.data.stamps;
},
});
}

export function useSrc20TokensByAddress(address: string) {
return useStampsByAddressQuery(address, {
select(data) {
return data.data.src20;
},
});
}
8 changes: 4 additions & 4 deletions src/app/query/bitcoin/stamps/stamps-by-address.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface Stamp {
file_hash: string;
}

interface Src20 {
export interface Src20Token {
id: string;
address: string;
cpid: string;
Expand All @@ -57,18 +57,18 @@ interface StampsByAddressQueryResponse {
};
data: {
stamps: Stamp[];
src20: Src20[];
src20: Src20Token[];
};
}

/**
* @see https://stampchain.io/docs#/default/get_api_v2_balance__address_
*/
async function fetchStampsByAddress(address: string): Promise<Stamp[]> {
async function fetchStampsByAddress(address: string): Promise<StampsByAddressQueryResponse> {
const resp = await axios.get<StampsByAddressQueryResponse>(
`https://stampchain.io/api/v2/balance/${address}`
);
return resp.data.data.stamps;
return resp.data;
}

type FetchStampsByAddressResp = Awaited<ReturnType<typeof fetchStampsByAddress>>;
Expand Down

0 comments on commit 087e017

Please sign in to comment.