@authon/react-native-wallet 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +221 -2
  2. package/android/build.gradle +40 -0
  3. package/android/consumer-rules.pro +10 -0
  4. package/android/src/main/AndroidManifest.xml +4 -0
  5. package/android/src/main/java/dev/authon/reactnative/wallet/AuthonWalletModule.kt +181 -0
  6. package/android/src/vendor/GENERATED.md +4 -0
  7. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/AuthonWallet.kt +191 -0
  8. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Bytes.kt +74 -0
  9. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/CredentialManagerPasskeys.kt +192 -0
  10. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/DeviceStore.kt +170 -0
  11. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/DialogWalletApprover.kt +141 -0
  12. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Eip712.kt +176 -0
  13. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/EvmAccount.kt +45 -0
  14. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/EvmTransaction.kt +180 -0
  15. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/GfTables.kt +44 -0
  16. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/HdWallet.kt +130 -0
  17. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Http.kt +82 -0
  18. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/JsValues.kt +83 -0
  19. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/KeystoreDeviceStore.kt +232 -0
  20. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Prf.kt +111 -0
  21. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Proof.kt +134 -0
  22. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/Shamir.kt +106 -0
  23. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/SignSummary.kt +335 -0
  24. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletApi.kt +353 -0
  25. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletError.kt +58 -0
  26. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletOps.kt +449 -0
  27. package/android/src/vendor/kotlin/dev/authon/sdk/wallet/WalletTypes.kt +71 -0
  28. package/build/authonContext.d.ts +19 -0
  29. package/build/authonContext.d.ts.map +1 -0
  30. package/build/authonContext.js +20 -0
  31. package/build/authonContext.js.map +1 -0
  32. package/build/client.d.ts +76 -0
  33. package/build/client.d.ts.map +1 -0
  34. package/build/client.js +349 -0
  35. package/build/client.js.map +1 -0
  36. package/build/errors.d.ts +18 -0
  37. package/build/errors.d.ts.map +1 -0
  38. package/build/errors.js +9 -0
  39. package/build/errors.js.map +1 -0
  40. package/build/index.d.ts +7 -0
  41. package/build/index.d.ts.map +1 -0
  42. package/build/index.js +4 -0
  43. package/build/index.js.map +1 -0
  44. package/build/native.d.ts +39 -0
  45. package/build/native.d.ts.map +1 -0
  46. package/build/native.js +12 -0
  47. package/build/native.js.map +1 -0
  48. package/build/types.d.ts +121 -0
  49. package/build/types.d.ts.map +1 -0
  50. package/build/types.js +3 -0
  51. package/build/types.js.map +1 -0
  52. package/build/useAuthonWallet.d.ts +25 -0
  53. package/build/useAuthonWallet.d.ts.map +1 -0
  54. package/build/useAuthonWallet.js +77 -0
  55. package/build/useAuthonWallet.js.map +1 -0
  56. package/expo-module.config.json +9 -0
  57. package/ios/AuthonReactNativeWallet.podspec +37 -0
  58. package/ios/AuthonWalletModule.swift +190 -0
  59. package/ios/vendor/AuthonWallet/AuthonWallet.swift +212 -0
  60. package/ios/vendor/AuthonWallet/Crypto/BIP39Wordlist.swift +262 -0
  61. package/ios/vendor/AuthonWallet/Crypto/HDKey.swift +108 -0
  62. package/ios/vendor/AuthonWallet/Crypto/Secp256k1.swift +104 -0
  63. package/ios/vendor/AuthonWallet/Crypto/Shamir.swift +123 -0
  64. package/ios/vendor/AuthonWallet/EVM/Address.swift +57 -0
  65. package/ios/vendor/AuthonWallet/EVM/EvmAccount.swift +48 -0
  66. package/ios/vendor/AuthonWallet/EVM/Transaction.swift +165 -0
  67. package/ios/vendor/AuthonWallet/EVM/TypedData.swift +193 -0
  68. package/ios/vendor/AuthonWallet/Support/BigInteger.swift +220 -0
  69. package/ios/vendor/AuthonWallet/Support/Bytes.swift +89 -0
  70. package/ios/vendor/AuthonWallet/Support/JSONValue.swift +341 -0
  71. package/ios/vendor/AuthonWallet/UI/WalletApprover.swift +250 -0
  72. package/ios/vendor/AuthonWallet/Wallet/Backup.swift +69 -0
  73. package/ios/vendor/AuthonWallet/Wallet/DeviceStore.swift +351 -0
  74. package/ios/vendor/AuthonWallet/Wallet/Passkeys.swift +190 -0
  75. package/ios/vendor/AuthonWallet/Wallet/Proof.swift +143 -0
  76. package/ios/vendor/AuthonWallet/Wallet/Shares.swift +116 -0
  77. package/ios/vendor/AuthonWallet/Wallet/Summary.swift +342 -0
  78. package/ios/vendor/AuthonWallet/Wallet/WalletAPI.swift +350 -0
  79. package/ios/vendor/AuthonWallet/Wallet/WalletOps.swift +555 -0
  80. package/ios/vendor/AuthonWallet/WalletError.swift +54 -0
  81. package/ios/vendor/CKeccak/LICENSE +22 -0
  82. package/ios/vendor/CKeccak/README.md +27 -0
  83. package/ios/vendor/CKeccak/byte_order.h +59 -0
  84. package/ios/vendor/CKeccak/include/ckeccak.h +9 -0
  85. package/ios/vendor/CKeccak/memzero.c +75 -0
  86. package/ios/vendor/CKeccak/memzero.h +8 -0
  87. package/ios/vendor/CKeccak/options.h +99 -0
  88. package/ios/vendor/CKeccak/sha3.c +413 -0
  89. package/ios/vendor/CKeccak/sha3.h +89 -0
  90. package/ios/vendor/CSecp256k1/COPYING +19 -0
  91. package/ios/vendor/CSecp256k1/README.md +35 -0
  92. package/ios/vendor/CSecp256k1/build/precomputed_ecmult.c +3 -0
  93. package/ios/vendor/CSecp256k1/build/precomputed_ecmult_gen.c +3 -0
  94. package/ios/vendor/CSecp256k1/build/secp256k1.c +3 -0
  95. package/ios/vendor/CSecp256k1/include/secp256k1.h +899 -0
  96. package/ios/vendor/CSecp256k1/include/secp256k1_preallocated.h +134 -0
  97. package/ios/vendor/CSecp256k1/include/secp256k1_recovery.h +113 -0
  98. package/ios/vendor/CSecp256k1/src/assumptions.h +87 -0
  99. package/ios/vendor/CSecp256k1/src/checkmem.h +102 -0
  100. package/ios/vendor/CSecp256k1/src/ecdsa.h +21 -0
  101. package/ios/vendor/CSecp256k1/src/ecdsa_impl.h +304 -0
  102. package/ios/vendor/CSecp256k1/src/eckey.h +25 -0
  103. package/ios/vendor/CSecp256k1/src/eckey_impl.h +92 -0
  104. package/ios/vendor/CSecp256k1/src/ecmult.h +61 -0
  105. package/ios/vendor/CSecp256k1/src/ecmult_const.h +38 -0
  106. package/ios/vendor/CSecp256k1/src/ecmult_const_impl.h +399 -0
  107. package/ios/vendor/CSecp256k1/src/ecmult_gen.h +143 -0
  108. package/ios/vendor/CSecp256k1/src/ecmult_gen_impl.h +341 -0
  109. package/ios/vendor/CSecp256k1/src/ecmult_impl.h +853 -0
  110. package/ios/vendor/CSecp256k1/src/field.h +350 -0
  111. package/ios/vendor/CSecp256k1/src/field_10x26.h +57 -0
  112. package/ios/vendor/CSecp256k1/src/field_10x26_impl.h +1232 -0
  113. package/ios/vendor/CSecp256k1/src/field_5x52.h +62 -0
  114. package/ios/vendor/CSecp256k1/src/field_5x52_impl.h +522 -0
  115. package/ios/vendor/CSecp256k1/src/field_5x52_int128_impl.h +274 -0
  116. package/ios/vendor/CSecp256k1/src/field_impl.h +457 -0
  117. package/ios/vendor/CSecp256k1/src/group.h +212 -0
  118. package/ios/vendor/CSecp256k1/src/group_impl.h +974 -0
  119. package/ios/vendor/CSecp256k1/src/hash.h +44 -0
  120. package/ios/vendor/CSecp256k1/src/hash_impl.h +299 -0
  121. package/ios/vendor/CSecp256k1/src/hsort.h +33 -0
  122. package/ios/vendor/CSecp256k1/src/hsort_impl.h +125 -0
  123. package/ios/vendor/CSecp256k1/src/int128.h +90 -0
  124. package/ios/vendor/CSecp256k1/src/int128_impl.h +18 -0
  125. package/ios/vendor/CSecp256k1/src/int128_native.h +19 -0
  126. package/ios/vendor/CSecp256k1/src/int128_native_impl.h +94 -0
  127. package/ios/vendor/CSecp256k1/src/int128_struct.h +14 -0
  128. package/ios/vendor/CSecp256k1/src/int128_struct_impl.h +205 -0
  129. package/ios/vendor/CSecp256k1/src/modinv32.h +43 -0
  130. package/ios/vendor/CSecp256k1/src/modinv32_impl.h +725 -0
  131. package/ios/vendor/CSecp256k1/src/modinv64.h +47 -0
  132. package/ios/vendor/CSecp256k1/src/modinv64_impl.h +780 -0
  133. package/ios/vendor/CSecp256k1/src/modules/recovery/main_impl.h +159 -0
  134. package/ios/vendor/CSecp256k1/src/precomputed_ecmult.c +16456 -0
  135. package/ios/vendor/CSecp256k1/src/precomputed_ecmult.h +38 -0
  136. package/ios/vendor/CSecp256k1/src/precomputed_ecmult_gen.c +1779 -0
  137. package/ios/vendor/CSecp256k1/src/precomputed_ecmult_gen.h +26 -0
  138. package/ios/vendor/CSecp256k1/src/scalar.h +105 -0
  139. package/ios/vendor/CSecp256k1/src/scalar_4x64.h +19 -0
  140. package/ios/vendor/CSecp256k1/src/scalar_4x64_impl.h +1000 -0
  141. package/ios/vendor/CSecp256k1/src/scalar_8x32.h +19 -0
  142. package/ios/vendor/CSecp256k1/src/scalar_8x32_impl.h +816 -0
  143. package/ios/vendor/CSecp256k1/src/scalar_impl.h +321 -0
  144. package/ios/vendor/CSecp256k1/src/scalar_low.h +24 -0
  145. package/ios/vendor/CSecp256k1/src/scalar_low_impl.h +206 -0
  146. package/ios/vendor/CSecp256k1/src/scratch.h +44 -0
  147. package/ios/vendor/CSecp256k1/src/scratch_impl.h +99 -0
  148. package/ios/vendor/CSecp256k1/src/secp256k1.c +831 -0
  149. package/ios/vendor/CSecp256k1/src/selftest.h +32 -0
  150. package/ios/vendor/CSecp256k1/src/util.h +451 -0
  151. package/ios/vendor/GENERATED.md +4 -0
  152. package/package.json +65 -3
  153. package/src/authonContext.ts +30 -0
  154. package/src/client.ts +375 -0
  155. package/src/errors.ts +52 -0
  156. package/src/index.ts +21 -0
  157. package/src/native.ts +50 -0
  158. package/src/types.ts +120 -0
  159. package/src/useAuthonWallet.ts +103 -0
@@ -0,0 +1,350 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from swift/Sources/AuthonWallet/Wallet/WalletAPI.swift. Do not edit: change the source and run the script.
2
+ import Foundation
3
+
4
+ public struct WalletDevice: Equatable, Sendable {
5
+ public var id: String
6
+ public var label: String?
7
+ public var shareVersion: Int?
8
+ public var createdAt: String?
9
+ public var lastUsedAt: String?
10
+ public var revokedAt: String?
11
+ }
12
+
13
+ public struct ServerWallet: Equatable, Sendable {
14
+ public var id: String
15
+ public var address: String
16
+ public var status: String
17
+ public var isProtected: Bool
18
+ /// nil without a backup; false for a legacy backup that has no passkey public key.
19
+ public var backupVerifiable: Bool?
20
+ public var shareVersion: Int
21
+ public var devices: [WalletDevice]
22
+ }
23
+
24
+ /// `GET /v1/wallet/app-config`, the native counterpart of the frame's frame-config.
25
+ public struct WalletAppConfig: Equatable, Sendable {
26
+ public var projectId: String?
27
+ /// The app (team ID + bundle ID) is registered for this project.
28
+ public var allowed: Bool
29
+ /// A platform admin approved the registration, so passkeys for the wallet RP work.
30
+ public var approved: Bool
31
+ public var enabled: Bool
32
+ public var chainIds: [Int]
33
+ /// Server-side project setting. Missing means true.
34
+ public var confirmSigning: Bool
35
+ public var prfSaltVersion: BackupVersion
36
+ public var rpId: String
37
+ public var minFrameProtocol: Int?
38
+ }
39
+
40
+ enum ProofKind: String {
41
+ case create, device, restore
42
+ }
43
+
44
+ struct DeviceProof: Equatable {
45
+ var deviceId: String
46
+ var challenge: String
47
+ var signature: String
48
+
49
+ var fields: [(String, JSONValue)] {
50
+ [("deviceId", .string(deviceId)), ("challenge", .string(challenge)), ("signature", .string(signature))]
51
+ }
52
+ }
53
+
54
+ struct RestoreStart {
55
+ var backupCiphertext: String?
56
+ var credentialId: String?
57
+ /// Present only for backups without a registered passkey key (older backups).
58
+ var companyShare: String?
59
+ var upgradeRecommended: Bool
60
+ var passkeyChallenge: String?
61
+ }
62
+
63
+ /// HTTP client for `/v1/wallet/*`, request for request the same as `apps/wallet/src/api.ts`.
64
+ final class WalletAPIClient: @unchecked Sendable {
65
+ /// Protocol sent as `protocol` in proof and backup requests (the API's CURRENT_FRAME_PROTOCOL).
66
+ static let frameProtocol = 2
67
+
68
+ private let base: String
69
+ let publishableKey: String
70
+ private let session: URLSession
71
+
72
+ init(apiURL: String, publishableKey: String, session: URLSession = .shared) {
73
+ var b = apiURL
74
+ while b.hasSuffix("/") { b.removeLast() }
75
+ base = b
76
+ self.publishableKey = publishableKey
77
+ self.session = session
78
+ }
79
+
80
+ private static let statusCodes: [Int: WalletErrorCode] = [
81
+ 400: .invalidRequest, 401: .unauthorized, 403: .forbidden, 404: .notFound,
82
+ 409: .alreadyExists, 426: .frameOutdated, 429: .rateLimited,
83
+ ]
84
+ private static let restoreCodes: Set<String> = ["NEEDS_RESTORE", "DEVICE_REVOKED", "DEVICE_NOT_FOUND", "STALE_SHARE_VERSION"]
85
+ private static let knownCodes: Set<String> = [
86
+ "NOT_PROTECTED", "ORIGIN_NOT_ALLOWED", "ALREADY_EXISTS", "NOT_FOUND", "LAST_DEVICE", "SESSION_REQUIRED",
87
+ "WALLET_PENDING", "RATE_LIMITED", "FRAME_OUTDATED", "BACKUP_KEY_REQUIRED", "ALREADY_UPGRADED",
88
+ ]
89
+
90
+ struct Response {
91
+ var status: Int
92
+ var data: JSONObject
93
+ }
94
+
95
+ private func call(_ method: String, _ path: String, _ token: String?, _ body: JSONValue? = nil) async throws -> Response {
96
+ guard let url = URL(string: "\(base)/v1/wallet\(path)") else { throw WalletError(.apiError, "invalid URL") }
97
+ var req = URLRequest(url: url)
98
+ req.httpMethod = method
99
+ req.cachePolicy = .reloadIgnoringLocalCacheData
100
+ req.httpShouldHandleCookies = false
101
+ req.setValue(publishableKey, forHTTPHeaderField: "x-api-key")
102
+ req.setValue("application/json", forHTTPHeaderField: "accept")
103
+ if let token { req.setValue("Bearer \(token)", forHTTPHeaderField: "authorization") }
104
+ if let body {
105
+ req.setValue("application/json", forHTTPHeaderField: "content-type")
106
+ req.httpBody = Data(body.compact().utf8)
107
+ }
108
+ let data: Data
109
+ let response: URLResponse
110
+ do {
111
+ (data, response) = try await session.data(for: req)
112
+ } catch {
113
+ throw WalletError(.apiError, "network error")
114
+ }
115
+ let status = (response as? HTTPURLResponse)?.statusCode ?? 0
116
+ let parsed = (try? JSONValue.parse(String(decoding: data, as: UTF8.self)))?.objectValue ?? JSONObject()
117
+ return Response(status: status, data: parsed)
118
+ }
119
+
120
+ private func ensureOk(_ res: Response) throws -> JSONObject {
121
+ if (200..<300).contains(res.status) { return res.data }
122
+ if case .string(let code) = res.data["code"] ?? .null {
123
+ if Self.restoreCodes.contains(code) { throw WalletError(.needsRestore) }
124
+ if Self.knownCodes.contains(code), let c = WalletErrorCode(rawValue: code) { throw WalletError(c) }
125
+ }
126
+ throw WalletError(Self.statusCodes[res.status] ?? .apiError)
127
+ }
128
+
129
+ private static func string(_ v: JSONValue?) -> String? { v?.stringValue }
130
+
131
+ /// JavaScript String(x) for ids.
132
+ private static func text(_ v: JSONValue?) -> String {
133
+ guard let v else { return "undefined" }
134
+ return EvmAddress.jsString(v)
135
+ }
136
+
137
+ /// JavaScript Number(x) for integer fields.
138
+ private static func int(_ v: JSONValue?, default d: Int? = nil) -> Int? {
139
+ guard let v, v != .null else { return d }
140
+ if let n = v.jsBigInt, let u = n.magnitude.uint64, u <= UInt64(Int.max) { return n.negative ? -Int(u) : Int(u) }
141
+ return nil
142
+ }
143
+
144
+ static func normalizeWallet(_ raw: JSONValue?) -> ServerWallet {
145
+ let w = raw?.objectValue ?? JSONObject()
146
+ let devices: [WalletDevice] = (w["devices"]?.arrayValue ?? []).map { d in
147
+ WalletDevice(
148
+ id: text(d["id"]),
149
+ label: string(d["label"]),
150
+ shareVersion: int(d["shareVersion"]),
151
+ createdAt: string(d["createdAt"]),
152
+ lastUsedAt: string(d["lastUsedAt"]),
153
+ revokedAt: d["revokedAt"]?.isTruthy == true ? text(d["revokedAt"]) : nil
154
+ )
155
+ }
156
+ let isProtected: Bool
157
+ if case .bool(let b) = w["protected"] ?? .null { isProtected = b } else { isProtected = w["protectedAt"]?.isTruthy == true }
158
+ var backupVerifiable: Bool?
159
+ if case .bool(let b) = w["backupVerifiable"] ?? .null { backupVerifiable = b }
160
+ let idValue = (w["id"].flatMap { $0 == .null ? nil : $0 }) ?? w["walletId"]
161
+ let statusValue = (w["status"].flatMap { $0 == .null ? nil : $0 }).map { text($0) } ?? "active"
162
+ return ServerWallet(
163
+ id: text(idValue),
164
+ address: text(w["address"]),
165
+ status: statusValue,
166
+ isProtected: isProtected,
167
+ backupVerifiable: backupVerifiable,
168
+ shareVersion: int(w["shareVersion"], default: 1) ?? 1,
169
+ devices: devices
170
+ )
171
+ }
172
+
173
+ private static func bound(_ fields: [(String, JSONValue)], _ bound: ProofNonce) -> JSONValue {
174
+ .object(JSONObject(fields + [
175
+ ("nonce", .string(bound.nonce)),
176
+ ("nonceExpiresAt", .number(String(bound.expiresAt))),
177
+ ("protocol", .number(String(frameProtocol))),
178
+ ]))
179
+ }
180
+
181
+ // MARK: - Endpoints
182
+
183
+ func appConfig(platform: String, appId: String) async throws -> WalletAppConfig {
184
+ let query = "platform=\(Self.encode(platform))&appId=\(Self.encode(appId))"
185
+ let res = try await call("GET", "/app-config?\(query)", nil)
186
+ if res.status == 403 || res.status == 404 {
187
+ return WalletAppConfig(projectId: nil, allowed: false, approved: false, enabled: false, chainIds: [], confirmSigning: true, prfSaltVersion: .v1, rpId: "wallet.authon.dev", minFrameProtocol: nil)
188
+ }
189
+ let data = try ensureOk(res)
190
+ let cfg = data["walletConfig"]?.objectValue ?? JSONObject()
191
+ let rpId = Self.string(data["rpId"]).flatMap { $0.isEmpty ? nil : $0 } ?? "wallet.authon.dev"
192
+ return WalletAppConfig(
193
+ projectId: Self.string(data["projectId"]),
194
+ allowed: data["allowed"] != .bool(false),
195
+ approved: data["approved"] == .bool(true),
196
+ enabled: cfg["enabled"] == .bool(true),
197
+ chainIds: (cfg["chainIds"]?.arrayValue ?? []).compactMap { Self.int($0) },
198
+ confirmSigning: cfg["confirmSigning"] != .bool(false),
199
+ prfSaltVersion: Self.int(cfg["prfSaltVersion"]) == 2 ? .v2 : .v1,
200
+ rpId: rpId,
201
+ minFrameProtocol: Self.int(data["minFrameProtocol"])
202
+ )
203
+ }
204
+
205
+ func getMe(_ token: String) async throws -> ServerWallet? {
206
+ let res = try await call("GET", "/me", token)
207
+ if res.status == 404 { return nil }
208
+ let data = try ensureOk(res)
209
+ return Self.normalizeWallet(data["wallet"].flatMap { $0 == .null ? nil : $0 } ?? .object(data))
210
+ }
211
+
212
+ func proofChallenge(_ token: String, kind: ProofKind) async throws -> ProofNonce {
213
+ let data = try ensureOk(try await call("POST", "/me/proof-challenge", token, .object(JSONObject([("kind", .string(kind.rawValue))]))))
214
+ guard case .string(let nonce) = data["nonce"] ?? .null, case .number(let exp) = data["expiresAt"] ?? .null,
215
+ let expiresAt = Int64(exp) ?? Double(exp).map({ Int64($0) }) else {
216
+ throw WalletError(.apiError)
217
+ }
218
+ return ProofNonce(nonce: nonce, expiresAt: expiresAt)
219
+ }
220
+
221
+ struct Created {
222
+ var wallet: ServerWallet?
223
+ var walletId: String
224
+ var deviceId: String
225
+ var shareVersion: Int
226
+ }
227
+
228
+ func createWallet(_ token: String, address: String, companyShare: String, devicePublicKey: DevicePublicJwk, proof: String, bound: ProofNonce) async throws -> Created {
229
+ let body = Self.bound([
230
+ ("address", .string(address)), ("companyShare", .string(companyShare)),
231
+ ("devicePublicKey", devicePublicKey.json), ("proof", .string(proof)),
232
+ ], bound)
233
+ let data = try ensureOk(try await call("POST", "/me", token, body))
234
+ let wallet: ServerWallet?
235
+ if let w = data["wallet"], w.isTruthy {
236
+ wallet = Self.normalizeWallet(w)
237
+ } else if data["id"]?.isTruthy == true {
238
+ wallet = Self.normalizeWallet(.object(data))
239
+ } else {
240
+ wallet = nil
241
+ }
242
+ let device = data["device"]?.objectValue ?? JSONObject()
243
+ let walletIdValue = data["walletId"].flatMap { $0 == .null ? nil : $0 }
244
+ let deviceIdValue = data["deviceId"].flatMap { $0 == .null ? nil : $0 } ?? device["id"]
245
+ return Created(
246
+ wallet: wallet,
247
+ walletId: walletIdValue.map { Self.text($0) } ?? wallet?.id ?? "undefined",
248
+ deviceId: Self.text(deviceIdValue),
249
+ shareVersion: Self.int(data["shareVersion"]) ?? wallet?.shareVersion ?? 1
250
+ )
251
+ }
252
+
253
+ func confirmWallet(_ token: String, _ proof: DeviceProof) async throws {
254
+ _ = try ensureOk(try await call("POST", "/me/confirm", token, .object(JSONObject(proof.fields))))
255
+ }
256
+
257
+ func discardPending(_ token: String) async throws {
258
+ let res = try await call("DELETE", "/me/pending", token)
259
+ if res.status == 404 { return }
260
+ _ = try ensureOk(res)
261
+ }
262
+
263
+ func challenge(_ token: String, deviceId: String) async throws -> String {
264
+ let data = try ensureOk(try await call("POST", "/me/share/challenge", token, .object(JSONObject([("deviceId", .string(deviceId))]))))
265
+ guard case .string(let c) = data["challenge"] ?? .null else { throw WalletError(.apiError) }
266
+ return c
267
+ }
268
+
269
+ func releaseShare(_ token: String, _ proof: DeviceProof) async throws -> String {
270
+ let data = try ensureOk(try await call("POST", "/me/share", token, .object(JSONObject(proof.fields))))
271
+ guard case .string(let s) = data["companyShare"] ?? .null else { throw WalletError(.apiError) }
272
+ return s
273
+ }
274
+
275
+ struct BackupKeyFields {
276
+ var credentialPublicKey: String?
277
+ var credentialAlg: Int?
278
+ var attestationObject: String?
279
+ }
280
+
281
+ func putBackup(_ token: String, proof: DeviceProof, backupCiphertext: String, credentialId: String, newCompanyShare: String, key: BackupKeyFields, upgrade: Bool) async throws -> Int {
282
+ var fields = proof.fields + [
283
+ ("backupCiphertext", .string(backupCiphertext)), ("credentialId", .string(credentialId)),
284
+ ("newCompanyShare", .string(newCompanyShare)),
285
+ ]
286
+ if let pk = key.credentialPublicKey, let alg = key.credentialAlg {
287
+ fields += [("credentialPublicKey", .string(pk)), ("credentialAlg", .number(String(alg)))]
288
+ } else if let att = key.attestationObject {
289
+ fields += [("attestationObject", .string(att))]
290
+ }
291
+ if upgrade { fields.append(("upgrade", .bool(true))) }
292
+ fields.append(("protocol", .number(String(Self.frameProtocol))))
293
+ let data = try ensureOk(try await call("PUT", "/me/backup", token, .object(JSONObject(fields))))
294
+ guard let v = Self.int(data["shareVersion"]) else { throw WalletError(.apiError) }
295
+ return v
296
+ }
297
+
298
+ func restoreStart(_ token: String) async throws -> RestoreStart {
299
+ let data = try ensureOk(try await call("POST", "/me/restore/start", token, .object(JSONObject())))
300
+ let companyShare = Self.string(data["companyShare"])
301
+ let passkeyChallenge = Self.string(data["passkeyChallenge"])
302
+ if (companyShare ?? "").isEmpty && (passkeyChallenge ?? "").isEmpty { throw WalletError(.apiError) }
303
+ return RestoreStart(
304
+ backupCiphertext: Self.string(data["backupCiphertext"]),
305
+ credentialId: Self.string(data["credentialId"]),
306
+ companyShare: companyShare,
307
+ upgradeRecommended: !(companyShare ?? "").isEmpty || data["upgradeRecommended"] == .bool(true),
308
+ passkeyChallenge: passkeyChallenge
309
+ )
310
+ }
311
+
312
+ func restoreAssert(_ token: String, _ a: PasskeyAssertion, challenge: String) async throws -> String {
313
+ let body = JSONValue.object(JSONObject([
314
+ ("credentialId", .string(a.credentialId)), ("clientDataJSON", .string(a.clientDataJSON)),
315
+ ("authenticatorData", .string(a.authenticatorData)), ("signature", .string(a.signature)),
316
+ ("challenge", .string(challenge)),
317
+ ]))
318
+ let data = try ensureOk(try await call("POST", "/me/restore/assert", token, body))
319
+ guard case .string(let s) = data["companyShare"] ?? .null else { throw WalletError(.apiError) }
320
+ return s
321
+ }
322
+
323
+ func restore(_ token: String, newCompanyShare: String, newBackupCiphertext: String, newDevicePublicKey: DevicePublicJwk, credentialId: String, proof: String, bound: ProofNonce) async throws -> (deviceId: String, shareVersion: Int) {
324
+ let body = Self.bound([
325
+ ("newCompanyShare", .string(newCompanyShare)), ("newBackupCiphertext", .string(newBackupCiphertext)),
326
+ ("newDevicePublicKey", newDevicePublicKey.json), ("credentialId", .string(credentialId)), ("proof", .string(proof)),
327
+ ], bound)
328
+ let data = try ensureOk(try await call("POST", "/me/restore", token, body))
329
+ let deviceId = data["deviceId"].flatMap { $0 == .null ? nil : $0 } ?? data["device"]?["id"]
330
+ guard let v = Self.int(data["shareVersion"]) else { throw WalletError(.apiError) }
331
+ return (Self.text(deviceId), v)
332
+ }
333
+
334
+ func addDevice(_ token: String, devicePublicKey: DevicePublicJwk, proof: String, bound: ProofNonce) async throws -> (deviceId: String, shareVersion: Int) {
335
+ let body = Self.bound([("devicePublicKey", devicePublicKey.json), ("proof", .string(proof))], bound)
336
+ let data = try ensureOk(try await call("POST", "/me/devices", token, body))
337
+ guard let v = Self.int(data["shareVersion"]) else { throw WalletError(.apiError) }
338
+ return (Self.text(data["deviceId"]), v)
339
+ }
340
+
341
+ func revokeDevice(_ token: String, deviceId: String, proof: DeviceProof) async throws {
342
+ _ = try ensureOk(try await call("DELETE", "/me/devices/\(Self.encode(deviceId))", token, .object(JSONObject(proof.fields))))
343
+ }
344
+
345
+ /// encodeURIComponent
346
+ static func encode(_ s: String) -> String {
347
+ let allowed = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.!~*'()")
348
+ return s.addingPercentEncoding(withAllowedCharacters: allowed) ?? s
349
+ }
350
+ }