identity_iota_core/rebased/client/
full_client.rs1use std::ops::Deref;
5
6use crate::iota_interaction_adapter::IotaClientAdapter;
7use crate::rebased::iota::move_calls;
8use crate::rebased::iota::package::identity_package_id;
9use crate::rebased::migration::CreateIdentity;
10use crate::IotaDID;
11use crate::IotaDocument;
12use crate::StateMetadataDocument;
13use crate::StateMetadataEncoding;
14use async_trait::async_trait;
15use identity_verification::jwk::Jwk;
16use iota_interaction::move_types::language_storage::StructTag;
17use iota_interaction::rpc_types::IotaObjectData;
18use iota_interaction::rpc_types::IotaObjectDataFilter;
19use iota_interaction::rpc_types::IotaObjectResponseQuery;
20use iota_interaction::rpc_types::IotaTransactionBlockEffects;
21use iota_interaction::types::base_types::IotaAddress;
22use iota_interaction::types::base_types::ObjectRef;
23use iota_interaction::types::crypto::PublicKey;
24use iota_interaction::types::transaction::ProgrammableTransaction;
25use product_common::core_client::CoreClient;
26use product_common::core_client::CoreClientReadOnly;
27use product_common::network_name::NetworkName;
28use product_common::transaction::transaction_builder::Transaction;
29use product_common::transaction::transaction_builder::TransactionBuilder;
30use secret_storage::Signer;
31use serde::de::DeserializeOwned;
32use tokio::sync::OnceCell;
33
34use super::get_object_id_from_did;
35use crate::rebased::assets::AuthenticatedAssetBuilder;
36use crate::rebased::migration::Identity;
37use crate::rebased::migration::IdentityBuilder;
38use crate::rebased::Error;
39use iota_interaction::types::base_types::ObjectID;
40use iota_interaction::IotaClientTrait;
41use iota_interaction::IotaKeySignature;
42use iota_interaction::MoveType;
43use iota_interaction::OptionalSync;
44
45use super::IdentityClientReadOnly;
46
47#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
49pub struct KeyId(String);
50
51impl KeyId {
52 pub fn new(id: impl Into<String>) -> Self {
54 Self(id.into())
55 }
56
57 pub fn as_str(&self) -> &str {
59 &self.0
60 }
61}
62
63impl std::fmt::Display for KeyId {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 f.write_str(&self.0)
66 }
67}
68
69impl From<KeyId> for String {
70 fn from(value: KeyId) -> Self {
71 value.0
72 }
73}
74
75#[derive(Clone)]
77pub struct IdentityClient<S> {
78 read_client: IdentityClientReadOnly,
80 public_key: PublicKey,
82 signer: S,
84}
85
86impl<S> Deref for IdentityClient<S> {
87 type Target = IdentityClientReadOnly;
88 fn deref(&self) -> &Self::Target {
89 &self.read_client
90 }
91}
92
93impl<S> IdentityClient<S>
94where
95 S: Signer<IotaKeySignature>,
96{
97 pub async fn new(client: IdentityClientReadOnly, signer: S) -> Result<Self, Error> {
99 let public_key = signer
100 .public_key()
101 .await
102 .map_err(|e| Error::InvalidKey(e.to_string()))?;
103
104 Ok(Self {
105 public_key,
106 read_client: client,
107 signer,
108 })
109 }
110}
111
112impl<S> IdentityClient<S> {
113 pub fn create_identity(&self, iota_document: IotaDocument) -> IdentityBuilder {
115 IdentityBuilder::new(iota_document)
116 }
117
118 pub fn create_authenticated_asset<T>(&self, content: T) -> AuthenticatedAssetBuilder<T>
120 where
121 T: MoveType + DeserializeOwned + Send + Sync + PartialEq,
122 {
123 AuthenticatedAssetBuilder::new(content)
124 }
125}
126
127impl<S> IdentityClient<S>
128where
129 S: Signer<IotaKeySignature> + OptionalSync,
130{
131 pub fn publish_did_document(&self, document: IotaDocument) -> TransactionBuilder<PublishDidDocument> {
133 TransactionBuilder::new(PublishDidDocument::new(document, self.sender_address()))
134 }
135
136 pub async fn publish_did_document_update(
139 &self,
140 document: IotaDocument,
141 gas_budget: u64,
142 ) -> Result<IotaDocument, Error> {
143 let mut oci =
144 if let Identity::FullFledged(value) = self.get_identity(get_object_id_from_did(document.id())?).await? {
145 value
146 } else {
147 return Err(Error::Identity("only new identities can be updated".to_string()));
148 };
149
150 let controller_token = oci.get_controller_token(self).await?.ok_or_else(|| {
151 Error::Identity(format!(
152 "address {} has no control over Identity {}",
153 self.sender_address(),
154 oci.id()
155 ))
156 })?;
157
158 oci
159 .update_did_document(document.clone(), &controller_token)
160 .finish(self)
161 .await?
162 .with_gas_budget(gas_budget)
163 .build_and_execute(self)
164 .await
165 .map_err(|e| Error::TransactionUnexpectedResponse(e.to_string()))?;
166
167 Ok(document)
168 }
169
170 pub async fn deactivate_did_output(&self, did: &IotaDID, gas_budget: u64) -> Result<(), Error> {
172 let mut oci = if let Identity::FullFledged(value) = self.get_identity(get_object_id_from_did(did)?).await? {
173 value
174 } else {
175 return Err(Error::Identity("only new identities can be deactivated".to_string()));
176 };
177
178 let controller_token = oci.get_controller_token(self).await?.ok_or_else(|| {
179 Error::Identity(format!(
180 "address {} has no control over Identity {}",
181 self.sender_address(),
182 oci.id()
183 ))
184 })?;
185
186 oci
187 .deactivate_did(&controller_token)
188 .finish(self)
189 .await?
190 .with_gas_budget(gas_budget)
191 .build_and_execute(self)
192 .await
193 .map_err(|e| Error::TransactionUnexpectedResponse(e.to_string()))?;
194
195 Ok(())
196 }
197
198 pub async fn find_owned_ref<P>(&self, tag: StructTag, predicate: P) -> Result<Option<ObjectRef>, Error>
201 where
202 P: Fn(&IotaObjectData) -> bool,
203 {
204 let filter = IotaObjectResponseQuery::new_with_filter(IotaObjectDataFilter::StructType(tag));
205
206 let mut cursor = None;
207 loop {
208 let mut page = self
209 .read_api()
210 .get_owned_objects(self.sender_address(), Some(filter.clone()), cursor, None)
211 .await?;
212 let obj_ref = std::mem::take(&mut page.data)
213 .into_iter()
214 .filter_map(|res| res.data)
215 .find(|obj| predicate(obj))
216 .map(|obj_data| obj_data.object_ref());
217 cursor = page.next_cursor;
218
219 if obj_ref.is_some() {
220 return Ok(obj_ref);
221 }
222 if !page.has_next_page {
223 break;
224 }
225 }
226
227 Ok(None)
228 }
229}
230
231#[cfg_attr(feature = "send-sync", async_trait)]
232#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
233impl<S> CoreClientReadOnly for IdentityClient<S>
234where
235 S: OptionalSync,
236{
237 fn client_adapter(&self) -> &IotaClientAdapter {
238 &self.read_client
239 }
240
241 fn package_id(&self) -> ObjectID {
242 self.read_client.package_id()
243 }
244
245 fn package_history(&self) -> Vec<ObjectID> {
246 self.read_client.package_history()
247 }
248
249 fn network_name(&self) -> &NetworkName {
250 self.read_client.network()
251 }
252}
253
254impl<S> CoreClient<S> for IdentityClient<S>
255where
256 S: Signer<IotaKeySignature> + OptionalSync,
257{
258 fn sender_address(&self) -> IotaAddress {
259 IotaAddress::from(&self.public_key)
260 }
261
262 fn signer(&self) -> &S {
263 &self.signer
264 }
265
266 fn sender_public_key(&self) -> &PublicKey {
267 &self.public_key
268 }
269}
270
271pub fn get_sender_public_key(sender_public_jwk: &Jwk) -> Result<Vec<u8>, Error> {
273 let public_key_base_64 = &sender_public_jwk
274 .try_okp_params()
275 .map_err(|err| Error::InvalidKey(format!("key not of type `Okp`; {err}")))?
276 .x;
277
278 identity_jose::jwu::decode_b64(public_key_base_64)
279 .map_err(|err| Error::InvalidKey(format!("could not decode base64 public key; {err}")))
280}
281
282#[derive(Debug, Clone)]
285pub struct PublishDidDocument {
286 did_document: IotaDocument,
287 controller: IotaAddress,
288 cached_ptb: OnceCell<ProgrammableTransaction>,
289}
290
291impl PublishDidDocument {
292 pub fn new(did_document: IotaDocument, controller: IotaAddress) -> Self {
294 Self {
295 did_document,
296 controller,
297 cached_ptb: OnceCell::new(),
298 }
299 }
300
301 async fn make_ptb(&self, client: &impl CoreClientReadOnly) -> Result<ProgrammableTransaction, Error> {
302 let package = identity_package_id(client).await?;
303 let did_doc = StateMetadataDocument::from(self.did_document.clone())
304 .pack(StateMetadataEncoding::Json)
305 .map_err(|e| Error::TransactionBuildingFailed(e.to_string()))?;
306
307 let programmable_tx_bcs =
308 move_calls::identity::new_with_controllers(Some(&did_doc), [(self.controller, 1, false)], 1, package).await?;
309 Ok(bcs::from_bytes(&programmable_tx_bcs)?)
310 }
311}
312
313#[cfg_attr(not(feature = "send-sync"), async_trait(?Send))]
314#[cfg_attr(feature = "send-sync", async_trait)]
315impl Transaction for PublishDidDocument {
316 type Output = IotaDocument;
317 type Error = Error;
318
319 async fn build_programmable_transaction<C>(&self, client: &C) -> Result<ProgrammableTransaction, Self::Error>
320 where
321 C: CoreClientReadOnly + OptionalSync,
322 {
323 self.cached_ptb.get_or_try_init(|| self.make_ptb(client)).await.cloned()
324 }
325
326 async fn apply<C>(self, effects: &mut IotaTransactionBlockEffects, client: &C) -> Result<Self::Output, Self::Error>
327 where
328 C: CoreClientReadOnly + OptionalSync,
329 {
330 let tx = {
331 let builder = IdentityBuilder::new(self.did_document)
332 .threshold(1)
333 .controller(self.controller, 1);
334 CreateIdentity::new(builder)
335 };
336
337 tx.apply(effects, client).await.map(IotaDocument::from)
338 }
339}