nft_server/
lib.rs

1//! A simple NFT Json Metadata library, with easy axum-based servers
2
3#![warn(missing_docs)]
4#![warn(missing_debug_implementations)]
5#![warn(missing_copy_implementations)]
6
7use std::{convert::Infallible, str::FromStr};
8
9use async_trait::async_trait;
10
11use ethers::types::U256;
12use serde::{Deserialize, Serialize};
13use url::Url;
14
15/// Provided generator implementations
16pub mod generators;
17/// OpenSea-specific data structures
18pub mod open_sea;
19
20#[cfg(feature = "axum")]
21/// Servers
22pub mod server;
23
24use open_sea::{ContractMetadata, OpenSeaAttribute};
25
26/// Prelude
27pub mod prelude {
28    #[cfg(feature = "axum")]
29    pub use crate::server::{
30        serve_generator, serve_generator_with_span, serve_router, serve_router_with_span,
31    };
32    #[cfg(feature = "axum")]
33    pub use ethers::types::U256;
34
35    pub use crate::{open_sea::*, MetadataGenerator, NftImage, NftMetadata};
36    pub use url::Url;
37}
38
39/// An `NftImage` is a URL to an image, or the image data as a string.
40#[derive(Clone, Debug, Serialize, Deserialize)]
41#[serde(untagged)]
42pub enum NftImage {
43    /// Image URL
44    Url {
45        /// The URL
46        image: url::Url,
47    },
48    /// Image Data
49    Data {
50        /// The image file as a string
51        image_data: String,
52    },
53}
54
55impl FromStr for NftImage {
56    type Err = Infallible;
57
58    fn from_str(s: &str) -> Result<Self, Self::Err> {
59        s.parse::<Url>().map(Into::into).or_else(|_| Ok(s.into()))
60    }
61}
62
63impl From<Url> for NftImage {
64    fn from(image: Url) -> Self {
65        NftImage::Url { image }
66    }
67}
68
69impl From<&str> for NftImage {
70    fn from(image_data: &str) -> Self {
71        NftImage::Data {
72            image_data: image_data.to_string(),
73        }
74    }
75}
76
77/// Top-level NFT Metadata supporting basic ERC-721 schema, with OpenSea
78/// extensions.
79#[derive(Clone, Debug, Serialize, Deserialize)]
80pub struct NftMetadata {
81    /// The NFT name
82    pub name: String,
83    /// The NFT description
84    pub description: String,
85    /// An external URL related to the NFT
86    pub external_url: Url,
87    /// The NFT image link or data
88    #[serde(flatten)]
89    pub image: NftImage,
90    /// NFT Attributes, in the OpenSea format
91    #[serde(default)]
92    pub attributes: Vec<OpenSeaAttribute>,
93    /// The background color to be displayed on OpenSea
94    #[serde(default, skip_serializing_if = "Option::is_none")]
95    pub background_color: Option<String>,
96    /// The animation URL to be displayed on OpenSea
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub animation_url: Option<Url>,
99    /// The Youtube link to be displayed on OpenSea
100    #[serde(default, skip_serializing_if = "Option::is_none")]
101    pub youtube_url: Option<Url>,
102}
103
104/// A `MetadataGenerator` asynchronously generates token and contract Metadata.
105/// Tokens are referenced by the `uint256` tokenId used to identify them in the
106/// ERC-721 contract.
107///
108/// A `MetadataGenerator` may query an outside API, a DB, the local filesystem,
109/// or any other potential data source. Projects seeking to use this library
110/// should make their own metadata generator
111#[async_trait]
112pub trait MetadataGenerator {
113    /// Associated Error type
114    type Error: std::error::Error + Send + Sync + 'static;
115
116    /// Generate metadata for a specific token
117    async fn metadata_for(&self, token_id: U256) -> Result<Option<NftMetadata>, Self::Error>;
118
119    /// Generate contract-level metadata (in the OpenSea format). See
120    /// [`ContractMetadata`].
121    ///
122    /// This function returns an option, as contract metadata is not intended
123    /// to be dynamically generated
124    async fn contract_metadata(&self) -> Option<ContractMetadata>;
125}
126
127#[cfg(test)]
128mod tests {
129    use ethers::types::U256;
130    use serde_json::json;
131
132    #[test]
133    fn it_works() {
134        let num = "0x3e10".parse::<U256>().unwrap();
135        dbg!(num);
136        dbg!(num.to_string());
137        dbg!(format!("{}", num));
138        dbg!(json!({"number": 5u64}));
139    }
140}