iota_stardust_types/block/output/
nft.rs1use alloc::collections::BTreeSet;
5
6use packable::Packable;
7
8use crate::block::{
9 Error,
10 address::{Address, NftAddress},
11 output::{
12 ChainId, NativeToken, NativeTokens, NftId, OutputBuilderAmount, OutputId,
13 feature::{Feature, FeatureFlags, Features, verify_allowed_features},
14 unlock_condition::{
15 UnlockCondition, UnlockConditionFlags, UnlockConditions,
16 verify_allowed_unlock_conditions,
17 },
18 },
19};
20
21#[derive(Clone)]
23#[must_use]
24pub struct NftOutputBuilder {
25 amount: OutputBuilderAmount,
26 native_tokens: BTreeSet<NativeToken>,
27 nft_id: NftId,
28 unlock_conditions: BTreeSet<UnlockCondition>,
29 features: BTreeSet<Feature>,
30 immutable_features: BTreeSet<Feature>,
31}
32
33impl NftOutputBuilder {
34 pub fn new_with_amount(amount: u64, nft_id: NftId) -> Self {
36 Self::new(OutputBuilderAmount::Amount(amount), nft_id)
37 }
38
39 fn new(amount: OutputBuilderAmount, nft_id: NftId) -> Self {
40 Self {
41 amount,
42 native_tokens: BTreeSet::new(),
43 nft_id,
44 unlock_conditions: BTreeSet::new(),
45 features: BTreeSet::new(),
46 immutable_features: BTreeSet::new(),
47 }
48 }
49
50 #[inline(always)]
52 pub fn with_amount(mut self, amount: u64) -> Self {
53 self.amount = OutputBuilderAmount::Amount(amount);
54 self
55 }
56
57 #[inline(always)]
59 pub fn add_native_token(mut self, native_token: NativeToken) -> Self {
60 self.native_tokens.insert(native_token);
61 self
62 }
63
64 #[inline(always)]
66 pub fn with_native_tokens(
67 mut self,
68 native_tokens: impl IntoIterator<Item = NativeToken>,
69 ) -> Self {
70 self.native_tokens = native_tokens.into_iter().collect();
71 self
72 }
73
74 #[inline(always)]
76 pub fn with_nft_id(mut self, nft_id: NftId) -> Self {
77 self.nft_id = nft_id;
78 self
79 }
80
81 #[inline(always)]
84 pub fn add_unlock_condition(mut self, unlock_condition: impl Into<UnlockCondition>) -> Self {
85 self.unlock_conditions.insert(unlock_condition.into());
86 self
87 }
88
89 #[inline(always)]
92 pub fn with_unlock_conditions(
93 mut self,
94 unlock_conditions: impl IntoIterator<Item = impl Into<UnlockCondition>>,
95 ) -> Self {
96 self.unlock_conditions = unlock_conditions.into_iter().map(Into::into).collect();
97 self
98 }
99
100 pub fn replace_unlock_condition(
103 mut self,
104 unlock_condition: impl Into<UnlockCondition>,
105 ) -> Self {
106 self.unlock_conditions.replace(unlock_condition.into());
107 self
108 }
109
110 #[inline(always)]
112 pub fn clear_unlock_conditions(mut self) -> Self {
113 self.unlock_conditions.clear();
114 self
115 }
116
117 #[inline(always)]
120 pub fn add_feature(mut self, feature: impl Into<Feature>) -> Self {
121 self.features.insert(feature.into());
122 self
123 }
124
125 #[inline(always)]
127 pub fn with_features(mut self, features: impl IntoIterator<Item = impl Into<Feature>>) -> Self {
128 self.features = features.into_iter().map(Into::into).collect();
129 self
130 }
131
132 pub fn replace_feature(mut self, feature: impl Into<Feature>) -> Self {
134 self.features.replace(feature.into());
135 self
136 }
137
138 #[inline(always)]
140 pub fn clear_features(mut self) -> Self {
141 self.features.clear();
142 self
143 }
144
145 #[inline(always)]
148 pub fn add_immutable_feature(mut self, immutable_feature: impl Into<Feature>) -> Self {
149 self.immutable_features.insert(immutable_feature.into());
150 self
151 }
152
153 #[inline(always)]
156 pub fn with_immutable_features(
157 mut self,
158 immutable_features: impl IntoIterator<Item = impl Into<Feature>>,
159 ) -> Self {
160 self.immutable_features = immutable_features.into_iter().map(Into::into).collect();
161 self
162 }
163
164 pub fn replace_immutable_feature(mut self, immutable_feature: impl Into<Feature>) -> Self {
167 self.immutable_features.replace(immutable_feature.into());
168 self
169 }
170
171 #[inline(always)]
173 pub fn clear_immutable_features(mut self) -> Self {
174 self.immutable_features.clear();
175 self
176 }
177
178 pub fn finish(self) -> Result<NftOutput, Error> {
180 let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?;
181
182 verify_unlock_conditions(&unlock_conditions, &self.nft_id)?;
183
184 let features = Features::from_set(self.features)?;
185
186 verify_allowed_features(&features, NftOutput::ALLOWED_FEATURES)?;
187
188 let immutable_features = Features::from_set(self.immutable_features)?;
189
190 verify_allowed_features(&immutable_features, NftOutput::ALLOWED_IMMUTABLE_FEATURES)?;
191
192 let mut output = NftOutput {
193 amount: 1u64,
194 native_tokens: NativeTokens::from_set(self.native_tokens)?,
195 nft_id: self.nft_id,
196 unlock_conditions,
197 features,
198 immutable_features,
199 };
200
201 output.amount = match self.amount {
202 OutputBuilderAmount::Amount(amount) => amount,
203 };
204
205 Ok(output)
206 }
207}
208
209impl From<&NftOutput> for NftOutputBuilder {
210 fn from(output: &NftOutput) -> Self {
211 Self {
212 amount: OutputBuilderAmount::Amount(output.amount),
213 native_tokens: output.native_tokens.iter().copied().collect(),
214 nft_id: output.nft_id,
215 unlock_conditions: output.unlock_conditions.iter().cloned().collect(),
216 features: output.features.iter().cloned().collect(),
217 immutable_features: output.immutable_features.iter().cloned().collect(),
218 }
219 }
220}
221
222#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)]
224#[packable(unpack_error = Error)]
225pub struct NftOutput {
226 amount: u64,
228 native_tokens: NativeTokens,
230 nft_id: NftId,
232 unlock_conditions: UnlockConditions,
233 features: Features,
234 immutable_features: Features,
235}
236
237impl NftOutput {
238 pub const KIND: u8 = 6;
240 pub const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags = UnlockConditionFlags::ADDRESS
242 .union(UnlockConditionFlags::STORAGE_DEPOSIT_RETURN)
243 .union(UnlockConditionFlags::TIMELOCK)
244 .union(UnlockConditionFlags::EXPIRATION);
245 pub const ALLOWED_FEATURES: FeatureFlags = FeatureFlags::SENDER
247 .union(FeatureFlags::METADATA)
248 .union(FeatureFlags::TAG);
249 pub const ALLOWED_IMMUTABLE_FEATURES: FeatureFlags =
251 FeatureFlags::ISSUER.union(FeatureFlags::METADATA);
252
253 #[inline(always)]
255 pub fn build_with_amount(amount: u64, nft_id: NftId) -> NftOutputBuilder {
256 NftOutputBuilder::new_with_amount(amount, nft_id)
257 }
258
259 #[inline(always)]
261 pub fn amount(&self) -> u64 {
262 self.amount
263 }
264
265 #[inline(always)]
267 pub fn native_tokens(&self) -> &NativeTokens {
268 &self.native_tokens
269 }
270
271 #[inline(always)]
273 pub fn nft_id(&self) -> &NftId {
274 &self.nft_id
275 }
276
277 #[inline(always)]
279 pub fn nft_id_non_null(&self, output_id: &OutputId) -> NftId {
280 self.nft_id.or_from_output_id(output_id)
281 }
282
283 #[inline(always)]
285 pub fn unlock_conditions(&self) -> &UnlockConditions {
286 &self.unlock_conditions
287 }
288
289 #[inline(always)]
291 pub fn features(&self) -> &Features {
292 &self.features
293 }
294
295 #[inline(always)]
297 pub fn immutable_features(&self) -> &Features {
298 &self.immutable_features
299 }
300
301 #[inline(always)]
303 pub fn address(&self) -> &Address {
304 self.unlock_conditions
306 .address()
307 .map(|unlock_condition| unlock_condition.address())
308 .unwrap()
309 }
310
311 #[inline(always)]
313 pub fn chain_id(&self) -> ChainId {
314 ChainId::Nft(self.nft_id)
315 }
316
317 pub fn nft_address(&self, output_id: &OutputId) -> NftAddress {
319 NftAddress::new(self.nft_id_non_null(output_id))
320 }
321}
322
323fn verify_unlock_conditions(
324 unlock_conditions: &UnlockConditions,
325 nft_id: &NftId,
326) -> Result<(), Error> {
327 if let Some(unlock_condition) = unlock_conditions.address() {
328 if let Address::Nft(nft_address) = unlock_condition.address() {
329 if !nft_id.is_null() && nft_address.nft_id() == nft_id {
330 return Err(Error::SelfDepositNft(*nft_id));
331 }
332 }
333 } else {
334 return Err(Error::MissingAddressUnlockCondition);
335 }
336
337 verify_allowed_unlock_conditions(unlock_conditions, NftOutput::ALLOWED_UNLOCK_CONDITIONS)
338}