// // WalletConnect+.swift // damus // // Created by Daniel D’Aquino on 2023-11-27. // import Foundation // TODO: Eventually we should move these convenience functions into structured classes responsible for managing this type of functionality, such as `WalletModel` extension WalletConnect { /// Creates and sends a subscription to an NWC relay requesting NWC responses to be sent back. /// /// Notes: This assumes there is already a listener somewhere else /// /// - Parameters: /// - url: The Nostr Wallet Connect URL containing connection info to the NWC wallet /// - pool: The RelayPool to send the subscription request through static func subscribe(url: WalletConnectURL, pool: RelayPool) async { var filter = NostrFilter(kinds: [.nwc_response]) filter.authors = [url.pubkey] filter.pubkeys = [url.keypair.pubkey] filter.limit = 0 let sub = NostrSubscribe(filters: [filter], sub_id: "nwc") await pool.send(.subscribe(sub), to: [url.relay], skip_ephemeral: false) } /// Sends out a request to pay an invoice to the NWC relay, and ensures that: /// 1. the NWC relay is connected and we are listening to NWC events /// 2. the NWC relay is connected and we are listening to NWC /// /// Note: This does not return information about whether the payment is succesful or not. The actual confirmation is handled elsewhere around `HomeModel` and `WalletModel` /// /// - Parameters: /// - url: The NWC wallet connection URL /// - pool: The relay pool to connect to /// - post: The postbox to send events in /// - delay: The delay before actually sending the request to the network _(this makes it possible to cancel a zap)_ /// - on_flush: A callback to call after the event has been flushed to the network /// - Returns: The Nostr Event that was sent to the network, representing the request that was made @discardableResult static func pay(url: WalletConnectURL, pool: RelayPool, post: PostBox, invoice: String, zap_request: NostrEvent?, delay: TimeInterval? = 5.0, on_flush: OnFlush? = nil) async -> NostrEvent? { let req = WalletConnect.Request.payZapRequest(invoice: invoice, zapRequest: zap_request) guard let ev = req.to_nostr_event(to_pk: url.pubkey, keypair: url.keypair) else { return nil } try? await pool.add_relay(.nwc(url: url.relay)) // Ensure the NWC relay is connected await WalletConnect.subscribe(url: url, pool: pool) // Ensure we are listening to NWC updates from the relay await post.send(ev, to: [url.relay], skip_ephemeral: false, delay: delay, on_flush: on_flush) return ev } static func handle_zap_success(state: DamusState, resp: WalletConnect.FullWalletResponse) { // find the pending zap and mark it as pending-confirmed for kv in state.zaps.our_zaps { let zaps = kv.value for zap in zaps { guard case .pending(let pzap) = zap, case .nwc(let nwc_state) = pzap.state, case .postbox_pending(let nwc_req) = nwc_state.state, nwc_req.id == resp.req_id else { continue } if nwc_state.update_state(state: .confirmed) { // notify the zaps model of an update so it can mark them as paid state.events.get_cache_data(NoteId(pzap.target.id)).zaps_model.objectWillChange.send() print("NWC success confirmed") } return } } } /// Handles a received Nostr Wallet Connect error static func handle_error(zapcache: Zaps, evcache: EventCache, resp: WalletConnect.FullWalletResponse) { // find a pending zap with the nwc request id associated with this response and remove it for kv in zapcache.our_zaps { let zaps = kv.value for zap in zaps { guard case .pending(let pzap) = zap, case .nwc(let nwc_state) = pzap.state, case .postbox_pending(let req) = nwc_state.state, req.id == resp.req_id else { continue } // remove the pending zap if there was an error let reqid = ZapRequestId(from_pending: pzap) remove_zap(reqid: reqid, zapcache: zapcache, evcache: evcache) return } } } }