Skip to main content

bsv_messagebox_client/
adapter.rs

1use std::sync::Arc;
2
3use async_trait::async_trait;
4use bsv::remittance::CommsLayer;
5use bsv::remittance::types::PeerMessage;
6use bsv::remittance::error::RemittanceError;
7use bsv::wallet::interfaces::WalletInterface;
8
9use crate::client::MessageBoxClient;
10
11/// Bridge between `MessageBoxClient<W>` and the `CommsLayer` trait.
12///
13/// `RemittanceAdapter<W>` is the primary integration point for downstream
14/// consumers (e.g. `metawatt-edge-rs`) that interact with the messagebox
15/// protocol through the SDK's `CommsLayer` trait boundary.
16///
17/// Uses composition (`Arc<MessageBoxClient<W>>`) rather than inheritance —
18/// this matches the pre-phase architectural decision in STATE.md and keeps
19/// the adapter lightweight.
20pub struct RemittanceAdapter<W: WalletInterface + Clone + 'static> {
21    inner: Arc<MessageBoxClient<W>>,
22}
23
24impl<W: WalletInterface + Clone + 'static> RemittanceAdapter<W> {
25    /// Construct a new `RemittanceAdapter` wrapping `client`.
26    pub fn new(client: Arc<MessageBoxClient<W>>) -> Self {
27        Self { inner: client }
28    }
29}
30
31#[async_trait]
32impl<W: WalletInterface + Clone + 'static + Send + Sync> CommsLayer for RemittanceAdapter<W> {
33    /// Delegate to `MessageBoxClient`, passing `host_override` through.
34    ///
35    /// When `host_override` is `Some(host)`, calls `send_message_to_host` directly
36    /// bypassing overlay resolution. When `None`, calls `send_message` which resolves
37    /// the recipient's host via overlay (TS parity: `overrideHost ?? resolveHostForRecipient`).
38    async fn send_message(
39        &self,
40        recipient: &str,
41        message_box: &str,
42        body: &str,
43        host_override: Option<&str>,
44    ) -> Result<String, RemittanceError> {
45        match host_override {
46            Some(host) => self.inner
47                .send_message_to_host(host, recipient, message_box, body, false, false, None, None)
48                .await
49                .map_err(|e| RemittanceError::Protocol(e.to_string())),
50            None => self.inner
51                .send_message(recipient, message_box, body, false, false, None, None)
52                .await
53                .map_err(|e| RemittanceError::Protocol(e.to_string())),
54        }
55    }
56
57    /// Retrieve messages and map them to `Vec<PeerMessage>`.
58    ///
59    /// CRITICAL: `PeerMessage.recipient` is populated from `get_identity_key()`
60    /// — NOT from `ServerPeerMessage`, which does not carry a recipient field.
61    /// `PeerMessage.message_box` comes from the parameter, not the server response.
62    async fn list_messages(
63        &self,
64        message_box: &str,
65        _host: Option<&str>,
66    ) -> Result<Vec<PeerMessage>, RemittanceError> {
67        // Fetch identity key once before the mapping loop (cached by OnceCell).
68        let identity_key = self
69            .inner
70            .get_identity_key()
71            .await
72            .map_err(|e| RemittanceError::Protocol(e.to_string()))?;
73
74        let server_msgs = self
75            .inner
76            .list_messages_lite(message_box, _host)
77            .await
78            .map_err(|e| RemittanceError::Protocol(e.to_string()))?;
79
80        Ok(server_msgs
81            .into_iter()
82            .map(|m| PeerMessage {
83                message_id: m.message_id,
84                sender: m.sender,
85                recipient: identity_key.clone(),  // Pitfall 3: not from ServerPeerMessage
86                message_box: message_box.to_string(), // from parameter, not server response
87                body: m.body,
88            })
89            .collect())
90    }
91
92    /// Delegate to `MessageBoxClient::acknowledge_message`.
93    ///
94    /// Converts `&[String]` to `Vec<String>` to match the inner method signature (Pitfall 4).
95    async fn acknowledge_message(
96        &self,
97        message_ids: &[String],
98    ) -> Result<(), RemittanceError> {
99        self.inner
100            .acknowledge_message(message_ids.to_vec(), None)
101            .await
102            .map_err(|e| RemittanceError::Protocol(e.to_string()))
103    }
104
105    /// Delegate to `MessageBoxClient::send_live_message`.
106    ///
107    /// Passes `host_override` through to `MessageBoxClient::send_live_message`
108    /// which applies it on the HTTP fallback path.
109    ///
110    /// The `CommsLayer` trait requires `Result<String, RemittanceError>`.
111    /// `MessageBoxClient::send_live_message` now returns `Result<DeliveryMode>`;
112    /// we extract the message ID via `.message_id()`. Callers that need to
113    /// distinguish live vs persisted delivery should use `MessageBoxClient`
114    /// directly rather than going through this adapter.
115    async fn send_live_message(
116        &self,
117        recipient: &str,
118        message_box: &str,
119        body: &str,
120        host_override: Option<&str>,
121    ) -> Result<String, RemittanceError> {
122        self.inner
123            .send_live_message(recipient, message_box, body, false, false, None, host_override)
124            .await
125            .map(|d| d.message_id().to_string())
126            .map_err(|e| RemittanceError::Protocol(e.to_string()))
127    }
128
129    /// Delegate to `MessageBoxClient::listen_for_live_messages`.
130    ///
131    /// Passes `override_host` through (currently deferred in WS path).
132    async fn listen_for_live_messages(
133        &self,
134        message_box: &str,
135        override_host: Option<&str>,
136        on_message: Arc<dyn Fn(PeerMessage) + Send + Sync>,
137    ) -> Result<(), RemittanceError> {
138        self.inner
139            .listen_for_live_messages(message_box, on_message, override_host)
140            .await
141            .map_err(|e| RemittanceError::Protocol(e.to_string()))
142    }
143}
144
145// ---------------------------------------------------------------------------
146// Tests
147// ---------------------------------------------------------------------------
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use crate::types::ServerPeerMessage;
153    use bsv::primitives::private_key::PrivateKey;
154    use bsv::wallet::error::WalletError;
155    use bsv::wallet::interfaces::*;
156    use bsv::wallet::proto_wallet::ProtoWallet;
157
158    // Reuse the same ArcWallet test helper pattern as client::tests / http_ops::tests.
159    #[derive(Clone)]
160    struct ArcWallet(Arc<ProtoWallet>);
161
162    impl ArcWallet {
163        fn new() -> Self {
164            let key = PrivateKey::from_random().expect("random key");
165            ArcWallet(Arc::new(ProtoWallet::new(key)))
166        }
167    }
168
169    #[async_trait::async_trait]
170    impl WalletInterface for ArcWallet {
171        async fn create_action(&self, args: CreateActionArgs, orig: Option<&str>) -> Result<CreateActionResult, WalletError> { self.0.create_action(args, orig).await }
172        async fn sign_action(&self, args: SignActionArgs, orig: Option<&str>) -> Result<SignActionResult, WalletError> { self.0.sign_action(args, orig).await }
173        async fn abort_action(&self, args: AbortActionArgs, orig: Option<&str>) -> Result<AbortActionResult, WalletError> { self.0.abort_action(args, orig).await }
174        async fn list_actions(&self, args: ListActionsArgs, orig: Option<&str>) -> Result<ListActionsResult, WalletError> { self.0.list_actions(args, orig).await }
175        async fn internalize_action(&self, args: InternalizeActionArgs, orig: Option<&str>) -> Result<InternalizeActionResult, WalletError> { self.0.internalize_action(args, orig).await }
176        async fn list_outputs(&self, args: ListOutputsArgs, orig: Option<&str>) -> Result<ListOutputsResult, WalletError> { self.0.list_outputs(args, orig).await }
177        async fn relinquish_output(&self, args: RelinquishOutputArgs, orig: Option<&str>) -> Result<RelinquishOutputResult, WalletError> { self.0.relinquish_output(args, orig).await }
178        async fn get_public_key(&self, args: GetPublicKeyArgs, orig: Option<&str>) -> Result<GetPublicKeyResult, WalletError> { self.0.get_public_key(args, orig).await }
179        async fn reveal_counterparty_key_linkage(&self, args: RevealCounterpartyKeyLinkageArgs, orig: Option<&str>) -> Result<RevealCounterpartyKeyLinkageResult, WalletError> { self.0.reveal_counterparty_key_linkage(args, orig).await }
180        async fn reveal_specific_key_linkage(&self, args: RevealSpecificKeyLinkageArgs, orig: Option<&str>) -> Result<RevealSpecificKeyLinkageResult, WalletError> { self.0.reveal_specific_key_linkage(args, orig).await }
181        async fn encrypt(&self, args: EncryptArgs, orig: Option<&str>) -> Result<EncryptResult, WalletError> { self.0.encrypt(args, orig).await }
182        async fn decrypt(&self, args: DecryptArgs, orig: Option<&str>) -> Result<DecryptResult, WalletError> { self.0.decrypt(args, orig).await }
183        async fn create_hmac(&self, args: CreateHmacArgs, orig: Option<&str>) -> Result<CreateHmacResult, WalletError> { self.0.create_hmac(args, orig).await }
184        async fn verify_hmac(&self, args: VerifyHmacArgs, orig: Option<&str>) -> Result<VerifyHmacResult, WalletError> { self.0.verify_hmac(args, orig).await }
185        async fn create_signature(&self, args: CreateSignatureArgs, orig: Option<&str>) -> Result<CreateSignatureResult, WalletError> { self.0.create_signature(args, orig).await }
186        async fn verify_signature(&self, args: VerifySignatureArgs, orig: Option<&str>) -> Result<VerifySignatureResult, WalletError> { self.0.verify_signature(args, orig).await }
187        async fn acquire_certificate(&self, args: AcquireCertificateArgs, orig: Option<&str>) -> Result<Certificate, WalletError> { self.0.acquire_certificate(args, orig).await }
188        async fn list_certificates(&self, args: ListCertificatesArgs, orig: Option<&str>) -> Result<ListCertificatesResult, WalletError> { self.0.list_certificates(args, orig).await }
189        async fn prove_certificate(&self, args: ProveCertificateArgs, orig: Option<&str>) -> Result<ProveCertificateResult, WalletError> { self.0.prove_certificate(args, orig).await }
190        async fn relinquish_certificate(&self, args: RelinquishCertificateArgs, orig: Option<&str>) -> Result<RelinquishCertificateResult, WalletError> { self.0.relinquish_certificate(args, orig).await }
191        async fn discover_by_identity_key(&self, args: DiscoverByIdentityKeyArgs, orig: Option<&str>) -> Result<DiscoverCertificatesResult, WalletError> { self.0.discover_by_identity_key(args, orig).await }
192        async fn discover_by_attributes(&self, args: DiscoverByAttributesArgs, orig: Option<&str>) -> Result<DiscoverCertificatesResult, WalletError> { self.0.discover_by_attributes(args, orig).await }
193        async fn is_authenticated(&self, orig: Option<&str>) -> Result<AuthenticatedResult, WalletError> { self.0.is_authenticated(orig).await }
194        async fn wait_for_authentication(&self, orig: Option<&str>) -> Result<AuthenticatedResult, WalletError> { self.0.wait_for_authentication(orig).await }
195        async fn get_height(&self, orig: Option<&str>) -> Result<GetHeightResult, WalletError> { self.0.get_height(orig).await }
196        async fn get_header_for_height(&self, args: GetHeaderArgs, orig: Option<&str>) -> Result<GetHeaderResult, WalletError> { self.0.get_header_for_height(args, orig).await }
197        async fn get_network(&self, orig: Option<&str>) -> Result<GetNetworkResult, WalletError> { self.0.get_network(orig).await }
198        async fn get_version(&self, orig: Option<&str>) -> Result<GetVersionResult, WalletError> { self.0.get_version(orig).await }
199    }
200
201    fn make_client() -> Arc<MessageBoxClient<ArcWallet>> {
202        Arc::new(MessageBoxClient::new(
203            "https://example.com".to_string(),
204            ArcWallet::new(),
205            None,
206            bsv::services::overlay_tools::Network::Mainnet,
207        ))
208    }
209
210    /// `RemittanceAdapter::new` constructs successfully from an Arc<MessageBoxClient<W>>.
211    #[test]
212    fn adapter_can_be_constructed() {
213        let client = make_client();
214        let _adapter = RemittanceAdapter::new(client);
215    }
216
217    /// `RemittanceAdapter` satisfies `Arc<dyn CommsLayer + Send + Sync>` — compile check.
218    ///
219    /// If `RemittanceAdapter` does not implement `CommsLayer` correctly this
220    /// type coercion will fail to compile.
221    #[test]
222    fn adapter_is_comms_layer() {
223        let client = make_client();
224        let adapter = Arc::new(RemittanceAdapter::new(client));
225        let _: Arc<dyn CommsLayer + Send + Sync> = adapter;
226    }
227
228    /// `ServerPeerMessage` maps to `PeerMessage` with all 5 fields correct.
229    ///
230    /// This exercises the mapping logic from `list_messages` directly,
231    /// without a live HTTP call.
232    #[test]
233    fn map_server_message_all_five_fields() {
234        let server_msg = ServerPeerMessage {
235            message_id: "msg-001".to_string(),
236            body: "hello body".to_string(),
237            sender: "03senderkey".to_string(),
238            created_at: "2024-01-01T00:00:00Z".to_string(),
239            updated_at: "2024-01-01T00:01:00Z".to_string(),
240            acknowledged: None,
241        };
242        let identity_key = "03myidentitykey".to_string();
243        let message_box = "payment_inbox";
244
245        // Apply the same mapping logic as list_messages.
246        let peer_msg = PeerMessage {
247            message_id: server_msg.message_id.clone(),
248            sender: server_msg.sender.clone(),
249            recipient: identity_key.clone(),
250            message_box: message_box.to_string(),
251            body: server_msg.body.clone(),
252        };
253
254        assert_eq!(peer_msg.message_id, "msg-001");
255        assert_eq!(peer_msg.sender, "03senderkey");
256        assert_eq!(peer_msg.recipient, "03myidentitykey", "recipient from identity key");
257        assert_eq!(peer_msg.message_box, "payment_inbox", "message_box from parameter");
258        assert_eq!(peer_msg.body, "hello body");
259    }
260
261    /// `PeerMessage.recipient` is the identity key — NOT an empty string.
262    ///
263    /// This test guards against the common mistake of leaving recipient empty
264    /// when ServerPeerMessage has no recipient field.
265    #[tokio::test]
266    async fn recipient_from_identity_key() {
267        let client = make_client();
268        let identity_key = client
269            .get_identity_key()
270            .await
271            .expect("get_identity_key");
272
273        assert!(!identity_key.is_empty(), "identity key must not be empty");
274
275        // The mapping assigns this key to PeerMessage.recipient.
276        let peer_msg = PeerMessage {
277            message_id: "x".to_string(),
278            sender: "03other".to_string(),
279            recipient: identity_key.clone(),
280            message_box: "inbox".to_string(),
281            body: "body".to_string(),
282        };
283
284        assert_eq!(peer_msg.recipient, identity_key);
285        assert_ne!(peer_msg.recipient, "", "recipient must not be empty string");
286    }
287
288    /// `acknowledge_message` converts `&[String]` to `Vec<String>` — compile check.
289    ///
290    /// Verifies the `.to_vec()` conversion compiles correctly with the adapter impl.
291    #[test]
292    fn acknowledge_message_accepts_slice() {
293        // Verifies that &[String] (the CommsLayer signature) is accepted.
294        // This is a compile-time check — if &[String] -> Vec<String> conversion
295        // is missing in the adapter, this function fails to compile.
296        let ids: &[String] = &["id1".to_string(), "id2".to_string()];
297        let converted: Vec<String> = ids.to_vec();
298        assert_eq!(converted, vec!["id1", "id2"]);
299    }
300
301    /// `send_live_message` is overridden — compile check via method resolution.
302    ///
303    /// If this resolves to the override (not the default trait impl), the method
304    /// is wired to `MessageBoxClient::send_live_message`.
305    #[allow(dead_code)]
306    fn send_live_message_compiles(adapter: &RemittanceAdapter<ArcWallet>) {
307        let _fut = adapter.send_live_message("03abc", "inbox", "hello", None);
308    }
309
310    /// `listen_for_live_messages` is overridden — compile check via method resolution.
311    ///
312    /// Constructs a dummy callback to verify the method resolves and compiles.
313    #[allow(dead_code)]
314    fn listen_for_live_messages_compiles(adapter: &RemittanceAdapter<ArcWallet>) {
315        let cb: Arc<dyn Fn(PeerMessage) + Send + Sync> = Arc::new(|_msg| {});
316        let _fut = adapter.listen_for_live_messages("inbox", None, cb);
317    }
318
319    /// `send_message` with a host_override compiles — compile check.
320    ///
321    /// Verifies `send_message_to_host` is called when host_override is Some.
322    #[allow(dead_code)]
323    fn test_adapter_send_message_with_host_override_compiles(adapter: &RemittanceAdapter<ArcWallet>) {
324        let _fut = adapter.send_message("03recipient", "inbox", "body", Some("https://other.host"));
325    }
326
327    /// `send_message` with no host_override compiles — compile check.
328    #[allow(dead_code)]
329    fn test_adapter_send_message_without_override_compiles(adapter: &RemittanceAdapter<ArcWallet>) {
330        let _fut = adapter.send_message("03recipient", "inbox", "body", None);
331    }
332}