diff --git a/package.json b/package.json index a3f7d348f82..2f4b6341da8 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.6.3", + "@leather-wallet/models": "0.6.4", "@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 cdfb539aa40..707b243a083 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.6.3 - version: 0.6.3 + specifier: 0.6.4 + version: 0.6.4 '@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.6.3: - resolution: {integrity: sha512-Q8GIE4sH6yg57LtF28Ya/bDAueQdf++M0yAMMJKSJSW4Ue62HNcX2s60RdxAE/mlKQHZKK8JL9y2jwXKVNlJXA==} + /@leather-wallet/models@0.6.4: + resolution: {integrity: sha512-VPtMoeUeulHaRk5ECo1OCtwzV3QLh9uihPjr5fd1dmWPCiiy2sVHOM2g5+VU/NRaUJU7aBB5Pjmj3pyatWvSqg==} dependencies: bignumber.js: 9.1.2 dev: false diff --git a/src/app/common/stacks-utils.ts b/src/app/common/stacks-utils.ts index 9d4b9ab2004..071850d2ed4 100644 --- a/src/app/common/stacks-utils.ts +++ b/src/app/common/stacks-utils.ts @@ -3,6 +3,7 @@ import BigNumber from 'bignumber.js'; import { c32addressDecode } from 'c32check'; import { NetworkConfiguration, STX_DECIMALS } from '@shared/constants'; +import { logger } from '@shared/logger'; import { isValidUrl } from '@shared/utils/validate-url'; import { abbreviateNumber } from '@app/common/utils'; @@ -76,3 +77,74 @@ export function getSafeImageCanonicalUri(imageCanonicalUri: string, name: string ? imageCanonicalUri : ''; } + +/** + * Gets the contract name of a fully qualified name of an asset. + * + * @param contractId - the source string: [principal].[contract-name] or [principal].[contract-name]::[asset-name] + */ +export const getStacksContractName = (contractId: string): string => { + if (contractId.includes('.')) { + const parts = contractId?.split('.'); + if (contractId.includes('::')) { + return parts[1].split('::')[0]; + } + return parts[1]; + } + logger.warn( + 'getStacksContractName: does not contain a period, does not appear to be a contractId.', + contractId + ); + return contractId; +}; + +/** + * Gets the asset name from a a fully qualified name of an asset. + * + * @param contractId - the fully qualified name of the asset: [principal].[contract-name]::[asset-name] + */ +const getStacksContractAssetName = (contractId: string): string => { + if (!contractId.includes('::')) { + logger.warn( + 'getStacksContractAssetName: does not contain "::", does not appear to be a fully qualified name of an asset.', + contractId + ); + return contractId; + } + return contractId.split('::')[1]; +}; + +/** + * Gets the parts that make up a fully qualified name of an asset. + * + * @param contractId - the fully qualified name of the asset: [principal].[contract-name]::[asset-name] + */ +export const getStacksContractIdStringParts = ( + contractId: string +): { + contractAddress: string; + contractAssetName: string; + contractName: string; +} => { + if (!contractId.includes('.') || !contractId.includes('::')) { + logger.warn( + 'getStacksContractIdStringParts: does not contain a period or "::", does not appear to be a fully qualified name of an asset.', + contractId + ); + return { + contractAddress: contractId, + contractAssetName: contractId, + contractName: contractId, + }; + } + + const contractAddress = contractId.split('.')[0]; + const contractAssetName = getStacksContractAssetName(contractId); + const contractName = getStacksContractName(contractId); + + return { + contractAddress, + contractAssetName, + contractName, + }; +}; diff --git a/src/app/common/transactions/stacks/transaction.utils.ts b/src/app/common/transactions/stacks/transaction.utils.ts index a4aab96e1bb..f6d86c70e8f 100644 --- a/src/app/common/transactions/stacks/transaction.utils.ts +++ b/src/app/common/transactions/stacks/transaction.utils.ts @@ -18,8 +18,7 @@ import { BigNumber } from 'bignumber.js'; import { StacksTx, StacksTxStatus } from '@shared/models/transactions/stacks-transaction.model'; -import { stacksValue } from '@app/common/stacks-utils'; -import { getContractName } from '@app/ui/utils/get-contract-name'; +import { getStacksContractName, stacksValue } from '@app/common/stacks-utils'; import { truncateMiddle } from '@app/ui/utils/truncate-middle'; export const statusFromTx = (tx: StacksTx): StacksTxStatus => { @@ -74,7 +73,7 @@ export const getTxTitle = (tx: StacksTx) => { case 'contract_call': return tx.contract_call.function_name; case 'smart_contract': - return getContractName(tx.smart_contract.contract_id); + return getStacksContractName(tx.smart_contract.contract_id); case 'coinbase': return `Coinbase ${(tx as CoinbaseTransaction).block_height}`; case 'poison_microblock': diff --git a/src/app/common/utils.ts b/src/app/common/utils.ts index a561d212dcf..9f9e31198dc 100644 --- a/src/app/common/utils.ts +++ b/src/app/common/utils.ts @@ -256,7 +256,7 @@ export function with0x(value: string): string { return !value.startsWith('0x') ? `0x${value}` : value; } -export function pullContractIdFromIdentity(identifier: string) { +export function getPrincipalFromContractId(identifier: string) { return identifier.split('::')[0]; } diff --git a/src/app/features/activity-list/components/transaction-list/stacks-transaction/ft-transfer-item.tsx b/src/app/features/activity-list/components/transaction-list/stacks-transaction/ft-transfer-item.tsx index e9413e82f4b..97e9aedc552 100644 --- a/src/app/features/activity-list/components/transaction-list/stacks-transaction/ft-transfer-item.tsx +++ b/src/app/features/activity-list/components/transaction-list/stacks-transaction/ft-transfer-item.tsx @@ -11,7 +11,7 @@ import { calculateTokenTransferAmount, getTxCaption, } from '@app/common/transactions/stacks/transaction.utils'; -import { pullContractIdFromIdentity } from '@app/common/utils'; +import { getPrincipalFromContractId } from '@app/common/utils'; import { StacksAssetAvatar } from '@app/components/stacks-asset-avatar'; import { StacksTransactionItem } from '@app/components/stacks-transaction-item/stacks-transaction-item'; import { useGetFungibleTokenMetadataQuery } from '@app/query/stacks/token-metadata/fungible-tokens/fungible-token-metadata.query'; @@ -28,7 +28,7 @@ interface FtTransferItemProps { } export function FtTransferItem({ ftTransfer, parentTx }: FtTransferItemProps) { const { data: assetMetadata } = useGetFungibleTokenMetadataQuery( - pullContractIdFromIdentity(ftTransfer.asset_identifier) + getPrincipalFromContractId(ftTransfer.asset_identifier) ); const currentAccount = useCurrentStacksAccount(); const isOriginator = ftTransfer.sender === currentAccount?.address; 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 d587a6af6a7..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 @@ -20,7 +20,7 @@ export function Sip10TokenAssetItem({ marketData, onSelectAsset, }: Sip10TokenAssetItemProps) { - const name = spamFilter(info.tokenName); + const name = spamFilter(info.name); const fiatBalance = convertAssetBalanceToFiat({ balance: balance.availableBalance, marketData, 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 a059c6fcac4..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 @@ -38,7 +38,7 @@ export function Sip10TokenAssetListUnsupported({ {tokens.map(token => ( ( { const tokenInfo = supportedCurrencies .filter(isDefined) - .find(token => pullContractIdFromIdentity(token.contractAddress) === principal); + .find(token => getPrincipalFromContractId(token.contractAddress) === principal); if (!prices || !tokenInfo) return createMarketData(createMarketPair(symbol, 'USD'), createMoney(0, 'USD')); const currency = tokenInfo.id as Currency; @@ -68,7 +68,7 @@ function useCreateSwapAsset() { } const currency = tokenInfo.id as Currency; - const principal = pullContractIdFromIdentity(tokenInfo.contractAddress); + const principal = getPrincipalFromContractId(tokenInfo.contractAddress); const availableBalance = sip10Tokens.find(token => token.info.contractId === principal) ?.balance.availableBalance; diff --git a/src/app/query/stacks/sip10/sip10-tokens.utils.ts b/src/app/query/stacks/sip10/sip10-tokens.utils.ts index f77ec6f9df6..444c8751b63 100644 --- a/src/app/query/stacks/sip10/sip10-tokens.utils.ts +++ b/src/app/query/stacks/sip10/sip10-tokens.utils.ts @@ -3,33 +3,29 @@ import type { CryptoAssetBalance, Sip10CryptoAssetInfo } from '@leather-wallet/m import { isUndefined } from '@shared/utils'; -import { getTicker } from '@app/common/utils'; +import { getStacksContractIdStringParts } from '@app/common/stacks-utils'; +import { getPrincipalFromContractId, getTicker } from '@app/common/utils'; import type { SwapAsset } from '@app/query/common/alex-sdk/alex-sdk.hooks'; -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, address } = getAssetStringParts(key); - const tokenName = ftAsset.name ? ftAsset.name : assetName; + const { contractAssetName } = getStacksContractIdStringParts(key); + const name = ftAsset.name || contractAssetName; return { canTransfer: isTransferableSip10Token(ftAsset), - contractAddress: address, - contractAssetName: assetName, - contractId, - contractName, + contractId: key, decimals: ftAsset.decimals ?? 0, hasMemo: isTransferableSip10Token(ftAsset), imageCanonicalUri: ftAsset.image_canonical_uri ?? '', - tokenName, - symbol: ftAsset.symbol ?? getTicker(tokenName), + name, + symbol: ftAsset.symbol || getTicker(name), }; } @@ -44,11 +40,12 @@ export function filterSip10Tokens( filter: Sip10CryptoAssetFilter ) { return tokens.filter(token => { + const principal = getPrincipalFromContractId(token.info.contractId); if (filter === 'supported') { - return swapAssets.some(swapAsset => swapAsset.principal === token.info.contractId); + return swapAssets.some(swapAsset => swapAsset.principal === principal); } if (filter === 'unsupported') { - return !swapAssets.some(swapAsset => swapAsset.principal === token.info.contractId); + return !swapAssets.some(swapAsset => swapAsset.principal === principal); } return true; }); 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 eaa4855f5d4..66c90d10392 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 @@ -5,7 +5,8 @@ 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 { getStacksContractIdStringParts } from '@app/common/stacks-utils'; +import { getPrincipalFromContractId, getTicker } 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'; @@ -57,16 +58,18 @@ export function useGetFungibleTokensBalanceMetadataQuery( return useQueries({ queries: Object.entries(ftBalances).map(([key, value]) => { - const contractId = pullContractIdFromIdentity(key); + const principal = getPrincipalFromContractId(key); return { - enabled: !!contractId, - queryKey: ['get-ft-metadata', contractId, network.chain.stacks.url], - queryFn: fetchFungibleTokenMetadata(client, limiter)(contractId), + enabled: !!principal, + queryKey: ['get-ft-metadata', principal, network.chain.stacks.url], + queryFn: fetchFungibleTokenMetadata(client, limiter)(principal), select: (resp: FtAssetResponse) => { if (!(resp && isFtAsset(resp))) return; - const symbol = resp.symbol ?? getTicker(resp.name ?? ''); + const { contractAssetName } = getStacksContractIdStringParts(key); + const name = resp.name || contractAssetName; + const symbol = resp.symbol || getTicker(name); return { - contractId, + contractId: key, balance: createCryptoAssetBalance( createMoney(new BigNumber(value.balance), symbol, resp.decimals ?? 0) ), @@ -85,14 +88,14 @@ export function useGetFungibleTokensMetadataQuery(keys: string[]) { return useQueries({ queries: keys.map(key => { - const contractId = pullContractIdFromIdentity(key); + const principal = getPrincipalFromContractId(key); return { - enabled: !!contractId, - queryKey: ['get-ft-metadata', contractId, network.chain.stacks.url], - queryFn: fetchFungibleTokenMetadata(client, limiter)(contractId), + enabled: !!principal, + queryKey: ['get-ft-metadata', principal, network.chain.stacks.url], + queryFn: fetchFungibleTokenMetadata(client, limiter)(principal), select: (resp: FtAssetResponse) => { if (!(resp && isFtAsset(resp))) return; - return createSip10CryptoAssetInfo(contractId, key, resp); + return createSip10CryptoAssetInfo(key, resp); }, ...queryOptions, }; diff --git a/src/app/query/stacks/token-metadata/non-fungible-tokens/non-fungible-token-metadata.query.ts b/src/app/query/stacks/token-metadata/non-fungible-tokens/non-fungible-token-metadata.query.ts index c8da035f92c..6709965471e 100644 --- a/src/app/query/stacks/token-metadata/non-fungible-tokens/non-fungible-token-metadata.query.ts +++ b/src/app/query/stacks/token-metadata/non-fungible-tokens/non-fungible-token-metadata.query.ts @@ -1,7 +1,7 @@ import { hexToCV } from '@stacks/transactions'; import { UseQueryResult, useQueries } from '@tanstack/react-query'; -import { pullContractIdFromIdentity } from '@app/common/utils'; +import { getPrincipalFromContractId } from '@app/common/utils'; import { QueryPrefixes } from '@app/query/query-prefixes'; import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; import { useStacksClient } from '@app/store/common/api-clients.hooks'; @@ -34,7 +34,7 @@ export function useGetNonFungibleTokenMetadataListQuery( return useQueries({ queries: (nftHoldings.data?.results ?? []).map(nft => { - const principal = pullContractIdFromIdentity(nft.asset_identifier); + const principal = getPrincipalFromContractId(nft.asset_identifier); const tokenId = getTokenId(nft.value.hex); return { diff --git a/src/app/store/transactions/token-transfer.hooks.ts b/src/app/store/transactions/token-transfer.hooks.ts index c88cc12bcb1..e33ea18ca07 100644 --- a/src/app/store/transactions/token-transfer.hooks.ts +++ b/src/app/store/transactions/token-transfer.hooks.ts @@ -21,7 +21,7 @@ import { logger } from '@shared/logger'; import type { StacksSendFormValues, StacksTransactionFormValues } from '@shared/models/form.model'; import { stxToMicroStx } from '@app/common/money/unit-conversion'; -import { ftUnshiftDecimals } from '@app/common/stacks-utils'; +import { ftUnshiftDecimals, getStacksContractIdStringParts } from '@app/common/stacks-utils'; import { GenerateUnsignedTransactionOptions, generateUnsignedTransaction, @@ -83,7 +83,9 @@ export function useGenerateFtTokenTransferUnsignedTx(info: Sip10CryptoAssetInfo) const { data: nextNonce } = useNextNonce(); const account = useCurrentStacksAccount(); const network = useCurrentStacksNetworkState(); - const { contractName, contractAddress, contractAssetName } = info; + const { contractId } = info; + const { contractAddress, contractAssetName, contractName } = + getStacksContractIdStringParts(contractId); return useCallback( async (values?: StacksSendFormValues | StacksTransactionFormValues) => { diff --git a/src/app/ui/utils/README.md b/src/app/ui/utils/README.md index cedad0310cb..910406b2526 100644 --- a/src/app/ui/utils/README.md +++ b/src/app/ui/utils/README.md @@ -2,48 +2,6 @@ Legacy utils taken from [Stacks UI](https://github.com/hirosystems/ui). Moved here initially but can be moved to monorepo -## Transactions - -### getContractName - -This will parse a string and return the contract name. - -```ts -const contract = - 'ST12EY99GS4YKP0CP2CFW6SEPWQ2CGVRWK5GHKDRV.market' || - 'ST12EY99GS4YKP0CP2CFW6SEPWQ2CGVRWK5GHKDRV.market::asset-name'; - -const name = getContractName(contract); - -// market -``` - -### getAssetName - -This will parse a fully qualified asset name string and pull out the name of the asset. - -```ts -const contract = 'ST12EY99GS4YKP0CP2CFW6SEPWQ2CGVRWK5GHKDRV.market::asset-name'; - -const asset = getAssetName(contract); - -// asset-name -``` - -### getAssetStringParts - -This will parse a fully qualified asset name string and return the various parts: `address`, `contractName`, `assetName`. - -```ts -const contract = 'ST12EY99GS4YKP0CP2CFW6SEPWQ2CGVRWK5GHKDRV.market::asset-name'; - -const { address, contractName, assetName } = getAssetStringParts(contract); - -// ST12EY99GS4YKP0CP2CFW6SEPWQ2CGVRWK5GHKDRV -// market -// asset-name -``` - ## Strings ### truncateHex diff --git a/src/app/ui/utils/get-asset-name.ts b/src/app/ui/utils/get-asset-name.ts deleted file mode 100644 index e178340fb14..00000000000 --- a/src/app/ui/utils/get-asset-name.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * getAssetName - * - * Gets the asset name from a string. - * - * @param fullyQualifiedName - the fully qualified name of the asset: [principal].[contract-name]::[asset-name] - */ -export const getAssetName = (fullyQualifiedName: string): string => { - if (!fullyQualifiedName.includes('::')) { - // console.warn( - // 'getAssetName: does not contain "::", does not appear to be a fully qualified name of an asset.', - // { - // fullyQualifiedName, - // } - // ); - return fullyQualifiedName; - } - return fullyQualifiedName.split('::')[1]; -}; - -// get rebasing this then get to work on the next list diff --git a/src/app/ui/utils/get-asset-string-parts.ts b/src/app/ui/utils/get-asset-string-parts.ts deleted file mode 100644 index a82b44ed3fb..00000000000 --- a/src/app/ui/utils/get-asset-string-parts.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { getAssetName } from './get-asset-name'; -import { getContractName } from './get-contract-name'; - -/** - * getAssetStringParts - * - * Gets the parts that make up a fully qualified name of an asset - * - * @param fullyQualifiedName - the fully qualified name of the asset: [principal].[contract-name]::[asset-name] - */ -export const getAssetStringParts = ( - fullyQualifiedName: string -): { - address: string; - contractName: string; - assetName: string; -} => { - if (!fullyQualifiedName.includes('.') || !fullyQualifiedName.includes('::')) { - // console.warn( - // 'getAssetStringParts: does not contain a period or "::", does not appear to be a fully qualified name of an asset.', - // { - // fullyQualifiedName, - // } - // ); - return { - address: fullyQualifiedName, - contractName: fullyQualifiedName, - assetName: fullyQualifiedName, - }; - } - - const address = fullyQualifiedName.split('.')[0]; - const contractName = getContractName(fullyQualifiedName); - const assetName = getAssetName(fullyQualifiedName); - - return { - address, - contractName, - assetName, - }; -}; diff --git a/src/app/ui/utils/get-contract-name.ts b/src/app/ui/utils/get-contract-name.ts deleted file mode 100644 index 59efe32a85e..00000000000 --- a/src/app/ui/utils/get-contract-name.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * getContractName - * - * Gets the contract name of a string: contract_id or fully qualified asset name. - * - * @param value - the source string: [principal].[contract-name] or [principal].[contract-name]::[asset-name] - */ -export const getContractName = (value: string): string => { - if (value.includes('.')) { - const parts = value?.split('.'); - if (value.includes('::')) { - return parts[1].split('::')[0]; - } - return parts[1]; - } - // #4476 TODO: migrate to log in Sentry - // console.warn('getContractName: does not contain a period, does not appear to be a contract_id.', { - // value, - // }); - return value; -};