Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | fix(nwc): Include client pubkey tag in NWC Response |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
f19b421d5cd900d78dfcc6561ff2ffad |
User & Date: | carlos 2023-09-21 20:06:11 |
Context
2023-09-23
| ||
06:30 | ref: Bump dependencies check-in: 8910740ab9 user: carlos tags: trunk | |
2023-09-21
| ||
20:06 | fix(nwc): Include client pubkey tag in NWC Response check-in: f19b421d5c user: carlos tags: trunk | |
2023-09-20
| ||
18:59 | nit(ui): Use npubs in Nostr bot UI check-in: dec188c782 user: carlos tags: trunk | |
Changes
Changes to CHANGELOG.md.
︙ | ︙ | |||
8 9 10 11 12 13 14 15 16 17 18 19 20 21 | Changed * LNURL-pay: Removed deprecated description hash validation * Nostr: When using more relays, latency is used to determine best relay * Nostr: Improve connection reliability and choice of default relay during NIP-47 Wallet Connect setup --- ## [v0.3.7 (2023-06-20)](/timeline?p=v0.3.7&bt=v0.3.6) Added * Nostr: Support for NIP-47 Nostr Wallet Connect | > > > > | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | Changed * LNURL-pay: Removed deprecated description hash validation * Nostr: When using more relays, latency is used to determine best relay * Nostr: Improve connection reliability and choice of default relay during NIP-47 Wallet Connect setup Fixed * Nostr: NWC responses include the client pubkey tag, allowing clients like Amethyst to show confirmations for successful NWC operations --- ## [v0.3.7 (2023-06-20)](/timeline?p=v0.3.7&bt=v0.3.6) Added * Nostr: Support for NIP-47 Nostr Wallet Connect |
︙ | ︙ |
Changes to src/bot/nostr.rs.
︙ | ︙ | |||
37 38 39 40 41 42 43 44 45 46 47 48 49 50 | // For new entries: update get_env_figment() ] }) } #[derive(Clone)] pub struct NostrContext { 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> { | > | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | // For new entries: update get_env_figment() ] }) } #[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> { |
︙ | ︙ |
Changes to src/bot/ons/nip47.rs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | use crate::bot::nostr::NostrContext; use crate::bot::ons::Ons; use crate::model::api::ApiError::GenericError; use crate::model::api::ApiErrorLogged; use crate::model::bot::nostr::OnsMetadata; use crate::params::StartupParamsOptional; use crate::Db; use anyhow::{anyhow, Result}; use hex::ToHex; use ln_sdk::client::lnd::{LndClient, LndClientOps, LndConfig}; use ln_sdk::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}; | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | use crate::bot::nostr::NostrContext; use crate::bot::ons::Ons; use crate::model::api::ApiError::GenericError; use crate::model::api::ApiErrorLogged; use crate::model::bot::nostr::OnsMetadata; use crate::params::StartupParamsOptional; use crate::Db; use anyhow::{anyhow, Result}; use hex::ToHex; use lightning_invoice::Bolt11Invoice; use ln_sdk::client::lnd::{LndClient, LndClientOps, LndConfig}; use ln_sdk::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}; |
︙ | ︙ | |||
69 70 71 72 73 74 75 76 77 78 79 80 81 82 | // TODO Allowance (once this runs out, disable) // TODO Allowance per day // TODO Onetime use // TODO Max per payment // TODO Max routing fee // TODO Optional description } pub(crate) struct Nip47 {} #[async_trait] impl Ons for Nip47 { fn id(&self) -> String { ID.to_string() | > > > > > > > > > > > > > | 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 | // TODO Allowance (once this runs out, disable) // TODO Allowance per day // TODO Onetime use // TODO Max per payment // TODO Max routing fee // TODO Optional description } /// Get [Keys] built from all available NWC secrets fn available_nwc_secrets(spo: &StartupParamsOptional) -> Result<Vec<Keys>> { // Find pks associated with NWC secrets // They are the pks from which NWC requests will originate let res: Vec<Keys> = get_inner_config(spo)? .secrets .iter() .flat_map(|s| Keys::from_sk_str(&s.secret)) .collect::<Vec<Keys>>(); Ok(res) } pub(crate) struct Nip47 {} #[async_trait] impl Ons for Nip47 { fn id(&self) -> String { ID.to_string() |
︙ | ︙ | |||
115 116 117 118 119 120 121 | enabled: self.is_enabled(composite_params_opt), } } fn filter(&self, _ctx: &NostrContext, spo: &StartupParamsOptional) -> Result<Filter> { info!("Preparing subscription to NIP-47 Nostr Wallet Connect calls by anyone with a valid connection string"); | | | | < | < | > > | > > > > | | | < | | | | | | | | > > > > > | > > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > | > > | | | | | | | | | | | > > > | 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 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 196 197 198 199 200 201 202 203 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 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | enabled: self.is_enabled(composite_params_opt), } } fn filter(&self, _ctx: &NostrContext, spo: &StartupParamsOptional) -> Result<Filter> { info!("Preparing subscription to NIP-47 Nostr Wallet Connect calls by anyone with a valid connection string"); // Find pks associated with NWC secrets // They are the pks from which NWC requests will originate let pks: Vec<String> = available_nwc_secrets(spo)? .into_iter() .map(|keys| keys.public_key()) .map(|pk| pk.serialize().encode_hex::<String>()) .collect::<Vec<String>>(); Ok(Filter::new() .kind(Kind::WalletConnectRequest) .since(Timestamp::now()) .authors(pks)) } async fn handle( &self, ctx: &mut NostrContext, lnd_client: &LndClient, event: &Event, spo: &StartupParamsOptional, _lnd_config: &LndConfig, _http_config: &HttpConfig, ) -> Result<Option<()>> { let nwc_secrets = available_nwc_secrets(spo)?; if let Kind::WalletConnectRequest = event.kind { // 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 }), ) => { let resp = match lnd_client.pay_invoice_simple(&invoice).await { Ok(preimage_base64) => { let preimage_decoded = base64::decode(preimage_base64)?; let preimage = hex::encode(preimage_decoded); let invoice_parsed = invoice.parse::<Bolt11Invoice>()?; info!("Payment was successful, amt={:?} msat, preimage: {preimage}", invoice_parsed.amount_milli_satoshis(), ); Response { result_type: Method::PayInvoice, error: None, result: Some(ResponseResult::PayInvoice( PayInvoiceResponseResult { preimage }, )), } } Err(e) => { warn!("Payment failed: {e}"); Response { result_type: Method::PayInvoice, error: Some(NIP47Error { code: ErrorCode::Other, message: e.to_string(), }), 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 } } Ok(Some(())) } None => Ok(None), } } else { Ok(None) } } fn is_enabled(&self, composite_params_opt: &StartupParamsOptional) -> bool { composite_params_opt.nip47.enabled } } |