diff --git a/Cargo.lock b/Cargo.lock
index 13e2925ab2..6b1cb397e7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -991,6 +991,8 @@ dependencies = [
"parachain-info",
"parity-scale-codec",
"polkadot-parachain",
+ "public-credentials",
+ "public-credentials-runtime-api",
"runtime-common",
"scale-info",
"serde",
@@ -1929,6 +1931,7 @@ dependencies = [
"log",
"pallet-balances",
"parity-scale-codec",
+ "public-credentials",
"scale-info",
"serde",
"sp-core",
@@ -1998,7 +2001,7 @@ dependencies = [
[[package]]
name = "did-rpc"
-version = "1.6.2"
+version = "1.7.2"
dependencies = [
"did-rpc-runtime-api",
"jsonrpsee",
@@ -2011,7 +2014,7 @@ dependencies = [
[[package]]
name = "did-rpc-runtime-api"
-version = "1.6.2"
+version = "1.7.2"
dependencies = [
"did",
"kilt-support",
@@ -3593,6 +3596,21 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838"
+[[package]]
+name = "kilt-asset-dids"
+version = "1.7.2"
+dependencies = [
+ "base58",
+ "frame-support",
+ "hex",
+ "hex-literal",
+ "log",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-core",
+ "sp-std",
+]
+
[[package]]
name = "kilt-parachain"
version = "1.7.2"
@@ -3618,6 +3636,7 @@ dependencies = [
"hex-literal",
"jsonrpsee",
"log",
+ "node-common",
"pallet-did-lookup",
"pallet-transaction-payment-rpc",
"parity-scale-codec",
@@ -3627,6 +3646,8 @@ dependencies = [
"polkadot-parachain",
"polkadot-primitives",
"polkadot-service",
+ "public-credentials",
+ "public-credentials-rpc",
"runtime-common",
"sc-basic-authorship",
"sc-chain-spec",
@@ -4598,9 +4619,12 @@ dependencies = [
"jsonrpsee",
"log",
"mashnet-node-runtime",
+ "node-common",
"pallet-did-lookup",
"pallet-transaction-payment",
"pallet-transaction-payment-rpc",
+ "public-credentials",
+ "public-credentials-rpc",
"runtime-common",
"sc-basic-authorship",
"sc-cli",
@@ -4673,6 +4697,8 @@ dependencies = [
"pallet-utility",
"pallet-web3-names",
"parity-scale-codec",
+ "public-credentials",
+ "public-credentials-runtime-api",
"runtime-common",
"scale-info",
"serde",
@@ -5047,6 +5073,16 @@ dependencies = [
"libc",
]
+[[package]]
+name = "node-common"
+version = "1.7.2"
+dependencies = [
+ "kilt-support",
+ "public-credentials",
+ "public-credentials-rpc",
+ "serde",
+]
+
[[package]]
name = "nodrop"
version = "0.1.14"
@@ -6511,6 +6547,8 @@ dependencies = [
"parachain-staking",
"parity-scale-codec",
"polkadot-parachain",
+ "public-credentials",
+ "public-credentials-runtime-api",
"runtime-common",
"scale-info",
"serde",
@@ -7990,6 +8028,49 @@ dependencies = [
"cc",
]
+[[package]]
+name = "public-credentials"
+version = "1.7.2"
+dependencies = [
+ "base58",
+ "ctype",
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "hex",
+ "kilt-support",
+ "log",
+ "pallet-balances",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-core",
+ "sp-io",
+ "sp-keystore",
+ "sp-runtime",
+ "sp-std",
+]
+
+[[package]]
+name = "public-credentials-rpc"
+version = "1.7.2"
+dependencies = [
+ "jsonrpsee",
+ "parity-scale-codec",
+ "public-credentials-runtime-api",
+ "sp-api",
+ "sp-blockchain",
+ "sp-runtime",
+]
+
+[[package]]
+name = "public-credentials-runtime-api"
+version = "1.7.2"
+dependencies = [
+ "parity-scale-codec",
+ "sp-api",
+ "sp-std",
+]
+
[[package]]
name = "quick-error"
version = "1.2.3"
@@ -8440,6 +8521,8 @@ dependencies = [
"attestation",
"frame-support",
"frame-system",
+ "kilt-asset-dids",
+ "kilt-support",
"log",
"pallet-authorship",
"pallet-balances",
@@ -8448,6 +8531,7 @@ dependencies = [
"parachain-staking",
"parity-scale-codec",
"polkadot-parachain",
+ "public-credentials",
"scale-info",
"serde",
"smallvec",
@@ -10822,6 +10906,8 @@ dependencies = [
"parachain-staking",
"parity-scale-codec",
"polkadot-parachain",
+ "public-credentials",
+ "public-credentials-runtime-api",
"runtime-common",
"scale-info",
"serde",
diff --git a/Cargo.toml b/Cargo.toml
index 040d093c24..a91307b735 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,6 +7,9 @@ members = [
"pallets/*",
"rpc/did",
"rpc/did/runtime-api",
+ "rpc/public-credentials",
+ "rpc/public-credentials/runtime-api",
"runtimes/*",
"support",
+ "crates/*",
]
diff --git a/README.md b/README.md
index 6b15195d69..d52b685d45 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# KILT-node · [](https://gitlab.com/kiltprotocol/mashnet-node/-/commits/develop)
+# KILT-node · [](https://gitlab.com/kiltprotocol/kilt-node/-/commits/develop)
diff --git a/crates/assets/Cargo.toml b/crates/assets/Cargo.toml
new file mode 100644
index 0000000000..8a94802e8f
--- /dev/null
+++ b/crates/assets/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+authors = ["KILT "]
+description = "Asset DIDs and related structs, suitable for no_std environments."
+edition = "2021"
+name = "kilt-asset-dids"
+repository = "https://github.com/KILTprotocol/kilt-node"
+version = "1.7.2"
+
+[dependencies]
+# External dependencies
+base58 = {version = "0.2.0", default-features = false}
+hex = {version = "0.4.3", default-features = false}
+hex-literal = {version = "0.3.4", default-features = false}
+log = {version = "0.4.17", default-features = false}
+
+# Parity dependencies
+codec = {package = "parity-scale-codec", version = "3.1.2", default-features = false, features = ["derive"]}
+scale-info = {version = "2.1.1", default-features = false, features = ["derive"]}
+
+# Substrate dependencies
+frame-support = {branch = "polkadot-v0.9.28", default-features = false, git = "https://github.com/paritytech/substrate"}
+sp-core = {branch = "polkadot-v0.9.28", default-features = false, git = "https://github.com/paritytech/substrate"}
+sp-std = {branch = "polkadot-v0.9.28", default-features = false, git = "https://github.com/paritytech/substrate"}
+
+[features]
+default = ["std"]
+
+std = [
+ "codec/std",
+ "hex/std",
+ "log/std",
+ "scale-info/std",
+ "frame-support/std",
+ "sp-core/std",
+ "sp-std/std",
+]
diff --git a/crates/assets/src/asset.rs b/crates/assets/src/asset.rs
new file mode 100644
index 0000000000..413adb10b9
--- /dev/null
+++ b/crates/assets/src/asset.rs
@@ -0,0 +1,947 @@
+// KILT Blockchain – https://botlabs.org
+// Copyright (C) 2019-2022 BOTLabs GmbH
+
+// The KILT Blockchain is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// The KILT Blockchain is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+// If you feel like getting in touch with us, you can do so at info@botlabs.org
+
+// Exported types. This will always only re-export the latest version by
+// default.
+pub use v1::*;
+
+pub mod v1 {
+ use crate::errors::asset::{Error, IdentifierError, NamespaceError, ReferenceError};
+
+ use codec::{Decode, Encode, MaxEncodedLen};
+ use scale_info::TypeInfo;
+
+ use core::{format_args, str};
+
+ use frame_support::{sp_runtime::RuntimeDebug, traits::ConstU32, BoundedVec};
+ use sp_core::U256;
+ use sp_std::{fmt::Display, vec::Vec};
+
+ /// The minimum length, including separator symbols, an asset ID can have
+ /// according to the minimum values defined by the CAIP-19 definition.
+ pub const MINIMUM_ASSET_ID_LENGTH: usize = MINIMUM_NAMESPACE_LENGTH + 1 + MINIMUM_REFERENCE_LENGTH;
+ /// The maximum length, including separator symbols, an asset ID can have
+ /// according to the minimum values defined by the CAIP-19 definition.
+ pub const MAXIMUM_ASSET_ID_LENGTH: usize =
+ MAXIMUM_NAMESPACE_LENGTH + 1 + MAXIMUM_REFERENCE_LENGTH + 1 + MAXIMUM_IDENTIFIER_LENGTH;
+
+ /// The minimum length of a valid asset ID namespace.
+ pub const MINIMUM_NAMESPACE_LENGTH: usize = 3;
+ /// The maximum length of a valid asset ID namespace.
+ pub const MAXIMUM_NAMESPACE_LENGTH: usize = 8;
+ const MAXIMUM_NAMESPACE_LENGTH_U32: u32 = MAXIMUM_NAMESPACE_LENGTH as u32;
+ /// The minimum length of a valid asset ID reference.
+ pub const MINIMUM_REFERENCE_LENGTH: usize = 1;
+ /// The maximum length of a valid asset ID reference.
+ pub const MAXIMUM_REFERENCE_LENGTH: usize = 64;
+ const MAXIMUM_REFERENCE_LENGTH_U32: u32 = MAXIMUM_REFERENCE_LENGTH as u32;
+ /// The minimum length of a valid asset ID identifier.
+ pub const MINIMUM_IDENTIFIER_LENGTH: usize = 1;
+ /// The maximum length of a valid asset ID reference.
+ pub const MAXIMUM_IDENTIFIER_LENGTH: usize = 78;
+ const MAXIMUM_IDENTIFIER_LENGTH_U32: u32 = MAXIMUM_IDENTIFIER_LENGTH as u32;
+
+ /// Separator between asset namespace and asset reference.
+ const NAMESPACE_REFERENCE_SEPARATOR: u8 = b':';
+ /// Separator between asset reference and asset identifier.
+ const REFERENCE_IDENTIFIER_SEPARATOR: u8 = b':';
+
+ /// Namespace for Slip44 assets.
+ pub const SLIP44_NAMESPACE: &[u8] = b"slip44";
+ /// Namespace for Erc20 assets.
+ pub const ERC20_NAMESPACE: &[u8] = b"erc20";
+ /// Namespace for Erc721 assets.
+ pub const ERC721_NAMESPACE: &[u8] = b"erc721";
+ /// Namespace for Erc1155 assets.
+ pub const ERC1155_NAMESPACE: &[u8] = b"erc1155";
+
+ // TODO: Add link to the Asset DID spec once merged.
+
+ /// The Asset ID component as specified in the Asset DID specification.
+ #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+ pub enum AssetId {
+ // A SLIP44 asset reference.
+ Slip44(Slip44Reference),
+ // An ERC20 asset reference.
+ Erc20(EvmSmartContractFungibleReference),
+ // An ERC721 asset reference.
+ Erc721(EvmSmartContractNonFungibleReference),
+ // An ERC1155 asset reference.
+ Erc1155(EvmSmartContractNonFungibleReference),
+ // A generic asset.
+ Generic(GenericAssetId),
+ }
+
+ impl From for AssetId {
+ fn from(reference: Slip44Reference) -> Self {
+ Self::Slip44(reference)
+ }
+ }
+
+ impl From for AssetId {
+ fn from(reference: EvmSmartContractFungibleReference) -> Self {
+ Self::Erc20(reference)
+ }
+ }
+
+ impl AssetId {
+ /// Try to parse an `AssetId` instance from the provided UTF8-encoded
+ /// input.
+ pub fn from_utf8_encoded(input: I) -> Result
+ where
+ I: AsRef<[u8]> + Into>,
+ {
+ let input = input.as_ref();
+ let input_length = input.len();
+ if !(MINIMUM_ASSET_ID_LENGTH..=MAXIMUM_ASSET_ID_LENGTH).contains(&input_length) {
+ log::trace!(
+ "Length of provided input {} is not included in the inclusive range [{},{}]",
+ input_length,
+ MINIMUM_ASSET_ID_LENGTH,
+ MAXIMUM_ASSET_ID_LENGTH
+ );
+ return Err(Error::InvalidFormat);
+ }
+
+ let AssetComponents {
+ namespace,
+ reference,
+ identifier,
+ } = split_components(input);
+
+ match (namespace, reference, identifier) {
+ // "slip44:" assets -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-20.md
+ (Some(SLIP44_NAMESPACE), _, Some(_)) => {
+ log::trace!("Slip44 namespace does not accept an asset identifier.");
+ Err(Error::InvalidFormat)
+ }
+ (Some(SLIP44_NAMESPACE), Some(slip44_reference), None) => {
+ Slip44Reference::from_utf8_encoded(slip44_reference).map(Self::Slip44)
+ }
+ // "erc20:" assets -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-21.md
+ (Some(ERC20_NAMESPACE), _, Some(_)) => {
+ log::trace!("Erc20 namespace does not accept an asset identifier.");
+ Err(Error::InvalidFormat)
+ }
+ (Some(ERC20_NAMESPACE), Some(erc20_reference), None) => {
+ EvmSmartContractFungibleReference::from_utf8_encoded(erc20_reference).map(Self::Erc20)
+ }
+ // "erc721:" assets -> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-22.md
+ (Some(ERC721_NAMESPACE), Some(erc721_reference), identifier) => {
+ let reference = EvmSmartContractFungibleReference::from_utf8_encoded(erc721_reference)?;
+ let identifier = identifier.map_or(Ok(None), |id| {
+ EvmSmartContractNonFungibleIdentifier::from_utf8_encoded(id).map(Some)
+ })?;
+ Ok(Self::Erc721(EvmSmartContractNonFungibleReference(
+ reference, identifier,
+ )))
+ }
+ // "erc1155:" assets-> https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-29.md
+ (Some(ERC1155_NAMESPACE), Some(erc1155_reference), identifier) => {
+ let reference = EvmSmartContractFungibleReference::from_utf8_encoded(erc1155_reference)?;
+ let identifier = identifier.map_or(Ok(None), |id| {
+ EvmSmartContractNonFungibleIdentifier::from_utf8_encoded(id).map(Some)
+ })?;
+ Ok(Self::Erc1155(EvmSmartContractNonFungibleReference(
+ reference, identifier,
+ )))
+ }
+ // Generic yet valid asset IDs
+ _ => GenericAssetId::from_utf8_encoded(input).map(Self::Generic),
+ }
+ }
+ }
+
+ impl Display for AssetId {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Self::Slip44(reference) => {
+ write!(
+ f,
+ "{}",
+ str::from_utf8(SLIP44_NAMESPACE)
+ .expect("Conversion of Slip44 namespace to string should never fail.")
+ )?;
+ write!(f, "{}", char::from(NAMESPACE_REFERENCE_SEPARATOR))?;
+ reference.fmt(f)?;
+ }
+ Self::Erc20(reference) => {
+ write!(
+ f,
+ "{}",
+ str::from_utf8(ERC20_NAMESPACE)
+ .expect("Conversion of Erc20 namespace to string should never fail.")
+ )?;
+ write!(f, "{}", char::from(NAMESPACE_REFERENCE_SEPARATOR))?;
+ reference.fmt(f)?;
+ }
+ Self::Erc721(EvmSmartContractNonFungibleReference(reference, identifier)) => {
+ write!(
+ f,
+ "{}",
+ str::from_utf8(ERC721_NAMESPACE)
+ .expect("Conversion of Erc721 namespace to string should never fail.")
+ )?;
+ write!(f, "{}", char::from(NAMESPACE_REFERENCE_SEPARATOR))?;
+ reference.fmt(f)?;
+ if let Some(id) = identifier {
+ write!(f, "{}", char::from(REFERENCE_IDENTIFIER_SEPARATOR))?;
+ id.fmt(f)?;
+ }
+ }
+ Self::Erc1155(EvmSmartContractNonFungibleReference(reference, identifier)) => {
+ write!(
+ f,
+ "{}",
+ str::from_utf8(ERC1155_NAMESPACE)
+ .expect("Conversion of Erc1155 namespace to string should never fail.")
+ )?;
+ write!(f, "{}", char::from(NAMESPACE_REFERENCE_SEPARATOR))?;
+ reference.fmt(f)?;
+ if let Some(id) = identifier {
+ write!(f, "{}", char::from(REFERENCE_IDENTIFIER_SEPARATOR))?;
+ id.fmt(f)?;
+ }
+ }
+ Self::Generic(GenericAssetId {
+ namespace,
+ reference,
+ id,
+ }) => {
+ namespace.fmt(f)?;
+ write!(f, "{}", char::from(NAMESPACE_REFERENCE_SEPARATOR))?;
+ reference.fmt(f)?;
+ if let Some(identifier) = id {
+ write!(f, "{}", char::from(REFERENCE_IDENTIFIER_SEPARATOR))?;
+ identifier.fmt(f)?;
+ }
+ }
+ }
+ Ok(())
+ }
+ }
+
+ const fn check_namespace_length_bounds(namespace: &[u8]) -> Result<(), NamespaceError> {
+ let namespace_length = namespace.len();
+ if namespace_length < MINIMUM_NAMESPACE_LENGTH {
+ Err(NamespaceError::TooShort)
+ } else if namespace_length > MAXIMUM_NAMESPACE_LENGTH {
+ Err(NamespaceError::TooLong)
+ } else {
+ Ok(())
+ }
+ }
+
+ const fn check_reference_length_bounds(reference: &[u8]) -> Result<(), ReferenceError> {
+ let reference_length = reference.len();
+ if reference_length < MINIMUM_REFERENCE_LENGTH {
+ Err(ReferenceError::TooShort)
+ } else if reference_length > MAXIMUM_REFERENCE_LENGTH {
+ Err(ReferenceError::TooLong)
+ } else {
+ Ok(())
+ }
+ }
+
+ const fn check_identifier_length_bounds(identifier: &[u8]) -> Result<(), IdentifierError> {
+ let identifier_length = identifier.len();
+ if identifier_length < MINIMUM_IDENTIFIER_LENGTH {
+ Err(IdentifierError::TooShort)
+ } else if identifier_length > MAXIMUM_IDENTIFIER_LENGTH {
+ Err(IdentifierError::TooLong)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Split the given input into its components, i.e., namespace, reference,
+ /// and identifier, if the proper separators are found.
+ fn split_components(input: &[u8]) -> AssetComponents {
+ let mut split = input.splitn(2, |c| *c == NAMESPACE_REFERENCE_SEPARATOR);
+ let (namespace, reference) = (split.next(), split.next());
+
+ // Split the remaining reference to extract the identifier, if present
+ let (reference, identifier) = if let Some(r) = reference {
+ let mut split = r.splitn(2, |c| *c == REFERENCE_IDENTIFIER_SEPARATOR);
+ // Split the reference further, if present
+ (split.next(), split.next())
+ } else {
+ // Return the old reference, which is None if we are at this point
+ (reference, None)
+ };
+
+ AssetComponents {
+ namespace,
+ reference,
+ identifier,
+ }
+ }
+
+ struct AssetComponents<'a> {
+ namespace: Option<&'a [u8]>,
+ reference: Option<&'a [u8]>,
+ identifier: Option<&'a [u8]>,
+ }
+
+ /// A Slip44 asset reference.
+ /// It is a modification of the [CAIP-20 spec](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-20.md)
+ /// according to the rules defined in the Asset DID method specification.
+ #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+ pub struct Slip44Reference(pub(crate) U256);
+
+ impl Slip44Reference {
+ /// Parse a UTF8-encoded decimal Slip44 asset reference, failing if the
+ /// input string is not valid.
+ pub(crate) fn from_utf8_encoded(input: I) -> Result
+ where
+ I: AsRef<[u8]> + Into>,
+ {
+ let input = input.as_ref();
+ check_reference_length_bounds(input)?;
+
+ let decoded = str::from_utf8(input).map_err(|_| {
+ log::trace!("Provided input is not a valid UTF8 string as expected by a Slip44 reference.");
+ ReferenceError::InvalidFormat
+ })?;
+ let parsed = U256::from_dec_str(decoded).map_err(|_| {
+ log::trace!("Provided input is not a valid u256 value as expected by a Slip44 reference.");
+ ReferenceError::InvalidFormat
+ })?;
+ // Unchecked since we already checked for maximum length and hence maximum value
+ Ok(Self(parsed))
+ }
+ }
+
+ impl TryFrom for Slip44Reference {
+ type Error = Error;
+
+ fn try_from(value: U256) -> Result {
+ // Max value for 64-digit decimal values (used for Slip44 references so far).
+ // TODO: This could be enforced at compilation time once constraints on generics
+ // will be available.
+ // https://rust-lang.github.io/rfcs/2000-const-generics.html
+ if value
+ <= U256::from_str_radix("9999999999999999999999999999999999999999999999999999999999999999", 10)
+ .expect("Casting the maximum value for a Slip44 reference into a U256 should never fail.")
+ {
+ Ok(Self(value))
+ } else {
+ Err(ReferenceError::TooLong.into())
+ }
+ }
+ }
+
+ impl From for Slip44Reference {
+ fn from(value: u128) -> Self {
+ Self(value.into())
+ }
+ }
+
+ // Getters
+ impl Slip44Reference {
+ pub fn inner(&self) -> &U256 {
+ &self.0
+ }
+ }
+
+ impl Display for Slip44Reference {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+ }
+
+ /// An asset reference that is identifiable only by an EVM smart contract
+ /// (e.g., a fungible token). It is a modification of the [CAIP-21 spec](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-21.md)
+ /// according to the rules defined in the Asset DID method specification.
+ #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+ pub struct EvmSmartContractFungibleReference(pub(crate) [u8; 20]);
+
+ impl EvmSmartContractFungibleReference {
+ /// Parse a UTF8-encoded smart contract HEX address (including the `0x`
+ /// prefix), failing if the input string is not valid.
+ pub(crate) fn from_utf8_encoded(input: I) -> Result
+ where
+ I: AsRef<[u8]> + Into>,
+ {
+ let input = input.as_ref();
+ // If the prefix is "0x" => parse the address
+ if let [b'0', b'x', contract_address @ ..] = input {
+ check_reference_length_bounds(contract_address)?;
+
+ let decoded = hex::decode(contract_address).map_err(|_| {
+ log::trace!("Provided input is not a valid hex value as expected by a smart contract reference.");
+ ReferenceError::InvalidFormat
+ })?;
+ let inner: [u8; 20] = decoded.try_into().map_err(|_| {
+ log::trace!("Provided input is not 20 bytes long as expected by a smart contract reference.");
+ ReferenceError::InvalidFormat
+ })?;
+ Ok(Self(inner))
+ // Otherwise fail
+ } else {
+ log::trace!("Provided input does not have the `0x` prefix as expected by a smart contract reference.");
+ Err(ReferenceError::InvalidFormat.into())
+ }
+ }
+ }
+
+ // Getters
+ impl EvmSmartContractFungibleReference {
+ pub fn inner(&self) -> &[u8] {
+ &self.0
+ }
+ }
+
+ impl Display for EvmSmartContractFungibleReference {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "0x{}", hex::encode(self.0))
+ }
+ }
+
+ /// An asset reference that is identifiable by an EVM smart contract and an
+ /// optional identifier (e.g., an NFT collection or instance thereof). It is
+ /// a modification of the [CAIP-22 spec](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-22.md) and
+ /// [CAIP-29 spec](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-29.md)
+ /// according to the rules defined in the Asset DID method specification.
+ #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+ pub struct EvmSmartContractNonFungibleReference(
+ pub(crate) EvmSmartContractFungibleReference,
+ pub(crate) Option,
+ );
+
+ // Getters
+ impl EvmSmartContractNonFungibleReference {
+ pub fn smart_contract(&self) -> &EvmSmartContractFungibleReference {
+ &self.0
+ }
+
+ pub fn identifier(&self) -> &Option {
+ &self.1
+ }
+ }
+
+ /// An asset identifier for an EVM smart contract collection (e.g., an NFT
+ /// instance).
+ /// Since the identifier can be up to 78 characters long of an unknown
+ /// alphabet, this type simply contains the UTF-8 encoding of such an
+ /// identifier without applying any special parsing/decoding logic.
+ #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+ pub struct EvmSmartContractNonFungibleIdentifier(
+ pub(crate) BoundedVec>,
+ );
+
+ impl EvmSmartContractNonFungibleIdentifier {
+ /// Parse a UTF8-encoded smart contract asset identifier, failing if the
+ /// input string is not valid.
+ pub(crate) fn from_utf8_encoded(input: I) -> Result
+ where
+ I: AsRef<[u8]> + Into>,
+ {
+ let input = input.as_ref();
+ check_identifier_length_bounds(input)?;
+
+ input.iter().try_for_each(|c| {
+ if !(b'0'..=b'9').contains(c) {
+ log::trace!("Provided input has some invalid values as expected by a smart contract-based asset identifier.");
+ Err(IdentifierError::InvalidFormat)
+ } else {
+ Ok(())
+ }
+ })?;
+
+ Ok(Self(
+ Vec::::from(input)
+ .try_into()
+ .map_err(|_| IdentifierError::InvalidFormat)?,
+ ))
+ }
+ }
+
+ // Getters
+ impl EvmSmartContractNonFungibleIdentifier {
+ pub fn inner(&self) -> &[u8] {
+ &self.0
+ }
+ }
+
+ impl Display for EvmSmartContractNonFungibleIdentifier {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ // We checked when the type is created that all characters are valid digits.
+ write!(
+ f,
+ "{}",
+ str::from_utf8(&self.0)
+ .expect("Conversion of EvmSmartContractNonFungibleIdentifier to string should never fail.")
+ )
+ }
+ }
+
+ /// A generic asset ID compliant with the [CAIP-19 spec](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-19.md) that cannot be boxed in any of the supported variants.
+ #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
+ pub struct GenericAssetId {
+ pub(crate) namespace: GenericAssetNamespace,
+ pub(crate) reference: GenericAssetReference,
+ pub(crate) id: Option,
+ }
+
+ impl GenericAssetId {
+ /// Parse a generic UTF8-encoded asset ID, failing if the input does not
+ /// respect the CAIP-19 requirements.
+ pub(crate) fn from_utf8_encoded(input: I) -> Result
+ where
+ I: AsRef<[u8]> + Into>,
+ {
+ let AssetComponents {
+ namespace,
+ reference,
+ identifier,
+ } = split_components(input.as_ref());
+
+ match (namespace, reference, identifier) {
+ (Some(namespace), Some(reference), identifier) => Ok(Self {
+ namespace: GenericAssetNamespace::from_utf8_encoded(namespace)?,
+ reference: GenericAssetReference::from_utf8_encoded(reference)?,
+ // Transform Option to Result