@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 kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/WalletApi.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import kotlinx.serialization.json.JsonArray
5
+ import kotlinx.serialization.json.JsonElement
6
+ import kotlinx.serialization.json.JsonNull
7
+ import kotlinx.serialization.json.JsonObject
8
+ import kotlinx.serialization.json.JsonObjectBuilder
9
+ import kotlinx.serialization.json.JsonPrimitive
10
+ import kotlinx.serialization.json.booleanOrNull
11
+ import kotlinx.serialization.json.buildJsonObject
12
+ import kotlinx.serialization.json.contentOrNull
13
+ import kotlinx.serialization.json.intOrNull
14
+ import kotlinx.serialization.json.longOrNull
15
+ import kotlinx.serialization.json.put
16
+ import okhttp3.OkHttpClient
17
+ import java.io.IOException
18
+ import java.net.URLEncoder
19
+
20
+ data class WalletDevice(
21
+ val id: String,
22
+ val label: String?,
23
+ val shareVersion: Int?,
24
+ val createdAt: String?,
25
+ val lastUsedAt: String?,
26
+ val revokedAt: String?,
27
+ )
28
+
29
+ data class ServerWallet(
30
+ val id: String,
31
+ val address: String,
32
+ val status: String,
33
+ val protected: Boolean,
34
+ /** Null without a backup; false for a legacy backup that has no passkey public key. */
35
+ val backupVerifiable: Boolean?,
36
+ val shareVersion: Int,
37
+ val devices: List<WalletDevice>,
38
+ )
39
+
40
+ /** `GET /v1/wallet/app-config` for this app (platform=android, appId=package name). */
41
+ data class WalletAppConfig(
42
+ val allowed: Boolean,
43
+ val approved: Boolean,
44
+ val projectId: String?,
45
+ val enabled: Boolean,
46
+ val chainIds: List<Long>,
47
+ /** Missing means true: signing needs a confirmation. */
48
+ val confirmSigning: Boolean,
49
+ /** PRF salt version for new backups. Missing means 1. */
50
+ val prfSaltVersion: Int,
51
+ val rpId: String,
52
+ val minFrameProtocol: Int?,
53
+ /** Android: whether the running app's signing certificate is registered; null when not asked or not registered. */
54
+ val signatureMatches: Boolean? = null,
55
+ /** `X-Request-Id` of the app-config response. */
56
+ val diagnosticId: String? = null,
57
+ )
58
+
59
+ internal data class DeviceProof(val deviceId: String, val challenge: String, val signature: String) {
60
+ fun fill(b: JsonObjectBuilder) {
61
+ b.put("deviceId", deviceId)
62
+ b.put("challenge", challenge)
63
+ b.put("signature", signature)
64
+ }
65
+ }
66
+
67
+ internal data class RestoreStart(
68
+ val backupCiphertext: String?,
69
+ val credentialId: String?,
70
+ val companyShare: String?,
71
+ val upgradeRecommended: Boolean,
72
+ val passkeyChallenge: String?,
73
+ )
74
+
75
+ internal data class CreatedWallet(val wallet: ServerWallet?, val deviceId: String, val walletId: String, val shareVersion: Int)
76
+
77
+ internal data class EnrolledDevice(val deviceId: String, val shareVersion: Int)
78
+
79
+ /** HTTP contract of `apps/wallet/src/api.ts` (protocol 2), byte for byte in paths, headers and bodies. */
80
+ internal class WalletApi(
81
+ apiUrl: String,
82
+ private val publishableKey: String,
83
+ private val client: OkHttpClient = Http.defaultClient,
84
+ ) {
85
+ private val base = apiUrl.trimEnd('/')
86
+
87
+ private suspend fun call(method: String, path: String, session: String?, body: JsonObject? = null): HttpResult {
88
+ val headers = LinkedHashMap<String, String>()
89
+ headers["x-api-key"] = publishableKey
90
+ headers["accept"] = "application/json"
91
+ if (session != null) headers["authorization"] = "Bearer $session"
92
+ if (body != null) headers["content-type"] = "application/json"
93
+ headers["user-agent"] = Http.userAgent()
94
+ return try {
95
+ Http.execute(client, Http.request("$base/v1/wallet$path", method, headers, body))
96
+ } catch (e: IOException) {
97
+ throw WalletException(WalletErrorCode.API_ERROR, "network error", e)
98
+ }
99
+ }
100
+
101
+ private fun ensureOk(res: HttpResult): JsonObject {
102
+ if (res.ok) return res.json
103
+ val serverCode = res.json.str("code")
104
+ val id = res.requestId
105
+ if (serverCode != null && serverCode in RESTORE_CODES) throw walletError(WalletErrorCode.NEEDS_RESTORE, diagnosticId = id)
106
+ if (serverCode != null && serverCode in KNOWN_CODES) throw walletError(WalletErrorCode.valueOf(serverCode), diagnosticId = id)
107
+ throw walletError(STATUS_CODES[res.status] ?: WalletErrorCode.API_ERROR, diagnosticId = id)
108
+ }
109
+
110
+ suspend fun appConfig(packageName: String, certSha256: String? = null): WalletAppConfig {
111
+ val cert = certSha256?.let { "&certSha256=${enc(it)}" } ?: ""
112
+ val res = call("GET", "/app-config?platform=android&appId=${enc(packageName)}$cert", null)
113
+ if (res.status == 403 || res.status == 404) {
114
+ return WalletAppConfig(false, false, null, false, emptyList(), true, 1, DEFAULT_RP_ID, null, null, res.requestId)
115
+ }
116
+ val data = ensureOk(res)
117
+ val wc = data["walletConfig"] as? JsonObject ?: JsonObject(emptyMap())
118
+ return WalletAppConfig(
119
+ allowed = data.bool("allowed") != false,
120
+ approved = data.bool("approved") == true,
121
+ projectId = data.str("projectId"),
122
+ enabled = wc.bool("enabled") != false,
123
+ chainIds = (wc["chainIds"] as? JsonArray)?.mapNotNull { (it as? JsonPrimitive)?.longOrNull } ?: emptyList(),
124
+ confirmSigning = wc.bool("confirmSigning") != false,
125
+ prfSaltVersion = if ((wc["prfSaltVersion"] as? JsonPrimitive)?.intOrNull == 2) 2 else 1,
126
+ rpId = data.str("rpId") ?: DEFAULT_RP_ID,
127
+ minFrameProtocol = (data["minFrameProtocol"] as? JsonPrimitive)?.intOrNull,
128
+ signatureMatches = data.bool("signatureMatches"),
129
+ diagnosticId = res.requestId,
130
+ )
131
+ }
132
+
133
+ suspend fun getMe(session: String): ServerWallet? {
134
+ val res = call("GET", "/me", session)
135
+ if (res.status == 404) return null
136
+ val data = ensureOk(res)
137
+ return normalizeWallet(data["wallet"]?.takeIf { it !is JsonNull } ?: res.json)
138
+ }
139
+
140
+ suspend fun proofChallenge(session: String, kind: String): ProofNonce {
141
+ val data = ensureOk(call("POST", "/me/proof-challenge", session, buildJsonObject { put("kind", kind) }))
142
+ val nonce = data.str("nonce")
143
+ val expiresAt = (data["expiresAt"] as? JsonPrimitive)?.takeIf { !it.isString }?.longOrNull
144
+ if (nonce == null || expiresAt == null) throw walletError(WalletErrorCode.API_ERROR)
145
+ return ProofNonce(nonce, expiresAt)
146
+ }
147
+
148
+ suspend fun createWallet(
149
+ session: String,
150
+ address: String,
151
+ companyShare: String,
152
+ devicePublicKey: DevicePublicJwk,
153
+ proof: String,
154
+ bound: ProofNonce,
155
+ ): CreatedWallet {
156
+ val body = buildJsonObject {
157
+ put("address", address)
158
+ put("companyShare", companyShare)
159
+ put("devicePublicKey", devicePublicKey.toJson())
160
+ put("proof", proof)
161
+ put("nonce", bound.nonce)
162
+ put("nonceExpiresAt", bound.expiresAt)
163
+ put("protocol", FRAME_PROTOCOL)
164
+ }
165
+ val data = ensureOk(call("POST", "/me", session, body))
166
+ val wallet = when {
167
+ data["wallet"] is JsonObject -> normalizeWallet(data["wallet"]!!)
168
+ data["id"] != null && data["id"] !is JsonNull -> normalizeWallet(data)
169
+ else -> null
170
+ }
171
+ val device = data["device"] as? JsonObject
172
+ return CreatedWallet(
173
+ wallet = wallet,
174
+ walletId = data.str("walletId") ?: wallet?.id ?: "undefined",
175
+ deviceId = data.str("deviceId") ?: device?.str("id") ?: "undefined",
176
+ shareVersion = data.int("shareVersion") ?: wallet?.shareVersion ?: 1,
177
+ )
178
+ }
179
+
180
+ suspend fun confirmWallet(session: String, proof: DeviceProof) {
181
+ ensureOk(call("POST", "/me/confirm", session, buildJsonObject { proof.fill(this) }))
182
+ }
183
+
184
+ suspend fun discardPending(session: String) {
185
+ val res = call("DELETE", "/me/pending", session)
186
+ if (res.status == 404) return
187
+ ensureOk(res)
188
+ }
189
+
190
+ suspend fun challenge(session: String, deviceId: String): String {
191
+ val data = ensureOk(call("POST", "/me/share/challenge", session, buildJsonObject { put("deviceId", deviceId) }))
192
+ return data.str("challenge") ?: throw walletError(WalletErrorCode.API_ERROR)
193
+ }
194
+
195
+ suspend fun releaseShare(session: String, proof: DeviceProof): String {
196
+ val data = ensureOk(call("POST", "/me/share", session, buildJsonObject { proof.fill(this) }))
197
+ return data.str("companyShare") ?: throw walletError(WalletErrorCode.API_ERROR)
198
+ }
199
+
200
+ class BackupKeyFields(val credentialPublicKey: String?, val credentialAlg: Int?, val attestationObject: String?)
201
+
202
+ suspend fun putBackup(
203
+ session: String,
204
+ proof: DeviceProof,
205
+ backupCiphertext: String,
206
+ credentialId: String,
207
+ newCompanyShare: String,
208
+ key: BackupKeyFields,
209
+ upgrade: Boolean,
210
+ ): Int {
211
+ val body = buildJsonObject {
212
+ proof.fill(this)
213
+ put("backupCiphertext", backupCiphertext)
214
+ put("credentialId", credentialId)
215
+ put("newCompanyShare", newCompanyShare)
216
+ if (key.credentialPublicKey != null && key.credentialAlg != null) {
217
+ put("credentialPublicKey", key.credentialPublicKey)
218
+ put("credentialAlg", key.credentialAlg)
219
+ } else if (key.attestationObject != null) {
220
+ put("attestationObject", key.attestationObject)
221
+ }
222
+ if (upgrade) put("upgrade", true)
223
+ put("protocol", FRAME_PROTOCOL)
224
+ }
225
+ val data = ensureOk(call("PUT", "/me/backup", session, body))
226
+ return data.int("shareVersion") ?: throw walletError(WalletErrorCode.API_ERROR)
227
+ }
228
+
229
+ suspend fun restoreStart(session: String): RestoreStart {
230
+ val data = ensureOk(call("POST", "/me/restore/start", session, JsonObject(emptyMap())))
231
+ val companyShare = data.str("companyShare")
232
+ val passkeyChallenge = data.str("passkeyChallenge")
233
+ if (companyShare == null && passkeyChallenge == null) throw walletError(WalletErrorCode.API_ERROR)
234
+ return RestoreStart(
235
+ backupCiphertext = data.str("backupCiphertext"),
236
+ credentialId = data.str("credentialId"),
237
+ companyShare = companyShare,
238
+ passkeyChallenge = passkeyChallenge,
239
+ upgradeRecommended = companyShare != null || data.bool("upgradeRecommended") == true,
240
+ )
241
+ }
242
+
243
+ suspend fun restoreAssert(session: String, assertion: PasskeyAssertion, challenge: String): String {
244
+ val body = buildJsonObject {
245
+ put("credentialId", assertion.credentialId)
246
+ put("clientDataJSON", assertion.clientDataJSON)
247
+ put("authenticatorData", assertion.authenticatorData)
248
+ put("signature", assertion.signature)
249
+ put("challenge", challenge)
250
+ }
251
+ val data = ensureOk(call("POST", "/me/restore/assert", session, body))
252
+ return data.str("companyShare") ?: throw walletError(WalletErrorCode.API_ERROR)
253
+ }
254
+
255
+ suspend fun restore(
256
+ session: String,
257
+ newCompanyShare: String,
258
+ newBackupCiphertext: String,
259
+ newDevicePublicKey: DevicePublicJwk,
260
+ credentialId: String,
261
+ proof: String,
262
+ bound: ProofNonce,
263
+ ): EnrolledDevice {
264
+ val body = buildJsonObject {
265
+ put("newCompanyShare", newCompanyShare)
266
+ put("newBackupCiphertext", newBackupCiphertext)
267
+ put("newDevicePublicKey", newDevicePublicKey.toJson())
268
+ put("credentialId", credentialId)
269
+ put("proof", proof)
270
+ put("nonce", bound.nonce)
271
+ put("nonceExpiresAt", bound.expiresAt)
272
+ put("protocol", FRAME_PROTOCOL)
273
+ }
274
+ val data = ensureOk(call("POST", "/me/restore", session, body))
275
+ return EnrolledDevice(
276
+ data.str("deviceId") ?: (data["device"] as? JsonObject)?.str("id") ?: "undefined",
277
+ data.int("shareVersion") ?: throw walletError(WalletErrorCode.API_ERROR),
278
+ )
279
+ }
280
+
281
+ suspend fun addDevice(session: String, devicePublicKey: DevicePublicJwk, proof: String, bound: ProofNonce): EnrolledDevice {
282
+ val body = buildJsonObject {
283
+ put("devicePublicKey", devicePublicKey.toJson())
284
+ put("proof", proof)
285
+ put("nonce", bound.nonce)
286
+ put("nonceExpiresAt", bound.expiresAt)
287
+ put("protocol", FRAME_PROTOCOL)
288
+ }
289
+ val data = ensureOk(call("POST", "/me/devices", session, body))
290
+ return EnrolledDevice(
291
+ data.str("deviceId") ?: "undefined",
292
+ data.int("shareVersion") ?: throw walletError(WalletErrorCode.API_ERROR),
293
+ )
294
+ }
295
+
296
+ suspend fun revokeDevice(session: String, deviceId: String, proof: DeviceProof) {
297
+ ensureOk(call("DELETE", "/me/devices/${enc(deviceId)}", session, buildJsonObject { proof.fill(this) }))
298
+ }
299
+
300
+ companion object {
301
+ /** Protocol sent as `protocol` in proof and backup requests (the API's CURRENT_FRAME_PROTOCOL). */
302
+ const val FRAME_PROTOCOL = 2
303
+ const val DEFAULT_RP_ID = "wallet.authon.dev"
304
+
305
+ private val STATUS_CODES = mapOf(
306
+ 400 to WalletErrorCode.INVALID_REQUEST,
307
+ 401 to WalletErrorCode.UNAUTHORIZED,
308
+ 403 to WalletErrorCode.FORBIDDEN,
309
+ 404 to WalletErrorCode.NOT_FOUND,
310
+ 409 to WalletErrorCode.ALREADY_EXISTS,
311
+ 426 to WalletErrorCode.FRAME_OUTDATED,
312
+ 429 to WalletErrorCode.RATE_LIMITED,
313
+ )
314
+ private val RESTORE_CODES = setOf("NEEDS_RESTORE", "DEVICE_REVOKED", "DEVICE_NOT_FOUND", "STALE_SHARE_VERSION")
315
+ private val KNOWN_CODES = setOf(
316
+ "NOT_PROTECTED", "ORIGIN_NOT_ALLOWED", "ALREADY_EXISTS", "NOT_FOUND", "LAST_DEVICE", "SESSION_REQUIRED",
317
+ "WALLET_PENDING", "RATE_LIMITED", "FRAME_OUTDATED", "BACKUP_KEY_REQUIRED", "ALREADY_UPGRADED",
318
+ )
319
+
320
+ /** encodeURIComponent. */
321
+ fun enc(value: String): String = URLEncoder.encode(value, "UTF-8")
322
+ .replace("+", "%20").replace("%21", "!").replace("%27", "'")
323
+ .replace("%28", "(").replace("%29", ")").replace("%7E", "~")
324
+
325
+ private fun JsonObject.str(key: String): String? = (this[key] as? JsonPrimitive)?.takeIf { it.isString }?.content
326
+
327
+ private fun JsonObject.bool(key: String): Boolean? = (this[key] as? JsonPrimitive)?.takeIf { !it.isString }?.booleanOrNull
328
+
329
+ /** `Number(x)` for counters the server sends as numbers (or numeric strings). */
330
+ private fun JsonObject.int(key: String): Int? = (this[key] as? JsonPrimitive)?.contentOrNull?.toBigDecimalOrNull()?.toInt()
331
+
332
+ private fun JsonElement.asObj(): JsonObject = this as? JsonObject ?: JsonObject(emptyMap())
333
+
334
+ fun normalizeWallet(raw: JsonElement): ServerWallet {
335
+ val w = raw.asObj()
336
+ val prot = w.bool("protected") ?: (w["protectedAt"].let { it != null && it !is JsonNull && (it as? JsonPrimitive)?.contentOrNull?.isNotEmpty() == true })
337
+ return ServerWallet(
338
+ id = w.anyString("id") ?: w.anyString("walletId") ?: "undefined",
339
+ address = w.anyString("address") ?: "undefined",
340
+ status = w.anyString("status") ?: "active",
341
+ protected = prot,
342
+ backupVerifiable = w.bool("backupVerifiable"),
343
+ shareVersion = w.int("shareVersion") ?: 1,
344
+ devices = (w["devices"] as? JsonArray)?.mapNotNull { d ->
345
+ val o = d as? JsonObject ?: return@mapNotNull null
346
+ WalletDevice(
347
+ id = o.anyString("id") ?: return@mapNotNull null,
348
+ label = o.str("label"),
349
+ shareVersion = o.int("shareVersion"),
350
+ createdAt = o.str("createdAt"),
351
+ lastUsedAt = o.str("lastUsedAt"),
352
+ revokedAt = o.str("revokedAt"),
353
+ )
354
+ } ?: emptyList(),
355
+ )
356
+ }
357
+
358
+ private fun JsonObject.anyString(key: String): String? =
359
+ (this[key] as? JsonPrimitive)?.takeIf { it !is JsonNull }?.contentOrNull
360
+ }
361
+ }
@@ -0,0 +1,91 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/WalletCapabilities.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ /** NATIVE.md section 9. [wire] is the contract string. */
5
+ enum class CapabilityStatus(val wire: String) { AVAILABLE("available"), UNAVAILABLE("unavailable"), UNKNOWN("unknown") }
6
+
7
+ data class CapabilityCheck(val status: CapabilityStatus, val reason: String? = null) {
8
+ companion object {
9
+ val AVAILABLE = CapabilityCheck(CapabilityStatus.AVAILABLE)
10
+ fun unavailable(reason: String) = CapabilityCheck(CapabilityStatus.UNAVAILABLE, reason)
11
+ fun unknown(reason: String) = CapabilityCheck(CapabilityStatus.UNKNOWN, reason)
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Whether this device and app can use the wallet, answered before sign-in: no session, no
17
+ * login or passkey UI, no wallet is created.
18
+ */
19
+ data class WalletCapabilities(
20
+ /** Any unavailable -> unavailable; else any unknown -> unknown; else available. */
21
+ val ready: CapabilityStatus,
22
+ val app: CapabilityCheck,
23
+ val wallet: CapabilityCheck,
24
+ val secureStorage: CapabilityCheck,
25
+ val deviceLock: CapabilityCheck,
26
+ val passkeyBackup: CapabilityCheck,
27
+ /** `X-Request-Id` of the app-config response, when the API answered. */
28
+ val diagnosticId: String?,
29
+ ) {
30
+ companion object {
31
+ fun readyOf(checks: List<CapabilityCheck>): CapabilityStatus = when {
32
+ checks.any { it.status == CapabilityStatus.UNAVAILABLE } -> CapabilityStatus.UNAVAILABLE
33
+ checks.any { it.status == CapabilityStatus.UNKNOWN } -> CapabilityStatus.UNKNOWN
34
+ else -> CapabilityStatus.AVAILABLE
35
+ }
36
+ }
37
+ }
38
+
39
+ /** Static device checks behind capabilities(); the Android implementation is [AndroidCapabilityProbe]. */
40
+ interface CapabilityProbe {
41
+ /** SHA-256 of the running app's signing certificate, uppercase hex with colons; null when unreadable. */
42
+ fun signingCertSha256(): String?
43
+ suspend fun secureStorage(): CapabilityCheck
44
+ fun deviceLock(): CapabilityCheck
45
+ fun passkeyBackup(): CapabilityCheck
46
+ }
47
+
48
+ internal object Capabilities {
49
+ const val APP_NOT_REGISTERED = "APP_NOT_REGISTERED"
50
+ const val APP_SIGNATURE_MISMATCH = "APP_SIGNATURE_MISMATCH"
51
+ const val APP_NOT_APPROVED = "APP_NOT_APPROVED"
52
+ const val WALLET_DISABLED = "WALLET_DISABLED"
53
+ const val NETWORK_ERROR = "NETWORK_ERROR"
54
+ const val API_ERROR = "API_ERROR"
55
+ const val HARDWARE_KEYSTORE_UNAVAILABLE = "HARDWARE_KEYSTORE_UNAVAILABLE"
56
+ const val SIMULATOR = "SIMULATOR"
57
+ const val CHECK_FAILED = "CHECK_FAILED"
58
+ const val DEVICE_LOCK_NOT_SET = "DEVICE_LOCK_NOT_SET"
59
+ const val OS_UNSUPPORTED = "OS_UNSUPPORTED"
60
+ const val PASSKEY_API_UNAVAILABLE = "PASSKEY_API_UNAVAILABLE"
61
+ const val PRF_PROVIDER_UNVERIFIED = "PRF_PROVIDER_UNVERIFIED"
62
+
63
+ fun appCheck(cfg: WalletAppConfig): CapabilityCheck = when {
64
+ !cfg.allowed || cfg.projectId == null -> CapabilityCheck.unavailable(APP_NOT_REGISTERED)
65
+ cfg.signatureMatches == false -> CapabilityCheck.unavailable(APP_SIGNATURE_MISMATCH)
66
+ !cfg.approved -> CapabilityCheck.unknown(APP_NOT_APPROVED)
67
+ else -> CapabilityCheck.AVAILABLE
68
+ }
69
+
70
+ fun walletCheck(cfg: WalletAppConfig): CapabilityCheck =
71
+ if (cfg.enabled) CapabilityCheck.AVAILABLE else CapabilityCheck.unavailable(WALLET_DISABLED)
72
+
73
+ suspend fun evaluate(api: WalletApi, appId: String, probe: CapabilityProbe): Pair<WalletCapabilities, WalletAppConfig?> {
74
+ val cert = runCatching { probe.signingCertSha256() }.getOrNull()
75
+ var config: WalletAppConfig? = null
76
+ val (app, wallet, diagnosticId) = try {
77
+ val cfg = api.appConfig(appId, cert)
78
+ config = cfg
79
+ Triple(appCheck(cfg), walletCheck(cfg), cfg.diagnosticId)
80
+ } catch (e: WalletException) {
81
+ // No response at all (WalletApi wraps the IOException) vs. an API error.
82
+ val reason = if (e.cause is java.io.IOException) NETWORK_ERROR else API_ERROR
83
+ Triple(CapabilityCheck.unknown(reason), CapabilityCheck.unknown(reason), e.diagnosticId)
84
+ }
85
+ val secure = runCatching { probe.secureStorage() }.getOrElse { CapabilityCheck.unknown(CHECK_FAILED) }
86
+ val lock = runCatching { probe.deviceLock() }.getOrElse { CapabilityCheck.unknown(CHECK_FAILED) }
87
+ val passkey = runCatching { probe.passkeyBackup() }.getOrElse { CapabilityCheck.unknown(PASSKEY_API_UNAVAILABLE) }
88
+ val checks = listOf(app, wallet, secure, lock, passkey)
89
+ return WalletCapabilities(WalletCapabilities.readyOf(checks), app, wallet, secure, lock, passkey, diagnosticId) to config
90
+ }
91
+ }
@@ -0,0 +1,74 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/WalletError.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ /** Error codes of the web wallet frame (`apps/wallet/src/errors.ts`); [name] is the wire string. */
5
+ enum class WalletErrorCode {
6
+ NOT_FOUND,
7
+ NEEDS_RESTORE,
8
+ NOT_PROTECTED,
9
+ PRF_UNSUPPORTED,
10
+ ORIGIN_NOT_ALLOWED,
11
+ USER_CANCELLED,
12
+ NEEDS_POPUP,
13
+ INVALID_REQUEST,
14
+ UNSUPPORTED_TYPE,
15
+ UNAUTHORIZED,
16
+ FORBIDDEN,
17
+ ALREADY_EXISTS,
18
+ RATE_LIMITED,
19
+ ADDRESS_MISMATCH,
20
+ STORAGE_UNAVAILABLE,
21
+ API_ERROR,
22
+ INTERNAL,
23
+ LAST_DEVICE,
24
+ SESSION_REQUIRED,
25
+ WALLET_PENDING,
26
+
27
+ /** The approval UI could not be shown. Nothing was signed. */
28
+ CONFIRM_WINDOW_BLOCKED,
29
+
30
+ /** The approval UI was dismissed before the user approved. Nothing was signed. */
31
+ CONFIRM_WINDOW_CLOSED,
32
+
33
+ /** The message or typed data is too long to show in full; it is refused. */
34
+ MESSAGE_TOO_LARGE,
35
+
36
+ /** The API no longer serves this SDK protocol version. */
37
+ FRAME_OUTDATED,
38
+
39
+ /** The backup passkey did not report a usable public key. */
40
+ BACKUP_KEY_REQUIRED,
41
+
42
+ /** upgradeBackup on a wallet whose backup is already verifiable. */
43
+ ALREADY_UPGRADED,
44
+
45
+ /** The wallet predates HD wallets and has only account 0. */
46
+ ACCOUNT_UNAVAILABLE,
47
+ }
48
+
49
+ /**
50
+ * A wallet error with the fields of NATIVE.md section 10. [diagnosticId] is the server's request id
51
+ * when the API answered (log only this id, never tokens or key material).
52
+ */
53
+ class WalletException(
54
+ val code: WalletErrorCode,
55
+ message: String? = null,
56
+ cause: Throwable? = null,
57
+ val diagnosticId: String? = null,
58
+ val retryable: Boolean = code in RETRYABLE,
59
+ ) : Exception(message ?: code.name, cause) {
60
+ companion object {
61
+ /** Codes worth retrying unchanged (later); everything else needs a different action. */
62
+ val RETRYABLE: Set<WalletErrorCode> = setOf(
63
+ WalletErrorCode.API_ERROR,
64
+ WalletErrorCode.RATE_LIMITED,
65
+ WalletErrorCode.WALLET_PENDING,
66
+ )
67
+ }
68
+ }
69
+
70
+ internal fun walletError(code: WalletErrorCode, message: String? = null, diagnosticId: String? = null): WalletException =
71
+ WalletException(code, message, diagnosticId = diagnosticId)
72
+
73
+ internal fun Throwable.toWalletException(): WalletException =
74
+ this as? WalletException ?: WalletException(WalletErrorCode.INTERNAL, "unexpected error", this)