@yoroi/api
Version:
The API package of Yoroi SDK
190 lines (163 loc) • 5.6 kB
text/typescript
import {Fetcher, fetcher, isArray, isRecord} from '@yoroi/common'
import {
Api,
ApiMetadataRecord,
ApiOnChainFtMetadataResult,
ApiOnChainNftMetadataResult,
} from '@yoroi/types'
import {AxiosRequestConfig} from 'axios'
import {CIP25_KEY_NFT, CIP25_V2, CIP26_KEY_FT} from './constants'
import {parseFtMetadataRecord, parseNftMetadataRecord} from './parsers'
import {getTokenIdentity} from '../translators/helpers/getTokenIdentity'
export const getOnChainMetadatas = (
baseUrl: string,
request: Fetcher = fetcher,
) => {
return (
tokenIds: Api.Cardano.OnChainMetadataRequest,
fetcherConfig?: AxiosRequestConfig,
): Promise<Api.Cardano.OnChainMetadataResponse> => {
if (tokenIds.length === 0) {
return Promise.resolve({})
}
const assets = tokenIds.map((id) => {
const [policy, nameHex] = id.split('.')
return {policy, nameHex}
})
const payload = {assets}
return request<Api.Cardano.OnChainMetadataResponse>({
...fetcherConfig,
url: `${baseUrl}/multiAsset/metadata`,
method: 'POST',
headers: {'Content-Type': 'application/json'},
data: payload,
}).then((response) => {
if (!isRecord(response))
return Promise.reject(new Error('Invalid asset metadatas'))
const responseRecords = response as Record<string, unknown>
const result: Api.Cardano.OnChainMetadataResponse = {}
for (const id of tokenIds) {
const {policyId, name, assetName} = getTokenIdentity(id)
// API returns with utf8 name, the request sends with hex name
const tokenId = `${policyId}.${name}`
const records = responseRecords[tokenId]
if (!isArray(records)) {
result[id] = emptyOnChainMetadataRecord
continue
}
const tokenIdentity: Api.Cardano.TokenIdentity = {
policyId,
name,
nameHex: assetName,
} as const
result[id] = getMetadataResult(records, tokenIdentity)
}
return Promise.resolve(result)
})
}
}
export function getMetadataResult(
records: Readonly<Array<unknown>>,
tokenIdentity: Readonly<Api.Cardano.TokenIdentity>,
): Api.Cardano.OnChainMetadataRecord {
let ftMetadataResult: ApiOnChainFtMetadataResult = {
mintFtMetadata: undefined,
mintFtRecordSelected: undefined,
}
let nftMetadataResult: ApiOnChainNftMetadataResult = {
mintNftMetadata: undefined,
mintNftRecordSelected: undefined,
}
for (const record of records) {
if (!isRecord(record)) continue
const possibleMetadatas = record as Readonly<Record<string, unknown>> // avoid casting in every usage
if (
possibleMetadatas?.key === CIP26_KEY_FT &&
!ftMetadataResult.mintFtRecordSelected
) {
ftMetadataResult = getFtRecord(possibleMetadatas, tokenIdentity)
continue
}
if (
possibleMetadatas?.key === CIP25_KEY_NFT &&
!nftMetadataResult.mintNftRecordSelected
) {
nftMetadataResult = getNftRecord(possibleMetadatas, tokenIdentity)
}
}
return {
...ftMetadataResult,
...nftMetadataResult,
}
}
function getFtRecord(
record: Readonly<Record<string, unknown>> | Api.Cardano.FtMetadataRecord,
tokenIdentity: Readonly<Api.Cardano.TokenIdentity>,
): ApiOnChainFtMetadataResult {
const possibleFtMetadataRecord = findMetadataRecord(record, tokenIdentity)
if (
possibleFtMetadataRecord !== undefined &&
isRecord(possibleFtMetadataRecord)
) {
const parsedFtMetadataRecord = parseFtMetadataRecord(
possibleFtMetadataRecord,
)
if (parsedFtMetadataRecord !== undefined) {
return {
// record holds original tx mint metadata is safe to cast here
mintFtMetadata: record as Api.Cardano.FtMetadataRecord,
mintFtRecordSelected: parsedFtMetadataRecord,
}
}
}
return {
mintFtMetadata: record,
mintFtRecordSelected: undefined,
}
}
function getNftRecord(
record: Readonly<Record<string, unknown>> | Api.Cardano.NftMetadataRecord,
tokenIdentity: Readonly<Api.Cardano.TokenIdentity>,
): ApiOnChainNftMetadataResult {
const possibleNftMetadataRecord = findMetadataRecord(record, tokenIdentity)
if (
possibleNftMetadataRecord !== undefined &&
isRecord(possibleNftMetadataRecord)
) {
const parsedNftMetadataRecord = parseNftMetadataRecord(
possibleNftMetadataRecord,
)
if (parsedNftMetadataRecord !== undefined) {
return {
// record holds original tx mint metadata is safe to cast here
mintNftMetadata: record as Api.Cardano.NftMetadataRecord,
mintNftRecordSelected: parsedNftMetadataRecord,
}
}
}
return {
mintNftMetadata: record,
mintNftRecordSelected: undefined,
}
}
export function findMetadataRecord(
possibleMetadataRecord: Readonly<Record<string, unknown>>,
tokenIdentity: Readonly<Api.Cardano.TokenIdentity>,
) {
const {policyId, name, nameHex} = tokenIdentity
const metadataRecord = possibleMetadataRecord as Partial<ApiMetadataRecord>
const {metadata} = metadataRecord
if (!isRecord(metadata)) return undefined
const {version, ...policyRecords} = metadata
const isV2 = version === CIP25_V2
const assetRecords = policyRecords[policyId]
if (!isRecord(assetRecords)) return undefined
return isV2 ? assetRecords[nameHex] : assetRecords[name]
}
export const emptyOnChainMetadataRecord: Readonly<Api.Cardano.OnChainMetadataRecord> =
{
mintFtMetadata: undefined,
mintFtRecordSelected: undefined,
mintNftMetadata: undefined,
mintNftRecordSelected: undefined,
} as const