Skip to content

Commit

Permalink
fix: asset models contractid inconsistencies
Browse files Browse the repository at this point in the history
  • Loading branch information
fbwoolf committed May 23, 2024
1 parent 093856e commit fcf8ada
Show file tree
Hide file tree
Showing 19 changed files with 123 additions and 174 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 72 additions & 0 deletions src/app/common/stacks-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
};
};
5 changes: 2 additions & 3 deletions src/app/common/transactions/stacks/transaction.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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':
Expand Down
2 changes: 1 addition & 1 deletion src/app/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function Sip10TokenAssetListUnsupported({
{tokens.map(token => (
<Sip10TokenAssetItem
balance={token.balance}
key={token.info.tokenName}
key={token.info.name}
info={token.info}
isLoading={isLoading}
marketData={priceAsMarketData(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Stack } from 'leather-styles/jsx';

import { getPrincipalFromContractId } from '@app/common/utils';
import { useAlexCurrencyPriceAsMarketData } from '@app/query/common/alex-sdk/alex-sdk.hooks';
import type { Sip10TokenAssetDetails } from '@app/query/stacks/sip10/sip10-tokens.hooks';

Expand All @@ -24,11 +25,11 @@ export function Sip10TokenAssetList({
{tokens.map(token => (
<Sip10TokenAssetItem
balance={token.balance}
key={token.info.tokenName}
key={token.info.name}
info={token.info}
isLoading={isLoading}
marketData={priceAsMarketData(
token.info.contractId,
getPrincipalFromContractId(token.info.contractId),
token.balance.availableBalance.symbol
)}
onSelectAsset={onSelectAsset}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function useSip10SendForm({ balance, info }: UseSip10SendFormArgs) {
function createFtAvatar() {
return {
avatar: info.contractId,
imageCanonicalUri: getSafeImageCanonicalUri(info.imageCanonicalUri, info.tokenName),
imageCanonicalUri: getSafeImageCanonicalUri(info.imageCanonicalUri, info.name),
};
}

Expand Down Expand Up @@ -76,7 +76,7 @@ export function useSip10SendForm({ balance, info }: UseSip10SendFormArgs) {

sendFormNavigate.toConfirmAndSignStacksSip10Transaction({
decimals: info.decimals,
name: info.tokenName,
name: info.name,
tx,
});
},
Expand Down
6 changes: 3 additions & 3 deletions src/app/query/common/alex-sdk/alex-sdk.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { isDefined } from '@shared/utils';

import { sortAssetsByName } from '@app/common/asset-utils';
import { convertAmountToFractionalUnit } from '@app/common/money/calculate-money';
import { pullContractIdFromIdentity } from '@app/common/utils';
import { getPrincipalFromContractId } from '@app/common/utils';
import { useCurrentStxAvailableUnlockedBalance } from '@app/query/stacks/balance/account-balance.hooks';
import { useTransferableSip10Tokens } from '@app/query/stacks/sip10/sip10-tokens.hooks';
import { useCurrentStacksAccountAddress } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
Expand Down Expand Up @@ -41,7 +41,7 @@ export function useAlexCurrencyPriceAsMarketData() {
(principal: string, symbol: string) => {
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;
Expand All @@ -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;

Expand Down
23 changes: 10 additions & 13 deletions src/app/query/stacks/sip10/sip10-tokens.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<FtMetadataResponse>) {
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),
};
}

Expand All @@ -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;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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)
),
Expand All @@ -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,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 4 additions & 2 deletions src/app/store/transactions/token-transfer.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) => {
Expand Down
Loading

0 comments on commit fcf8ada

Please sign in to comment.