Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #141 from input-output-hk/feat/asset-info
feat: asset info
- Loading branch information
Showing
54 changed files
with
751 additions
and
143 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
node_modules | ||
docs | ||
docs | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
BLOCKFROST_API_KEY=testnetNElagmhpQDubE6Ic4XBUVJjV5DROyijO | ||
NETWORK_ID=0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('../../test/e2e.jest.config'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { Asset, AssetProvider, Cardano, util } from '@cardano-sdk/core'; | ||
import { BlockFrostAPI, Responses } from '@blockfrost/blockfrost-js'; | ||
import { Options } from '@blockfrost/blockfrost-js/lib/types'; | ||
import { fetchSequentially, withProviderErrors } from './util'; | ||
|
||
const mapMetadata = ( | ||
onChain: Responses['asset']['onchain_metadata'], | ||
offChain: Responses['asset']['metadata'] | ||
): Cardano.AssetMetadata => { | ||
const metadata = { ...onChain, ...offChain }; | ||
return { | ||
...util.replaceNullsWithUndefineds(metadata), | ||
desc: metadata.description, | ||
// The other type option is any[] - not sure what it means, omitting if no string. | ||
image: typeof metadata.image === 'string' ? metadata.image : undefined | ||
}; | ||
}; | ||
|
||
/** | ||
* Connect to the [Blockfrost service](https://docs.blockfrost.io/) | ||
* | ||
* @param {Options} options BlockFrostAPI options | ||
* @returns {AssetProvider} WalletProvider | ||
* @throws ProviderFailure | ||
*/ | ||
export const blockfrostAssetProvider = (options: Options): AssetProvider => { | ||
const blockfrost = new BlockFrostAPI(options); | ||
|
||
const getAssetHistory = async (assetId: string): Promise<Cardano.AssetMintOrBurn[]> => | ||
fetchSequentially({ | ||
arg: assetId, | ||
request: blockfrost.assetsHistory, | ||
responseTranslator: (response): Cardano.AssetMintOrBurn[] => | ||
response.map(({ action, amount, tx_hash }) => ({ | ||
action: action === 'minted' ? Cardano.AssetProvisioning.Mint : Cardano.AssetProvisioning.Burn, | ||
quantity: BigInt(amount), | ||
transactionId: tx_hash | ||
})) | ||
}); | ||
|
||
const getAsset: AssetProvider['getAsset'] = async (assetId) => { | ||
const response = await blockfrost.assetsById(assetId); | ||
const name = Buffer.from(Asset.util.assetNameFromAssetId(assetId), 'hex').toString('utf-8'); | ||
const quantity = BigInt(response.quantity); | ||
return { | ||
assetId, | ||
fingerprint: response.fingerprint, | ||
history: | ||
response.mint_or_burn_count === 1 | ||
? [{ action: Cardano.AssetProvisioning.Mint, quantity, transactionId: response.initial_mint_tx_hash }] | ||
: await getAssetHistory(assetId), | ||
metadata: mapMetadata(response.onchain_metadata, response.metadata), | ||
name, | ||
policyId: response.policy_id, | ||
quantity | ||
}; | ||
}; | ||
|
||
const providerFunctions: AssetProvider = { | ||
getAsset | ||
}; | ||
|
||
return withProviderErrors(providerFunctions); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export { WalletProvider } from '@cardano-sdk/core'; | ||
export * from './blockfrostProvider'; | ||
export * from './blockfrostWalletProvider'; | ||
export * from './blockfrostAssetProvider'; | ||
export { Options } from '@blockfrost/blockfrost-js/lib/types'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import { Error as BlockfrostError } from '@blockfrost/blockfrost-js'; | ||
import { PaginationOptions } from '@blockfrost/blockfrost-js/lib/types'; | ||
import { ProviderError, ProviderFailure } from '@cardano-sdk/core'; | ||
|
||
export const formatBlockfrostError = (error: unknown) => { | ||
const blockfrostError = error as BlockfrostError; | ||
if (typeof blockfrostError === 'string') { | ||
throw new ProviderError(ProviderFailure.Unknown, error, blockfrostError); | ||
} | ||
if (typeof blockfrostError !== 'object') { | ||
throw new ProviderError(ProviderFailure.Unknown, error, 'failed to parse error (response type)'); | ||
} | ||
const errorAsType1 = blockfrostError as { | ||
status_code: number; | ||
message: string; | ||
error: string; | ||
}; | ||
if (errorAsType1.status_code) { | ||
return errorAsType1; | ||
} | ||
const errorAsType2 = blockfrostError as { | ||
errno: number; | ||
message: string; | ||
code: string; | ||
}; | ||
if (errorAsType2.code) { | ||
const status_code = Number.parseInt(errorAsType2.code); | ||
if (!status_code) { | ||
throw new ProviderError(ProviderFailure.Unknown, error, 'failed to parse error (status code)'); | ||
} | ||
return { | ||
error: errorAsType2.errno.toString(), | ||
message: errorAsType1.message, | ||
status_code | ||
}; | ||
} | ||
throw new ProviderError(ProviderFailure.Unknown, error, 'failed to parse error (response json)'); | ||
}; | ||
|
||
export const toProviderError = (error: unknown) => { | ||
const { status_code } = formatBlockfrostError(error); | ||
if (status_code === 404) { | ||
throw new ProviderError(ProviderFailure.NotFound); | ||
} | ||
throw new ProviderError(ProviderFailure.Unknown, error, `status_code: ${status_code}`); | ||
}; | ||
|
||
export const withProviderErrors = <T>(providerImplementation: T) => | ||
Object.keys(providerImplementation).reduce((provider, key) => { | ||
provider[key] = (...args: any[]) => (providerImplementation as any)[key](...args).catch(toProviderError); | ||
return provider; | ||
}, {} as any) as T; | ||
|
||
export const fetchSequentially = async <Item, Arg, Response>( | ||
props: { | ||
arg: Arg; | ||
request: (arg: Arg, pagination: PaginationOptions) => Promise<Response[]>; | ||
responseTranslator?: (response: Response[], arg: Arg) => Item[]; | ||
}, | ||
itemsPerPage = 100, | ||
page = 1, | ||
accumulated: Item[] = [] | ||
): Promise<Item[]> => { | ||
try { | ||
const response = await props.request(props.arg, { count: itemsPerPage, page }); | ||
const maybeTranslatedResponse = props.responseTranslator ? props.responseTranslator(response, props.arg) : response; | ||
const newAccumulatedItems = [...accumulated, ...maybeTranslatedResponse] as Item[]; | ||
if (response.length === itemsPerPage) { | ||
return fetchSequentially<Item, Arg, Response>(props, itemsPerPage, page + 1, newAccumulatedItems); | ||
} | ||
return newAccumulatedItems; | ||
} catch (error) { | ||
if (formatBlockfrostError(error).status_code === 404) { | ||
return []; | ||
} | ||
throw error; | ||
} | ||
}; |
Oops, something went wrong.