-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathModel.swift
254 lines (218 loc) · 8.06 KB
/
Model.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
//
// Model.swift
//
//
// Created by Pacu on 2023-11-07.
//
import Foundation
public struct PaymentRequest: Equatable {
public let payments: [Payment]
}
/// A Single payment that will be requested
public struct Payment: Equatable {
/// Recipient of the payment.
public let recipientAddress: RecipientAddress
/// The amount of the payment expressed in decimal ZEC
public let amount: Amount
/// bytes of the ZIP-302 Memo if present. Payments to addresses that are not shielded should be reported as erroneous by wallets.
public let memo: MemoBytes?
/// A human-readable label for this payment within the larger structure of the transaction request.
/// this will be pct-encoded
public let label: String?
/// A human-readable message to be displayed to the user describing the purpose of this payment.
public let message: String?
/// A list of other arbitrary key/value pairs associated with this payment.
public let otherParams: [OtherParam]?
/// Initializes a Payment struct. validation of the whole payment is deferred to the ZIP-321 serializer.
/// - parameter recipientAddress: a valid Zcash recipient address
/// - parameter amount: a valid `Amount`
/// - parameter memo: valid `MemoBytes`
/// - parameter label: a label that wallets might show to their users as a way to label this payment.
/// Will not be included in the blockchain
/// - parameter message: a message that wallets might show to their users as part of this payment.
/// Will not be included in the blockchain
/// - parameter otherParams: other parameters that you'd like to define. See ZIP-321 for more
/// information about these parameters.
public init(
recipientAddress: RecipientAddress,
amount: Amount,
memo: MemoBytes?,
label: String?,
message: String?,
otherParams: [OtherParam]?
) {
self.recipientAddress = recipientAddress
self.amount = amount
self.memo = memo
self.label = label
self.message = message
self.otherParams = otherParams
}
public static func == (lhs: Payment, rhs: Payment) -> Bool {
lhs.amount == rhs.amount &&
lhs.label == rhs.label &&
lhs.memo == rhs.memo &&
lhs.message == rhs.message &&
lhs.recipientAddress == rhs.recipientAddress &&
lhs.otherParams == rhs.otherParams
}
}
public struct OtherParam: Equatable {
public let key: String
public let value: String
}
public struct MemoBytes: Equatable {
public enum MemoError: Error {
case memoTooLong
case memoEmpty
case notUTF8String
case invalidBase64URL
}
public let maxLength = 512
let data: Data
public init(bytes: [UInt8]) throws {
guard !bytes.isEmpty else {
throw MemoError.memoEmpty
}
guard bytes.count <= maxLength else {
throw MemoError.memoTooLong
}
self.data = Data(bytes)
}
public init(utf8String: String) throws {
guard !utf8String.isEmpty else {
throw MemoError.memoEmpty
}
guard let memoStringData = utf8String.data(using: .utf8) else {
throw MemoError.notUTF8String
}
guard memoStringData.count <= maxLength else {
throw MemoError.memoTooLong
}
self.data = memoStringData
}
public init(base64URL: String) throws {
var base64 = base64URL.replacingOccurrences(of: "_", with: "/")
.replacingOccurrences(of: "-", with: "+")
if base64.utf8.count % 4 != 0 {
base64.append(
String(repeating: "=", count: 4 - base64.utf8.count % 4)
)
}
guard let data = Data(base64Encoded: base64) else {
throw MemoBytes.MemoError.invalidBase64URL
}
try self.init(bytes: [UInt8](data))
}
/// Conversion of the present bytes to Base64URL
/// - Notes: According to https://en.wikipedia.org/wiki/Base64#Variants_summary_table
public func toBase64URL() -> String {
self.data.base64EncodedString()
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "=", with: "")
}
}
public extension MemoBytes {
var memoData: Data {
self.data
}
}
extension NumberFormatter {
static let zcashNumberFormatter: NumberFormatter = {
var formatter = NumberFormatter()
formatter.maximumFractionDigits = 8
formatter.maximumIntegerDigits = 8
formatter.numberStyle = .decimal
formatter.usesGroupingSeparator = false
formatter.decimalSeparator = "."
formatter.roundingMode = .halfUp
return formatter
}()
}
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: Amount) {
appendLiteral(value.toString())
}
}
extension String {
/// Encode this string as qchar.
/// As defined on ZIP-321
/// qchar = unreserved / pct-encoded / allowed-delims / ":" / "@"
/// allowed-delims = "!" / "$" / "'" / "(" / ")" / "*" / "+" / "," / ";"
///
/// from RPC-3968: https://www.rfc-editor.org/rfc/rfc3986.html#appendix-A
/// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
/// pct-encoded = "%" HEXDIG HEXDIG
func qcharEncoded() -> String? {
let qcharEncodeAllowed = CharacterSet.qchar.subtracting(.qcharComplement)
return self.addingPercentEncoding(withAllowedCharacters: qcharEncodeAllowed)
}
func qcharDecode() -> String? {
self.removingPercentEncoding
}
}
extension CharacterSet {
/// All ASCII characters from 0 to 127
static let ASCIICharacters = CharacterSet(
charactersIn: UnicodeScalar(0) ... UnicodeScalar(127)
)
/// ASCII Alphabetic
static let ASCIIAlpha = CharacterSet(
charactersIn: UnicodeScalar(65) ... UnicodeScalar(90)
).union(
CharacterSet(charactersIn: UnicodeScalar(97) ... UnicodeScalar(122))
)
/// ASCII numbers
static let ASCIINum = CharacterSet(
charactersIn: UnicodeScalar(48) ... UnicodeScalar(57)
)
/// ASCII Alphanumerics
static let ASCIIAlphaNum = ASCIIAlpha.union(.ASCIINum)
/// ASCII Hexadecimal digits
static let ASCIIHexDigits = ASCIINum.union(
CharacterSet(charactersIn: UnicodeScalar(65) ... UnicodeScalar(70))
.union(
CharacterSet(charactersIn: UnicodeScalar(97) ... UnicodeScalar(102))
)
)
/// `unreserved`character set defined on [rfc3986](https://www.rfc-editor.org/rfc/rfc3986.html#appendix-A)
static let unreserved = CharacterSet
.ASCIIAlphaNum
.union(CharacterSet(arrayLiteral: "-", ".", "_", "~"))
/// `pct-encoded` charset according to [rfc3986](https://www.rfc-editor.org/rfc/rfc3986.html#appendix-A)
static let pctEncoded = CharacterSet.ASCIIHexDigits.union(CharacterSet(charactersIn: "%"))
/// `allowed-delims` character set as defined on [ZIP-321](https://zips.z.cash/zip-0321)
static let allowedDelims = CharacterSet(charactersIn: "-._~!$'()*+,;:@%")
/// ASCII control characters from 0x00 to 0x1F
static let ASCIIControl = CharacterSet((0x00...0x1F).map { UnicodeScalar($0) })
/// All characters of qchar as defined on [ZIP-321](https://zips.z.cash/zip-0321)
static let qchar = CharacterSet()
.union(.ASCIIAlphaNum)
.union(.unreserved)
.union(.allowedDelims)
.union(CharacterSet(arrayLiteral: "@", ":"))
static let qcharComplement = CharacterSet.ASCIIControl
.union(
CharacterSet(
arrayLiteral: " ",
"\"",
"#",
"%",
"&",
"/",
"<",
"=",
">",
"?",
"[",
"\\",
"]",
"^",
"`",
"{",
"|",
"}"
)
)
}