damus

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

Response.swift (5694B)


      1 //
      2 //  Response.swift
      3 //  damus
      4 //
      5 //  Created by Daniel D’Aquino on 2025-03-10.
      6 //
      7 
      8 import Combine
      9 
     10 extension WalletConnect {
     11     /// Models a response from the NWC provider
     12     struct Response: Decodable {
     13         let result_type: Response.Result.ResultType
     14         let error: WalletResponseErr?
     15         let result: Response.Result?
     16         
     17         private enum CodingKeys: CodingKey {
     18             case result_type, error, result
     19         }
     20         
     21         init(from decoder: Decoder) throws {
     22             let container = try decoder.container(keyedBy: CodingKeys.self)
     23             let result_type_str = try container.decode(String.self, forKey: .result_type)
     24             
     25             guard let result_type = Response.Result.ResultType(rawValue: result_type_str) else {
     26                 throw DecodingError.typeMismatch(Response.Result.ResultType.self, .init(codingPath: decoder.codingPath, debugDescription: "result_type \(result_type_str) is unknown"))
     27             }
     28             
     29             self.result_type = result_type
     30             self.error = try container.decodeIfPresent(WalletResponseErr.self, forKey: .error)
     31             
     32             guard self.error == nil else {
     33                 self.result = nil
     34                 return
     35             }
     36             
     37             switch result_type {
     38             case .pay_invoice:
     39                 let res = try container.decode(Result.PayInvoiceResponse.self, forKey: .result)
     40                 self.result = .pay_invoice(res)
     41             case .get_balance:
     42                 let res = try container.decode(Result.GetBalanceResponse.self, forKey: .result)
     43                 self.result = .get_balance(res)
     44             case .list_transactions:
     45                 let res = try container.decode(Result.ListTransactionsResponse.self, forKey: .result)
     46                 self.result = .list_transactions(res)
     47             }
     48         }
     49     }
     50     
     51     struct FullWalletResponse {
     52         let req_id: NoteId
     53         let response: Response
     54         
     55         init(from event: NostrEvent, nwc: WalletConnect.ConnectURL) throws(InitializationError) {
     56             guard event.pubkey == nwc.pubkey else { throw .incorrectAuthorPubkey }
     57             
     58             guard let referencedNoteId = event.referenced_ids.first else { throw .missingRequestIdReference }
     59 
     60             self.req_id = referencedNoteId
     61             
     62             var json = ""
     63             do {
     64                 json = try NIP04.decryptContent(
     65                     recipientPrivateKey: nwc.keypair.privkey,
     66                     senderPubkey: nwc.pubkey,
     67                     content: event.content,
     68                     encoding: .base64
     69                 )
     70             }
     71             catch { throw .failedToDecrypt(error) }
     72             
     73             do {
     74                 let response: WalletConnect.Response = try decode_json_throwing(json)
     75                 self.response = response
     76             }
     77             catch { throw .failedToDecodeJSON(error) }
     78         }
     79         
     80         enum InitializationError: Error {
     81             case incorrectAuthorPubkey
     82             case missingRequestIdReference
     83             case failedToDecodeJSON(any Error)
     84             case failedToDecrypt(any Error)
     85         }
     86     }
     87     
     88     struct WalletResponseErr: Codable, Error {
     89         let code: Code?
     90         let message: String?
     91 
     92         enum Code: String, Codable {
     93             /// The client is sending commands too fast. It should retry in a few seconds.
     94             case rateLimited = "RATE_LIMITED"
     95             /// The command is not known or is intentionally not implemented.
     96             case notImplemented = "NOT_IMPLEMENTED"
     97             /// The wallet does not have enough funds to cover a fee reserve or the payment amount.
     98             case insufficientBalance = "INSUFFICIENT_BALANCE"
     99             /// The wallet has exceeded its spending quota.
    100             case quotaExceeded = "QUOTA_EXCEEDED"
    101             /// This public key is not allowed to do this operation.
    102             case restricted = "RESTRICTED"
    103             /// This public key has no wallet connected.
    104             case unauthorized = "UNAUTHORIZED"
    105             /// An internal error.
    106             case internalError = "INTERNAL"
    107             /// Other error.
    108             case other = "OTHER"
    109         }
    110 
    111         enum CodingKeys: String, CodingKey {
    112             case code, message
    113         }
    114 
    115         init(from decoder: Decoder) throws {
    116             let container = try decoder.container(keyedBy: CodingKeys.self)
    117             
    118             // Attempt to decode the code as a String
    119             if let codeString = try container.decodeIfPresent(String.self, forKey: .code),
    120                let validCode = Code(rawValue: codeString) {
    121                 self.code = validCode
    122             } else {
    123                 // If the code is either missing or not one of the allowed cases, set it to nil
    124                 self.code = nil
    125             }
    126             
    127             self.message = try container.decodeIfPresent(String.self, forKey: .message)
    128         }
    129     }
    130 }
    131 
    132 extension WalletConnect.Response {
    133     /// The response data resulting from an NWC request
    134     enum Result {
    135         case pay_invoice(PayInvoiceResponse)
    136         case get_balance(GetBalanceResponse)
    137         case list_transactions(ListTransactionsResponse)
    138         
    139         enum ResultType: String {
    140             case pay_invoice
    141             case get_balance
    142             case list_transactions
    143         }
    144         
    145         struct PayInvoiceResponse: Decodable {
    146             let preimage: String
    147         }
    148 
    149         struct GetBalanceResponse: Decodable {
    150             let balance: Int64
    151         }
    152 
    153         struct ListTransactionsResponse: Decodable {
    154             let transactions: [WalletConnect.Transaction]
    155         }
    156     }
    157 }