Oak

Check-in [2be15f5044]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:ref(deps): Cache Nostr keys to avoid async calls
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 2be15f5044cd004efe4598da2ad988fb1a67297c7176475af04afd4b585a2d52
User & Date: carlos 2023-10-27 14:56:56
Context
2023-11-12
11:23
chore: Bump dependencies check-in: 7668189ce1 user: carlos tags: trunk
2023-10-27
14:56
ref(deps): Cache Nostr keys to avoid async calls check-in: 2be15f5044 user: carlos tags: trunk
2023-10-22
09:46
ref(deps): Bump ln-sdk and powpub dependencies check-in: 8321f1314c user: carlos tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to Cargo.lock.

2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"

[[package]]
name = "ln-sdk"
version = "0.2.0-SNAPSHOT"
source = "git+https://oak-node.net/cgit/ln-sdk.git?rev=6147917922b4405e73d4deb69bcee721709434cb#6147917922b4405e73d4deb69bcee721709434cb"
dependencies = [
 "anyhow",
 "async-trait",
 "base64 0.20.0",
 "bech32",
 "bitcoin_hashes 0.12.0",
 "cfg_aliases",







|







2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"

[[package]]
name = "ln-sdk"
version = "0.2.0-SNAPSHOT"
source = "git+https://oak-node.net/cgit/ln-sdk.git?rev=8366a4dcaf4df53b4ecafde1af30b2fb865e8200#8366a4dcaf4df53b4ecafde1af30b2fb865e8200"
dependencies = [
 "anyhow",
 "async-trait",
 "base64 0.20.0",
 "bech32",
 "bitcoin_hashes 0.12.0",
 "cfg_aliases",
2565
2566
2567
2568
2569
2570
2571






2572
2573
2574
2575
2576
2577
2578
 "openssl-sys",
 "schannel",
 "security-framework",
 "security-framework-sys",
 "tempfile",
]







[[package]]
name = "nom"
version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
dependencies = [
 "memchr",







>
>
>
>
>
>







2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
 "openssl-sys",
 "schannel",
 "security-framework",
 "security-framework-sys",
 "tempfile",
]

[[package]]
name = "negentropy"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e664971378a3987224f7a0e10059782035e89899ae403718ee07de85bec42afe"

[[package]]
name = "nom"
version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
dependencies = [
 "memchr",
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619

2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
dependencies = [
 "windows-sys 0.48.0",
]

[[package]]
name = "nostr"
version = "0.24.0"
source = "git+https://github.com/rust-nostr/nostr?tag=v0.24.0#fda219a74b89c62ace2b1869b1f6a7eec37e116c"
dependencies = [
 "aes",
 "base64 0.21.4",
 "bip39",
 "bitcoin 0.30.1",
 "cbc",
 "chacha20",
 "getrandom 0.2.10",
 "instant",

 "once_cell",
 "reqwest",
 "serde",
 "serde_json",
 "tracing",
 "url-fork",
]

[[package]]
name = "nostr-sdk"
version = "0.24.0"
source = "git+https://github.com/rust-nostr/nostr?tag=v0.24.0#fda219a74b89c62ace2b1869b1f6a7eec37e116c"
dependencies = [
 "async-utility",
 "nostr",
 "nostr-sdk-net",
 "once_cell",
 "thiserror",
 "tokio",
 "tracing",
]

[[package]]
name = "nostr-sdk-net"
version = "0.24.0"
source = "git+https://github.com/rust-nostr/nostr?tag=v0.24.0#fda219a74b89c62ace2b1869b1f6a7eec37e116c"
dependencies = [
 "futures-util",
 "thiserror",
 "tokio",
 "tokio-rustls 0.24.1",
 "tokio-socks",
 "tokio-tungstenite",







|









>











|













|







2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
dependencies = [
 "windows-sys 0.48.0",
]

[[package]]
name = "nostr"
version = "0.24.0"
source = "git+https://github.com/rust-nostr/nostr?rev=2799d037ecef0f14e993ef140920ed9c90d8374b#2799d037ecef0f14e993ef140920ed9c90d8374b"
dependencies = [
 "aes",
 "base64 0.21.4",
 "bip39",
 "bitcoin 0.30.1",
 "cbc",
 "chacha20",
 "getrandom 0.2.10",
 "instant",
 "negentropy",
 "once_cell",
 "reqwest",
 "serde",
 "serde_json",
 "tracing",
 "url-fork",
]

[[package]]
name = "nostr-sdk"
version = "0.24.0"
source = "git+https://github.com/rust-nostr/nostr?rev=2799d037ecef0f14e993ef140920ed9c90d8374b#2799d037ecef0f14e993ef140920ed9c90d8374b"
dependencies = [
 "async-utility",
 "nostr",
 "nostr-sdk-net",
 "once_cell",
 "thiserror",
 "tokio",
 "tracing",
]

[[package]]
name = "nostr-sdk-net"
version = "0.24.0"
source = "git+https://github.com/rust-nostr/nostr?rev=2799d037ecef0f14e993ef140920ed9c90d8374b#2799d037ecef0f14e993ef140920ed9c90d8374b"
dependencies = [
 "futures-util",
 "thiserror",
 "tokio",
 "tokio-rustls 0.24.1",
 "tokio-socks",
 "tokio-tungstenite",
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
 "flate2",
 "miniz_oxide",
]

[[package]]
name = "powpub"
version = "0.1.4-SNAPSHOT"
source = "git+https://oak-node.net/cgit/powpub.git?rev=cb9a9662810ccc43270a194bd6c4e579197e3879#cb9a9662810ccc43270a194bd6c4e579197e3879"
dependencies = [
 "anyhow",
 "async-trait",
 "base64 0.20.0",
 "bech32",
 "cfg_aliases",
 "env_logger",







|







3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
 "flate2",
 "miniz_oxide",
]

[[package]]
name = "powpub"
version = "0.1.4-SNAPSHOT"
source = "git+https://oak-node.net/cgit/powpub.git?rev=d8e050a51cf12622562efef34d2d6de8b58f363e#d8e050a51cf12622562efef34d2d6de8b58f363e"
dependencies = [
 "anyhow",
 "async-trait",
 "base64 0.20.0",
 "bech32",
 "cfg_aliases",
 "env_logger",

Changes to Cargo.toml.

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
chrono = { version = "*", default-features = false }
deltachat = { git = "https://github.com/deltachat/deltachat-core-rust", tag="1.108.0"}
futures-lite = "*"
futures-util = "*"
hex = "*"
itertools = "*"
lightning-invoice = "*"
ln-sdk = { git = "https://oak-node.net/cgit/ln-sdk.git", rev = "6147917922b4405e73d4deb69bcee721709434cb", features = [ "client-lnd", "utoipa" ] }
log = "*"
log4rs = "*"
qrcode-generator = "*"
nostr = { git = "https://github.com/rust-nostr/nostr", default-features = false, features = ["nip04", "nip05", "nip47"], tag = "v0.24.0" }
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", default-features = false, tag = "v0.24.0" }
powpub = { git = "https://oak-node.net/cgit/powpub.git", rev = "cb9a9662810ccc43270a194bd6c4e579197e3879", features = [ "client-lnd", "utoipa" ] }
reqwest = { version = "*", default-features = false, features = ["json", "native-tls", "socks"] }
rocket = { version = "0.5.0-rc.3", features = ["json"] }
rocket_db_pools = { version = "0.1.0-rc.3", features = ["sqlx_sqlite"] }
rocket_dyn_templates = { version = "0.1.0-rc.3", features = ["tera"] }
serde = "*"
serde_json = "*"
serde_with = "*"







|



|
|
|







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
chrono = { version = "*", default-features = false }
deltachat = { git = "https://github.com/deltachat/deltachat-core-rust", tag="1.108.0"}
futures-lite = "*"
futures-util = "*"
hex = "*"
itertools = "*"
lightning-invoice = "*"
ln-sdk = { git = "https://oak-node.net/cgit/ln-sdk.git", rev = "8366a4dcaf4df53b4ecafde1af30b2fb865e8200", features = [ "client-lnd", "utoipa" ] }
log = "*"
log4rs = "*"
qrcode-generator = "*"
nostr = { git = "https://github.com/rust-nostr/nostr", default-features = false, features = ["nip04", "nip05", "nip47"], rev = "2799d037ecef0f14e993ef140920ed9c90d8374b" }
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", default-features = false, rev = "2799d037ecef0f14e993ef140920ed9c90d8374b" }
powpub = { git = "https://oak-node.net/cgit/powpub.git", rev = "d8e050a51cf12622562efef34d2d6de8b58f363e", features = [ "client-lnd", "utoipa" ] }
reqwest = { version = "*", default-features = false, features = ["json", "native-tls", "socks"] }
rocket = { version = "0.5.0-rc.3", features = ["json"] }
rocket_db_pools = { version = "0.1.0-rc.3", features = ["sqlx_sqlite"] }
rocket_dyn_templates = { version = "0.1.0-rc.3", features = ["tera"] }
serde = "*"
serde_json = "*"
serde_with = "*"

Changes to src/bot/nostr.rs.

39
40
41
42
43
44
45


46
47
48
49
50
51
52
    })
}

#[derive(Clone)]
pub struct NostrContext {
    /// The nostr client
    pub(crate) client: Arc<Client>,


    pub(in crate::bot) nostr_bot_owner_nprofile: Profile,
}

impl NostrContext {
    /// Calculates a map with the current status of each relay used by the [Client]
    pub(crate) async fn get_relay_status(&self) -> HashMap<String, OakRelayStatus> {
        let mut result = HashMap::new();







>
>







39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
    })
}

#[derive(Clone)]
pub struct NostrContext {
    /// The nostr client
    pub(crate) client: Arc<Client>,
    /// The cached nostr keys. We don't expect to change them at runtime, so a cache helps bypass unnecessary locking on the client.
    pub(crate) client_keys_cached: Keys,
    pub(in crate::bot) nostr_bot_owner_nprofile: Profile,
}

impl NostrContext {
    /// Calculates a map with the current status of each relay used by the [Client]
    pub(crate) async fn get_relay_status(&self) -> HashMap<String, OakRelayStatus> {
        let mut result = HashMap::new();
126
127
128
129
130
131
132


133

134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162

        Ok(())
    }

    pub(in crate::bot) fn decrypt(&self, event: &Event) -> String {
        debug!("Encrypted: {}", event.content);
        let decrypted_msg = decrypt(


            &self.client.keys().secret_key().unwrap(),

            &self.nostr_bot_owner_nprofile.public_key,
            &event.content,
        )
        .unwrap();
        debug!("Decrypted: {}", decrypted_msg);

        decrypted_msg
    }
}

#[async_trait]
impl BotSender for NostrContext {
    async fn send(&mut self, msg: String) {
        let nostr_bot_encrypted_msg = EventBuilder::new_encrypted_direct_msg(
            &self.client.keys(),
            self.nostr_bot_owner_nprofile.public_key,
            &msg,
            None, // TODO Add owner msg ID as reply-to
        )
        .map_err(|e| error!("Failed to create encrypted DM for owner: {e:?}"))
        .unwrap()
        .to_event(&self.client.keys())
        .unwrap();

        self.client
            .send_event(nostr_bot_encrypted_msg)
            .await
            .map_err(|e| error!("Failed to send Nostr message: {e:?}"))
            .unwrap();







>
>
|
>














|






|







128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167

        Ok(())
    }

    pub(in crate::bot) fn decrypt(&self, event: &Event) -> String {
        debug!("Encrypted: {}", event.content);
        let decrypted_msg = decrypt(
            &self
                .client_keys_cached
                .secret_key()
                .expect("Client keys don't contain a sk"),
            &self.nostr_bot_owner_nprofile.public_key,
            &event.content,
        )
        .unwrap();
        debug!("Decrypted: {}", decrypted_msg);

        decrypted_msg
    }
}

#[async_trait]
impl BotSender for NostrContext {
    async fn send(&mut self, msg: String) {
        let nostr_bot_encrypted_msg = EventBuilder::new_encrypted_direct_msg(
            &self.client_keys_cached,
            self.nostr_bot_owner_nprofile.public_key,
            &msg,
            None, // TODO Add owner msg ID as reply-to
        )
        .map_err(|e| error!("Failed to create encrypted DM for owner: {e:?}"))
        .unwrap()
        .to_event(&self.client_keys_cached)
        .unwrap();

        self.client
            .send_event(nostr_bot_encrypted_msg)
            .await
            .map_err(|e| error!("Failed to send Nostr message: {e:?}"))
            .unwrap();
227
228
229
230
231
232
233

234
235

236
237
238
239
240
241
242
                client.add_relay(relay.clone(), None).await?;
            }
            client.connect().await;

            info!("Nostr bot successful configured");
            nostr_bot_status.to_state_running()?;


            Ok(Some(NostrContext {
                client: Arc::new(client),

                nostr_bot_owner_nprofile: owner_profile,
            }))
        }
    }
}

pub async fn start(







>


>







232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
                client.add_relay(relay.clone(), None).await?;
            }
            client.connect().await;

            info!("Nostr bot successful configured");
            nostr_bot_status.to_state_running()?;

            let client_keys_cached = client.keys().await;
            Ok(Some(NostrContext {
                client: Arc::new(client),
                client_keys_cached,
                nostr_bot_owner_nprofile: owner_profile,
            }))
        }
    }
}

pub async fn start(

Changes to src/bot/ons/nip47.rs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use std::collections::HashSet;
use std::string::ToString;

use anyhow::{anyhow, Result};
use hex::ToHex;
use lightning_invoice::Bolt11Invoice;
use ln_sdk::client_v2::lnd::{LndClient, LndConfig};
use ln_sdk::client_v2::CanPayInvoices;
use ln_sdk::http::HttpConfig;
use nostr::nips::nip47::*;
use nostr::prelude::{decrypt, encrypt, FromSkStr};
use nostr::{Event, EventBuilder, Filter, Keys, Kind, Tag, Timestamp};
use rocket_db_pools::Connection;
use serde::{Deserialize, Serialize};
use serde_json::{from_str, to_string};
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;
use utoipa::ToSchema;












|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use std::collections::HashSet;
use std::string::ToString;

use anyhow::{anyhow, Result};
use hex::ToHex;
use lightning_invoice::Bolt11Invoice;
use ln_sdk::client_v2::lnd::{LndClient, LndConfig};
use ln_sdk::client_v2::CanPayInvoices;
use ln_sdk::http::HttpConfig;
use nostr::nips::nip47::*;
use nostr::prelude::{decrypt, encrypt, FromSkStr};
use nostr::{Event, EventBuilder, Filter, JsonUtil, Keys, Kind, Tag, Timestamp};
use rocket_db_pools::Connection;
use serde::{Deserialize, Serialize};
use serde_json::{from_str, to_string};
use sqlx::pool::PoolConnection;
use sqlx::Sqlite;
use utoipa::ToSchema;

102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
    async fn on_startup(
        &self,
        ctx: &mut NostrContext,
        _spo: &StartupParamsOptional,
        _db: &mut PoolConnection<Sqlite>,
    ) -> Result<()> {
        let info_ev = EventBuilder::new(Kind::WalletConnectInfo, "pay_invoice", &[])
            .to_event(&ctx.client.keys())?;

        let relays = ctx.client.relays().await;
        for (relay_url, _) in relays {
            let supports_nip47 = ctx
                .test_nip47_info_event(info_ev.clone(), relay_url.clone())
                .await
                .unwrap_or(false);







|







102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
    async fn on_startup(
        &self,
        ctx: &mut NostrContext,
        _spo: &StartupParamsOptional,
        _db: &mut PoolConnection<Sqlite>,
    ) -> Result<()> {
        let info_ev = EventBuilder::new(Kind::WalletConnectInfo, "pay_invoice", &[])
            .to_event(&ctx.client_keys_cached)?;

        let relays = ctx.client.relays().await;
        for (relay_url, _) in relays {
            let supports_nip47 = ctx
                .test_nip47_info_event(info_ev.clone(), relay_url.clone())
                .await
                .unwrap_or(false);
165
166
167
168
169
170
171
172

173
174
175
176
177
178
179
            // Derived from secret part of NWC info
            // The pubkey part of the NWC info is this client's pubkey
            let matching_keys = nwc_secrets.iter().find(|&k| k.public_key() == event.pubkey);
            match matching_keys {
                Some(nwc_keys) => {
                    let sk = nwc_keys.secret_key()?;

                    let decrypted = decrypt(&sk, &ctx.client.keys().public_key(), &event.content)?;


                    let req = Request::from_json(&decrypted)?;
                    match (req.method, req.params) {
                        (
                            Method::PayInvoice,
                            RequestParams::PayInvoice(PayInvoiceRequestParams { invoice }),
                        ) => {







|
>







165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
            // Derived from secret part of NWC info
            // The pubkey part of the NWC info is this client's pubkey
            let matching_keys = nwc_secrets.iter().find(|&k| k.public_key() == event.pubkey);
            match matching_keys {
                Some(nwc_keys) => {
                    let sk = nwc_keys.secret_key()?;

                    let decrypted =
                        decrypt(&sk, &ctx.client_keys_cached.public_key(), &event.content)?;

                    let req = Request::from_json(&decrypted)?;
                    match (req.method, req.params) {
                        (
                            Method::PayInvoice,
                            RequestParams::PayInvoice(PayInvoiceRequestParams { invoice }),
                        ) => {
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
                                        result: None,
                                    }
                                }
                            };

                            let resp_json_decrypted = resp.as_json();
                            let resp_json_encrypted = encrypt(
                                &ctx.client.keys().secret_key()?,
                                &nwc_keys.public_key(),
                                resp_json_decrypted,
                            )?;

                            let resp_ev = EventBuilder::new(
                                Kind::WalletConnectResponse,
                                resp_json_encrypted,
                                &[
                                    Tag::Event(event.id, None, None),
                                    Tag::PubKey(nwc_keys.public_key(), None),
                                ],
                            )
                            .to_event(&ctx.client.keys())?;

                            ctx.client.send_event(resp_ev).await?;
                            info!("Sent NWC response event");
                        }
                        _ => {
                            // TODO Handle new methods
                            // TODO Show errors for invalid method / request combos







|












|







205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
                                        result: None,
                                    }
                                }
                            };

                            let resp_json_decrypted = resp.as_json();
                            let resp_json_encrypted = encrypt(
                                &ctx.client_keys_cached.secret_key()?,
                                &nwc_keys.public_key(),
                                resp_json_decrypted,
                            )?;

                            let resp_ev = EventBuilder::new(
                                Kind::WalletConnectResponse,
                                resp_json_encrypted,
                                &[
                                    Tag::Event(event.id, None, None),
                                    Tag::PubKey(nwc_keys.public_key(), None),
                                ],
                            )
                            .to_event(&ctx.client_keys_cached)?;

                            ctx.client.send_event(resp_ev).await?;
                            info!("Sent NWC response event");
                        }
                        _ => {
                            // TODO Handle new methods
                            // TODO Show errors for invalid method / request combos

Changes to src/bot/ons/ons1.rs.

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
100
101
102
103
            .kind(Kind::EncryptedDirectMessage)
            .since(Timestamp::now())
            .authors(vec![ctx
                .nostr_bot_owner_nprofile
                .public_key
                .serialize()
                .encode_hex::<String>()])
            .pubkey(ctx.client.keys().public_key()))
    }

    async fn handle(
        &self,
        ctx: &mut NostrContext,
        lnd_client: &LndClient,
        event: &Event,
        _composite_params: &StartupParamsOptional,
        lnd_config: &LndConfig,
        http_config: &HttpConfig,
    ) -> Result<Option<()>> {
        if event.kind == Kind::EncryptedDirectMessage {
            // Process DMs specifically sent by the owner, ignore the rest
            if event
                .tags
                .iter()
                .any(|t| matches!(t, Tag::PubKey(pk, _) if pk == &ctx.client.keys().public_key()))
            {

                info!("Received DM from bot owner");

                let msg_from_owner = ctx.decrypt(event);
                info!("Processing command: {msg_from_owner}");

                handle_owner_msg(ctx, lnd_client, msg_from_owner, lnd_config, http_config).await;
            }







|













|
<
<
|
<
>







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
100
101
            .kind(Kind::EncryptedDirectMessage)
            .since(Timestamp::now())
            .authors(vec![ctx
                .nostr_bot_owner_nprofile
                .public_key
                .serialize()
                .encode_hex::<String>()])
            .pubkey(ctx.client_keys_cached.public_key()))
    }

    async fn handle(
        &self,
        ctx: &mut NostrContext,
        lnd_client: &LndClient,
        event: &Event,
        _composite_params: &StartupParamsOptional,
        lnd_config: &LndConfig,
        http_config: &HttpConfig,
    ) -> Result<Option<()>> {
        if event.kind == Kind::EncryptedDirectMessage {
            // Process DMs specifically sent by the owner, ignore the rest
            if event.tags.iter().any(


                |t| matches!(t, Tag::PubKey(pk, _) if pk == &ctx.client_keys_cached.public_key()),

            ) {
                info!("Received DM from bot owner");

                let msg_from_owner = ctx.decrypt(event);
                info!("Processing command: {msg_from_owner}");

                handle_owner_msg(ctx, lnd_client, msg_from_owner, lnd_config, http_config).await;
            }

Changes to src/bot/ons/ons4.rs.

73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
        Ok(Filter::new()
            .kind(Kind::Custom(API_CALL_MESSAGE_KIND))
            .since(Timestamp::now()) // ephemeral ones don't need since
            .authors(vec![deck_profile
                .public_key
                .serialize()
                .encode_hex::<String>()])
            .pubkey(ctx.client.keys().public_key()))
    }

    async fn handle(
        &self,
        ctx: &mut NostrContext,
        lnd_client: &LndClient,
        event: &Event,







|







73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
        Ok(Filter::new()
            .kind(Kind::Custom(API_CALL_MESSAGE_KIND))
            .since(Timestamp::now()) // ephemeral ones don't need since
            .authors(vec![deck_profile
                .public_key
                .serialize()
                .encode_hex::<String>()])
            .pubkey(ctx.client_keys_cached.public_key()))
    }

    async fn handle(
        &self,
        ctx: &mut NostrContext,
        lnd_client: &LndClient,
        event: &Event,
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
                    amount_sat: r.amt_paid_sat.unwrap_or_default(),
                    ts: r.settle_date,
                };
                let content_json = serde_json::to_string(&content)?;
                let event_resp_item = EventBuilder::new(
                    Kind::Custom(API_CALL_RESP_MESSAGE_KIND),
                    nip04::encrypt(
                        &ctx.client.keys().secret_key()?,
                        &deck_nprofile.public_key,
                        content_json,
                    )?,
                    &[Tag::PubKey(deck_nprofile.public_key, None)],
                )
                .to_event(&ctx.client.keys())?;

                ctx.client.send_event(event_resp_item).await?;
            }

            let event_resp_eot = EventBuilder::new(
                Kind::Custom(API_CALL_RESP_EOT_MESSAGE_KIND),
                "",
                &[Tag::PubKey(deck_nprofile.public_key, None)],
            )
            .to_event(&ctx.client.keys())?;

            ctx.client.send_event(event_resp_eot).await?;

            info!("Response sent");
        }

        Ok(None)
    }

    fn is_enabled(&self, composite_params_opt: &StartupParamsOptional) -> bool {
        composite_params_opt.ons4.enabled
    }
}







|





|









|













159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
                    amount_sat: r.amt_paid_sat.unwrap_or_default(),
                    ts: r.settle_date,
                };
                let content_json = serde_json::to_string(&content)?;
                let event_resp_item = EventBuilder::new(
                    Kind::Custom(API_CALL_RESP_MESSAGE_KIND),
                    nip04::encrypt(
                        &ctx.client_keys_cached.secret_key()?,
                        &deck_nprofile.public_key,
                        content_json,
                    )?,
                    &[Tag::PubKey(deck_nprofile.public_key, None)],
                )
                .to_event(&ctx.client_keys_cached)?;

                ctx.client.send_event(event_resp_item).await?;
            }

            let event_resp_eot = EventBuilder::new(
                Kind::Custom(API_CALL_RESP_EOT_MESSAGE_KIND),
                "",
                &[Tag::PubKey(deck_nprofile.public_key, None)],
            )
            .to_event(&ctx.client_keys_cached)?;

            ctx.client.send_event(event_resp_eot).await?;

            info!("Response sent");
        }

        Ok(None)
    }

    fn is_enabled(&self, composite_params_opt: &StartupParamsOptional) -> bool {
        composite_params_opt.ons4.enabled
    }
}