xmtp_api_d14n/queries/d14n/
identity.rs1use super::D14nClient;
2use crate::protocol::CursorStore;
3use crate::protocol::IdentityUpdateExtractor;
4use crate::protocol::SequencedExtractor;
5use crate::protocol::traits::{Envelope, EnvelopeCollection, Extractor};
6use crate::{d14n::PublishClientEnvelopes, d14n::QueryEnvelopes, endpoints::d14n::GetInboxIds};
7use itertools::Itertools;
8use std::collections::HashMap;
9use xmtp_common::RetryableError;
10use xmtp_configuration::Originators;
11use xmtp_id::associations::AccountId;
12use xmtp_id::scw_verifier::{SmartContractSignatureVerifier, VerifierError};
13use xmtp_proto::ConversionError;
14use xmtp_proto::api::{self, ApiClientError, Client, Query};
15use xmtp_proto::api_client::XmtpIdentityClient;
16use xmtp_proto::identity_v1;
17use xmtp_proto::identity_v1::VerifySmartContractWalletSignatureRequestSignature;
18use xmtp_proto::identity_v1::get_identity_updates_response::IdentityUpdateLog;
19use xmtp_proto::identity_v1::verify_smart_contract_wallet_signatures_response::ValidationResponse;
20use xmtp_proto::types::Topic;
21use xmtp_proto::xmtp::identity::api::v1::get_identity_updates_response::Response;
22use xmtp_proto::xmtp::identity::associations::IdentifierKind;
23use xmtp_proto::xmtp::xmtpv4::envelopes::Cursor;
24use xmtp_proto::xmtp::xmtpv4::message_api::{
25 EnvelopesQuery, GetInboxIdsResponse as GetInboxIdsResponseV4, QueryEnvelopesResponse,
26};
27
28#[xmtp_common::async_trait]
29impl<C, Store, E> XmtpIdentityClient for D14nClient<C, Store>
30where
31 E: RetryableError + 'static,
32 C: Client<Error = E>,
33 ApiClientError<E>: From<ApiClientError<<C as xmtp_proto::api::Client>::Error>> + 'static,
34 Store: CursorStore,
35{
36 type Error = ApiClientError<E>;
37
38 #[tracing::instrument(level = "trace", skip_all)]
39 async fn publish_identity_update(
40 &self,
41 request: identity_v1::PublishIdentityUpdateRequest,
42 ) -> Result<identity_v1::PublishIdentityUpdateResponse, Self::Error> {
43 let update = request.identity_update.ok_or(ConversionError::Missing {
44 item: "identity_update",
45 r#type: std::any::type_name::<identity_v1::PublishIdentityUpdateRequest>(),
46 })?;
47
48 let envelopes = update.client_envelope()?;
49 api::ignore(
50 PublishClientEnvelopes::builder()
51 .envelope(envelopes)
52 .build()?,
53 )
54 .query(&self.client)
55 .await?;
56
57 Ok(identity_v1::PublishIdentityUpdateResponse {})
58 }
59
60 #[tracing::instrument(level = "trace", skip_all)]
61 async fn get_identity_updates_v2(
62 &self,
63 request: identity_v1::GetIdentityUpdatesRequest,
64 ) -> Result<identity_v1::GetIdentityUpdatesResponse, Self::Error> {
65 if request.requests.is_empty() {
66 return Ok(identity_v1::GetIdentityUpdatesResponse { responses: vec![] });
67 }
68 let min_sid = request
69 .requests
70 .iter()
71 .map(|r| r.sequence_id)
72 .min()
73 .unwrap_or(0);
74 let topics = request.requests.topics()?;
75 let last_seen = Some(Cursor {
76 node_id_to_sequence_id: [(Originators::INBOX_LOG, min_sid)].into(),
77 });
78 let result: QueryEnvelopesResponse = QueryEnvelopes::builder()
79 .envelopes(EnvelopesQuery {
80 topics: topics.iter().map(Topic::cloned_vec).collect(),
81 originator_node_ids: vec![],
82 last_seen,
83 })
84 .build()?
85 .query(&self.client)
86 .await?;
87
88 let updates: HashMap<String, Vec<IdentityUpdateLog>> = SequencedExtractor::builder()
89 .envelopes(result.envelopes)
90 .build::<IdentityUpdateExtractor>()
91 .get()?
92 .into_iter()
93 .into_group_map();
94
95 let responses = updates
96 .into_iter()
97 .map(|(inbox_id, updates)| Response { updates, inbox_id })
98 .collect();
99 Ok(identity_v1::GetIdentityUpdatesResponse { responses })
100 }
101
102 #[tracing::instrument(level = "trace", skip_all)]
103 async fn get_inbox_ids(
104 &self,
105 request: identity_v1::GetInboxIdsRequest,
106 ) -> Result<identity_v1::GetInboxIdsResponse, Self::Error> {
107 let res: GetInboxIdsResponseV4 = GetInboxIds::builder()
108 .addresses(
109 request
110 .requests
111 .iter()
112 .filter(|r| r.identifier_kind == IdentifierKind::Ethereum as i32)
113 .map(|r| r.identifier.clone())
114 .collect::<Vec<_>>(),
115 )
116 .passkeys(
117 request
118 .requests
119 .iter()
120 .filter(|r| r.identifier_kind == IdentifierKind::Passkey as i32)
121 .map(|r| r.identifier.clone())
122 .collect::<Vec<_>>(),
123 )
124 .build()?
125 .query(&self.client)
126 .await?;
127
128 Ok(identity_v1::GetInboxIdsResponse {
129 responses: res
130 .responses
131 .iter()
132 .map(|r| identity_v1::get_inbox_ids_response::Response {
133 identifier: r.identifier.clone(),
134 identifier_kind: IdentifierKind::Ethereum as i32,
135 inbox_id: r.inbox_id.clone(),
136 })
137 .collect::<Vec<_>>(),
138 })
139 }
140
141 #[tracing::instrument(level = "trace", skip_all)]
142 async fn verify_smart_contract_wallet_signatures(
143 &self,
144 request: identity_v1::VerifySmartContractWalletSignaturesRequest,
145 ) -> Result<identity_v1::VerifySmartContractWalletSignaturesResponse, Self::Error> {
146 let mut responses = vec![];
147 for VerifySmartContractWalletSignatureRequestSignature {
148 account_id,
149 block_number,
150 signature,
151 hash,
152 } in request.signatures
153 {
154 let id = AccountId::try_from(account_id)?;
155 let hash = hash.try_into().map_err(|e| {
156 ApiClientError::Other(Box::new(VerifierError::InvalidHash(e)) as Box<_>)
157 })?;
158 let result = self
159 .scw_verifier
160 .is_valid_signature(id, hash, signature.into(), block_number)
161 .await
162 .map_err(|e| ApiClientError::Other(Box::new(e) as Box<_>))?;
163 responses.push(ValidationResponse {
164 is_valid: result.is_valid,
165 block_number: result.block_number,
166 error: result.error,
167 })
168 }
169 Ok(identity_v1::VerifySmartContractWalletSignaturesResponse { responses })
170 }
171}