Skip to content

Commit

Permalink
feat(cardano-services): implement missing Blockfrost providers featur…
Browse files Browse the repository at this point in the history
…es and tests
  • Loading branch information
ginnun committed Sep 6, 2024
1 parent 34d1ca8 commit 8df1252
Show file tree
Hide file tree
Showing 22 changed files with 1,500 additions and 1,142 deletions.
Original file line number Diff line number Diff line change
@@ -1,95 +1,85 @@
/* eslint-disable unicorn/no-nested-ternary */
import { Asset, AssetProvider, Cardano, ProviderUtil } from '@cardano-sdk/core';
import { Asset, AssetProvider, Cardano, GetAssetArgs, GetAssetsArgs } from '@cardano-sdk/core';
import { BlockFrostAPI, Responses } from '@blockfrost/blockfrost-js';
import { blockfrostMetadataToTxMetadata, fetchSequentially, healthCheck, toProviderError } from '../../util';
import { BlockfrostProvider } from '../../util/BlockfrostProvider/BlockfrostProvider';
import { blockfrostMetadataToTxMetadata, blockfrostToProviderError, fetchSequentially } from '../../util';
import { replaceNullsWithUndefineds } from '@cardano-sdk/util';

const mapMetadata = (
assetId: Cardano.AssetId,
offChain: Responses['asset']['metadata']
): Asset.TokenMetadata | null => {
const { logo, ...metadata } = { ...offChain };

if (Object.values(metadata).every((value) => value === undefined || value === null)) return null;

return {
...replaceNullsWithUndefineds(metadata),
assetId,
desc: metadata.description,
// The other type option is any[] - not sure what it means, omitting if no string.
icon: typeof logo === 'string' ? logo : undefined
};
};

/**
* Connect to the [Blockfrost service](https://docs.blockfrost.io/)
*
* @param {BlockFrostAPI} blockfrost BlockFrostAPI instance
* @returns {AssetProvider} AssetProvider
* @throws ProviderFailure
*/
export const blockfrostAssetProvider = (blockfrost: BlockFrostAPI): AssetProvider => {
const getLastMintedTx = async (assetId: Cardano.AssetId): Promise<Responses['asset_history'][number] | undefined> => {
export class BlockfrostAssetProvider extends BlockfrostProvider implements AssetProvider {
protected async getLastMintedTx(assetId: Cardano.AssetId): Promise<Responses['asset_history'][number] | undefined> {
const [lastMintedTx] = await fetchSequentially({
arg: assetId.toString(),
haveEnoughItems: (items: Responses['asset_history']): boolean => items.length > 0,
paginationOptions: { order: 'desc' },
request: blockfrost.assetsHistory.bind<BlockFrostAPI['assetsHistory']>(blockfrost),
request: this.blockfrost.assetsHistory.bind<BlockFrostAPI['assetsHistory']>(this.blockfrost),
responseTranslator: (response): Responses['asset_history'] => response.filter((tx) => tx.action === 'minted')
});

if (!lastMintedTx) return undefined;
return lastMintedTx;
};
}

const getNftMetadata = async (
protected async getNftMetadata(
asset: Pick<Asset.AssetInfo, 'name' | 'policyId'>,
lastMintedTxHash: string
): Promise<Asset.NftMetadata | null> => {
const metadata = await blockfrost.txsMetadata(lastMintedTxHash);
): Promise<Asset.NftMetadata | null> {
const metadata = await this.blockfrost.txsMetadata(lastMintedTxHash);
// Not sure if types are correct, missing 'label', but it's present in docs
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const metadatumMap = blockfrostMetadataToTxMetadata(metadata as any);
return Asset.NftMetadata.fromMetadatum(asset, metadatumMap, console) ?? null;
};
}

const getAsset: AssetProvider['getAsset'] = async ({ assetId, extraData }) => {
const response = await blockfrost.assetsById(assetId.toString());
const name = Cardano.AssetId.getAssetName(assetId);
const policyId = Cardano.PolicyId(response.policy_id);
const quantity = BigInt(response.quantity);
protected mapMetadata(
assetId: Cardano.AssetId,
offChain: Responses['asset']['metadata']
): Asset.TokenMetadata | null {
const { logo, ...metadata } = { ...offChain };

const nftMetadata = async () => {
let lastMintedTxHash: string = response.initial_mint_tx_hash;
if (response.mint_or_burn_count > 1) {
const lastMintedTx = await getLastMintedTx(assetId);
if (lastMintedTx) lastMintedTxHash = lastMintedTx.tx_hash;
}
return getNftMetadata({ name, policyId }, lastMintedTxHash);
};
if (Object.values(metadata).every((value) => value === undefined || value === null)) return null;

return {
...replaceNullsWithUndefineds(metadata),
assetId,
fingerprint: Cardano.AssetFingerprint(response.fingerprint),
// history: extraData?.history ? await history() : undefined,
mintOrBurnCount: response.mint_or_burn_count,
name,
nftMetadata: extraData?.nftMetadata ? await nftMetadata() : undefined,
policyId,
quantity,
supply: quantity,
tokenMetadata: extraData?.tokenMetadata ? mapMetadata(assetId, response.metadata) : undefined
desc: metadata.description,
// The other type option is any[] - not sure what it means, omitting if no string.
icon: typeof logo === 'string' ? logo : undefined
};
};
}

const getAssets: AssetProvider['getAssets'] = async ({ assetIds, extraData }) =>
Promise.all(assetIds.map((assetId) => getAsset({ assetId, extraData })));
async getAsset({ assetId, extraData }: GetAssetArgs) {
try {
const response = await this.blockfrost.assetsById(assetId.toString());
const name = Cardano.AssetId.getAssetName(assetId);
const policyId = Cardano.PolicyId(response.policy_id);
const quantity = BigInt(response.quantity);

const providerFunctions: AssetProvider = {
getAsset,
getAssets,
healthCheck: healthCheck.bind(undefined, blockfrost)
};
const nftMetadata = async () => {
let lastMintedTxHash: string = response.initial_mint_tx_hash;
if (response.mint_or_burn_count > 1) {
const lastMintedTx = await this.getLastMintedTx(assetId);
if (lastMintedTx) lastMintedTxHash = lastMintedTx.tx_hash;
}
return this.getNftMetadata({ name, policyId }, lastMintedTxHash);
};

return ProviderUtil.withProviderErrors(providerFunctions, toProviderError);
};
return {
assetId,
fingerprint: Cardano.AssetFingerprint(response.fingerprint),
// history: extraData?.history ? await history() : undefined,
mintOrBurnCount: response.mint_or_burn_count,
name,
nftMetadata: extraData?.nftMetadata ? await nftMetadata() : undefined,
policyId,
quantity,
supply: quantity,
tokenMetadata: extraData?.tokenMetadata ? this.mapMetadata(assetId, response.metadata) : undefined
};
} catch (error) {
throw blockfrostToProviderError(error);
}
}
async getAssets({ assetIds, extraData }: GetAssetsArgs) {
return Promise.all(assetIds.map((assetId) => this.getAsset({ assetId, extraData })));
}
}
Loading

0 comments on commit 8df1252

Please sign in to comment.