iota_types/stardust/output/
nft.rs1use iota_sdk_types::{Identifier, StructTag, TypeTag};
5use serde::{Deserialize, Serialize};
6use serde_with::serde_as;
7
8use super::unlock_conditions::{
9 ExpirationUnlockCondition, StorageDepositReturnUnlockCondition, TimelockUnlockCondition,
10};
11use crate::{
12 balance::Balance,
13 base_types::IotaAddress,
14 collection_types::{Bag, VecMap},
15 error::IotaError,
16 id::UID,
17 object::{Data, Object},
18};
19
20pub const NFT_MODULE_NAME: Identifier = Identifier::from_static("nft");
21pub const NFT_OUTPUT_MODULE_NAME: Identifier = Identifier::from_static("nft_output");
22pub const NFT_OUTPUT_STRUCT_NAME: Identifier = Identifier::from_static("NftOutput");
23pub const NFT_STRUCT_NAME: Identifier = Identifier::from_static("Nft");
24pub const NFT_DYNAMIC_OBJECT_FIELD_KEY: &[u8] = b"nft";
25pub const NFT_DYNAMIC_OBJECT_FIELD_KEY_TYPE: &str = "vector<u8>";
26
27#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
29pub struct FixedPoint32 {
30 pub value: u64,
31}
32
33#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
35pub struct Url {
36 url: String,
44}
45
46impl Url {
47 pub fn url(&self) -> &str {
48 &self.url
49 }
50}
51
52impl TryFrom<String> for Url {
53 type Error = anyhow::Error;
54
55 fn try_from(url: String) -> Result<Self, Self::Error> {
57 if !url.is_ascii() {
58 anyhow::bail!("url `{url}` does not consist of only ascii characters")
59 }
60 Ok(Self { url })
61 }
62}
63
64#[serde_as]
65#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
66pub struct Irc27Metadata {
67 pub version: String,
69
70 pub media_type: String,
79
80 pub uri: Url,
82
83 pub name: String,
86
87 pub collection_name: Option<String>,
89
90 pub royalties: VecMap<IotaAddress, FixedPoint32>,
95
96 pub issuer_name: Option<String>,
98
99 pub description: Option<String>,
101
102 pub attributes: VecMap<String, String>,
104
105 pub non_standard_fields: VecMap<String, String>,
107}
108
109#[serde_as]
110#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
111pub struct Nft {
112 pub id: UID,
115
116 pub legacy_sender: Option<IotaAddress>,
119 pub metadata: Option<Vec<u8>>,
121 pub tag: Option<Vec<u8>>,
123
124 pub immutable_issuer: Option<IotaAddress>,
126 pub immutable_metadata: Irc27Metadata,
128}
129
130impl Nft {
131 pub fn tag() -> StructTag {
134 StructTag::new(
135 IotaAddress::STARDUST,
136 NFT_MODULE_NAME,
137 NFT_STRUCT_NAME,
138 Vec::new(),
139 )
140 }
141}
142
143#[serde_as]
144#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
145pub struct NftOutput {
146 pub id: UID,
148
149 pub balance: Balance,
151 pub native_tokens: Bag,
155
156 pub storage_deposit_return: Option<StorageDepositReturnUnlockCondition>,
158 pub timelock: Option<TimelockUnlockCondition>,
160 pub expiration: Option<ExpirationUnlockCondition>,
162}
163
164impl NftOutput {
165 pub fn tag(type_param: TypeTag) -> StructTag {
168 StructTag::new(
169 IotaAddress::STARDUST,
170 NFT_OUTPUT_MODULE_NAME,
171 NFT_OUTPUT_STRUCT_NAME,
172 vec![type_param],
173 )
174 }
175
176 pub fn from_bcs_bytes(content: &[u8]) -> Result<Self, IotaError> {
178 bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization {
179 error: format!("Unable to deserialize NftOutput object: {err:?}"),
180 })
181 }
182
183 pub fn is_nft_output(s: &StructTag) -> bool {
184 s.address() == IotaAddress::STARDUST
185 && s.module() == &NFT_OUTPUT_MODULE_NAME
186 && s.name() == &NFT_OUTPUT_STRUCT_NAME
187 }
188}
189
190impl TryFrom<&Object> for NftOutput {
191 type Error = IotaError;
192 fn try_from(object: &Object) -> Result<Self, Self::Error> {
193 match &object.data {
194 Data::Struct(o) => {
195 if NftOutput::is_nft_output(o.struct_tag()) {
196 return NftOutput::from_bcs_bytes(o.contents());
197 }
198 }
199 Data::Package(_) => {}
200 }
201
202 Err(IotaError::Type {
203 error: format!("Object type is not a NftOutput: {object:?}"),
204 })
205 }
206}