From e23c1a116867fa869f43bd8b6ae20f539e86b31b Mon Sep 17 00:00:00 2001 From: Fara Woolf Date: Mon, 29 Apr 2024 19:01:28 -0500 Subject: [PATCH 1/2] feat: stx-20 balances, closes #5077 --- public/assets/avatars/stx20-avatar-icon.png | Bin 0 -> 19817 bytes .../src20-token-asset-list.tsx | 6 +-- .../stx20-token-asset-item.layout.tsx | 37 ++++++++++++++++++ .../stx20-token-asset-list.tsx | 12 ++++++ .../loaders/brc20-tokens-loader.tsx | 6 +-- .../loaders/src20-tokens-loader.tsx | 6 +-- .../loaders/stx20-tokens-loader.tsx | 11 ++++++ src/app/features/asset-list/asset-list.tsx | 27 ++++++++++--- .../bitcoin-fungible-tokens-asset-list.tsx | 29 -------------- src/app/query/bitcoin/bitcoin-client.ts | 10 ++--- .../ordinals/brc20/brc20-tokens.hooks.ts | 5 ++- .../ordinals/brc20/brc20-tokens.query.ts | 4 +- ...ils.spec.ts => brc20-tokens.utils.spec.ts} | 2 +- ...{brc-20.utils.ts => brc20-tokens.utils.ts} | 0 .../runes/runes-outputs-by-address.query.ts | 2 +- .../bitcoin/runes/runes-ticker-info.query.ts | 2 +- .../runes/runes-wallet-balances.query.ts | 2 +- .../bitcoin/transaction/use-check-utxos.ts | 4 +- src/app/query/stacks/stacks-client.ts | 37 +++++++++++++++++- .../query/stacks/stx20/stx20-tokens.hooks.ts | 20 ++++++++++ .../query/stacks/stx20/stx20-tokens.query.ts | 23 +++++++++++ src/app/query/stacks/token-metadata-client.ts | 15 ------- .../fungible-token-metadata.query.ts | 10 ++--- .../non-fungible-token-metadata.query.ts | 4 +- src/app/query/stacks/utils.ts | 15 ------- src/app/store/common/api-clients.hooks.ts | 27 +++---------- .../components/avatar/stx20-avatar-icon.tsx | 14 +++++++ src/shared/constants.ts | 2 + 28 files changed, 215 insertions(+), 117 deletions(-) create mode 100644 public/assets/avatars/stx20-avatar-icon.png create mode 100644 src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-item.layout.tsx create mode 100644 src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx create mode 100644 src/app/components/loaders/stx20-tokens-loader.tsx delete mode 100644 src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx rename src/app/query/bitcoin/ordinals/brc20/{brc-20.utils.spec.ts => brc20-tokens.utils.spec.ts} (86%) rename src/app/query/bitcoin/ordinals/brc20/{brc-20.utils.ts => brc20-tokens.utils.ts} (100%) create mode 100644 src/app/query/stacks/stx20/stx20-tokens.hooks.ts create mode 100644 src/app/query/stacks/stx20/stx20-tokens.query.ts delete mode 100644 src/app/query/stacks/token-metadata-client.ts delete mode 100644 src/app/query/stacks/utils.ts create mode 100644 src/app/ui/components/avatar/stx20-avatar-icon.tsx diff --git a/public/assets/avatars/stx20-avatar-icon.png b/public/assets/avatars/stx20-avatar-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7a686f283d518388bd295e1f746c48a94f0934e7 GIT binary patch literal 19817 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
ccco* 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 U3Vg5KO#lD@07*qoM6N<$f_(*MyZ`_I literal 0 HcmV?d00001 diff --git a/src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx b/src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx index 022aec7a158..706537735a8 100644 --- a/src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx +++ b/src/app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list.tsx @@ -3,10 +3,10 @@ import type { Src20Token } from '@app/query/bitcoin/stamps/stamps-by-address.que import { Src20TokenAssetItemLayout } from './src20-token-asset-item.layout'; interface Src20TokenAssetListProps { - src20Tokens: Src20Token[]; + tokens: Src20Token[]; } -export function Src20TokenAssetList({ src20Tokens }: Src20TokenAssetListProps) { - return src20Tokens.map((token, i) => ( +export function Src20TokenAssetList({ tokens }: Src20TokenAssetListProps) { + return tokens.map((token, i) => ( )); } diff --git a/src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-item.layout.tsx b/src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-item.layout.tsx new file mode 100644 index 00000000000..6cc4b7d2f1d --- /dev/null +++ b/src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-item.layout.tsx @@ -0,0 +1,37 @@ +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/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx b/src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx new file mode 100644 index 00000000000..18462051c5c --- /dev/null +++ b/src/app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-list.tsx @@ -0,0 +1,12 @@ +import type { Stx20Token } from '@app/query/stacks/stacks-client'; + +import { Stx20TokenAssetItemLayout } from './stx20-token-asset-item.layout'; + +interface Stx20TokenAssetListProps { + tokens: Stx20Token[]; +} +export function Stx20TokenAssetList({ tokens }: Stx20TokenAssetListProps) { + return tokens.map((token, i) => ( + + )); +} diff --git a/src/app/components/loaders/brc20-tokens-loader.tsx b/src/app/components/loaders/brc20-tokens-loader.tsx index b5769cd74da..cbc9af4a770 100644 --- a/src/app/components/loaders/brc20-tokens-loader.tsx +++ b/src/app/components/loaders/brc20-tokens-loader.tsx @@ -2,9 +2,9 @@ import { Brc20Token } from '@app/query/bitcoin/bitcoin-client'; import { useBrc20Tokens } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks'; interface Brc20TokensLoaderProps { - children(brc20Tokens: Brc20Token[]): React.ReactNode; + children(tokens: Brc20Token[]): React.ReactNode; } export function Brc20TokensLoader({ children }: Brc20TokensLoaderProps) { - const brc20Tokens = useBrc20Tokens(); - return children(brc20Tokens); + const tokens = useBrc20Tokens(); + return children(tokens); } diff --git a/src/app/components/loaders/src20-tokens-loader.tsx b/src/app/components/loaders/src20-tokens-loader.tsx index 170fdbfc3d1..1ce829f6beb 100644 --- a/src/app/components/loaders/src20-tokens-loader.tsx +++ b/src/app/components/loaders/src20-tokens-loader.tsx @@ -3,9 +3,9 @@ import type { Src20Token } from '@app/query/bitcoin/stamps/stamps-by-address.que interface Src20TokensLoaderProps { address: string; - children(src20Tokens: Src20Token[]): React.ReactNode; + children(tokens: Src20Token[]): React.ReactNode; } export function Src20TokensLoader({ address, children }: Src20TokensLoaderProps) { - const { data: src20Tokens = [] } = useSrc20TokensByAddress(address); - return children(src20Tokens); + const { data: tokens = [] } = useSrc20TokensByAddress(address); + return children(tokens); } diff --git a/src/app/components/loaders/stx20-tokens-loader.tsx b/src/app/components/loaders/stx20-tokens-loader.tsx new file mode 100644 index 00000000000..986d214f6e6 --- /dev/null +++ b/src/app/components/loaders/stx20-tokens-loader.tsx @@ -0,0 +1,11 @@ +import type { Stx20Token } from '@app/query/stacks/stacks-client'; +import { useStx20Tokens } from '@app/query/stacks/stx20/stx20-tokens.hooks'; + +interface Stx20TokensLoaderProps { + address: string; + children(tokens: Stx20Token[]): React.ReactNode; +} +export function Stx20TokensLoader({ address, children }: Stx20TokensLoaderProps) { + const { data: tokens = [] } = useStx20Tokens(address); + return children(tokens); +} diff --git a/src/app/features/asset-list/asset-list.tsx b/src/app/features/asset-list/asset-list.tsx index 4963ad8bcd9..0324823ece5 100644 --- a/src/app/features/asset-list/asset-list.tsx +++ b/src/app/features/asset-list/asset-list.tsx @@ -10,8 +10,16 @@ import { BitcoinTaprootAccountLoader, } from '@app/components/account/bitcoin-account-loader'; import { BitcoinContractEntryPoint } from '@app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point'; +import { Brc20TokenAssetList } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list'; +import { RunesAssetList } from '@app/components/crypto-assets/bitcoin/runes-asset-list/runes-asset-list'; +import { Src20TokenAssetList } from '@app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list'; import { CryptoCurrencyAssetItemLayout } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout'; +import { Stx20TokenAssetList } from '@app/components/crypto-assets/stacks/stx20-token-asset-list/stx20-token-asset-list'; +import { Brc20TokensLoader } from '@app/components/loaders/brc20-tokens-loader'; +import { RunesLoader } from '@app/components/loaders/runes-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 { useHasBitcoinLedgerKeychain } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger'; import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useCurrentNetwork } from '@app/store/networks/networks.selectors'; @@ -20,7 +28,6 @@ import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; import { Collectibles } from '../collectibles/collectibles'; import { PendingBrc20TransferList } from '../pending-brc-20-transfers/pending-brc-20-transfers'; import { AddStacksLedgerKeysItem } from './components/add-stacks-ledger-keys-item'; -import { BitcoinFungibleTokenAssetList } from './components/bitcoin-fungible-tokens-asset-list'; import { ConnectLedgerAssetBtn } from './components/connect-ledger-asset-button'; import { StacksBalanceListItem } from './components/stacks-balance-list-item'; import { StacksFungibleTokenAssetList } from './components/stacks-fungible-token-asset-list'; @@ -74,6 +81,9 @@ export function AssetsList() { <> + + {tokens => } + )} @@ -82,10 +92,17 @@ export function AssetsList() { {nativeSegwitAccount => ( {taprootAccount => ( - + <> + + {tokens => } + + + {tokens => } + + + {runes => } + + )} )} diff --git a/src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx b/src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx deleted file mode 100644 index ceacfafe001..00000000000 --- a/src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Brc20TokenAssetList } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list'; -import { RunesAssetList } from '@app/components/crypto-assets/bitcoin/runes-asset-list/runes-asset-list'; -import { Src20TokenAssetList } from '@app/components/crypto-assets/bitcoin/src20-token-asset-list/src20-token-asset-list'; -import { Brc20TokensLoader } from '@app/components/loaders/brc20-tokens-loader'; -import { RunesLoader } from '@app/components/loaders/runes-loader'; -import { Src20TokensLoader } from '@app/components/loaders/src20-tokens-loader'; - -interface BitcoinFungibleTokenAssetListProps { - btcAddressNativeSegwit: string; - btcAddressTaproot: string; -} -export function BitcoinFungibleTokenAssetList({ - btcAddressNativeSegwit, - btcAddressTaproot, -}: BitcoinFungibleTokenAssetListProps) { - return ( - <> - - {brc20Tokens => } - - - {src20Tokens => } - - - {runes => } - - - ); -} diff --git a/src/app/query/bitcoin/bitcoin-client.ts b/src/app/query/bitcoin/bitcoin-client.ts index 5882888ac1c..aee6896925b 100644 --- a/src/app/query/bitcoin/bitcoin-client.ts +++ b/src/app/query/bitcoin/bitcoin-client.ts @@ -179,7 +179,7 @@ interface RunesOutputsByAddressResponse { data: RunesOutputsByAddress[]; } -class BestinslotApi { +class BestinSlotApi { url = BESTINSLOT_API_BASE_URL_MAINNET; testnetUrl = BESTINSLOT_API_BASE_URL_TESTNET; @@ -407,13 +407,13 @@ export class BitcoinClient { addressApi: AddressApi; feeEstimatesApi: FeeEstimatesApi; transactionsApi: TransactionsApi; - BestinslotApi: BestinslotApi; + bestinSlotApi: BestinSlotApi; - constructor(basePath: string) { - this.configuration = new Configuration(basePath); + constructor(baseUrl: string) { + this.configuration = new Configuration(baseUrl); this.addressApi = new AddressApi(this.configuration); this.feeEstimatesApi = new FeeEstimatesApi(this.configuration); this.transactionsApi = new TransactionsApi(this.configuration); - this.BestinslotApi = new BestinslotApi(this.configuration); + this.bestinSlotApi = new BestinSlotApi(this.configuration); } } 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 cb3e43ddab9..6e7bd527808 100644 --- a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts +++ b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts @@ -15,7 +15,10 @@ import { brc20TransferInitiated } from '@app/store/ordinals/ordinals.slice'; import type { Brc20Token } from '../../bitcoin-client'; import { useAverageBitcoinFeeRates } from '../../fees/fee-estimates.hooks'; import { useOrdinalsbotClient } from '../../ordinalsbot-client'; -import { createBrc20TransferInscription, encodeBrc20TransferInscription } from './brc-20.utils'; +import { + createBrc20TransferInscription, + encodeBrc20TransferInscription, +} from './brc20-tokens.utils'; // ts-unused-exports:disable-next-line export function useBrc20FeatureFlag() { diff --git a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.query.ts b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.query.ts index c5a294cc309..6440524a788 100644 --- a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.query.ts +++ b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.query.ts @@ -51,11 +51,11 @@ export function useGetBrc20TokensQuery() { } const brc20TokensPromises = addressesData.map(async address => { - const brc20Tokens = await client.BestinslotApi.getBrc20Balances(address); + const brc20Tokens = await client.bestinSlotApi.getBrc20Balances(address); const tickerPromises = await Promise.all( brc20Tokens.data.map(token => { - return client.BestinslotApi.getBrc20TickerInfo(token.ticker); + return client.bestinSlotApi.getBrc20TickerInfo(token.ticker); }) ); diff --git a/src/app/query/bitcoin/ordinals/brc20/brc-20.utils.spec.ts b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.utils.spec.ts similarity index 86% rename from src/app/query/bitcoin/ordinals/brc20/brc-20.utils.spec.ts rename to src/app/query/bitcoin/ordinals/brc20/brc20-tokens.utils.spec.ts index d9f2b8f4e59..d916ffc4a03 100644 --- a/src/app/query/bitcoin/ordinals/brc20/brc-20.utils.spec.ts +++ b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.utils.spec.ts @@ -1,4 +1,4 @@ -import { encodeBrc20TransferInscription } from './brc-20.utils'; +import { encodeBrc20TransferInscription } from './brc20-tokens.utils'; describe(encodeBrc20TransferInscription.name, () => { test('that it encodes the BRC-20 transfer correctly', () => { diff --git a/src/app/query/bitcoin/ordinals/brc20/brc-20.utils.ts b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.utils.ts similarity index 100% rename from src/app/query/bitcoin/ordinals/brc20/brc-20.utils.ts rename to src/app/query/bitcoin/ordinals/brc20/brc20-tokens.utils.ts diff --git a/src/app/query/bitcoin/runes/runes-outputs-by-address.query.ts b/src/app/query/bitcoin/runes/runes-outputs-by-address.query.ts index 5d92ffb56df..a38fa9425bc 100644 --- a/src/app/query/bitcoin/runes/runes-outputs-by-address.query.ts +++ b/src/app/query/bitcoin/runes/runes-outputs-by-address.query.ts @@ -21,7 +21,7 @@ export function useGetRunesOutputsByAddressQuery - client.BestinslotApi.getRunesOutputsByAddress({ + client.bestinSlotApi.getRunesOutputsByAddress({ address, network: network.chain.bitcoin.bitcoinNetwork, }), 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 36223438ced..ed9d2804d5e 100644 --- a/src/app/query/bitcoin/runes/runes-ticker-info.query.ts +++ b/src/app/query/bitcoin/runes/runes-ticker-info.query.ts @@ -19,7 +19,7 @@ export function useGetRunesTickerInfoQuery(runeNames: string[]): UseQueryResult< enabled: !!runeName && (network.chain.bitcoin.bitcoinNetwork === 'testnet' || runesEnabled), queryKey: ['runes-ticker-info', runeName], queryFn: () => - client.BestinslotApi.getRunesTickerInfo(runeName, network.chain.bitcoin.bitcoinNetwork), + client.bestinSlotApi.getRunesTickerInfo(runeName, network.chain.bitcoin.bitcoinNetwork), ...queryOptions, }; }), diff --git a/src/app/query/bitcoin/runes/runes-wallet-balances.query.ts b/src/app/query/bitcoin/runes/runes-wallet-balances.query.ts index 16e8177c169..c33fcf5536b 100644 --- a/src/app/query/bitcoin/runes/runes-wallet-balances.query.ts +++ b/src/app/query/bitcoin/runes/runes-wallet-balances.query.ts @@ -23,7 +23,7 @@ export function useGetRunesWalletBalancesByAddressesQuery - client.BestinslotApi.getRunesWalletBalances( + client.bestinSlotApi.getRunesWalletBalances( address, network.chain.bitcoin.bitcoinNetwork ), diff --git a/src/app/query/bitcoin/transaction/use-check-utxos.ts b/src/app/query/bitcoin/transaction/use-check-utxos.ts index 71fe959a921..09fe190bdab 100644 --- a/src/app/query/bitcoin/transaction/use-check-utxos.ts +++ b/src/app/query/bitcoin/transaction/use-check-utxos.ts @@ -55,7 +55,7 @@ async function checkInscribedUtxosByBestinslot({ * @see https://docs.bestinslot.xyz/reference/api-reference/ordinals-and-brc-20-and-bitmap-v3-api-mainnet+testnet/inscriptions */ const inscriptionIdsList = await Promise.all( - txids.map(id => client.BestinslotApi.getInscriptionsByTransactionId(id)) + txids.map(id => client.bestinSlotApi.getInscriptionsByTransactionId(id)) ); const inscriptionIds = inscriptionIdsList.flatMap(inscription => @@ -63,7 +63,7 @@ async function checkInscribedUtxosByBestinslot({ ); const inscriptionsList = await Promise.all( - inscriptionIds.map(id => client.BestinslotApi.getInscriptionById(id)) + inscriptionIds.map(id => client.bestinSlotApi.getInscriptionById(id)) ); const hasInscribedUtxos = inscriptionsList.some(resp => { diff --git a/src/app/query/stacks/stacks-client.ts b/src/app/query/stacks/stacks-client.ts index e86c6f26497..a06231c957f 100644 --- a/src/app/query/stacks/stacks-client.ts +++ b/src/app/query/stacks/stacks-client.ts @@ -1,8 +1,9 @@ +import { TokensApi } from '@hirosystems/token-metadata-api-client'; import { AccountsApi, BlocksApi, Configuration, - ConfigurationParameters, + type ConfigurationParameters, FaucetsApi, FeesApi, FungibleTokensApi, @@ -14,6 +15,36 @@ import { SmartContractsApi, TransactionsApi, } from '@stacks/blockchain-api-client'; +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; + balance: string; + updateDate: string; +} + +interface Stx20BalanceResponse { + address: string; + balances: Stx20Balance[]; +} + +export interface Stx20Token { + balance: Money; + marketData: null; + tokenData: Stx20Balance; +} + +class Stx20Api { + url = STX20_API_BASE_URL_MAINNET; + + async getStx20Balances(address: string) { + const resp = await axios.get(`${this.url}/balance/${address}`); + return resp.data.balances; + } +} export class StacksClient { configuration: Configuration; @@ -29,6 +60,8 @@ export class StacksClient { rosettaApi: RosettaApi; fungibleTokensApi: FungibleTokensApi; nonFungibleTokensApi: NonFungibleTokensApi; + tokensApi: TokensApi; + stx20Api: Stx20Api; constructor(config: ConfigurationParameters) { this.configuration = new Configuration(config); @@ -44,5 +77,7 @@ export class StacksClient { this.rosettaApi = new RosettaApi(this.configuration); this.fungibleTokensApi = new FungibleTokensApi(this.configuration); this.nonFungibleTokensApi = new NonFungibleTokensApi(this.configuration); + this.tokensApi = new TokensApi({ basePath: config.basePath }); + this.stx20Api = new Stx20Api(); } } diff --git a/src/app/query/stacks/stx20/stx20-tokens.hooks.ts b/src/app/query/stacks/stx20/stx20-tokens.hooks.ts new file mode 100644 index 00000000000..f3394d4a650 --- /dev/null +++ b/src/app/query/stacks/stx20/stx20-tokens.hooks.ts @@ -0,0 +1,20 @@ +import BigNumber from 'bignumber.js'; + +import { createMoney } from '@shared/models/money.model'; + +import type { Stx20Balance, Stx20Token } from '../stacks-client'; +import { useStx20BalancesQuery } from './stx20-tokens.query'; + +function makeStx20Token(token: Stx20Balance): Stx20Token { + return { + balance: createMoney(new BigNumber(token.balance), token.ticker, 0), + marketData: null, + tokenData: token, + }; +} + +export function useStx20Tokens(address: string) { + return useStx20BalancesQuery(address, { + select: resp => resp.map(balance => makeStx20Token(balance)), + }); +} diff --git a/src/app/query/stacks/stx20/stx20-tokens.query.ts b/src/app/query/stacks/stx20/stx20-tokens.query.ts new file mode 100644 index 00000000000..529922547fb --- /dev/null +++ b/src/app/query/stacks/stx20/stx20-tokens.query.ts @@ -0,0 +1,23 @@ +import { ChainID } from '@stacks/transactions'; +import { useQuery } from '@tanstack/react-query'; + +import { AppUseQueryConfig } from '@app/query/query-config'; +import { useStacksClient } from '@app/store/common/api-clients.hooks'; +import { useCurrentNetwork } from '@app/store/networks/networks.selectors'; + +import type { Stx20Balance } from '../stacks-client'; + +export function useStx20BalancesQuery( + address: string, + options?: AppUseQueryConfig +) { + const client = useStacksClient(); + const network = useCurrentNetwork(); + + return useQuery({ + enabled: network.chain.stacks.chainId === ChainID.Mainnet, + queryKey: ['stx20-balances', address], + queryFn: () => client.stx20Api.getStx20Balances(address), + ...options, + }); +} diff --git a/src/app/query/stacks/token-metadata-client.ts b/src/app/query/stacks/token-metadata-client.ts deleted file mode 100644 index d5fc9c8c75f..00000000000 --- a/src/app/query/stacks/token-metadata-client.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - Configuration, - ConfigurationParameters, - TokensApi, -} from '@hirosystems/token-metadata-api-client'; - -export class TokenMetadataClient { - configuration: Configuration; - tokensApi: TokensApi; - - constructor(config: ConfigurationParameters) { - this.configuration = new Configuration(config); - this.tokensApi = new TokensApi(this.configuration); - } -} diff --git a/src/app/query/stacks/tokens/fungible-tokens/fungible-token-metadata.query.ts b/src/app/query/stacks/tokens/fungible-tokens/fungible-token-metadata.query.ts index 7e53a2db505..8bdaa6e8b7a 100644 --- a/src/app/query/stacks/tokens/fungible-tokens/fungible-token-metadata.query.ts +++ b/src/app/query/stacks/tokens/fungible-tokens/fungible-token-metadata.query.ts @@ -1,11 +1,11 @@ import { UseQueryResult, useQueries, useQuery } from '@tanstack/react-query'; import PQueue from 'p-queue'; -import { useTokenMetadataClient } from '@app/store/common/api-clients.hooks'; +import { useStacksClient } from '@app/store/common/api-clients.hooks'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; import { useHiroApiRateLimiter } from '../../hiro-rate-limiter'; -import { TokenMetadataClient } from '../../token-metadata-client'; +import type { StacksClient } from '../../stacks-client'; import { FtAssetResponse } from '../token-metadata.utils'; const staleTime = 12 * 60 * 60 * 1000; @@ -21,7 +21,7 @@ const queryOptions = { retry: 0, } as const; -function fetchFungibleTokenMetadata(client: TokenMetadataClient, limiter: PQueue) { +function fetchFungibleTokenMetadata(client: StacksClient, limiter: PQueue) { return (principal: string) => async () => { return limiter.add(() => client.tokensApi.getFtMetadata(principal), { throwOnTimeout: true, @@ -32,7 +32,7 @@ function fetchFungibleTokenMetadata(client: TokenMetadataClient, limiter: PQueue export function useGetFungibleTokenMetadataQuery( principal: string ): UseQueryResult { - const client = useTokenMetadataClient(); + const client = useStacksClient(); const network = useCurrentNetworkState(); const limiter = useHiroApiRateLimiter(); @@ -46,7 +46,7 @@ export function useGetFungibleTokenMetadataQuery( export function useGetFungibleTokenMetadataListQuery( principals: string[] ): UseQueryResult[] { - const client = useTokenMetadataClient(); + const client = useStacksClient(); const network = useCurrentNetworkState(); const limiter = useHiroApiRateLimiter(); diff --git a/src/app/query/stacks/tokens/non-fungible-tokens/non-fungible-token-metadata.query.ts b/src/app/query/stacks/tokens/non-fungible-tokens/non-fungible-token-metadata.query.ts index 02311940d5f..04cdd650d08 100644 --- a/src/app/query/stacks/tokens/non-fungible-tokens/non-fungible-token-metadata.query.ts +++ b/src/app/query/stacks/tokens/non-fungible-tokens/non-fungible-token-metadata.query.ts @@ -4,7 +4,7 @@ import { UseQueryResult, useQueries } from '@tanstack/react-query'; import { pullContractIdFromIdentity } from '@app/common/utils'; import { QueryPrefixes } from '@app/query/query-prefixes'; import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; -import { useTokenMetadataClient } from '@app/store/common/api-clients.hooks'; +import { useStacksClient } from '@app/store/common/api-clients.hooks'; import { useHiroApiRateLimiter } from '../../hiro-rate-limiter'; import { NftAssetResponse } from '../token-metadata.utils'; @@ -24,7 +24,7 @@ function getTokenId(hex: string) { export function useGetNonFungibleTokenMetadataListQuery( account: StacksAccount ): UseQueryResult[] { - const client = useTokenMetadataClient(); + const client = useStacksClient(); const limiter = useHiroApiRateLimiter(); const nftHoldings = useGetNonFungibleTokenHoldingsQuery(account.address); diff --git a/src/app/query/stacks/utils.ts b/src/app/query/stacks/utils.ts deleted file mode 100644 index 390bcbee57b..00000000000 --- a/src/app/query/stacks/utils.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Configuration as TokenMetadataConfiguration } from '@hirosystems/token-metadata-api-client'; -import { Configuration } from '@stacks/blockchain-api-client'; - -import { wrappedFetch as fetchApi } from '@app/common/api/fetch-wrapper'; - -export function createStacksClientConfig(basePath: string) { - return new Configuration({ - basePath, - fetchApi, - }); -} - -export function createTokenMetadataConfig(basePath: string) { - return new TokenMetadataConfiguration({ basePath }); -} diff --git a/src/app/store/common/api-clients.hooks.ts b/src/app/store/common/api-clients.hooks.ts index 8103caf66cd..33c3a59934c 100644 --- a/src/app/store/common/api-clients.hooks.ts +++ b/src/app/store/common/api-clients.hooks.ts @@ -1,14 +1,8 @@ import { useMemo } from 'react'; -import { ChainID } from '@stacks/transactions'; - -import { HIRO_API_BASE_URL_MAINNET, HIRO_API_BASE_URL_TESTNET } from '@shared/constants'; -import { whenStacksChainId } from '@shared/crypto/stacks/stacks.utils'; - +import { wrappedFetch as fetchApi } from '@app/common/api/fetch-wrapper'; import { BitcoinClient } from '@app/query/bitcoin/bitcoin-client'; import { StacksClient } from '@app/query/stacks/stacks-client'; -import { TokenMetadataClient } from '@app/query/stacks/token-metadata-client'; -import { createStacksClientConfig, createTokenMetadataConfig } from '@app/query/stacks/utils'; import { useCurrentNetworkState } from '../networks/networks.hooks'; @@ -21,20 +15,9 @@ export function useStacksClient() { const network = useCurrentNetworkState(); return useMemo(() => { - const config = createStacksClientConfig(network.chain.stacks.url); - return new StacksClient(config); + return new StacksClient({ + basePath: network.chain.stacks.url, + fetchApi, + }); }, [network.chain.stacks.url]); } - -export function useTokenMetadataClient() { - const currentNetwork = useCurrentNetworkState(); - - const basePath = whenStacksChainId(currentNetwork.chain.stacks.chainId)({ - [ChainID.Mainnet]: HIRO_API_BASE_URL_MAINNET, - [ChainID.Testnet]: HIRO_API_BASE_URL_TESTNET, - }); - - const config = createTokenMetadataConfig(basePath); - - return new TokenMetadataClient(config); -} diff --git a/src/app/ui/components/avatar/stx20-avatar-icon.tsx b/src/app/ui/components/avatar/stx20-avatar-icon.tsx new file mode 100644 index 00000000000..05e6aefe9e6 --- /dev/null +++ b/src/app/ui/components/avatar/stx20-avatar-icon.tsx @@ -0,0 +1,14 @@ +import Stx20AvatarIconSrc from '@assets/avatars/stx20-avatar-icon.png'; + +import { Avatar, type AvatarProps } from './avatar'; + +const fallback = 'ST'; + +export function Stx20AvatarIcon(props: AvatarProps) { + return ( + + + {fallback} + + ); +} diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 9b5b770fcb8..8f95f08798c 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -78,6 +78,8 @@ export interface NetworkConfiguration { export const BESTINSLOT_API_BASE_URL_MAINNET = 'https://leatherapi.bestinslot.xyz/v3'; export const BESTINSLOT_API_BASE_URL_TESTNET = 'https://leatherapi_testnet.bestinslot.xyz/v3'; +export const STX20_API_BASE_URL_MAINNET = 'https://api.stx20.com/api/v1'; + export const HIRO_API_BASE_URL_MAINNET = 'https://api.hiro.so'; export const HIRO_API_BASE_URL_TESTNET = 'https://api.testnet.hiro.so'; export const HIRO_INSCRIPTIONS_API_URL = 'https://api.hiro.so/ordinals/v1/inscriptions'; From 50aeb1cbd6162889e090aa38199ad439899efebc Mon Sep 17 00:00:00 2001 From: Fara Woolf Date: Tue, 30 Apr 2024 11:10:53 -0500 Subject: [PATCH 2/2] fix: swap test --- src/app/pages/swap/components/swap-review.tsx | 2 +- tests/page-object-models/swap.page.ts | 8 +++--- tests/selectors/swap.selectors.ts | 2 +- tests/specs/swap/swap.spec.ts | 26 +++++++------------ 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/app/pages/swap/components/swap-review.tsx b/src/app/pages/swap/components/swap-review.tsx index e4e5e1ab8dd..e99767770a8 100644 --- a/src/app/pages/swap/components/swap-review.tsx +++ b/src/app/pages/swap/components/swap-review.tsx @@ -23,7 +23,7 @@ export function SwapReview() {