@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,449 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/WalletOps.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ import kotlinx.serialization.json.Json
5
+ import kotlinx.serialization.json.JsonObject
6
+ import kotlinx.serialization.json.JsonPrimitive
7
+
8
+ internal class WalletContext(
9
+ val api: WalletApi,
10
+ val store: DeviceStore,
11
+ val passkeys: PasskeyProvider,
12
+ val projectId: String,
13
+ /** From the project's server-side settings only. */
14
+ val confirmSigning: Boolean = true,
15
+ /** PRF salt version for new backups (server flag). */
16
+ val backupVersion: Int = Prf.DEFAULT_WRITE_BACKUP_VERSION,
17
+ val approver: WalletApprover? = null,
18
+ val deviceAuthenticator: DeviceAuthenticator? = null,
19
+ )
20
+
21
+ internal data class WalletStatus(
22
+ val exists: Boolean,
23
+ val pending: Boolean,
24
+ val hasLocalShare: Boolean,
25
+ val needsRestore: Boolean,
26
+ val deviceId: String?,
27
+ val wallet: ServerWallet?,
28
+ )
29
+
30
+ /** The flows of `apps/wallet/src/ops.ts`, step for step. */
31
+ internal class WalletOps(private val ctx: WalletContext) {
32
+
33
+ fun toPublicWallet(st: WalletStatus): PublicWallet {
34
+ val w = st.wallet
35
+ if (w == null || st.pending) throw walletError(WalletErrorCode.NOT_FOUND)
36
+ return PublicWallet(
37
+ address = w.address,
38
+ protected = w.protected,
39
+ backupUpgradeRecommended = w.protected && w.backupVerifiable == false,
40
+ needsRestore = st.needsRestore,
41
+ devices = w.devices.filter { it.revokedAt == null }.map {
42
+ PublicWallet.Device(it.id, it.label, it.createdAt, it.lastUsedAt, it.id == st.deviceId)
43
+ },
44
+ )
45
+ }
46
+
47
+ private fun recordKey(session: String) = DeviceStorage.storageKey(ctx.projectId, decodeUserId(session))
48
+
49
+ suspend fun status(session: String): WalletStatus {
50
+ val wallet = ctx.api.getMe(session)
51
+ val local = ctx.store.get(recordKey(session))
52
+ val pending = wallet?.status == "pending"
53
+ val device = if (local != null && wallet != null) wallet.devices.firstOrNull { it.id == local.deviceId } else null
54
+ val hasLocal = !pending && local != null && wallet != null && local.walletId == wallet.id && device != null &&
55
+ device.revokedAt == null && (device.shareVersion ?: local.shareVersion) == wallet.shareVersion &&
56
+ local.shareVersion == wallet.shareVersion
57
+ val exists = wallet != null && !pending
58
+ return WalletStatus(exists, pending, hasLocal, exists && !hasLocal, if (hasLocal) local!!.deviceId else null, wallet)
59
+ }
60
+
61
+ private fun openShare(record: DeviceRecord): ByteArray = try {
62
+ record.keys.open(SealedShare(record.iv, record.ciphertext), Bytes.utf8(record.key))
63
+ } catch (e: Exception) {
64
+ throw WalletException(WalletErrorCode.NEEDS_RESTORE, "local share unreadable", e)
65
+ }
66
+
67
+ private suspend fun deviceProof(session: String, record: DeviceRecord): DeviceProof {
68
+ val challenge = ctx.api.challenge(session, record.deviceId)
69
+ val signature = try {
70
+ record.keys.sign(Bytes.utf8(challenge))
71
+ } catch (e: Exception) {
72
+ throw WalletException(WalletErrorCode.NEEDS_RESTORE, "device key unavailable", e)
73
+ }
74
+ return DeviceProof(record.deviceId, challenge, Bytes.toBase64(signature))
75
+ }
76
+
77
+ /** Reads the record back and checks that it opens to [share]; storage that drops writes fails here. */
78
+ private suspend fun verifyPersisted(key: String, deviceId: String, walletId: String, share: ByteArray?): DeviceRecord {
79
+ val stored = ctx.store.get(key)
80
+ if (stored == null || stored.deviceId != deviceId || stored.walletId != walletId) {
81
+ throw walletError(WalletErrorCode.STORAGE_UNAVAILABLE, "device share was not persisted")
82
+ }
83
+ if (share != null) {
84
+ var opened: ByteArray? = null
85
+ try {
86
+ opened = openShare(stored)
87
+ if (!Bytes.sameBytes(opened, share)) throw walletError(WalletErrorCode.STORAGE_UNAVAILABLE, "device share read back differs")
88
+ } catch (e: WalletException) {
89
+ if (e.code == WalletErrorCode.STORAGE_UNAVAILABLE) throw e
90
+ throw walletError(WalletErrorCode.STORAGE_UNAVAILABLE, "device share cannot be read back")
91
+ } finally {
92
+ Bytes.wipe(opened)
93
+ }
94
+ }
95
+ return stored
96
+ }
97
+
98
+ private suspend fun finishPending(session: String, wallet: ServerWallet): Boolean {
99
+ val key = recordKey(session)
100
+ val record = ctx.store.get(key)
101
+ if (record == null || record.walletId != wallet.id || wallet.devices.none { it.id == record.deviceId }) return false
102
+ var share: ByteArray? = null
103
+ try {
104
+ share = openShare(record)
105
+ verifyPersisted(key, record.deviceId, wallet.id, share)
106
+ } catch (_: Exception) {
107
+ return false
108
+ } finally {
109
+ Bytes.wipe(share)
110
+ }
111
+ ctx.api.confirmWallet(session, deviceProof(session, record))
112
+ return true
113
+ }
114
+
115
+ /**
116
+ * Two-phase creation: register address + A (wallet pending), store D, read it back, then
117
+ * confirm with a challenge signed by the stored device key. A pending wallet left behind is
118
+ * confirmed (D is here) or discarded and recreated (D is not).
119
+ */
120
+ suspend fun createWallet(session: String, attempt: Int = 0): WalletStatus {
121
+ val existing = status(session)
122
+ if (existing.exists) return existing
123
+ if (existing.pending && existing.wallet != null) {
124
+ if (finishPending(session, existing.wallet)) return status(session)
125
+ if (attempt > 0) throw walletError(WalletErrorCode.STORAGE_UNAVAILABLE)
126
+ ctx.api.discardPending(session)
127
+ runCatching { ctx.store.delete(recordKey(session)) }
128
+ return createWallet(session, attempt + 1)
129
+ }
130
+
131
+ val key = recordKey(session)
132
+ val secret = HdWallet.generateSecret()
133
+ var shares: HdWallet.SplitShares? = null
134
+ var keys: DeviceKeys? = null
135
+ var stored = false
136
+ var registered = false
137
+ try {
138
+ val address = HdWallet.address(secret)
139
+ shares = HdWallet.splitUnprotected(secret)
140
+ keys = ctx.store.generateKeys()
141
+ val sealed = keys.seal(shares.device, Bytes.utf8(key))
142
+ val bound = ctx.api.proofChallenge(session, "create")
143
+ val proof = Proof.sign(secret, Proof.createTypedData(ctx.projectId, decodeUserId(session), keys.publicJwk, bound))
144
+ val created = ctx.api.createWallet(session, address, Bytes.toBase64(shares.company), keys.publicJwk, proof, bound)
145
+ registered = true
146
+ val record = DeviceRecord(
147
+ key, keys, sealed.iv, sealed.ciphertext, created.deviceId, created.walletId, address, created.shareVersion,
148
+ System.currentTimeMillis(),
149
+ )
150
+ ctx.store.put(record)
151
+ stored = true
152
+ val persisted = verifyPersisted(key, created.deviceId, created.walletId, shares.device)
153
+ ctx.api.confirmWallet(session, deviceProof(session, persisted))
154
+ } catch (e: WalletException) {
155
+ if (!stored) keys?.let { runCatching { ctx.store.discardKeys(it) } }
156
+ if (e.code == WalletErrorCode.ALREADY_EXISTS && attempt == 0) return createWallet(session, attempt + 1)
157
+ if (registered && e.code == WalletErrorCode.STORAGE_UNAVAILABLE) {
158
+ runCatching { ctx.api.discardPending(session) }
159
+ runCatching { ctx.store.delete(key) }
160
+ }
161
+ throw e
162
+ } catch (e: Exception) {
163
+ if (!stored) keys?.let { runCatching { ctx.store.discardKeys(it) } }
164
+ throw e
165
+ } finally {
166
+ Bytes.wipe(secret)
167
+ shares?.wipe()
168
+ }
169
+ return status(session)
170
+ }
171
+
172
+ private class Local(val record: DeviceRecord, val wallet: ServerWallet)
173
+
174
+ private suspend fun requireLocal(session: String): Local {
175
+ val wallet = ctx.api.getMe(session)
176
+ if (wallet == null || wallet.status == "pending") throw walletError(WalletErrorCode.NOT_FOUND)
177
+ val record = ctx.store.get(recordKey(session))
178
+ if (record == null || record.walletId != wallet.id) throw walletError(WalletErrorCode.NEEDS_RESTORE)
179
+ return Local(record, wallet)
180
+ }
181
+
182
+ private suspend fun fetchCompanyShare(session: String, record: DeviceRecord): String =
183
+ ctx.api.releaseShare(session, deviceProof(session, record))
184
+
185
+ private suspend fun approveRequest(summary: SignSummary) {
186
+ val approver = ctx.approver ?: throw walletError(WalletErrorCode.CONFIRM_WINDOW_BLOCKED, "no approval UI is available")
187
+ if (!approver.approve(summary)) throw walletError(WalletErrorCode.USER_CANCELLED)
188
+ }
189
+
190
+ private suspend fun authenticateDevice(reason: String) {
191
+ val auth = ctx.deviceAuthenticator ?: return
192
+ if (!auth.authenticate(reason)) throw walletError(WalletErrorCode.USER_CANCELLED)
193
+ }
194
+
195
+ private fun withAccountRow(summary: SignSummary, account: SignAccount): SignSummary =
196
+ if (account.index == 0L) summary else summary.copy(rows = listOf(SignRow("Account", account.address!!, mono = true)) + summary.rows)
197
+
198
+ private suspend fun <T> withSecret(
199
+ session: String,
200
+ approve: (() -> SignSummary)?,
201
+ target: SignAccount,
202
+ fn: (EvmAccount) -> T,
203
+ ): T {
204
+ val summary = if (approve != null && ctx.confirmSigning) withAccountRow(approve(), target) else null
205
+ val local = requireLocal(session)
206
+ // Approval comes before A is requested, so a rejected request never assembles k.
207
+ if (summary != null) approveRequest(summary)
208
+ if (approve != null) authenticateDevice(summary?.title ?: "Sign")
209
+ val companyB64 = fetchCompanyShare(session, local.record)
210
+ val device = openShare(local.record)
211
+ val company = Bytes.fromBase64(companyB64)
212
+ var secret: ByteArray? = null
213
+ try {
214
+ secret = HdWallet.assemble(device, company)
215
+ val primary = HdWallet.account(secret, 0)
216
+ if (!primary.address.equals(local.wallet.address, ignoreCase = true)) throw walletError(WalletErrorCode.ADDRESS_MISMATCH)
217
+ if (target.index == 0L) return fn(primary)
218
+ if (!HdWallet.isHdSecret(secret)) throw walletError(WalletErrorCode.ACCOUNT_UNAVAILABLE, "this wallet has only account 0")
219
+ val account = HdWallet.account(secret, target.index)
220
+ if (!account.address.equals(target.address, ignoreCase = true)) throw walletError(WalletErrorCode.ADDRESS_MISMATCH)
221
+ return fn(account)
222
+ } finally {
223
+ Bytes.wipe(secret, device, company)
224
+ }
225
+ }
226
+
227
+ suspend fun accounts(session: String, start: Long, count: Int): List<WalletAccount> {
228
+ if (!HdWallet.isAccountIndex(start) || count < 1 || !HdWallet.isAccountIndex(start + count - 1)) {
229
+ throw walletError(WalletErrorCode.INVALID_REQUEST, "start..start+count-1 must stay within 0..${HdWallet.MAX_ACCOUNT_INDEX}")
230
+ }
231
+ val local = requireLocal(session)
232
+ val companyB64 = fetchCompanyShare(session, local.record)
233
+ val device = openShare(local.record)
234
+ val company = Bytes.fromBase64(companyB64)
235
+ var secret: ByteArray? = null
236
+ try {
237
+ secret = HdWallet.assemble(device, company)
238
+ if (!HdWallet.address(secret).equals(local.wallet.address, ignoreCase = true)) throw walletError(WalletErrorCode.ADDRESS_MISMATCH)
239
+ if (!HdWallet.isHdSecret(secret) && (start != 0L || count != 1)) {
240
+ throw walletError(WalletErrorCode.ACCOUNT_UNAVAILABLE, "this wallet has only account 0")
241
+ }
242
+ return HdWallet.addresses(secret, start, count).mapIndexed { i, a -> WalletAccount(start + i, HdWallet.evmPath(start + i), a) }
243
+ } finally {
244
+ Bytes.wipe(secret, device, company)
245
+ }
246
+ }
247
+
248
+ private fun reserved() = walletError(WalletErrorCode.INVALID_REQUEST, "this message is reserved for wallet key proofs and cannot be signed")
249
+
250
+ suspend fun signMessage(session: String, message: WalletMessage, account: SignAccount = SignAccount.PRIMARY): String {
251
+ val (summaryInput, bytes, text) = when (message) {
252
+ is WalletMessage.Text -> Triple(Summaries.Message.Text(message.text), Bytes.utf8(message.text), message.text)
253
+ is WalletMessage.Raw -> {
254
+ if (!Regex("^0x[0-9a-fA-F]*$").matches(message.hex)) {
255
+ throw walletError(WalletErrorCode.INVALID_REQUEST, "message must be a string or {raw}")
256
+ }
257
+ Triple(Summaries.Message.Raw(message.hex), Bytes.fromHex(message.hex), Proof.rawMessageText(message.hex))
258
+ }
259
+ }
260
+ if (text != null && Proof.isReservedMessageText(text)) throw reserved()
261
+ return withSecret(session, { Summaries.message(summaryInput) }, account) { it.signMessage(bytes) }
262
+ }
263
+
264
+ suspend fun signTypedData(session: String, typedData: JsonObject, account: SignAccount = SignAccount.PRIMARY): String {
265
+ fun present(k: String) = typedData[k].let { it != null && it !is kotlinx.serialization.json.JsonNull && !(it is JsonPrimitive && it.isString && it.content.isEmpty()) }
266
+ if (!present("types") || !present("primaryType") || !present("message")) {
267
+ throw walletError(WalletErrorCode.INVALID_REQUEST, "typedData incomplete")
268
+ }
269
+ if (Proof.isReservedTypedData(typedData)) throw reserved()
270
+ val digest = Eip712.hashTypedData(typedData)
271
+ return withSecret(session, { Summaries.typedData(typedData) }, account) { it.signDigest(digest).toHex() }
272
+ }
273
+
274
+ suspend fun signTransaction(session: String, transaction: JsonObject, account: SignAccount = SignAccount.PRIMARY): String {
275
+ val tx = EvmTransaction.parse(transaction)
276
+ return withSecret(session, { Summaries.transaction(tx.fields) }, account) { it.signTransaction(tx) }
277
+ }
278
+
279
+ private suspend fun writeBackup(session: String, upgrade: Boolean): ProtectResult {
280
+ val local = requireLocal(session)
281
+ val record = local.record
282
+ val wallet = local.wallet
283
+ if (!upgrade && wallet.protected) return ProtectResult(true, wallet.shareVersion)
284
+ if (upgrade) {
285
+ if (!wallet.protected) throw walletError(WalletErrorCode.NOT_PROTECTED)
286
+ if (wallet.backupVerifiable != false) throw walletError(WalletErrorCode.ALREADY_UPGRADED)
287
+ }
288
+ val key = recordKey(session)
289
+ val user = decodeUserId(session)
290
+ val version = ctx.backupVersion
291
+ val salt = Prf.salt(version, ctx.projectId, wallet.id)
292
+ val passkeyUser = Prf.passkeyUserId("${ctx.projectId}:$user")
293
+ val pk = ctx.passkeys.create(passkeyUser, wallet.address, salt)
294
+ var oldDevice: ByteArray? = null
295
+ var oldCompany: ByteArray? = null
296
+ var secret: ByteArray? = null
297
+ var shares: HdWallet.SplitShares? = null
298
+ var backupKey: ByteArray? = null
299
+ try {
300
+ val companyB64 = fetchCompanyShare(session, record)
301
+ oldDevice = openShare(record)
302
+ oldCompany = Bytes.fromBase64(companyB64)
303
+ secret = HdWallet.assemble(oldDevice, oldCompany)
304
+ if (!HdWallet.address(secret).equals(wallet.address, ignoreCase = true)) throw walletError(WalletErrorCode.ADDRESS_MISMATCH)
305
+ shares = HdWallet.splitProtected(secret)
306
+ backupKey = Prf.deriveBackupKey(pk.prfOutput)
307
+ val backupCiphertext = Prf.encryptBackup(backupKey, shares.backup!!, wallet.id, version)
308
+ val proof = deviceProof(session, record)
309
+ val keyFields = if (pk.publicKey != null && pk.publicKeyAlg != null) {
310
+ WalletApi.BackupKeyFields(pk.publicKey, pk.publicKeyAlg, null)
311
+ } else {
312
+ WalletApi.BackupKeyFields(null, null, pk.attestationObject)
313
+ }
314
+ val shareVersion = ctx.api.putBackup(
315
+ session, proof, backupCiphertext, pk.credentialId, Bytes.toBase64(shares.company), keyFields, upgrade,
316
+ )
317
+ val sealed = record.keys.seal(shares.device, Bytes.utf8(key))
318
+ ctx.store.put(record.copy(iv = sealed.iv, ciphertext = sealed.ciphertext, shareVersion = shareVersion))
319
+ return ProtectResult(true, shareVersion)
320
+ } finally {
321
+ Bytes.wipe(secret, oldDevice, oldCompany, pk.prfOutput, backupKey)
322
+ shares?.wipe()
323
+ }
324
+ }
325
+
326
+ suspend fun protect(session: String): ProtectResult = writeBackup(session, upgrade = false)
327
+
328
+ suspend fun upgradeBackup(session: String): ProtectResult = writeBackup(session, upgrade = true)
329
+
330
+ suspend fun restore(session: String, mode: RestoreMode = RestoreMode.ADD): RestoreResult {
331
+ val wallet = ctx.api.getMe(session)
332
+ if (wallet == null || wallet.status == "pending") throw walletError(WalletErrorCode.NOT_FOUND)
333
+ val start = ctx.api.restoreStart(session)
334
+ val backupCiphertext = start.backupCiphertext
335
+ val credentialId = start.credentialId
336
+ if (backupCiphertext == null || credentialId == null) throw walletError(WalletErrorCode.NOT_PROTECTED)
337
+
338
+ val key = recordKey(session)
339
+ val version = Prf.backupVersionOf(backupCiphertext)
340
+ val salt = Prf.salt(version, ctx.projectId, wallet.id)
341
+ val challenge = start.passkeyChallenge?.let { Bytes.fromBase64(it) }
342
+ val got = ctx.passkeys.get(credentialId, salt, challenge)
343
+ val prfOutput = got.prfOutput
344
+ var company: ByteArray? = null
345
+ var backup: ByteArray? = null
346
+ var secret: ByteArray? = null
347
+ var shares: HdWallet.SplitShares? = null
348
+ var backupKey: ByteArray? = null
349
+ try {
350
+ backupKey = Prf.deriveBackupKey(prfOutput)
351
+ backup = Prf.decryptBackup(backupKey, backupCiphertext, wallet.id)
352
+ val companyB64 = start.companyShare ?: ctx.api.restoreAssert(session, got.assertion, start.passkeyChallenge!!)
353
+ company = Bytes.fromBase64(companyB64)
354
+ secret = HdWallet.assemble(backup, company)
355
+ if (!HdWallet.address(secret).equals(wallet.address, ignoreCase = true)) throw walletError(WalletErrorCode.ADDRESS_MISMATCH)
356
+
357
+ val upgradeRecommended = start.upgradeRecommended
358
+ if (mode == RestoreMode.ADD) {
359
+ val added = addThisDevice(session, key, wallet, secret, company, backup)
360
+ return RestoreResult(true, added, upgradeRecommended)
361
+ }
362
+
363
+ shares = HdWallet.splitProtected(secret)
364
+ val keys = ctx.store.generateKeys()
365
+ var stored = false
366
+ try {
367
+ val bound = ctx.api.proofChallenge(session, "restore")
368
+ val proof = Proof.sign(secret, Proof.restoreTypedData(wallet.id, wallet.shareVersion, keys.publicJwk, bound))
369
+ // The PRF output belongs to this backup's salt version, so the new backup keeps it.
370
+ val newBackupCiphertext = Prf.encryptBackup(backupKey, shares.backup!!, wallet.id, version)
371
+ val res = ctx.api.restore(
372
+ session, Bytes.toBase64(shares.company), newBackupCiphertext, keys.publicJwk, credentialId, proof, bound,
373
+ )
374
+ val sealed = keys.seal(shares.device, Bytes.utf8(key))
375
+ ctx.store.put(
376
+ DeviceRecord(key, keys, sealed.iv, sealed.ciphertext, res.deviceId, wallet.id, wallet.address, res.shareVersion, System.currentTimeMillis()),
377
+ )
378
+ stored = true
379
+ return RestoreResult(true, res.shareVersion, upgradeRecommended)
380
+ } finally {
381
+ if (!stored) runCatching { ctx.store.discardKeys(keys) }
382
+ }
383
+ } finally {
384
+ Bytes.wipe(prfOutput, company, backup, secret, backupKey)
385
+ shares?.wipe()
386
+ }
387
+ }
388
+
389
+ private suspend fun addThisDevice(
390
+ session: String,
391
+ key: String,
392
+ wallet: ServerWallet,
393
+ secret: ByteArray,
394
+ company: ByteArray,
395
+ backup: ByteArray,
396
+ ): Int {
397
+ val device = HdWallet.deriveShare(company, backup)
398
+ var check1: ByteArray? = null
399
+ var check2: ByteArray? = null
400
+ try {
401
+ check1 = HdWallet.assemble(device, company)
402
+ check2 = HdWallet.assemble(device, backup)
403
+ if (!Bytes.sameBytes(check1, secret) || !Bytes.sameBytes(check2, secret)) {
404
+ throw walletError(WalletErrorCode.INTERNAL, "derived share mismatch")
405
+ }
406
+ val keys = ctx.store.generateKeys()
407
+ var stored = false
408
+ try {
409
+ val bound = ctx.api.proofChallenge(session, "device")
410
+ val proof = Proof.sign(secret, Proof.deviceTypedData(wallet.id, wallet.shareVersion, keys.publicJwk, bound))
411
+ val res = ctx.api.addDevice(session, keys.publicJwk, proof, bound)
412
+ val sealed = keys.seal(device, Bytes.utf8(key))
413
+ ctx.store.put(
414
+ DeviceRecord(key, keys, sealed.iv, sealed.ciphertext, res.deviceId, wallet.id, wallet.address, res.shareVersion, System.currentTimeMillis()),
415
+ )
416
+ stored = true
417
+ return res.shareVersion
418
+ } finally {
419
+ if (!stored) runCatching { ctx.store.discardKeys(keys) }
420
+ }
421
+ } finally {
422
+ Bytes.wipe(device, check1, check2)
423
+ }
424
+ }
425
+
426
+ suspend fun revokeDevice(session: String, deviceId: String): Boolean {
427
+ if (deviceId.isEmpty()) throw walletError(WalletErrorCode.INVALID_REQUEST, "deviceId required")
428
+ val local = requireLocal(session)
429
+ ctx.api.revokeDevice(session, deviceId, deviceProof(session, local.record))
430
+ if (local.record.deviceId == deviceId) ctx.store.delete(recordKey(session))
431
+ return true
432
+ }
433
+
434
+ companion object {
435
+ /** The `sub` claim of the session JWT (no verification; the API verifies the token). */
436
+ fun decodeUserId(session: String): String {
437
+ val part = session.split('.').getOrNull(1)?.takeIf { it.isNotEmpty() }
438
+ ?: throw walletError(WalletErrorCode.UNAUTHORIZED, "malformed session token")
439
+ return try {
440
+ val claims = Json.parseToJsonElement(String(Bytes.fromBase64(part), Charsets.UTF_8)) as JsonObject
441
+ val sub = claims["sub"] as? JsonPrimitive
442
+ if (sub == null || !sub.isString || sub.content.isEmpty()) throw IllegalStateException()
443
+ sub.content
444
+ } catch (_: Exception) {
445
+ throw walletError(WalletErrorCode.UNAUTHORIZED, "malformed session token")
446
+ }
447
+ }
448
+ }
449
+ }
@@ -0,0 +1,71 @@
1
+ // GENERATED by scripts/sync-native-wallet.mjs from kotlin/authon/src/main/kotlin/dev/authon/sdk/wallet/WalletTypes.kt. Do not edit: change the source and run the script.
2
+ package dev.authon.sdk.wallet
3
+
4
+ /** What apps see about the wallet (the frame's PublicWallet). */
5
+ data class PublicWallet(
6
+ val address: String,
7
+ /** False until a passkey backup exists. An unprotected wallet is lost with this app's storage. */
8
+ val protected: Boolean,
9
+ /** True for a legacy backup without a passkey public key; call upgradeBackup() from a device that holds the wallet. */
10
+ val backupUpgradeRecommended: Boolean,
11
+ val needsRestore: Boolean,
12
+ val devices: List<Device>,
13
+ ) {
14
+ data class Device(val id: String, val label: String?, val createdAt: String?, val lastUsedAt: String?, val current: Boolean)
15
+ }
16
+
17
+ data class WalletAccount(val index: Long, val path: String, val address: String)
18
+
19
+ /**
20
+ * Which account signs. Account 0 is the wallet address the server knows; another account must
21
+ * name the address the caller expects (from accounts()), and signing is refused when it differs.
22
+ */
23
+ data class SignAccount(val index: Long = 0, val address: String? = null) {
24
+ companion object {
25
+ val PRIMARY = SignAccount()
26
+
27
+ fun parse(index: Long?, address: String?): SignAccount {
28
+ if (index == null || index == 0L) return PRIMARY
29
+ if (!HdWallet.isAccountIndex(index)) {
30
+ throw walletError(WalletErrorCode.INVALID_REQUEST, "account must be an integer 0..${HdWallet.MAX_ACCOUNT_INDEX}")
31
+ }
32
+ if (address == null || !Regex("^0x[0-9a-fA-F]{40}$").matches(address)) {
33
+ throw walletError(WalletErrorCode.INVALID_REQUEST, "address of the signing account required")
34
+ }
35
+ return SignAccount(index, address)
36
+ }
37
+ }
38
+ }
39
+
40
+ sealed class WalletMessage {
41
+ /** UTF-8 text. */
42
+ data class Text(val text: String) : WalletMessage()
43
+
44
+ /** Raw bytes as 0x hex. */
45
+ data class Raw(val hex: String) : WalletMessage()
46
+ }
47
+
48
+ enum class RestoreMode {
49
+ /** Enroll this device next to the existing ones (default). */
50
+ ADD,
51
+
52
+ /** Re-split the key and revoke every other device (lost device). */
53
+ RESET,
54
+ }
55
+
56
+ data class ProtectResult(val protected: Boolean, val shareVersion: Int)
57
+
58
+ data class RestoreResult(val restored: Boolean, val shareVersion: Int, val backupUpgradeRecommended: Boolean)
59
+
60
+ /**
61
+ * Shows a signing request and returns true when the user approves. Called before the company share
62
+ * is requested, so a rejected request never assembles the key.
63
+ */
64
+ fun interface WalletApprover {
65
+ suspend fun approve(summary: SignSummary): Boolean
66
+ }
67
+
68
+ /** Optional extra gate (device lock / biometrics) before any key assembly. Throw or return false to refuse. */
69
+ fun interface DeviceAuthenticator {
70
+ suspend fun authenticate(reason: String): Boolean
71
+ }
@@ -0,0 +1,19 @@
1
+ import { type Context } from 'react';
2
+ /** The part of @authon/react-native's AuthonContext value the wallet uses. */
3
+ export interface AuthonSession {
4
+ isLoaded?: boolean;
5
+ isSignedIn?: boolean;
6
+ userId?: string | null;
7
+ getToken?: () => string | null;
8
+ client?: {
9
+ refreshToken?: () => Promise<{
10
+ accessToken?: string;
11
+ } | null>;
12
+ };
13
+ }
14
+ /**
15
+ * @authon/react-native is an optional peer: Metro resolves this require when the package is
16
+ * installed, and the catch keeps apps without it working (they pass getToken instead).
17
+ */
18
+ export declare function getAuthonContext(): Context<AuthonSession | null>;
19
+ //# sourceMappingURL=authonContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authonContext.d.ts","sourceRoot":"","sources":["../src/authonContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,OAAO,CAAC;AAIpD,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC;YAAE,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC,CAAA;KAAE,CAAC;CAC5E;AAKD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAShE"}
@@ -0,0 +1,20 @@
1
+ import { createContext } from 'react';
2
+ const missing = createContext(null);
3
+ let cached;
4
+ /**
5
+ * @authon/react-native is an optional peer: Metro resolves this require when the package is
6
+ * installed, and the catch keeps apps without it working (they pass getToken instead).
7
+ */
8
+ export function getAuthonContext() {
9
+ if (cached)
10
+ return cached;
11
+ try {
12
+ const mod = require('@authon/react-native');
13
+ cached = mod?.AuthonContext ?? missing;
14
+ }
15
+ catch {
16
+ cached = missing;
17
+ }
18
+ return cached;
19
+ }
20
+ //# sourceMappingURL=authonContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authonContext.js","sourceRoot":"","sources":["../src/authonContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAgB,MAAM,OAAO,CAAC;AAapD,MAAM,OAAO,GAAG,aAAa,CAAuB,IAAI,CAAC,CAAC;AAC1D,IAAI,MAAiD,CAAC;AAEtD;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,OAAO,CAAC,sBAAsB,CAAkE,CAAC;QAC7G,MAAM,GAAG,GAAG,EAAE,aAAa,IAAI,OAAO,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,GAAG,OAAO,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,76 @@
1
+ import type { AuthonWalletOptions, EIP1193Provider, EmbeddedWallet, Hex, TypedDataPayload, WalletAccount, WalletAppConfig, WalletChangeListener, WalletMessage, WalletSignOptions, WalletTransactionRequest } from './types';
2
+ /**
3
+ * The Authon embedded wallet in a React Native app. Signing runs in the native Swift / Kotlin
4
+ * wallet; JS only sees addresses, signatures, status and devices.
5
+ */
6
+ export declare class AuthonWalletClient {
7
+ private readonly id;
8
+ private readonly options;
9
+ private readonly listeners;
10
+ private readonly accountAddresses;
11
+ private configured;
12
+ private ensuring;
13
+ /** Bumped by dispose(); results of calls started before it are dropped (DISPOSED). */
14
+ private generation;
15
+ constructor(options: AuthonWalletOptions);
16
+ /** App registration and wallet settings (does not need a session). */
17
+ appConfig(): Promise<WalletAppConfig>;
18
+ /** The signed-in user's wallet, or null when there is none yet. */
19
+ get(): Promise<EmbeddedWallet | null>;
20
+ /** Same as `get()` (the frame method is `wallet.status`). */
21
+ status(): Promise<EmbeddedWallet | null>;
22
+ /** Creates the wallet (two-phase on the server), or returns the existing one. */
23
+ create(): Promise<EmbeddedWallet>;
24
+ /** Returns the wallet, creating it first when the user has none. Concurrent calls share one request. */
25
+ ensure(): Promise<EmbeddedWallet>;
26
+ /** True when the wallet exists and has a passkey backup. */
27
+ isProtected(): Promise<boolean>;
28
+ /** Addresses of accounts `start`..`start + count - 1` (public, no approval). */
29
+ getAccounts(options?: {
30
+ start?: number;
31
+ count?: number;
32
+ }): Promise<WalletAccount[]>;
33
+ /**
34
+ * EIP-191 personal_sign. The native approval sheet / dialog shows the full text first when the
35
+ * project requires confirmation (default); rejecting gives USER_CANCELLED and nothing is signed.
36
+ */
37
+ signMessage(message: WalletMessage, options?: WalletSignOptions): Promise<Hex>;
38
+ /** EIP-712 v4 typed data. */
39
+ signTypedData(typedData: TypedDataPayload, options?: WalletSignOptions): Promise<Hex>;
40
+ /** Signs a legacy, EIP-2930 or EIP-1559 transaction and returns the serialized signed transaction. */
41
+ signTransaction(tx: WalletTransactionRequest, options?: WalletSignOptions): Promise<Hex>;
42
+ /** Creates the passkey backup (iOS 18+ / Android with PRF support). Needs an approved app registration. */
43
+ protect(): Promise<void>;
44
+ /** Replaces a legacy backup with a verifiable one, on a device that holds the wallet. */
45
+ upgradeBackup(): Promise<void>;
46
+ /**
47
+ * Restores the wallet on this install with the backup passkey. By default this install is added
48
+ * as another device; `resetOtherDevices: true` re-splits the key and revokes every other device.
49
+ */
50
+ restore(options?: {
51
+ resetOtherDevices?: boolean;
52
+ }): Promise<void>;
53
+ /** Revokes a device (signed with this install's device key). */
54
+ revokeDevice(deviceId: string): Promise<void>;
55
+ onChange(listener: WalletChangeListener): () => void;
56
+ /** A minimal EIP-1193 provider (same methods as @authon/js). */
57
+ provider(chainId: number): EIP1193Provider;
58
+ /**
59
+ * Call on sign-out: releases the native instance, rejects calls in flight with DISPOSED and
60
+ * forgets cached account addresses. The device share stays on the device. Like @authon/js, the
61
+ * client can be used again afterwards (it reconnects on the next call).
62
+ */
63
+ dispose(): void;
64
+ private native;
65
+ private token;
66
+ private call;
67
+ private unwrap;
68
+ /** For accounts other than 0 the native wallet needs the expected address and refuses on mismatch. */
69
+ private account;
70
+ /** A different wallet (another user) invalidates the cached account addresses. */
71
+ private remember;
72
+ private refresh;
73
+ private notify;
74
+ }
75
+ export declare function createAuthonWallet(options: AuthonWalletOptions): AuthonWalletClient;
76
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EACf,cAAc,EAEd,GAAG,EACH,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,iBAAiB,EACjB,wBAAwB,EACzB,MAAM,SAAS,CAAC;AA0DjB;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAgC;IACnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA6B;IAC9D,OAAO,CAAC,UAAU,CAAkD;IACpE,OAAO,CAAC,QAAQ,CAAwC;IACxD,sFAAsF;IACtF,OAAO,CAAC,UAAU,CAAK;gBAEX,OAAO,EAAE,mBAAmB;IAMxC,sEAAsE;IAChE,SAAS,IAAI,OAAO,CAAC,eAAe,CAAC;IAM3C,mEAAmE;IAC7D,GAAG,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAS3C,6DAA6D;IAC7D,MAAM,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAIxC,iFAAiF;IAC3E,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC;IAMvC,wGAAwG;IACxG,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC;IAWjC,4DAA4D;IACtD,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAIrC,gFAAgF;IAC1E,WAAW,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAW7F;;;OAGG;IACG,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,GAAG,CAAC;IAgBxF,6BAA6B;IACvB,aAAa,CAAC,SAAS,EAAE,gBAAgB,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,GAAG,CAAC;IAO/F,sGAAsG;IAChG,eAAe,CAAC,EAAE,EAAE,wBAAwB,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,GAAG,CAAC;IAOlG,2GAA2G;IACrG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAK9B,yFAAyF;IACnF,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAKpC;;;OAGG;IACG,OAAO,CAAC,OAAO,GAAE;QAAE,iBAAiB,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAM3E,gEAAgE;IAC1D,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMnD,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,IAAI;IAOpD,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe;IAqC1C;;;;OAIG;IACH,OAAO,IAAI,IAAI;YAYD,MAAM;YAkCN,KAAK;YAML,IAAI;IAOlB,OAAO,CAAC,MAAM;IAQd,sGAAsG;YACxF,OAAO;IAcrB,kFAAkF;IAClF,OAAO,CAAC,QAAQ;YAOF,OAAO;IAIrB,OAAO,CAAC,MAAM;CASf;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CAEnF"}