diff --git a/packages/cardano-services/src/Asset/CardanoTokenRegistry.ts b/packages/cardano-services/src/Asset/CardanoTokenRegistry.ts index 127f087b711..e99b3098fd3 100644 --- a/packages/cardano-services/src/Asset/CardanoTokenRegistry.ts +++ b/packages/cardano-services/src/Asset/CardanoTokenRegistry.ts @@ -148,7 +148,15 @@ export class CardanoTokenRegistry implements TokenMetadataService { } } } catch (error) { - throw toProviderError(error, 'while fetching metadata from token registry'); + if (axios.isAxiosError(error)) { + throw new ProviderError( + ProviderFailure.ConnectionFailure, + error, + 'CardanoTokenRegistry failed to fetch asset metatada from the token registry server' + ); + } + + throw error; } return tokenMetadata; diff --git a/packages/cardano-services/src/Asset/DbSyncAssetProvider.ts b/packages/cardano-services/src/Asset/DbSyncAssetProvider.ts index 69c2db0fd2b..5c7fd8fb994 100644 --- a/packages/cardano-services/src/Asset/DbSyncAssetProvider.ts +++ b/packages/cardano-services/src/Asset/DbSyncAssetProvider.ts @@ -66,8 +66,17 @@ export class DbSyncAssetProvider extends DbSyncProvider implements AssetProvider if (extraData?.history) await this.loadHistory(assetInfo); if (extraData?.nftMetadata) assetInfo.nftMetadata = await this.#dependencies.ntfMetadataService.getNftMetadata(assetInfo); - if (extraData?.tokenMetadata) - assetInfo.tokenMetadata = (await this.#dependencies.tokenMetadataService.getTokenMetadata([assetId]))[0]; + if (extraData?.tokenMetadata) { + try { + assetInfo.tokenMetadata = (await this.#dependencies.tokenMetadataService.getTokenMetadata([assetId]))[0]; + } catch (error) { + if (error instanceof ProviderError && error.reason === ProviderFailure.ConnectionFailure) { + assetInfo.tokenMetadata = undefined; + } else { + throw error; + } + } + } return assetInfo; } diff --git a/packages/cardano-services/src/Asset/openApi.json b/packages/cardano-services/src/Asset/openApi.json index 7ffb3e9bb2d..95ddf2ae241 100644 --- a/packages/cardano-services/src/Asset/openApi.json +++ b/packages/cardano-services/src/Asset/openApi.json @@ -95,7 +95,9 @@ } }, "GetAssetRequest": { - "required": ["assetId"], + "required": [ + "assetId" + ], "type": "object", "properties": { "assetId": { diff --git a/packages/cardano-services/test/Asset/CardanoTokenRegistry.test.ts b/packages/cardano-services/test/Asset/CardanoTokenRegistry.test.ts index 30ce68ace20..585c433e57d 100644 --- a/packages/cardano-services/test/Asset/CardanoTokenRegistry.test.ts +++ b/packages/cardano-services/test/Asset/CardanoTokenRegistry.test.ts @@ -1,4 +1,4 @@ -import { Asset, Cardano, ProviderError } from '@cardano-sdk/core'; +import { Cardano, ProviderError, ProviderFailure } from '@cardano-sdk/core'; import { CardanoTokenRegistry, toCoreTokenMetadata } from '../../src/Asset'; import { InMemoryCache, Key } from '../../src/InMemoryCache'; import { IncomingMessage, createServer } from 'http'; @@ -135,11 +135,11 @@ describe('CardanoTokenRegistry', () => { }); it('returns metadata when subject exists', async () => { - const [metadata] = await tokenRegistry.getTokenMetadata([validAssetId]); + const metadata = await tokenRegistry.getTokenMetadata([validAssetId]); - expect(metadata).not.toBeNull(); + expect(metadata![0]).not.toBeNull(); - const result: Asset.TokenMetadata = { + const result = { decimals: 8, desc: 'SingularityNET', icon: 'testLogo', @@ -148,17 +148,17 @@ describe('CardanoTokenRegistry', () => { url: 'https://singularitynet.io/' }; - expect(metadata).toEqual(result); + expect(metadata![0]).toEqual(result); }); it('correctly returns null or metadata for request with good and bad assetIds', async () => { const firstResult = await tokenRegistry.getTokenMetadata([invalidAssetId, validAssetId]); const secondResult = await tokenRegistry.getTokenMetadata([validAssetId, invalidAssetId]); - expect(firstResult[0]).toBeNull(); - expect(firstResult[1]).not.toBeNull(); - expect(secondResult[0]).not.toBeNull(); - expect(secondResult[1]).toBeNull(); + expect(firstResult![0]).toBeNull(); + expect(firstResult![1]).not.toBeNull(); + expect(secondResult![0]).not.toBeNull(); + expect(secondResult![1]).toBeNull(); }); }); @@ -193,12 +193,12 @@ describe('CardanoTokenRegistry', () => { }); it('metadata are cached', async () => { - const [firstResult] = await tokenRegistry.getTokenMetadata([validAssetId]); - const [secondResult] = await tokenRegistry.getTokenMetadata([validAssetId]); + const firstResult = await tokenRegistry.getTokenMetadata([validAssetId]); + const secondResult = await tokenRegistry.getTokenMetadata([validAssetId]); expect(gotValues[0]).toBeUndefined(); - expect(gotValues[1]).toEqual(firstResult); - expect(firstResult).toEqual(secondResult); + expect(gotValues[1]).toEqual(firstResult![0]); + expect(firstResult![0]).toEqual(secondResult![0]); }); }); @@ -214,7 +214,13 @@ describe('CardanoTokenRegistry', () => { ({ closeMock, tokenMetadataServerUrl } = await mockTokenRegistry(() => ({ body: { subjects: [null] } }))); const tokenRegistry = new CardanoTokenRegistry({ logger }, { tokenMetadataServerUrl }); - await expect(tokenRegistry.getTokenMetadata([validAssetId])).rejects.toThrow(ProviderError); + await expect(tokenRegistry.getTokenMetadata([validAssetId])).rejects.toThrow( + new ProviderError( + ProviderFailure.Unknown, + undefined, + "Cannot destructure property 'subject' of 'record' as it is null. while evaluating metatada record null" + ) + ); }); it('record without the subject property', async () => { @@ -222,10 +228,19 @@ describe('CardanoTokenRegistry', () => { ({ closeMock, tokenMetadataServerUrl } = await mockTokenRegistry(() => ({ body: { subjects: [record] } }))); const tokenRegistry = new CardanoTokenRegistry({ logger }, { tokenMetadataServerUrl }); - await expect(tokenRegistry.getTokenMetadata([validAssetId])).rejects.toThrow(ProviderError); + await expect(tokenRegistry.getTokenMetadata([validAssetId])).rejects.toThrow( + new ProviderError( + ProviderFailure.InvalidResponse, + undefined, + 'Missing \'subject\' property in metadata record {"test":"test"}' + ) + ); }); it('internal server error', async () => { + const failedMetadata = null; + const succeededMetadata = { name: 'test' }; + let alreadyCalled = false; const record = () => { if (alreadyCalled) return { body: {}, code: 500 }; @@ -243,12 +258,16 @@ describe('CardanoTokenRegistry', () => { ({ closeMock, tokenMetadataServerUrl } = await mockTokenRegistry(record)); const tokenRegistry = new CardanoTokenRegistry({ logger }, { tokenMetadataServerUrl }); - const result = await tokenRegistry.getTokenMetadata([invalidAssetId, validAssetId]); - - await expect(tokenRegistry.getTokenMetadata([invalidAssetId, validAssetId])).rejects.toThrow(ProviderError); - - expect(result[0]).toBeNull(); - expect(result[1]).toStrictEqual({ name: 'test' }); + const firstSucceedResult = await tokenRegistry.getTokenMetadata([invalidAssetId, validAssetId]); + expect(firstSucceedResult).toEqual([failedMetadata, succeededMetadata]); + + await expect(tokenRegistry.getTokenMetadata([invalidAssetId, validAssetId])).rejects.toThrow( + new ProviderError( + ProviderFailure.ConnectionFailure, + null, + 'CardanoTokenRegistry failed to fetch asset metatada from the token registry server' + ) + ); }); }); }); diff --git a/packages/cardano-services/test/Asset/DbSyncAssetProvider.test.ts b/packages/cardano-services/test/Asset/DbSyncAssetProvider.test.ts index e1b64e775e1..cbd75315321 100644 --- a/packages/cardano-services/test/Asset/DbSyncAssetProvider.test.ts +++ b/packages/cardano-services/test/Asset/DbSyncAssetProvider.test.ts @@ -1,4 +1,5 @@ -import { Asset, Cardano, ProviderError } from '@cardano-sdk/core'; +/* eslint-disable @typescript-eslint/no-shadow */ +import { Asset, Cardano, ProviderError, ProviderFailure } from '@cardano-sdk/core'; import { CardanoTokenRegistry, DbSyncAssetProvider, @@ -43,7 +44,9 @@ describe('DbSyncAssetProvider', () => { }); it('rejects for not found assetId', async () => { - await expect(provider.getAsset({ assetId: notValidAssetId })).rejects.toThrow(ProviderError); + await expect(provider.getAsset({ assetId: notValidAssetId })).rejects.toThrow( + new ProviderError(ProviderFailure.NotFound, undefined, 'No entries found in multi_asset table') + ); }); it('returns an AssetInfo without extra data', async () => { @@ -80,4 +83,20 @@ describe('DbSyncAssetProvider', () => { name: 'macaron cake token' }); }); + + it('returns undefined asset token metadata if the token registry throws a network error', async () => { + const { tokenMetadataServerUrl, closeMock } = await mockTokenRegistry(() => ({ body: {}, code: 500 })); + const tokenMetadataService = new CardanoTokenRegistry({ logger }, { tokenMetadataServerUrl }); + + provider = new DbSyncAssetProvider({ db, logger, ntfMetadataService, tokenMetadataService }); + + const asset = await provider.getAsset({ + assetId: validAssetId, + extraData: { tokenMetadata: true } + }); + + expect(asset.tokenMetadata).toBeUndefined(); + tokenMetadataService.shutdown(); + await closeMock(); + }); });