Skip to content

Commit

Permalink
fix: is asset transferable logic, closes #2154
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranjamie committed Jan 25, 2022
1 parent 881e88d commit 6729219
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 25 deletions.
74 changes: 74 additions & 0 deletions src/app/common/transactions/is-transferable-asset.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { AssetWithMeta } from '../asset-types';
import { isTransferableAsset } from './is-transferable-asset';

describe(isTransferableAsset.name, () => {
test('assets with a name, symbol and decimals are allowed to be transferred', () => {
const asset = {
type: 'ft',
meta: {
name: 'Test token',
symbol: 'TEST',
decimals: 2,
},
} as AssetWithMeta;
expect(isTransferableAsset(asset)).toBeTruthy();
});

test('stella the cat token', () => {
const asset = {
type: 'ft',
subtitle: 'ST6G7…PSTK7.stella-the-cat',
contractAddress: 'ST6G7N19FKNW24XH5JQ5P5WR1DN10QWMKQSPSTK7',
contractName: 'stella-the-cat',
name: 'stella-token',
canTransfer: true,
meta: {
token_uri: 'https://example.com',
name: 'SteLLa the Cat',
description: '',
image_uri: '',
image_canonical_uri: '',
symbol: 'CAT',
decimals: 9,
tx_id: '0x56c6381874c8f6b152c8815d950764b8759b97660fdc50091f3c1368d7f1c514',
sender_address: 'ST6G7N19FKNW24XH5JQ5P5WR1DN10QWMKQSPSTK7',
},
} as unknown as AssetWithMeta;
expect(isTransferableAsset(asset)).toBeTruthy();
});

test('a token with no decimals is transferable', () => {
const asset = {
type: 'ft',
meta: {
token_uri: 'https://example.com',
name: 'SteLLa the Cat',
description: '',
image_uri: '',
image_canonical_uri: '',
symbol: 'CAT',
decimals: 0,
tx_id: '0x56c6381874c8f6b152c8815d950764b8759b97660fdc50091f3c1368d7f1c514',
sender_address: 'ST6G7N19FKNW24XH5JQ5P5WR1DN10QWMKQSPSTK7',
},
} as unknown as AssetWithMeta;
expect(isTransferableAsset(asset)).toBeTruthy();
});

test('assets missing either name, symbol or decimals may not be transferred', () => {
const asset = {
type: 'ft',
meta: {
name: 'Test token',
symbol: 'TEST',
decimals: undefined,
},
} as unknown as AssetWithMeta;
expect(isTransferableAsset(asset)).toBeFalsy();
});

test('NFTs cannot be sent', () => {
const asset = { type: 'nft' } as unknown as AssetWithMeta;
expect(isTransferableAsset(asset)).toBeFalsy();
});
});
14 changes: 14 additions & 0 deletions src/app/common/transactions/is-transferable-asset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AssetWithMeta } from '../asset-types';
import { isUndefined } from '../utils';

export function isTransferableAsset(asset: AssetWithMeta) {
if (asset.type === 'stx') return true;
if (asset.type === 'ft') {
return asset.meta
? !isUndefined(asset.meta.decimals) &&
!isUndefined(asset.meta.name) &&
!isUndefined(asset.meta.symbol)
: false;
}
return false;
}
4 changes: 4 additions & 0 deletions src/app/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,10 @@ export function isString(value: unknown): value is string {
return typeof value === 'string';
}

export function isUndefined(value: unknown) {
return typeof value === 'undefined';
}

export function isEmpty(value: Object) {
return Object.keys(value).length === 0;
}
Expand Down
30 changes: 29 additions & 1 deletion src/app/query/tokens/fungible-token-metadata.hook.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
import { useGetFungibleTokenMetadataQuery } from './fungible-token-metadata.query';
import { useMemo } from 'react';

import { useAssets } from '@app/store/assets/asset.hooks';
import { isTransferableAsset } from '@app/common/transactions/is-transferable-asset';
import { formatContractId } from '@app/common/utils';
import {
useGetFungibleTokenMetadataListQuery,
useGetFungibleTokenMetadataQuery,
} from './fungible-token-metadata.query';

export function useFungibleTokenMetadata(contractId: string) {
const { data: ftMetadata } = useGetFungibleTokenMetadataQuery(contractId);
return ftMetadata;
}

export function useAssetsWithMetadata() {
const assets = useAssets();
const assetMetadata = useGetFungibleTokenMetadataListQuery(
assets.map(a => formatContractId(a.contractAddress, a.contractName))
);
return useMemo(
() =>
assets
.map((asset, i) => ({ ...asset, meta: assetMetadata[i].data }))
.map(assetWithMetaData => ({
...assetWithMetaData,
canTransfer: isTransferableAsset(assetWithMetaData),
hasMemo: isTransferableAsset(assetWithMetaData),
})),
// We don't want to reevaluate on assetMetadata reference change
// eslint-disable-next-line react-hooks/exhaustive-deps
[assets]
);
}
19 changes: 1 addition & 18 deletions src/app/query/tokens/fungible-token-metadata.query.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { useMemo } from 'react';
import { useQueries, useQuery } from 'react-query';

import { useApi, Api } from '@app/store/common/api-clients.hooks';

import { useCurrentNetwork } from '@app/common/hooks/use-current-network';
import { useAssets } from '@app/store/assets/asset.hooks';
import { formatContractId } from '@app/common/utils';

const staleTime = 10 * 60 * 1000;

Expand All @@ -32,7 +28,7 @@ export function useGetFungibleTokenMetadataQuery(contractId: string) {
});
}

function useGetFungibleTokenMetadataListQuery(contractIds: string[]) {
export function useGetFungibleTokenMetadataListQuery(contractIds: string[]) {
const api = useApi();
const network = useCurrentNetwork();
return useQueries(
Expand All @@ -43,16 +39,3 @@ function useGetFungibleTokenMetadataListQuery(contractIds: string[]) {
}))
);
}

export function useAssetsWithMetadata() {
const assets = useAssets();
const assetMetadata = useGetFungibleTokenMetadataListQuery(
assets.map(a => formatContractId(a.contractAddress, a.contractName))
);
return useMemo(
() => assets.map((asset, i) => ({ ...asset, canTransfer: true, meta: assetMetadata[i].data })),
// We don't want to reevaluate on assetMetadata reference change
// eslint-disable-next-line react-hooks/exhaustive-deps
[assets]
);
}
15 changes: 9 additions & 6 deletions src/app/store/assets/asset.hooks.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useMemo } from 'react';
import { useAtomValue, useUpdateAtom } from 'jotai/utils';
import { baseAssetsAnchoredState, mergeAssetBalances } from '@app/store/assets/tokens';
import type { Asset, AssetWithMeta } from '@app/common/asset-types';
Expand All @@ -10,28 +11,30 @@ import {
} from '@app/query/balance/balance.hooks';
import { useCurrentAccountStxAddressState } from '../accounts/account.hooks';
import { transformAssets } from './utils';
import { useAssetsWithMetadata } from '@app/query/tokens/fungible-token-metadata.query';
import { getFullyQualifiedAssetName } from '@app/common/hooks/use-selected-asset';
import { useFungibleTokenMetadata } from '@app/query/tokens/fungible-token-metadata.hook';
import { useMemo } from 'react';
import {
useAssetsWithMetadata,
useFungibleTokenMetadata,
} from '@app/query/tokens/fungible-token-metadata.hook';
import { formatContractId } from '@app/common/utils';
import { isTransferableAsset } from '@app/common/transactions/is-transferable-asset';

export function useAssets() {
return useAtomValue(baseAssetsAnchoredState);
}

export function useTransferableAssets() {
const assets = useAssetsWithMetadata();
return assets.filter(a => !!a.meta || a.type !== 'nft') as AssetWithMeta[];
return assets.filter(asset => isTransferableAsset(asset));
}

export function useAssetWithMetadata(asset: Asset) {
const assetMetadata = useFungibleTokenMetadata(
formatContractId(asset.contractAddress, asset.contractName)
);
if (asset.type === 'ft') {
const canTransfer = !!assetMetadata;
return { ...asset, meta: assetMetadata, canTransfer, hasMemo: canTransfer } as AssetWithMeta;
const canTransfer = assetMetadata ? isTransferableAsset(asset) : false;
return { ...asset, meta: assetMetadata, canTransfer, hasMemo: canTransfer };
}
return asset as AssetWithMeta;
}
Expand Down

0 comments on commit 6729219

Please sign in to comment.