damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

WalletConnect.swift (4851B)


      1 //
      2 //  WalletConnect.swift
      3 //  damus
      4 //
      5 //  Created by William Casarin on 2023-03-22.
      6 //
      7 
      8 import Foundation
      9 
     10 struct WalletConnectURL: Equatable {
     11     static func == (lhs: WalletConnectURL, rhs: WalletConnectURL) -> Bool {
     12         return lhs.keypair == rhs.keypair &&
     13                 lhs.pubkey == rhs.pubkey &&
     14                 lhs.relay == rhs.relay
     15     }
     16     
     17     let relay: RelayURL
     18     let keypair: FullKeypair
     19     let pubkey: Pubkey
     20     let lud16: String?
     21     
     22     func to_url() -> URL {
     23         var urlComponents = URLComponents()
     24         urlComponents.scheme = "nostrwalletconnect"
     25         urlComponents.host = pubkey.hex()
     26         urlComponents.queryItems = [
     27             URLQueryItem(name: "relay", value: relay.absoluteString),
     28             URLQueryItem(name: "secret", value: keypair.privkey.hex())
     29         ]
     30 
     31         if let lud16 {
     32             urlComponents.queryItems?.append(URLQueryItem(name: "lud16", value: lud16))
     33         }
     34 
     35         return urlComponents.url!
     36     }
     37     
     38     init?(str: String) {
     39         guard let components = URLComponents(string: str),
     40               components.scheme == "nostrwalletconnect" || components.scheme == "nostr+walletconnect",
     41               // The line below provides flexibility for both `nostrwalletconnect://` (non-compliant, but commonly used) and `nostrwalletconnect:` (NIP-47 compliant) formats
     42               let encoded_pubkey = components.path == "" ? components.host : components.path,
     43               let pubkey = hex_decode_pubkey(encoded_pubkey),
     44               let items = components.queryItems,
     45               let relay = items.first(where: { qi in qi.name == "relay" })?.value,
     46               let relay_url = RelayURL(relay),
     47               let secret = items.first(where: { qi in qi.name == "secret" })?.value,
     48               secret.utf8.count == 64,
     49               let decoded = hex_decode(secret)
     50         else {
     51             return nil
     52         }
     53 
     54         let privkey = Privkey(Data(decoded))
     55         guard let our_pk = privkey_to_pubkey(privkey: privkey) else { return nil }
     56 
     57         let lud16 = items.first(where: { qi in qi.name == "lud16" })?.value
     58         let keypair = FullKeypair(pubkey: our_pk, privkey: privkey)
     59         self = WalletConnectURL(pubkey: pubkey, relay: relay_url, keypair: keypair, lud16: lud16)
     60     }
     61     
     62     init(pubkey: Pubkey, relay: RelayURL, keypair: FullKeypair, lud16: String?) {
     63         self.pubkey = pubkey
     64         self.relay = relay
     65         self.keypair = keypair
     66         self.lud16 = lud16
     67     }
     68 }
     69 
     70 struct WalletRequest<T: Codable>: Codable {
     71     let method: String
     72     let params: T?
     73 }
     74 
     75 struct WalletResponseErr: Codable {
     76     let code: String?
     77     let message: String?
     78 }
     79 
     80 struct PayInvoiceResponse: Decodable {
     81     let preimage: String
     82 }
     83 
     84 enum WalletResponseResultType: String {
     85     case pay_invoice
     86 }
     87 
     88 enum WalletResponseResult {
     89     case pay_invoice(PayInvoiceResponse)
     90 }
     91 
     92 struct FullWalletResponse {
     93     let req_id: NoteId
     94     let response: WalletResponse
     95     
     96     init?(from: NostrEvent, nwc: WalletConnectURL) async {
     97         guard let note_id = from.referenced_ids.first else {
     98             return nil
     99         }
    100 
    101         self.req_id = note_id
    102 
    103         let ares = Task {
    104             guard let json = decrypt_dm(nwc.keypair.privkey, pubkey: nwc.pubkey, content: from.content, encoding: .base64),
    105                   let resp: WalletResponse = decode_json(json)
    106             else {
    107                 let resp: WalletResponse? = nil
    108                 return resp
    109             }
    110             
    111             return resp
    112         }
    113         
    114         guard let res = await ares.value else {
    115             return nil
    116         }
    117             
    118         self.response = res
    119     }
    120     
    121 }
    122 
    123 struct WalletResponse: Decodable {
    124     let result_type: WalletResponseResultType
    125     let error: WalletResponseErr?
    126     let result: WalletResponseResult?
    127     
    128     private enum CodingKeys: CodingKey {
    129         case result_type, error, result
    130     }
    131     
    132     init(from decoder: Decoder) throws {
    133         let container = try decoder.container(keyedBy: CodingKeys.self)
    134         let result_type_str = try container.decode(String.self, forKey: .result_type)
    135         
    136         guard let result_type = WalletResponseResultType(rawValue: result_type_str) else {
    137             throw DecodingError.typeMismatch(WalletResponseResultType.self, .init(codingPath: decoder.codingPath, debugDescription: "result_type \(result_type_str) is unknown"))
    138         }
    139         
    140         self.result_type = result_type
    141         self.error = try container.decodeIfPresent(WalletResponseErr.self, forKey: .error)
    142         
    143         guard self.error == nil else {
    144             self.result = nil
    145             return
    146         }
    147         
    148         switch result_type {
    149         case .pay_invoice:
    150             let res = try container.decode(PayInvoiceResponse.self, forKey: .result)
    151             self.result = .pay_invoice(res)
    152         }
    153     }
    154 }
    155