iota_types/stardust/output/
nft.rs1use move_core_types::{ident_str, identifier::IdentStr, language_storage::StructTag};
5use serde::{Deserialize, Serialize};
6use serde_with::serde_as;
7
8use super::unlock_conditions::{
9 ExpirationUnlockCondition, StorageDepositReturnUnlockCondition, TimelockUnlockCondition,
10};
11use crate::{
12 STARDUST_ADDRESS, TypeTag,
13 balance::Balance,
14 base_types::IotaAddress,
15 collection_types::{Bag, VecMap},
16 error::IotaError,
17 id::UID,
18 object::{Data, Object},
19};
20
21pub const IRC27_MODULE_NAME: &IdentStr = ident_str!("irc27");
22pub const NFT_MODULE_NAME: &IdentStr = ident_str!("nft");
23pub const NFT_OUTPUT_MODULE_NAME: &IdentStr = ident_str!("nft_output");
24pub const NFT_OUTPUT_STRUCT_NAME: &IdentStr = ident_str!("NftOutput");
25pub const NFT_STRUCT_NAME: &IdentStr = ident_str!("Nft");
26pub const IRC27_STRUCT_NAME: &IdentStr = ident_str!("Irc27Metadata");
27pub const NFT_DYNAMIC_OBJECT_FIELD_KEY: &[u8] = b"nft";
28pub const NFT_DYNAMIC_OBJECT_FIELD_KEY_TYPE: &str = "vector<u8>";
29
30#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
32pub struct FixedPoint32 {
33 pub value: u64,
34}
35
36#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
38pub struct Url {
39 url: String,
47}
48
49impl Url {
50 pub fn url(&self) -> &str {
51 &self.url
52 }
53}
54
55impl TryFrom<String> for Url {
56 type Error = anyhow::Error;
57
58 fn try_from(url: String) -> Result<Self, Self::Error> {
60 if !url.is_ascii() {
61 anyhow::bail!("url `{url}` does not consist of only ascii characters")
62 }
63 Ok(Self { url })
64 }
65}
66
67#[serde_as]
68#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
69pub struct Irc27Metadata {
70 pub version: String,
72
73 pub media_type: String,
82
83 pub uri: Url,
85
86 pub name: String,
89
90 pub collection_name: Option<String>,
92
93 pub royalties: VecMap<IotaAddress, FixedPoint32>,
98
99 pub issuer_name: Option<String>,
101
102 pub description: Option<String>,
104
105 pub attributes: VecMap<String, String>,
107
108 pub non_standard_fields: VecMap<String, String>,
110}
111
112#[serde_as]
113#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
114pub struct Nft {
115 pub id: UID,
118
119 pub legacy_sender: Option<IotaAddress>,
122 pub metadata: Option<Vec<u8>>,
124 pub tag: Option<Vec<u8>>,
126
127 pub immutable_issuer: Option<IotaAddress>,
129 pub immutable_metadata: Irc27Metadata,
131}
132
133impl Nft {
134 pub fn tag() -> StructTag {
137 StructTag {
138 address: STARDUST_ADDRESS,
139 module: NFT_MODULE_NAME.to_owned(),
140 name: NFT_STRUCT_NAME.to_owned(),
141 type_params: Vec::new(),
142 }
143 }
144}
145
146#[serde_as]
147#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
148pub struct NftOutput {
149 pub id: UID,
151
152 pub balance: Balance,
154 pub native_tokens: Bag,
158
159 pub storage_deposit_return: Option<StorageDepositReturnUnlockCondition>,
161 pub timelock: Option<TimelockUnlockCondition>,
163 pub expiration: Option<ExpirationUnlockCondition>,
165}
166
167impl NftOutput {
168 pub fn tag(type_param: TypeTag) -> StructTag {
171 StructTag {
172 address: STARDUST_ADDRESS,
173 module: NFT_OUTPUT_MODULE_NAME.to_owned(),
174 name: NFT_OUTPUT_STRUCT_NAME.to_owned(),
175 type_params: vec![type_param],
176 }
177 }
178
179 pub fn from_bcs_bytes(content: &[u8]) -> Result<Self, IotaError> {
181 bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization {
182 error: format!("Unable to deserialize NftOutput object: {err:?}"),
183 })
184 }
185
186 pub fn is_nft_output(s: &StructTag) -> bool {
187 s.address == STARDUST_ADDRESS
188 && s.module.as_ident_str() == NFT_OUTPUT_MODULE_NAME
189 && s.name.as_ident_str() == NFT_OUTPUT_STRUCT_NAME
190 }
191}
192
193impl TryFrom<&Object> for NftOutput {
194 type Error = IotaError;
195 fn try_from(object: &Object) -> Result<Self, Self::Error> {
196 match &object.data {
197 Data::Move(o) => {
198 if o.type_().is_nft_output() {
199 return NftOutput::from_bcs_bytes(o.contents());
200 }
201 }
202 Data::Package(_) => {}
203 }
204
205 Err(IotaError::Type {
206 error: format!("Object type is not a NftOutput: {object:?}"),
207 })
208 }
209}