This repository has been archived by the owner on Feb 8, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCAPI.swift
297 lines (258 loc) · 10 KB
/
CAPI.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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
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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
//
// CAPI.swift
// MyCitadelKit
//
// Created by Maxim Orlovsky on 1/31/21.
//
import os
import Foundation
import Combine
struct ContractJson: Codable {
let id: String
let name: String
let chain: BitcoinNetwork
let policy: Policy
}
public enum Policy {
case current(String)
}
extension Policy {
var descriptor: String {
switch self {
case .current(let descriptor): return descriptor
}
}
}
extension Policy: Codable {
enum CodingKeys: CodingKey {
case current
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if container.contains(.current) {
let value = try container.decode(String.self, forKey: .current)
self = .current(value)
} else {
throw DecodingError.typeMismatch(String.self, DecodingError.Context(codingPath: [CodingKeys.current], debugDescription: "string value expected"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .current(let value):
try container.encode(value, forKey: .current)
}
}
}
struct ContractDataJson: Codable {
let blindingFactors: [String: OutPoint]
let sentInvoices: [String]
let unpaidInvoices: [String: Date]
let p2cTweaks: [TweakedOutpoint]
}
struct UTXOJson: Codable {
let height: Int32
let offset: UInt32
let txid: String
let vout: UInt16
let value: UInt64
let derivationIndex: UInt32
let address: String?
private enum CodingKeys: String, CodingKey {
case height, offset, txid, vout, value, derivationIndex = "derivation_index", address
}
}
public struct RGB20Json: Codable {
public let genesis: String
public let id: String
public let ticker: String
public let name: String
public let description: String?
public let decimalPrecision: UInt8
public let date: String
public let knownCirculating: UInt64
public let issueLimit: UInt64?
}
public struct Transfer {
public let psbt: String
public let consignment: String?
}
public enum Validity {
case valid
case unresolvedTransactions
case invalid
}
public struct ValidationStatus {
public let validity: Validity
public let info: [String]
public let warnings: [String]
public let failures: [String]
}
extension CitadelVault {
public func lastError() -> CitadelError? {
if citadel_has_err(rpcClient) {
return CitadelError(errNo: Int(rpcClient.pointee.err_no), message: String(cString: rpcClient.pointee.message))
} else {
return nil
}
}
private func processResponseToString(_ response: UnsafePointer<Int8>?) throws -> String {
guard let response = response else {
guard let err = lastError() else {
throw CitadelError("MyCitadel C API is broken")
}
throw err
}
var string = String(cString: response)
// TODO: Remove this debug printing
print(string)
string.reserveCapacity(string.count * 2)
release_string(UnsafeMutablePointer(mutating: response))
return string
}
private func processResponse(_ response: UnsafePointer<Int8>?) throws -> Data {
try Data(processResponseToString(response).utf8)
}
}
extension WalletContract {
internal func parseDescriptor() throws -> DescriptorInfo {
let info = lnpbp_descriptor_parse(policy.descriptor)
defer {
result_destroy(info)
}
if !is_success(info) {
let errorMessage = String(cString: info.details.error)
print("Error parsing address: \(errorMessage)")
throw CitadelError(errorMessage)
}
let jsonString = String(cString: info.details.data)
let jsonData = Data(jsonString.utf8)
let decoder = JSONDecoder();
print("Parsing JSON descriptor data: \(jsonString)")
do {
return try decoder.decode(DescriptorInfo.self, from: jsonData)
} catch {
print("Error parsing descriptor: \(error.localizedDescription)")
throw error
}
}
}
extension CitadelVault {
internal func create(singleSig derivation: String, name: String, descriptorType: DescriptorType) throws -> ContractJson {
print("Creating seed")
try createSeed()
let pubkeyChain = try createScopedChain(derivation: derivation)
let response = citadel_single_sig_create(rpcClient, name, pubkeyChain, descriptorType.cDescriptorType());
return try JSONDecoder().decode(ContractJson.self, from: processResponse(response))
}
internal func listContracts() throws -> [ContractJson] {
print("Listing contracts")
let response = citadel_contract_list(rpcClient)
return try JSONDecoder().decode([ContractJson].self, from: processResponse(response))
}
internal func operations(walletId: String) throws -> [TransferOperation] {
print("Listing operations")
let response = citadel_contract_operations(rpcClient, walletId)
return try JSONDecoder().decode([TransferOperation].self, from: processResponse(response))
}
internal func balance(walletId: String) throws -> [String: [UTXOJson]] {
print("Requesting balance for \(walletId)")
let response = citadel_contract_balance(rpcClient, walletId, true, 20)
return try JSONDecoder().decode([String: [UTXOJson]].self, from: processResponse(response))
}
internal func listAssets() throws -> [RGB20Json] {
print("Listing assets")
let response = citadel_asset_list(rpcClient);
return try JSONDecoder().decode([RGB20Json].self, from: processResponse(response))
}
internal func importRGB(genesisBech32 genesis: String) throws -> RGB20Json {
print("Importing RGB asset")
let response = citadel_asset_import(rpcClient, genesis);
return try JSONDecoder().decode(RGB20Json.self, from: processResponse(response))
}
public func nextAddress(forContractId contractId: String, useLegacySegWit legacy: Bool = false) throws -> AddressDerivation {
print("Generating next avaliable address")
let response = citadel_address_create(rpcClient, contractId, false, legacy)
return try JSONDecoder().decode(AddressDerivation.self, from: processResponse(response))
}
internal func usedAddresses(forContractId contractId: String) throws -> [AddressDerivation] {
print("Listing used addresses")
let response = citadel_address_list(rpcClient, contractId, false, 0)
return try JSONDecoder().decode([String: UInt32].self, from: processResponse(response))
.map { (address, index) in AddressDerivation(address: address, derivation: [index]) }
}
internal func invoice(usingFormat format: InvoiceType, receiveTo contractId: String, nominatedIn assetId: String?, value: UInt64?, from merchant: String? = nil, purpose: String? = nil, useLegacySegWit legacy: Bool = false) throws -> String {
print("Creating invoice")
let invoice = citadel_invoice_create(rpcClient, format.cType(), contractId, assetId ?? nil, value ?? 0, merchant ?? nil, purpose ?? nil, false, legacy)
return try processResponseToString(invoice)
}
/*
public func mark(addressUnused address: String) throws {
citadel_mark
}
public func mark(invoiceUnused invoice: String) throws {
try vault.mark(invoice: invoice, used: used)
}
*/
internal func pay(from contractId: String, invoice: String, value: UInt64? = nil, fee: UInt64, giveaway: UInt64? = nil) throws -> Transfer {
print("Paying invoice")
let transfer = citadel_invoice_pay(rpcClient, contractId, invoice, value ?? 0, fee, giveaway ?? 0)
if !transfer.success {
guard let err = lastError() else {
throw CitadelError("MyCitadel C API is broken")
}
throw err
}
let psbt = String(cString: transfer.psbt_base64)
release_string(UnsafeMutablePointer(mutating: transfer.psbt_base64))
let consignment: String?
if transfer.consignment_bech32 != nil {
consignment = String(cString: transfer.consignment_bech32)
release_string(UnsafeMutablePointer(mutating: transfer.consignment_bech32))
} else {
consignment = nil
}
return Transfer(psbt: psbt, consignment: consignment)
}
internal func publish(psbt: String) throws -> String {
print("Signing and publishing transaction")
let txid = citadel_psbt_publish(rpcClient, psbt)
return try processResponseToString(txid)
}
internal func accept(consignment: String) throws -> ValidationStatus {
print("Accepting consignment")
let status = citadel_invoice_accept(rpcClient, consignment)
print("Status: \(status)")
if status.validity == VALIDITY_T_UNABLE_TO_VALIDATE {
guard let err = lastError() else {
throw CitadelError("MyCitadel C API is broken")
}
throw err
}
var info = [String]()
for i in 0..<status.info_len {
if let item = status.info[Int(i)] {
info.append(String(cString: item))
}
}
var warn = [String]()
for i in 0..<status.warn_len {
if let item = status.info[Int(i)] {
warn.append(String(cString: item))
}
}
var failures = [String]()
for i in 0..<status.failures_len {
if let item = status.info[Int(i)] {
failures.append(String(cString: item))
}
}
var validity = Validity.invalid;
switch status.validity {
case VALIDITY_T_VALID: validity = .valid
case VALIDITY_T_UNABLE_TO_VALIDATE: validity = .unresolvedTransactions
default: validity = .invalid
}
return ValidationStatus(validity: validity, info: info, warnings: warn, failures: failures)
}
}