1use alloc::vec::Vec;
5use core::cmp::Ordering;
6
7use packable::{
8 error::{UnpackError, UnpackErrorExt},
9 packer::Packer,
10 unpacker::Unpacker,
11 Packable,
12};
13
14use crate::{
15 address::{Address, AliasAddress},
16 output::{
17 feature::{verify_allowed_features, Feature, FeatureFlags, Features},
18 unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions},
19 verify_output_amount, ChainId, FoundryId, NativeToken, NativeTokens, Output, OutputBuilderAmount, OutputId,
20 Rent, RentStructure, StateTransitionError, StateTransitionVerifier, TokenId, TokenScheme,
21 },
22 protocol::ProtocolParameters,
23 semantic::{ConflictReason, ValidationContext},
24 unlock::Unlock,
25 Error,
26};
27
28#[derive(Clone)]
30#[must_use]
31pub struct FoundryOutputBuilder {
32 amount: OutputBuilderAmount,
33 native_tokens: Vec<NativeToken>,
34 serial_number: u32,
35 token_scheme: TokenScheme,
36 unlock_conditions: Vec<UnlockCondition>,
37 features: Vec<Feature>,
38 immutable_features: Vec<Feature>,
39}
40
41impl FoundryOutputBuilder {
42 pub fn new_with_amount(
44 amount: u64,
45 serial_number: u32,
46 token_scheme: TokenScheme,
47 ) -> Result<FoundryOutputBuilder, Error> {
48 Self::new(OutputBuilderAmount::Amount(amount), serial_number, token_scheme)
49 }
50
51 pub fn new_with_minimum_storage_deposit(
54 rent_structure: RentStructure,
55 serial_number: u32,
56 token_scheme: TokenScheme,
57 ) -> Result<FoundryOutputBuilder, Error> {
58 Self::new(
59 OutputBuilderAmount::MinimumStorageDeposit(rent_structure),
60 serial_number,
61 token_scheme,
62 )
63 }
64
65 fn new(
66 amount: OutputBuilderAmount,
67 serial_number: u32,
68 token_scheme: TokenScheme,
69 ) -> Result<FoundryOutputBuilder, Error> {
70 Ok(Self {
71 amount,
72 native_tokens: Vec::new(),
73 serial_number,
74 token_scheme,
75 unlock_conditions: Vec::new(),
76 features: Vec::new(),
77 immutable_features: Vec::new(),
78 })
79 }
80
81 #[inline(always)]
83 pub fn with_amount(mut self, amount: u64) -> Result<Self, Error> {
84 self.amount = OutputBuilderAmount::Amount(amount);
85 Ok(self)
86 }
87
88 #[inline(always)]
90 pub fn with_minimum_storage_deposit(mut self, rent_structure: RentStructure) -> Self {
91 self.amount = OutputBuilderAmount::MinimumStorageDeposit(rent_structure);
92 self
93 }
94
95 #[inline(always)]
97 pub fn add_native_token(mut self, native_token: NativeToken) -> Self {
98 self.native_tokens.push(native_token);
99 self
100 }
101
102 #[inline(always)]
104 pub fn with_native_tokens(mut self, native_tokens: impl IntoIterator<Item = NativeToken>) -> Self {
105 self.native_tokens = native_tokens.into_iter().collect();
106 self
107 }
108
109 #[inline(always)]
111 pub fn with_serial_number(mut self, serial_number: u32) -> Self {
112 self.serial_number = serial_number;
113 self
114 }
115
116 #[inline(always)]
118 pub fn with_token_scheme(mut self, token_scheme: TokenScheme) -> Self {
119 self.token_scheme = token_scheme;
120 self
121 }
122
123 #[inline(always)]
125 pub fn add_unlock_condition(mut self, unlock_condition: UnlockCondition) -> Self {
126 self.unlock_conditions.push(unlock_condition);
127 self
128 }
129
130 #[inline(always)]
132 pub fn with_unlock_conditions(mut self, unlock_conditions: impl IntoIterator<Item = UnlockCondition>) -> Self {
133 self.unlock_conditions = unlock_conditions.into_iter().collect();
134 self
135 }
136
137 pub fn replace_unlock_condition(mut self, unlock_condition: UnlockCondition) -> Result<Self, Error> {
139 match self
140 .unlock_conditions
141 .iter_mut()
142 .find(|u| u.kind() == unlock_condition.kind())
143 {
144 Some(u) => *u = unlock_condition,
145 None => return Err(Error::CannotReplaceMissingField),
146 }
147 Ok(self)
148 }
149
150 #[inline(always)]
152 pub fn add_feature(mut self, feature: Feature) -> Self {
153 self.features.push(feature);
154 self
155 }
156
157 #[inline(always)]
159 pub fn with_features(mut self, features: impl IntoIterator<Item = Feature>) -> Self {
160 self.features = features.into_iter().collect();
161 self
162 }
163
164 pub fn replace_feature(mut self, feature: Feature) -> Result<Self, Error> {
166 match self.features.iter_mut().find(|f| f.kind() == feature.kind()) {
167 Some(f) => *f = feature,
168 None => return Err(Error::CannotReplaceMissingField),
169 }
170 Ok(self)
171 }
172
173 #[inline(always)]
175 pub fn add_immutable_feature(mut self, immutable_feature: Feature) -> Self {
176 self.immutable_features.push(immutable_feature);
177 self
178 }
179
180 #[inline(always)]
182 pub fn with_immutable_features(mut self, immutable_features: impl IntoIterator<Item = Feature>) -> Self {
183 self.immutable_features = immutable_features.into_iter().collect();
184 self
185 }
186
187 pub fn replace_immutable_feature(mut self, immutable_feature: Feature) -> Result<Self, Error> {
189 match self
190 .immutable_features
191 .iter_mut()
192 .find(|f| f.kind() == immutable_feature.kind())
193 {
194 Some(f) => *f = immutable_feature,
195 None => return Err(Error::CannotReplaceMissingField),
196 }
197 Ok(self)
198 }
199
200 pub fn finish(self, token_supply: u64) -> Result<FoundryOutput, Error> {
202 let unlock_conditions = UnlockConditions::new(self.unlock_conditions)?;
203
204 verify_unlock_conditions(&unlock_conditions)?;
205
206 let features = Features::new(self.features)?;
207
208 verify_allowed_features(&features, FoundryOutput::ALLOWED_FEATURES)?;
209
210 let immutable_features = Features::new(self.immutable_features)?;
211
212 verify_allowed_features(&immutable_features, FoundryOutput::ALLOWED_IMMUTABLE_FEATURES)?;
213
214 let mut output = FoundryOutput {
215 amount: 1u64,
216 native_tokens: NativeTokens::new(self.native_tokens)?,
217 serial_number: self.serial_number,
218 token_scheme: self.token_scheme,
219 unlock_conditions,
220 features,
221 immutable_features,
222 };
223
224 output.amount = match self.amount {
225 OutputBuilderAmount::Amount(amount) => amount,
226 OutputBuilderAmount::MinimumStorageDeposit(rent_structure) => {
227 Output::Foundry(output.clone()).rent_cost(&rent_structure)
228 }
229 };
230
231 verify_output_amount::<true>(&output.amount, &token_supply)?;
232
233 Ok(output)
234 }
235
236 pub fn finish_output(self, token_supply: u64) -> Result<Output, Error> {
238 Ok(Output::Foundry(self.finish(token_supply)?))
239 }
240}
241
242impl From<&FoundryOutput> for FoundryOutputBuilder {
243 fn from(output: &FoundryOutput) -> Self {
244 FoundryOutputBuilder {
245 amount: OutputBuilderAmount::Amount(output.amount),
246 native_tokens: output.native_tokens.to_vec(),
247 serial_number: output.serial_number,
248 token_scheme: output.token_scheme.clone(),
249 unlock_conditions: output.unlock_conditions.to_vec(),
250 features: output.features.to_vec(),
251 immutable_features: output.immutable_features.to_vec(),
252 }
253 }
254}
255
256#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
258#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
259pub struct FoundryOutput {
260 amount: u64,
262 native_tokens: NativeTokens,
264 serial_number: u32,
266 token_scheme: TokenScheme,
267 unlock_conditions: UnlockConditions,
268 features: Features,
269 immutable_features: Features,
270}
271
272impl FoundryOutput {
273 pub const KIND: u8 = 5;
275 pub const ALLOWED_UNLOCK_CONDITIONS: UnlockConditionFlags = UnlockConditionFlags::IMMUTABLE_ALIAS_ADDRESS;
277 pub const ALLOWED_FEATURES: FeatureFlags = FeatureFlags::METADATA;
279 pub const ALLOWED_IMMUTABLE_FEATURES: FeatureFlags = FeatureFlags::METADATA;
281
282 #[inline(always)]
284 pub fn new_with_amount(
285 amount: u64,
286 serial_number: u32,
287 token_scheme: TokenScheme,
288 token_supply: u64,
289 ) -> Result<Self, Error> {
290 FoundryOutputBuilder::new_with_amount(amount, serial_number, token_scheme)?.finish(token_supply)
291 }
292
293 #[inline(always)]
296 pub fn new_with_minimum_storage_deposit(
297 serial_number: u32,
298 token_scheme: TokenScheme,
299 rent_structure: RentStructure,
300 token_supply: u64,
301 ) -> Result<Self, Error> {
302 FoundryOutputBuilder::new_with_minimum_storage_deposit(rent_structure, serial_number, token_scheme)?
303 .finish(token_supply)
304 }
305
306 #[inline(always)]
308 pub fn build_with_amount(
309 amount: u64,
310 serial_number: u32,
311 token_scheme: TokenScheme,
312 ) -> Result<FoundryOutputBuilder, Error> {
313 FoundryOutputBuilder::new_with_amount(amount, serial_number, token_scheme)
314 }
315
316 #[inline(always)]
319 pub fn build_with_minimum_storage_deposit(
320 rent_structure: RentStructure,
321 serial_number: u32,
322 token_scheme: TokenScheme,
323 ) -> Result<FoundryOutputBuilder, Error> {
324 FoundryOutputBuilder::new_with_minimum_storage_deposit(rent_structure, serial_number, token_scheme)
325 }
326
327 #[inline(always)]
329 pub fn amount(&self) -> u64 {
330 self.amount
331 }
332
333 #[inline(always)]
335 pub fn native_tokens(&self) -> &NativeTokens {
336 &self.native_tokens
337 }
338
339 #[inline(always)]
341 pub fn serial_number(&self) -> u32 {
342 self.serial_number
343 }
344
345 #[inline(always)]
347 pub fn token_scheme(&self) -> &TokenScheme {
348 &self.token_scheme
349 }
350
351 #[inline(always)]
353 pub fn unlock_conditions(&self) -> &UnlockConditions {
354 &self.unlock_conditions
355 }
356
357 #[inline(always)]
359 pub fn features(&self) -> &Features {
360 &self.features
361 }
362
363 #[inline(always)]
365 pub fn immutable_features(&self) -> &Features {
366 &self.immutable_features
367 }
368
369 #[inline(always)]
371 pub fn alias_address(&self) -> &AliasAddress {
372 self.unlock_conditions
374 .immutable_alias_address()
375 .map(|unlock_condition| unlock_condition.alias_address())
376 .unwrap()
377 }
378
379 pub fn id(&self) -> FoundryId {
381 FoundryId::build(self.alias_address(), self.serial_number, self.token_scheme.kind())
382 }
383
384 pub fn token_id(&self) -> TokenId {
386 TokenId::from(self.id())
387 }
388
389 #[inline(always)]
391 pub fn chain_id(&self) -> ChainId {
392 ChainId::Foundry(self.id())
393 }
394
395 pub fn unlock(
397 &self,
398 _output_id: &OutputId,
399 unlock: &Unlock,
400 inputs: &[(OutputId, &Output)],
401 context: &mut ValidationContext,
402 ) -> Result<(), ConflictReason> {
403 Address::from(*self.alias_address()).unlock(unlock, inputs, context)
404 }
405}
406
407impl StateTransitionVerifier for FoundryOutput {
408 fn creation(next_state: &Self, context: &ValidationContext) -> Result<(), StateTransitionError> {
409 let alias_chain_id = ChainId::from(*next_state.alias_address().alias_id());
410
411 if let (Some(Output::Alias(input_alias)), Some(Output::Alias(output_alias))) = (
412 context.input_chains.get(&alias_chain_id),
413 context.output_chains.get(&alias_chain_id),
414 ) {
415 if input_alias.foundry_counter() >= next_state.serial_number()
416 || next_state.serial_number() > output_alias.foundry_counter()
417 {
418 return Err(StateTransitionError::InconsistentFoundrySerialNumber);
419 }
420 } else {
421 return Err(StateTransitionError::MissingAliasForFoundry);
422 }
423
424 let token_id = next_state.token_id();
425 let output_tokens = context.output_native_tokens.get(&token_id).copied().unwrap_or_default();
426 let TokenScheme::Simple(ref next_token_scheme) = next_state.token_scheme;
427
428 if context.input_native_tokens.contains_key(&token_id) {
430 return Err(StateTransitionError::InconsistentNativeTokensFoundryCreation);
431 }
432
433 if output_tokens != next_token_scheme.minted_tokens() || !next_token_scheme.melted_tokens().is_zero() {
434 return Err(StateTransitionError::InconsistentNativeTokensFoundryCreation);
435 }
436
437 Ok(())
438 }
439
440 fn transition(
441 current_state: &Self,
442 next_state: &Self,
443 context: &ValidationContext,
444 ) -> Result<(), StateTransitionError> {
445 if current_state.alias_address() != next_state.alias_address()
446 || current_state.serial_number != next_state.serial_number
447 || current_state.immutable_features != next_state.immutable_features
448 {
449 return Err(StateTransitionError::MutatedImmutableField);
450 }
451
452 let token_id = next_state.token_id();
453 let input_tokens = context.input_native_tokens.get(&token_id).copied().unwrap_or_default();
454 let output_tokens = context.output_native_tokens.get(&token_id).copied().unwrap_or_default();
455 let TokenScheme::Simple(ref current_token_scheme) = current_state.token_scheme;
456 let TokenScheme::Simple(ref next_token_scheme) = next_state.token_scheme;
457
458 if current_token_scheme.maximum_supply() != next_token_scheme.maximum_supply() {
459 return Err(StateTransitionError::MutatedImmutableField);
460 }
461
462 if current_token_scheme.minted_tokens() > next_token_scheme.minted_tokens()
463 || current_token_scheme.melted_tokens() > next_token_scheme.melted_tokens()
464 {
465 return Err(StateTransitionError::NonMonotonicallyIncreasingNativeTokens);
466 }
467
468 match input_tokens.cmp(&output_tokens) {
469 Ordering::Less => {
470 let minted_diff = next_token_scheme.minted_tokens() - current_token_scheme.minted_tokens();
474 let token_diff = output_tokens - input_tokens;
476
477 if minted_diff != token_diff {
478 return Err(StateTransitionError::InconsistentNativeTokensMint);
479 }
480
481 if current_token_scheme.melted_tokens() != next_token_scheme.melted_tokens() {
482 return Err(StateTransitionError::InconsistentNativeTokensMint);
483 }
484 }
485 Ordering::Equal => {
486 if current_token_scheme.minted_tokens() != next_token_scheme.minted_tokens()
489 || current_token_scheme.melted_tokens() != next_token_scheme.melted_tokens()
490 {
491 return Err(StateTransitionError::InconsistentNativeTokensTransition);
492 }
493 }
494 Ordering::Greater => {
495 if current_token_scheme.melted_tokens() != next_token_scheme.melted_tokens()
498 && current_token_scheme.minted_tokens() != next_token_scheme.minted_tokens()
499 {
500 return Err(StateTransitionError::InconsistentNativeTokensMeltBurn);
501 }
502
503 let melted_diff = next_token_scheme.melted_tokens() - current_token_scheme.melted_tokens();
505 let token_diff = input_tokens - output_tokens;
507
508 if melted_diff > token_diff {
509 return Err(StateTransitionError::InconsistentNativeTokensMeltBurn);
510 }
511 }
512 }
513
514 Ok(())
515 }
516
517 fn destruction(current_state: &Self, context: &ValidationContext) -> Result<(), StateTransitionError> {
518 let token_id = current_state.token_id();
519 let input_tokens = context.input_native_tokens.get(&token_id).copied().unwrap_or_default();
520 let TokenScheme::Simple(ref current_token_scheme) = current_state.token_scheme;
521
522 if context.output_native_tokens.contains_key(&token_id) {
524 return Err(StateTransitionError::InconsistentNativeTokensFoundryDestruction);
525 }
526
527 let minted_melted_diff = current_token_scheme.minted_tokens() - current_token_scheme.melted_tokens();
529
530 if minted_melted_diff != input_tokens {
531 return Err(StateTransitionError::InconsistentNativeTokensFoundryDestruction);
532 }
533
534 Ok(())
535 }
536}
537
538impl Packable for FoundryOutput {
539 type UnpackError = Error;
540 type UnpackVisitor = ProtocolParameters;
541
542 fn pack<P: Packer>(&self, packer: &mut P) -> Result<(), P::Error> {
543 self.amount.pack(packer)?;
544 self.native_tokens.pack(packer)?;
545 self.serial_number.pack(packer)?;
546 self.token_scheme.pack(packer)?;
547 self.unlock_conditions.pack(packer)?;
548 self.features.pack(packer)?;
549 self.immutable_features.pack(packer)?;
550
551 Ok(())
552 }
553
554 fn unpack<U: Unpacker, const VERIFY: bool>(
555 unpacker: &mut U,
556 visitor: &Self::UnpackVisitor,
557 ) -> Result<Self, UnpackError<Self::UnpackError, U::Error>> {
558 let amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?;
559
560 verify_output_amount::<VERIFY>(&amount, &visitor.token_supply()).map_err(UnpackError::Packable)?;
561
562 let native_tokens = NativeTokens::unpack::<_, VERIFY>(unpacker, &())?;
563 let serial_number = u32::unpack::<_, VERIFY>(unpacker, &()).coerce()?;
564 let token_scheme = TokenScheme::unpack::<_, VERIFY>(unpacker, &())?;
565
566 let unlock_conditions = UnlockConditions::unpack::<_, VERIFY>(unpacker, visitor)?;
567
568 if VERIFY {
569 verify_unlock_conditions(&unlock_conditions).map_err(UnpackError::Packable)?;
570 }
571
572 let features = Features::unpack::<_, VERIFY>(unpacker, &())?;
573
574 if VERIFY {
575 verify_allowed_features(&features, FoundryOutput::ALLOWED_FEATURES).map_err(UnpackError::Packable)?;
576 }
577
578 let immutable_features = Features::unpack::<_, VERIFY>(unpacker, &())?;
579
580 if VERIFY {
581 verify_allowed_features(&immutable_features, FoundryOutput::ALLOWED_IMMUTABLE_FEATURES)
582 .map_err(UnpackError::Packable)?;
583 }
584
585 Ok(Self {
586 amount,
587 native_tokens,
588 serial_number,
589 token_scheme,
590 unlock_conditions,
591 features,
592 immutable_features,
593 })
594 }
595}
596
597fn verify_unlock_conditions(unlock_conditions: &UnlockConditions) -> Result<(), Error> {
598 if unlock_conditions.immutable_alias_address().is_none() {
599 Err(Error::MissingAddressUnlockCondition)
600 } else {
601 verify_allowed_unlock_conditions(unlock_conditions, FoundryOutput::ALLOWED_UNLOCK_CONDITIONS)
602 }
603}
604
605#[cfg(feature = "dto")]
606#[allow(missing_docs)]
607pub mod dto {
608 use serde::{Deserialize, Serialize};
609
610 use super::*;
611 use crate::{
612 error::dto::DtoError,
613 output::{
614 dto::OutputBuilderAmountDto, feature::dto::FeatureDto, native_token::dto::NativeTokenDto,
615 token_scheme::dto::TokenSchemeDto, unlock_condition::dto::UnlockConditionDto,
616 },
617 };
618
619 #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
621 pub struct FoundryOutputDto {
622 #[serde(rename = "type")]
623 pub kind: u8,
624 pub amount: String,
626 #[serde(rename = "nativeTokens", skip_serializing_if = "Vec::is_empty", default)]
628 pub native_tokens: Vec<NativeTokenDto>,
629 #[serde(rename = "serialNumber")]
631 pub serial_number: u32,
632 #[serde(rename = "tokenScheme")]
633 pub token_scheme: TokenSchemeDto,
634 #[serde(rename = "unlockConditions")]
635 pub unlock_conditions: Vec<UnlockConditionDto>,
636 #[serde(skip_serializing_if = "Vec::is_empty", default)]
637 pub features: Vec<FeatureDto>,
638 #[serde(rename = "immutableFeatures", skip_serializing_if = "Vec::is_empty", default)]
639 pub immutable_features: Vec<FeatureDto>,
640 }
641
642 impl From<&FoundryOutput> for FoundryOutputDto {
643 fn from(value: &FoundryOutput) -> Self {
644 Self {
645 kind: FoundryOutput::KIND,
646 amount: value.amount().to_string(),
647 native_tokens: value.native_tokens().iter().map(Into::into).collect::<_>(),
648 serial_number: value.serial_number(),
649 token_scheme: value.token_scheme().into(),
650 unlock_conditions: value.unlock_conditions().iter().map(Into::into).collect::<_>(),
651 features: value.features().iter().map(Into::into).collect::<_>(),
652 immutable_features: value.immutable_features().iter().map(Into::into).collect::<_>(),
653 }
654 }
655 }
656
657 impl FoundryOutput {
658 pub fn try_from_dto(value: &FoundryOutputDto, token_supply: u64) -> Result<FoundryOutput, DtoError> {
659 let mut builder = FoundryOutputBuilder::new_with_amount(
660 value
661 .amount
662 .parse::<u64>()
663 .map_err(|_| DtoError::InvalidField("amount"))?,
664 value.serial_number,
665 (&value.token_scheme).try_into()?,
666 )?;
667
668 for t in &value.native_tokens {
669 builder = builder.add_native_token(t.try_into()?);
670 }
671
672 for u in &value.unlock_conditions {
673 builder = builder.add_unlock_condition(UnlockCondition::try_from_dto(u, token_supply)?);
674 }
675
676 for b in &value.features {
677 builder = builder.add_feature(b.try_into()?);
678 }
679
680 for b in &value.immutable_features {
681 builder = builder.add_immutable_feature(b.try_into()?);
682 }
683
684 Ok(builder.finish(token_supply)?)
685 }
686
687 #[allow(clippy::too_many_arguments)]
688 pub fn try_from_dtos(
689 amount: OutputBuilderAmountDto,
690 native_tokens: Option<Vec<NativeTokenDto>>,
691 serial_number: u32,
692 token_scheme: &TokenSchemeDto,
693 unlock_conditions: Vec<UnlockConditionDto>,
694 features: Option<Vec<FeatureDto>>,
695 immutable_features: Option<Vec<FeatureDto>>,
696 token_supply: u64,
697 ) -> Result<FoundryOutput, DtoError> {
698 let token_scheme = TokenScheme::try_from(token_scheme)?;
699
700 let mut builder = match amount {
701 OutputBuilderAmountDto::Amount(amount) => FoundryOutputBuilder::new_with_amount(
702 amount.parse().map_err(|_| DtoError::InvalidField("amount"))?,
703 serial_number,
704 token_scheme,
705 )?,
706 OutputBuilderAmountDto::MinimumStorageDeposit(rent_structure) => {
707 FoundryOutputBuilder::new_with_minimum_storage_deposit(rent_structure, serial_number, token_scheme)?
708 }
709 };
710
711 if let Some(native_tokens) = native_tokens {
712 let native_tokens = native_tokens
713 .iter()
714 .map(NativeToken::try_from)
715 .collect::<Result<Vec<NativeToken>, DtoError>>()?;
716 builder = builder.with_native_tokens(native_tokens);
717 }
718
719 let unlock_conditions = unlock_conditions
720 .iter()
721 .map(|u| UnlockCondition::try_from_dto(u, token_supply))
722 .collect::<Result<Vec<UnlockCondition>, DtoError>>()?;
723 builder = builder.with_unlock_conditions(unlock_conditions);
724
725 if let Some(features) = features {
726 let features = features
727 .iter()
728 .map(Feature::try_from)
729 .collect::<Result<Vec<Feature>, DtoError>>()?;
730 builder = builder.with_features(features);
731 }
732
733 if let Some(immutable_features) = immutable_features {
734 let immutable_features = immutable_features
735 .iter()
736 .map(Feature::try_from)
737 .collect::<Result<Vec<Feature>, DtoError>>()?;
738 builder = builder.with_immutable_features(immutable_features);
739 }
740
741 Ok(builder.finish(token_supply)?)
742 }
743 }
744}