1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
pub mod error;

use error::MessagingOperationError;
use ethers::{
    abi::{Address, Token},
    contract::abigen,
    core::abi::encode_packed,
    core::types::{Bytes, Signature},
    providers::Middleware,
    signers::LocalWallet,
    types::H256,
    utils::keccak256,
};
use xps_types::{error::ExtSignerError, Message, SendMessageResult, Status};

abigen!(
    Conversation,
    "./abi/Conversation.json",
    derives(serde::Serialize, serde::Deserialize)
);

pub struct MessagingOperations<Middleware> {
    contract: Conversation<Middleware>,
}

impl<M> MessagingOperations<M>
where
    M: Middleware + 'static,
{
    /// Creates a new MessagingOperations instance
    pub fn new(contract: Conversation<M>) -> Self {
        Self { contract }
    }

    pub async fn send_message(
        &self,
        m: Message,
    ) -> Result<SendMessageResult, MessagingOperationError<M>> {
        let transaction_receipt = self
            .contract
            .send_message_signed(
                m.conversation_id,
                m.payload,
                m.identity,
                m.signature.v.try_into()?,
                m.signature.r.into(),
                m.signature.s.into(),
            )
            .send()
            .await?
            .await?;
        Ok(SendMessageResult {
            status: Status::Success,
            message: "Message sent.".to_string(),
            transaction: transaction_receipt.unwrap().transaction_hash.to_string(),
        })
    }
}

/// Signer for data that is externally signed to be processed by the Conversation Contract.
#[async_trait::async_trait]
pub trait ConversationSignerExt {
    /// Sign hash of the data for [`Conversation::send_message_signed`]
    async fn sign_xmtp_message<M: Middleware>(
        &self,
        conversation: &Conversation<M>,
        conversation_id: [u8; 32],
        payload: Bytes,
        identity: Address,
    ) -> Result<Signature, ExtSignerError<M>>;
}

#[async_trait::async_trait]
impl ConversationSignerExt for LocalWallet {
    async fn sign_xmtp_message<M: Middleware>(
        &self,
        conversation: &Conversation<M>,
        conversation_id: [u8; 32],
        payload: Bytes,
        identity: Address,
    ) -> Result<Signature, ExtSignerError<M>> {
        let nonce = conversation.nonce(identity).call().await?;
        let mut nonce_bytes = [0; 32];
        nonce.to_big_endian(&mut nonce_bytes);
        let tokens = vec![
            Token::FixedBytes(vec![0x19]),
            Token::FixedBytes(vec![0x0]),
            Token::FixedBytes(conversation_id[0..32].to_vec()),
            Token::Bytes(payload.to_vec()),
            Token::Address(identity),
            Token::Bytes(nonce_bytes[0..32].to_vec()),
        ];

        let encoded = encode_packed(tokens.as_slice())?;
        let digest = H256(keccak256(encoded));
        let signature = self.sign_hash(digest)?;
        Ok(signature)
    }
}