|
| 1 | +/* eslint-disable unicorn/no-nested-ternary */ |
| 2 | +import { Asset, AssetProvider, Cardano, GetAssetArgs, GetAssetsArgs } from '@cardano-sdk/core'; |
| 3 | +import { BlockFrostAPI, Responses } from '@blockfrost/blockfrost-js'; |
| 4 | +import { BlockfrostProvider } from '../../util/BlockfrostProvider/BlockfrostProvider'; |
| 5 | +import { blockfrostMetadataToTxMetadata, blockfrostToProviderError, fetchSequentially } from '../../util'; |
| 6 | +import { replaceNullsWithUndefineds } from '@cardano-sdk/util'; |
| 7 | + |
| 8 | +export class BlockfrostAssetProvider extends BlockfrostProvider implements AssetProvider { |
| 9 | + protected async getLastMintedTx(assetId: Cardano.AssetId): Promise<Responses['asset_history'][number] | undefined> { |
| 10 | + const [lastMintedTx] = await fetchSequentially({ |
| 11 | + arg: assetId.toString(), |
| 12 | + haveEnoughItems: (items: Responses['asset_history']): boolean => items.length > 0, |
| 13 | + paginationOptions: { order: 'desc' }, |
| 14 | + request: this.blockfrost.assetsHistory.bind<BlockFrostAPI['assetsHistory']>(this.blockfrost), |
| 15 | + responseTranslator: (response): Responses['asset_history'] => response.filter((tx) => tx.action === 'minted') |
| 16 | + }); |
| 17 | + |
| 18 | + if (!lastMintedTx) return undefined; |
| 19 | + return lastMintedTx; |
| 20 | + } |
| 21 | + |
| 22 | + protected async getNftMetadata( |
| 23 | + asset: Pick<Asset.AssetInfo, 'name' | 'policyId'>, |
| 24 | + lastMintedTxHash: string |
| 25 | + ): Promise<Asset.NftMetadata | null> { |
| 26 | + const metadata = await this.blockfrost.txsMetadata(lastMintedTxHash); |
| 27 | + // Not sure if types are correct, missing 'label', but it's present in docs |
| 28 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 29 | + const metadatumMap = blockfrostMetadataToTxMetadata(metadata as any); |
| 30 | + return Asset.NftMetadata.fromMetadatum(asset, metadatumMap, console) ?? null; |
| 31 | + } |
| 32 | + |
| 33 | + protected mapMetadata( |
| 34 | + assetId: Cardano.AssetId, |
| 35 | + offChain: Responses['asset']['metadata'] |
| 36 | + ): Asset.TokenMetadata | null { |
| 37 | + const { logo, ...metadata } = { ...offChain }; |
| 38 | + |
| 39 | + if (Object.values(metadata).every((value) => value === undefined || value === null)) return null; |
| 40 | + |
| 41 | + return { |
| 42 | + ...replaceNullsWithUndefineds(metadata), |
| 43 | + assetId, |
| 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 |
| 47 | + }; |
| 48 | + } |
| 49 | + |
| 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); |
| 56 | + |
| 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 | + }; |
| 65 | + |
| 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