xmtp_api_d14n/queries/d14n/
identity.rs

1use 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}