diff --git a/bindings/nodejs/examples/client/11-build-output.ts b/bindings/nodejs/examples/client/11-build-output.ts index fb44f9e71f..187d4dc64b 100644 --- a/bindings/nodejs/examples/client/11-build-output.ts +++ b/bindings/nodejs/examples/client/11-build-output.ts @@ -55,7 +55,9 @@ async function run() { const basicOutputWithMetadata = await client.buildBasicOutput({ amount: BigInt(1000000), unlockConditions: [addressUnlockCondition], - features: [new MetadataFeature(utf8ToHex('Hello World!'))], + features: [ + new MetadataFeature({ data: utf8ToHex('Hello World!') }), + ], }); console.log(JSON.stringify(basicOutputWithMetadata, null, 2)); diff --git a/bindings/nodejs/examples/client/13-build-account-output.ts b/bindings/nodejs/examples/client/13-build-account-output.ts index f1dfcf2992..0b37a76302 100644 --- a/bindings/nodejs/examples/client/13-build-account-output.ts +++ b/bindings/nodejs/examples/client/13-build-account-output.ts @@ -44,11 +44,11 @@ async function run() { ], features: [ new SenderFeature(new Ed25519Address(hexAddress)), - new MetadataFeature(utf8ToHex('hello')), + new MetadataFeature({ data: utf8ToHex('hello') }), ], immutableFeatures: [ new IssuerFeature(new Ed25519Address(hexAddress)), - new MetadataFeature(utf8ToHex('hello')), + new MetadataFeature({ data: utf8ToHex('hello') }), ], }); diff --git a/bindings/nodejs/examples/client/15-build-nft-output.ts b/bindings/nodejs/examples/client/15-build-nft-output.ts index 1cd9ab3f80..494695a5d7 100644 --- a/bindings/nodejs/examples/client/15-build-nft-output.ts +++ b/bindings/nodejs/examples/client/15-build-nft-output.ts @@ -56,7 +56,9 @@ async function run() { ], features: [ new SenderFeature(new Ed25519Address(hexAddress)), - new MetadataFeature(utf8ToHex('mutable metadata')), + new MetadataFeature({ + data: utf8ToHex('mutable metadata'), + }), new TagFeature(utf8ToHex('my tag')), ], }); diff --git a/bindings/nodejs/examples/how_tos/outputs/features.ts b/bindings/nodejs/examples/how_tos/outputs/features.ts index c6bb77e9ac..996261c13b 100644 --- a/bindings/nodejs/examples/how_tos/outputs/features.ts +++ b/bindings/nodejs/examples/how_tos/outputs/features.ts @@ -53,7 +53,9 @@ async function run() { const nftOutputWithMetadata = await client.buildNftOutput({ nftId: '0x0000000000000000000000000000000000000000000000000000000000000000', unlockConditions: [addressUnlockCondition], - features: [new MetadataFeature(utf8ToHex('Hello, World!'))], + features: [ + new MetadataFeature({ data: utf8ToHex('Hello, World!') }), + ], }); // Output with immutable metadata feature @@ -61,7 +63,7 @@ async function run() { nftId: '0x0000000000000000000000000000000000000000000000000000000000000000', unlockConditions: [addressUnlockCondition], immutableFeatures: [ - new MetadataFeature(utf8ToHex('Hello, World!')), + new MetadataFeature({ data: utf8ToHex('Hello, World!') }), ], }); diff --git a/bindings/nodejs/lib/types/block/output/feature.ts b/bindings/nodejs/lib/types/block/output/feature.ts index 1d56e6d08a..1f1e1ade19 100644 --- a/bindings/nodejs/lib/types/block/output/feature.ts +++ b/bindings/nodejs/lib/types/block/output/feature.ts @@ -13,6 +13,11 @@ import { EpochIndex } from '../../block/slot'; import { NativeToken } from '../../models/native-token'; import { HexEncodedString } from '../../utils/hex-encoding'; +/** + * Printable ASCII characters. + */ +export declare type PrintableASCII = string; + /** * All of the feature block types. */ @@ -88,14 +93,30 @@ class IssuerFeature extends Feature { */ class MetadataFeature extends Feature { /** Defines metadata (arbitrary binary data) that will be stored in the output. */ - readonly data: string; + readonly entries: { [key: PrintableASCII]: HexEncodedString }; /** - * @param data The metadata stored with the feature. + * @param entries The metadata stored with the feature. */ - constructor(data: string) { + constructor(entries: { [key: PrintableASCII]: HexEncodedString }) { super(FeatureType.Metadata); - this.data = data; + this.entries = entries; + } +} + +/** + * A Metadata Feature that can only be changed by the State Controller. + */ +class StateMetadataFeature extends Feature { + /** Defines metadata (arbitrary binary data) that will be stored in the output. */ + readonly entries: { [key: PrintableASCII]: HexEncodedString }; + + /** + * @param entries The metadata stored with the feature. + */ + constructor(entries: { [key: PrintableASCII]: HexEncodedString }) { + super(FeatureType.StateMetadata); + this.entries = entries; } } @@ -213,6 +234,7 @@ const FeatureDiscriminator = { { value: SenderFeature, name: FeatureType.Sender as any }, { value: IssuerFeature, name: FeatureType.Issuer as any }, { value: MetadataFeature, name: FeatureType.Metadata as any }, + { value: StateMetadataFeature, name: FeatureType.StateMetadata as any }, { value: TagFeature, name: FeatureType.Tag as any }, { value: NativeTokenFeature, name: FeatureType.NativeToken as any }, { value: BlockIssuerFeature, name: FeatureType.BlockIssuer as any }, @@ -227,6 +249,7 @@ export { SenderFeature, IssuerFeature, MetadataFeature, + StateMetadataFeature, TagFeature, NativeTokenFeature, BlockIssuerFeature, diff --git a/bindings/nodejs/lib/types/block/output/irc-27.ts b/bindings/nodejs/lib/types/block/output/irc-27.ts index 19825e3197..86f2cf20c9 100644 --- a/bindings/nodejs/lib/types/block/output/irc-27.ts +++ b/bindings/nodejs/lib/types/block/output/irc-27.ts @@ -88,7 +88,7 @@ class Irc27Metadata { } asFeature(): MetadataFeature { - return new MetadataFeature(this.asHex()); + return new MetadataFeature({ 'irc-27': this.asHex() }); } } diff --git a/bindings/nodejs/lib/types/block/output/irc-30.ts b/bindings/nodejs/lib/types/block/output/irc-30.ts index daba7224a7..ea9e40868d 100644 --- a/bindings/nodejs/lib/types/block/output/irc-30.ts +++ b/bindings/nodejs/lib/types/block/output/irc-30.ts @@ -61,7 +61,7 @@ class Irc30Metadata { } asFeature(): MetadataFeature { - return new MetadataFeature(this.asHex()); + return new MetadataFeature({ 'irc-30': this.asHex() }); } } diff --git a/bindings/python/examples/client/build_account.py b/bindings/python/examples/client/build_account.py index 95efa4f88d..8a0132a40a 100644 --- a/bindings/python/examples/client/build_account.py +++ b/bindings/python/examples/client/build_account.py @@ -24,11 +24,11 @@ ] features = [ SenderFeature(Ed25519Address(hexAddress)), - MetadataFeature(utf8_to_hex('Hello, World!')) + MetadataFeature({'data': utf8_to_hex('Hello, World!')}) ] immutable_features = [ IssuerFeature(Ed25519Address(hexAddress)), - MetadataFeature(utf8_to_hex('Hello, World!')) + MetadataFeature({'data': utf8_to_hex('Hello, World!')}) ] # Build account output diff --git a/bindings/python/examples/client/build_basic.py b/bindings/python/examples/client/build_basic.py index 455ec49084..8f6916ba67 100644 --- a/bindings/python/examples/client/build_basic.py +++ b/bindings/python/examples/client/build_basic.py @@ -37,7 +37,7 @@ address_unlock_condition, ], features=[ - MetadataFeature(utf8_to_hex('Hello, World!')) + MetadataFeature({'data': utf8_to_hex('Hello, World!')}) ], amount=1000000, ) diff --git a/bindings/python/examples/how_tos/outputs/features.py b/bindings/python/examples/how_tos/outputs/features.py index c8fc009baa..463d1b8c03 100644 --- a/bindings/python/examples/how_tos/outputs/features.py +++ b/bindings/python/examples/how_tos/outputs/features.py @@ -57,7 +57,7 @@ address_unlock_condition, ], features=[ - MetadataFeature(utf8_to_hex('Hello, World!')) + MetadataFeature({'data': utf8_to_hex('Hello, World!')}) ], ) outputs.append(nft_output) @@ -69,7 +69,7 @@ address_unlock_condition, ], immutable_features=[ - MetadataFeature(utf8_to_hex('Hello, World!')) + MetadataFeature({'data': utf8_to_hex('Hello, World!')}) ], ) outputs.append(nft_output) diff --git a/bindings/python/iota_sdk/types/feature.py b/bindings/python/iota_sdk/types/feature.py index 5dc4cfb5f5..bb7f6cf78b 100644 --- a/bindings/python/iota_sdk/types/feature.py +++ b/bindings/python/iota_sdk/types/feature.py @@ -81,6 +81,20 @@ class MetadataFeature: entries: Dict[str, HexStr] +@json +@dataclass +class StateMetadataFeature: + """A Metadata Feature that can only be changed by the State Controller. + Attributes: + entries: A key-value map where the keys are graphic ASCII strings and the values hex-encoded binary data. + """ + type: int = field( + default_factory=lambda: int( + FeatureType.StateMetadata), + init=False) + entries: Dict[str, HexStr] + + @json @dataclass class TagFeature: @@ -149,7 +163,7 @@ class StakingFeature: Feature: TypeAlias = Union[SenderFeature, IssuerFeature, - MetadataFeature, TagFeature, NativeTokenFeature, BlockIssuerFeature, StakingFeature] + MetadataFeature, StateMetadataFeature, TagFeature, NativeTokenFeature, BlockIssuerFeature, StakingFeature] # pylint: disable=too-many-return-statements @@ -167,6 +181,8 @@ def deserialize_feature(d: Dict[str, Any]) -> Feature: return IssuerFeature.from_dict(d) if feature_type == FeatureType.Metadata: return MetadataFeature.from_dict(d) + if feature_type == FeatureType.StateMetadata: + return StateMetadataFeature.from_dict(d) if feature_type == FeatureType.Tag: return TagFeature.from_dict(d) if feature_type == FeatureType.NativeToken: diff --git a/bindings/python/iota_sdk/types/irc_27.py b/bindings/python/iota_sdk/types/irc_27.py index e66de06bb1..3ebed849ed 100644 --- a/bindings/python/iota_sdk/types/irc_27.py +++ b/bindings/python/iota_sdk/types/irc_27.py @@ -70,4 +70,4 @@ def as_hex(self): def as_feature(self): """Turns this schema into a MetadataFeature type """ - MetadataFeature(self.as_hex()) + MetadataFeature({'irc-27': self.as_hex()}) diff --git a/bindings/python/iota_sdk/types/irc_30.py b/bindings/python/iota_sdk/types/irc_30.py index 3c3341f435..afa3166df6 100644 --- a/bindings/python/iota_sdk/types/irc_30.py +++ b/bindings/python/iota_sdk/types/irc_30.py @@ -47,4 +47,4 @@ def as_hex(self): def as_feature(self): """Turns this schema into a MetadataFeature type """ - MetadataFeature(self.as_hex()) + MetadataFeature({'irc-30': self.as_hex()}) diff --git a/sdk/src/types/block/output/anchor.rs b/sdk/src/types/block/output/anchor.rs index 3df3d1711c..7f1089e13a 100644 --- a/sdk/src/types/block/output/anchor.rs +++ b/sdk/src/types/block/output/anchor.rs @@ -473,8 +473,7 @@ impl AnchorOutput { } else if next_state.state_index == current_state.state_index { // Governance transition. if current_state.amount != next_state.amount - // TODO https://github.com/iotaledger/iota-sdk/issues/1650 - // || current_state.state_metadata != next_state.state_metadata + || current_state.features().state_metadata() != next_state.features().state_metadata() { return Err(TransactionFailureReason::AnchorInvalidGovernanceTransition); } diff --git a/sdk/src/types/block/output/feature/state_metadata.rs b/sdk/src/types/block/output/feature/state_metadata.rs index de5c806d05..5bb8a3bd6c 100644 --- a/sdk/src/types/block/output/feature/state_metadata.rs +++ b/sdk/src/types/block/output/feature/state_metadata.rs @@ -23,7 +23,7 @@ use super::{ }; use crate::types::block::{output::StorageScore, protocol::WorkScore, Error}; -/// Defines metadata, arbitrary binary data, that will be stored in the output. +/// A Metadata Feature that can only be changed by the State Controller. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct StateMetadataFeature(pub(crate) MetadataBTreeMapPrefix);