iota_types/stardust/output/
nft.rs1use iota_sdk_types::{Address, Identifier, ObjectData, 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 collection_types::{Bag, VecMap},
14 error::IotaError,
15 id::UID,
16 object::Object,
17};
18
19pub const NFT_MODULE_NAME: Identifier = Identifier::from_static("nft");
20pub const NFT_OUTPUT_MODULE_NAME: Identifier = Identifier::from_static("nft_output");
21pub const NFT_OUTPUT_STRUCT_NAME: Identifier = Identifier::from_static("NftOutput");
22pub const NFT_STRUCT_NAME: Identifier = Identifier::from_static("Nft");
23pub const NFT_DYNAMIC_OBJECT_FIELD_KEY: &[u8] = b"nft";
24pub const NFT_DYNAMIC_OBJECT_FIELD_KEY_TYPE: &str = "vector<u8>";
25
26#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
28pub struct FixedPoint32 {
29 pub value: u64,
30}
31
32#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
34pub struct Url {
35 url: String,
43}
44
45impl Url {
46 pub fn url(&self) -> &str {
47 &self.url
48 }
49}
50
51impl TryFrom<String> for Url {
52 type Error = anyhow::Error;
53
54 fn try_from(url: String) -> Result<Self, Self::Error> {
56 if !url.is_ascii() {
57 anyhow::bail!("url `{url}` does not consist of only ascii characters")
58 }
59 Ok(Self { url })
60 }
61}
62
63#[serde_as]
64#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
65pub struct Irc27Metadata {
66 pub version: String,
68
69 pub media_type: String,
78
79 pub uri: Url,
81
82 pub name: String,
85
86 pub collection_name: Option<String>,
88
89 pub royalties: VecMap<Address, FixedPoint32>,
94
95 pub issuer_name: Option<String>,
97
98 pub description: Option<String>,
100
101 pub attributes: VecMap<String, String>,
103
104 pub non_standard_fields: VecMap<String, String>,
106}
107
108#[serde_as]
109#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
110pub struct Nft {
111 pub id: UID,
114
115 pub legacy_sender: Option<Address>,
118 pub metadata: Option<Vec<u8>>,
120 pub tag: Option<Vec<u8>>,
122
123 pub immutable_issuer: Option<Address>,
125 pub immutable_metadata: Irc27Metadata,
127}
128
129impl Nft {
130 pub fn tag() -> StructTag {
133 StructTag::new(
134 Address::STARDUST,
135 NFT_MODULE_NAME,
136 NFT_STRUCT_NAME,
137 Vec::new(),
138 )
139 }
140}
141
142#[serde_as]
143#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
144pub struct NftOutput {
145 pub id: UID,
147
148 pub balance: Balance,
150 pub native_tokens: Bag,
154
155 pub storage_deposit_return: Option<StorageDepositReturnUnlockCondition>,
157 pub timelock: Option<TimelockUnlockCondition>,
159 pub expiration: Option<ExpirationUnlockCondition>,
161}
162
163impl NftOutput {
164 pub fn tag(type_param: TypeTag) -> StructTag {
167 StructTag::new(
168 Address::STARDUST,
169 NFT_OUTPUT_MODULE_NAME,
170 NFT_OUTPUT_STRUCT_NAME,
171 vec![type_param],
172 )
173 }
174
175 pub fn from_bcs_bytes(content: &[u8]) -> Result<Self, IotaError> {
177 bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization {
178 error: format!("Unable to deserialize NftOutput object: {err:?}"),
179 })
180 }
181
182 pub fn is_nft_output(s: &StructTag) -> bool {
183 s.address() == Address::STARDUST
184 && s.module() == &NFT_OUTPUT_MODULE_NAME
185 && s.name() == &NFT_OUTPUT_STRUCT_NAME
186 }
187}
188
189impl TryFrom<&Object> for NftOutput {
190 type Error = IotaError;
191 fn try_from(object: &Object) -> Result<Self, Self::Error> {
192 match &object.data {
193 ObjectData::Struct(o) => {
194 if NftOutput::is_nft_output(o.struct_tag()) {
195 return NftOutput::from_bcs_bytes(o.contents());
196 }
197 }
198 ObjectData::Package(_) => {}
199 }
200
201 Err(IotaError::Type {
202 error: format!("Object type is not a NftOutput: {object:?}"),
203 })
204 }
205}