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