refactor: ft and nft query to new client

This commit is contained in:
fbwoolf
2023-03-17 11:54:31 -05:00
committed by Fara Woolf
parent 2045628c08
commit 5bc54b5ab0
24 changed files with 123 additions and 132 deletions

View File

@@ -129,7 +129,7 @@
"@emotion/react": "11.10.5",
"@emotion/styled": "11.10.5",
"@fungible-systems/zone-file": "2.0.0",
"@hirosystems/token-metadata-api-client": "1.0.1",
"@hirosystems/token-metadata-api-client": "1.0.3",
"@ledgerhq/hw-transport-webusb": "6.27.10",
"@noble/hashes": "1.2.0",
"@noble/secp256k1": "1.7.1",

View File

@@ -1,5 +1,3 @@
import { FungibleTokenMetadata } from '@stacks/stacks-blockchain-api-types';
import { StacksFungibleTokenAsset } from '@shared/models/crypto-asset.model';
import {
@@ -7,19 +5,6 @@ import {
isTransferableStacksFungibleTokenAsset,
} from './stacks-crypto-asset.utils';
// Metadata is used here temporarily until we have the new Hiro API types
const metadata: FungibleTokenMetadata = {
name: '',
symbol: '',
token_uri: '',
decimals: 0,
description: '',
image_uri: '',
image_canonical_uri: '',
tx_id: '',
sender_address: '',
};
describe(isFtNameLikeStx.name, () => {
it('detect impersonating token names', () => {
expect(isFtNameLikeStx('STX')).toBeTruthy();
@@ -48,7 +33,7 @@ describe(isTransferableStacksFungibleTokenAsset.name, () => {
imageCanonicalUri: '',
symbol: 'CAT',
};
expect(isTransferableStacksFungibleTokenAsset(asset, metadata)).toBeTruthy();
expect(isTransferableStacksFungibleTokenAsset(asset)).toBeTruthy();
});
test('a token with no decimals is transferable', () => {
@@ -64,7 +49,7 @@ describe(isTransferableStacksFungibleTokenAsset.name, () => {
imageCanonicalUri: '',
symbol: 'CAT',
};
expect(isTransferableStacksFungibleTokenAsset(asset, metadata)).toBeTruthy();
expect(isTransferableStacksFungibleTokenAsset(asset)).toBeTruthy();
});
test('assets missing either name, symbol or decimals may not be transferred', () => {
@@ -74,12 +59,12 @@ describe(isTransferableStacksFungibleTokenAsset.name, () => {
decimals: undefined,
type: 'fungible-token',
} as unknown as StacksFungibleTokenAsset;
expect(isTransferableStacksFungibleTokenAsset(asset, metadata)).toBeFalsy();
expect(isTransferableStacksFungibleTokenAsset(asset)).toBeFalsy();
});
test('NFTs cannot be sent', () => {
const asset = { type: 'non-fungible-token' } as unknown as StacksFungibleTokenAsset;
expect(isTransferableStacksFungibleTokenAsset(asset, metadata)).toBeFalsy();
expect(isTransferableStacksFungibleTokenAsset(asset)).toBeFalsy();
});
});

View File

@@ -1,5 +1,3 @@
import { FungibleTokenMetadata } from '@stacks/blockchain-api-client';
import { StacksFungibleTokenAsset } from '@shared/models/crypto-asset.model';
import { isUndefined } from '@shared/utils';
import { isValidUrl } from '@shared/utils/validate-url';
@@ -27,20 +25,12 @@ export function isFtNameLikeStx(name: string) {
return ['stx', 'stack', 'stacks'].includes(convertUnicodeToAscii(name).toLocaleLowerCase());
}
export function getImageCanonicalUri(imageCanonicalUri?: string, name?: string) {
return imageCanonicalUri && isValidUrl(imageCanonicalUri) && !isFtNameLikeStx(name ?? '')
export function getImageCanonicalUri(imageCanonicalUri: string, name: string) {
return imageCanonicalUri && isValidUrl(imageCanonicalUri) && !isFtNameLikeStx(name)
? imageCanonicalUri
: '';
}
// Metadata is used here temporarily until we have the new Hiro API types
export function isTransferableStacksFungibleTokenAsset(
asset: StacksFungibleTokenAsset
// metadata?: FungibleTokenMetadata
) {
return (
// !isUndefined(metadata) &&
// !('error' in metadata) &&
!isUndefined(asset.decimals) && !isUndefined(asset.name) && !isUndefined(asset.symbol)
);
export function isTransferableStacksFungibleTokenAsset(asset: StacksFungibleTokenAsset) {
return !isUndefined(asset.decimals) && !isUndefined(asset.name) && !isUndefined(asset.symbol);
}

View File

@@ -2,6 +2,7 @@ import { FiArrowDown, FiArrowUp } from 'react-icons/fi';
import type { AddressTransactionWithTransfers } from '@stacks/stacks-blockchain-api-types';
import { logger } from '@shared/logger';
import {
FtTransfer,
TxTransferDetails,
@@ -15,7 +16,8 @@ import {
import { pullContractIdFromIdentity } from '@app/common/utils';
import { StacksAssetAvatar } from '@app/components/crypto-assets/stacks/components/stacks-asset-avatar';
import { StacksTransactionItem } from '@app/components/stacks-transaction-item/stacks-transaction-item';
import { useGetFungibleTokenMetadataQuery } from '@app/query/stacks/fungible-tokens/fungible-token-metadata.query';
import { useGetFungibleTokenMetadataQuery } from '@app/query/stacks/tokens/fungible-tokens/fungible-token-metadata.query';
import { isFtAsset } from '@app/query/stacks/tokens/token-metadata.utils';
import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
import { TxTransferIconWrapper } from './tx-transfer-icon-wrapper';
@@ -31,6 +33,11 @@ export function FtTransferItem({ ftTransfer, parentTx }: FtTransferItemProps) {
const currentAccount = useCurrentStacksAccount();
const isOriginator = ftTransfer.sender === currentAccount?.address;
if (!(assetMetadata && isFtAsset(assetMetadata))) {
logger.error('Token metadata not found');
return null;
}
const displayAmount = calculateTokenTransferAmount(
assetMetadata?.decimals ?? 0,
ftTransfer.amount
@@ -39,9 +46,11 @@ export function FtTransferItem({ ftTransfer, parentTx }: FtTransferItemProps) {
const caption = getTxCaption(parentTx.tx) ?? '';
const ftImageCanonicalUri =
assetMetadata && getImageCanonicalUri(assetMetadata.imageCanonicalUri, assetMetadata.name);
assetMetadata.image_canonical_uri &&
assetMetadata.name &&
getImageCanonicalUri(assetMetadata.image_canonical_uri, assetMetadata.name);
const icon = isOriginator ? FiArrowUp : FiArrowDown;
const title = `${assetMetadata?.name || 'Token'} Transfer`;
const title = `${assetMetadata.name || 'Token'} Transfer`;
const value = `${isOriginator ? '-' : ''}${displayAmount.toFormat()}`;
const transferIcon = ftImageCanonicalUri ? (
<StacksAssetAvatar

View File

@@ -1,6 +1,6 @@
import { parseIfValidPunycode } from '@app/common/utils';
import { useCurrentAccountNames } from '@app/query/stacks/bns/bns.hooks';
import { useNonFungibleTokensMetadata } from '@app/query/stacks/non-fungible-tokens/non-fungible-token-metadata.hooks';
import { useNonFungibleTokensMetadata } from '@app/query/stacks/tokens/non-fungible-tokens/non-fungible-token-metadata.hooks';
import { StacksBnsName } from './stacks-bns-name';
import { StacksNonFungibleTokens } from './stacks-non-fungible-tokens';
@@ -14,9 +14,10 @@ export function StacksCryptoAssets() {
{names.map(name => (
<StacksBnsName bnsName={parseIfValidPunycode(name)} key={name} />
))}
{stacksNftsMetadataResp.map(nft =>
nft ? <StacksNonFungibleTokens key={nft.token_uri} metadata={nft.metadata} /> : null
)}
{stacksNftsMetadataResp.map(nft => {
if (!nft || !nft.metadata) return null;
return <StacksNonFungibleTokens key={nft.token_uri} metadata={nft.metadata} />;
})}
</>
);
}

View File

@@ -1,8 +1,8 @@
import { useState } from 'react';
import { Metadata as StacksNftMetadata } from '@hirosystems/token-metadata-api-client';
import { Spinner } from '@stacks/ui';
import { StacksNftMetadata } from '@shared/models/stacks-nft-metadata.model';
import { isValidUrl } from '@shared/utils/validate-url';
import { figmaTheme } from '@app/common/utils/figma-theme';
@@ -20,20 +20,16 @@ const backgroundProps = {
interface StacksNftCryptoAssetsProps {
metadata: StacksNftMetadata;
}
export function StacksNonFungibleTokens({ metadata }: StacksNftCryptoAssetsProps) {
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
if (!metadata) return null;
const isImageAvailable = isValidUrl(metadata.cached_image);
const isImageAvailable = metadata.cached_image && isValidUrl(metadata.cached_image);
return (
<CollectibleItemLayout
backgroundElementProps={backgroundProps}
subtitle="Stacks NFT"
title={metadata.name}
title={metadata.name ?? ''}
collectibleTypeIcon={<StxAvatar size="30px" />}
>
{isError || !isImageAvailable ? (

View File

@@ -5,8 +5,8 @@ import BigNumber from 'bignumber.js';
import { SuggestedFirstStepStatus, SuggestedFirstSteps } from '@shared/models/onboarding-types';
import { useGetAnchoredAccountBalanceListQuery } from '@app/query/stacks/balance/stx-balance.query';
import { useAllAccountsNonFungibleTokenHoldingsTotal } from '@app/query/stacks/non-fungible-tokens/non-fungible-token-holdings.hooks';
import { useGetNonFungibleTokenHoldingsQuery } from '@app/query/stacks/non-fungible-tokens/non-fungible-token-holdings.query';
import { useAllAccountsNonFungibleTokenHoldingsTotal } from '@app/query/stacks/tokens/non-fungible-tokens/non-fungible-token-holdings.hooks';
import { useGetNonFungibleTokenHoldingsQuery } from '@app/query/stacks/tokens/non-fungible-tokens/non-fungible-token-holdings.query';
import {
useCurrentStacksAccount,
useStacksAccounts,

View File

@@ -20,7 +20,7 @@ export function CryptoAssetListItem(props: CryptoAssetListItemProps) {
function navigateToSendForm(state?: object) {
if (blockchain === 'bitcoin' && !isBitcoinSendEnabled) navigate(RouteUrls.SendBtcDisabled);
navigate(`${RouteUrls.SendCryptoAsset}/${asset.symbol.toLowerCase()}`, { state });
navigate(`${RouteUrls.SendCryptoAsset}/${asset.symbol?.toLowerCase()}`, { state });
}
switch (type) {

View File

@@ -33,7 +33,10 @@ function FungiblePostConditionItemSuspense(
const pendingTransaction = useTransactionRequestState();
// Use token meta data if available
const asset = useAssetFromFungiblePostCondition(pc);
const imageCanonicalUri = asset && getImageCanonicalUri(asset.imageCanonicalUri, asset.name);
const imageCanonicalUri =
asset?.image_canonical_uri &&
asset.name &&
getImageCanonicalUri(asset.image_canonical_uri, asset.name);
const title = getPostConditionTitle(pc);
const iconString = imageCanonicalUri ?? getIconStringFromPostCondition(pc);

View File

@@ -5,7 +5,8 @@ import type { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asse
import { formatContractId, getFullyQualifiedStacksAssetName } from '@app/common/utils';
import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
import { useGetFungibleTokenMetadataListQuery } from '../fungible-tokens/fungible-token-metadata.query';
import { useGetFungibleTokenMetadataListQuery } from '../tokens/fungible-tokens/fungible-token-metadata.query';
import { isFtAsset } from '../tokens/token-metadata.utils';
import {
addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance,
convertFtBalancesToStacksFungibleTokenAssetBalanceType,
@@ -61,7 +62,7 @@ export function useStacksFungibleTokenAssetBalancesAnchoredWithMetadata(address:
() =>
initializedAssetBalances.map((assetBalance, i) => {
const metadata = ftAssetsMetadata[i].data;
if (!metadata) return assetBalance;
if (!(metadata && isFtAsset(metadata))) return assetBalance;
return addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance(
assetBalance,
metadata
@@ -87,7 +88,7 @@ function useStacksFungibleTokenAssetBalancesUnanchoredWithMetadata(
() =>
initializedAssetBalances.map((assetBalance, i) => {
const metadata = ftAssetsMetadata[i].data;
if (!metadata) return assetBalance;
if (!(metadata && isFtAsset(metadata))) return assetBalance;
return addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance(
assetBalance,
metadata

View File

@@ -1,4 +1,4 @@
import { FungibleTokenMetadata } from '@stacks/stacks-blockchain-api-types';
import { FtMetadataResponse } from '@hirosystems/token-metadata-api-client';
import { getAssetStringParts } from '@stacks/ui-utils';
import BigNumber from 'bignumber.js';
@@ -99,7 +99,7 @@ export function convertNftBalancesToStacksNonFungibleTokenAssetBalanceType(
export function addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance(
assetBalance: StacksFungibleTokenAssetBalance,
metadata: FungibleTokenMetadata
metadata: FtMetadataResponse
) {
return {
...assetBalance,
@@ -111,11 +111,11 @@ export function addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance(
asset: {
...assetBalance.asset,
canTransfer: isTransferableStacksFungibleTokenAsset(assetBalance.asset),
decimals: metadata.decimals,
decimals: metadata.decimals ?? 0,
hasMemo: isTransferableStacksFungibleTokenAsset(assetBalance.asset),
imageCanonicalUri: metadata.image_canonical_uri,
name: metadata.name,
symbol: metadata.symbol,
imageCanonicalUri: metadata.image_canonical_uri ?? '',
name: metadata.name ?? '',
symbol: metadata.symbol ?? '',
},
};
}

View File

@@ -1,6 +0,0 @@
import { useGetNonFungibleTokenMetadataListQuery } from './non-fungible-token-metadata.query';
export function useNonFungibleTokensMetadata() {
const respList = useGetNonFungibleTokenMetadataListQuery();
return respList.map(resp => resp.data).filter(data => data?.token_uri);
}

View File

@@ -1,11 +1,11 @@
import { FtMetadataResponse } from '@hirosystems/token-metadata-api-client';
import { UseQueryResult, useQueries, useQuery } from '@tanstack/react-query';
import { useTokenMetadataClient } from '@app/store/common/api-clients.hooks';
import { useCurrentNetworkState } from '@app/store/networks/networks.hooks';
import { RateLimiter, useHiroApiRateLimiter } from '../rate-limiter';
import { TokenMetadataClient } from '../token-metadata-client';
import { RateLimiter, useHiroApiRateLimiter } from '../../rate-limiter';
import { TokenMetadataClient } from '../../token-metadata-client';
import { FtAssetResponse } from '../token-metadata.utils';
const staleTime = 12 * 60 * 60 * 1000;
@@ -29,7 +29,7 @@ function fetchFungibleTokenMetadata(client: TokenMetadataClient, limiter: RateLi
export function useGetFungibleTokenMetadataQuery(
principal: string
): UseQueryResult<FtMetadataResponse> {
): UseQueryResult<FtAssetResponse> {
const client = useTokenMetadataClient();
const network = useCurrentNetworkState();
const limiter = useHiroApiRateLimiter();
@@ -43,7 +43,7 @@ export function useGetFungibleTokenMetadataQuery(
export function useGetFungibleTokenMetadataListQuery(
principals: string[]
): UseQueryResult<FtMetadataResponse>[] {
): UseQueryResult<FtAssetResponse>[] {
const client = useTokenMetadataClient();
const network = useCurrentNetworkState();
const limiter = useHiroApiRateLimiter();

View File

@@ -7,7 +7,7 @@ import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-acco
import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks';
import { useCurrentNetworkState } from '@app/store/networks/networks.hooks';
import { RateLimiter, useHiroApiRateLimiter } from '../rate-limiter';
import { RateLimiter, useHiroApiRateLimiter } from '../../rate-limiter';
const staleTime = 15 * 60 * 1000; // 15 min

View File

@@ -0,0 +1,19 @@
import { useMemo } from 'react';
import { isNftAsset } from '../token-metadata.utils';
import { useGetNonFungibleTokenMetadataListQuery } from './non-fungible-token-metadata.query';
export function useNonFungibleTokensMetadata() {
const respList = useGetNonFungibleTokenMetadataListQuery();
return useMemo(
() =>
respList
.filter(resp => resp.status === 'success')
.map(resp => {
if (resp.data && isNftAsset(resp.data)) return resp.data;
return;
}),
[respList]
);
}

View File

@@ -1,13 +1,13 @@
import { hexToCV } from '@stacks/transactions';
import { useQueries } from '@tanstack/react-query';
import { UseQueryResult, useQueries } from '@tanstack/react-query';
import { StacksNftMetadataResponse } from '@shared/models/stacks-nft-metadata.model';
import { fetcher } from '@app/common/api/wrapped-fetch';
import { pullContractIdFromIdentity } from '@app/common/utils';
import { QueryPrefixes } from '@app/query/query-prefixes';
import { useTokenMetadataClient } from '@app/store/common/api-clients.hooks';
import { RateLimiter, useHiroApiRateLimiter } from '../rate-limiter';
import { RateLimiter, useHiroApiRateLimiter } from '../../rate-limiter';
import { TokenMetadataClient } from '../../token-metadata-client';
import { NftAssetResponse } from '../token-metadata.utils';
import { useAccountNonFungibleTokenHoldings } from './non-fungible-token-holdings.hooks';
const queryOptions = {
@@ -21,18 +21,17 @@ function getTokenId(hex: string) {
return clarityValue.type === 1 ? Number(clarityValue.value) : 0;
}
function fetchNonFungibleTokenMetadata(limiter: RateLimiter) {
return async (principal: string, tokenId?: number) => {
function fetchNonFungibleTokenMetadata(client: TokenMetadataClient, limiter: RateLimiter) {
return (principal: string, tokenId: number) => async () => {
await limiter.removeTokens(1);
const resp = await fetcher(`https://api.hiro.so/metadata/v1/nft/${principal}/${tokenId}`);
const data = await resp.json();
return data as Promise<StacksNftMetadataResponse>;
return client.tokensApi.getNftMetadata(principal, tokenId);
};
}
export function useGetNonFungibleTokenMetadataListQuery() {
const nftHoldings = useAccountNonFungibleTokenHoldings();
export function useGetNonFungibleTokenMetadataListQuery(): UseQueryResult<NftAssetResponse>[] {
const client = useTokenMetadataClient();
const limiter = useHiroApiRateLimiter();
const nftHoldings = useAccountNonFungibleTokenHoldings();
return useQueries({
queries: nftHoldings.map(nft => {
@@ -42,7 +41,7 @@ export function useGetNonFungibleTokenMetadataListQuery() {
return {
enabled: !!tokenId,
queryKey: [QueryPrefixes.GetNftMetadata, principal, tokenId],
queryFn: () => fetchNonFungibleTokenMetadata(limiter)(principal, tokenId),
queryFn: fetchNonFungibleTokenMetadata(client, limiter)(principal, tokenId),
...queryOptions,
};
}),

View File

@@ -0,0 +1,22 @@
import {
FtMetadataResponse,
NftMetadataResponse,
TokenNotFoundResponse,
} from '@hirosystems/token-metadata-api-client';
export type FtAssetResponse = FtMetadataResponse | TokenNotFoundResponse;
export type NftAssetResponse = NftMetadataResponse | TokenNotFoundResponse;
function isAssetMetadataNotFoundResponse(
resp: FtAssetResponse | NftAssetResponse
): resp is TokenNotFoundResponse {
return 'error' in resp;
}
export function isFtAsset(resp: FtAssetResponse): resp is FtMetadataResponse {
return !isAssetMetadataNotFoundResponse(resp);
}
export function isNftAsset(resp: NftAssetResponse): resp is NftMetadataResponse {
return !isAssetMetadataNotFoundResponse(resp);
}

View File

@@ -5,8 +5,8 @@ import { ChainID } from '@stacks/transactions';
import {
BITCOIN_API_BASE_URL_MAINNET,
BITCOIN_API_BASE_URL_TESTNET,
TOKEN_METADATA_API_BASE_URL_MAINNET,
TOKEN_METADATA_API_BASE_URL_TESTNET,
HIRO_API_BASE_URL_MAINNET,
HIRO_API_BASE_URL_TESTNET,
} from '@shared/constants';
import { whenStxChainId } from '@app/common/utils';
@@ -56,8 +56,8 @@ export function useTokenMetadataClient() {
const network = useCurrentNetworkState();
const basePath = whenStxChainId(network.chain.stacks.chainId)({
[ChainID.Mainnet]: TOKEN_METADATA_API_BASE_URL_MAINNET,
[ChainID.Testnet]: TOKEN_METADATA_API_BASE_URL_TESTNET,
[ChainID.Mainnet]: HIRO_API_BASE_URL_MAINNET,
[ChainID.Testnet]: HIRO_API_BASE_URL_TESTNET,
});
const config = createTokenMetadataConfig(basePath);

View File

@@ -7,7 +7,8 @@ import {
getPostCondition,
handlePostConditions,
} from '@app/common/transactions/stacks/post-condition.utils';
import { useGetFungibleTokenMetadataQuery } from '@app/query/stacks/fungible-tokens/fungible-token-metadata.query';
import { useGetFungibleTokenMetadataQuery } from '@app/query/stacks/tokens/fungible-tokens/fungible-token-metadata.query';
import { isFtAsset } from '@app/query/stacks/tokens/token-metadata.utils';
import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
import { useTransactionRequestState } from './requests.hooks';
@@ -42,5 +43,6 @@ export function useAssetFromFungiblePostCondition(pc: FungiblePostCondition) {
const contractName = pc.assetInfo.contractName.content;
const contractId = `${contractAddress}.${contractName}`;
const { data: asset } = useGetFungibleTokenMetadataQuery(contractId);
return !asset || 'error' in asset ? undefined : asset;
return !(asset && isFtAsset(asset)) ? undefined : asset;
}

View File

@@ -35,11 +35,6 @@ export enum DefaultNetworkConfigurationIds {
export type DefaultNetworkConfigurations = keyof typeof DefaultNetworkConfigurationIds;
export const chainIdToNetworkModeMap: Record<ChainID, NetworkModes> = {
[ChainID.Mainnet]: 'mainnet',
[ChainID.Testnet]: 'testnet',
};
interface BaseChainConfig {
blockchain: Blockchains;
}
@@ -75,8 +70,8 @@ export type NetworkModes = keyof typeof DefaultNetworkModes;
const DEFAULT_SERVER_MAINNET = 'https://stacks-node-api.stacks.co';
export const DEFAULT_SERVER_TESTNET = 'https://stacks-node-api.testnet.stacks.co';
export const TOKEN_METADATA_API_BASE_URL_MAINNET = 'https://api.hiro.so';
export const TOKEN_METADATA_API_BASE_URL_TESTNET = 'https://api.testnet.hiro.so';
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 BITCOIN_API_BASE_URL_MAINNET = 'https://blockstream.info/api';
export const BITCOIN_API_BASE_URL_TESTNET = 'https://blockstream.info/testnet/api';

View File

@@ -18,11 +18,11 @@ export interface StacksFungibleTokenAsset {
contractAddress: string;
contractAssetName: string;
contractName: string;
decimals?: number;
decimals: number;
hasMemo: boolean;
imageCanonicalUri?: string;
name?: string;
symbol?: string;
imageCanonicalUri: string;
name: string;
symbol: string;
}
export interface StacksNonFungibleTokenAsset {

View File

@@ -1,25 +0,0 @@
interface NftMetadataAttribute {
trait_type: string;
display_type: string;
value: string;
}
interface NftMetadataProperty {
type: string;
description: string;
}
export interface StacksNftMetadata {
sip: number;
name: string;
description: string;
image: string;
cached_image: string;
attributes: NftMetadataAttribute[];
properties: { [key: string]: NftMetadataProperty };
}
export interface StacksNftMetadataResponse {
token_uri: string;
metadata: StacksNftMetadata;
}

View File

@@ -1241,10 +1241,10 @@
dependencies:
"@hapi/hoek" "^9.0.0"
"@hirosystems/token-metadata-api-client@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@hirosystems/token-metadata-api-client/-/token-metadata-api-client-1.0.1.tgz#b7dd8cdd7c17d47cdde8fca2a53f7af5903c6eb9"
integrity sha512-JN2PVx4K0FzBjqOxxJP1x3+YxAB4E1RFvFepgknFQJePIa2RVxFnVyJD3fck7+s8WW27RuEDWQfpOxbspzBbNw==
"@hirosystems/token-metadata-api-client@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@hirosystems/token-metadata-api-client/-/token-metadata-api-client-1.0.3.tgz#3d941449d32f2e73848c76dfc2b39784229b5a13"
integrity sha512-FGQAsuEELIYo/boOy9EScVtrcud+8BAcSuPM+BDN6PRUZNqLXHlR7G5ufB1oHA1RnNioP/Mzc+yQkzatD6ak2A==
dependencies:
isomorphic-fetch "^3.0.0"