@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,413 @@
1
+ /**
2
+ * ACT History Management
3
+ *
4
+ * This module provides timestamped versioning of ACT entries, matching Bee's
5
+ * approach to tracking ACT versions over time.
6
+ *
7
+ * Key concepts:
8
+ * - Each ACT update creates a new history entry
9
+ * - Entries are keyed by reversed timestamp (MaxInt64 - timestamp)
10
+ * - History enables looking up ACT state at any point in time
11
+ * - Encrypted grantee list reference is stored in metadata
12
+ *
13
+ * This implementation uses the MantarayNode class from bee-js to produce
14
+ * Bee-compatible binary manifests with the proper version hash header.
15
+ *
16
+ * IMPORTANT: Mantaray manifests are hierarchical - each child node must be
17
+ * uploaded separately to Swarm. The root node references children by their
18
+ * content addresses (selfAddress). To read entries, all child nodes must be
19
+ * loaded recursively.
20
+ */
21
+
22
+ import { MantarayNode } from "@ethersphere/bee-js"
23
+ import { hexToUint8Array, uint8ArrayToHex } from "../../utils/hex"
24
+
25
+ // Constants
26
+ const MAX_INT64 = BigInt("9223372036854775807")
27
+ const ENCODER = new TextEncoder()
28
+
29
+ // Bee uses "encryptedglref" as the metadata key for encrypted grantee list reference
30
+ const ENCRYPTED_GRANTEE_LIST_METADATA_KEY = "encryptedglref"
31
+
32
+ // Ensure monotonic seconds so multiple ACT updates within the same second
33
+ // don't collide on the same history key (mantaray requires unique paths).
34
+ let lastTimestamp = 0
35
+
36
+ /**
37
+ * Single history entry metadata
38
+ */
39
+ export interface HistoryEntryMetadata {
40
+ actReference: string // Reference to the ACT manifest
41
+ encryptedGranteeListRef?: string // Reference to encrypted grantee list (publisher only)
42
+ }
43
+
44
+ /**
45
+ * History entry with timestamp
46
+ */
47
+ export interface HistoryEntry {
48
+ timestamp: number // Unix timestamp in seconds
49
+ metadata: HistoryEntryMetadata
50
+ }
51
+
52
+ /**
53
+ * Result of serializing a history tree
54
+ */
55
+ export interface SerializedHistoryTree {
56
+ blobs: Map<string, Uint8Array> // Content address -> serialized data
57
+ rootReference: string // Reference to the root node
58
+ }
59
+
60
+ /**
61
+ * Result of saving a history tree
62
+ */
63
+ export interface SaveHistoryTreeResult {
64
+ rootReference: string // Reference to the root node (from Bee)
65
+ tagUid?: number // Tag UID from root upload
66
+ }
67
+
68
+ /**
69
+ * Calculate reversed timestamp key for history lookup
70
+ *
71
+ * Bee uses reversed timestamps so that the latest entry sorts first.
72
+ * Key = MaxInt64 - timestamp
73
+ *
74
+ * @param timestamp - Unix timestamp in seconds
75
+ * @returns Reversed timestamp as string (for use as path)
76
+ */
77
+ export function calculateReversedTimestamp(timestamp: number): string {
78
+ const reversed = MAX_INT64 - BigInt(timestamp)
79
+ return reversed.toString()
80
+ }
81
+
82
+ /**
83
+ * Calculate original timestamp from reversed key
84
+ *
85
+ * @param reversedKey - Reversed timestamp string
86
+ * @returns Original Unix timestamp in seconds
87
+ */
88
+ export function calculateOriginalTimestamp(reversedKey: string): number {
89
+ const reversed = BigInt(reversedKey)
90
+ const original = MAX_INT64 - reversed
91
+ return Number(original)
92
+ }
93
+
94
+ /**
95
+ * Create a new empty history manifest using MantarayNode
96
+ */
97
+ export function createHistoryManifest(): MantarayNode {
98
+ return new MantarayNode()
99
+ }
100
+
101
+ /**
102
+ * Add an entry to the history manifest
103
+ *
104
+ * This mutates the manifest in place by adding a fork.
105
+ *
106
+ * @param manifest - Existing history manifest (MantarayNode)
107
+ * @param timestamp - Unix timestamp for this entry
108
+ * @param actReference - Reference to the ACT manifest (hex string)
109
+ * @param encryptedGranteeListRef - Optional reference to encrypted grantee list (hex string)
110
+ */
111
+ export function addHistoryEntry(
112
+ manifest: MantarayNode,
113
+ timestamp: number,
114
+ actReference: string,
115
+ encryptedGranteeListRef?: string,
116
+ ): void {
117
+ const path = calculateReversedTimestamp(timestamp)
118
+ const reference = hexToUint8Array(actReference)
119
+ const metadata = encryptedGranteeListRef
120
+ ? { [ENCRYPTED_GRANTEE_LIST_METADATA_KEY]: encryptedGranteeListRef }
121
+ : undefined
122
+ manifest.addFork(ENCODER.encode(path), reference, metadata)
123
+ }
124
+
125
+ /**
126
+ * Get the latest history entry (most recent timestamp)
127
+ *
128
+ * Since keys are reversed timestamps, the smallest key is the latest entry.
129
+ * NOTE: This requires the manifest to have been loaded with loadRecursively()
130
+ * or to have been populated locally with addHistoryEntry().
131
+ *
132
+ * @param manifest - History manifest (MantarayNode)
133
+ * @returns Latest entry with its timestamp, or undefined if empty
134
+ */
135
+ export function getLatestEntry(
136
+ manifest: MantarayNode,
137
+ ): HistoryEntry | undefined {
138
+ const nodes = manifest.collect()
139
+ if (nodes.length === 0) {
140
+ return undefined
141
+ }
142
+
143
+ // Sort by path ascending (smallest = latest timestamp due to reversal)
144
+ nodes.sort((a, b) => a.fullPathString.localeCompare(b.fullPathString))
145
+
146
+ const latest = nodes[0]
147
+ return {
148
+ timestamp: calculateOriginalTimestamp(latest.fullPathString),
149
+ metadata: {
150
+ actReference: uint8ArrayToHex(latest.targetAddress),
151
+ encryptedGranteeListRef:
152
+ latest.metadata?.[ENCRYPTED_GRANTEE_LIST_METADATA_KEY],
153
+ },
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Get entry at or before a specific timestamp
159
+ *
160
+ * This finds the ACT state that was valid at the given timestamp.
161
+ * NOTE: This requires the manifest to have been loaded with loadRecursively().
162
+ *
163
+ * @param manifest - History manifest (MantarayNode)
164
+ * @param timestamp - Target timestamp
165
+ * @returns Entry at or before timestamp, or undefined if none exists
166
+ */
167
+ export function getEntryAtTimestamp(
168
+ manifest: MantarayNode,
169
+ timestamp: number,
170
+ ): HistoryEntry | undefined {
171
+ const targetPath = calculateReversedTimestamp(timestamp)
172
+ const nodes = manifest.collect()
173
+
174
+ if (nodes.length === 0) {
175
+ return undefined
176
+ }
177
+
178
+ // Sort by path ascending
179
+ nodes.sort((a, b) => a.fullPathString.localeCompare(b.fullPathString))
180
+
181
+ // Find the first node with path >= targetPath (which corresponds to timestamp <= target)
182
+ for (const node of nodes) {
183
+ if (node.fullPathString >= targetPath) {
184
+ return {
185
+ timestamp: calculateOriginalTimestamp(node.fullPathString),
186
+ metadata: {
187
+ actReference: uint8ArrayToHex(node.targetAddress),
188
+ encryptedGranteeListRef:
189
+ node.metadata?.[ENCRYPTED_GRANTEE_LIST_METADATA_KEY],
190
+ },
191
+ }
192
+ }
193
+ }
194
+
195
+ // If no entry at or before timestamp, return the oldest entry
196
+ // (this is the "first" ACT state)
197
+ const oldest = nodes[nodes.length - 1]
198
+ return {
199
+ timestamp: calculateOriginalTimestamp(oldest.fullPathString),
200
+ metadata: {
201
+ actReference: uint8ArrayToHex(oldest.targetAddress),
202
+ encryptedGranteeListRef:
203
+ oldest.metadata?.[ENCRYPTED_GRANTEE_LIST_METADATA_KEY],
204
+ },
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Get all history entries sorted by timestamp (newest first)
210
+ *
211
+ * @param manifest - History manifest (MantarayNode)
212
+ * @returns Array of entries sorted newest first
213
+ */
214
+ export function getAllEntries(manifest: MantarayNode): HistoryEntry[] {
215
+ const nodes = manifest.collect()
216
+
217
+ // Sort by path ascending (newest first due to reversed timestamps)
218
+ nodes.sort((a, b) => a.fullPathString.localeCompare(b.fullPathString))
219
+
220
+ return nodes.map((node) => ({
221
+ timestamp: calculateOriginalTimestamp(node.fullPathString),
222
+ metadata: {
223
+ actReference: uint8ArrayToHex(node.targetAddress),
224
+ encryptedGranteeListRef:
225
+ node.metadata?.[ENCRYPTED_GRANTEE_LIST_METADATA_KEY],
226
+ },
227
+ }))
228
+ }
229
+
230
+ /**
231
+ * Serialize the entire history manifest tree to individual blobs
232
+ *
233
+ * Mantaray manifests are hierarchical - each node is stored at its content
234
+ * address. This function returns all blobs that need to be uploaded, keyed
235
+ * by their content addresses.
236
+ *
237
+ * @deprecated Use saveHistoryTreeRecursively instead which uploads bottom-up
238
+ * and uses Bee's actual returned references to avoid address mismatches.
239
+ *
240
+ * @param manifest - History manifest (MantarayNode)
241
+ * @returns Map of content address -> serialized data, plus root reference
242
+ */
243
+ export async function serializeHistoryTree(
244
+ manifest: MantarayNode,
245
+ ): Promise<SerializedHistoryTree> {
246
+ const blobs = new Map<string, Uint8Array>()
247
+
248
+ async function marshalRecursively(node: MantarayNode): Promise<void> {
249
+ // First, marshal all child nodes
250
+ for (const fork of node.forks.values()) {
251
+ await marshalRecursively(fork.node)
252
+ }
253
+
254
+ // Now marshal this node (which will use children's selfAddresses)
255
+ const data = await node.marshal()
256
+ const selfAddress = await node.calculateSelfAddress()
257
+ node.selfAddress = selfAddress.toUint8Array()
258
+ blobs.set(selfAddress.toHex(), data)
259
+ }
260
+
261
+ await marshalRecursively(manifest)
262
+
263
+ const rootRef = uint8ArrayToHex(manifest.selfAddress!)
264
+ return { blobs, rootReference: rootRef }
265
+ }
266
+
267
+ /**
268
+ * Upload callback type for saveHistoryTreeRecursively
269
+ */
270
+ export type UploadCallback = (
271
+ data: Uint8Array,
272
+ isRoot: boolean,
273
+ ) => Promise<{ reference: string; tagUid?: number }>
274
+
275
+ /**
276
+ * Save the entire history manifest tree by uploading bottom-up
277
+ *
278
+ * This function uploads nodes in the correct order (children before parents)
279
+ * and uses Bee's actual returned references to update selfAddress before
280
+ * marshaling parents. This avoids address mismatches between local hash
281
+ * computation and Bee's storage.
282
+ *
283
+ * The flow mirrors MantarayNode.saveRecursively() from bee-js:
284
+ * 1. Recursively save all child forks first
285
+ * 2. Marshal this node (which uses children's updated selfAddress)
286
+ * 3. Upload and set selfAddress from Bee's response
287
+ *
288
+ * @param manifest - History manifest (MantarayNode)
289
+ * @param uploadFn - Callback to upload data, returns reference from Bee
290
+ * @returns Root reference from Bee and optional tag UID
291
+ */
292
+ export async function saveHistoryTreeRecursively(
293
+ manifest: MantarayNode,
294
+ uploadFn: UploadCallback,
295
+ ): Promise<SaveHistoryTreeResult> {
296
+ async function saveRecursively(
297
+ node: MantarayNode,
298
+ isRoot: boolean,
299
+ ): Promise<{ reference: string; tagUid?: number }> {
300
+ // First, save all child forks recursively
301
+ for (const fork of node.forks.values()) {
302
+ await saveRecursively(fork.node, false)
303
+ }
304
+
305
+ // Now marshal this node - children's selfAddress should be set from their uploads
306
+ const data = await node.marshal()
307
+
308
+ // Upload and get Bee's actual reference
309
+ const result = await uploadFn(data, isRoot)
310
+
311
+ // Update selfAddress with Bee's reference (critical for parent marshaling)
312
+ node.selfAddress = hexToUint8Array(result.reference)
313
+
314
+ return result
315
+ }
316
+
317
+ const result = await saveRecursively(manifest, true)
318
+
319
+ return {
320
+ rootReference: result.reference,
321
+ tagUid: result.tagUid,
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Serialize history manifest root to Mantaray binary format
327
+ *
328
+ * @deprecated Use serializeHistoryTree for proper Mantaray serialization
329
+ * @param manifest - History manifest (MantarayNode)
330
+ * @returns Serialized root manifest as Uint8Array
331
+ */
332
+ export async function serializeHistory(
333
+ manifest: MantarayNode,
334
+ ): Promise<Uint8Array> {
335
+ return manifest.marshal()
336
+ }
337
+
338
+ /**
339
+ * Deserialize history manifest from Mantaray binary format
340
+ *
341
+ * NOTE: After deserialization, call loadRecursively() or manually load
342
+ * child nodes to populate targetAddress for entries.
343
+ *
344
+ * @param data - Serialized manifest
345
+ * @param selfAddress - The reference/address of the manifest (32 bytes as Uint8Array)
346
+ * @returns Parsed history manifest (MantarayNode)
347
+ */
348
+ export function deserializeHistory(
349
+ data: Uint8Array,
350
+ selfAddress: Uint8Array,
351
+ ): MantarayNode {
352
+ return MantarayNode.unmarshalFromData(data, selfAddress)
353
+ }
354
+
355
+ /**
356
+ * Load child node data into a deserialized manifest
357
+ *
358
+ * After deserializing the root node, this function loads all child node data
359
+ * so that targetAddress is available for each entry.
360
+ *
361
+ * @param manifest - Deserialized root manifest
362
+ * @param loadData - Callback to load data for a given reference
363
+ */
364
+ export async function loadHistoryEntries(
365
+ manifest: MantarayNode,
366
+ loadData: (reference: string) => Promise<Uint8Array>,
367
+ ): Promise<void> {
368
+ async function loadRecursively(node: MantarayNode): Promise<void> {
369
+ for (const fork of node.forks.values()) {
370
+ if (!fork.node.selfAddress) {
371
+ throw new Error("Fork node selfAddress is not set")
372
+ }
373
+
374
+ // Load the child node data
375
+ const childRef = uint8ArrayToHex(fork.node.selfAddress)
376
+ const childData = await loadData(childRef)
377
+
378
+ // Deserialize the child node
379
+ const childNode = MantarayNode.unmarshalFromData(
380
+ childData,
381
+ fork.node.selfAddress,
382
+ )
383
+
384
+ // Copy the loaded data to the fork node
385
+ fork.node.targetAddress = childNode.targetAddress
386
+ fork.node.forks = childNode.forks
387
+ fork.node.obfuscationKey = childNode.obfuscationKey
388
+
389
+ // Fix parent pointers for nested forks
390
+ for (const nestedFork of fork.node.forks.values()) {
391
+ nestedFork.node.parent = fork.node
392
+ }
393
+
394
+ // Recursively load any nested forks
395
+ await loadRecursively(fork.node)
396
+ }
397
+ }
398
+
399
+ await loadRecursively(manifest)
400
+ }
401
+
402
+ /**
403
+ * Get current Unix timestamp in seconds
404
+ */
405
+ export function getCurrentTimestamp(): number {
406
+ const now = Math.floor(Date.now() / 1000)
407
+ if (now <= lastTimestamp) {
408
+ lastTimestamp += 1
409
+ return lastTimestamp
410
+ }
411
+ lastTimestamp = now
412
+ return now
413
+ }