Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend AssetDetails data type #40

Merged
merged 3 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 110 additions & 26 deletions blockfrost-api/src/Blockfrost/Types/Cardano/Assets.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ module Blockfrost.Types.Cardano.Assets
( AssetInfo (..)
, AssetDetails (..)
, AssetOnChainMetadata (..)
, MetadataMediaFile (..)
, parseStandardMetadata
, parseStandardMetadataInDetails
, AssetMetadata (..)
, AssetHistory (..)
, AssetAction (..)
Expand All @@ -12,9 +15,15 @@ module Blockfrost.Types.Cardano.Assets
) where

import Blockfrost.Types.Shared
import Data.Aeson (Value (..), FromJSON(parseJSON))
import Data.Aeson.Types (parseMaybe)
import Data.Aeson.KeyMap (fromList)
import Data.Char (toLower)
import Data.Text (Text)
import qualified Data.Vector as V (singleton)
import Deriving.Aeson
import Servant.Docs (ToSample (..), samples, singleSample)
import Prelude hiding (String)

-- | Asset information, result of listing assets
data AssetInfo = AssetInfo
Expand All @@ -41,23 +50,57 @@ instance ToSample AssetInfo where
}
]

-- CIP25 uses camelCase, so we need this to derive appropriate instances
data LowerHead

instance StringModifier LowerHead where
getStringModifier (f:rest) = toLower f : rest
getStringModifier [] = error "Empty field name"

-- | Additional media files (accordingly to CIP25 and CIP68)
data MetadataMediaFile = MetadataMediaFile
{ _metadataMediaFileName :: Maybe Text -- ^ file name
, _metadataMediaFileMediaType :: Maybe Text -- ^ file MIME content-type
, _metadataMediaFileSrc :: Maybe Text -- ^ URI of the media file
}
deriving stock (Show, Eq, Generic)
deriving (FromJSON, ToJSON)
via CustomJSON '[FieldLabelModifier '[StripPrefix "_metadataMediaFile", LowerHead]] MetadataMediaFile

instance ToSample MetadataMediaFile where
toSamples = pure $ singleSample sampleAssetOnChainMetadataFile

sampleAssetOnChainMetadataFile :: MetadataMediaFile
sampleAssetOnChainMetadataFile =
MetadataMediaFile
{ _metadataMediaFileName = Just "Detailed image"
, _metadataMediaFileMediaType = Just "image/png"
, _metadataMediaFileSrc = Just "ipfs://ipfs/QmfKyJ4tuvHowwKQCbCHj4L5T3fSj8cjs7Aau8V7BWv226"
}

-- | On-chain metadata stored in the minting transaction under label 721,
-- community discussion around the standard ongoing at https://github.com/cardano-foundation/CIPs/pull/85
data AssetOnChainMetadata = AssetOnChainMetadata
{ _assetOnChainMetadataName :: Text -- ^ Name of the asset
, _assetOnChainMetadataImage :: Text -- ^ URI of the associated asset
-- TODO: in schema has `additionalProperties: true`
-- so it can carry more arbitrary properties, keep as Value?
{ _assetOnChainMetadataName :: Maybe Text -- ^ Name of the asset
, _assetOnChainMetadataDescription :: Maybe Text -- ^ Description of the asset
, _assetOnChainMetadataImage :: Maybe Text -- ^ URI of the image, usually IPFS-based
, _assetOnChainMetadataMediaType :: Maybe Text -- ^ image MIME content-type
, _assetOnChainMetadataFiles :: Maybe [MetadataMediaFile] -- ^ Additional media files
}
deriving stock (Show, Eq, Generic)
deriving (FromJSON, ToJSON)
via CustomJSON '[FieldLabelModifier '[StripPrefix "_assetOnChainMetadata", CamelToSnake]] AssetOnChainMetadata
via CustomJSON '[FieldLabelModifier '[StripPrefix "_assetOnChainMetadata", LowerHead]] AssetOnChainMetadata

instance ToSample AssetOnChainMetadata where
toSamples = pure $ singleSample
AssetOnChainMetadata
{ _assetOnChainMetadataName = "My NFT token"
, _assetOnChainMetadataImage = "ipfs://ipfs/QmfKyJ4tuvHowwKQCbCHj4L5T3fSj8cjs7Aau8V7BWv226"
toSamples = pure $ singleSample $ sampleAssetOnChainMetadata

sampleAssetOnChainMetadata :: AssetOnChainMetadata
sampleAssetOnChainMetadata = AssetOnChainMetadata
{ _assetOnChainMetadataName = Just "My NFT token"
, _assetOnChainMetadataDescription = Just "A cool token for joy and fun."
, _assetOnChainMetadataImage = Just "ipfs://ipfs/QmfKyJ4tuvHowwKQCbCHj4L5T3fSj8cjs7Aau8V7BWv226"
, _assetOnChainMetadataMediaType = Just "image/png"
, _assetOnChainMetadataFiles = Just [ sampleAssetOnChainMetadataFile ]
}

-- | Asset metadata obtained from Cardano token registry
Expand Down Expand Up @@ -87,21 +130,42 @@ instance ToSample AssetMetadata where

-- | Details of an asset
data AssetDetails = AssetDetails
{ _assetDetailsAsset :: Text -- ^ Hex-encoded asset full name
, _assetDetailsPolicyId :: PolicyId -- ^ Policy ID of the asset
, _assetDetailsAssetName :: Maybe Text -- ^ Hex-encoded asset name of the asset
, _assetDetailsFingerprint :: Text -- ^ CIP14 based user-facing fingerprint
, _assetDetailsQuantity :: Quantity -- ^ Current asset quantity
, _assetDetailsInitialMintTxHash :: TxHash -- ^ ID of the initial minting transaction
, _assetDetailsMintOrBurnCount :: Integer -- ^ Count of mint and burn transactions
, _assetDetailsOnchainMetadata :: Maybe AssetOnChainMetadata
-- ^ On-chain metadata stored in the minting transaction under label 721,
-- community discussion around the standard ongoing at https://github.com/cardano-foundation/CIPs/pull/85
, _assetDetailsMetadata :: Maybe AssetMetadata
{ _assetDetailsAsset :: Text -- ^ Hex-encoded asset full name
, _assetDetailsPolicyId :: PolicyId -- ^ Policy ID of the asset
, _assetDetailsAssetName :: Maybe Text -- ^ Hex-encoded asset name of the asset
, _assetDetailsFingerprint :: Text -- ^ CIP14 based user-facing fingerprint
, _assetDetailsQuantity :: Quantity -- ^ Current asset quantity
, _assetDetailsInitialMintTxHash :: TxHash -- ^ ID of the initial minting transaction
, _assetDetailsMintOrBurnCount :: Integer -- ^ Count of mint and burn transactions
, _assetDetailsOnchainMetadataValue :: Maybe Value
-- ^ On-chain metadata stored in the minting transaction under label 721 for CIP25
-- standard or in the first element in the specific constructor in a reference token's
-- datum for CIP68 standard. In both cases this part is represenatable as JSON
-- and might be arbitrary, so we keep it as @Value@
, _assetDetailsOnchainStandardMetadata :: Maybe AssetOnChainMetadata
-- ^ Some bits of on-chain metadata are considered being "standard" (like name, image and so on).
-- If they are found in the metadata JSON bundle, this field will contain them.
, _assetDetailsMetadata :: Maybe AssetMetadata
, _assetDetailsOnchainMetadataExtra :: Maybe Text
-- ^ CIP68 extra metadata plutus data (CBOR-encoded)
}
deriving stock (Show, Eq, Generic)
deriving (FromJSON, ToJSON)
via CustomJSON '[FieldLabelModifier '[StripPrefix "_assetDetails", CamelToSnake]] AssetDetails
via CustomJSON '[ FieldLabelModifier
'[ StripPrefix "_assetDetails"
, CamelToSnake
, Rename "onchain_metadata_value" "onchain_metadata"
]
] AssetDetails

parseStandardMetadata :: Maybe Value -> Maybe AssetOnChainMetadata
parseStandardMetadata mbValue = mbValue >>= parseMaybe parseJSON

parseStandardMetadataInDetails :: AssetDetails -> AssetDetails
parseStandardMetadataInDetails
details@AssetDetails{_assetDetailsOnchainMetadataValue = mbValue} =
details { _assetDetailsOnchainStandardMetadata = parseStandardMetadata mbValue}


instance ToSample AssetDetails where
toSamples = pure $ singleSample
Expand All @@ -113,11 +177,30 @@ instance ToSample AssetDetails where
, _assetDetailsQuantity = 12000
, _assetDetailsInitialMintTxHash = "6804edf9712d2b619edb6ac86861fe93a730693183a262b165fcc1ba1bc99cad"
, _assetDetailsMintOrBurnCount = 1
, _assetDetailsOnchainMetadata = pure $
AssetOnChainMetadata
{ _assetOnChainMetadataName = "My NFT token"
, _assetOnChainMetadataImage = "ipfs://ipfs/QmfKyJ4tuvHowwKQCbCHj4L5T3fSj8cjs7Aau8V7BWv226"
}
, _assetDetailsOnchainMetadataValue = Just $
(
Object
(fromList
[ ("id",Number 630.0)
, ("image",String "ipfs://ipfs/QmfKyJ4tuvHowwKQCbCHj4L5T3fSj8cjs7Aau8V7BWv226")
, ("mediaType",String "image/png")
, ("name",String "My NFT token")
, ("files", Array
( V.singleton
(Object
(fromList
[ ("name", String "Detailed image")
, ("mediaType", String "image/png")
, ("src", String "ipfs://ipfs/QmfKyJ4tuvHowwKQCbCHj4L5T3fSj8cjs7Aau8V7BWv226")
]
)
)
)
)
]
)
)
, _assetDetailsOnchainStandardMetadata = pure sampleAssetOnChainMetadata
, _assetDetailsMetadata = pure $
AssetMetadata
{ _assetMetadataName = "nutcoin"
Expand All @@ -127,6 +210,7 @@ instance ToSample AssetDetails where
, _assetMetadataLogo = pure "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"
, _assetMetadataDecimals = pure 6
}
, _assetDetailsOnchainMetadataExtra = Just "9f01582430303030303030302d303030302d303030302d303030302d3030303030303030303132334d47616d65585f4578616d706c65020203454c6576656c413145506f7765724231304553706565644131ff"
}

-- | Action of the asset.
Expand Down
Loading