diff --git a/bindings/wasm/docs/api-reference.md b/bindings/wasm/docs/api-reference.md index 41d0f5f654..fe562d2d8f 100644 --- a/bindings/wasm/docs/api-reference.md +++ b/bindings/wasm/docs/api-reference.md @@ -179,11 +179,8 @@ See IVerifierOptions.

## Members
-
MethodRelationship
+
StateMetadataEncoding
-
DIDType
-

Supported types representing a DID that can be generated by the storage interface.

-
StatusCheck

Controls validation behaviour when checking whether or not a credential has been revoked by its credentialStatus.

@@ -226,11 +223,14 @@ This variant is the default used if no other variant is specified when construct
FirstError

Return after the first error occurs.

+
DIDType
+

Supported types representing a DID that can be generated by the storage interface.

+
KeyType
-
DIDMessageEncoding
+
MethodRelationship
-
StateMetadataEncoding
+
DIDMessageEncoding
@@ -274,10 +274,10 @@ publishing to the Tangle. * [.unrevokeCredentials(fragment, indices)](#Account+unrevokeCredentials) ⇒ Promise.<void> * [.encryptData(plaintext, associated_data, encryption_algorithm, cek_algorithm, public_key)](#Account+encryptData) ⇒ [Promise.<EncryptedData>](#EncryptedData) * [.decryptData(data, encryption_algorithm, cek_algorithm, fragment)](#Account+decryptData) ⇒ Promise.<Uint8Array> - * [.deleteMethod(options)](#Account+deleteMethod) ⇒ Promise.<void> * [.deleteService(options)](#Account+deleteService) ⇒ Promise.<void> * [.setAlsoKnownAs(options)](#Account+setAlsoKnownAs) ⇒ Promise.<void> * [.setController(options)](#Account+setController) ⇒ Promise.<void> + * [.deleteMethod(options)](#Account+deleteMethod) ⇒ Promise.<void> @@ -517,17 +517,6 @@ Returns the decrypted text. | cek_algorithm | [CekAlgorithm](#CekAlgorithm) | | fragment | string | - - -### account.deleteMethod(options) ⇒ Promise.<void> -Deletes a verification method if the method exists. - -**Kind**: instance method of [Account](#Account) - -| Param | Type | -| --- | --- | -| options | DeleteMethodOptions | - ### account.deleteService(options) ⇒ Promise.<void> @@ -561,6 +550,17 @@ Sets the controllers of the DID document. | --- | --- | | options | SetControllerOptions | + + +### account.deleteMethod(options) ⇒ Promise.<void> +Deletes a verification method if the method exists. + +**Kind**: instance method of [Account](#Account) + +| Param | Type | +| --- | --- | +| options | DeleteMethodOptions | + ## AccountBuilder @@ -5127,6 +5127,7 @@ Deserializes an instance from a JSON object. * _static_ * [.newWithId(id)](#StardustDocument.newWithId) ⇒ [StardustDocument](#StardustDocument) * [.unpack(did, stateMetadata, allowEmpty)](#StardustDocument.unpack) ⇒ [StardustDocument](#StardustDocument) + * [.unpackFromBlock(network, block)](#StardustDocument.unpackFromBlock) ⇒ [Array.<StardustDocument>](#StardustDocument) * [.fromJSON(json)](#StardustDocument.fromJSON) ⇒ [StardustDocument](#StardustDocument) @@ -5527,6 +5528,21 @@ encoded in the `AliasId` alone. | stateMetadata | Uint8Array | | allowEmpty | boolean | + + +### StardustDocument.unpackFromBlock(network, block) ⇒ [Array.<StardustDocument>](#StardustDocument) +Returns all DID documents of the Alias Outputs contained in the block's transaction payload +outputs, if any. + +Errors if any Alias Output does not contain a valid or empty DID Document. + +**Kind**: static method of [StardustDocument](#StardustDocument) + +| Param | Type | +| --- | --- | +| network | string | +| block | IBlock | + ### StardustDocument.fromJSON(json) ⇒ [StardustDocument](#StardustDocument) @@ -6263,15 +6279,9 @@ This is possible because Ed25519 is birationally equivalent to Curve25519 used b | --- | --- | | publicKey | Uint8Array | - - -## MethodRelationship -**Kind**: global variable - - -## DIDType -Supported types representing a DID that can be generated by the storage interface. + +## StateMetadataEncoding **Kind**: global variable @@ -6350,18 +6360,24 @@ Return all errors that occur during validation. ## FirstError Return after the first error occurs. +**Kind**: global variable + + +## DIDType +Supported types representing a DID that can be generated by the storage interface. + **Kind**: global variable ## KeyType **Kind**: global variable - + -## DIDMessageEncoding +## MethodRelationship **Kind**: global variable - + -## StateMetadataEncoding +## DIDMessageEncoding **Kind**: global variable diff --git a/bindings/wasm/examples-stardust/src/ex0_create_did.ts b/bindings/wasm/examples-stardust/src/ex0_create_did.ts index f13a6df25d..ca690a9f05 100644 --- a/bindings/wasm/examples-stardust/src/ex0_create_did.ts +++ b/bindings/wasm/examples-stardust/src/ex0_create_did.ts @@ -10,8 +10,7 @@ import { StardustIdentityClient, StardustVerificationMethod } from '../../node'; - -import {Bech32Helper, IAliasOutput,} from '@iota/iota.js'; +import {Bech32Helper, IAliasOutput} from '@iota/iota.js'; import {Bip39} from "@iota/crypto.js"; import fetch from "node-fetch"; import {Client, MnemonicSecretManager, SecretManager} from "@cycraig/iota-client-wasm/node"; @@ -49,8 +48,8 @@ export async function createIdentity(): Promise<{ }))[0]; console.log("Wallet address Bech32:", walletAddressBech32); - // Request funds for the newly-created wallet - only works on development networks. - await requestFundsFromFaucet(walletAddressBech32); + // Request funds for the wallet, if needed - only works on development networks. + await ensureAddressHasFunds(client, walletAddressBech32); // Create a new DID document with a placeholder DID. // The DID will be derived from the Alias Id of the Alias Output after publishing. @@ -78,7 +77,46 @@ export async function createIdentity(): Promise<{ }; } -/** Request tokens from the faucet API. */ +/** Request funds from the testnet faucet API, if needed, and wait for them to show in the wallet. */ +async function ensureAddressHasFunds(client: Client, addressBech32: string) { + let balance = await getAddressBalance(client, addressBech32); + if (balance > 0) { + return; + } + + await requestFundsFromFaucet(addressBech32); + + for (let i = 0; i < 9; i++) { + // Wait for the funds to reflect. + await new Promise(f => setTimeout(f, 5000)); + + let balance = await getAddressBalance(client, addressBech32); + if (balance > 0) { + break; + } + } +} + +/** Returns the balance of the given Bech32-encoded address. */ +async function getAddressBalance(client: Client, addressBech32: string): Promise { + // TODO: use the `addresses/ed25519/` API to get the balance? + const outputIds = await client.basicOutputIds([ + {address: addressBech32}, + {hasExpiration: false}, + {hasTimelock: false}, + {hasStorageDepositReturn: false} + ]); + const outputs = await client.getOutputs(outputIds); + + let totalAmount = 0; + for (const output of outputs) { + totalAmount += Number(output.output.amount); + } + + return totalAmount; +} + +/** Request tokens from the testnet faucet API. */ async function requestFundsFromFaucet(addressBech32: string) { const requestObj = JSON.stringify({address: addressBech32}); let errorMessage, data; diff --git a/bindings/wasm/examples-stardust/src/ex1_update_did.ts b/bindings/wasm/examples-stardust/src/ex1_update_did.ts index f4889db535..8fea877b66 100644 --- a/bindings/wasm/examples-stardust/src/ex1_update_did.ts +++ b/bindings/wasm/examples-stardust/src/ex1_update_did.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import {MethodRelationship, StardustDocument, StardustService, Timestamp} from '../../node'; -import {IAliasOutput, IRent, TransactionHelper,} from '@iota/iota.js'; +import {IAliasOutput, IRent, TransactionHelper} from '@iota/iota.js'; import {createIdentity} from "./ex0_create_did"; diff --git a/bindings/wasm/examples-stardust/src/ex4_delete_did.ts b/bindings/wasm/examples-stardust/src/ex4_delete_did.ts index 342b39d409..976bc5977d 100644 --- a/bindings/wasm/examples-stardust/src/ex4_delete_did.ts +++ b/bindings/wasm/examples-stardust/src/ex4_delete_did.ts @@ -15,9 +15,6 @@ export async function deleteIdentity() { const destinationAddress = Bech32Helper.addressFromBech32(walletAddressBech32, await didClient.getNetworkHrp()); await didClient.deleteDidOutput(secretManager, destinationAddress, did); - // Wait for the node to index the new state. - await new Promise(f => setTimeout(f, 5000)); - // Attempting to resolve a deleted DID results in a `NotFound` error. let deleted = false; try { diff --git a/bindings/wasm/lib/stardust_identity_client.ts b/bindings/wasm/lib/stardust_identity_client.ts index e26b8d192f..9b2a046228 100644 --- a/bindings/wasm/lib/stardust_identity_client.ts +++ b/bindings/wasm/lib/stardust_identity_client.ts @@ -9,17 +9,11 @@ import { AddressTypes, ALIAS_OUTPUT_TYPE, IAliasOutput, - IBlock, IOutputResponse, IRent, - ITransactionPayload, IUTXOInput, - OutputTypes, - TRANSACTION_PAYLOAD_TYPE, TransactionHelper } from '@iota/iota.js'; -import {Converter} from '@iota/util.js'; -import {Blake2b} from '@iota/crypto.js'; /** Provides operations for IOTA UTXO DID Documents with Alias Outputs. */ export class StardustIdentityClient implements IStardustIdentityClient { @@ -120,7 +114,7 @@ export class StardustIdentityClient implements IStardustIdentityClient { await this.client.retryUntilIncluded(blockId); // Extract document with computed AliasId. - const documents = extractDocumentsFromBlock(networkHrp, block); + const documents = StardustDocument.unpackFromBlock(networkHrp, block); if (documents.length < 1) { throw new Error("publishDidOutput: no DID document in transaction payload"); } @@ -166,60 +160,3 @@ export class StardustIdentityClient implements IStardustIdentityClient { await this.client.retryUntilIncluded(blockId); } } - -/** Compute the AliasId as a prefix-hex encoded string. */ -function computeAliasId(transactionIdHex: string, outputIndex: number): string { - const outputIdHex: string = TransactionHelper.outputIdFromTransactionData(transactionIdHex, outputIndex); - return computeAliasIdFromOutputId(outputIdHex); -} - -/** Compute the AliasId as a prefix-hex encoded string. */ -function computeAliasIdFromOutputId(outputIdHex: string): string { - // Blake2b-256 digest of output id. - const outputIdBytes: Uint8Array = Converter.hexToBytes(outputIdHex); - const digest: Uint8Array = Blake2b.sum256(outputIdBytes); - return Converter.bytesToHex(digest, true); -} - -/** Extract all DID documents of the Alias Outputs contained in a transaction payload, if any. */ -function extractDocumentsFromBlock(networkHrp: string, block: IBlock): StardustDocument[] { - const documents: StardustDocument[] = []; - - if (block.payload === undefined || block.payload?.type !== TRANSACTION_PAYLOAD_TYPE) { - throw new Error("failed to extract documents from block, transaction payload missing or wrong type"); - } - const payload: ITransactionPayload = block.payload; - - // Compute TransactionId. - const transactionPayloadHash: Uint8Array = TransactionHelper.getTransactionPayloadHash(payload); - const transactionId: string = Converter.bytesToHex(transactionPayloadHash, true); - - // Loop over Alias Outputs. - const outputs: OutputTypes[] = payload.essence.outputs; - for (let index = 0; index < outputs.length; index += 1) { - const output = outputs[index]; - if (output.type !== ALIAS_OUTPUT_TYPE) { - continue; - } - - // Compute Alias Id. - let aliasIdHex: string; - if (output.stateIndex === 0) { - aliasIdHex = computeAliasId(transactionId, index); - } else { - aliasIdHex = output.aliasId; - } - const aliasId: Uint8Array = Converter.hexToBytes(aliasIdHex); - - // Unpack document. - const did: StardustDID = new StardustDID(aliasId, networkHrp); - let stateMetadata: Uint8Array; - if (output.stateMetadata === undefined) { - stateMetadata = new Uint8Array(0); - } else { - stateMetadata = Converter.hexToBytes(output.stateMetadata); - } - documents.push(StardustDocument.unpack(did, stateMetadata, true)); - } - return documents; -} diff --git a/bindings/wasm/package-lock.json b/bindings/wasm/package-lock.json index e60260e666..697755f84d 100644 --- a/bindings/wasm/package-lock.json +++ b/bindings/wasm/package-lock.json @@ -14,6 +14,7 @@ "node-fetch": "^2.6.7" }, "devDependencies": { + "@iota/crypto.js": "^1.9.0-stardust.6", "@types/mocha": "^9.1.0", "concurrently": "^7.0.0", "copy-webpack-plugin": "^7.0.0", @@ -38,9 +39,7 @@ }, "peerDependencies": { "@cycraig/iota-client-wasm": "^0.5.0-alpha.1", - "@iota/crypto.js": "^1.9.0-stardust.6", - "@iota/iota.js": "^1.9.0-stardust.25", - "@iota/util.js": "^1.9.0-stardust.5" + "@iota/iota.js": "^1.9.0-stardust.25" } }, "../../../iota.rs/bindings/wasm": { @@ -190,7 +189,6 @@ "version": "1.9.0-stardust.6", "resolved": "https://registry.npmjs.org/@iota/crypto.js/-/crypto.js-1.9.0-stardust.6.tgz", "integrity": "sha512-jbruSRXlEKW2dgfPFG7e42ODBp4dVu2/CAh/oZrzMjehPdyF2tgAeN1woOqOAmVONWoGVV9GViZti3nShhGF1g==", - "peer": true, "dependencies": { "@iota/util.js": "^1.9.0-stardust.5", "big-integer": "^1.6.51" @@ -222,7 +220,6 @@ "version": "1.9.0-stardust.5", "resolved": "https://registry.npmjs.org/@iota/util.js/-/util.js-1.9.0-stardust.5.tgz", "integrity": "sha512-BGXzzjUQQYaV/H0bXJYKW+Zgd9PIN+HLPH8BcYhubI65X4G2ID23/osmpK/za+Ds6t/MkpdPKIiBwZPGXuCUBw==", - "peer": true, "dependencies": { "big-integer": "^1.6.51" }, @@ -935,7 +932,6 @@ "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "peer": true, "engines": { "node": ">=0.6" } @@ -6503,7 +6499,6 @@ "version": "1.9.0-stardust.6", "resolved": "https://registry.npmjs.org/@iota/crypto.js/-/crypto.js-1.9.0-stardust.6.tgz", "integrity": "sha512-jbruSRXlEKW2dgfPFG7e42ODBp4dVu2/CAh/oZrzMjehPdyF2tgAeN1woOqOAmVONWoGVV9GViZti3nShhGF1g==", - "peer": true, "requires": { "@iota/util.js": "^1.9.0-stardust.5", "big-integer": "^1.6.51" @@ -6529,7 +6524,6 @@ "version": "1.9.0-stardust.5", "resolved": "https://registry.npmjs.org/@iota/util.js/-/util.js-1.9.0-stardust.5.tgz", "integrity": "sha512-BGXzzjUQQYaV/H0bXJYKW+Zgd9PIN+HLPH8BcYhubI65X4G2ID23/osmpK/za+Ds6t/MkpdPKIiBwZPGXuCUBw==", - "peer": true, "requires": { "big-integer": "^1.6.51" } @@ -7111,8 +7105,7 @@ "big-integer": { "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "peer": true + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==" }, "big.js": { "version": "5.2.2", diff --git a/bindings/wasm/package.json b/bindings/wasm/package.json index 19abf98bcc..bf1a8039c9 100644 --- a/bindings/wasm/package.json +++ b/bindings/wasm/package.json @@ -54,6 +54,7 @@ "node/*" ], "devDependencies": { + "@iota/crypto.js": "^1.9.0-stardust.6", "@types/mocha": "^9.1.0", "concurrently": "^7.0.0", "copy-webpack-plugin": "^7.0.0", @@ -80,9 +81,7 @@ }, "peerDependencies": { "@cycraig/iota-client-wasm": "^0.5.0-alpha.1", - "@iota/crypto.js": "^1.9.0-stardust.6", - "@iota/iota.js": "^1.9.0-stardust.25", - "@iota/util.js": "^1.9.0-stardust.5" + "@iota/iota.js": "^1.9.0-stardust.25" }, "engines": { "node": ">=16" diff --git a/bindings/wasm/src/stardust/identity_client.rs b/bindings/wasm/src/stardust/identity_client.rs index 9e983161a1..1620c20ed0 100644 --- a/bindings/wasm/src/stardust/identity_client.rs +++ b/bindings/wasm/src/stardust/identity_client.rs @@ -96,7 +96,7 @@ impl StardustIdentityClient for WasmStardustIdentityClient { #[wasm_bindgen(typescript_custom_section)] const I_STARDUST_IDENTITY_CLIENT: &'static str = r#" -import { IAliasOutput, IRent } from '@iota/types'; +import type { IAliasOutput, IRent } from '@iota/types'; /** Helper interface necessary for `StardustIdentityClientExt`. */ interface IStardustIdentityClient { /** diff --git a/bindings/wasm/src/stardust/identity_client_ext.rs b/bindings/wasm/src/stardust/identity_client_ext.rs index 3cc1f7b958..e6549c8175 100644 --- a/bindings/wasm/src/stardust/identity_client_ext.rs +++ b/bindings/wasm/src/stardust/identity_client_ext.rs @@ -23,7 +23,7 @@ use crate::stardust::WasmStardustDocument; // `IAliasOutput`, `AddressTypes`, and `IRent` are external interfaces. // See the custom TypeScript section in `identity_client.rs` for the first import statement. #[wasm_bindgen(typescript_custom_section)] -const TYPESCRIPT_IMPORTS: &'static str = r#"import { AddressTypes } from '@iota/types';"#; +const TYPESCRIPT_IMPORTS: &'static str = r#"import type { AddressTypes } from '@iota/types';"#; #[wasm_bindgen] extern "C" { #[wasm_bindgen(typescript_type = "Promise")] diff --git a/bindings/wasm/src/stardust/stardust_document.rs b/bindings/wasm/src/stardust/stardust_document.rs index 0f81487c4f..a342ac9d4d 100644 --- a/bindings/wasm/src/stardust/stardust_document.rs +++ b/bindings/wasm/src/stardust/stardust_document.rs @@ -396,6 +396,34 @@ impl WasmStardustDocument { .wasm_result() } + /// Returns all DID documents of the Alias Outputs contained in the block's transaction payload + /// outputs, if any. + /// + /// Errors if any Alias Output does not contain a valid or empty DID Document. + #[wasm_bindgen(js_name = unpackFromBlock)] + pub fn unpack_from_block(network: String, block: &IBlock) -> Result { + let network_name: NetworkName = NetworkName::try_from(network).wasm_result()?; + let block_dto: bee_block::BlockDto = block + .into_serde() + .map_err(|err| { + identity_stardust::Error::JsError(format!("unpackFromBlock failed to deserialize BlockDto: {}", err)) + }) + .wasm_result()?; + let block: bee_block::Block = bee_block::Block::try_from(&block_dto) + .map_err(|err| identity_stardust::Error::JsError(format!("unpackFromBlock failed to convert BlockDto: {}", err))) + .wasm_result()?; + + Ok( + StardustDocument::unpack_from_block(&network_name, &block) + .wasm_result()? + .into_iter() + .map(WasmStardustDocument::from) + .map(JsValue::from) + .collect::() + .unchecked_into::(), + ) + } + // =========================================================================== // Metadata // =========================================================================== @@ -515,9 +543,19 @@ extern "C" { #[wasm_bindgen(typescript_type = "StardustDID[]")] pub type ArrayStardustDID; + #[wasm_bindgen(typescript_type = "StardustDocument[]")] + pub type ArrayStardustDocument; + #[wasm_bindgen(typescript_type = "StardustService[]")] pub type ArrayStardustService; #[wasm_bindgen(typescript_type = "StardustVerificationMethod[]")] pub type ArrayStardustVerificationMethods; + + // External interface from `@iota/types`, must be deserialized via BlockDto. + #[wasm_bindgen(typescript_type = "IBlock")] + pub type IBlock; } + +#[wasm_bindgen(typescript_custom_section)] +const TYPESCRIPT_IMPORTS: &'static str = r#"import type { IBlock } from '@iota/types';"#; diff --git a/identity_stardust/src/client/client_ext.rs b/identity_stardust/src/client/client_ext.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/identity_stardust/src/client/iota_client.rs b/identity_stardust/src/client/iota_client.rs index 713a7cccae..fb76512f6b 100644 --- a/identity_stardust/src/client/iota_client.rs +++ b/identity_stardust/src/client/iota_client.rs @@ -1,8 +1,6 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::ops::Deref; - use iota_client::api_types::responses::OutputResponse; use iota_client::secret::SecretManager; use iota_client::Client; @@ -16,8 +14,6 @@ use crate::block::output::Output; use crate::block::output::OutputId; use crate::block::output::RentStructure; use crate::block::output::UnlockCondition; -use crate::block::payload::transaction::TransactionEssence; -use crate::block::payload::Payload; use crate::block::Block; use crate::client::identity_client::validate_network; use crate::error::Result; @@ -71,7 +67,7 @@ impl StardustClientExt for Client { .map_err(|err| Error::DIDUpdateError("publish_did_output: publish failed", Some(err)))?; let network: NetworkName = self.network_name().await?; - extract_documents_from_block(&network, &block)? + StardustDocument::unpack_from_block(&network, &block)? .into_iter() .next() .ok_or(Error::DIDUpdateError( @@ -155,35 +151,3 @@ async fn publish_output( Ok(block) } - -/// Returns all DID documents of the Alias Outputs contained in the payload's transaction, if any. -fn extract_documents_from_block(network: &NetworkName, block: &Block) -> Result> { - let mut documents = Vec::new(); - - if let Some(Payload::Transaction(tx_payload)) = block.payload() { - let TransactionEssence::Regular(regular) = tx_payload.essence(); - - for (index, output) in regular.outputs().iter().enumerate() { - if let Output::Alias(alias_output) = output { - let alias_id = if alias_output.alias_id().is_null() { - AliasId::from( - OutputId::new( - tx_payload.id(), - index - .try_into() - .map_err(|_| Error::OutputIdConversionError(format!("output index {index} must fit into a u16")))?, - ) - .map_err(|err| Error::OutputIdConversionError(err.to_string()))?, - ) - } else { - alias_output.alias_id().to_owned() - }; - - let did: StardustDID = StardustDID::new(alias_id.deref(), network); - documents.push(StardustDocument::unpack(&did, alias_output.state_metadata(), true)?); - } - } - } - - Ok(documents) -} diff --git a/identity_stardust/src/document/stardust_document.rs b/identity_stardust/src/document/stardust_document.rs index 2f8674426b..ed5f8b1789 100644 --- a/identity_stardust/src/document/stardust_document.rs +++ b/identity_stardust/src/document/stardust_document.rs @@ -303,6 +303,60 @@ impl StardustDocument { } } +#[cfg(feature = "client")] +mod client_document { + use std::ops::Deref; + + use crate::block::output::AliasId; + use crate::block::output::Output; + use crate::block::output::OutputId; + use crate::block::payload::transaction::TransactionEssence; + use crate::block::payload::Payload; + use crate::block::Block; + use crate::error::Result; + use crate::Error; + use crate::NetworkName; + + use super::*; + + impl StardustDocument { + /// Returns all DID documents of the Alias Outputs contained in the block's transaction payload + /// outputs, if any. + /// + /// Errors if any Alias Output does not contain a valid or empty DID Document. + pub fn unpack_from_block(network: &NetworkName, block: &Block) -> Result> { + let mut documents = Vec::new(); + + if let Some(Payload::Transaction(tx_payload)) = block.payload() { + let TransactionEssence::Regular(regular) = tx_payload.essence(); + + for (index, output) in regular.outputs().iter().enumerate() { + if let Output::Alias(alias_output) = output { + let alias_id = if alias_output.alias_id().is_null() { + AliasId::from( + OutputId::new( + tx_payload.id(), + index + .try_into() + .map_err(|_| Error::OutputIdConversionError(format!("output index {index} must fit into a u16")))?, + ) + .map_err(|err| Error::OutputIdConversionError(err.to_string()))?, + ) + } else { + alias_output.alias_id().to_owned() + }; + + let did: StardustDID = StardustDID::new(alias_id.deref(), network); + documents.push(StardustDocument::unpack(&did, alias_output.state_metadata(), true)?); + } + } + } + + Ok(documents) + } + } +} + impl Document for StardustDocument { type D = StardustDID; type U = Object;