|
1 | 1 | /* eslint-disable unicorn/no-nested-ternary */ |
2 | | -import { Asset, AssetProvider, Cardano, ProviderUtil } from '@cardano-sdk/core'; |
| 2 | +import { Asset, AssetProvider, Cardano, GetAssetArgs, GetAssetsArgs } from '@cardano-sdk/core'; |
3 | 3 | import { BlockFrostAPI, Responses } from '@blockfrost/blockfrost-js'; |
4 | | -import { blockfrostMetadataToTxMetadata, fetchSequentially, healthCheck, toProviderError } from '../../util'; |
| 4 | +import { BlockfrostProvider } from '../../util/BlockfrostProvider/BlockfrostProvider'; |
| 5 | +import { blockfrostMetadataToTxMetadata, blockfrostToProviderError, fetchSequentially } from '../../util'; |
5 | 6 | import { replaceNullsWithUndefineds } from '@cardano-sdk/util'; |
6 | 7 |
|
7 | | -const mapMetadata = ( |
8 | | - assetId: Cardano.AssetId, |
9 | | - offChain: Responses['asset']['metadata'] |
10 | | -): Asset.TokenMetadata | null => { |
11 | | - const { logo, ...metadata } = { ...offChain }; |
12 | | - |
13 | | - if (Object.values(metadata).every((value) => value === undefined || value === null)) return null; |
14 | | - |
15 | | - return { |
16 | | - ...replaceNullsWithUndefineds(metadata), |
17 | | - assetId, |
18 | | - desc: metadata.description, |
19 | | - // The other type option is any[] - not sure what it means, omitting if no string. |
20 | | - icon: typeof logo === 'string' ? logo : undefined |
21 | | - }; |
22 | | -}; |
23 | | - |
24 | | -/** |
25 | | - * Connect to the [Blockfrost service](https://docs.blockfrost.io/) |
26 | | - * |
27 | | - * @param {BlockFrostAPI} blockfrost BlockFrostAPI instance |
28 | | - * @returns {AssetProvider} AssetProvider |
29 | | - * @throws ProviderFailure |
30 | | - */ |
31 | | -export const blockfrostAssetProvider = (blockfrost: BlockFrostAPI): AssetProvider => { |
32 | | - const getLastMintedTx = async (assetId: Cardano.AssetId): Promise<Responses['asset_history'][number] | undefined> => { |
| 8 | +export class BlockfrostAssetProvider extends BlockfrostProvider implements AssetProvider { |
| 9 | + protected async getLastMintedTx(assetId: Cardano.AssetId): Promise<Responses['asset_history'][number] | undefined> { |
33 | 10 | const [lastMintedTx] = await fetchSequentially({ |
34 | 11 | arg: assetId.toString(), |
35 | 12 | haveEnoughItems: (items: Responses['asset_history']): boolean => items.length > 0, |
36 | 13 | paginationOptions: { order: 'desc' }, |
37 | | - request: blockfrost.assetsHistory.bind<BlockFrostAPI['assetsHistory']>(blockfrost), |
| 14 | + request: this.blockfrost.assetsHistory.bind<BlockFrostAPI['assetsHistory']>(this.blockfrost), |
38 | 15 | responseTranslator: (response): Responses['asset_history'] => response.filter((tx) => tx.action === 'minted') |
39 | 16 | }); |
40 | 17 |
|
41 | 18 | if (!lastMintedTx) return undefined; |
42 | 19 | return lastMintedTx; |
43 | | - }; |
| 20 | + } |
44 | 21 |
|
45 | | - const getNftMetadata = async ( |
| 22 | + protected async getNftMetadata( |
46 | 23 | asset: Pick<Asset.AssetInfo, 'name' | 'policyId'>, |
47 | 24 | lastMintedTxHash: string |
48 | | - ): Promise<Asset.NftMetadata | null> => { |
49 | | - const metadata = await blockfrost.txsMetadata(lastMintedTxHash); |
| 25 | + ): Promise<Asset.NftMetadata | null> { |
| 26 | + const metadata = await this.blockfrost.txsMetadata(lastMintedTxHash); |
50 | 27 | // Not sure if types are correct, missing 'label', but it's present in docs |
51 | 28 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
52 | 29 | const metadatumMap = blockfrostMetadataToTxMetadata(metadata as any); |
53 | 30 | return Asset.NftMetadata.fromMetadatum(asset, metadatumMap, console) ?? null; |
54 | | - }; |
| 31 | + } |
55 | 32 |
|
56 | | - const getAsset: AssetProvider['getAsset'] = async ({ assetId, extraData }) => { |
57 | | - const response = await blockfrost.assetsById(assetId.toString()); |
58 | | - const name = Cardano.AssetId.getAssetName(assetId); |
59 | | - const policyId = Cardano.PolicyId(response.policy_id); |
60 | | - const quantity = BigInt(response.quantity); |
| 33 | + protected mapMetadata( |
| 34 | + assetId: Cardano.AssetId, |
| 35 | + offChain: Responses['asset']['metadata'] |
| 36 | + ): Asset.TokenMetadata | null { |
| 37 | + const { logo, ...metadata } = { ...offChain }; |
61 | 38 |
|
62 | | - const nftMetadata = async () => { |
63 | | - let lastMintedTxHash: string = response.initial_mint_tx_hash; |
64 | | - if (response.mint_or_burn_count > 1) { |
65 | | - const lastMintedTx = await getLastMintedTx(assetId); |
66 | | - if (lastMintedTx) lastMintedTxHash = lastMintedTx.tx_hash; |
67 | | - } |
68 | | - return getNftMetadata({ name, policyId }, lastMintedTxHash); |
69 | | - }; |
| 39 | + if (Object.values(metadata).every((value) => value === undefined || value === null)) return null; |
70 | 40 |
|
71 | 41 | return { |
| 42 | + ...replaceNullsWithUndefineds(metadata), |
72 | 43 | assetId, |
73 | | - fingerprint: Cardano.AssetFingerprint(response.fingerprint), |
74 | | - // history: extraData?.history ? await history() : undefined, |
75 | | - mintOrBurnCount: response.mint_or_burn_count, |
76 | | - name, |
77 | | - nftMetadata: extraData?.nftMetadata ? await nftMetadata() : undefined, |
78 | | - policyId, |
79 | | - quantity, |
80 | | - supply: quantity, |
81 | | - tokenMetadata: extraData?.tokenMetadata ? mapMetadata(assetId, response.metadata) : undefined |
| 44 | + desc: metadata.description, |
| 45 | + // The other type option is any[] - not sure what it means, omitting if no string. |
| 46 | + icon: typeof logo === 'string' ? logo : undefined |
82 | 47 | }; |
83 | | - }; |
| 48 | + } |
84 | 49 |
|
85 | | - const getAssets: AssetProvider['getAssets'] = async ({ assetIds, extraData }) => |
86 | | - Promise.all(assetIds.map((assetId) => getAsset({ assetId, extraData }))); |
| 50 | + async getAsset({ assetId, extraData }: GetAssetArgs) { |
| 51 | + try { |
| 52 | + const response = await this.blockfrost.assetsById(assetId.toString()); |
| 53 | + const name = Cardano.AssetId.getAssetName(assetId); |
| 54 | + const policyId = Cardano.PolicyId(response.policy_id); |
| 55 | + const quantity = BigInt(response.quantity); |
87 | 56 |
|
88 | | - const providerFunctions: AssetProvider = { |
89 | | - getAsset, |
90 | | - getAssets, |
91 | | - healthCheck: healthCheck.bind(undefined, blockfrost) |
92 | | - }; |
| 57 | + const nftMetadata = async () => { |
| 58 | + let lastMintedTxHash: string = response.initial_mint_tx_hash; |
| 59 | + if (response.mint_or_burn_count > 1) { |
| 60 | + const lastMintedTx = await this.getLastMintedTx(assetId); |
| 61 | + if (lastMintedTx) lastMintedTxHash = lastMintedTx.tx_hash; |
| 62 | + } |
| 63 | + return this.getNftMetadata({ name, policyId }, lastMintedTxHash); |
| 64 | + }; |
93 | 65 |
|
94 | | - return ProviderUtil.withProviderErrors(providerFunctions, toProviderError); |
95 | | -}; |
| 66 | + return { |
| 67 | + assetId, |
| 68 | + fingerprint: Cardano.AssetFingerprint(response.fingerprint), |
| 69 | + // history: extraData?.history ? await history() : undefined, |
| 70 | + mintOrBurnCount: response.mint_or_burn_count, |
| 71 | + name, |
| 72 | + nftMetadata: extraData?.nftMetadata ? await nftMetadata() : undefined, |
| 73 | + policyId, |
| 74 | + quantity, |
| 75 | + supply: quantity, |
| 76 | + tokenMetadata: extraData?.tokenMetadata ? this.mapMetadata(assetId, response.metadata) : undefined |
| 77 | + }; |
| 78 | + } catch (error) { |
| 79 | + throw blockfrostToProviderError(error); |
| 80 | + } |
| 81 | + } |
| 82 | + async getAssets({ assetIds, extraData }: GetAssetsArgs) { |
| 83 | + return Promise.all(assetIds.map((assetId) => this.getAsset({ assetId, extraData }))); |
| 84 | + } |
| 85 | +} |
0 commit comments