did_resolver_cheqd/lib.rs
1//! This crate contains a resolver for DIDs of the [did:cheqd](https://docs.cheqd.io/product/architecture/adr-list/adr-001-cheqd-did-method) method.
2//! The implementation resolves DIDs via gRPC network requests to the configured nodes. Default nodes for cheqd's `mainnet` & `testnet` can be used,
3//! or custom nodes can be opt-in by supplying a different gRPC URL configuration.
4//!
5//! This crate uses gRPC types and clients generated using [tonic](https://github.com/hyperium/tonic).
6//! The generated rust code is checked-in to this repository for monitoring, [see here](./src/proto/mod.rs).
7//! These generated rust files are checked-in alongside the V2 cheqd proto files & dependencies.
8//! which are sourced from [cheqd's Buf registry](https://buf.build/cheqd/proto/docs).
9//!
10//! Since the generated code & proto files are not relatively large nor overwhelming in content, they are checked-in rather than pulled and/or generated at build time. The benefit is that the contents of the files can be monitored with each update, making supply-chain attacks obvious. It also reduces the build time complexity for consumers - such as reducing requirements for any 3rd party build tools to be installed (`protobuf`). The drawback is that it introduces some more manual maintainence.
11//! The crate exports the `DIDCheqd` type which implements the
12//! [`ssi_dids_core::DIDMethod`] and
13//! [`ssi_dids_core::resolution::DIDMethodResolver`] traits. This crate is
14//! uses cheqd network's GRPC
15//!
16//! # Example
17//!
18//! The example below is intentionally minimal and self-contained so it can be
19//! executed as a doc-test (no network calls, no async runtime). It verifies the
20//! public associated constant and basic construction of the type. This keeps
21//! `cargo test --doc` and tools like `cargo-rdme` reliable.
22//!
23//! ```
24//! use did_resolver_cheqd::DIDCheqd;
25//! use ssi_dids_core::DIDMethod;
26//! // Confirm the API constant and that we can construct the value
27//! assert_eq!(DIDCheqd::DID_METHOD_NAME, "cheqd");
28//! let _ = DIDCheqd::default();
29//! let _ = DIDCheqd::new(None);
30//! let _ = DIDCheqd::new(Some(DidCheqdResolverConfiguration {
31//! networks: vec![
32//! NetworkConfiguration {
33//! grpc_url: "https://grpc.cheqd.net:443".to_string(),
34//! namespace: "mainnet".to_string(),
35//! },
36//! ],
37//! }));
38//! ```
39//!
40//! # Library features
41//!
42//! - Implements a `DIDMethodResolver` for the `did:cheqd` DID method.
43//! - Exposes `resolution`, `proto` and `error` modules for integration.
44
45use crate::resolution::parser::DidCheqdParser;
46use crate::resolution::resolver::{DidCheqdResolver, DidCheqdResolverConfiguration};
47use crate::resolution::transformer::cheqd_diddoc_to_json;
48use serde_json::to_vec;
49use ssi_dids_core::{
50 DIDMethod, DIDResolver,
51 document::{self, representation::MediaType},
52 resolution::{Error, Metadata as ResolutionMetadata, Options, Output},
53};
54
55pub mod error;
56pub mod proto;
57pub mod resolution;
58
59pub struct DIDCheqd {
60 /// Resolver configuration used when resolving DIDs/resources.
61 pub config: DidCheqdResolverConfiguration,
62}
63
64impl DIDCheqd {
65 /// Create a resolver using an optional custom configuration.
66 /// If `None` is provided, it defaults to `DidCheqdResolverConfiguration::default()`.
67 pub fn new(config: Option<DidCheqdResolverConfiguration>) -> Self {
68 Self {
69 config: config.unwrap_or_default(),
70 }
71 }
72}
73
74impl Default for DIDCheqd {
75 fn default() -> Self {
76 Self::new(None)
77 }
78}
79
80impl DIDMethod for DIDCheqd {
81 const DID_METHOD_NAME: &'static str = "cheqd";
82}
83
84impl DIDResolver for DIDCheqd {
85 async fn resolve_representation<'a>(
86 &'a self,
87 did: &'a ssi_dids_core::DID,
88 options: Options,
89 ) -> Result<Output<Vec<u8>>, Error> {
90 // Try parse as a DID URL (resource) first, otherwise as a DID
91 // We will use the internal cheqd resolver to fetch a DidDocument or a resource and
92 // then convert it into bytes (JSON-LD) to match the did:key style Output.
93 // Use the resolver configuration provided to this DIDCheqd instance.
94 let cfg = self.config.clone();
95 let resolver = DidCheqdResolver::new(cfg);
96
97 // Check if it's a DidUrl (resource)
98 let parsed = DidCheqdParser::parse(did.as_str())
99 .map_err(|e| Error::InvalidMethodSpecificId(e.to_string()))?;
100
101 if parsed.query.is_some() {
102 // treat as a full did URL
103 match resolver.query_resource_by_str(did.as_str(), parsed).await {
104 Ok((content_bytes, media_type)) => {
105 return Ok(Output::new(
106 content_bytes,
107 document::Metadata::default(),
108 ResolutionMetadata::from_content_type(media_type),
109 ));
110 }
111 Err(e) => return Err(Error::internal(format!("cheqd resolver error: {e:?}"))),
112 }
113 }
114
115 match resolver.query_did_doc_by_str(did.as_str(), parsed).await {
116 Ok((proto_doc, metadata)) => {
117 // convert proto DIDDoc to a JSON representation and serialize
118 let json_value = cheqd_diddoc_to_json(proto_doc)
119 .map_err(|e| Error::internal(format!("cheqd transform error: {e:?}")))?;
120 let json = to_vec(&json_value).map_err(|e| {
121 Error::internal(format!("failed to serialize DID document: {e}"))
122 })?;
123
124 let content_type = options.accept.unwrap_or(MediaType::JsonLd);
125
126 Ok(Output::new(
127 json,
128 match metadata {
129 Some(meta) => document::Metadata {
130 deactivated: Some(meta.deactivated),
131 },
132 None => document::Metadata { deactivated: None },
133 },
134 ResolutionMetadata::from_content_type(Some(content_type.to_string())),
135 ))
136 }
137 Err(e) => Err(Error::internal(format!("cheqd resolver error: {e:?}"))),
138 }
139 }
140}