iota_types/stardust/output/
nft.rs1use iota_sdk_move_types::{
5 iota_framework::vec_map::VecMap,
6 move_stdlib::fixed_point32::FixedPoint32,
7 stardust::{
8 expiration_unlock_condition::ExpirationUnlockCondition,
9 storage_deposit_return_unlock_condition::StorageDepositReturnUnlockCondition,
10 timelock_unlock_condition::TimelockUnlockCondition,
11 },
12};
13use iota_sdk_types::{Address, Identifier, StructTag};
14use serde::{Deserialize, Serialize};
15use serde_with::serde_as;
16
17use crate::{balance::Balance, collection_types::Bag, error::IotaError, id::UID, object::Object};
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 Url {
27 url: String,
35}
36
37impl Url {
38 pub fn url(&self) -> &str {
39 &self.url
40 }
41}
42
43impl TryFrom<String> for Url {
44 type Error = anyhow::Error;
45
46 fn try_from(url: String) -> Result<Self, Self::Error> {
48 if !url.is_ascii() {
49 anyhow::bail!("url `{url}` does not consist of only ascii characters")
50 }
51 Ok(Self { url })
52 }
53}
54
55#[serde_as]
56#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
57pub struct Irc27Metadata {
58 pub version: String,
60
61 pub media_type: String,
70
71 pub uri: Url,
73
74 pub name: String,
77
78 pub collection_name: Option<String>,
80
81 pub royalties: VecMap<Address, FixedPoint32>,
86
87 pub issuer_name: Option<String>,
89
90 pub description: Option<String>,
92
93 pub attributes: VecMap<String, String>,
95
96 pub non_standard_fields: VecMap<String, String>,
98}
99
100#[serde_as]
101#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
102pub struct Nft {
103 pub id: UID,
106
107 pub legacy_sender: Option<Address>,
110 pub metadata: Option<Vec<u8>>,
112 pub tag: Option<Vec<u8>>,
114
115 pub immutable_issuer: Option<Address>,
117 pub immutable_metadata: Irc27Metadata,
119}
120
121#[serde_as]
122#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
123pub struct NftOutput {
124 pub id: UID,
126
127 pub balance: Balance,
129 pub native_tokens: Bag,
133
134 pub storage_deposit_return: Option<StorageDepositReturnUnlockCondition>,
136 pub timelock: Option<TimelockUnlockCondition>,
138 pub expiration: Option<ExpirationUnlockCondition>,
140}
141
142impl NftOutput {
143 pub fn from_bcs_bytes(content: &[u8]) -> Result<Self, IotaError> {
145 bcs::from_bytes(content).map_err(|err| IotaError::ObjectDeserialization {
146 error: format!("Unable to deserialize NftOutput object: {err:?}"),
147 })
148 }
149
150 pub fn is_nft_output(s: &StructTag) -> bool {
151 s.address() == Address::STARDUST
152 && s.module() == &NFT_OUTPUT_MODULE_NAME
153 && s.name() == &NFT_OUTPUT_STRUCT_NAME
154 }
155}
156
157impl TryFrom<&Object> for NftOutput {
158 type Error = IotaError;
159 fn try_from(object: &Object) -> Result<Self, Self::Error> {
160 if let Some(o) = object.data.as_opt_struct() {
161 if NftOutput::is_nft_output(o.struct_tag()) {
162 return NftOutput::from_bcs_bytes(o.contents());
163 }
164 }
165
166 Err(IotaError::Type {
167 error: format!("Object type is not a NftOutput: {object:?}"),
168 })
169 }
170}