@snaha/swarm-id 0.0.1

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 (223) hide show
  1. package/README.md +431 -0
  2. package/dist/chunk/bmt.d.ts +17 -0
  3. package/dist/chunk/bmt.d.ts.map +1 -0
  4. package/dist/chunk/cac.d.ts +18 -0
  5. package/dist/chunk/cac.d.ts.map +1 -0
  6. package/dist/chunk/constants.d.ts +10 -0
  7. package/dist/chunk/constants.d.ts.map +1 -0
  8. package/dist/chunk/encrypted-cac.d.ts +48 -0
  9. package/dist/chunk/encrypted-cac.d.ts.map +1 -0
  10. package/dist/chunk/encryption.d.ts +86 -0
  11. package/dist/chunk/encryption.d.ts.map +1 -0
  12. package/dist/chunk/index.d.ts +6 -0
  13. package/dist/chunk/index.d.ts.map +1 -0
  14. package/dist/index.d.ts +46 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/proxy/act/act.d.ts +78 -0
  17. package/dist/proxy/act/act.d.ts.map +1 -0
  18. package/dist/proxy/act/crypto.d.ts +44 -0
  19. package/dist/proxy/act/crypto.d.ts.map +1 -0
  20. package/dist/proxy/act/grantee-list.d.ts +82 -0
  21. package/dist/proxy/act/grantee-list.d.ts.map +1 -0
  22. package/dist/proxy/act/history.d.ts +183 -0
  23. package/dist/proxy/act/history.d.ts.map +1 -0
  24. package/dist/proxy/act/index.d.ts +104 -0
  25. package/dist/proxy/act/index.d.ts.map +1 -0
  26. package/dist/proxy/chunking-encrypted.d.ts +14 -0
  27. package/dist/proxy/chunking-encrypted.d.ts.map +1 -0
  28. package/dist/proxy/chunking.d.ts +15 -0
  29. package/dist/proxy/chunking.d.ts.map +1 -0
  30. package/dist/proxy/download-data.d.ts +16 -0
  31. package/dist/proxy/download-data.d.ts.map +1 -0
  32. package/dist/proxy/feed-manifest.d.ts +62 -0
  33. package/dist/proxy/feed-manifest.d.ts.map +1 -0
  34. package/dist/proxy/feeds/epochs/async-finder.d.ts +77 -0
  35. package/dist/proxy/feeds/epochs/async-finder.d.ts.map +1 -0
  36. package/dist/proxy/feeds/epochs/epoch.d.ts +88 -0
  37. package/dist/proxy/feeds/epochs/epoch.d.ts.map +1 -0
  38. package/dist/proxy/feeds/epochs/finder.d.ts +67 -0
  39. package/dist/proxy/feeds/epochs/finder.d.ts.map +1 -0
  40. package/dist/proxy/feeds/epochs/index.d.ts +35 -0
  41. package/dist/proxy/feeds/epochs/index.d.ts.map +1 -0
  42. package/dist/proxy/feeds/epochs/test-utils.d.ts +93 -0
  43. package/dist/proxy/feeds/epochs/test-utils.d.ts.map +1 -0
  44. package/dist/proxy/feeds/epochs/types.d.ts +109 -0
  45. package/dist/proxy/feeds/epochs/types.d.ts.map +1 -0
  46. package/dist/proxy/feeds/epochs/updater.d.ts +68 -0
  47. package/dist/proxy/feeds/epochs/updater.d.ts.map +1 -0
  48. package/dist/proxy/feeds/epochs/utils.d.ts +22 -0
  49. package/dist/proxy/feeds/epochs/utils.d.ts.map +1 -0
  50. package/dist/proxy/feeds/index.d.ts +5 -0
  51. package/dist/proxy/feeds/index.d.ts.map +1 -0
  52. package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
  53. package/dist/proxy/feeds/sequence/async-finder.d.ts.map +1 -0
  54. package/dist/proxy/feeds/sequence/finder.d.ts +17 -0
  55. package/dist/proxy/feeds/sequence/finder.d.ts.map +1 -0
  56. package/dist/proxy/feeds/sequence/index.d.ts +23 -0
  57. package/dist/proxy/feeds/sequence/index.d.ts.map +1 -0
  58. package/dist/proxy/feeds/sequence/types.d.ts +80 -0
  59. package/dist/proxy/feeds/sequence/types.d.ts.map +1 -0
  60. package/dist/proxy/feeds/sequence/updater.d.ts +26 -0
  61. package/dist/proxy/feeds/sequence/updater.d.ts.map +1 -0
  62. package/dist/proxy/index.d.ts +6 -0
  63. package/dist/proxy/index.d.ts.map +1 -0
  64. package/dist/proxy/manifest-builder.d.ts +183 -0
  65. package/dist/proxy/manifest-builder.d.ts.map +1 -0
  66. package/dist/proxy/mantaray-encrypted.d.ts +27 -0
  67. package/dist/proxy/mantaray-encrypted.d.ts.map +1 -0
  68. package/dist/proxy/mantaray.d.ts +26 -0
  69. package/dist/proxy/mantaray.d.ts.map +1 -0
  70. package/dist/proxy/types.d.ts +29 -0
  71. package/dist/proxy/types.d.ts.map +1 -0
  72. package/dist/proxy/upload-data.d.ts +17 -0
  73. package/dist/proxy/upload-data.d.ts.map +1 -0
  74. package/dist/proxy/upload-encrypted-data.d.ts +103 -0
  75. package/dist/proxy/upload-encrypted-data.d.ts.map +1 -0
  76. package/dist/schemas.d.ts +240 -0
  77. package/dist/schemas.d.ts.map +1 -0
  78. package/dist/storage/debounced-uploader.d.ts +62 -0
  79. package/dist/storage/debounced-uploader.d.ts.map +1 -0
  80. package/dist/storage/utilization-store.d.ts +108 -0
  81. package/dist/storage/utilization-store.d.ts.map +1 -0
  82. package/dist/swarm-id-auth.d.ts +74 -0
  83. package/dist/swarm-id-auth.d.ts.map +1 -0
  84. package/dist/swarm-id-auth.js +2 -0
  85. package/dist/swarm-id-auth.js.map +1 -0
  86. package/dist/swarm-id-client.d.ts +878 -0
  87. package/dist/swarm-id-client.d.ts.map +1 -0
  88. package/dist/swarm-id-client.js +2 -0
  89. package/dist/swarm-id-client.js.map +1 -0
  90. package/dist/swarm-id-proxy.d.ts +236 -0
  91. package/dist/swarm-id-proxy.d.ts.map +1 -0
  92. package/dist/swarm-id-proxy.js +2 -0
  93. package/dist/swarm-id-proxy.js.map +1 -0
  94. package/dist/swarm-id.esm.js +2 -0
  95. package/dist/swarm-id.esm.js.map +1 -0
  96. package/dist/swarm-id.umd.js +2 -0
  97. package/dist/swarm-id.umd.js.map +1 -0
  98. package/dist/sync/index.d.ts +9 -0
  99. package/dist/sync/index.d.ts.map +1 -0
  100. package/dist/sync/key-derivation.d.ts +25 -0
  101. package/dist/sync/key-derivation.d.ts.map +1 -0
  102. package/dist/sync/restore-account.d.ts +28 -0
  103. package/dist/sync/restore-account.d.ts.map +1 -0
  104. package/dist/sync/serialization.d.ts +16 -0
  105. package/dist/sync/serialization.d.ts.map +1 -0
  106. package/dist/sync/store-interfaces.d.ts +53 -0
  107. package/dist/sync/store-interfaces.d.ts.map +1 -0
  108. package/dist/sync/sync-account.d.ts +44 -0
  109. package/dist/sync/sync-account.d.ts.map +1 -0
  110. package/dist/sync/types.d.ts +13 -0
  111. package/dist/sync/types.d.ts.map +1 -0
  112. package/dist/test-fixtures.d.ts +17 -0
  113. package/dist/test-fixtures.d.ts.map +1 -0
  114. package/dist/types-BD_VkNn0.js +2 -0
  115. package/dist/types-BD_VkNn0.js.map +1 -0
  116. package/dist/types-lJCaT-50.js +2 -0
  117. package/dist/types-lJCaT-50.js.map +1 -0
  118. package/dist/types.d.ts +2157 -0
  119. package/dist/types.d.ts.map +1 -0
  120. package/dist/utils/account-payload.d.ts +94 -0
  121. package/dist/utils/account-payload.d.ts.map +1 -0
  122. package/dist/utils/account-state-snapshot.d.ts +38 -0
  123. package/dist/utils/account-state-snapshot.d.ts.map +1 -0
  124. package/dist/utils/backup-encryption.d.ts +127 -0
  125. package/dist/utils/backup-encryption.d.ts.map +1 -0
  126. package/dist/utils/batch-utilization.d.ts +432 -0
  127. package/dist/utils/batch-utilization.d.ts.map +1 -0
  128. package/dist/utils/constants.d.ts +11 -0
  129. package/dist/utils/constants.d.ts.map +1 -0
  130. package/dist/utils/hex.d.ts +17 -0
  131. package/dist/utils/hex.d.ts.map +1 -0
  132. package/dist/utils/key-derivation.d.ts +92 -0
  133. package/dist/utils/key-derivation.d.ts.map +1 -0
  134. package/dist/utils/storage-managers.d.ts +65 -0
  135. package/dist/utils/storage-managers.d.ts.map +1 -0
  136. package/dist/utils/swarm-id-export.d.ts +24 -0
  137. package/dist/utils/swarm-id-export.d.ts.map +1 -0
  138. package/dist/utils/ttl.d.ts +49 -0
  139. package/dist/utils/ttl.d.ts.map +1 -0
  140. package/dist/utils/url.d.ts +41 -0
  141. package/dist/utils/url.d.ts.map +1 -0
  142. package/dist/utils/versioned-storage.d.ts +131 -0
  143. package/dist/utils/versioned-storage.d.ts.map +1 -0
  144. package/package.json +78 -0
  145. package/src/chunk/bmt.test.ts +217 -0
  146. package/src/chunk/bmt.ts +57 -0
  147. package/src/chunk/cac.test.ts +214 -0
  148. package/src/chunk/cac.ts +65 -0
  149. package/src/chunk/constants.ts +18 -0
  150. package/src/chunk/encrypted-cac.test.ts +385 -0
  151. package/src/chunk/encrypted-cac.ts +131 -0
  152. package/src/chunk/encryption.test.ts +352 -0
  153. package/src/chunk/encryption.ts +300 -0
  154. package/src/chunk/index.ts +47 -0
  155. package/src/index.ts +430 -0
  156. package/src/proxy/act/act.test.ts +278 -0
  157. package/src/proxy/act/act.ts +158 -0
  158. package/src/proxy/act/bee-compat.test.ts +948 -0
  159. package/src/proxy/act/crypto.test.ts +436 -0
  160. package/src/proxy/act/crypto.ts +376 -0
  161. package/src/proxy/act/grantee-list.test.ts +393 -0
  162. package/src/proxy/act/grantee-list.ts +239 -0
  163. package/src/proxy/act/history.test.ts +360 -0
  164. package/src/proxy/act/history.ts +413 -0
  165. package/src/proxy/act/index.test.ts +748 -0
  166. package/src/proxy/act/index.ts +853 -0
  167. package/src/proxy/chunking-encrypted.ts +95 -0
  168. package/src/proxy/chunking.ts +65 -0
  169. package/src/proxy/download-data.ts +448 -0
  170. package/src/proxy/feed-manifest.ts +174 -0
  171. package/src/proxy/feeds/epochs/async-finder.ts +372 -0
  172. package/src/proxy/feeds/epochs/epoch.test.ts +249 -0
  173. package/src/proxy/feeds/epochs/epoch.ts +181 -0
  174. package/src/proxy/feeds/epochs/finder.ts +282 -0
  175. package/src/proxy/feeds/epochs/index.ts +73 -0
  176. package/src/proxy/feeds/epochs/integration.test.ts +1336 -0
  177. package/src/proxy/feeds/epochs/test-utils.ts +274 -0
  178. package/src/proxy/feeds/epochs/types.ts +128 -0
  179. package/src/proxy/feeds/epochs/updater.ts +192 -0
  180. package/src/proxy/feeds/epochs/utils.ts +62 -0
  181. package/src/proxy/feeds/index.ts +5 -0
  182. package/src/proxy/feeds/sequence/async-finder.ts +31 -0
  183. package/src/proxy/feeds/sequence/finder.ts +73 -0
  184. package/src/proxy/feeds/sequence/index.ts +54 -0
  185. package/src/proxy/feeds/sequence/integration.test.ts +966 -0
  186. package/src/proxy/feeds/sequence/types.ts +103 -0
  187. package/src/proxy/feeds/sequence/updater.ts +71 -0
  188. package/src/proxy/index.ts +5 -0
  189. package/src/proxy/manifest-builder.test.ts +427 -0
  190. package/src/proxy/manifest-builder.ts +679 -0
  191. package/src/proxy/mantaray-encrypted.ts +78 -0
  192. package/src/proxy/mantaray.ts +104 -0
  193. package/src/proxy/types.ts +32 -0
  194. package/src/proxy/upload-data.ts +189 -0
  195. package/src/proxy/upload-encrypted-data.ts +658 -0
  196. package/src/schemas.ts +299 -0
  197. package/src/storage/debounced-uploader.ts +192 -0
  198. package/src/storage/utilization-store.ts +397 -0
  199. package/src/swarm-id-client.test.ts +99 -0
  200. package/src/swarm-id-client.ts +3095 -0
  201. package/src/swarm-id-proxy.ts +3891 -0
  202. package/src/sync/index.ts +28 -0
  203. package/src/sync/restore-account.ts +90 -0
  204. package/src/sync/serialization.ts +39 -0
  205. package/src/sync/store-interfaces.ts +62 -0
  206. package/src/sync/sync-account.test.ts +302 -0
  207. package/src/sync/sync-account.ts +396 -0
  208. package/src/sync/types.ts +11 -0
  209. package/src/test-fixtures.ts +109 -0
  210. package/src/types.ts +1651 -0
  211. package/src/utils/account-state-snapshot.test.ts +595 -0
  212. package/src/utils/account-state-snapshot.ts +94 -0
  213. package/src/utils/backup-encryption.test.ts +442 -0
  214. package/src/utils/backup-encryption.ts +352 -0
  215. package/src/utils/batch-utilization.ts +1309 -0
  216. package/src/utils/constants.ts +20 -0
  217. package/src/utils/hex.ts +27 -0
  218. package/src/utils/key-derivation.ts +197 -0
  219. package/src/utils/storage-managers.ts +365 -0
  220. package/src/utils/ttl.ts +129 -0
  221. package/src/utils/url.test.ts +136 -0
  222. package/src/utils/url.ts +71 -0
  223. package/src/utils/versioned-storage.ts +323 -0
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Time and Session Constants
3
+ *
4
+ * Centralized constants for time units and default session durations.
5
+ */
6
+
7
+ // ============================================================================
8
+ // Time Units (in milliseconds)
9
+ // ============================================================================
10
+
11
+ export const SECOND = 1_000
12
+ export const MINUTE = 60 * SECOND
13
+ export const HOUR = 60 * MINUTE
14
+ export const DAY = 24 * HOUR
15
+
16
+ // ============================================================================
17
+ // Session Defaults
18
+ // ============================================================================
19
+
20
+ export const DEFAULT_SESSION_DURATION = 30 * DAY
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Hex utilities
3
+ *
4
+ * Re-exports hex conversion functions from key-derivation
5
+ */
6
+
7
+ import type { Address } from "../schemas"
8
+
9
+ export { hexToUint8Array, uint8ArrayToHex } from "./key-derivation"
10
+
11
+ /**
12
+ * Create a validated hex address string (40 lowercase hex chars).
13
+ * Accepts optional 0x prefix and any case.
14
+ *
15
+ * @param input - Ethereum address string (with or without 0x prefix)
16
+ * @returns Normalized 40-character lowercase hex string
17
+ * @throws {Error} If the input is not a valid 40-char hex address
18
+ */
19
+ export function hexAddress(input: string): Address {
20
+ const clean = input.replace(/^0x/i, "").toLowerCase()
21
+ if (!/^[0-9a-f]{40}$/.test(clean)) {
22
+ throw new Error(
23
+ `Invalid hex address: expected 40 hex characters (with optional 0x prefix), got "${input}"`,
24
+ )
25
+ }
26
+ return clean
27
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Swarm Identity - Key Derivation Utilities
3
+ *
4
+ * Provides cryptographic functions for deriving app-specific secrets
5
+ * from a master identity key.
6
+ */
7
+
8
+ import { PrivateKey } from "@ethersphere/bee-js"
9
+
10
+ /**
11
+ * Derive an app-specific secret from a master key and app origin
12
+ *
13
+ * Uses HMAC-SHA256 to create a deterministic, unique secret for each app.
14
+ * The same master key + app origin will always produce the same secret.
15
+ *
16
+ * @param masterKey - The master identity key (hex string)
17
+ * @param appOrigin - The app's origin (e.g., "https://swarm-app.local:8080")
18
+ * @returns The derived secret as a hex string
19
+ */
20
+ export async function deriveSecret(
21
+ masterKey: string,
22
+ appOrigin: string,
23
+ ): Promise<string> {
24
+ const encoder = new TextEncoder()
25
+
26
+ // Convert master key from hex string to Uint8Array
27
+ const keyData = hexToUint8Array(masterKey)
28
+ const message = encoder.encode(appOrigin)
29
+
30
+ // Import the master key for HMAC
31
+ const cryptoKey = await crypto.subtle.importKey(
32
+ "raw",
33
+ keyData,
34
+ { name: "HMAC", hash: "SHA-256" },
35
+ false,
36
+ ["sign"],
37
+ )
38
+
39
+ // Sign the app origin with the master key
40
+ const signature = await crypto.subtle.sign("HMAC", cryptoKey, message)
41
+
42
+ // Convert to hex string
43
+ const secretHex = uint8ArrayToHex(new Uint8Array(signature))
44
+
45
+ return secretHex
46
+ }
47
+
48
+ /**
49
+ * Generate a random master key for testing/demo purposes
50
+ *
51
+ * In production, this would be derived from a user's mnemonic or
52
+ * imported from an existing identity.
53
+ *
54
+ * @returns A random 32-byte key as a hex string
55
+ */
56
+ export async function generateMasterKey(): Promise<string> {
57
+ const randomBytes = new Uint8Array(32)
58
+ crypto.getRandomValues(randomBytes)
59
+
60
+ const masterKey = uint8ArrayToHex(randomBytes)
61
+
62
+ return masterKey
63
+ }
64
+
65
+ /**
66
+ * Convert a hex string to Uint8Array
67
+ *
68
+ * @param hexString - Hex string (e.g., "deadbeef")
69
+ * @returns Uint8Array
70
+ */
71
+ export function hexToUint8Array(hexString: string): Uint8Array {
72
+ // Remove any whitespace and ensure even length
73
+ const hex = hexString.replace(/\s/g, "")
74
+ if (hex.length % 2 !== 0) {
75
+ throw new Error("Invalid hex string: length must be even")
76
+ }
77
+
78
+ const bytes = new Uint8Array(hex.length / 2)
79
+ for (let i = 0; i < hex.length; i += 2) {
80
+ bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16)
81
+ }
82
+
83
+ return bytes
84
+ }
85
+
86
+ /**
87
+ * Convert a Uint8Array to hex string
88
+ *
89
+ * @param bytes - Uint8Array to convert
90
+ * @returns Hex string (e.g., "deadbeef")
91
+ */
92
+ export function uint8ArrayToHex(bytes: Uint8Array): string {
93
+ return Array.from(bytes)
94
+ .map((b) => b.toString(16).padStart(2, "0"))
95
+ .join("")
96
+ }
97
+
98
+ /**
99
+ * Verify that a derived secret matches the expected value
100
+ *
101
+ * Useful for testing.
102
+ *
103
+ * @param masterKey - Master key hex string
104
+ * @param appOrigin - App origin
105
+ * @param expectedSecret - Expected secret hex string
106
+ * @returns true if the derived secret matches the expected secret
107
+ */
108
+ export async function verifySecret(
109
+ masterKey: string,
110
+ appOrigin: string,
111
+ expectedSecret: string,
112
+ ): Promise<boolean> {
113
+ const derived = await deriveSecret(masterKey, appOrigin)
114
+ return derived === expectedSecret
115
+ }
116
+
117
+ /**
118
+ * Derive an identity-specific master key from account master key and identity ID
119
+ *
120
+ * Uses HMAC-SHA256 to create a deterministic, unique key for each identity.
121
+ * This enables hierarchical key derivation: Account → Identity → App.
122
+ * The same account master key + identity ID will always produce the same identity key.
123
+ *
124
+ * @param accountMasterKey - The account's master key (hex string)
125
+ * @param identityId - The identity's unique identifier
126
+ * @returns The derived identity master key as a hex string
127
+ */
128
+ export async function deriveIdentityKey(
129
+ accountMasterKey: string,
130
+ identityId: string,
131
+ ): Promise<string> {
132
+ const encoder = new TextEncoder()
133
+
134
+ // Convert account master key to Uint8Array
135
+ const keyData = hexToUint8Array(accountMasterKey)
136
+ const message = encoder.encode(identityId)
137
+
138
+ // Import the account master key for HMAC
139
+ const cryptoKey = await crypto.subtle.importKey(
140
+ "raw",
141
+ keyData,
142
+ { name: "HMAC", hash: "SHA-256" },
143
+ false,
144
+ ["sign"],
145
+ )
146
+
147
+ // Sign the identity ID with the account master key
148
+ const signature = await crypto.subtle.sign("HMAC", cryptoKey, message)
149
+
150
+ // Convert to hex string
151
+ const identityKeyHex = uint8ArrayToHex(new Uint8Array(signature))
152
+
153
+ return identityKeyHex
154
+ }
155
+
156
+ /**
157
+ * Derive account backup key from account master key
158
+ *
159
+ * Used for signing account feed updates
160
+ *
161
+ * @param accountMasterKey - Account master key (hex string)
162
+ * @param accountId - Account ID (EthAddress hex string)
163
+ * @returns 32-byte account backup key (as hex string)
164
+ */
165
+ export async function deriveAccountBackupKey(
166
+ accountMasterKey: string,
167
+ accountId: string,
168
+ ): Promise<string> {
169
+ return deriveSecret(accountMasterKey, `account:${accountId}`)
170
+ }
171
+
172
+ /**
173
+ * Derive account Swarm encryption key from account master key
174
+ *
175
+ * Used for encrypting account snapshot data before upload to Swarm
176
+ *
177
+ * @param accountMasterKey - Account master key (hex string)
178
+ * @returns 32-byte encryption key (as hex string)
179
+ */
180
+ export async function deriveAccountSwarmEncryptionKey(
181
+ accountMasterKey: string,
182
+ ): Promise<string> {
183
+ return deriveSecret(accountMasterKey, `swarm-encryption`)
184
+ }
185
+
186
+ /**
187
+ * Convert backup key to PrivateKey for feed signing
188
+ */
189
+ export function backupKeyToPrivateKey(backupKeyHex: string): PrivateKey {
190
+ return new PrivateKey(backupKeyHex)
191
+ }
192
+
193
+ // Export utility functions for testing
194
+ export const utils = {
195
+ hexToUint8Array,
196
+ uint8ArrayToHex,
197
+ }
@@ -0,0 +1,365 @@
1
+ /**
2
+ * Pre-configured Storage Managers for Entity Types
3
+ *
4
+ * Provides ready-to-use storage managers for accounts, identities,
5
+ * connected apps, and postage stamps with versioning support.
6
+ */
7
+
8
+ import { z } from "zod"
9
+ import {
10
+ VersionedStorageManager,
11
+ createLocalStorageManager,
12
+ type VersionParser,
13
+ } from "./versioned-storage"
14
+ import type { Account, Identity, ConnectedApp, PostageStamp } from "../types"
15
+ import {
16
+ STORAGE_KEY_ACCOUNTS,
17
+ STORAGE_KEY_IDENTITIES,
18
+ STORAGE_KEY_CONNECTED_APPS,
19
+ STORAGE_KEY_POSTAGE_STAMPS,
20
+ STORAGE_KEY_NETWORK_SETTINGS,
21
+ } from "../types"
22
+ import {
23
+ AccountSchemaV1,
24
+ IdentitySchemaV1,
25
+ ConnectedAppSchemaV1,
26
+ PostageStampSchemaV1,
27
+ NetworkSettingsSchemaV1,
28
+ type NetworkSettings,
29
+ } from "../schemas"
30
+
31
+ // ============================================================================
32
+ // Parsers (Zod transforms handle primitive → bee-js conversion)
33
+ // ============================================================================
34
+
35
+ /**
36
+ * Parse accounts - Zod transforms handle type conversion
37
+ */
38
+ const parseAccountsV1: VersionParser<Account> = (data: unknown) => {
39
+ const result = z.array(AccountSchemaV1).safeParse(data)
40
+
41
+ if (!result.success) {
42
+ console.error("Parse failed:", result.error.format())
43
+ return []
44
+ }
45
+
46
+ return result.data
47
+ }
48
+
49
+ /**
50
+ * Parse identities - Zod transforms handle type conversion
51
+ */
52
+ const parseIdentitiesV1: VersionParser<Identity> = (data: unknown) => {
53
+ const result = z.array(IdentitySchemaV1).safeParse(data)
54
+
55
+ if (!result.success) {
56
+ console.error("Parse failed:", result.error.format())
57
+ return []
58
+ }
59
+
60
+ return result.data
61
+ }
62
+
63
+ /**
64
+ * Parse connected apps
65
+ */
66
+ const parseConnectedAppsV1: VersionParser<ConnectedApp> = (data: unknown) => {
67
+ const result = z.array(ConnectedAppSchemaV1).safeParse(data)
68
+
69
+ if (!result.success) {
70
+ console.error("Parse failed:", result.error.format())
71
+ return []
72
+ }
73
+
74
+ return result.data
75
+ }
76
+
77
+ /**
78
+ * Parse postage stamps - Zod transforms handle type conversion
79
+ */
80
+ const parsePostageStampsV1: VersionParser<PostageStamp> = (data: unknown) => {
81
+ const result = z.array(PostageStampSchemaV1).safeParse(data)
82
+
83
+ if (!result.success) {
84
+ console.error("Parse failed:", result.error.format())
85
+ return []
86
+ }
87
+
88
+ return result.data
89
+ }
90
+
91
+ // ============================================================================
92
+ // Serializers
93
+ // ============================================================================
94
+
95
+ /**
96
+ * Serialize Account for storage
97
+ */
98
+ export function serializeAccount(account: Account): Record<string, unknown> {
99
+ if (account.type === "passkey") {
100
+ return {
101
+ id: account.id.toString(),
102
+ name: account.name,
103
+ createdAt: account.createdAt,
104
+ type: account.type,
105
+ credentialId: account.credentialId,
106
+ swarmEncryptionKey: account.swarmEncryptionKey,
107
+ defaultPostageStampBatchID:
108
+ account.defaultPostageStampBatchID?.toString(),
109
+ }
110
+ } else if (account.type === "agent") {
111
+ return {
112
+ id: account.id.toString(),
113
+ name: account.name,
114
+ createdAt: account.createdAt,
115
+ type: account.type,
116
+ swarmEncryptionKey: account.swarmEncryptionKey,
117
+ defaultPostageStampBatchID:
118
+ account.defaultPostageStampBatchID?.toString(),
119
+ }
120
+ } else {
121
+ return {
122
+ id: account.id.toString(),
123
+ name: account.name,
124
+ createdAt: account.createdAt,
125
+ type: account.type,
126
+ ethereumAddress: account.ethereumAddress.toString(),
127
+ encryptedMasterKey: Array.from(account.encryptedMasterKey.toUint8Array()),
128
+ encryptionSalt: Array.from(account.encryptionSalt.toUint8Array()),
129
+ encryptedSecretSeed: Array.from(
130
+ account.encryptedSecretSeed.toUint8Array(),
131
+ ),
132
+ swarmEncryptionKey: account.swarmEncryptionKey,
133
+ defaultPostageStampBatchID:
134
+ account.defaultPostageStampBatchID?.toString(),
135
+ }
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Serialize Identity for storage
141
+ */
142
+ export function serializeIdentity(identity: Identity): Record<string, unknown> {
143
+ return {
144
+ id: identity.id,
145
+ accountId: identity.accountId.toString(),
146
+ name: identity.name,
147
+ defaultPostageStampBatchID: identity.defaultPostageStampBatchID?.toString(),
148
+ createdAt: identity.createdAt,
149
+ settings: identity.settings,
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Serialize ConnectedApp for storage
155
+ */
156
+ export function serializeConnectedApp(
157
+ app: ConnectedApp,
158
+ ): Record<string, unknown> {
159
+ return {
160
+ appUrl: app.appUrl,
161
+ appName: app.appName,
162
+ lastConnectedAt: app.lastConnectedAt,
163
+ identityId: app.identityId,
164
+ appIcon: app.appIcon,
165
+ appDescription: app.appDescription,
166
+ connectedUntil: app.connectedUntil,
167
+ appSecret: app.appSecret,
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Serialize PostageStamp for storage
173
+ */
174
+ export function serializePostageStamp(
175
+ stamp: PostageStamp,
176
+ ): Record<string, unknown> {
177
+ return {
178
+ accountId: stamp.accountId,
179
+ batchID: stamp.batchID.toString(),
180
+ signerKey: stamp.signerKey.toString(),
181
+ utilization: stamp.utilization,
182
+ usable: stamp.usable,
183
+ depth: stamp.depth,
184
+ amount: stamp.amount.toString(), // Convert bigint to string for JSON
185
+ bucketDepth: stamp.bucketDepth,
186
+ blockNumber: stamp.blockNumber,
187
+ immutableFlag: stamp.immutableFlag,
188
+ exists: stamp.exists,
189
+ batchTTL: stamp.batchTTL,
190
+ createdAt: stamp.createdAt,
191
+ }
192
+ }
193
+
194
+ // ============================================================================
195
+ // Storage Manager Factories
196
+ // ============================================================================
197
+
198
+ /**
199
+ * Create storage manager for accounts
200
+ */
201
+ export function createAccountsStorageManager(): VersionedStorageManager<Account> {
202
+ return createLocalStorageManager<Account>({
203
+ key: STORAGE_KEY_ACCOUNTS,
204
+ currentVersion: 1,
205
+ parsers: {
206
+ 1: parseAccountsV1,
207
+ },
208
+ serializer: serializeAccount,
209
+ loggerName: "AccountsStorage",
210
+ })
211
+ }
212
+
213
+ /**
214
+ * Create storage manager for identities
215
+ */
216
+ export function createIdentitiesStorageManager(): VersionedStorageManager<Identity> {
217
+ return createLocalStorageManager<Identity>({
218
+ key: STORAGE_KEY_IDENTITIES,
219
+ currentVersion: 1,
220
+ parsers: {
221
+ 1: parseIdentitiesV1,
222
+ },
223
+ serializer: serializeIdentity,
224
+ loggerName: "IdentitiesStorage",
225
+ })
226
+ }
227
+
228
+ /**
229
+ * Create storage manager for connected apps
230
+ */
231
+ export function createConnectedAppsStorageManager(): VersionedStorageManager<ConnectedApp> {
232
+ return createLocalStorageManager<ConnectedApp>({
233
+ key: STORAGE_KEY_CONNECTED_APPS,
234
+ currentVersion: 1,
235
+ parsers: {
236
+ 1: parseConnectedAppsV1,
237
+ },
238
+ serializer: serializeConnectedApp,
239
+ loggerName: "ConnectedAppsStorage",
240
+ })
241
+ }
242
+
243
+ /**
244
+ * Invalidate all connected app entries for a given app URL.
245
+ * Sets lastConnectedAt to 0 and connectedUntil to undefined so
246
+ * isConnectionValid() returns false and reconnect won't happen on refresh.
247
+ */
248
+ export function disconnectApp(appUrl: string): void {
249
+ const manager = createConnectedAppsStorageManager()
250
+ const apps = manager.load()
251
+ const updated = apps.map((app) =>
252
+ app.appUrl === appUrl
253
+ ? { ...app, lastConnectedAt: 0, connectedUntil: undefined }
254
+ : app,
255
+ )
256
+ manager.save(updated)
257
+ }
258
+
259
+ /**
260
+ * Create storage manager for postage stamps
261
+ */
262
+ export function createPostageStampsStorageManager(): VersionedStorageManager<PostageStamp> {
263
+ return createLocalStorageManager<PostageStamp>({
264
+ key: STORAGE_KEY_POSTAGE_STAMPS,
265
+ currentVersion: 1,
266
+ parsers: {
267
+ 1: parsePostageStampsV1,
268
+ },
269
+ serializer: serializePostageStamp,
270
+ loggerName: "PostageStampsStorage",
271
+ })
272
+ }
273
+
274
+ // ============================================================================
275
+ // Network Settings Storage (Singleton)
276
+ // ============================================================================
277
+
278
+ /**
279
+ * Parse network settings - Zod validates URL format
280
+ */
281
+ function parseNetworkSettingsV1(data: unknown): NetworkSettings | undefined {
282
+ const result = NetworkSettingsSchemaV1.safeParse(data)
283
+
284
+ if (!result.success) {
285
+ console.error(
286
+ "[NetworkSettingsStorage] Parse failed:",
287
+ result.error.format(),
288
+ )
289
+ return undefined
290
+ }
291
+
292
+ return result.data
293
+ }
294
+
295
+ /**
296
+ * Serialize NetworkSettings for storage
297
+ */
298
+ export function serializeNetworkSettings(
299
+ settings: NetworkSettings,
300
+ ): Record<string, unknown> {
301
+ return {
302
+ beeNodeUrl: settings.beeNodeUrl,
303
+ gnosisRpcUrl: settings.gnosisRpcUrl,
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Singleton storage manager interface for network settings
309
+ */
310
+ export interface NetworkSettingsStorageManager {
311
+ load(): NetworkSettings | undefined
312
+ save(settings: NetworkSettings): void
313
+ clear(): void
314
+ }
315
+
316
+ /**
317
+ * Create storage manager for network settings (singleton)
318
+ * Unlike other storage managers, this stores a single object, not an array
319
+ */
320
+ export function createNetworkSettingsStorageManager(): NetworkSettingsStorageManager {
321
+ return {
322
+ load(): NetworkSettings | undefined {
323
+ if (typeof localStorage === "undefined") {
324
+ return undefined
325
+ }
326
+
327
+ const raw = localStorage.getItem(STORAGE_KEY_NETWORK_SETTINGS)
328
+ if (!raw) {
329
+ return undefined
330
+ }
331
+
332
+ try {
333
+ const parsed = JSON.parse(raw)
334
+ return parseNetworkSettingsV1(parsed)
335
+ } catch (e) {
336
+ console.error(
337
+ "[NetworkSettingsStorage] Failed to parse stored data:",
338
+ e,
339
+ )
340
+ return undefined
341
+ }
342
+ },
343
+
344
+ save(settings: NetworkSettings): void {
345
+ if (typeof localStorage === "undefined") {
346
+ console.warn("[NetworkSettingsStorage] localStorage not available")
347
+ return
348
+ }
349
+
350
+ const serialized = serializeNetworkSettings(settings)
351
+ localStorage.setItem(
352
+ STORAGE_KEY_NETWORK_SETTINGS,
353
+ JSON.stringify(serialized),
354
+ )
355
+ },
356
+
357
+ clear(): void {
358
+ if (typeof localStorage === "undefined") {
359
+ return
360
+ }
361
+
362
+ localStorage.removeItem(STORAGE_KEY_NETWORK_SETTINGS)
363
+ },
364
+ }
365
+ }