@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,679 @@
1
+ import { Reference, MantarayNode, NULL_ADDRESS } from "@ethersphere/bee-js"
2
+ import { makeContentAddressedChunk } from "../chunk"
3
+ import { hexToUint8Array } from "../utils/hex"
4
+
5
+ /**
6
+ * Mantaray v0.2 version hash (31 bytes).
7
+ * From bee-js: the first 31 bytes of keccak256("mantaray:0.2")
8
+ */
9
+ const MANTARAY_VERSION_HASH = new Uint8Array([
10
+ 0x57, 0x68, 0xb3, 0xb6, 0xa7, 0xdb, 0x56, 0xd2, 0x1d, 0x1a, 0xbf, 0xf4, 0x0d,
11
+ 0x41, 0xce, 0xbf, 0xc8, 0x34, 0x48, 0xfe, 0xd8, 0xd7, 0xe9, 0xb0, 0x6e, 0xc0,
12
+ 0xd3, 0xb0, 0x73, 0xf2, 0x8f,
13
+ ])
14
+
15
+ /**
16
+ * Fork flags for Mantaray format.
17
+ */
18
+ const FORK_FLAG_VALUE_TYPE = 0x02 // TYPE_VALUE: has targetAddress (matches bee-js)
19
+ const FORK_FLAG_HAS_METADATA = 0x10 // TYPE_WITH_METADATA: has metadata bytes (matches bee-js)
20
+
21
+ /**
22
+ * Result of building a /bzz/-compatible manifest.
23
+ *
24
+ * This is a FLAT manifest with no child nodes - just the root manifest.
25
+ * Bee's /bzz/ endpoint can directly resolve paths from this structure.
26
+ *
27
+ * IMPORTANT: The `data` field contains RAW manifest bytes (without span prefix).
28
+ * When uploaded via `uploadData()`, span will be added and the resulting address
29
+ * will match the pre-computed `address` field.
30
+ */
31
+ export interface BzzCompatibleManifestResult {
32
+ /** The manifest chunk to upload. Raw bytes, no span. */
33
+ manifestChunk: {
34
+ data: Uint8Array
35
+ address: string
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Build a /bzz/-compatible flat manifest with VALUE forks.
41
+ *
42
+ * Creates a flat manifest with two VALUE forks at root level:
43
+ * - "/" → VALUE fork with website-index-document metadata pointing to NULL
44
+ * - "index.bin" → VALUE fork pointing directly to content reference
45
+ *
46
+ * This is built manually because bee-js MantarayNode always creates EDGE forks
47
+ * with child nodes, which causes Bee to fail to resolve the content.
48
+ *
49
+ * When Bee accesses /bzz/manifest/:
50
+ * 1. Finds "/" fork, reads website-index-document metadata
51
+ * 2. Looks up "index.bin" fork at root level
52
+ * 3. "index.bin" is VALUE type → serves content directly
53
+ *
54
+ * Binary format (Mantaray v0.2):
55
+ * - obfuscationKey (32 bytes): All zeros for no obfuscation
56
+ * - versionHash (31 bytes): MANTARAY_VERSION_HASH
57
+ * - refBytesCount (1 byte): 32
58
+ * - entry (32 bytes): All zeros (root has no entry)
59
+ * - indexBitmap (32 bytes): Bits set for '/' (47) and 'i' (105)
60
+ * - fork1 (64+ bytes): "/" fork with metadata
61
+ * - fork2 (64+ bytes): "index.bin" fork with metadata
62
+ *
63
+ * @param contentReference - Reference to the actual content (32 bytes / 64 hex chars)
64
+ * @returns Single manifest chunk to upload
65
+ */
66
+ export async function buildBzzCompatibleManifest(
67
+ contentReference: string | Uint8Array,
68
+ ): Promise<BzzCompatibleManifestResult> {
69
+ const refBytes =
70
+ contentReference instanceof Uint8Array
71
+ ? contentReference
72
+ : new Reference(contentReference).toUint8Array()
73
+
74
+ if (refBytes.length !== 32) {
75
+ throw new Error(
76
+ `Invalid content reference length: ${refBytes.length} (expected 32 bytes)`,
77
+ )
78
+ }
79
+
80
+ // Prepare metadata JSON for both forks
81
+ const slashMetadata =
82
+ JSON.stringify({ "website-index-document": "index.bin" }) + "\n"
83
+ const indexMetadata =
84
+ JSON.stringify({
85
+ "Content-Type": "application/octet-stream",
86
+ Filename: "index.bin",
87
+ }) + "\n"
88
+
89
+ const slashMetadataBytes = new TextEncoder().encode(slashMetadata)
90
+ const indexMetadataBytes = new TextEncoder().encode(indexMetadata)
91
+
92
+ // Calculate sizes
93
+ // Header: 32 + 31 + 1 + 32 + 32 = 128 bytes
94
+ // Fork "/" : flags(1) + prefixLen(1) + prefix(30) + reference(32) + metaLen(2) + meta
95
+ // Fork "index.bin": flags(1) + prefixLen(1) + prefix(30) + reference(32) + metaLen(2) + meta
96
+ const HEADER_SIZE = 128
97
+ const FORK_BASE_SIZE = 66 // 1 + 1 + 30 + 32 + 2
98
+ const TOTAL_SIZE =
99
+ HEADER_SIZE +
100
+ FORK_BASE_SIZE +
101
+ slashMetadataBytes.length +
102
+ FORK_BASE_SIZE +
103
+ indexMetadataBytes.length
104
+
105
+ const manifest = new Uint8Array(TOTAL_SIZE)
106
+ let offset = 0
107
+
108
+ // 1. Obfuscation key (32 bytes) - all zeros for no obfuscation
109
+ offset += 32
110
+
111
+ // 2. Version hash (31 bytes)
112
+ manifest.set(MANTARAY_VERSION_HASH, offset)
113
+ offset += 31
114
+
115
+ // 3. refBytesCount (1 byte) - 32 for standard reference size
116
+ manifest[offset] = 32
117
+ offset += 1
118
+
119
+ // 4. Entry (32 bytes) - all zeros for root node
120
+ offset += 32
121
+
122
+ // 5. Index bitmap (32 bytes)
123
+ // Bit 47 set for '/' and bit 105 set for 'i' (start of "index.bin")
124
+ // Byte 47/8 = 5, bit 47%8 = 7 → byte 5 = 0x80
125
+ // Byte 105/8 = 13, bit 105%8 = 1 → byte 13 = 0x02
126
+ manifest[offset + 5] = 0x80 // '/' at position 47
127
+ manifest[offset + 13] = 0x02 // 'i' at position 105
128
+ offset += 32
129
+
130
+ // Forks are ordered by their first byte value
131
+ // '/' = 47 comes before 'i' = 105
132
+
133
+ // 6. Fork entry for '/' (first because 47 < 105)
134
+ // Flags: TYPE_WITH_METADATA only (0x10) - NO VALUE since reference is NULL
135
+ // The "/" fork is just a metadata carrier for website-index-document
136
+ manifest[offset] = FORK_FLAG_HAS_METADATA
137
+ offset += 1
138
+
139
+ // Prefix length
140
+ manifest[offset] = 1
141
+ offset += 1
142
+
143
+ // Prefix (30 bytes padded)
144
+ manifest[offset] = 0x2f // '/'
145
+ offset += 30
146
+
147
+ // Reference (32 bytes) - NULL_ADDRESS (all zeros) because "/" just has metadata
148
+ // Already zeros, just advance
149
+ offset += 32
150
+
151
+ // Metadata length (2 bytes big-endian)
152
+ manifest[offset] = (slashMetadataBytes.length >> 8) & 0xff
153
+ manifest[offset + 1] = slashMetadataBytes.length & 0xff
154
+ offset += 2
155
+
156
+ // Metadata content
157
+ manifest.set(slashMetadataBytes, offset)
158
+ offset += slashMetadataBytes.length
159
+
160
+ // 7. Fork entry for 'index.bin'
161
+ // Flags: TYPE_VALUE (0x02) + TYPE_WITH_METADATA (0x10) = 0x12
162
+ manifest[offset] = FORK_FLAG_VALUE_TYPE | FORK_FLAG_HAS_METADATA
163
+ offset += 1
164
+
165
+ // Prefix length - "index.bin" = 9 characters
166
+ manifest[offset] = 9
167
+ offset += 1
168
+
169
+ // Prefix (30 bytes padded)
170
+ const indexBinBytes = new TextEncoder().encode("index.bin")
171
+ manifest.set(indexBinBytes, offset)
172
+ offset += 30
173
+
174
+ // Reference (32 bytes) - the actual content reference
175
+ manifest.set(refBytes, offset)
176
+ offset += 32
177
+
178
+ // Metadata length (2 bytes big-endian)
179
+ manifest[offset] = (indexMetadataBytes.length >> 8) & 0xff
180
+ manifest[offset + 1] = indexMetadataBytes.length & 0xff
181
+ offset += 2
182
+
183
+ // Metadata content
184
+ manifest.set(indexMetadataBytes, offset)
185
+ offset += indexMetadataBytes.length
186
+
187
+ // Calculate manifest address
188
+ const rootChunk = makeContentAddressedChunk(manifest)
189
+
190
+ return {
191
+ manifestChunk: {
192
+ data: manifest,
193
+ address: rootChunk.address.toHex(),
194
+ },
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Result of building a /bzz/-compatible MantarayNode manifest.
200
+ */
201
+ export interface BzzManifestNodeResult {
202
+ /** The MantarayNode to be uploaded with saveMantarayTreeRecursively */
203
+ manifestNode: MantarayNode
204
+ }
205
+
206
+ /**
207
+ * Build a /bzz/-compatible manifest as a MantarayNode.
208
+ *
209
+ * This function creates a MantarayNode with the proper structure for /bzz/ access:
210
+ * - "/" fork with website-index-document metadata pointing to "index.bin"
211
+ * - "index.bin" fork pointing to the actual content reference
212
+ *
213
+ * IMPORTANT: After calling this function, use saveMantarayTreeRecursively() to
214
+ * upload the manifest. This ensures all child nodes are uploaded bottom-up and
215
+ * their addresses are correctly set from Bee's responses.
216
+ *
217
+ * Usage:
218
+ * ```typescript
219
+ * const { manifestNode } = buildBzzManifestNode(contentReference)
220
+ * const result = await saveMantarayTreeRecursively(manifestNode, async (data, isRoot) => {
221
+ * const uploadResult = await uploadData(bee, stamper, data, uploadOptions)
222
+ * return { reference: uploadResult.reference }
223
+ * })
224
+ * // result.rootReference is the /bzz/ compatible manifest address
225
+ * ```
226
+ *
227
+ * @param contentReference - Reference to the actual content (32 bytes / 64 hex chars)
228
+ * @returns MantarayNode ready for upload with saveMantarayTreeRecursively
229
+ */
230
+ export function buildBzzManifestNode(
231
+ contentReference: string | Uint8Array,
232
+ ): BzzManifestNodeResult {
233
+ const refBytes =
234
+ contentReference instanceof Uint8Array
235
+ ? contentReference
236
+ : hexToUint8Array(contentReference)
237
+
238
+ if (refBytes.length !== 32) {
239
+ throw new Error(
240
+ `Invalid content reference length: ${refBytes.length} (expected 32 bytes)`,
241
+ )
242
+ }
243
+
244
+ const manifest = new MantarayNode()
245
+
246
+ // Add "/" fork with website-index-document metadata
247
+ // This tells Bee to look for "index.bin" when accessing the root path
248
+ manifest.addFork("/", NULL_ADDRESS, {
249
+ "website-index-document": "index.bin",
250
+ })
251
+
252
+ // Add "index.bin" fork pointing to the actual content
253
+ manifest.addFork("index.bin", refBytes, {
254
+ "Content-Type": "application/octet-stream",
255
+ Filename: "index.bin",
256
+ })
257
+
258
+ return { manifestNode: manifest }
259
+ }
260
+
261
+ /**
262
+ * @deprecated Use buildBzzManifestNode() with saveMantarayTreeRecursively() instead.
263
+ * This function manually builds binary format but doesn't upload child nodes,
264
+ * which causes Bee to fail to resolve content via /bzz/.
265
+ */
266
+
267
+ /**
268
+ * @deprecated Use buildBzzCompatibleManifest() for /bzz/ compatibility.
269
+ * This function creates a manifest that won't work with Bee's /bzz/ endpoint
270
+ * because it embeds the content reference directly in the manifest instead
271
+ * of using the proper two-level structure (root → child → content).
272
+ *
273
+ * Build a minimal mantaray manifest for /bzz/ feed compatibility.
274
+ *
275
+ * This creates a manifest with a single "/" fork pointing to a content reference.
276
+ * The manifest is built manually (not using bee-js MantarayNode.marshal()) because
277
+ * bee-js doesn't embed the targetAddress correctly for value-type nodes.
278
+ *
279
+ * Binary format (Mantaray v0.2):
280
+ * - obfuscationKey (32 bytes): All zeros for no obfuscation
281
+ * - versionHash (31 bytes): MANTARAY_VERSION_HASH
282
+ * - refBytesCount (1 byte): 32 (size of entry reference)
283
+ * - entry (32 bytes): All zeros (root has no entry)
284
+ * - indexBitmap (32 bytes): Bitmap with bit 47 set (for '/')
285
+ * - fork (64+ bytes): flags + prefixLen + prefix(30) + reference(32) + metadata
286
+ *
287
+ * @param contentReference - Reference to the actual content (32 bytes / 64 hex chars)
288
+ * @returns Marshaled manifest bytes
289
+ */
290
+ export function buildMinimalManifest(
291
+ contentReference: string | Uint8Array,
292
+ ): Uint8Array {
293
+ // Normalize reference to Uint8Array
294
+ const refBytes =
295
+ contentReference instanceof Uint8Array
296
+ ? contentReference
297
+ : new Reference(contentReference).toUint8Array()
298
+
299
+ if (refBytes.length !== 32) {
300
+ throw new Error(
301
+ `Invalid content reference length: ${refBytes.length} (expected 32 bytes)`,
302
+ )
303
+ }
304
+
305
+ // Calculate total size
306
+ // Header: 32 + 31 + 1 + 32 + 32 = 128 bytes
307
+ // Fork: 1 + 1 + 30 + 32 = 64 bytes (no metadata for simplicity)
308
+ // Total: 192 bytes
309
+ const HEADER_SIZE = 128
310
+ const FORK_SIZE = 64
311
+ const TOTAL_SIZE = HEADER_SIZE + FORK_SIZE
312
+
313
+ const manifest = new Uint8Array(TOTAL_SIZE)
314
+ let offset = 0
315
+
316
+ // 1. Obfuscation key (32 bytes) - all zeros for no obfuscation
317
+ offset += 32
318
+
319
+ // 2. Version hash (31 bytes)
320
+ manifest.set(MANTARAY_VERSION_HASH, offset)
321
+ offset += 31
322
+
323
+ // 3. refBytesCount (1 byte) - 32 for standard reference size
324
+ manifest[offset] = 32
325
+ offset += 1
326
+
327
+ // 4. Entry (32 bytes) - all zeros for root node
328
+ offset += 32
329
+
330
+ // 5. Index bitmap (32 bytes) - bit 47 set for '/'
331
+ // Byte index = 47 / 8 = 5, bit position = 47 % 8 = 7 (MSB)
332
+ // So we set byte 5 to 0x80 (bit 7 set)
333
+ manifest[offset + 5] = 0x80
334
+ offset += 32
335
+
336
+ // 6. Fork entry for '/'
337
+ // Flags: VALUE_TYPE (reference is the target)
338
+ manifest[offset] = FORK_FLAG_VALUE_TYPE
339
+ offset += 1
340
+
341
+ // Prefix length
342
+ manifest[offset] = 1
343
+ offset += 1
344
+
345
+ // Prefix (30 bytes padded)
346
+ manifest[offset] = 0x2f // '/'
347
+ offset += 30
348
+
349
+ // Reference (32 bytes) - the actual content reference
350
+ manifest.set(refBytes, offset)
351
+ offset += 32
352
+
353
+ return manifest
354
+ }
355
+
356
+ /**
357
+ * Maximum CAC payload size for /bzz/ compatible feed uploads.
358
+ *
359
+ * For the /chunks endpoint to detect SOC correctly, total SOC size must be > 4104 bytes.
360
+ * SOC structure: identifier(32) + signature(65) + span(8) + payload(N)
361
+ * For N=4096: total = 32 + 65 + 8 + 4096 = 4201 bytes > 4104 ✓
362
+ */
363
+ export const MAX_PADDED_PAYLOAD_SIZE = 4096
364
+
365
+ /**
366
+ * Pad payload to 4096 bytes for SOC detection by /chunks endpoint.
367
+ *
368
+ * The span field in the CAC contains the actual payload size (before padding),
369
+ * so Bee's joiner will only read the actual data and ignore padding.
370
+ *
371
+ * @param payload - Original payload (must be <= 4096 bytes)
372
+ * @returns Padded payload (exactly 4096 bytes)
373
+ */
374
+ export function padPayloadForSOCDetection(payload: Uint8Array): Uint8Array {
375
+ if (payload.length > MAX_PADDED_PAYLOAD_SIZE) {
376
+ throw new Error(
377
+ `Payload too large to pad: ${payload.length} > ${MAX_PADDED_PAYLOAD_SIZE}`,
378
+ )
379
+ }
380
+
381
+ if (payload.length === MAX_PADDED_PAYLOAD_SIZE) {
382
+ return payload // Already at max size
383
+ }
384
+
385
+ // Create padded buffer with zeros
386
+ const padded = new Uint8Array(MAX_PADDED_PAYLOAD_SIZE)
387
+ padded.set(payload, 0)
388
+
389
+ return padded
390
+ }
391
+
392
+ /**
393
+ * Extract content reference from a minimal mantaray manifest.
394
+ *
395
+ * This is the reverse of buildMinimalManifest() - it parses the manifest
396
+ * binary format and extracts the "/" fork's targetAddress.
397
+ *
398
+ * Binary format (Mantaray v0.2):
399
+ * - Offset 0-31: obfuscationKey
400
+ * - Offset 32-62: versionHash (31 bytes)
401
+ * - Offset 63: refBytesCount
402
+ * - Offset 64-95: entry (refBytesCount bytes, assuming 32)
403
+ * - Offset 96-127: indexBitmap
404
+ * - Offset 128+: forks (flags + prefixLen + prefix + reference + metadata)
405
+ *
406
+ * For a minimal manifest with just "/" fork:
407
+ * - Fork flags at offset 128
408
+ * - Prefix length at offset 129
409
+ * - Prefix (30 bytes) at offset 130
410
+ * - Reference (32 bytes) at offset 160
411
+ *
412
+ * @param manifestData - Raw manifest bytes (from feed payload)
413
+ * @returns Content reference as 64 hex characters
414
+ */
415
+ export function extractReferenceFromManifest(manifestData: Uint8Array): string {
416
+ // Minimum size check: header (128) + minimal fork (64)
417
+ if (manifestData.length < 192) {
418
+ throw new Error(
419
+ `Manifest too small: ${manifestData.length} bytes (expected at least 192)`,
420
+ )
421
+ }
422
+
423
+ // Header offsets (for reference)
424
+ // Offset 0-31: obfuscationKey
425
+ // Offset 32-62: versionHash (31 bytes)
426
+ // Offset 63: refBytesCount
427
+ // Offset 64-95: entry (refBytesCount bytes, assuming 32)
428
+ // Offset 96-127: indexBitmap
429
+ // Offset 128+: forks
430
+ const REF_BYTES_COUNT_OFFSET = 63
431
+ const INDEX_BITMAP_OFFSET = 96
432
+ const FORKS_OFFSET = 128
433
+
434
+ // Read refBytesCount to determine entry size
435
+ const refBytesCount = manifestData[REF_BYTES_COUNT_OFFSET]
436
+ if (refBytesCount !== 32) {
437
+ console.warn(
438
+ `[ManifestBuilder] Unexpected refBytesCount: ${refBytesCount} (expected 32)`,
439
+ )
440
+ }
441
+
442
+ // Verify "/" fork exists in index bitmap
443
+ // Byte 5, bit 7 should be set for '/' (byte value 47)
444
+ // Bit ordering: value N is at byte N/8, bit N%8
445
+ // So value 47 is at byte 5, bit 7 (mask 0x80)
446
+ const indexBitmap = manifestData.slice(
447
+ INDEX_BITMAP_OFFSET,
448
+ INDEX_BITMAP_OFFSET + 32,
449
+ )
450
+ const slashByteIndex = Math.floor(47 / 8) // = 5
451
+ const slashBitMask = 1 << (47 % 8) // = 0x80
452
+
453
+ if ((indexBitmap[slashByteIndex] & slashBitMask) === 0) {
454
+ throw new Error('Manifest does not contain "/" fork')
455
+ }
456
+
457
+ // Count forks before "/" to find its offset
458
+ // We need to count all set bits for byte values less than 47
459
+ let forkIndex = 0
460
+ // Count all bits in bytes 0-4
461
+ for (let byte = 0; byte < slashByteIndex; byte++) {
462
+ forkIndex += popcount8(indexBitmap[byte])
463
+ }
464
+ // Count bits in byte 5 for values 40-46 (bits 0-6)
465
+ for (let bit = 0; bit < 47 % 8; bit++) {
466
+ if (indexBitmap[slashByteIndex] & (1 << bit)) {
467
+ forkIndex++
468
+ }
469
+ }
470
+
471
+ // Calculate fork offset
472
+ // Each fork is: flags(1) + prefixLen(1) + prefix(30) + reference(32) + metadata(variable)
473
+ // For simplicity, assume all forks before "/" have the same minimal structure
474
+ // In a minimal manifest, "/" should be the first (and only) fork
475
+
476
+ let forkOffset = FORKS_OFFSET
477
+
478
+ // Skip previous forks if any
479
+ for (let i = 0; i < forkIndex; i++) {
480
+ // Read flags to check if metadata exists
481
+ const flags = manifestData[forkOffset]
482
+ const hasMetadata = (flags & FORK_FLAG_HAS_METADATA) !== 0
483
+
484
+ forkOffset += 1 + 1 + 30 + 32 // flags + prefixLen + prefix + reference
485
+
486
+ if (hasMetadata) {
487
+ // Read metadata length (2 bytes big-endian)
488
+ const metadataLen =
489
+ (manifestData[forkOffset] << 8) | manifestData[forkOffset + 1]
490
+ forkOffset += 2 + metadataLen
491
+ }
492
+ }
493
+
494
+ // Now at the "/" fork
495
+ const prefixLen = manifestData[forkOffset + 1]
496
+
497
+ // Verify it's actually "/"
498
+ if (prefixLen !== 1 || manifestData[forkOffset + 2] !== 0x2f) {
499
+ throw new Error(
500
+ `Expected "/" prefix, got length ${prefixLen} and byte 0x${manifestData[forkOffset + 2].toString(16)}`,
501
+ )
502
+ }
503
+
504
+ // Reference is at offset: forkOffset + 1 (flags) + 1 (prefixLen) + 30 (prefix) = forkOffset + 32
505
+ const referenceOffset = forkOffset + 32
506
+ const reference = manifestData.slice(referenceOffset, referenceOffset + 32)
507
+
508
+ if (isAllZeros(reference)) {
509
+ throw new Error('Manifest "/" fork has zero reference')
510
+ }
511
+
512
+ const hexRef = new Reference(reference).toHex()
513
+
514
+ return hexRef
515
+ }
516
+
517
+ /**
518
+ * Count number of set bits in a byte (population count).
519
+ */
520
+ function popcount8(n: number): number {
521
+ let count = 0
522
+ while (n) {
523
+ count += n & 1
524
+ n >>>= 1
525
+ }
526
+ return count
527
+ }
528
+
529
+ /**
530
+ * Check if a Uint8Array is all zeros.
531
+ */
532
+ function isAllZeros(arr: Uint8Array): boolean {
533
+ for (let i = 0; i < arr.length; i++) {
534
+ if (arr[i] !== 0) return false
535
+ }
536
+ return true
537
+ }
538
+
539
+ /**
540
+ * Extract entry (targetAddress) from a leaf manifest node.
541
+ *
542
+ * In Mantaray v0.2, leaf nodes store their targetAddress in the entry field
543
+ * (bytes 64-95), not in a fork. This is used for the second level of parsing
544
+ * when using buildBzzCompatibleManifest().
545
+ *
546
+ * Binary format (Mantaray v0.2):
547
+ * - Offset 0-31: obfuscationKey
548
+ * - Offset 32-62: versionHash (31 bytes)
549
+ * - Offset 63: refBytesCount
550
+ * - Offset 64-95: entry (32 bytes) ← targetAddress for leaf nodes
551
+ * - Offset 96-127: indexBitmap
552
+ * - Offset 128+: forks
553
+ *
554
+ * @param manifestData - Raw manifest bytes (child chunk data from two-level structure)
555
+ * @returns Content reference as 64 hex characters
556
+ */
557
+ export function extractEntryFromManifest(manifestData: Uint8Array): string {
558
+ if (manifestData.length < 96) {
559
+ throw new Error(
560
+ `Manifest too small: ${manifestData.length} bytes (expected at least 96)`,
561
+ )
562
+ }
563
+
564
+ const ENTRY_OFFSET = 64
565
+ const entry = manifestData.slice(ENTRY_OFFSET, ENTRY_OFFSET + 32)
566
+
567
+ if (isAllZeros(entry)) {
568
+ throw new Error("Manifest entry is zero")
569
+ }
570
+
571
+ const hexRef = new Reference(entry).toHex()
572
+
573
+ return hexRef
574
+ }
575
+
576
+ /**
577
+ * Extract content reference from a flat /bzz/-compatible manifest.
578
+ *
579
+ * In the flat structure, the manifest has:
580
+ * - "/" fork with NULL_ADDRESS and website-index-document metadata
581
+ * - "index.bin" fork with VALUE type pointing to content reference
582
+ *
583
+ * This function looks for the "index.bin" fork ('i' = ASCII 105) and
584
+ * extracts its reference.
585
+ *
586
+ * @param manifestData - Raw manifest bytes (flat manifest)
587
+ * @returns Content reference as 64 hex characters
588
+ */
589
+ export function extractContentFromFlatManifest(
590
+ manifestData: Uint8Array,
591
+ ): string {
592
+ if (manifestData.length < 192) {
593
+ throw new Error(
594
+ `Manifest too small: ${manifestData.length} bytes (expected at least 192)`,
595
+ )
596
+ }
597
+
598
+ const INDEX_BITMAP_OFFSET = 96
599
+ const FORKS_OFFSET = 128
600
+
601
+ // Look for 'i' (105) in the index bitmap
602
+ // Byte index = 105 / 8 = 13, bit position = 105 % 8 = 1
603
+ const indexBitmap = manifestData.slice(
604
+ INDEX_BITMAP_OFFSET,
605
+ INDEX_BITMAP_OFFSET + 32,
606
+ )
607
+ const iByteIndex = Math.floor(105 / 8) // = 13
608
+ const iBitMask = 1 << (105 % 8) // = 0x02
609
+
610
+ if ((indexBitmap[iByteIndex] & iBitMask) === 0) {
611
+ throw new Error('Manifest does not contain "index.bin" fork')
612
+ }
613
+
614
+ // Count forks before 'i' to find its offset
615
+ let forkIndex = 0
616
+ for (let byte = 0; byte < iByteIndex; byte++) {
617
+ forkIndex += popcount8(indexBitmap[byte])
618
+ }
619
+ // Count bits in byte 13 for values less than 105
620
+ for (let bit = 0; bit < 105 % 8; bit++) {
621
+ if (indexBitmap[iByteIndex] & (1 << bit)) {
622
+ forkIndex++
623
+ }
624
+ }
625
+
626
+ // Navigate to the correct fork by skipping previous forks
627
+ let forkOffset = FORKS_OFFSET
628
+ for (let i = 0; i < forkIndex; i++) {
629
+ const flags = manifestData[forkOffset]
630
+ const hasMetadata = (flags & FORK_FLAG_HAS_METADATA) !== 0
631
+ forkOffset += 1 + 1 + 30 + 32 // flags + prefixLen + prefix + reference
632
+
633
+ if (hasMetadata) {
634
+ const metadataLen =
635
+ (manifestData[forkOffset] << 8) | manifestData[forkOffset + 1]
636
+ forkOffset += 2 + metadataLen
637
+ }
638
+ }
639
+
640
+ // Now at the "index.bin" fork
641
+ const forkFlags = manifestData[forkOffset]
642
+ const prefixLen = manifestData[forkOffset + 1]
643
+
644
+ // Verify it starts with 'i' (index.bin)
645
+ if (manifestData[forkOffset + 2] !== 0x69) {
646
+ // 'i'
647
+ throw new Error(
648
+ `Expected "index.bin" prefix, got byte 0x${manifestData[forkOffset + 2].toString(16)}`,
649
+ )
650
+ }
651
+
652
+ // Optionally verify full prefix "index.bin" (9 bytes)
653
+ if (prefixLen >= 9) {
654
+ const prefixBytes = manifestData.slice(forkOffset + 2, forkOffset + 2 + 9)
655
+ const expectedPrefix = new TextEncoder().encode("index.bin")
656
+ const prefixMatches = prefixBytes.every((b, i) => b === expectedPrefix[i])
657
+ if (!prefixMatches) {
658
+ console.warn('[ManifestBuilder] Prefix does not fully match "index.bin"')
659
+ }
660
+ }
661
+
662
+ // Check if this is a value type (reference is the target)
663
+ const isValueType = (forkFlags & FORK_FLAG_VALUE_TYPE) !== 0
664
+ if (!isValueType) {
665
+ throw new Error('"index.bin" fork is not a VALUE type')
666
+ }
667
+
668
+ // Reference is at offset: forkOffset + 1 (flags) + 1 (prefixLen) + 30 (prefix) = forkOffset + 32
669
+ const referenceOffset = forkOffset + 32
670
+ const reference = manifestData.slice(referenceOffset, referenceOffset + 32)
671
+
672
+ if (isAllZeros(reference)) {
673
+ throw new Error('Manifest "index.bin" fork has zero reference')
674
+ }
675
+
676
+ const hexRef = new Reference(reference).toHex()
677
+
678
+ return hexRef
679
+ }