iota_types/stardust/output/
nft.rs1use iota_sdk_types::{Address, Identifier, ObjectData, StructTag};
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_OUTPUT_MODULE_NAME: Identifier = Identifier::from_static("nft_output");
20pub const NFT_OUTPUT_STRUCT_NAME: Identifier = Identifier::from_static("NftOutput");
21pub const NFT_DYNAMIC_OBJECT_FIELD_KEY: &[u8] = b"nft";
22pub const NFT_DYNAMIC_OBJECT_FIELD_KEY_TYPE: &str = "vector<u8>";
23
24#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
26pub struct FixedPoint32 {
27 pub value: u64,
28}
29
30#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
32pub struct Url {
33 url: String,
41}
42
43impl Url {
44 pub fn url(&self) -> &str {
45 &self.url
46 }
47}
48
49impl TryFrom<String> for Url {
50 type Error = anyhow::Error;
51
52 fn try_from(url: String) -> Result<Self, Self::Error> {
54 if !url.is_ascii() {
55 anyhow::bail!("url `{url}` does not consist of only ascii characters")
56 }
57 Ok(Self { url })
58 }
59}
60
61#[serde_as]
62#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
63pub struct Irc27Metadata {
64 pub version: String,
66
67 pub media_type: String,
76
77 pub uri: Url,
79
80 pub name: String,
83
84 pub collection_name: Option<String>,
86
87 pub royalties: VecMap<Address, FixedPoint32>,
92
93 pub issuer_name: Option<String>,
95
96 pub description: Option<String>,
98
99 pub attributes: VecMap<String, String>,
101
102 pub non_standard_fields: VecMap<String, String>,
104}
105
106#[serde_as]
107#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
108pub struct Nft {
109 pub id: UID,
112
113 pub legacy_sender: Option<Address>,
116 pub metadata: Option<Vec<u8>>,
118 pub tag: Option<Vec<u8>>,
120
121 pub immutable_issuer: Option<Address>,
123 pub immutable_metadata: Irc27Metadata,
125}
126
127#[serde_as]
128#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
129pub struct NftOutput {
130 pub id: UID,
132
133 pub balance: Balance,
135 pub native_tokens: Bag,
139
140 pub storage_deposit_return: Option<StorageDepositReturnUnlockCondition>,
142 pub timelock: Option<TimelockUnlockCondition>,
144 pub expiration: Option<ExpirationUnlockCondition>,
146}
147
148impl NftOutput {
149 pub fn from_bcs_bytes(content: &[u8]) -> Result<Self, IotaError> {
151 bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization {
152 error: format!("Unable to deserialize NftOutput object: {err:?}"),
153 })
154 }
155
156 pub fn is_nft_output(s: &StructTag) -> bool {
157 s.address() == Address::STARDUST
158 && s.module() == &NFT_OUTPUT_MODULE_NAME
159 && s.name() == &NFT_OUTPUT_STRUCT_NAME
160 }
161}
162
163impl TryFrom<&Object> for NftOutput {
164 type Error = IotaError;
165 fn try_from(object: &Object) -> Result<Self, Self::Error> {
166 match &object.data {
167 ObjectData::Struct(o) => {
168 if NftOutput::is_nft_output(o.struct_tag()) {
169 return NftOutput::from_bcs_bytes(o.contents());
170 }
171 }
172 ObjectData::Package(_) => {}
173 }
174
175 Err(IotaError::Type {
176 error: format!("Object type is not a NftOutput: {object:?}"),
177 })
178 }
179}