@solarpunkltd/swarm-id 0.0.0-stage → 0.8.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 (111) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +89 -2
  3. package/dist/bus/account-bus.d.ts +104 -0
  4. package/dist/bus/account-delta.d.ts +52 -0
  5. package/dist/bus/bus-context.d.ts +7 -0
  6. package/dist/bus/envelope.d.ts +9 -0
  7. package/dist/bus/messages.d.ts +241 -0
  8. package/dist/bus/presence.d.ts +62 -0
  9. package/dist/bus/signaling-transport.d.ts +79 -0
  10. package/dist/chunk/bmt.d.ts +16 -0
  11. package/dist/chunk/cac.d.ts +17 -0
  12. package/dist/chunk/constants.d.ts +10 -0
  13. package/dist/chunk/encrypted-cac.d.ts +47 -0
  14. package/dist/chunk/encryption.d.ts +85 -0
  15. package/dist/chunk/index.d.ts +5 -0
  16. package/dist/chunks/chat-binding-DO5aGhPl.js +23 -0
  17. package/dist/chunks/chat-binding-DO5aGhPl.js.map +1 -0
  18. package/dist/errors.d.ts +35 -0
  19. package/dist/index.d.ts +27 -0
  20. package/dist/internal.d.ts +49 -0
  21. package/dist/internal.esm.js +2 -0
  22. package/dist/internal.esm.js.map +1 -0
  23. package/dist/proxy/act/act.d.ts +58 -0
  24. package/dist/proxy/act/crypto.d.ts +46 -0
  25. package/dist/proxy/act/grantee-list.d.ts +81 -0
  26. package/dist/proxy/act/history.d.ts +182 -0
  27. package/dist/proxy/act/index.d.ts +114 -0
  28. package/dist/proxy/app-stamp-limits.d.ts +62 -0
  29. package/dist/proxy/chunk-upload-stream.d.ts +37 -0
  30. package/dist/proxy/chunking.d.ts +15 -0
  31. package/dist/proxy/collection.d.ts +38 -0
  32. package/dist/proxy/download-data.d.ts +27 -0
  33. package/dist/proxy/errors.d.ts +8 -0
  34. package/dist/proxy/feed-manifest.d.ts +60 -0
  35. package/dist/proxy/feeds/epochs/async-finder.d.ts +70 -0
  36. package/dist/proxy/feeds/epochs/epoch.d.ts +79 -0
  37. package/dist/proxy/feeds/epochs/finder.d.ts +60 -0
  38. package/dist/proxy/feeds/epochs/index.d.ts +30 -0
  39. package/dist/proxy/feeds/epochs/types.d.ts +110 -0
  40. package/dist/proxy/feeds/epochs/updater.d.ts +76 -0
  41. package/dist/proxy/feeds/epochs/utils.d.ts +21 -0
  42. package/dist/proxy/feeds/index.d.ts +4 -0
  43. package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
  44. package/dist/proxy/feeds/sequence/finder.d.ts +11 -0
  45. package/dist/proxy/feeds/sequence/index.d.ts +22 -0
  46. package/dist/proxy/feeds/sequence/types.d.ts +82 -0
  47. package/dist/proxy/feeds/sequence/updater.d.ts +20 -0
  48. package/dist/proxy/index.d.ts +5 -0
  49. package/dist/proxy/manifest-builder.d.ts +164 -0
  50. package/dist/proxy/mantaray.d.ts +76 -0
  51. package/dist/proxy/soc-restamp-slots.d.ts +48 -0
  52. package/dist/proxy/stamp-chunk.d.ts +45 -0
  53. package/dist/proxy/stamp-worker-handler.d.ts +32 -0
  54. package/dist/proxy/stamp-worker-pool.d.ts +52 -0
  55. package/dist/proxy/stamp-worker.d.ts +1 -0
  56. package/dist/proxy/types.d.ts +38 -0
  57. package/dist/proxy/upload.d.ts +207 -0
  58. package/dist/schemas.d.ts +671 -0
  59. package/dist/stamp-worker.iife.js +1 -0
  60. package/dist/storage/debounced-uploader.d.ts +61 -0
  61. package/dist/storage/utilization-store.d.ts +113 -0
  62. package/dist/swarm-id-client.d.ts +1143 -0
  63. package/dist/swarm-id-proxy.d.ts +1079 -0
  64. package/dist/swarm-id.esm.js +2 -0
  65. package/dist/swarm-id.esm.js.map +1 -0
  66. package/dist/sync/batch-write-coordinator.d.ts +348 -0
  67. package/dist/sync/device-roster.d.ts +59 -0
  68. package/dist/sync/device-state.d.ts +199 -0
  69. package/dist/sync/fold-account-from-swarm.d.ts +53 -0
  70. package/dist/sync/index.d.ts +11 -0
  71. package/dist/sync/merge-snapshot.d.ts +57 -0
  72. package/dist/sync/partition-intent.d.ts +218 -0
  73. package/dist/sync/partition-lease.d.ts +531 -0
  74. package/dist/sync/partition-lock.d.ts +218 -0
  75. package/dist/sync/partition-state.d.ts +287 -0
  76. package/dist/sync/session-check.d.ts +29 -0
  77. package/dist/sync/store-interfaces.d.ts +48 -0
  78. package/dist/sync/sync-account.d.ts +45 -0
  79. package/dist/sync/timing-constants.d.ts +32 -0
  80. package/dist/sync/types.d.ts +18 -0
  81. package/dist/types.d.ts +3042 -0
  82. package/dist/utils/account-state-snapshot.d.ts +43 -0
  83. package/dist/utils/active-devices.d.ts +45 -0
  84. package/dist/utils/avatar.d.ts +30 -0
  85. package/dist/utils/batch-utilization.d.ts +840 -0
  86. package/dist/utils/batch-write-lock.d.ts +12 -0
  87. package/dist/utils/bee-error.d.ts +11 -0
  88. package/dist/utils/chat-binding.d.ts +113 -0
  89. package/dist/utils/coalesced-task.d.ts +28 -0
  90. package/dist/utils/collection.d.ts +6 -0
  91. package/dist/utils/connection-info.d.ts +11 -0
  92. package/dist/utils/constants.d.ts +28 -0
  93. package/dist/utils/device-id.d.ts +109 -0
  94. package/dist/utils/hex.d.ts +31 -0
  95. package/dist/utils/json-rpc.d.ts +34 -0
  96. package/dist/utils/key-derivation.d.ts +124 -0
  97. package/dist/utils/lock-soc.d.ts +35 -0
  98. package/dist/utils/postage-contract.d.ts +143 -0
  99. package/dist/utils/postage-stamp-association.d.ts +39 -0
  100. package/dist/utils/promise.d.ts +37 -0
  101. package/dist/utils/sealed-values.d.ts +23 -0
  102. package/dist/utils/seed-encryption.d.ts +17 -0
  103. package/dist/utils/site-allow-list.d.ts +54 -0
  104. package/dist/utils/stamp-lifespan.d.ts +37 -0
  105. package/dist/utils/storage-managers.d.ts +62 -0
  106. package/dist/utils/storage-probe.d.ts +40 -0
  107. package/dist/utils/tag.d.ts +11 -0
  108. package/dist/utils/ttl.d.ts +120 -0
  109. package/dist/utils/url.d.ts +55 -0
  110. package/dist/utils/versioned-storage.d.ts +183 -0
  111. package/package.json +91 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.esm.js","sources":["../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/size.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/stamps.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/headers.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/chunk.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/chunk/soc.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/soc.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/balance.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/consumed.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/balance.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/chequebook.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/cheque.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/chequebook.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/chunk.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/manifest/manifest.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/chunk-size.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/collection.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/mime.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/chunk-stream.browser.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/tar.browser.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/tar-uploader.browser.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/tar-writer.browser.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/bzz.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/collection.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/collection.browser.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/connectivity.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/connectivity.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/bytes.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/stewardship.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/resource-locator.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/data.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/feed.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/feed/identifier.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/feed/index.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/feed/retrievable.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/feed.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/file.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/grantee.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/grantee.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/data.browser.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/messaging.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/pss.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/pin.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/pin.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/settlement.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/settlement.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/soc.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/stake.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/stake.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/redistributionstate.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/stamp.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/batches.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/stamp.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/status.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/status.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/storage.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/tag.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/tag.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/transaction.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/transaction.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/wallet.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/modules/wallet.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/bad-ports.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/utils/url.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/bee.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/envelope.js","../../node_modules/.pnpm/@ethersphere+bee-js@13.0.0/node_modules/@ethersphere/bee-js/dist/mjs/api/rchash.js","../src/proxy/chunking.ts","../src/bus/messages.ts","../src/bus/account-bus.ts","../src/utils/versioned-storage.ts","../src/utils/sealed-values.ts","../src/proxy/errors.ts","../src/proxy/chunk-upload-stream.ts","../src/utils/tag.ts","../src/proxy/upload.ts","../src/utils/lock-soc.ts","../src/sync/timing-constants.ts","../src/utils/batch-utilization.ts","../src/utils/storage-managers.ts","../src/utils/account-state-snapshot.ts","../src/bus/account-delta.ts","../src/bus/bus-context.ts","../src/bus/presence.ts","../../signaling/src/protocol.ts","../src/bus/envelope.ts","../src/bus/signaling-transport.ts","../src/storage/debounced-uploader.ts","../src/utils/seed-encryption.ts","../src/storage/utilization-store.ts","../src/proxy/stamp-chunk.ts","../src/proxy/app-stamp-limits.ts","../src/proxy/soc-restamp-slots.ts","../src/proxy/stamp-worker-pool.ts","../src/proxy/collection.ts","../src/utils/postage-stamp-association.ts","../src/utils/stamp-lifespan.ts","../src/sync/merge-snapshot.ts","../src/utils/batch-write-lock.ts","../src/sync/partition-lock.ts","../src/sync/partition-intent.ts","../src/sync/partition-state.ts","../src/sync/partition-lease.ts","../src/sync/batch-write-coordinator.ts","../src/proxy/feeds/epochs/epoch.ts","../src/proxy/feeds/epochs/async-finder.ts","../src/proxy/feeds/epochs/utils.ts","../src/proxy/feeds/epochs/updater.ts","../src/proxy/feeds/epochs/index.ts","../src/proxy/feeds/sequence/finder.ts","../src/proxy/feeds/sequence/async-finder.ts","../src/proxy/feeds/sequence/updater.ts","../src/utils/bee-error.ts","../src/sync/device-roster.ts","../src/sync/device-state.ts","../src/utils/device-id.ts","../src/utils/active-devices.ts","../src/sync/sync-account.ts","../src/sync/fold-account-from-swarm.ts","../src/sync/session-check.ts","../src/utils/storage-probe.ts","../src/utils/postage-contract.ts","../src/utils/constants.ts","../src/proxy/act/crypto.ts","../src/proxy/act/act.ts","../src/proxy/act/grantee-list.ts","../src/proxy/act/history.ts","../src/proxy/act/index.ts","../src/swarm-id-proxy.ts","../src/utils/connection-info.ts","../src/proxy/feeds/sequence/index.ts","../src/proxy/feed-manifest.ts","../src/utils/coalesced-task.ts","../src/utils/site-allow-list.ts"],"sourcesContent":["import { Numbers } from 'cafe-utility';\n/**\n * Represents a size in bytes.\n *\n * Uses 1000 instead of 1024 for converting between units.\n * This is to stay consistent with the Swarm papers\n * on theoretical and effective storage capacity.\n */\nexport class Size {\n constructor(bytes) {\n this.bytes = Math.ceil(bytes);\n if (bytes < 0) {\n throw Error('Size must be at least 0');\n }\n }\n static fromBytes(bytes) {\n return new Size(bytes);\n }\n static fromKilobytes(kilobytes) {\n return new Size(kilobytes * 1000);\n }\n static fromMegabytes(megabytes) {\n return new Size(megabytes * 1000 * 1000);\n }\n static fromGigabytes(gigabytes) {\n return new Size(gigabytes * 1000 * 1000 * 1000);\n }\n /**\n * Parses a size string and returns a `Size` instance.\n *\n * Case insensitive. E.g. both `\"28MB\"` and `\"1gb\"` are valid.\n *\n * Whitespaces are ignored. E.g. both `\"512 kb\"` and `\"2megabytes\"` are valid.\n *\n * Decimal numbers are supported. E.g. `\"1.5gb\"` is valid.\n *\n * Uses 1000 as the base for conversions. E.g. 1kb = 1000 bytes.\n * This is consistent with the effective stamp utilization table.\n *\n * Supported units:\n * - b, byte, bytes\n * - kb, kilobyte, kilobytes\n * - mb, megabyte, megabytes\n * - gb, gigabyte, gigabytes\n * - tb, terabyte, terabytes\n *\n * @param size - A string representing a size\n * @returns a `Size` instance\n */\n static parseFromString(size) {\n return Size.fromBytes(Numbers.makeStorage(size, 1000));\n }\n toBytes() {\n return this.bytes;\n }\n toGigabytes() {\n return this.bytes / 1000 / 1000 / 1000;\n }\n toFormattedString() {\n return Numbers.convertBytes(this.bytes, 1000);\n }\n represent() {\n return this.toFormattedString();\n }\n}","import { Duration } from \"./duration.js\";\nimport { Size } from \"./size.js\";\nimport { BZZ } from \"./tokens.js\";\nimport { asNumberString } from \"./type.js\";\nimport { normalizeBatchTTL } from \"./workaround.js\";\nimport { BatchId, convertEnvelopeToMarshaledStamp as coreConvertEnvelopeToMarshaledStamp, getDepthForSize as coreGetDepthForSize, getStampEffectiveBytes, getStampEffectiveBytesBreakpoints, getStampTheoreticalBytes, getStampUsage, marshalStamp as coreMarshalStamp } from '@ethersphere/core-sdk';\nexport { getStampEffectiveBytes, getStampEffectiveBytesBreakpoints, getStampTheoreticalBytes, getStampUsage };\n/**\n * Utility function that calculates the cost of a postage batch based on its depth and amount.\n */\nexport function getStampCost(depth, amount) {\n return BZZ.fromPLUR(2n ** BigInt(depth) * BigInt(amount));\n}\n/**\n * Utility function that calculates the TTL of a postage batch based on its amount, price per block and block time.\n *\n * For more accurate results, get the price per block and block time from the Bee node or the blockchain.\n *\n * @returns {number} The TTL of the postage batch.\n */\nexport function getStampDuration(amount, pricePerBlock, blockTime) {\n const amountBigInt = BigInt(asNumberString(amount));\n return Duration.fromSeconds(Number(amountBigInt * BigInt(blockTime) / BigInt(pricePerBlock)));\n}\n/**\n * Get the postage batch `amount` required for a given `duration`.\n *\n * @param duration A duration object representing the duration of the storage.\n * @param pricePerBlock The price per block in PLUR.\n * @param blockTime The block time in seconds.\n */\nexport function getAmountForDuration(duration, pricePerBlock, blockTime) {\n return BigInt(duration.toSeconds()) / BigInt(blockTime) * BigInt(pricePerBlock) + 1n;\n}\n/**\n * Utility function that calculates the depth required for a postage batch to achieve the specified effective size\n *\n * @param size The effective size of the postage batch\n * @returns\n */\nexport function getDepthForSize(size, encryption, erasureCodeLevel) {\n return coreGetDepthForSize(size.toBytes(), encryption, erasureCodeLevel);\n}\nexport function convertEnvelopeToMarshaledStamp(envelope) {\n return coreConvertEnvelopeToMarshaledStamp(envelope);\n}\nexport function marshalStamp(signature, batchId, timestamp, index) {\n return coreMarshalStamp(signature, batchId, timestamp, index);\n}\nexport function mapPostageBatch(raw, encryption, erasureCodeLevel) {\n const batchTTL = normalizeBatchTTL(raw.batchTTL);\n const duration = Duration.fromSeconds(batchTTL);\n const effectiveBytes = getStampEffectiveBytes(raw.depth, encryption, erasureCodeLevel);\n const usage = raw.utilizationRatio ?? getStampUsage(raw.utilization, raw.depth, raw.bucketDepth);\n return {\n batchID: new BatchId(raw.batchID),\n utilization: raw.utilization,\n usable: raw.usable,\n label: raw.label,\n depth: raw.depth,\n amount: asNumberString(raw.amount),\n bucketDepth: raw.bucketDepth,\n blockNumber: raw.blockNumber,\n immutableFlag: raw.immutableFlag,\n usage,\n usageText: `${Math.round(usage * 100)}%`,\n size: Size.fromBytes(effectiveBytes),\n remainingSize: Size.fromBytes(Math.ceil(effectiveBytes * (1 - usage))),\n theoreticalSize: Size.fromBytes(getStampTheoreticalBytes(raw.depth)),\n duration,\n calculateSize(encryption, redundancyLevel) {\n const effectiveBytes = getStampEffectiveBytes(raw.depth, encryption, redundancyLevel);\n return Size.fromBytes(effectiveBytes);\n },\n calculateRemainingSize(encryption, redundancyLevel) {\n const effectiveBytes = getStampEffectiveBytes(raw.depth, encryption, redundancyLevel);\n return Size.fromBytes(Math.ceil(effectiveBytes * (1 - this.usage)));\n }\n };\n}\nexport function unmapPostageBatch(batch) {\n return {\n batchID: batch.batchID.toHex(),\n utilization: batch.utilization,\n utilizationRatio: batch.usage,\n usable: batch.usable,\n label: batch.label,\n depth: batch.depth,\n amount: batch.amount,\n bucketDepth: batch.bucketDepth,\n blockNumber: batch.blockNumber,\n immutableFlag: batch.immutableFlag,\n batchTTL: batch.duration.toSeconds()\n };\n}","import { BatchId, PublicKey, Reference } from '@ethersphere/core-sdk';\nimport { z } from 'zod';\nimport { BeeError } from \"./error.js\";\nimport { convertEnvelopeToMarshaledStamp } from \"./stamps.js\";\nexport function readFileHeaders(headers) {\n const name = readContentDispositionFilename(headers['content-disposition']);\n const tagUid = readTagUid(headers['swarm-tag-uid']);\n const contentType = headers['content-type'] || undefined;\n return {\n name,\n tagUid,\n contentType\n };\n}\nfunction readContentDispositionFilename(header) {\n if (!header) {\n throw new BeeError('missing content-disposition header');\n }\n // Regex was found here\n // https://stackoverflow.com/questions/23054475/javascript-regex-for-extracting-filename-from-content-disposition-header\n const dispositionMatch = header.match(/filename\\*?=['\"]?(?:UTF-\\d['\"]*)?([^;\\r\\n\"']*)['\"]?;?/i);\n if (dispositionMatch && dispositionMatch.length > 0) {\n return dispositionMatch[1];\n }\n throw new BeeError('invalid content-disposition header');\n}\nfunction readTagUid(header) {\n if (!header) {\n return undefined;\n }\n return parseInt(header, 10);\n}\nexport function prepareRequestHeaders(stamp, nullableOptions) {\n const headers = {};\n if (isEnvelopeWithBatchId(stamp)) {\n headers['swarm-postage-stamp'] = convertEnvelopeToMarshaledStamp(stamp).toHex();\n } else if (stamp) {\n stamp = new BatchId(stamp);\n headers['swarm-postage-batch-id'] = stamp.toHex();\n }\n if (!nullableOptions) {\n return headers;\n }\n const options = nullableOptions;\n if (options.size) {\n headers['content-length'] = String(options.size);\n }\n if (options.contentType) {\n headers['content-type'] = String(options.contentType);\n }\n if (options.redundancyLevel) {\n headers['swarm-redundancy-level'] = String(options.redundancyLevel);\n }\n const act = z.boolean().safeParse(options.act);\n if (act.success) {\n headers['swarm-act'] = String(act.data);\n }\n const pin = z.boolean().safeParse(options.pin);\n if (pin.success) {\n headers['swarm-pin'] = String(pin.data);\n }\n const encrypt = z.boolean().safeParse(options.encrypt);\n if (encrypt.success) {\n headers['swarm-encrypt'] = String(encrypt.data);\n }\n if (options.tag) {\n headers['swarm-tag'] = String(options.tag);\n }\n const deferred = z.boolean().safeParse(options.deferred);\n if (deferred.success) {\n headers['swarm-deferred-upload'] = String(deferred.data);\n }\n if (options.redundancyStrategy) {\n headers['swarm-redundancy-strategy'] = String(options.redundancyStrategy);\n }\n const fallback = z.boolean().safeParse(options.fallback);\n if (fallback.success) {\n headers['swarm-redundancy-fallback-mode'] = String(fallback.data);\n }\n if (options.timeoutMs) {\n headers['swarm-chunk-retrieval-timeout'] = String(options.timeoutMs);\n }\n if (options.indexDocument) {\n headers['swarm-index-document'] = String(options.indexDocument);\n }\n if (options.errorDocument) {\n headers['swarm-error-document'] = String(options.errorDocument);\n }\n if (options.actPublisher) {\n headers['swarm-act-publisher'] = new PublicKey(options.actPublisher).toCompressedHex();\n }\n if (options.actHistoryAddress) {\n headers['swarm-act-history-address'] = new Reference(options.actHistoryAddress).toHex();\n }\n if (options.actTimestamp) {\n headers['swarm-act-timestamp'] = String(options.actTimestamp);\n }\n if (options.actPublisher || options.actHistoryAddress || options.actTimestamp) {\n headers['swarm-act'] = 'true';\n }\n if (options.gasPrice) {\n headers['gas-price'] = String(options.gasPrice);\n }\n if (options.gasLimit) {\n headers['gas-limit'] = String(options.gasLimit);\n }\n return headers;\n}\nfunction isEnvelopeWithBatchId(value) {\n const result = z.object({}).safeParse(value);\n if (!result.success) {\n return false;\n }\n const envelope = value;\n return envelope.issuer !== undefined && envelope.index !== undefined && envelope.signature !== undefined && envelope.timestamp !== undefined && envelope.batchId !== undefined;\n}","import { Reference } from '@ethersphere/core-sdk';\nimport { Optional } from 'cafe-utility';\nimport { UploadResultBody } from \"../types/schema/upload.js\";\nimport { prepareRequestHeaders } from \"../utils/headers.js\";\nimport { http } from \"../utils/http.js\";\nimport { makeTagUid } from \"../utils/type.js\";\nconst endpoint = 'chunks';\n/**\n * Raw HTTP calls for the `/chunks` endpoint.\n */\nexport async function upload(requestOptions, data, stamp, options) {\n const response = await http(requestOptions, {\n method: 'post',\n url: `${endpoint}`,\n data,\n headers: {\n 'content-type': 'application/octet-stream',\n ...prepareRequestHeaders(stamp, options)\n },\n responseType: 'json'\n });\n const body = UploadResultBody.parse(response.data);\n return {\n reference: body.reference,\n tagUid: response.headers['swarm-tag'] ? makeTagUid(response.headers['swarm-tag']) : undefined,\n historyAddress: response.headers['swarm-act-history-address'] ? Optional.of(new Reference(response.headers['swarm-act-history-address'])) : Optional.empty()\n };\n}\nexport async function download(requestOptions, reference, options) {\n reference = new Reference(reference);\n const response = await http(requestOptions, {\n responseType: 'arraybuffer',\n url: `${endpoint}/${reference}`,\n headers: prepareRequestHeaders(null, options)\n });\n return new Uint8Array(response.data);\n}","import { Bytes, EthAddress, Identifier, PrivateKey, makeContentAddressedChunk, makeSOCAddress, makeSingleOwnerChunk as coreMakeSingleOwnerChunk, unmarshalSingleOwnerChunk as coreUnmarshalSingleOwnerChunk } from '@ethersphere/core-sdk';\nimport * as chunkAPI from \"../api/chunk.js\";\nimport * as socAPI from \"../api/soc.js\";\nimport { BeeError } from \"../utils/error.js\";\nexport { makeSOCAddress };\n/**\n * Unmarshals arbitrary data into a Single Owner Chunk.\n * Throws an error if the data is not a valid SOC.\n *\n * @param data The chunk data\n * @param address The address of the single owner chunk\n *\n * @returns a single owner chunk or throws error\n */\nexport function unmarshalSingleOwnerChunk(data, address) {\n try {\n return coreUnmarshalSingleOwnerChunk(data, address);\n } catch (e) {\n throw new BeeError(e.message);\n }\n}\n/**\n * Creates a single owner chunk object\n *\n * @param chunk A chunk object used for the span and payload\n * @param identifier The identifier of the chunk\n * @param signer The signer interface for signing the chunk\n */\nexport function makeSingleOwnerChunk(address, span, payload, identifier, signer) {\n identifier = new Identifier(identifier);\n signer = new PrivateKey(signer);\n const wrappedChunk = {\n data: Bytes.concat(span.toUint8Array(), payload.toUint8Array()),\n span,\n payload,\n address\n };\n return coreMakeSingleOwnerChunk(wrappedChunk, identifier, signer.toBigInt());\n}\n/**\n * Helper function to upload a chunk.\n *\n * It uses the Chunk API and calculates the address before uploading.\n *\n * @param requestOptions Options for making requests\n * @param chunk A chunk object\n * @param stamp Postage BatchId that will be assigned to uploaded data\n * @param options Upload options\n */\nexport async function uploadSingleOwnerChunk(requestOptions, chunk, stamp, options) {\n const data = Bytes.concat(chunk.span.toUint8Array(), chunk.payload.toUint8Array());\n return socAPI.upload(requestOptions, chunk.owner, chunk.identifier, chunk.signature, data, stamp, options);\n}\n/**\n * Helper function to create and upload SOC.\n *\n * @param requestOptions Options for making requests\n * @param signer The signer interface for signing the chunk\n * @param postageBatchId\n * @param identifier The identifier of the chunk\n * @param data The chunk data\n * @param options\n */\nexport async function uploadSingleOwnerChunkData(requestOptions, signer, stamp, identifier, data, options) {\n signer = new PrivateKey(signer);\n identifier = new Identifier(identifier);\n const cac = makeContentAddressedChunk(data);\n const soc = cac.toSingleOwnerChunk(identifier, signer);\n return uploadSingleOwnerChunk(requestOptions, soc, stamp, options);\n}\nexport async function uploadSingleOwnerChunkWithWrappedChunk(requestOptions, signer, stamp, identifier, wrappedChunk, options) {\n signer = new PrivateKey(signer);\n identifier = new Identifier(identifier);\n const soc = wrappedChunk.toSingleOwnerChunk(identifier, signer);\n return uploadSingleOwnerChunk(requestOptions, soc, stamp, options);\n}\n/**\n * Helper function to download SOC.\n *\n * @param url The url of the Bee service\n * @param ownerAddress The signer interface for signing the chunk\n * @param identifier The identifier of the chunk\n */\nexport async function downloadSingleOwnerChunk(requestOptions, ownerAddress, identifier) {\n identifier = new Identifier(identifier);\n ownerAddress = new EthAddress(ownerAddress);\n const address = makeSOCAddress(identifier, ownerAddress);\n const cac = await chunkAPI.download(requestOptions, address.toHex());\n return unmarshalSingleOwnerChunk(cac, address);\n}","import { Reference } from '@ethersphere/core-sdk';\nimport { Optional } from 'cafe-utility';\nimport { UploadResultBody } from \"../types/schema/upload.js\";\nimport { prepareRequestHeaders } from \"../utils/headers.js\";\nimport { http } from \"../utils/http.js\";\nimport { makeTagUid } from \"../utils/type.js\";\nconst socEndpoint = 'soc';\n/**\n * Raw HTTP calls for the `/soc` endpoint.\n */\nexport async function upload(requestOptions, owner, identifier, signature, data, stamp, options) {\n const response = await http(requestOptions, {\n method: 'post',\n url: `${socEndpoint}/${owner}/${identifier}`,\n data,\n headers: {\n 'content-type': 'application/octet-stream',\n ...prepareRequestHeaders(stamp, options)\n },\n responseType: 'json',\n params: {\n sig: signature.toHex()\n }\n });\n const body = UploadResultBody.parse(response.data);\n return {\n reference: body.reference,\n tagUid: response.headers['swarm-tag'] ? makeTagUid(response.headers['swarm-tag']) : undefined,\n historyAddress: response.headers['swarm-act-history-address'] ? Optional.of(new Reference(response.headers['swarm-act-history-address'])) : Optional.empty()\n };\n}","import { GetAllBalancesResponse, GetPeerBalanceResponse } from \"../types/schema/balance.js\";\nimport { http } from \"../utils/http.js\";\nconst balancesEndpoint = 'balances';\n/**\n * Raw HTTP calls for the `/balances` endpoint.\n */\nexport async function getBalances(requestOptions) {\n const response = await http(requestOptions, {\n url: balancesEndpoint,\n responseType: 'json'\n });\n return GetAllBalancesResponse.parse(response.data);\n}\nexport async function getPeerBalance(requestOptions, peer) {\n const response = await http(requestOptions, {\n url: `${balancesEndpoint}/${peer}`,\n responseType: 'json'\n });\n return GetPeerBalanceResponse.parse(response.data);\n}","import { GetAllBalancesResponse, GetPeerBalanceResponse } from \"../types/schema/balance.js\";\nimport { http } from \"../utils/http.js\";\nconst consumedEndpoint = 'consumed';\n/**\n * Raw HTTP calls for the `/consumed` endpoint.\n */\nexport async function getPastDueConsumptionBalances(requestOptions) {\n const response = await http(requestOptions, {\n url: consumedEndpoint,\n responseType: 'json'\n });\n return GetAllBalancesResponse.parse(response.data);\n}\nexport async function getPastDueConsumptionPeerBalance(requestOptions, peer) {\n const response = await http(requestOptions, {\n url: `${consumedEndpoint}/${peer}`,\n responseType: 'json'\n });\n return GetPeerBalanceResponse.parse(response.data);\n}","import { PeerAddress } from '@ethersphere/core-sdk';\nimport * as balanceApi from \"../api/balance.js\";\nimport * as consumedApi from \"../api/consumed.js\";\n/**\n * SWAP balance operations. Related to the bandwidth incentives and the chequebook.\n *\n * Accessed as `bee.balance`.\n */\nexport class Balance {\n constructor(context) {\n this.context = context;\n }\n /**\n * Gets the SWAP balances with all known peers including prepaid services.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getAll(requestOptions) {\n return balanceApi.getBalances(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Gets the SWAP balances for a specific peer including prepaid services.\n *\n * @param address Swarm address of peer\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getPeer(address, requestOptions) {\n const peer = new PeerAddress(address);\n return balanceApi.getPeerBalance(this.context.getRequestOptionsForCall(requestOptions), peer);\n }\n /**\n * Gets the past due consumption balances for all known peers.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getAllPastDueConsumption(requestOptions) {\n return consumedApi.getPastDueConsumptionBalances(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Gets the past due consumption balance for a specific peer.\n *\n * @param address Swarm address of peer\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getAllPastDueConsumptionForPeer(address, requestOptions) {\n const peer = new PeerAddress(address);\n return consumedApi.getPastDueConsumptionPeerBalance(this.context.getRequestOptionsForCall(requestOptions), peer);\n }\n}","import { GetChequebookAddressResponse, GetChequebookBalanceResponse, GetLastCashoutActionResponse, GetLastChequesForPeerResponse, GetLastChequesResponse, TransactionHashResponse } from \"../types/schema/chequebook.js\";\nimport { prepareRequestHeaders } from \"../utils/headers.js\";\nimport { http } from \"../utils/http.js\";\nconst chequebookEndpoint = 'chequebook';\n/**\n * Raw HTTP calls for the `/chequebook` endpoint (chequebook + cheque operations).\n */\n/** Gets the address of the deployed chequebook. */\nexport async function getChequebookAddress(requestOptions) {\n const response = await http(requestOptions, {\n url: `${chequebookEndpoint}/address`,\n responseType: 'json'\n });\n return GetChequebookAddressResponse.parse(response.data);\n}\n/** Gets the balance of the chequebook. */\nexport async function getChequebookBalance(requestOptions) {\n const response = await http(requestOptions, {\n url: `${chequebookEndpoint}/balance`,\n responseType: 'json'\n });\n return GetChequebookBalanceResponse.parse(response.data);\n}\n/** Deposits tokens from the node wallet into the chequebook. */\nexport async function depositTokens(requestOptions, amount, gasPrice) {\n const headers = {};\n if (gasPrice) {\n headers['gas-price'] = gasPrice;\n }\n const response = await http(requestOptions, {\n method: 'post',\n url: `${chequebookEndpoint}/deposit`,\n responseType: 'json',\n params: {\n amount\n },\n headers\n });\n return TransactionHashResponse.parse(response.data).transactionHash;\n}\n/** Withdraws tokens from the chequebook to the node wallet. */\nexport async function withdrawTokens(requestOptions, amount, gasPrice) {\n const headers = {};\n if (gasPrice) {\n headers['gas-price'] = gasPrice;\n }\n const response = await http(requestOptions, {\n method: 'post',\n url: `${chequebookEndpoint}/withdraw`,\n responseType: 'json',\n params: {\n amount\n },\n headers\n });\n return TransactionHashResponse.parse(response.data).transactionHash;\n}\n/** Gets the last cheques for all peers. */\nexport async function getLastCheques(requestOptions) {\n const response = await http(requestOptions, {\n url: `${chequebookEndpoint}/cheque`,\n responseType: 'json'\n });\n return GetLastChequesResponse.parse(response.data);\n}\n/** Gets the last cheques for a specific peer. */\nexport async function getLastChequesForPeer(requestOptions, peer) {\n const response = await http(requestOptions, {\n url: `${chequebookEndpoint}/cheque/${peer}`,\n responseType: 'json'\n });\n return GetLastChequesForPeerResponse.parse(response.data);\n}\n/** Gets the last cashout action for a specific peer. */\nexport async function getLastCashoutAction(requestOptions, peer) {\n const response = await http(requestOptions, {\n url: `${chequebookEndpoint}/cashout/${peer}`,\n responseType: 'json'\n });\n return GetLastCashoutActionResponse.parse(response.data);\n}\n/** Cashes out the last cheque for a specific peer. */\nexport async function cashoutLastCheque(requestOptions, peer, options) {\n const response = await http(requestOptions, {\n method: 'post',\n url: `${chequebookEndpoint}/cashout/${peer}`,\n responseType: 'json',\n headers: prepareRequestHeaders(null, options)\n });\n return TransactionHashResponse.parse(response.data).transactionHash;\n}","import { PeerAddress } from '@ethersphere/core-sdk';\nimport { TransactionOptionsSchema } from \"../utils/schema.js\";\nimport * as api from \"../api/chequebook.js\";\n/**\n * Cheque operations (last cheques and cashouts).\n *\n * Accessed as `bee.cheque`.\n */\nexport class Cheque {\n constructor(context) {\n this.context = context;\n }\n /**\n * Gets the last cheques for all peers.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getAllLatest(requestOptions) {\n return api.getLastCheques(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Gets the last cheques for a specific peer.\n *\n * @param address Overlay address of peer.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getAllLatestForPeer(address, requestOptions) {\n const peer = new PeerAddress(address);\n return api.getLastChequesForPeer(this.context.getRequestOptionsForCall(requestOptions), peer);\n }\n /**\n * Gets the last cashout action for a specific peer.\n *\n * @param address Overlay address of peer.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getLastCashoutAction(address, requestOptions) {\n const peer = new PeerAddress(address);\n return api.getLastCashoutAction(this.context.getRequestOptionsForCall(requestOptions), peer);\n }\n /**\n * Cashes out the last cheque for a specific peer.\n *\n * @param address Swarm address of peer\n * @param options\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async cashoutLast(address, options, requestOptions) {\n const peer = new PeerAddress(address);\n if (options) {\n options = TransactionOptionsSchema.parse(options);\n }\n return api.cashoutLastCheque(this.context.getRequestOptionsForCall(requestOptions), peer, options);\n }\n}","import { BZZ } from \"../utils/tokens.js\";\nimport { asNumberString } from \"../utils/type.js\";\nimport * as api from \"../api/chequebook.js\";\n/**\n * Chequebook contract operations (address, balance, deposit, withdraw).\n *\n * Accessed as `bee.chequebook`.\n */\nexport class Chequebook {\n constructor(context) {\n this.context = context;\n }\n /**\n * Gets the address of the deployed chequebook.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getAddress(requestOptions) {\n return api.getChequebookAddress(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Gets the balance of the chequebook.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getBalance(requestOptions) {\n return api.getChequebookBalance(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Deposits tokens from the node wallet into the chequebook.\n *\n * @param amount Amount of BZZ tokens to deposit. If not providing a `BZZ` instance, the amount is denoted in PLUR.\n * @param gasPrice Gas Price in WEI for the transaction call\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async deposit(amount, gasPrice, requestOptions) {\n const amountString = amount instanceof BZZ ? amount.toPLURString() : asNumberString(amount, {\n min: 1n,\n name: 'amount'\n });\n const gasPriceString = gasPrice ? asNumberString(amount, {\n min: 0n,\n name: 'gasPrice'\n }) : undefined;\n return api.depositTokens(this.context.getRequestOptionsForCall(requestOptions), amountString, gasPriceString);\n }\n /**\n * Withdraws tokens from the chequebook to the node wallet.\n *\n * @param amount Amount of BZZ tokens to withdraw. If not providing a `BZZ` instance, the amount is denoted in PLUR.\n * @param gasPrice Gas Price in WEI for the transaction call.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async withdraw(amount, gasPrice, requestOptions) {\n const amountString = amount instanceof BZZ ? amount.toPLURString() : asNumberString(amount, {\n min: 1n,\n name: 'amount'\n });\n const gasPriceString = gasPrice ? asNumberString(amount, {\n min: 0n,\n name: 'gasPrice'\n }) : undefined;\n return api.withdrawTokens(this.context.getRequestOptionsForCall(requestOptions), amountString, gasPriceString);\n }\n}","import { Identifier, Reference, Signature, Span } from '@ethersphere/core-sdk';\nimport { CHUNK_SIZE } from \"../types/index.js\";\nimport { BeeArgumentError } from \"../utils/error.js\";\nimport { DownloadOptionsSchema, UploadOptionsSchema } from \"../utils/schema.js\";\nimport * as api from \"../api/chunk.js\";\n/**\n * Chunk operations backed by the `/chunks` endpoint.\n *\n * Accessed as `bee.chunk`.\n */\nexport class Chunk {\n constructor(context) {\n this.context = context;\n }\n /**\n * Uploads a chunk to the network.\n *\n * Chunks uploaded with this method should be retrieved with {@link download}.\n *\n * @param stamp Postage Batch ID or an Envelope created with the `bee.createEnvelope` method.\n * @param data Raw chunk to be uploaded (Content Addressed Chunk or Single Owner Chunk)\n * @param options Additional options like tag, encryption, pinning, content-type and request options\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async upload(stamp, data, options, requestOptions) {\n const isSOC = 'identifier' in data && 'signature' in data && 'owner' in data;\n data = data instanceof Uint8Array ? data : data.data;\n if (options) {\n options = UploadOptionsSchema.parse(options);\n }\n if (data.length < Span.LENGTH) {\n throw new BeeArgumentError(`Chunk has to have size of at least ${Span.LENGTH}.`, data);\n }\n if (!isSOC && data.length > CHUNK_SIZE + Span.LENGTH) {\n throw new BeeArgumentError(`Content Addressed Chunk must not exceed ${CHUNK_SIZE + Span.LENGTH} bytes.`, data);\n }\n if (isSOC && data.length > CHUNK_SIZE + Span.LENGTH + Signature.LENGTH + Identifier.LENGTH) {\n throw new BeeArgumentError(`Single Owner Chunk must not exceed ${CHUNK_SIZE + Span.LENGTH + Signature.LENGTH + Identifier.LENGTH} bytes.`, data);\n }\n return api.upload(this.context.getRequestOptionsForCall(requestOptions), data, stamp, options);\n }\n /**\n * Downloads a chunk as a `Uint8Array`.\n *\n * @param reference Bee chunk reference in hex string (either 64 or 128 chars long) or ENS domain.\n * @param options Options that affects the request behavior\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async download(reference, options, requestOptions) {\n const ref = new Reference(reference);\n if (options) {\n options = DownloadOptionsSchema.parse(options);\n }\n return api.download(this.context.getRequestOptionsForCall(requestOptions), ref, options);\n }\n}","import { Optional } from 'cafe-utility';\nimport { Bytes, MantarayNode as CoreMantarayNode, Reference } from '@ethersphere/core-sdk';\n/**\n * Uploads `node` and every node beneath it, updating each `selfAddress` in\n * place. Only the network/ACT-history side is bee-js specific - the trie\n * structure and marshaling live entirely in core-sdk's MantarayNode.\n */\nasync function saveRecursively(node, bee, postageBatchId, options, requestOptions) {\n for (const fork of node.forks.values()) {\n const uploadResult = await saveRecursively(fork.node, bee, postageBatchId, options, requestOptions);\n if (options?.act) {\n let historyAddress;\n uploadResult.historyAddress.ifPresent(ref => historyAddress = ref);\n if (historyAddress) {\n if (!fork.node.metadata) {\n fork.node.metadata = {};\n }\n fork.node.metadata['swarm-act-history-address'] = historyAddress.toHex();\n }\n }\n }\n const result = await bee.data.upload(postageBatchId, await node.marshal(), options, requestOptions);\n node.selfAddress = result.reference.toUint8Array();\n return result;\n}\n/**\n * Downloads and unmarshals every node beneath `node`, following ACT history\n * metadata left by {@link saveRecursively} where present.\n */\nasync function loadRecursively(node, bee, options, requestOptions) {\n for (const fork of node.forks.values()) {\n if (!fork.node.selfAddress) {\n throw Error('MantarayNode#loadRecursively fork.node.selfAddress is not set');\n }\n let downloadOptions = options;\n if (fork.node.metadata && fork.node.metadata['swarm-act-history-address']) {\n downloadOptions = {\n ...options,\n actHistoryAddress: fork.node.metadata['swarm-act-history-address']\n };\n }\n const data = (await bee.data.download(fork.node.selfAddress, downloadOptions, requestOptions)).toUint8Array();\n const loaded = CoreMantarayNode.unmarshalFromData(data, fork.node.selfAddress);\n fork.node.targetAddress = loaded.targetAddress;\n fork.node.forks = loaded.forks;\n fork.node.path = fork.prefix;\n fork.node.parent = node;\n await loadRecursively(fork.node, bee, options, requestOptions);\n }\n}\n/**\n * bee-js's Bee-client-coupled view of a Mantaray node: the trie structure,\n * marshaling, and byte-level operations all delegate to core-sdk's\n * `MantarayNode`; this class only adds what needs a live `Bee` instance\n * (uploading/downloading, ACT history, feed resolution).\n */\nexport class MantarayNode {\n constructor(core = new CoreMantarayNode()) {\n this.core = core;\n }\n get obfuscationKey() {\n return this.core.obfuscationKey;\n }\n get selfAddress() {\n return this.core.selfAddress;\n }\n get targetAddress() {\n return this.core.targetAddress;\n }\n get metadata() {\n return this.core.metadata;\n }\n get path() {\n return this.core.path;\n }\n get forks() {\n return this.core.forks;\n }\n get parent() {\n return this.core.parent;\n }\n get type() {\n return this.core.type;\n }\n get fullPath() {\n return this.core.fullPath;\n }\n get fullPathString() {\n return this.core.fullPathString;\n }\n /**\n * Returns the metadata at the `/` path to access idiomatic properties.\n */\n getRootMetadata() {\n const node = this.core.find('/');\n if (node && node.metadata) {\n return Optional.of(node.metadata);\n }\n return Optional.empty();\n }\n /**\n * Returns the `swarm-index-document` and `swarm-error-document` metadata values.\n */\n getDocsMetadata() {\n const node = this.core.find('/');\n if (!node || !node.metadata) {\n return {\n indexDocument: null,\n errorDocument: null\n };\n }\n return {\n indexDocument: node.metadata['website-index-document'] ?? null,\n errorDocument: node.metadata['website-error-document'] ?? null\n };\n }\n /**\n * Attempts to resolve the manifest as a feed, returning the latest update.\n */\n async resolveFeed(bee, requestOptions) {\n const node = this.core.find('/');\n if (!node || !node.metadata) {\n return Optional.empty();\n }\n const owner = node.metadata['swarm-feed-owner'];\n const topic = node.metadata['swarm-feed-topic'];\n if (!owner || !topic) {\n return Optional.empty();\n }\n return Optional.of(await bee.feed.fetchLatestUpdate(topic, owner, requestOptions));\n }\n /**\n * Gets the binary representation of the node.\n */\n async marshal() {\n return this.core.marshal();\n }\n /**\n * Downloads and unmarshals a MantarayNode from the given reference.\n *\n * Do not forget calling `loadRecursively` on the returned node to load the entire tree.\n */\n static async unmarshal(bee, reference, options, requestOptions) {\n reference = new Reference(reference);\n const data = (await bee.data.download(reference, options, requestOptions)).toUint8Array();\n return this.unmarshalFromData(data, reference.toUint8Array());\n }\n /**\n * Unmarshals a MantarayNode from the given data.\n *\n * Do not forget calling `loadRecursively` on the returned node to load the entire tree.\n */\n static unmarshalFromData(data, selfAddress) {\n return new MantarayNode(CoreMantarayNode.unmarshalFromData(data, selfAddress));\n }\n /**\n * Adds a fork to the node.\n */\n addFork(path, reference, metadata) {\n this.core.addFork(path, reference instanceof Bytes ? reference.toUint8Array() : reference, metadata);\n }\n /**\n * Removes a fork from the node.\n */\n removeFork(path) {\n this.core.removeFork(path);\n }\n /**\n * Calculates the self address of the node.\n */\n async calculateSelfAddress() {\n return this.core.calculateSelfAddress();\n }\n /**\n * Saves the node and its children recursively.\n */\n async saveRecursively(bee, postageBatchId, options, requestOptions) {\n return saveRecursively(this.core, bee, postageBatchId, options, requestOptions);\n }\n /**\n * Loads the node and its children recursively.\n */\n async loadRecursively(bee, options, requestOptions) {\n return loadRecursively(this.core, bee, options, requestOptions);\n }\n /**\n * Finds a node in the tree by its path.\n */\n find(path) {\n return this.core.find(path);\n }\n /**\n * Finds the closest node in the tree to the given path.\n */\n findClosest(path, current) {\n return this.core.findClosest(path, current);\n }\n /**\n * Returns an array of all nodes in the tree which have a target address set.\n *\n * Must be called after `loadRecursively`.\n */\n collect(nodes = []) {\n return this.core.collect(nodes);\n }\n /**\n * Returns a path:reference map of all nodes in the tree which have a target address set.\n *\n * Must be called after `loadRecursively`.\n */\n collectAndMap() {\n return this.core.collectAndMap();\n }\n determineType() {\n return this.core.determineType();\n }\n}","export function totalChunks(fileSize) {\n const chunkSize = 4096;\n const hashesPerChunk = 128;\n function chunksAtLevel(chunkCount) {\n if (chunkCount <= 1) {\n return chunkCount;\n }\n const upperLevelChunks = Math.ceil(chunkCount / hashesPerChunk);\n return chunkCount + chunksAtLevel(upperLevelChunks);\n }\n const baseLevelChunks = Math.ceil(fileSize / chunkSize);\n return chunksAtLevel(baseLevelChunks);\n}","import { BeeArgumentError } from \"./error.js\";\nexport function isCollection(data) {\n if (!Array.isArray(data)) {\n return false;\n }\n return data.every(entry => typeof entry === 'object' && entry.path && entry.size !== undefined);\n}\nexport function assertCollection(data) {\n if (!isCollection(data)) {\n throw new BeeArgumentError('invalid collection', data);\n }\n}\nexport function makeFilePath(file) {\n if (file.webkitRelativePath && file.webkitRelativePath !== '') {\n return file.webkitRelativePath.replace(/.*?\\//i, '');\n }\n if (file.name) {\n return file.name;\n }\n throw new TypeError('file is not valid File object');\n}\nexport function makeCollectionFromFileList(fileList) {\n return Array.from(fileList).map(file => ({\n path: makeFilePath(file),\n size: file.size,\n file\n }));\n}\n/**\n * Calculate cumulative size of files\n *\n * @param fileList list of files to check\n * @returns size in bytes\n */\nexport function getCollectionSize(fileList) {\n return Array.from(fileList).reduce((sum, file) => sum + file.size, 0);\n}","export const mimes = {\n aac: 'audio/aac',\n abw: 'application/x-abiword',\n ai: 'application/postscript',\n arc: 'application/octet-stream',\n avi: 'video/x-msvideo',\n azw: 'application/vnd.amazon.ebook',\n bin: 'application/octet-stream',\n bz: 'application/x-bzip',\n bz2: 'application/x-bzip2',\n csh: 'application/x-csh',\n css: 'text/css',\n csv: 'text/csv',\n doc: 'application/msword',\n dll: 'application/octet-stream',\n dmg: 'application/x-apple-diskimage',\n eot: 'application/vnd.ms-fontobject',\n epub: 'application/epub+zip',\n gif: 'image/gif',\n htm: 'text/html',\n html: 'text/html',\n ico: 'image/x-icon',\n ics: 'text/calendar',\n jar: 'application/java-archive',\n jpeg: 'image/jpeg',\n jpg: 'image/jpeg',\n js: 'application/javascript',\n json: 'application/json',\n mid: 'audio/midi',\n midi: 'audio/midi',\n mp2: 'audio/mpeg',\n mp3: 'audio/mpeg',\n mp4: 'video/mp4',\n mpa: 'video/mpeg',\n mpe: 'video/mpeg',\n mpeg: 'video/mpeg',\n mpkg: 'application/vnd.apple.installer+xml',\n odp: 'application/vnd.oasis.opendocument.presentation',\n ods: 'application/vnd.oasis.opendocument.spreadsheet',\n odt: 'application/vnd.oasis.opendocument.text',\n oga: 'audio/ogg',\n ogv: 'video/ogg',\n ogx: 'application/ogg',\n otf: 'font/otf',\n png: 'image/png',\n pdf: 'application/pdf',\n ppt: 'application/vnd.ms-powerpoint',\n rar: 'application/x-rar-compressed',\n rtf: 'application/rtf',\n sh: 'application/x-sh',\n svg: 'image/svg+xml',\n swf: 'application/x-shockwave-flash',\n tar: 'application/x-tar',\n tif: 'image/tiff',\n tiff: 'image/tiff',\n ts: 'application/typescript',\n ttf: 'font/ttf',\n txt: 'text/plain',\n vsd: 'application/vnd.visio',\n wav: 'audio/x-wav',\n weba: 'audio/webm',\n webm: 'video/webm',\n webp: 'image/webp',\n woff: 'font/woff',\n woff2: 'font/woff2',\n xhtml: 'application/xhtml+xml',\n xls: 'application/vnd.ms-excel',\n xlsx: 'application/vnd.ms-excel',\n xml: 'application/xml',\n xul: 'application/vnd.mozilla.xul+xml',\n zip: 'application/zip',\n '3gp': 'video/3gpp',\n '3gp2': 'video/3gpp2',\n '7z': 'application/x-7z-compressed'\n};","import { AsyncQueue, Strings } from 'cafe-utility';\nimport { NULL_ADDRESS } from \"../index.js\";\nimport { MantarayNode } from \"../manifest/manifest.js\";\nimport { totalChunks } from \"./chunk-size.js\";\nimport { makeFilePath } from \"./collection.js\";\nimport { mimes } from \"./mime.js\";\nimport { BatchId, ChunkSplitter } from '@ethersphere/core-sdk';\nexport async function hashDirectory(_dir) {\n throw new Error('Hashing directories is not supported in browsers!');\n}\nexport async function streamDirectory(_bee, _dir, _postageBatchId, _onUploadProgress, _options, _requestOptions) {\n throw new Error('Streaming directories is not supported in browsers!');\n}\nexport async function streamFiles(bee, files, postageBatchId, onUploadProgress, options, requestOptions) {\n const signal = requestOptions?.signal;\n if (signal?.aborted) {\n throw new Error('Request aborted');\n }\n const queue = new AsyncQueue(64, 64);\n let total = 0;\n let processed = 0;\n for (const file of files) {\n total += totalChunks(file.size);\n }\n postageBatchId = new BatchId(postageBatchId);\n async function uploadAndTrack(chunk) {\n if (signal?.aborted) {\n return;\n }\n try {\n await bee.chunk.upload(postageBatchId, chunk.build(), options, requestOptions);\n onUploadProgress?.({\n total,\n processed: ++processed\n });\n } catch (err) {\n if (signal?.aborted) {\n return;\n }\n throw err;\n }\n }\n async function onBatch(batch) {\n for (const {\n chunk\n } of batch) {\n await queue.enqueue(async () => uploadAndTrack(chunk));\n }\n return [];\n }\n const mantaray = new MantarayNode();\n for (const file of files) {\n if (signal?.aborted) {\n throw new Error('Request aborted');\n }\n const rootChunk = await new Promise((resolve, reject) => {\n const tree = new ChunkSplitter(onBatch);\n let offset = 0;\n const reader = new FileReader();\n reader.onerror = () => {\n reject(reader.error);\n };\n const readNextChunk = async () => {\n if (signal?.aborted) {\n reject(new Error('Request aborted'));\n return;\n }\n if (offset >= file.size) {\n // ChunkSplitter's onBatch only sees chunks once they're referenced\n // by a parent - the root chunk returned by finalize() is never\n // passed to it, so it has to be uploaded here explicitly.\n const rootChunk = await tree.finalize();\n await queue.enqueue(async () => uploadAndTrack(rootChunk));\n resolve(rootChunk);\n return;\n }\n const slice = file.slice(offset, offset + 4096);\n reader.readAsArrayBuffer(slice);\n };\n reader.onload = async event => {\n if (!event.target) {\n reject('No event target');\n return;\n }\n const data = event.target.result;\n if (data) {\n await tree.append(new Uint8Array(data));\n offset += 4096;\n }\n readNextChunk();\n };\n readNextChunk();\n });\n await queue.drain();\n const {\n filename,\n extension\n } = Strings.parseFilename(file.name);\n mantaray.addFork(makeFilePath(file), rootChunk.hash(), {\n 'Content-Type': maybeEnrichMime(mimes[extension.toLowerCase()] || 'application/octet-stream'),\n Filename: filename\n });\n if (file.name === 'index.html') {\n mantaray.addFork('/', NULL_ADDRESS, {\n 'website-index-document': 'index.html'\n });\n }\n }\n if (signal?.aborted) {\n throw new Error('Request aborted');\n }\n return mantaray.saveRecursively(bee, postageBatchId, options, requestOptions);\n}\nfunction maybeEnrichMime(mime) {\n if (['text/html', 'text/css'].includes(mime)) {\n return `${mime}; charset=utf-8`;\n }\n return mime;\n}","export class TarStream {\n constructor() {\n this.pieces = [];\n this.currentFileSize = 0;\n }\n get output() {\n return this.pieces.reduce((acc, piece) => {\n const newAcc = new Uint8Array(acc.length + piece.length);\n newAcc.set(acc);\n newAcc.set(piece, acc.length);\n return newAcc;\n });\n }\n beginFile(path, size) {\n const {\n name,\n prefix,\n longLink\n } = splitPath(path);\n if (longLink) {\n const encoder = new TextEncoder();\n const pathData = encoder.encode(path + '\\0');\n this.pieces.push(createLongLinkHeader(pathData.length));\n this.pieces.push(pathData);\n const padding = pathData.length % 512 === 0 ? 0 : 512 - pathData.length % 512;\n if (padding > 0) this.pieces.push(new Uint8Array(padding));\n }\n const header = createHeader(name, prefix, size);\n this.pieces.push(header);\n this.currentFileSize = 0;\n }\n async appendFile(data) {\n this.pieces.push(data);\n this.currentFileSize += data.length;\n }\n async endFile() {\n const padding = this.currentFileSize % 512 === 0 ? 0 : 512 - this.currentFileSize % 512;\n if (padding > 0) {\n this.pieces.push(new Uint8Array(padding));\n }\n }\n async end() {\n this.pieces.push(createEndOfArchive());\n }\n}\nfunction splitPath(path) {\n if (path.length <= 100) return {\n name: path,\n prefix: '',\n longLink: false\n };\n for (let i = path.length - 1; i >= 0; i--) {\n if (path[i] === '/') {\n const name = path.substring(i + 1);\n const prefix = path.substring(0, i);\n if (name.length <= 100 && prefix.length <= 155) return {\n name,\n prefix,\n longLink: false\n };\n }\n }\n // Filename itself is > 100 chars or path > 255 chars — use GNU LongLink\n const lastSlash = path.lastIndexOf('/');\n const truncatedName = (lastSlash >= 0 ? path.substring(lastSlash + 1) : path).slice(0, 100);\n return {\n name: truncatedName,\n prefix: '',\n longLink: true\n };\n}\nfunction createLongLinkHeader(size) {\n const encoder = new TextEncoder();\n function writeToBuffer(str, offset, length) {\n const bytes = encoder.encode(str);\n header.set(bytes.slice(0, length), offset);\n }\n const header = new Uint8Array(512);\n header.fill(0);\n writeToBuffer('././@LongLink', 0, 100);\n writeToBuffer('0000644\\0', 100, 8);\n writeToBuffer('0000000\\0', 108, 8);\n writeToBuffer('0000000\\0', 116, 8);\n writeToBuffer(size.toString(8).padStart(11, '0') + '\\0', 124, 12);\n writeToBuffer('00000000000\\0', 136, 12);\n writeToBuffer(' ', 148, 8);\n writeToBuffer('L', 156, 1);\n writeToBuffer('ustar\\0\\0', 257, 8);\n let checksum = 0;\n for (let i = 0; i < 512; i++) checksum += header[i];\n writeToBuffer(checksum.toString(8).padStart(6, '0') + '\\0 ', 148, 8);\n return header;\n}\nfunction createHeader(name, prefix, size) {\n const encoder = new TextEncoder();\n function writeToBuffer(str, offset, length) {\n const bytes = encoder.encode(str);\n header.set(bytes.slice(0, length), offset);\n }\n // Initialize header with zeros\n const header = new Uint8Array(512);\n header.fill(0, 0, 512);\n // File name, truncated to 100 characters if necessary\n writeToBuffer(name.slice(0, 100).padEnd(100, '\\0'), 0, 100);\n // File mode (octal) and null-terminated\n writeToBuffer('0000777\\0', 100, 8);\n // UID and GID (octal) and null-terminated\n writeToBuffer('0001750\\0', 108, 8); // UID\n writeToBuffer('0001750\\0', 116, 8); // GID\n // File size in octal (11 chars) and null-terminated\n writeToBuffer(size.toString(8).padStart(11, '0') + '\\0', 124, 12);\n // Modification time in octal and null-terminated\n const modTime = Math.floor(new Date().getTime() / 1000);\n writeToBuffer(modTime.toString(8).padStart(11, '0') + '\\0', 136, 12);\n // Checksum placeholder (8 spaces)\n writeToBuffer(' ', 148, 8);\n // Typeflag (normal file)\n writeToBuffer('0', 156, 1);\n // USTAR magic and version\n writeToBuffer('ustar\\0\\0', 257, 8);\n // UStar prefix field (offset 345, 155 bytes) — used when path > 100 but fits split\n if (prefix) writeToBuffer(prefix.slice(0, 155).padEnd(155, '\\0'), 345, 155);\n // Calculate checksum\n let checksum = 0;\n for (let i = 0; i < 512; i++) checksum += header[i];\n writeToBuffer(checksum.toString(8).padStart(6, '0') + '\\0 ', 148, 8);\n return header;\n}\nfunction createEndOfArchive() {\n return new Uint8Array(1024);\n}","import { prepareRequestHeaders } from \"./headers.js\";\nimport { http } from \"./http.js\";\nimport { TarStream } from \"./tar.js\";\nimport { writeTar } from \"./tar-writer.js\";\nconst bzzEndpoint = 'bzz';\nexport async function uploadTar(requestOptions, collection, postageBatchId, options) {\n const tarStream = new TarStream();\n await writeTar(collection, tarStream);\n await tarStream.end();\n const response = await http(requestOptions, {\n method: 'post',\n url: bzzEndpoint,\n data: tarStream.output,\n responseType: 'json',\n headers: {\n 'content-type': 'application/x-tar',\n 'swarm-collection': 'true',\n ...prepareRequestHeaders(postageBatchId, options)\n }\n });\n return response;\n}","import { fileArrayBuffer } from \"./file.js\";\nexport async function writeTar(collection, tarStream) {\n for (const item of collection) {\n if (item.file) {\n tarStream.beginFile(item.path, item.file.size);\n await tarStream.appendFile(new Uint8Array(await fileArrayBuffer(item.file)));\n await tarStream.endFile();\n } else {\n throw new Error('Invalid collection item');\n }\n }\n}","import { Bytes, Reference } from '@ethersphere/core-sdk';\nimport { Optional } from 'cafe-utility';\nimport { UploadResultBody } from \"../types/schema/upload.js\";\nimport { assertCollection } from \"../utils/collection.js\";\nimport { prepareRequestHeaders, readFileHeaders } from \"../utils/headers.js\";\nimport { http } from \"../utils/http.js\";\nimport { uploadTar } from \"../utils/tar-uploader.js\";\nimport { isReadable, makeTagUid } from \"../utils/type.js\";\nconst bzzEndpoint = 'bzz';\n/**\n * Raw HTTP calls for the `/bzz` endpoint.\n */\nexport async function uploadFile(requestOptions, data, postageBatchId, name, options) {\n if (isReadable(data) && !options?.contentType) {\n if (!options) {\n options = {};\n }\n options.contentType = 'application/octet-stream';\n }\n const response = await http(requestOptions, {\n method: 'post',\n url: bzzEndpoint,\n data,\n headers: prepareRequestHeaders(postageBatchId, options),\n params: {\n name\n },\n responseType: 'json'\n });\n const body = UploadResultBody.parse(response.data);\n return {\n reference: body.reference,\n tagUid: response.headers['swarm-tag'] ? makeTagUid(response.headers['swarm-tag']) : undefined,\n historyAddress: response.headers['swarm-act-history-address'] ? Optional.of(new Reference(response.headers['swarm-act-history-address'])) : Optional.empty()\n };\n}\nexport async function downloadFile(requestOptions, resource, path = '', options) {\n const response = await http(requestOptions, {\n method: 'GET',\n responseType: 'arraybuffer',\n url: `${bzzEndpoint}/${resource}/${path}`,\n headers: prepareRequestHeaders(null, options)\n });\n const file = {\n ...readFileHeaders(response.headers),\n data: new Bytes(response.data)\n };\n return file;\n}\nexport async function downloadFileReadable(requestOptions, reference, path = '', options) {\n reference = new Reference(reference);\n const response = await http(requestOptions, {\n method: 'GET',\n responseType: 'stream',\n url: `${bzzEndpoint}/${reference}/${path}`,\n headers: prepareRequestHeaders(null, options)\n });\n const file = {\n ...readFileHeaders(response.headers),\n data: response.data\n };\n return file;\n}\n/*******************************************************************************************************************/\n// Collections\nexport async function uploadCollection(requestOptions, collection, postageBatchId, options) {\n assertCollection(collection);\n const response = await uploadTar(requestOptions, collection, postageBatchId, options);\n const body = UploadResultBody.parse(response.data);\n return {\n reference: body.reference,\n tagUid: response.headers['swarm-tag'] ? makeTagUid(response.headers['swarm-tag']) : undefined,\n historyAddress: response.headers['swarm-act-history-address'] ? Optional.of(new Reference(response.headers['swarm-act-history-address'])) : Optional.empty()\n };\n}","import { BatchId } from '@ethersphere/core-sdk';\nimport { hashDirectory as hashDirectoryImpl, streamDirectory as streamDirectoryImpl, streamFiles as streamFilesImpl } from \"../utils/chunk-stream.js\";\nimport { assertCollection, makeCollectionFromFileList } from \"../utils/collection.js\";\nimport { makeCollectionFromFS } from \"../utils/collection.node.js\";\nimport { CollectionUploadOptionsSchema } from \"../utils/schema.js\";\nimport * as api from \"../api/bzz.js\";\n/**\n * Collection (multi-file) operations backed by the `/bzz` endpoint.\n *\n * Accessed as `bee.collection`.\n */\nexport class Collection {\n constructor(context) {\n this.context = context;\n }\n /**\n * Uploads a collection that you assemble yourself.\n *\n * @param postageBatchId\n * @param collection\n * @param options Collection and request options\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async upload(postageBatchId, collection, options, requestOptions) {\n const batchId = new BatchId(postageBatchId);\n assertCollection(collection);\n if (options) {\n options = CollectionUploadOptionsSchema.parse(options);\n }\n return api.uploadCollection(this.context.getRequestOptionsForCall(requestOptions), collection, batchId, options);\n }\n /**\n * Uploads a collection of files using the browser `FileList` API.\n *\n * @param postageBatchId Postage BatchId to be used to upload the data with\n * @param fileList list of files to be uploaded\n * @param options Additional options like tag, encryption, pinning and request options\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async uploadFromFileList(postageBatchId, fileList, options, requestOptions) {\n const batchId = new BatchId(postageBatchId);\n if (options) {\n options = CollectionUploadOptionsSchema.parse(options);\n }\n const data = makeCollectionFromFileList(fileList);\n return api.uploadCollection(this.context.getRequestOptionsForCall(requestOptions), data, batchId, options);\n }\n /**\n * Uploads a collection of files from a directory on the filesystem.\n *\n * Available only in Node.js as it uses the `fs` module.\n *\n * @param postageBatchId Postage BatchId to be used to upload the data with\n * @param dir the path of the files to be uploaded\n * @param options Additional options like tag, encryption, pinning and request options\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async uploadFromDirectory(postageBatchId, dir, options, requestOptions) {\n const batchId = new BatchId(postageBatchId);\n if (options) {\n options = CollectionUploadOptionsSchema.parse(options);\n }\n const data = await makeCollectionFromFS(dir);\n return api.uploadCollection(this.context.getRequestOptionsForCall(requestOptions), data, batchId, options);\n }\n /**\n * Uploads a collection of files by streaming them to the Bee node, which supports arbitrary\n * sizes, but may be slower due to uploading chunks one by one.\n *\n * Only intended for the browser environment.\n *\n * @param postageBatchId\n * @param files\n * @param onUploadProgress\n * @param options\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async stream(postageBatchId, files, onUploadProgress, options, requestOptions) {\n const batchId = new BatchId(postageBatchId);\n return streamFilesImpl(this.context.bee, files, batchId, onUploadProgress, options, this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Uploads a directory by streaming its contents directly to the Bee node, which supports\n * arbitrary directory sizes, but may be slower due to uploading chunks one by one.\n *\n * Only intended for the Node.js environment.\n *\n * @param postageBatchId\n * @param dir\n * @param onUploadProgress\n * @param options\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async streamFromDirectory(postageBatchId, dir, onUploadProgress, options, requestOptions) {\n const batchId = new BatchId(postageBatchId);\n return streamDirectoryImpl(this.context.bee, dir, batchId, onUploadProgress, options, this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Hashes a directory locally and returns the root hash (Swarm reference).\n *\n * The actual Swarm reference may be different as there is no canonical hashing of directories.\n *\n * @param dir\n */\n async hashDirectory(dir) {\n return hashDirectoryImpl(dir);\n }\n}","/**\n * Creates array in the format of Collection with data loaded from directory on filesystem.\n * The function loads all the data into memory!\n *\n * @param dir path to the directory\n */\nexport async function makeCollectionFromFS(_dir) {\n throw new Error('Creating Collection from File System is not supported in browsers!');\n}\n/**\n * Calculate folder size recursively\n *\n * @param dir the path to the folder to check\n * @returns size in bytes\n */\nexport async function getFolderSize(_dir) {\n throw new Error('Creating Collection from File System is not supported in browsers!');\n}","import { GetBlocklistResponse, GetNodeAddressesResponse, GetPeersResponse, GetTopologyResponse } from \"../types/schema/connectivity.js\";\nimport { IsGatewayResponse } from \"../types/schema/status.js\";\nimport { http } from \"../utils/http.js\";\nconst addressesEndpoint = 'addresses';\nconst blocklistEndpoint = 'blocklist';\nconst peersEndpoint = 'peers';\nconst topologyEndpoint = 'topology';\nconst pingpongEndpoint = 'pingpong';\nconst gatewayEndpoint = '/gateway';\n/**\n * Raw HTTP calls for the connectivity endpoints (`/addresses`, `/peers`, `/topology`, `/blocklist`, `/pingpong`, etc.).\n */\n/** Pings the Bee node to see if there is a live Bee node on the given URL. */\nexport async function checkConnection(requestOptions) {\n await http(requestOptions, {\n url: '',\n responseType: 'text'\n });\n}\n/** Checks the `/gateway` endpoint to see if the remote API is a gateway. */\nexport async function isGateway(requestOptions) {\n const response = await http(requestOptions, {\n url: gatewayEndpoint\n });\n return IsGatewayResponse.parse(response.data).gateway;\n}\n/** Fetches the overlay, underlay, Ethereum, and other addresses of the Bee node. */\nexport async function getNodeAddresses(requestOptions) {\n const response = await http(requestOptions, {\n url: addressesEndpoint,\n responseType: 'json'\n });\n return GetNodeAddressesResponse.parse(response.data);\n}\n/** Fetches the list of blocked peers for this node. */\nexport async function getBlocklist(requestOptions) {\n const response = await http(requestOptions, {\n url: blocklistEndpoint,\n responseType: 'json'\n });\n return GetBlocklistResponse.parse(response.data).peers;\n}\n/** Gets list of peers for this node. */\nexport async function getPeers(requestOptions) {\n const response = await http(requestOptions, {\n url: peersEndpoint,\n responseType: 'json'\n });\n return GetPeersResponse.parse(response.data).peers;\n}\n/** Disconnects from a specific peer. */\nexport async function removePeer(requestOptions, peer) {\n const response = await http(requestOptions, {\n url: `${peersEndpoint}/${peer}`,\n responseType: 'json',\n method: 'DELETE'\n });\n return response.data;\n}\n/** Fetches topology and connectivity information of the Bee node. */\nexport async function getTopology(requestOptions) {\n const response = await http(requestOptions, {\n url: topologyEndpoint,\n responseType: 'json'\n });\n return GetTopologyResponse.parse(response.data);\n}\n/** Pings a specific peer to check its availability. */\nexport async function ping(requestOptions, peer) {\n const response = await http(requestOptions, {\n url: `${pingpongEndpoint}/${peer}`,\n responseType: 'json',\n method: 'POST'\n });\n return response.data;\n}","import { PeerAddress } from '@ethersphere/core-sdk';\nimport * as api from \"../api/connectivity.js\";\n/**\n * Peer, topology and network connectivity operations.\n *\n * Accessed as `bee.connectivity`.\n */\nexport class Connectivity {\n constructor(context) {\n this.context = context;\n }\n /**\n * Pings the Bee node to see if there is a live Bee node on the given URL.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n * @throws If connection was not successful throw error\n */\n async checkConnection(requestOptions) {\n return api.checkConnection(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Pings the Bee node to see if there is a live Bee node on the given URL.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n * @returns true if successful, false on error\n */\n async isConnected(requestOptions) {\n try {\n await this.checkConnection(requestOptions);\n } catch {\n return false;\n }\n return true;\n }\n /**\n * Checks the `/gateway` endpoint to see if the remote API is a gateway.\n *\n * Do note that this is not a standard way to check for gateway nodes,\n * but some of the gateway tooling expose this endpoint.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async isGateway(requestOptions) {\n try {\n return await api.isGateway(this.context.getRequestOptionsForCall(requestOptions));\n } catch {\n return false;\n }\n }\n /**\n * Fetches the overlay, underlay, Ethereum, and other addresses of the Bee node.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getNodeAddresses(requestOptions) {\n return api.getNodeAddresses(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Fetches the list of blocked peers for this node.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getBlocklist(requestOptions) {\n return api.getBlocklist(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Gets list of peers for this node.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getPeers(requestOptions) {\n return api.getPeers(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Disconnects from a specific peer.\n *\n * @param peer Overlay address of the peer to be removed.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async removePeer(peer, requestOptions) {\n const address = new PeerAddress(peer);\n return api.removePeer(this.context.getRequestOptionsForCall(requestOptions), address);\n }\n /**\n * Fetches topology and connectivity information of the Bee node.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getTopology(requestOptions) {\n return api.getTopology(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Pings a specific peer to check its availability.\n *\n * @param peer Overlay address of the peer to be pinged.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async ping(peer, requestOptions) {\n const address = new PeerAddress(peer);\n return api.ping(this.context.getRequestOptionsForCall(requestOptions), address);\n }\n}","import { Bytes, Reference } from '@ethersphere/core-sdk';\nimport { Optional } from 'cafe-utility';\nimport { UploadResultBody } from \"../types/schema/upload.js\";\nimport { prepareRequestHeaders } from \"../utils/headers.js\";\nimport { http } from \"../utils/http.js\";\nimport { DownloadOptionsSchema } from \"../utils/schema.js\";\nimport { makeTagUid } from \"../utils/type.js\";\nconst endpoint = 'bytes';\n/**\n * Raw HTTP calls for the `/bytes` endpoint.\n */\nexport async function upload(requestOptions, data, postageBatchId, options) {\n const response = await http(requestOptions, {\n url: endpoint,\n method: 'post',\n responseType: 'json',\n data,\n headers: {\n 'content-type': 'application/octet-stream',\n ...prepareRequestHeaders(postageBatchId, options)\n }\n });\n const body = UploadResultBody.parse(response.data);\n return {\n reference: body.reference,\n tagUid: response.headers['swarm-tag'] ? makeTagUid(response.headers['swarm-tag']) : undefined,\n historyAddress: response.headers['swarm-act-history-address'] ? Optional.of(new Reference(response.headers['swarm-act-history-address'])) : Optional.empty()\n };\n}\nexport async function head(requestOptions, reference) {\n const response = await http(requestOptions, {\n url: `${endpoint}/${reference}`,\n method: 'head',\n responseType: 'json'\n });\n return {\n contentLength: parseInt(response.headers['content-length'])\n };\n}\nexport async function download(requestOptions, resource, options) {\n if (options) {\n options = DownloadOptionsSchema.parse(options);\n }\n const response = await http(requestOptions, {\n responseType: 'arraybuffer',\n url: `${endpoint}/${resource}`,\n headers: prepareRequestHeaders(null, options)\n });\n return new Bytes(response.data);\n}\nexport async function downloadReadable(requestOptions, resource, options) {\n if (options) {\n options = DownloadOptionsSchema.parse(options);\n }\n const response = await http(requestOptions, {\n responseType: 'stream',\n url: `${endpoint}/${resource}`,\n headers: prepareRequestHeaders(null, options)\n });\n return response.data;\n}","import { IsRetrievableResponse } from \"../types/schema/stewardship.js\";\nimport { http } from \"../utils/http.js\";\nconst stewardshipEndpoint = 'stewardship';\n/**\n * Raw HTTP calls for the `/stewardship` endpoint.\n */\nexport async function reupload(requestOptions, stamp, reference) {\n await http(requestOptions, {\n method: 'put',\n url: `${stewardshipEndpoint}/${reference}`,\n headers: {\n 'swarm-postage-batch-id': stamp.toHex()\n }\n });\n}\nexport async function isRetrievable(requestOptions, reference) {\n const response = await http(requestOptions, {\n method: 'get',\n responseType: 'json',\n url: `${stewardshipEndpoint}/${reference}`\n });\n return IsRetrievableResponse.parse(response.data).isRetrievable;\n}","import { Reference } from '@ethersphere/core-sdk';\nimport { z } from 'zod';\nexport class ResourceLocator {\n constructor(raw) {\n this.raw = raw;\n }\n toString() {\n const asString = z.string().safeParse(this.raw);\n if (asString.success && asString.data.includes('.eth')) {\n return asString.data;\n }\n return new Reference(this.raw).toHex();\n }\n}","import { BatchId, Reference } from '@ethersphere/core-sdk';\nimport * as bytes from \"../api/bytes.js\";\nimport * as stewardship from \"../api/stewardship.js\";\nimport { ResourceLocator } from \"../utils/resource-locator.js\";\nimport { DownloadOptionsSchema, RedundantUploadOptionsSchema } from \"../utils/schema.js\";\nimport { assertData } from \"../utils/type.js\";\n/**\n * Raw data operations backed by the `/bytes` endpoint.\n *\n * Accessed as `bee.data`.\n */\nexport class Data {\n constructor(context) {\n this.context = context;\n }\n /**\n * Uploads raw data through the `POST /bytes` endpoint.\n *\n * Data uploaded with this method should be retrieved with {@link download}.\n *\n * @param postageBatchId Usable Postage Batch ID with sufficient capacity to upload the data.\n * @param data A `string` (text data) or `Uint8Array` (raw data) to be uploaded.\n * @param options Additional options like tag, encryption, pinning, content-type and request options.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async upload(postageBatchId, data, options, requestOptions) {\n const batchId = new BatchId(postageBatchId);\n assertData(data);\n if (options) {\n options = RedundantUploadOptionsSchema.parse(options);\n }\n return bytes.upload(this.context.getRequestOptionsForCall(requestOptions), data, batchId, options);\n }\n /**\n * Downloads raw data through the `GET /bytes/{reference}` endpoint.\n *\n * @param resource Swarm reference, Swarm CID, or ENS domain.\n * @param options Options that affects the request behavior\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async download(resource, options, requestOptions) {\n if (options) {\n options = DownloadOptionsSchema.parse(options);\n }\n return bytes.download(this.context.getRequestOptionsForCall(requestOptions), new ResourceLocator(resource), options);\n }\n /**\n * Downloads raw data through the `GET /bytes/{reference}` endpoint as a readable stream.\n *\n * Only tested in Node.js environment.\n *\n * @param resource Swarm reference, Swarm CID, or ENS domain.\n * @param options Options that affects the request behavior.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async downloadReadable(resource, options, requestOptions) {\n if (options) {\n options = DownloadOptionsSchema.parse(options);\n }\n return bytes.downloadReadable(this.context.getRequestOptionsForCall(requestOptions), new ResourceLocator(resource), options);\n }\n /**\n * Fetches content length for a `/bytes` reference through the `HEAD /bytes/{reference}` endpoint.\n *\n * @param reference\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async probe(reference, requestOptions) {\n const ref = new Reference(reference);\n return bytes.head(this.context.getRequestOptionsForCall(requestOptions), ref);\n }\n /**\n * Checks if content specified by reference is retrievable from the network.\n *\n * @param reference Bee reference to be checked in hex string (either 64 or 128 chars long) or ENS domain.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async isRetrievable(reference, requestOptions) {\n const ref = new Reference(reference);\n return stewardship.isRetrievable(this.context.getRequestOptionsForCall(requestOptions), ref);\n }\n}","import { Bytes, FeedIndex } from '@ethersphere/core-sdk';\nimport { UploadResultBody } from \"../types/schema/upload.js\";\nimport { BeeError } from \"../utils/error.js\";\nimport { prepareRequestHeaders } from \"../utils/headers.js\";\nimport { http } from \"../utils/http.js\";\nconst feedEndpoint = 'feeds';\nexport async function createFeedManifest(requestOptions, owner, topic, stamp, options) {\n const response = await http(requestOptions, {\n method: 'post',\n responseType: 'json',\n url: `${feedEndpoint}/${owner}/${topic}`,\n headers: prepareRequestHeaders(stamp, options)\n });\n return UploadResultBody.parse(response.data).reference;\n}\nfunction readFeedUpdateHeaders(headers) {\n const feedIndex = headers['swarm-feed-index'];\n const feedIndexNext = headers['swarm-feed-index-next'];\n if (!feedIndex) {\n throw new BeeError('Response did not contain expected swarm-feed-index!');\n }\n if (!feedIndexNext) {\n throw new BeeError('Response did not contain expected swarm-feed-index-next!');\n }\n return {\n feedIndex: new FeedIndex(feedIndex),\n feedIndexNext: new FeedIndex(feedIndexNext)\n };\n}\nexport async function fetchLatestFeedUpdate(requestOptions, owner, topic, options) {\n const response = await http(requestOptions, {\n responseType: 'arraybuffer',\n url: `${feedEndpoint}/${owner}/${topic}`,\n params: {\n ...options\n }\n });\n return {\n payload: new Bytes(response.data),\n ...readFeedUpdateHeaders(response.headers)\n };\n}\nexport async function probeFeed(requestOptions, owner, topic) {\n const response = await http(requestOptions, {\n responseType: 'arraybuffer',\n url: `${feedEndpoint}/${owner}/${topic}`,\n params: {\n 'Swarm-Only-Root-Chunk': true\n }\n });\n return readFeedUpdateHeaders(response.headers);\n}","import { Bytes, FeedIndex, Identifier, keccak256 } from '@ethersphere/core-sdk';\nexport function makeFeedIdentifier(topic, index) {\n index = typeof index === 'number' ? FeedIndex.fromBigInt(BigInt(index)) : index;\n return new Identifier(keccak256(Bytes.concat(topic.toUint8Array(), index.toUint8Array())));\n}","import { Optional } from 'cafe-utility';\nimport { z } from 'zod';\nimport { Bytes, FeedIndex, Reference, keccak256, makeContentAddressedChunk, numberToUint64, uint64ToNumber, unmarshalContentAddressedChunk } from '@ethersphere/core-sdk';\nimport { unmarshalSingleOwnerChunk, uploadSingleOwnerChunkData, uploadSingleOwnerChunkWithWrappedChunk } from \"../chunk/soc.js\";\nimport * as bytes from \"../api/bytes.js\";\nimport * as chunkAPI from \"../api/chunk.js\";\nimport { fetchLatestFeedUpdate, probeFeed } from \"../api/feed.js\";\nimport { BeeResponseError } from \"../utils/error.js\";\nimport { ResourceLocator } from \"../utils/resource-locator.js\";\nimport { makeFeedIdentifier } from \"./identifier.js\";\nconst TIMESTAMP_PAYLOAD_OFFSET = 0;\nconst TIMESTAMP_PAYLOAD_SIZE = 8;\nconst REFERENCE_PAYLOAD_OFFSET = TIMESTAMP_PAYLOAD_SIZE;\nexport async function findNextIndex(requestOptions, owner, topic) {\n try {\n const feedUpdate = await fetchLatestFeedUpdate(requestOptions, owner, topic);\n if (!feedUpdate.feedIndexNext) {\n throw Error('Feed index next is not defined. This should happen when fetching an exact index.');\n }\n return feedUpdate.feedIndexNext;\n } catch (e) {\n if (e instanceof BeeResponseError) {\n return FeedIndex.fromBigInt(0n);\n }\n throw e;\n }\n}\nexport async function updateFeedWithReference(requestOptions, signer, topic, reference, postageBatchId, options) {\n reference = new Reference(reference);\n const nextIndex = options?.index ?? (await findNextIndex(requestOptions, signer.publicKey().address(), topic));\n const identifier = makeFeedIdentifier(topic, nextIndex);\n const at = options?.at ?? Date.now() / 1000.0;\n const timestamp = numberToUint64(BigInt(Math.floor(at)), 'BE');\n const payloadBytes = Bytes.concat(timestamp, reference.toUint8Array());\n return uploadSingleOwnerChunkData(requestOptions, signer, postageBatchId, identifier, payloadBytes, options);\n}\nexport async function updateFeedWithPayload(requestOptions, signer, topic, data, postageBatchId, options) {\n const nextIndex = options?.index ?? (await findNextIndex(requestOptions, signer.publicKey().address(), topic));\n const identifier = makeFeedIdentifier(topic, nextIndex);\n if (data.length > 4096) {\n const uploadResult = await bytes.upload(requestOptions, data, postageBatchId, options);\n const rootChunk = unmarshalContentAddressedChunk(await chunkAPI.download(requestOptions, uploadResult.reference));\n return uploadSingleOwnerChunkWithWrappedChunk(requestOptions, signer, postageBatchId, identifier, rootChunk, options);\n }\n const dataString = z.string().safeParse(data);\n return uploadSingleOwnerChunkData(requestOptions, signer, postageBatchId, identifier, dataString.success ? Bytes.fromUtf8(dataString.data).toUint8Array() : data, options);\n}\nexport function getFeedUpdateChunkReference(owner, topic, index) {\n const identifier = makeFeedIdentifier(topic, index);\n return new Reference(keccak256(Bytes.concat(identifier.toUint8Array(), owner.toUint8Array())));\n}\nexport async function downloadFeedUpdate(requestOptions, owner, topic, index, hasTimestamp = false) {\n index = typeof index === 'number' ? FeedIndex.fromBigInt(BigInt(index)) : index;\n const address = getFeedUpdateChunkReference(owner, topic, index);\n const data = await chunkAPI.download(requestOptions, address.toHex());\n const soc = unmarshalSingleOwnerChunk(data, address);\n let timestamp = Optional.empty();\n if (hasTimestamp) {\n const timestampBytes = Bytes.fromSlice(soc.payload.toUint8Array(), TIMESTAMP_PAYLOAD_OFFSET, TIMESTAMP_PAYLOAD_SIZE);\n timestamp = Optional.of(Number(uint64ToNumber(timestampBytes.toUint8Array(), 'BE')));\n }\n return {\n timestamp,\n payload: new Bytes(soc.payload.offset(hasTimestamp ? REFERENCE_PAYLOAD_OFFSET : 0))\n };\n}\nexport async function downloadFeedUpdateAsCAC(requestOptions, owner, topic, index) {\n index = typeof index === 'number' ? FeedIndex.fromBigInt(BigInt(index)) : index;\n const address = getFeedUpdateChunkReference(owner, topic, index);\n const data = await chunkAPI.download(requestOptions, address);\n const soc = unmarshalSingleOwnerChunk(data, address);\n return makeContentAddressedChunk(soc.payload, soc.span);\n}\nexport function makeFeedReader(requestOptions, topic, owner) {\n // TODO: remove after enough time has passed in deprecated version\n const download = async options => {\n if (options?.index === undefined) {\n return fetchLatestFeedUpdate(requestOptions, owner, topic);\n }\n const update = await downloadFeedUpdate(requestOptions, owner, topic, options.index, options.hasTimestamp ?? true);\n const feedIndex = typeof options.index === 'number' ? FeedIndex.fromBigInt(BigInt(options.index)) : options.index;\n return {\n payload: update.payload,\n feedIndex,\n feedIndexNext: feedIndex.next()\n };\n };\n const downloadPayload = async options => {\n if (options?.index === undefined) {\n return fetchLatestFeedUpdate(requestOptions, owner, topic);\n }\n const cac = await downloadFeedUpdateAsCAC(requestOptions, owner, topic, options.index);\n const payload = cac.span.toBigInt() <= 4096n ? cac.payload : await bytes.download(requestOptions, new ResourceLocator(cac.address));\n const feedIndex = typeof options.index === 'number' ? FeedIndex.fromBigInt(BigInt(options.index)) : options.index;\n return {\n payload,\n feedIndex,\n feedIndexNext: feedIndex.next()\n };\n };\n const downloadReference = async options => {\n let index = options?.index;\n if (index === undefined) {\n index = (await probeFeed(requestOptions, owner, topic)).feedIndex;\n }\n const payload = await download({\n ...options,\n index: index\n });\n return {\n reference: new Reference(payload.payload.toUint8Array()),\n feedIndex: payload.feedIndex,\n feedIndexNext: payload.feedIndexNext ?? payload.feedIndex.next()\n };\n };\n return {\n download,\n downloadPayload,\n downloadReference,\n owner,\n topic\n };\n}\nexport function makeFeedWriter(requestOptions, topic, signer) {\n const upload = async (postageBatchId, reference, options) => {\n return updateFeedWithReference(requestOptions, signer, topic, reference, postageBatchId, options);\n };\n const uploadPayload = async (postageBatchId, data, options) => {\n return updateFeedWithPayload(requestOptions, signer, topic, data, postageBatchId, options);\n };\n return {\n ...makeFeedReader(requestOptions, topic, signer.publicKey().address()),\n upload,\n uploadReference: upload,\n uploadPayload\n };\n}","import { FeedIndex } from '@ethersphere/core-sdk';\nimport { Objects } from 'cafe-utility';\nimport { getFeedUpdateChunkReference } from \"./index.js\";\n/**\n * Function that checks if a chunk is retrievable by actually downloading it.\n * The /stewardship/{reference} endpoint does not support verification of chunks, but only manifest's references.\n *\n * @param bee\n * @param ref\n * @param options\n */\nasync function isChunkRetrievable(bee, reference, options, requestOptions) {\n try {\n await bee.chunk.download(reference, options, requestOptions);\n return true;\n } catch (e) {\n const status = Objects.getDeep(e, 'status');\n if (status === 404 || status === 500) {\n return false;\n }\n throw e;\n }\n}\n/**\n * Creates array of references for all sequence updates chunk up to the given index.\n *\n * @param owner\n * @param topic\n * @param index\n */\nfunction getAllSequenceUpdateReferences(owner, topic, index) {\n const count = index.toBigInt();\n const updateReferences = [];\n for (let i = 0n; i <= count; i++) {\n updateReferences.push(getFeedUpdateChunkReference(owner, topic, FeedIndex.fromBigInt(i)));\n }\n return updateReferences;\n}\nexport async function areAllSequentialFeedsUpdateRetrievable(bee, owner, topic, index, options, requestOptions) {\n const chunkRetrievablePromises = getAllSequenceUpdateReferences(owner, topic, index).map(async reference => isChunkRetrievable(bee, reference, options, requestOptions));\n return (await Promise.all(chunkRetrievablePromises)).every(result => result);\n}","import { BatchId, EthAddress, PrivateKey, Topic } from '@ethersphere/core-sdk';\nimport { Objects } from 'cafe-utility';\nimport { createFeedManifest, fetchLatestFeedUpdate } from \"../api/feed.js\";\nimport { makeFeedReader, makeFeedWriter } from \"../feed/index.js\";\nimport { areAllSequentialFeedsUpdateRetrievable } from \"../feed/retrievable.js\";\nimport { DownloadOptionsSchema, UploadOptionsSchema } from \"../utils/schema.js\";\n/**\n * Feed operations.\n *\n * Accessed as `bee.feed`. Delegates to the feed subsystem (`src/feed`).\n */\nexport class Feed {\n constructor(context) {\n this.context = context;\n }\n /**\n * Makes a new feed reader for downloading feed updates.\n *\n * @param topic Topic in hex or bytes\n * @param owner Owner's ethereum address in hex or bytes\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n makeReader(topic, owner, requestOptions) {\n return makeFeedReader(this.context.getRequestOptionsForCall(requestOptions), new Topic(topic), new EthAddress(owner));\n }\n /**\n * Makes a new feed writer for updating feeds.\n *\n * @param topic Topic in hex or bytes\n * @param signer The signer's private key. Falls back to the Bee instance signer.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n makeWriter(topic, signer, requestOptions) {\n const key = signer ? new PrivateKey(signer) : this.context.signer;\n if (!key) {\n throw Error('No signer provided');\n }\n return makeFeedWriter(this.context.getRequestOptionsForCall(requestOptions), new Topic(topic), key);\n }\n /**\n * Creates a feed manifest chunk and returns the reference to it.\n *\n * @param postageBatchId Postage BatchId to be used to create the Feed Manifest\n * @param topic Topic in hex or bytes\n * @param owner Owner's ethereum address in hex or bytes\n * @param options Options that affect the request behavior\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async createManifest(postageBatchId, topic, owner, options, requestOptions) {\n const batchId = new BatchId(postageBatchId);\n const feedTopic = new Topic(topic);\n const feedOwner = new EthAddress(owner);\n if (options) {\n options = UploadOptionsSchema.parse(options);\n }\n return createFeedManifest(this.context.getRequestOptionsForCall(requestOptions), feedOwner, feedTopic, batchId, options);\n }\n /**\n * Fetches the latest feed update.\n *\n * @param topic Topic in hex or bytes\n * @param owner Owner's ethereum address in hex or bytes\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async fetchLatestUpdate(topic, owner, requestOptions) {\n return fetchLatestFeedUpdate(this.context.getRequestOptionsForCall(requestOptions), new EthAddress(owner), new Topic(topic));\n }\n /**\n * Validates whether a feed is retrievable in the network.\n *\n * @param owner Owner's ethereum address in hex or bytes\n * @param topic Topic in hex or bytes\n * @param index Optional feed index\n * @param options Download options\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async isRetrievable(owner, topic, index, options, requestOptions) {\n const feedOwner = new EthAddress(owner);\n const feedTopic = new Topic(topic);\n if (options) {\n options = DownloadOptionsSchema.parse(options);\n }\n if (!index) {\n try {\n await this.makeReader(feedTopic, feedOwner, requestOptions).download();\n return true;\n } catch (e) {\n const status = Objects.getDeep(e, 'status');\n if (status === 404 || status === 500) {\n return false;\n }\n throw e;\n }\n }\n return areAllSequentialFeedsUpdateRetrievable(this.context.bee, feedOwner, feedTopic, index, options, this.context.getRequestOptionsForCall(requestOptions));\n }\n}","import { BatchId, Reference } from '@ethersphere/core-sdk';\nimport { fileArrayBuffer, isFile } from \"../utils/file.js\";\nimport { ResourceLocator } from \"../utils/resource-locator.js\";\nimport { DownloadOptionsSchema, FileUploadOptionsSchema } from \"../utils/schema.js\";\nimport { assertFileData } from \"../utils/type.js\";\nimport * as api from \"../api/bzz.js\";\n/**\n * Single-file operations backed by the `/bzz` endpoint.\n *\n * Accessed as `bee.file`.\n */\nexport class File {\n constructor(context) {\n this.context = context;\n }\n /**\n * Uploads a single file to a Bee node.\n *\n * To download the file, use {@link download}.\n *\n * @param postageBatchId Postage BatchId to be used to upload the data with\n * @param data Data or file to be uploaded\n * @param name Optional name of the uploaded file\n * @param options Additional options like tag, encryption, pinning, content-type and request options\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async upload(postageBatchId, data, name, options, requestOptions) {\n const batchId = new BatchId(postageBatchId);\n assertFileData(data);\n if (options) {\n options = FileUploadOptionsSchema.parse(options);\n }\n if (name && typeof name !== 'string') {\n throw new TypeError('name has to be string or undefined!');\n }\n if (isFile(data)) {\n const fileData = await fileArrayBuffer(data);\n const fileName = name ?? data.name;\n const contentType = data.type;\n const fileOptions = {\n ...options,\n contentType\n };\n return api.uploadFile(this.context.getRequestOptionsForCall(requestOptions), fileData, batchId, fileName, fileOptions);\n } else {\n return api.uploadFile(this.context.getRequestOptionsForCall(requestOptions), data, batchId, name, options);\n }\n }\n /**\n * Downloads a single file.\n *\n * @param resource Swarm reference, Swarm CID, or ENS domain.\n * @param path If reference points to manifest, then this parameter defines path to the file\n * @param options Options that affects the request behavior\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async download(resource, path = '', options, requestOptions) {\n if (options) {\n options = DownloadOptionsSchema.parse(options);\n }\n return api.downloadFile(this.context.getRequestOptionsForCall(requestOptions), new ResourceLocator(resource), path, options);\n }\n /**\n * Downloads a single file as a readable stream.\n *\n * @param reference Bee file reference in hex string (either 64 or 128 chars long), ENS domain or Swarm CID.\n * @param path If reference points to manifest / collections, then this parameter defines path to the file\n * @param options Options that affects the request behavior\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async downloadReadable(reference, path = '', options, requestOptions) {\n const ref = new Reference(reference);\n if (options) {\n options = DownloadOptionsSchema.parse(options);\n }\n return api.downloadFileReadable(this.context.getRequestOptionsForCall(requestOptions), ref, path, options);\n }\n}","import { GetGranteesBodyResponse, GranteesResultBodyResponse } from \"../types/schema/grantee.js\";\nimport { prepareRequestHeaders } from \"../utils/headers.js\";\nimport { http } from \"../utils/http.js\";\nconst granteeEndpoint = 'grantee';\n/** Creates grantees for a postage batch. */\nexport async function createGrantees(requestOptions, batchId, publicKeys) {\n const response = await http(requestOptions, {\n method: 'post',\n url: granteeEndpoint,\n data: {\n grantees: publicKeys.map(x => x.toCompressedHex())\n },\n headers: prepareRequestHeaders(batchId),\n responseType: 'json'\n });\n const body = GranteesResultBodyResponse.parse(response.data);\n return {\n status: response.status,\n statusText: response.statusText,\n ref: body.ref,\n historyref: body.historyref\n };\n}\n/** Retrieves the grantees for a given reference. */\nexport async function getGrantees(requestOptions, reference) {\n const response = await http(requestOptions, {\n method: 'get',\n url: `${granteeEndpoint}/${reference}`,\n responseType: 'json'\n });\n return {\n status: response.status,\n statusText: response.statusText,\n grantees: GetGranteesBodyResponse.parse(response.data)\n };\n}\n/** Updates the grantees of a specific reference and history. */\nexport async function patchGrantees(requestOptions, batchId, reference, history, publicKeys) {\n const response = await http(requestOptions, {\n method: 'patch',\n url: `${granteeEndpoint}/${reference}`,\n data: {\n add: publicKeys.add.map(x => x.toCompressedHex()),\n revoke: publicKeys.revoke.map(x => x.toCompressedHex())\n },\n headers: {\n ...prepareRequestHeaders(batchId),\n 'swarm-act-history-address': history.toHex()\n },\n responseType: 'json'\n });\n const body = GranteesResultBodyResponse.parse(response.data);\n return {\n status: response.status,\n statusText: response.statusText,\n ref: body.ref,\n historyref: body.historyref\n };\n}","import { BatchId, PublicKey, Reference } from '@ethersphere/core-sdk';\nimport * as api from \"../api/grantee.js\";\n/**\n * Grantee (access control) operations.\n *\n * Accessed as `bee.grantee`.\n */\nexport class Grantee {\n constructor(context) {\n this.context = context;\n }\n /**\n * Creates grantees for a postage batch.\n *\n * @param postageBatchId\n * @param grantees Public keys of the grantees.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async create(postageBatchId, grantees, requestOptions) {\n const batchId = new BatchId(postageBatchId);\n const publicKeys = grantees.map(x => new PublicKey(x));\n return api.createGrantees(this.context.getRequestOptionsForCall(requestOptions), batchId, publicKeys);\n }\n /**\n * Retrieves the grantees for a given reference.\n *\n * @param reference The reference.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async get(reference, requestOptions) {\n const ref = new Reference(reference);\n return api.getGrantees(this.context.getRequestOptionsForCall(requestOptions), ref);\n }\n /**\n * Updates the grantees of a specific reference and history.\n *\n * @param postageBatchId The ID of the postage batch.\n * @param reference The reference.\n * @param history The history.\n * @param grantees The grantees to add and/or revoke.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async patch(postageBatchId, reference, history, grantees, requestOptions) {\n const batchId = new BatchId(postageBatchId);\n const ref = new Reference(reference);\n const historyRef = new Reference(history);\n const publicKeys = {\n add: grantees.add?.map(x => new PublicKey(x)) ?? [],\n revoke: grantees.revoke?.map(x => new PublicKey(x)) ?? []\n };\n return api.patchGrantees(this.context.getRequestOptionsForCall(requestOptions), batchId, ref, historyRef, publicKeys);\n }\n}","export async function prepareWebsocketData(data) {\n if (typeof data === 'string') {\n return new TextEncoder().encode(data);\n }\n if (data instanceof ArrayBuffer) {\n return new Uint8Array(data);\n }\n if (data instanceof Blob) {\n return new Uint8Array(await new Response(data).arrayBuffer());\n }\n throw new TypeError('unknown websocket data type');\n}","import { Binary, System } from 'cafe-utility';\nimport WebSocket from 'isomorphic-ws';\nimport { BatchId, Bytes, EthAddress, Identifier, makeContentAddressedChunk, numberToUint256, PeerAddress, PrivateKey, PublicKey } from '@ethersphere/core-sdk';\nimport * as pssApi from \"../api/pss.js\";\nimport { makeSOCAddress, uploadSingleOwnerChunk } from \"../chunk/soc.js\";\nimport { prepareWebsocketData } from \"../utils/data.js\";\nimport { BeeError } from \"../utils/error.js\";\nimport { GsocMessageHandlerSchema, PssMessageHandlerSchema } from \"../utils/schema.js\";\nimport { assertData } from \"../utils/type.js\";\nconst PSS_ENDPOINT = 'pss';\nconst GSOC_ENDPOINT = 'gsoc';\n/**\n * Messaging protocols — PSS (Postal Service for Swarm) and GSOC. Method names keep\n * their `pss`/`gsoc` prefixes to distinguish the two protocols within the namespace.\n *\n * Accessed as `bee.messaging`.\n */\nexport class Messaging {\n constructor(context) {\n this.context = context;\n this.cancelled = new WeakSet();\n }\n /**\n * Sends data to a recipient or target with the Postal Service for Swarm.\n *\n * **Warning! Only full nodes can accept PSS messages.**\n *\n * @param postageBatchId Postage BatchId that will be assigned to the sent message.\n * @param topic Topic name\n * @param target Target message address prefix\n * @param data Message to send\n * @param recipient Optional recipient public key\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async pssSend(postageBatchId, topic, target, data, recipient, requestOptions) {\n const batchId = new BatchId(postageBatchId);\n assertData(data);\n const recipientKey = recipient ? new PublicKey(recipient) : undefined;\n await pssApi.send(this.context.getRequestOptionsForCall(requestOptions), batchId, topic, target, data, recipientKey);\n }\n /**\n * Subscribes to messages for the given topic with the Postal Service for Swarm.\n *\n * **Warning! Only full nodes can accept PSS messages.**\n *\n * @param topic Topic name\n * @param handler Message handler interface\n */\n pssSubscribe(topic, handler) {\n handler = PssMessageHandlerSchema.parse(handler);\n const ws = this.openWebSocket(`${PSS_ENDPOINT}/subscribe/${topic.toHex()}`);\n const cancel = this.makeCancel(ws);\n const subscription = {\n topic,\n cancel\n };\n ws.onmessage = async event => {\n const data = await prepareWebsocketData(event.data);\n if (data.length) {\n handler.onMessage(new Bytes(data), subscription);\n }\n };\n ws.onerror = event => {\n if (!this.isCancelled(ws)) {\n handler.onError(new BeeError(event.message), subscription);\n }\n };\n ws.onclose = () => {\n handler.onClose(subscription);\n };\n return subscription;\n }\n /**\n * Receives a single message using the Postal Service for Swarm.\n *\n * **Warning! Only full nodes can accept PSS messages.**\n *\n * @param topic Topic name\n * @param timeoutMsec Timeout in milliseconds\n */\n async pssReceive(topic, timeoutMsec = 0) {\n if (typeof timeoutMsec !== 'number') {\n throw new TypeError('timeoutMsc parameter has to be a number!');\n }\n return new Promise((resolve, reject) => {\n let timeout;\n const subscription = this.pssSubscribe(topic, {\n onError: error => {\n clearTimeout(timeout);\n subscription.cancel();\n reject(error.message);\n },\n onMessage: message => {\n clearTimeout(timeout);\n subscription.cancel();\n resolve(message);\n },\n onClose: () => {\n clearTimeout(timeout);\n subscription.cancel();\n }\n });\n if (timeoutMsec > 0) {\n timeout = setTimeout(() => {\n subscription.cancel();\n reject(new BeeError('pssReceive timeout'));\n }, timeoutMsec);\n }\n });\n }\n /**\n * Mines the signer (a private key) to be used to send GSOC messages to the specific target overlay address.\n *\n * @param targetOverlay\n * @param identifier\n * @param proximity\n */\n gsocMine(targetOverlay, identifier, proximity = 12) {\n const overlay = new PeerAddress(targetOverlay);\n const id = new Identifier(identifier);\n const start = 0xb33n;\n for (let i = 0n; i < 0xffffn; i++) {\n const signer = new PrivateKey(numberToUint256(start + i, 'BE'));\n const socAddress = makeSOCAddress(id, signer.publicKey().address());\n // TODO: test the significance of the hardcoded 256\n const actualProximity = 256 - Binary.proximity(socAddress.toUint8Array(), overlay.toUint8Array());\n if (actualProximity <= 256 - proximity) {\n return signer;\n }\n }\n throw Error('Could not mine a valid signer');\n }\n /**\n * Sends a GSOC message with the specified signer and identifier.\n *\n * **Warning! Only full nodes can accept GSOC messages.**\n *\n * @param postageBatchId\n * @param signer\n * @param identifier\n * @param data\n * @param options\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async gsocSend(postageBatchId, signer, identifier, data, options, requestOptions) {\n const batchId = new BatchId(postageBatchId);\n const key = new PrivateKey(signer);\n const id = new Identifier(identifier);\n const cac = makeContentAddressedChunk(data);\n const soc = cac.toSingleOwnerChunk(id, key);\n return uploadSingleOwnerChunk(this.context.getRequestOptionsForCall(requestOptions), soc, batchId, options);\n }\n /**\n * Subscribes to GSOC messages for the specified owner (of the signer) and identifier.\n *\n * **Warning! Only full nodes can accept GSOC messages.**\n *\n * @param address\n * @param identifier\n * @param handler\n */\n gsocSubscribe(address, identifier, handler) {\n const ethAddress = new EthAddress(address);\n const id = new Identifier(identifier);\n handler = GsocMessageHandlerSchema.parse(handler);\n const socAddress = makeSOCAddress(id, ethAddress);\n const ws = this.openWebSocket(`${GSOC_ENDPOINT}/subscribe/${socAddress.toHex()}`);\n const cancel = this.makeCancel(ws);\n const subscription = {\n address: ethAddress,\n cancel\n };\n ws.onmessage = async event => {\n const data = await prepareWebsocketData(event.data);\n if (data.length) {\n handler.onMessage(new Bytes(data), subscription);\n }\n };\n ws.onerror = event => {\n if (!this.isCancelled(ws)) {\n handler.onError(new BeeError(event.message), subscription);\n }\n };\n ws.onclose = () => {\n handler.onClose(subscription);\n };\n return subscription;\n }\n openWebSocket(path) {\n const wsUrl = this.context.url.replace(/^http/i, 'ws');\n if (System.whereAmI() === 'browser') {\n return new WebSocket(`${wsUrl}/${path}`);\n }\n return new WebSocket(`${wsUrl}/${path}`, {\n headers: this.context.getRequestOptionsForCall().headers\n });\n }\n makeCancel(ws) {\n return () => {\n if (!this.cancelled.has(ws)) {\n this.cancelled.add(ws);\n if (ws.terminate) {\n ws.terminate();\n } else {\n ws.close();\n }\n }\n };\n }\n isCancelled(ws) {\n return this.cancelled.has(ws);\n }\n}","import { prepareRequestHeaders } from \"../utils/headers.js\";\nimport { http } from \"../utils/http.js\";\nconst pssEndpoint = 'pss';\n/**\n * Sends a message with the Postal Service for Swarm through the `POST /pss/send` endpoint.\n */\nexport async function send(requestOptions, batchId, topic, target, data, recipient) {\n await http(requestOptions, {\n method: 'post',\n url: `${pssEndpoint}/send/${topic}/${target}`,\n data,\n responseType: 'json',\n params: {\n recipient\n },\n headers: prepareRequestHeaders(batchId)\n });\n}","import { Reference } from '@ethersphere/core-sdk';\nimport { GetAllPinsResponse } from \"../types/schema/pinning.js\";\nimport { UploadResultBody } from \"../types/schema/upload.js\";\nimport { http } from \"../utils/http.js\";\nconst PINNING_ENDPOINT = 'pins';\n/** Pins local data with the given reference. */\nexport async function pin(requestOptions, reference) {\n await http(requestOptions, {\n method: 'post',\n responseType: 'json',\n url: `${PINNING_ENDPOINT}/${reference}`\n });\n}\n/** Unpins local data with the given reference. */\nexport async function unpin(requestOptions, reference) {\n await http(requestOptions, {\n method: 'delete',\n responseType: 'json',\n url: `${PINNING_ENDPOINT}/${reference}`\n });\n}\n/** Gets the list of all locally pinned references. */\nexport async function getAllPins(requestOptions) {\n const response = await http(requestOptions, {\n method: 'get',\n responseType: 'json',\n url: PINNING_ENDPOINT\n });\n const {\n references\n } = GetAllPinsResponse.parse(response.data);\n return (references ?? []).map(x => new Reference(x));\n}\n/** Gets the pinning status of the chunk with the given reference. */\nexport async function getPin(requestOptions, reference) {\n const response = await http(requestOptions, {\n method: 'get',\n responseType: 'json',\n url: `${PINNING_ENDPOINT}/${reference}`\n });\n return UploadResultBody.parse(response.data);\n}","import { BatchId, Reference } from '@ethersphere/core-sdk';\nimport * as pinApi from \"../api/pin.js\";\nimport * as stewardshipApi from \"../api/stewardship.js\";\n/**\n * Local pinning operations.\n *\n * Accessed as `bee.pin`.\n */\nexport class Pin {\n constructor(context) {\n this.context = context;\n }\n /**\n * Pins local data with the given reference.\n *\n * @param reference Data reference\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async add(reference, requestOptions) {\n const ref = new Reference(reference);\n await pinApi.pin(this.context.getRequestOptionsForCall(requestOptions), ref);\n }\n /**\n * Unpins local data with the given reference.\n *\n * @param reference Data reference\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async remove(reference, requestOptions) {\n const ref = new Reference(reference);\n await pinApi.unpin(this.context.getRequestOptionsForCall(requestOptions), ref);\n }\n /**\n * Gets the list of all locally pinned references.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getAll(requestOptions) {\n return pinApi.getAllPins(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Gets the pinning status of the chunk with the given reference.\n *\n * @param reference Data reference\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async get(reference, requestOptions) {\n const ref = new Reference(reference);\n return pinApi.getPin(this.context.getRequestOptionsForCall(requestOptions), ref);\n }\n /**\n * Instructs the Bee node to reupload locally pinned data into the network.\n *\n * @param postageBatchId Postage Batch ID that will be used to re-upload the data.\n * @param reference Data reference to be re-uploaded.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async reuploadData(postageBatchId, reference, requestOptions) {\n const batchId = new BatchId(postageBatchId);\n const ref = new Reference(reference);\n await stewardshipApi.reupload(this.context.getRequestOptionsForCall(requestOptions), batchId, ref);\n }\n}","import { GetAllSettlementsResponse, GetSettlementsResponse } from \"../types/schema/settlements.js\";\nimport { http } from \"../utils/http.js\";\nconst settlementsEndpoint = 'settlements';\n/**\n * Raw HTTP calls for the `/settlements` endpoint.\n */\nexport async function getSettlements(requestOptions, peer) {\n const response = await http(requestOptions, {\n url: `${settlementsEndpoint}/${peer}`,\n responseType: 'json'\n });\n return GetSettlementsResponse.parse(response.data);\n}\nexport async function getAllSettlements(requestOptions) {\n const response = await http(requestOptions, {\n url: settlementsEndpoint,\n responseType: 'json'\n });\n return GetAllSettlementsResponse.parse(response.data);\n}","import { PeerAddress } from '@ethersphere/core-sdk';\nimport * as api from \"../api/settlement.js\";\n/**\n * Settlement operations. Related to the bandwidth incentives and the chequebook.\n *\n * Accessed as `bee.settlement`.\n */\nexport class Settlement {\n constructor(context) {\n this.context = context;\n }\n /**\n * Gets the amount of sent and received micropayments from settlements with a peer.\n *\n * @param address Swarm address of peer\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async get(address, requestOptions) {\n const peer = new PeerAddress(address);\n return api.getSettlements(this.context.getRequestOptionsForCall(requestOptions), peer);\n }\n /**\n * Gets settlements with all known peers and total amount sent or received.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getAll(requestOptions) {\n return api.getAllSettlements(this.context.getRequestOptionsForCall(requestOptions));\n }\n}","import { EthAddress, PrivateKey } from '@ethersphere/core-sdk';\nimport { downloadSingleOwnerChunk, uploadSingleOwnerChunkData } from \"../chunk/soc.js\";\n/**\n * Single owner chunk (SOC) reader/writer operations.\n *\n * Accessed as `bee.soc`.\n */\nexport class Soc {\n constructor(context) {\n this.context = context;\n }\n /**\n * Returns an object for reading single owner chunks.\n *\n * @param ownerAddress The ethereum address of the owner\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n makeReader(ownerAddress, requestOptions) {\n const owner = new EthAddress(ownerAddress);\n return {\n owner,\n download: downloadSingleOwnerChunk.bind(null, this.context.getRequestOptionsForCall(requestOptions), owner)\n };\n }\n /**\n * Returns an object for reading and writing single owner chunks.\n *\n * @param signer The signer's private key. Falls back to the Bee instance signer.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n makeWriter(signer, requestOptions) {\n const key = signer ? new PrivateKey(signer) : this.context.signer;\n if (!key) {\n throw Error('No signer provided');\n }\n return {\n ...this.makeReader(key.publicKey().address(), requestOptions),\n upload: uploadSingleOwnerChunkData.bind(null, this.context.getRequestOptionsForCall(requestOptions), key)\n };\n }\n}","import { GetStakeResponse, GetWithdrawableStakeResponse, TxHashResponse } from \"../types/schema/stake.js\";\nimport { prepareRequestHeaders } from \"../utils/headers.js\";\nimport { http } from \"../utils/http.js\";\nconst STAKE_ENDPOINT = 'stake';\n/**\n * Raw HTTP calls for the `/stake` endpoint.\n */\nexport async function getStake(requestOptions) {\n const response = await http(requestOptions, {\n method: 'get',\n responseType: 'json',\n url: STAKE_ENDPOINT\n });\n return GetStakeResponse.parse(response.data).stakedAmount;\n}\nexport async function getWithdrawableStake(requestOptions) {\n const response = await http(requestOptions, {\n method: 'get',\n responseType: 'json',\n url: `${STAKE_ENDPOINT}/withdrawable`\n });\n return GetWithdrawableStakeResponse.parse(response.data).withdrawableAmount;\n}\nexport async function depositStake(requestOptions, amountString, options) {\n const response = await http(requestOptions, {\n method: 'post',\n responseType: 'json',\n url: `${STAKE_ENDPOINT}/${amountString}`,\n headers: prepareRequestHeaders(null, options)\n });\n return TxHashResponse.parse(response.data).txHash;\n}\nexport async function withdrawSurplusStake(requestOptions) {\n const response = await http(requestOptions, {\n method: 'delete',\n responseType: 'json',\n url: `${STAKE_ENDPOINT}/withdrawable`\n });\n return TxHashResponse.parse(response.data).txHash;\n}\nexport async function migrateStake(requestOptions) {\n const response = await http(requestOptions, {\n method: 'delete',\n responseType: 'json',\n url: STAKE_ENDPOINT\n });\n return TxHashResponse.parse(response.data).txHash;\n}","import { TransactionOptionsSchema } from \"../utils/schema.js\";\nimport { BZZ } from \"../utils/tokens.js\";\nimport { asNumberString } from \"../utils/type.js\";\nimport * as stakeApi from \"../api/stake.js\";\nimport * as redistributionApi from \"../api/redistributionstate.js\";\n/**\n * Staking operations.\n *\n * Accessed as `bee.stake`.\n */\nexport class Stake {\n constructor(context) {\n this.context = context;\n }\n /**\n * Gets the amount of staked BZZ.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async get(requestOptions) {\n return stakeApi.getStake(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Gets the amount of withdrawable staked BZZ.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getWithdrawable(requestOptions) {\n return stakeApi.getWithdrawableStake(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Stakes the given amount of BZZ. Initial deposit must be at least 10 BZZ.\n *\n * Be aware that staked BZZ tokens can **not** be withdrawn.\n *\n * @param amount Amount of BZZ tokens to be staked. If not providing a `BZZ` instance, the amount is denoted in PLUR.\n * @param options\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async deposit(amount, options, requestOptions) {\n const amountString = amount instanceof BZZ ? amount.toPLURString() : asNumberString(amount, {\n min: 1n,\n name: 'amount'\n });\n if (options) {\n options = TransactionOptionsSchema.parse(options);\n }\n return stakeApi.depositStake(this.context.getRequestOptionsForCall(requestOptions), amountString, options);\n }\n /**\n * Withdraws all surplus staked BZZ to the node wallet.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async withdrawSurplus(requestOptions) {\n return stakeApi.withdrawSurplusStake(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Withdraws all staked BZZ to the node wallet.\n *\n * **Only available when the staking contract is paused and is in the process of being migrated to a new contract!**\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async migrate(requestOptions) {\n return stakeApi.migrateStake(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Gets current status of node in redistribution game.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getRedistributionState(requestOptions) {\n return redistributionApi.getRedistributionState(this.context.getRequestOptionsForCall(requestOptions));\n }\n}","import { GetRedistributionStateResponse } from \"../types/schema/stake.js\";\nimport { http } from \"../utils/http.js\";\nconst redistributionStateEndpoint = 'redistributionstate';\n/**\n * Raw HTTP calls for the `/redistributionstate` endpoint.\n */\nexport async function getRedistributionState(requestOptions) {\n const response = await http(requestOptions, {\n method: 'get',\n responseType: 'json',\n url: redistributionStateEndpoint\n });\n return GetRedistributionStateResponse.parse(response.data);\n}","import { BatchIdResponse, GetAllPostageBatchesResponse, GetPostageBatchBucketsResponse, GetPostageBatchResponse } from \"../types/schema/stamps.js\";\nimport { http } from \"../utils/http.js\";\nimport { mapPostageBatch } from \"../utils/stamps.js\";\nconst STAMPS_ENDPOINT = 'stamps';\n/**\n * Raw HTTP calls for the `/stamps` endpoint.\n */\nexport async function createPostageBatch(requestOptions, amount, depth, label, gasPrice, immutableFlag) {\n const headers = {};\n if (gasPrice) {\n headers['gas-price'] = gasPrice;\n }\n if (immutableFlag !== undefined) {\n headers.immutable = String(immutableFlag);\n }\n const response = await http(requestOptions, {\n method: 'post',\n url: `${STAMPS_ENDPOINT}/${amount}/${depth}`,\n responseType: 'json',\n params: {\n label\n },\n headers\n });\n return BatchIdResponse.parse(response.data).batchID;\n}\nexport async function updateLabel(requestOptions, id, label) {\n await http(requestOptions, {\n method: 'patch',\n url: `${STAMPS_ENDPOINT}/${id}`,\n responseType: 'json',\n data: {\n label\n }\n });\n}\nexport async function topUpBatch(requestOptions, id, amount) {\n const response = await http(requestOptions, {\n method: 'patch',\n url: `${STAMPS_ENDPOINT}/topup/${id}/${amount}`,\n responseType: 'json'\n });\n return BatchIdResponse.parse(response.data).batchID;\n}\nexport async function diluteBatch(requestOptions, id, depth) {\n const response = await http(requestOptions, {\n method: 'patch',\n url: `${STAMPS_ENDPOINT}/dilute/${id}/${depth}`,\n responseType: 'json'\n });\n return BatchIdResponse.parse(response.data).batchID;\n}\nexport async function getPostageBatch(requestOptions, id, encryption, erasureCodeLevel) {\n const response = await http(requestOptions, {\n method: 'get',\n url: `${STAMPS_ENDPOINT}/${id}`,\n responseType: 'json'\n });\n return mapPostageBatch(GetPostageBatchResponse.parse(response.data), encryption, erasureCodeLevel);\n}\nexport async function getPostageBatchBuckets(requestOptions, id) {\n const response = await http(requestOptions, {\n method: 'get',\n url: `${STAMPS_ENDPOINT}/${id}/buckets`,\n responseType: 'json'\n });\n return GetPostageBatchBucketsResponse.parse(response.data);\n}\nexport async function getAllPostageBatches(requestOptions) {\n const response = await http(requestOptions, {\n method: 'get',\n url: STAMPS_ENDPOINT,\n responseType: 'json'\n });\n return GetAllPostageBatchesResponse.parse(response.data).stamps.map(x => mapPostageBatch(x));\n}","import { GetGlobalPostageBatchResponse, GetGlobalPostageBatchesResponse } from \"../types/schema/stamps.js\";\nimport { http } from \"../utils/http.js\";\nconst batchesEndpoint = 'batches';\n/**\n * Raw HTTP calls for the `/batches` endpoint.\n */\nexport async function getGlobalPostageBatch(requestOptions, id) {\n const response = await http(requestOptions, {\n method: 'get',\n url: `${batchesEndpoint}/${id}`,\n responseType: 'json'\n });\n return GetGlobalPostageBatchResponse.parse(response.data);\n}\nexport async function getAllGlobalPostageBatches(requestOptions) {\n const response = await http(requestOptions, {\n method: 'get',\n url: batchesEndpoint,\n responseType: 'json'\n });\n return GetGlobalPostageBatchesResponse.parse(response.data).batches;\n}","import { BatchId } from '@ethersphere/core-sdk';\nimport { System } from 'cafe-utility';\nimport { z } from 'zod';\nimport { STAMPS_DEPTH_MAX, STAMPS_DEPTH_MIN } from \"../types/index.js\";\nimport { BeeArgumentError, BeeError } from \"../utils/error.js\";\nimport { PostageBatchOptionsSchema } from \"../utils/schema.js\";\nimport { getStampDuration } from \"../utils/stamps.js\";\nimport { asNumberString } from \"../utils/type.js\";\nimport * as stampApi from \"../api/stamp.js\";\nimport * as batchesApi from \"../api/batches.js\";\n/**\n * Low-level postage batch (stamp) operations.\n *\n * Accessed as `bee.stamp`. For the ergonomic wrapper see `bee.storage`.\n */\nexport class Stamp {\n constructor(context) {\n this.context = context;\n }\n /**\n * Creates a new postage batch, spending BZZ tokens from the node wallet.\n *\n * Use `bee.storage.buy` for a more convenient way to create a postage batch.\n *\n * @param amount TTL parameter - 1 day at the minimum of 24,000 storage price requires an amount of 414,720,000.\n * @param depth Capacity parameter - 17..255.\n * @param options Options for creation of postage batch\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async create(amount, depth, options, requestOptions) {\n const amountString = asNumberString(amount, {\n min: 0n,\n name: 'amount'\n });\n if (options) {\n options = PostageBatchOptionsSchema.parse(options);\n }\n if (depth < STAMPS_DEPTH_MIN || depth > STAMPS_DEPTH_MAX) {\n throw new BeeArgumentError(`Depth has to be between ${STAMPS_DEPTH_MIN}..${STAMPS_DEPTH_MAX}`, depth);\n }\n const chainState = await this.context.bee.status.getChainState();\n const minimumAmount = BigInt(chainState.currentPrice) * 17280n + 1n;\n if (BigInt(amountString) < minimumAmount) {\n throw new BeeArgumentError(`Amount has to be at least ${minimumAmount} (1 day at current price ${chainState.currentPrice})`, amountString);\n }\n const batchId = await stampApi.createPostageBatch(this.context.getRequestOptionsForCall(requestOptions), amountString, depth, options?.label, options?.gasPrice?.toString(), options?.immutableFlag);\n if (options?.waitForUsable !== false) {\n await this.waitForUsable(batchId, options?.waitForUsableTimeout);\n }\n return batchId;\n }\n /**\n * Updates the label of a certain postage batch.\n *\n * @param postageBatchId Batch ID of the postage batch to update.\n * @param label New label for the postage batch.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async updateLabel(postageBatchId, label, requestOptions) {\n const id = new BatchId(postageBatchId);\n await stampApi.updateLabel(this.context.getRequestOptionsForCall(requestOptions), id, label);\n }\n /**\n * Calculates the `amount` and expected duration extension for topping up a postage batch with a given BZZ value.\n *\n * @param depth Depth of the postage batch to top up.\n * @param bzz The amount of BZZ to spend on the top-up.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async calculateTopUpForBZZ(depth, bzz, requestOptions) {\n const chainState = await this.context.bee.status.getChainState(requestOptions);\n const blockTime = this.context.network === 'gnosis' ? 5 : 15;\n const amount = bzz.toPLURBigInt() / 2n ** BigInt(depth);\n const duration = getStampDuration(amount, chainState.currentPrice, blockTime);\n return {\n amount,\n duration\n };\n }\n /**\n * Increases the duration of a postage batch by increasing its amount.\n *\n * @param postageBatchId Batch ID\n * @param amount Amount to be added to the batch\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async topUp(postageBatchId, amount, requestOptions) {\n const id = new BatchId(postageBatchId);\n const amountString = asNumberString(amount, {\n min: 1n,\n name: 'amount'\n });\n return stampApi.topUpBatch(this.context.getRequestOptionsForCall(requestOptions), id, amountString);\n }\n /**\n * Dilutes a postage batch to extend its capacity by increasing its depth.\n *\n * @param postageBatchId Batch ID\n * @param depth New depth for the batch\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async dilute(postageBatchId, depth, requestOptions) {\n const id = new BatchId(postageBatchId);\n const depthNumber = z.number().int().min(18).max(255).parse(depth);\n return stampApi.diluteBatch(this.context.getRequestOptionsForCall(requestOptions), id, depthNumber);\n }\n /**\n * Returns details for a specific postage batch.\n *\n * @param postageBatchId Batch ID\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n * @param encryption Assume that uploads with this postage batch are encrypted, which skews the capacity.\n * @param erasureCodeLevel Assume that uploads with this postage batch are erasure coded, which skews the capacity.\n */\n async get(postageBatchId, requestOptions, encryption, erasureCodeLevel) {\n const id = new BatchId(postageBatchId);\n return stampApi.getPostageBatch(this.context.getRequestOptionsForCall(requestOptions), id, encryption, erasureCodeLevel);\n }\n /**\n * Returns details for a specific globally available postage batch.\n *\n * @param postageBatchId Batch ID\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getGlobal(postageBatchId, requestOptions) {\n const id = new BatchId(postageBatchId);\n return batchesApi.getGlobalPostageBatch(this.context.getRequestOptionsForCall(requestOptions), id);\n }\n /**\n * Returns detailed information related to buckets for a specific postage batch.\n *\n * @param postageBatchId Batch ID\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getBuckets(postageBatchId, requestOptions) {\n const id = new BatchId(postageBatchId);\n return stampApi.getPostageBatchBuckets(this.context.getRequestOptionsForCall(requestOptions), id);\n }\n /**\n * Returns all postage batches that belong to the node.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getAll(requestOptions) {\n return stampApi.getAllPostageBatches(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Returns all globally available postage batches.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getAllGlobal(requestOptions) {\n return batchesApi.getAllGlobalPostageBatches(this.context.getRequestOptionsForCall(requestOptions));\n }\n async waitForUsable(id, timeout = 240000) {\n const TIME_STEP = 3000;\n for (let time = 0; time < timeout; time += TIME_STEP) {\n try {\n const stamp = await this.get(id);\n if (stamp.usable) {\n return;\n }\n } catch {\n // ignore error\n }\n await System.sleepMillis(TIME_STEP);\n }\n throw new BeeError('Timeout on waiting for postage stamp to become usable');\n }\n}","import getMajorSemver from 'semver/functions/major.js';\nimport { SUPPORTED_API_VERSION, SUPPORTED_BEE_VERSION_EXACT } from \"../version.js\";\nimport * as api from \"../api/status.js\";\n/**\n * Node status, health, version and chain/reserve state operations.\n *\n * Accessed as `bee.status`.\n */\nexport class Status {\n constructor(context) {\n this.context = context;\n }\n /**\n * Gets the general status of the node.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async get(requestOptions) {\n return api.getDebugStatus(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Gets the health of the node.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getHealth(requestOptions) {\n return api.getHealth(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Gets the readiness status of the node.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getReadiness(requestOptions) {\n return api.getReadiness(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Gets mode information of the node.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getNodeInfo(requestOptions) {\n return api.getNodeInfo(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Connects to a node and checks if its version matches with the one that bee-js supports.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async isSupportedExactVersion(requestOptions) {\n const {\n version\n } = await this.getHealth(requestOptions);\n return version === SUPPORTED_BEE_VERSION_EXACT;\n }\n /**\n * Connects to a node and checks if its Main API version matches with the one that bee-js supports.\n *\n * This should be the main way how to check compatibility for your app and Bee node.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async isSupportedApiVersion(requestOptions) {\n const {\n apiVersion\n } = await this.getHealth(requestOptions);\n return getMajorSemver(apiVersion) === getMajorSemver(SUPPORTED_API_VERSION);\n }\n /**\n * Returns object with all versions specified by the connected Bee node (properties prefixed with `bee*`)\n * and versions that bee-js supports (properties prefixed with `supported*`).\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getVersions(requestOptions) {\n const {\n version,\n apiVersion\n } = await this.getHealth(requestOptions);\n return {\n supportedBeeVersion: SUPPORTED_BEE_VERSION_EXACT,\n supportedBeeApiVersion: SUPPORTED_API_VERSION,\n beeVersion: version,\n beeApiVersion: apiVersion\n };\n }\n /**\n * Gets reserve state.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getReserveState(requestOptions) {\n return api.getReserveState(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Gets chain state.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getChainState(requestOptions) {\n return api.getChainState(this.context.getRequestOptionsForCall(requestOptions));\n }\n}","import { GetDebugStatusResponse, GetHealthResponse, GetNodeInfoResponse, GetReadinessResponse } from \"../types/schema/status.js\";\nimport { GetChainStateResponse, GetReserveStateResponse } from \"../types/schema/states.js\";\nimport { http } from \"../utils/http.js\";\nconst NODE_INFO_URL = 'node';\nconst STATUS_URL = 'status';\nconst HEALTH_URL = 'health';\nconst READINESS_URL = 'readiness';\nconst RESERVE_STATE_ENDPOINT = 'reservestate';\nconst CHAIN_STATE_ENDPOINT = 'chainstate';\n/**\n * Raw HTTP calls for the node status, health, version and chain/reserve state endpoints.\n */\nexport async function getDebugStatus(requestOptions) {\n const response = await http(requestOptions, {\n method: 'get',\n url: STATUS_URL,\n responseType: 'json'\n });\n return GetDebugStatusResponse.parse(response.data);\n}\nexport async function getHealth(requestOptions) {\n const response = await http(requestOptions, {\n method: 'get',\n url: HEALTH_URL,\n responseType: 'json'\n });\n return GetHealthResponse.parse(response.data);\n}\nexport async function getReadiness(requestOptions) {\n const response = await http(requestOptions, {\n method: 'get',\n url: READINESS_URL\n });\n return GetReadinessResponse.parse(response.data);\n}\nexport async function getNodeInfo(requestOptions) {\n const response = await http(requestOptions, {\n method: 'get',\n url: NODE_INFO_URL,\n responseType: 'json'\n });\n return GetNodeInfoResponse.parse(response.data);\n}\nexport async function getReserveState(requestOptions) {\n const response = await http(requestOptions, {\n method: 'get',\n url: RESERVE_STATE_ENDPOINT,\n responseType: 'json'\n });\n return GetReserveStateResponse.parse(response.data);\n}\nexport async function getChainState(requestOptions) {\n const response = await http(requestOptions, {\n method: 'get',\n url: CHAIN_STATE_ENDPOINT,\n responseType: 'json'\n });\n return GetChainStateResponse.parse(response.data);\n}","import { BeeArgumentError } from \"../utils/error.js\";\nimport { PostageBatchOptionsSchema } from \"../utils/schema.js\";\nimport { getAmountForDuration, getDepthForSize, getStampCost } from \"../utils/stamps.js\";\n/**\n * Ergonomic storage operations expressed in terms of size and duration.\n *\n * Accessed as `bee.storage`. Wraps the low-level `bee.stamp` operations.\n */\nexport class Storage {\n constructor(context) {\n this.context = context;\n }\n get blockTime() {\n return this.context.network === 'gnosis' ? 5 : 15;\n }\n /**\n * Creates a postage batch sized for a certain size and duration on the Swarm network.\n *\n * Use {@link getCost} to calculate the cost beforehand.\n *\n * @param size\n * @param duration\n * @param options\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n * @param encryption\n * @param erasureCodeLevel\n */\n async buy(size, duration, options, requestOptions, encryption, erasureCodeLevel) {\n const chainState = await this.context.bee.status.getChainState(requestOptions);\n const amount = getAmountForDuration(duration, chainState.currentPrice, this.blockTime);\n const depth = getDepthForSize(size, encryption, erasureCodeLevel);\n if (options) {\n options = PostageBatchOptionsSchema.parse(options);\n }\n return this.context.bee.stamp.create(amount, depth, options, requestOptions);\n }\n /**\n * Calculates the estimated BZZ cost for creating a postage batch for the given size and duration.\n *\n * @param size Size of the data to be stored.\n * @param duration Duration for which the data should be stored.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n * @param encryption Assume the future uploaded data is encrypted, which skews the capacity of the postage batch.\n * @param erasureCodeLevel Assume the future uploaded data is erasure coded, which skews the capacity of the postage batch.\n */\n async getCost(size, duration, requestOptions, encryption, erasureCodeLevel) {\n const chainState = await this.context.bee.status.getChainState(requestOptions);\n const amount = getAmountForDuration(duration, chainState.currentPrice, this.blockTime);\n const depth = getDepthForSize(size, encryption, erasureCodeLevel);\n return getStampCost(depth, amount);\n }\n /**\n * Extends the storage of a postage batch by either increasing its size, duration or both.\n *\n * The size is ABSOLUTE, while the duration is RELATIVE to the current duration of the postage batch.\n *\n * @param postageBatchId Batch ID of the postage batch to extend.\n * @param size Absolute size to extend the postage batch to.\n * @param duration Relative duration to extend the postage batch by.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n * @param encryption Assume the future uploaded data is encrypted, which skews the capacity of the postage batch.\n * @param erasureCodeLevel Assume the future uploaded data is erasure coded, which skews the capacity of the postage batch.\n */\n async extend(postageBatchId, size, duration, requestOptions, encryption, erasureCodeLevel) {\n const batch = await this.context.bee.stamp.get(postageBatchId, requestOptions);\n const depth = getDepthForSize(size, encryption, erasureCodeLevel);\n const chainState = await this.context.bee.status.getChainState(requestOptions);\n const depthDelta = depth - batch.depth;\n const multiplier = depthDelta <= 0 ? 1n : 2n ** BigInt(depthDelta);\n const additionalAmount = getAmountForDuration(duration, chainState.currentPrice, this.blockTime);\n const currentAmount = getAmountForDuration(batch.duration, chainState.currentPrice, this.blockTime);\n const targetAmount = duration.isZero() ? currentAmount * multiplier : (currentAmount + additionalAmount) * multiplier;\n const amountDelta = targetAmount - currentAmount;\n let transactionId;\n if (amountDelta > 0n) {\n transactionId = await this.context.bee.stamp.topUp(batch.batchID, amountDelta, requestOptions);\n }\n if (depthDelta > 0) {\n return this.context.bee.stamp.dilute(batch.batchID, depth, requestOptions);\n }\n if (!transactionId) {\n throw new Error('Nothing to extend, both size and duration are already sufficient');\n }\n return transactionId;\n }\n /**\n * Extends the storage size of a postage batch by increasing its depth.\n *\n * @param postageBatchId\n * @param size Absolute size to extend the postage batch to.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n * @param encryption Assume the future uploaded data is encrypted, which skews the capacity of the postage batch.\n * @param erasureCodeLevel Assume the future uploaded data is erasure coded, which skews the capacity of the postage batch.\n */\n async extendSize(postageBatchId, size, requestOptions, encryption, erasureCodeLevel) {\n const chainState = await this.context.bee.status.getChainState(requestOptions);\n const batch = await this.context.bee.stamp.get(postageBatchId, requestOptions);\n const depth = getDepthForSize(size, encryption, erasureCodeLevel);\n const delta = depth - batch.depth;\n if (delta <= 0) {\n throw new BeeArgumentError('New depth has to be greater than the original depth', depth);\n }\n const currentAmount = getAmountForDuration(batch.duration, chainState.currentPrice, this.blockTime);\n await this.context.bee.stamp.topUp(batch.batchID, currentAmount * (2n ** BigInt(delta) - 1n) + 1n, requestOptions);\n return this.context.bee.stamp.dilute(batch.batchID, depth, requestOptions);\n }\n /**\n * Extends the duration of a postage batch.\n *\n * @param postageBatchId\n * @param duration Relative duration to extend the postage batch by.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async extendDuration(postageBatchId, duration, requestOptions) {\n const batch = await this.context.bee.stamp.get(postageBatchId, requestOptions);\n const chainState = await this.context.bee.status.getChainState(requestOptions);\n const amount = getAmountForDuration(duration, chainState.currentPrice, this.blockTime);\n return this.context.bee.stamp.topUp(batch.batchID, amount, requestOptions);\n }\n /**\n * Calculates the cost of extending both the duration and the capacity of a postage batch.\n *\n * @param postageBatchId\n * @param size Absolute size to extend the postage batch to.\n * @param duration Relative duration to extend the postage batch by.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n * @param encryption Assume the future uploaded data is encrypted, which skews the capacity of the postage batch.\n * @param erasureCodeLevel Assume the future uploaded data is erasure coded, which skews the capacity of the postage batch.\n */\n async getExtensionCost(postageBatchId, size, duration, requestOptions, encryption, erasureCodeLevel) {\n const batch = await this.context.bee.stamp.get(postageBatchId, requestOptions);\n const chainState = await this.context.bee.status.getChainState(requestOptions);\n const amount = duration.isZero() ? 0n : getAmountForDuration(duration, chainState.currentPrice, this.blockTime);\n const depth = getDepthForSize(size, encryption, erasureCodeLevel);\n const currentAmount = getAmountForDuration(batch.duration, chainState.currentPrice, this.blockTime);\n const currentCost = getStampCost(batch.depth, currentAmount);\n const newCost = getStampCost(Math.max(batch.depth, depth), currentAmount + amount);\n return newCost.minus(currentCost);\n }\n /**\n * Calculates the cost of extending the size of a postage batch.\n *\n * @param postageBatchId\n * @param size\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n * @param encryption Assume the future uploaded data is encrypted, which skews the capacity of the postage batch.\n * @param erasureCodeLevel Assume the future uploaded data is erasure coded, which skews the capacity of the postage batch.\n */\n async getSizeExtensionCost(postageBatchId, size, requestOptions, encryption, erasureCodeLevel) {\n const batch = await this.context.bee.stamp.get(postageBatchId, requestOptions);\n const chainState = await this.context.bee.status.getChainState(requestOptions);\n const depth = getDepthForSize(size, encryption, erasureCodeLevel);\n const delta = depth - batch.depth;\n if (delta <= 0) {\n throw new BeeArgumentError('New depth has to be greater than the original depth', depth);\n }\n const currentAmount = getAmountForDuration(batch.duration, chainState.currentPrice, this.blockTime);\n const currentCost = getStampCost(batch.depth, currentAmount);\n const newCost = getStampCost(depth, currentAmount);\n return newCost.minus(currentCost);\n }\n /**\n * Calculates the cost of extending the duration of a postage batch.\n *\n * @param postageBatchId\n * @param duration\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getDurationExtensionCost(postageBatchId, duration, requestOptions) {\n const batch = await this.context.bee.stamp.get(postageBatchId, requestOptions);\n const chainState = await this.context.bee.status.getChainState(requestOptions);\n const amount = getAmountForDuration(duration, chainState.currentPrice, this.blockTime);\n return getStampCost(batch.depth, amount);\n }\n /**\n * Renames a storage. Convenience wrapper over `bee.stamp.updateLabel`.\n *\n * @param postageBatchId Batch ID of the postage batch to update.\n * @param newLabel New name for the storage.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async rename(postageBatchId, newLabel, requestOptions) {\n return this.context.bee.stamp.updateLabel(postageBatchId, newLabel, requestOptions);\n }\n}","import { GetAllTagsResponse, TagSchema } from \"../types/schema/tag.js\";\nimport { http } from \"../utils/http.js\";\nconst endpoint = 'tags';\n/** Creates a new tag. */\nexport async function createTag(requestOptions) {\n const response = await http(requestOptions, {\n method: 'post',\n url: endpoint,\n responseType: 'json'\n });\n return TagSchema.parse(response.data);\n}\n/** Fetches all tags in a paginated manner. */\nexport async function getAllTags(requestOptions, options) {\n const response = await http(requestOptions, {\n url: endpoint,\n params: {\n offset: options?.offset,\n limit: options?.limit\n },\n responseType: 'json'\n });\n return GetAllTagsResponse.parse(response.data).tags;\n}\n/** Retrieves tag information from the Bee node. */\nexport async function getTag(requestOptions, uid) {\n const response = await http(requestOptions, {\n url: `${endpoint}/${uid}`,\n responseType: 'json'\n });\n return TagSchema.parse(response.data);\n}\n/** Deletes a tag. */\nexport async function deleteTag(requestOptions, uid) {\n await http(requestOptions, {\n method: 'delete',\n url: `${endpoint}/${uid}`\n });\n}\n/** Updates a tag's total chunks count. */\nexport async function updateTag(requestOptions, uid, reference) {\n await http(requestOptions, {\n method: 'patch',\n url: `${endpoint}/${uid}`,\n data: {\n reference\n }\n });\n}","import { Reference } from '@ethersphere/core-sdk';\nimport * as api from \"../api/tag.js\";\nimport { AllTagsOptionsSchema } from \"../utils/schema.js\";\nimport { makeTagUid } from \"../utils/type.js\";\n/**\n * Tag operations for tracking upload and synchronization progress.\n *\n * Accessed as `bee.tag`.\n */\nexport class Tag {\n constructor(context) {\n this.context = context;\n }\n /**\n * Creates a new tag which is meant for tracking upload and synchronization progress.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async create(requestOptions) {\n return api.createTag(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Fetches all tags in a paginated manner.\n *\n * @param options Specify `limit` and `offset` to paginate through the tags.\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getAll(options, requestOptions) {\n if (options) {\n options = AllTagsOptionsSchema.parse(options);\n }\n return api.getAllTags(this.context.getRequestOptionsForCall(requestOptions), options);\n }\n /**\n * Retrieves tag information from the Bee node.\n *\n * @param tagUid UID or tag object to be retrieved\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async get(tagUid, requestOptions) {\n const uid = makeTagUid(tagUid);\n return api.getTag(this.context.getRequestOptionsForCall(requestOptions), uid);\n }\n /**\n * Deletes a tag.\n *\n * @param tagUid UID or tag object to be deleted\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async delete(tagUid, requestOptions) {\n const uid = makeTagUid(tagUid);\n await api.deleteTag(this.context.getRequestOptionsForCall(requestOptions), uid);\n }\n /**\n * Updates a tag's total chunks count.\n *\n * @param tagUid UID or tag object to be updated\n * @param reference The root reference that contains all the chunks to be counted\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async update(tagUid, reference, requestOptions) {\n const ref = new Reference(reference);\n const uid = makeTagUid(tagUid);\n await api.updateTag(this.context.getRequestOptionsForCall(requestOptions), uid, ref);\n }\n}","import { GetAllTransactionsResponse, GetTransactionResponse, TransactionHashResponse } from \"../types/schema/transactions.js\";\nimport { http } from \"../utils/http.js\";\nconst transactionsEndpoint = 'transactions';\n/**\n * Raw HTTP calls for the `/transactions` endpoint.\n */\nexport async function getAllTransactions(requestOptions) {\n const response = await http(requestOptions, {\n url: transactionsEndpoint,\n responseType: 'json'\n });\n return GetAllTransactionsResponse.parse(response.data).pendingTransactions;\n}\nexport async function getTransaction(requestOptions, hash) {\n const response = await http(requestOptions, {\n url: `${transactionsEndpoint}/${hash}`,\n responseType: 'json'\n });\n return GetTransactionResponse.parse(response.data);\n}\nexport async function rebroadcastTransaction(requestOptions, hash) {\n const response = await http(requestOptions, {\n method: 'post',\n url: `${transactionsEndpoint}/${hash}`,\n responseType: 'json'\n });\n return TransactionHashResponse.parse(response.data).transactionHash;\n}\nexport async function cancelTransaction(requestOptions, hash, gasPrice) {\n const headers = {};\n if (gasPrice) {\n headers['gas-price'] = gasPrice;\n }\n const response = await http(requestOptions, {\n method: 'delete',\n headers,\n url: `${transactionsEndpoint}/${hash}`,\n responseType: 'json'\n });\n return TransactionHashResponse.parse(response.data).transactionHash;\n}","import { TransactionId } from '@ethersphere/core-sdk';\nimport { asNumberString } from \"../utils/type.js\";\nimport * as api from \"../api/transaction.js\";\n/**\n * Pending transaction operations for the Bee node's `/transactions` queue.\n *\n * Accessed as `bee.transaction`.\n */\nexport class Transaction {\n constructor(context) {\n this.context = context;\n }\n /**\n * Fetches the list of all current pending transactions for the Bee node.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getAll(requestOptions) {\n return api.getAllTransactions(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Fetches the transaction information for a specific pending transaction.\n *\n * @param transactionHash Hash of the transaction\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async get(transactionHash, requestOptions) {\n const hash = new TransactionId(transactionHash);\n return api.getTransaction(this.context.getRequestOptionsForCall(requestOptions), hash);\n }\n /**\n * Rebroadcasts an already created pending transaction.\n *\n * Mainly needed when the transaction falls off the mempool or is not incorporated into any block.\n *\n * @param transactionHash Hash of the transaction\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async rebroadcast(transactionHash, requestOptions) {\n const hash = new TransactionId(transactionHash);\n return api.rebroadcastTransaction(this.context.getRequestOptionsForCall(requestOptions), hash);\n }\n /**\n * Cancels a currently pending transaction.\n *\n * @param transactionHash Hash of the transaction\n * @param gasPrice Optional gas price\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async cancel(transactionHash, gasPrice, requestOptions) {\n const hash = new TransactionId(transactionHash);\n const gasPriceString = gasPrice ? asNumberString(gasPrice, {\n min: 0n,\n name: 'gasPrice'\n }) : undefined;\n return api.cancelTransaction(this.context.getRequestOptionsForCall(requestOptions), hash, gasPriceString);\n }\n}","import { GetWalletBalanceResponse, WithdrawResponse } from \"../types/schema/states.js\";\nimport { http } from \"../utils/http.js\";\nconst WALLET_ENDPOINT = 'wallet';\n/**\n * Raw HTTP calls for the `/wallet` endpoint.\n */\nexport async function getWalletBalance(requestOptions) {\n const response = await http(requestOptions, {\n method: 'get',\n url: WALLET_ENDPOINT,\n responseType: 'json'\n });\n return GetWalletBalanceResponse.parse(response.data);\n}\nexport async function withdrawBZZ(requestOptions, amount, address) {\n const response = await http(requestOptions, {\n method: 'post',\n url: `${WALLET_ENDPOINT}/withdraw/bzz`,\n responseType: 'json',\n params: {\n amount: amount.toPLURString(),\n address: address.toHex()\n }\n });\n return WithdrawResponse.parse(response.data).transactionHash;\n}\nexport async function withdrawDAI(requestOptions, amount, address) {\n const response = await http(requestOptions, {\n method: 'post',\n url: `${WALLET_ENDPOINT}/withdraw/nativetoken`,\n responseType: 'json',\n params: {\n amount: amount.toWeiString(),\n address: address.toHex()\n }\n });\n return WithdrawResponse.parse(response.data).transactionHash;\n}","import { EthAddress } from '@ethersphere/core-sdk';\nimport { BZZ, DAI } from \"../utils/tokens.js\";\nimport * as api from \"../api/wallet.js\";\n/**\n * Node wallet operations (balances and external withdrawals).\n *\n * Accessed as `bee.wallet`.\n */\nexport class Wallet {\n constructor(context) {\n this.context = context;\n }\n /**\n * Gets DAI and BZZ balances of the Bee node wallet.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async getBalance(requestOptions) {\n return api.getWalletBalance(this.context.getRequestOptionsForCall(requestOptions));\n }\n /**\n * Withdraws BZZ from the node wallet (not chequebook) to a whitelisted external wallet address.\n *\n * @param amount Amount of BZZ tokens to withdraw. If not providing a `BZZ` instance, the amount is denoted in PLUR.\n * @param address\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async withdrawBZZ(amount, address, requestOptions) {\n const bzz = amount instanceof BZZ ? amount : BZZ.fromPLUR(amount);\n const ethAddress = new EthAddress(address);\n return api.withdrawBZZ(this.context.getRequestOptionsForCall(requestOptions), bzz, ethAddress);\n }\n /**\n * Withdraws DAI from the node wallet (not chequebook) to a whitelisted external wallet address.\n *\n * @param amount Amount of DAI tokens to withdraw. If not providing a `DAI` instance, the amount is denoted in wei.\n * @param address\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n */\n async withdrawDAI(amount, address, requestOptions) {\n const dai = amount instanceof DAI ? amount : DAI.fromWei(amount);\n const ethAddress = new EthAddress(address);\n return api.withdrawDAI(this.context.getRequestOptionsForCall(requestOptions), dai, ethAddress);\n }\n}","/**\n * Ports blocked by the WHATWG Fetch spec for HTTP/HTTPS requests.\n *\n * See: https://fetch.spec.whatwg.org/#port-blocking\n */\nexport const BAD_PORTS = ['1', '7', '9', '11', '13', '15', '17', '19', '20', '21', '22', '23', '25', '37', '42', '43', '53', '69', '77', '79', '87', '95', '101', '102', '103', '104', '109', '110', '111', '113', '115', '117', '119', '123', '135', '137', '139', '143', '161', '179', '389', '427', '465', '512', '513', '514', '515', '526', '530', '531', '532', '540', '548', '554', '556', '563', '587', '601', '636', '989', '990', '993', '995', '1719', '1720', '1723', '2049', '3659', '4045', '4190', '5060', '5061', '6000', '6566', '6665', '6666', '6667', '6668', '6669', '6679', '6697', '10080'];","import { BAD_PORTS } from \"./bad-ports.js\";\nimport { BeeArgumentError } from \"./error.js\";\n/**\n * Validates that passed string is valid URL of Bee.\n * We support only HTTP and HTTPS protocols.\n *\n * @param url\n */\nexport function isValidBeeUrl(url) {\n try {\n if (typeof url !== 'string') {\n return false;\n }\n const urlObject = new URL(url);\n // There can be wide range of protocols passed.\n return urlObject.protocol === 'http:' || urlObject.protocol === 'https:';\n } catch (e) {\n return false;\n }\n}\n/**\n * Validates that passed string is valid port to use Bee and is not a bad port.\n * We support only HTTP and HTTPS protocols. Bad ports are defined in\n * the WHATWG Fetch spec (https://fetch.spec.whatwg.org/#port-blocking)\n * and are blocked for HTTP/HTTPS requests.\n *\n * @param port\n */\nexport function isValidBeePort(port) {\n if (typeof port !== 'string') {\n return false;\n }\n if (BAD_PORTS.includes(port)) {\n return false;\n }\n const portNumber = parseInt(port, 10);\n return portNumber > 0 && portNumber < 65536 && !isNaN(portNumber);\n}\n/**\n * Validates that passed string is valid URL of Bee, if not it throws BeeArgumentError.\n * We support only HTTP and HTTPS protocols.\n * @param url\n * @throws BeeArgumentError if non valid URL\n */\nexport function assertBeeUrl(url) {\n if (!isValidBeeUrl(url)) {\n throw new BeeArgumentError('URL is not valid!', url);\n }\n const port = new URL(url).port;\n if (port && !isValidBeePort(port)) {\n throw new BeeArgumentError('Port in URL is considered bad port and cannot be used!', port);\n }\n}\n/**\n * Removes trailing slash out of the given string.\n * @param url\n */\nexport function stripLastSlash(url) {\n if (url.endsWith('/')) {\n return url.slice(0, -1);\n }\n return url;\n}","import { Objects } from 'cafe-utility';\nimport { BatchId, PrivateKey, Reference, makeContentAddressedChunk, unmarshalContentAddressedChunk } from '@ethersphere/core-sdk';\nimport { postEnvelope } from \"./api/envelope.js\";\nimport { rchash } from \"./api/rchash.js\";\nimport { makeSOCAddress, makeSingleOwnerChunk, unmarshalSingleOwnerChunk } from \"./chunk/soc.js\";\nimport { Balance } from \"./modules/balance.js\";\nimport { Cheque } from \"./modules/cheque.js\";\nimport { Chequebook } from \"./modules/chequebook.js\";\nimport { Chunk as ChunkNamespace } from \"./modules/chunk.js\";\nimport { Collection } from \"./modules/collection.js\";\nimport { Connectivity } from \"./modules/connectivity.js\";\nimport { Data } from \"./modules/data.js\";\nimport { Feed } from \"./modules/feed.js\";\nimport { File as FileNamespace } from \"./modules/file.js\";\nimport { Grantee } from \"./modules/grantee.js\";\nimport { Messaging } from \"./modules/messaging.js\";\nimport { Pin } from \"./modules/pin.js\";\nimport { Settlement } from \"./modules/settlement.js\";\nimport { Soc } from \"./modules/soc.js\";\nimport { Stake } from \"./modules/stake.js\";\nimport { Stamp } from \"./modules/stamp.js\";\nimport { Status } from \"./modules/status.js\";\nimport { Storage } from \"./modules/storage.js\";\nimport { Tag } from \"./modules/tag.js\";\nimport { Transaction } from \"./modules/transaction.js\";\nimport { Wallet } from \"./modules/wallet.js\";\nimport { BeeRequestOptionsSchema } from \"./utils/schema.js\";\nimport { assertBeeUrl, stripLastSlash } from \"./utils/url.js\";\n/**\n * The main component that abstracts operations available on the Bee API.\n *\n * Instantiate with `new Bee(url, options)` where `url` is the Bee node URL and `options` are optional parameters.\n *\n * @example\n * const bee = new Bee('http://localhost:1633')\n */\nexport class Bee {\n /**\n * @param url URL on which is the main API of Bee node exposed\n * @param options\n *\n * @example\n * const bee = new Bee('http://localhost:1633')\n */\n constructor(url, options) {\n assertBeeUrl(url);\n this.url = stripLastSlash(url);\n if (options?.signer) {\n this.signer = new PrivateKey(options.signer);\n }\n this.network = options?.network ?? 'gnosis';\n this.requestOptions = {\n baseURL: this.url,\n timeout: options?.timeout ?? 0,\n headers: options?.headers,\n onRequest: options?.onRequest,\n httpAgent: options?.httpAgent,\n httpsAgent: options?.httpsAgent\n };\n // Facade handed to each module (namespace) class, exposing only the shared\n // state and helpers they need — without widening the public `bee.` surface.\n const context = {\n getRequestOptionsForCall: requestOptions => this.getRequestOptionsForCall(requestOptions),\n url: this.url,\n signer: this.signer,\n network: this.network,\n bee: this\n };\n this.balance = new Balance(context);\n this.settlement = new Settlement(context);\n this.transaction = new Transaction(context);\n this.stake = new Stake(context);\n this.connectivity = new Connectivity(context);\n this.status = new Status(context);\n this.wallet = new Wallet(context);\n this.stamp = new Stamp(context);\n this.storage = new Storage(context);\n this.chequebook = new Chequebook(context);\n this.cheque = new Cheque(context);\n this.tag = new Tag(context);\n this.pin = new Pin(context);\n this.grantee = new Grantee(context);\n this.messaging = new Messaging(context);\n this.feed = new Feed(context);\n this.soc = new Soc(context);\n this.data = new Data(context);\n this.chunk = new ChunkNamespace(context);\n this.file = new FileNamespace(context);\n this.collection = new Collection(context);\n }\n /**\n * Creates a Content Addressed Chunk.\n *\n * To be uploaded with the {@link uploadChunk} method.\n *\n * Payload size must be between 1 and 4096 bytes.\n *\n * @param rawPayload Data to be stored in the chunk. If the data is a string, it will be converted to UTF-8 bytes.\n * @param span Optional span for the chunk. If not provided, it will be set to the length of the payload.\n */\n makeContentAddressedChunk(rawPayload, span) {\n return makeContentAddressedChunk(rawPayload, span);\n }\n /**\n * Attempts to unmarshal arbitrary data into a Content Addressed Chunk.\n * Throws an error if the data is not a valid CAC.\n *\n * @param data The chunk data (`span` and `payload`)\n */\n unmarshalContentAddressedChunk(data) {\n return unmarshalContentAddressedChunk(data);\n }\n /**\n * Creates a Single Owner Chunk.\n *\n * To be uploaded with the {@link uploadChunk} method.\n *\n * Identical to chaining `makeContentAddressedChunk` and `toSingleOwnerChunk`.\n *\n * Payload size must be between 1 and 4096 bytes.\n *\n * @param address Address of the Content Addressed Chunk\n * @param span Span of the Content Addressed Chunk\n * @param payload Payload of the Content Addressed Chunk\n * @param identifier The identifier of the chunk\n * @param signer The signer interface for signing the chunk\n */\n makeSingleOwnerChunk(address, span, payload, identifier, signer) {\n return makeSingleOwnerChunk(address, span, payload, identifier, signer);\n }\n /**\n * Calculates the address of a Single Owner Chunk based on its identifier and owner address.\n *\n * @param identifier\n * @param address\n */\n calculateSingleOwnerChunkAddress(identifier, address) {\n return makeSOCAddress(identifier, address);\n }\n /**\n * Attempts to unmarshal arbitrary data into a Single Owner Chunk.\n * Throws an error if the data is not a valid SOC.\n *\n * @param data The chunk data\n * @param address The address of the single owner chunk\n *\n * @returns a single owner chunk or throws error\n */\n unmarshalSingleOwnerChunk(data, address) {\n return unmarshalSingleOwnerChunk(data, address);\n }\n /**\n * Creates the postage batch signature for a specific chunk address.\n *\n * This is for advanced usage, where a pre-signed chunk can be uploaded\n * through a different Bee node.\n *\n * @param postageBatchId\n * @param reference\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n *\n * @example\n * const envelope = await bee.createEnvelope(batchId, chunk.address)\n * await bee.chunk.upload(envelope, chunk)\n *\n * @returns\n */\n async createEnvelope(postageBatchId, reference, requestOptions) {\n postageBatchId = new BatchId(postageBatchId);\n reference = new Reference(reference);\n return postEnvelope(this.getRequestOptionsForCall(requestOptions), postageBatchId, reference);\n }\n /**\n * Gets reserve commitment hash duration seconds.\n *\n * To be able to participe in the storage incentives and not get frozen, this should\n * ideally run under 5 minutes.\n *\n * This is a CPU intensice operation, as roughly 2^22 chunks are hashed in the process.\n *\n * @param requestOptions Options for making requests, such as timeouts, custom HTTP agents, headers, etc.\n *\n * @example\n * const addresses = await bee.connectivity.getNodeAddresses()\n * const topology = await bee.connectivity.getTopology()\n * const result = await bee.rchash(topology.depth, addresses.overlay.toHex(), addresses.overlay.toHex())\n * // result is a number of seconds\n */\n async rchash(depth, anchor1, anchor2, requestOptions) {\n return rchash(this.getRequestOptionsForCall(requestOptions), depth, anchor1, anchor2);\n }\n /**\n * Merges per-call request options with the instance defaults.\n *\n * Stays `protected`; module namespaces reach it only through the {@link BeeContext}\n * facade they are constructed with, so it never widens the public `bee.` surface.\n */\n getRequestOptionsForCall(requestOptions) {\n if (requestOptions) {\n requestOptions = BeeRequestOptionsSchema.parse(requestOptions);\n }\n return requestOptions ? Objects.deepMerge2(this.requestOptions, requestOptions) : this.requestOptions;\n }\n}","import { EthAddress, Signature } from '@ethersphere/core-sdk';\nimport { PostEnvelopeBodyResponse } from \"../types/schema/envelope.js\";\nimport { http } from \"../utils/http.js\";\nconst ENVELOPE_ENDPOINT = 'envelope';\nexport async function postEnvelope(requestOptions, postageBatchId, reference) {\n const response = await http(requestOptions, {\n method: 'post',\n responseType: 'json',\n url: `${ENVELOPE_ENDPOINT}/${reference}`,\n headers: {\n 'swarm-postage-batch-id': postageBatchId.toHex()\n }\n });\n const body = PostEnvelopeBodyResponse.parse(response.data);\n return {\n issuer: new EthAddress(body.issuer),\n index: body.index,\n timestamp: body.timestamp,\n signature: new Signature(body.signature),\n batchId: postageBatchId\n };\n}","import { RchashResponse } from \"../types/schema/rchash.js\";\nimport { http } from \"../utils/http.js\";\nconst RCHASH_ENDPOINT = 'rchash';\nexport async function rchash(requestOptions, depth, anchor1, anchor2) {\n const response = await http(requestOptions, {\n responseType: 'json',\n url: `${RCHASH_ENDPOINT}/${depth}/${anchor1}/${anchor2}`\n });\n return RchashResponse.parse(response.data).durationSeconds;\n}","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { Reference } from \"@ethersphere/bee-js\"\nimport { makeContentAddressedChunk, type ContentAddressedChunk } from \"../chunk\"\nimport type { ChunkReference } from \"./types\"\n\n// Constants\nexport const CHUNK_SIZE = 4096\nexport const PLAIN_REFS_PER_CHUNK = 128 // 4096 / 32 = 128 plain (unencrypted) refs per intermediate chunk\nexport const ENCRYPTED_REFS_PER_CHUNK = 64 // 4096 / 64 = 64 encrypted refs per intermediate chunk\n\n/**\n * Split data into 4096-byte chunks\n */\nexport function splitDataIntoChunks(data: Uint8Array): Uint8Array[] {\n // Zero-length content is a single empty chunk (span 0) — Bee's canonical\n // representation of an empty file.\n if (data.length === 0) {\n return [new Uint8Array(0)]\n }\n const chunks: Uint8Array[] = []\n for (let i = 0; i < data.length; i += CHUNK_SIZE) {\n chunks.push(data.slice(i, Math.min(i + CHUNK_SIZE, data.length)))\n }\n return chunks\n}\n\n/**\n * Build merkle tree from chunk references\n * Returns root reference (32 bytes)\n */\nexport async function buildMerkleTree(\n chunkRefs: ChunkReference[],\n onIntermediateChunk: (chunk: ContentAddressedChunk) => Promise<void>,\n): Promise<Reference> {\n // Single chunk - return direct reference\n if (chunkRefs.length === 1) {\n return new Reference(chunkRefs[0].address)\n }\n\n // Build intermediate level\n const intermediateRefs: ChunkReference[] = []\n\n for (let i = 0; i < chunkRefs.length; i += PLAIN_REFS_PER_CHUNK) {\n const refs = chunkRefs.slice(\n i,\n Math.min(i + PLAIN_REFS_PER_CHUNK, chunkRefs.length),\n )\n\n // Pass through single-ref batches without wrapping in an intermediate\n // (Bee's \"carrier chunk\" rule). A ghost intermediate with span ≤ 4096\n // would be misidentified as a leaf by the download path's span-based\n // leaf check.\n if (refs.length === 1) {\n intermediateRefs.push(refs[0])\n continue\n }\n\n // Build payload with 32-byte references\n const payload = new Uint8Array(refs.length * 32)\n refs.forEach((ref, idx) => {\n payload.set(ref.address, idx * 32)\n })\n\n // An intermediate chunk's span is the total data length covered by its\n // children, not the length of its references payload.\n const totalSpan = refs.reduce((sum, ref) => sum + (ref.span ?? 0n), 0n)\n\n // Create intermediate chunk\n const chunk = makeContentAddressedChunk(payload, totalSpan)\n intermediateRefs.push({\n address: chunk.address.toUint8Array(),\n span: totalSpan,\n })\n\n // Upload intermediate chunk\n await onIntermediateChunk(chunk)\n }\n\n // Recursively build tree if needed\n if (intermediateRefs.length > 1) {\n return buildMerkleTree(intermediateRefs, onIntermediateChunk)\n }\n\n // Return root reference\n return new Reference(intermediateRefs[0].address)\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Account-bus message schemas.\n *\n * Wire format is JSON-serializable (plain objects, hex strings) so the same\n * messages can later travel transports that leave the browser (WebRTC,\n * SWIP-60). Receivers parse with `BusMessageSchema`, which revives typed\n * values (BatchId, PrivateKey, bigint) exactly like the device-state feed\n * payloads do — see `docs/Account-Bus.md`.\n */\n\nimport { z } from \"zod\"\n\nimport {\n AccountStateSnapshotSchemaV1,\n ConnectedAppSchemaV1,\n UtilizationUpdateMessageSchema,\n} from \"../schemas\"\n\n/**\n * A connected app as it travels the bus: without the per-context session\n * material. Zod drops unknown keys, so this `.omit` enforces the strip on\n * receive — a publisher that forgets it still has `appSecret` and\n * `connectedUntil` removed here, before any receiver sees them.\n *\n * Enforced on receive because one forgetful publisher is expensive on both\n * counts: every partitioned iframe on the account would learn every dApp's\n * secret (the leak the strip exists to prevent), and an entry for the receiving\n * app carrying a live `connectedUntil` with a different secret would drive the\n * reconcile into `authenticateFromStorage`, which drops the hydrated view a\n * partitioned session cannot rebuild without a reload.\n *\n * `chatBinding` is omitted for the first of those reasons: a frame for one\n * site must not learn the account's chat address on another\n * (`accountDeltaFromSnapshot`).\n */\nconst BusConnectedAppSchema = ConnectedAppSchemaV1.omit({\n appSecret: true,\n connectedUntil: true,\n chatBinding: true,\n})\n\n/**\n * A full account-state snapshot from another live context. Merged with the\n * same LWW primitives as a device-state feed payload, minus the session\n * material above.\n */\nexport const AccountDeltaMessageSchema = z.object({\n type: z.literal(\"account-delta\"),\n snapshot: AccountStateSnapshotSchemaV1.extend({\n connectedApps: z.array(BusConnectedAppSchema),\n }),\n})\n\n/** The publishing device. Bounded like every other field on the wire: a\n * room-mate already holds the account keys and can do far worse, so this is\n * defense in depth, not a trust boundary. */\nconst DeviceIdSchema = z.string().min(1).max(100)\n\n/** One slot-wait round, as every holder must read it: lower-case hex of a\n * fixed width, so `parseInt(_, 16)` is a number for all of them or the\n * message never reaches a handler. */\nconst LeaseRequestIdSchema = z.string().regex(/^[0-9a-f]{8}$/)\n\n/**\n * A context waiting for a batch-write partition (all slots held by live\n * peers) asks live holders to yield. An idle holder releases via the normal\n * Swarm lease protocol and answers with `lease-released`; holders mid-write\n * ignore it. Repeats are harmless (yield-if-idle is a guarded no-op).\n *\n * `batchId` scopes the ask, because the lock it is a fast path for is scoped\n * that way: lanes are per (batch, partition) since #589, so a holder on batch X\n * and a waiter on batch Y contend for nothing. Answering across batches would\n * drop a lease for a waiter it cannot help — the bus must not be broader than\n * the lock it front-runs, any more than it may be narrower.\n *\n * `requestId` is 8 hex chars, fresh per poll round, and exists so exactly one\n * holder answers: each derives the same permutation of the partitions from it\n * and yields at its own rank (`swarm-id-proxy.ts`, `yieldRankDelayMs`). It is\n * format-checked: the rank is `parseInt(requestId, 16)`, so an unconstrained\n * string reintroduces the very bug this fixes (`NaN` or a negative collapses\n * every holder onto the same rank), silently and with nothing to diagnose.\n */\nexport const LeaseRequestMessageSchema = z.object({\n type: z.literal(\"lease-request\"),\n accountId: z.string().length(40),\n batchId: z.string().length(64),\n fromDeviceId: DeviceIdSchema,\n requestId: LeaseRequestIdSchema,\n})\n\n/**\n * The holder that drew the lowest rank for `requestId` is answering it. Sent\n * BEFORE the release starts, because the release itself is two stamped Swarm\n * writes — an order of magnitude longer than the rank step — so a signal sent\n * after it would reach the other holders only once they had all begun yielding\n * too, which is #576 again. Holders further down the order stand down on this;\n * the waiter ignores it (no slot is free yet).\n */\nexport const LeaseClaimMessageSchema = z.object({\n type: z.literal(\"lease-claim\"),\n accountId: z.string().length(40),\n batchId: z.string().length(64),\n fromDeviceId: DeviceIdSchema,\n requestId: LeaseRequestIdSchema,\n})\n\n/**\n * A holder released its partition (on request or teardown) — waiting peers\n * wake their slot poll immediately instead of sleeping out the interval. The\n * lock SOCs on Swarm remain the authority; this is purely a wake-up.\n *\n * `requestId` echoes the `lease-request` this answers, so holders further down\n * the rank order can stand down. Absent on a teardown release, which answers\n * no request.\n */\nexport const LeaseReleasedMessageSchema = z.object({\n type: z.literal(\"lease-released\"),\n accountId: z.string().length(40),\n batchId: z.string().length(64),\n partition: z.number().int().min(0),\n fromDeviceId: DeviceIdSchema,\n requestId: LeaseRequestIdSchema.optional(),\n})\n\n/**\n * \"This device is live.\" Published on joining the room and every\n * `PRESENCE_INTERVAL_MS` (`presence.ts`); a receiver stamps its own clock, so\n * there is no timestamp to skew and a replay (#604) only refreshes a device it\n * has already heard. A peer on an older bundle drops it at the schema — a new\n * kind, not an envelope change, so nothing to deploy in step.\n */\nexport const PresenceMessageSchema = z.object({\n type: z.literal(\"presence\"),\n accountId: z.string().length(40),\n fromDeviceId: DeviceIdSchema,\n})\n\nexport const BusMessageSchema = z.discriminatedUnion(\"type\", [\n UtilizationUpdateMessageSchema,\n AccountDeltaMessageSchema,\n LeaseRequestMessageSchema,\n LeaseClaimMessageSchema,\n LeaseReleasedMessageSchema,\n PresenceMessageSchema,\n])\n\nexport type AccountDeltaMessage = z.infer<typeof AccountDeltaMessageSchema>\n/** The wire form of a delta: hex strings, not revived `BatchId`/`PrivateKey`. */\nexport type AccountDeltaInput = z.input<typeof AccountDeltaMessageSchema>\nexport type BusMessage = z.infer<typeof BusMessageSchema>\n/** What publishers hand to the bus: the JSON-serializable wire form. */\nexport type BusMessageInput = z.input<typeof BusMessageSchema>\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * The account bus: a real-time message channel between the live contexts of an\n * account. Two contexts construct one: the proxy iframes, and — since the\n * `account-delta` publisher — the SwarmID tab, which is where a revoke happens\n * and the only context that can tell a partitioned iframe about one. The tab\n * attaches the signaling transport alone: everything a `BroadcastChannel`\n * reaches from there already converges through storage events, and the\n * partitioned iframe is in another storage partition, which only a server round\n * trip crosses.\n *\n * Durable truth stays in storage; the bus only carries deltas and coordination\n * between live peers. Publishers never receive their own messages back.\n */\n\nimport { BusMessageSchema } from \"./messages\"\nimport type { BusMessage, BusMessageInput } from \"./messages\"\n\n/**\n * A transport delivers wire-form messages between contexts; it does not\n * validate them — the bus does, in one place, on receive.\n */\nexport interface BusTransport {\n /**\n * True when the transport only reaches contexts of this browser profile —\n * i.e. peers that share this device's `deviceId` and therefore its slot\n * lane. `AccountBus.publish(…, { localOnly: true })` uses this to keep\n * lane-scoped traffic off the wire.\n */\n readonly local: boolean\n publish(message: BusMessageInput): void\n /**\n * `from` is the transport's own name for whoever sent the message — a peer\n * id on the signaling transport, absent on a transport that has no peers.\n * It is what lets `onPeerLeft` be attributed to the devices that peer spoke\n * for (#572).\n */\n subscribe(handler: (raw: unknown, from?: string) => void): () => void\n /**\n * A peer left the room. Optional: a transport without peers has no\n * departures to report, which is a truthful answer rather than a gap.\n */\n onPeerLeft?(handler: (peerId: string) => void): () => void\n close(): void\n}\n\n/** Restricts a publish to transports that stay inside this browser profile. */\nexport interface PublishOptions {\n localOnly?: boolean\n}\n\nconst CHANNEL_PREFIX = \"swarm-id-bus-v1\"\n\n/** The channel a topic's local transport speaks on. Exported so nothing has to\n * re-spell the prefix: a copy of it in a test keeps passing after a bump, on a\n * channel the proxy no longer listens to. */\nexport function busChannelName(topic: string): string {\n return `${CHANNEL_PREFIX}:${topic}`\n}\n\n/**\n * Same-origin, same-partition transport. Covers SwarmID tab↔tab and same-dApp\n * Safari tabs; delivery is per browsing-context, never back to the sender.\n *\n * The topic is the account-derived one from `deriveBusContext`, the same value\n * the signaling transport uses as its room. It used to be a single origin-wide\n * constant, which put every co-resident account's contexts on one unencrypted\n * channel — fine while the only traffic was batch-scoped utilization counters,\n * untenable for `account-delta`, whose payload carries `postageStamps[].signerKey`.\n */\nexport class BroadcastChannelTransport implements BusTransport {\n readonly local = true\n readonly channelName: string\n private channel: BroadcastChannel\n // No `from`: every context on this channel is this browser profile, and the\n // channel names none of them. Nothing here can leave the room separately.\n private handlers = new Set<(raw: unknown) => void>()\n\n constructor(topic: string) {\n this.channelName = busChannelName(topic)\n this.channel = new BroadcastChannel(this.channelName)\n this.channel.onmessage = (event: MessageEvent) => {\n for (const handler of this.handlers) {\n handler(event.data)\n }\n }\n }\n\n publish(message: BusMessageInput): void {\n this.channel.postMessage(message)\n }\n\n subscribe(handler: (raw: unknown) => void): () => void {\n this.handlers.add(handler)\n return () => this.handlers.delete(handler)\n }\n\n close(): void {\n this.handlers.clear()\n this.channel.close()\n }\n}\n\n/**\n * Fans a publish out to every transport and funnels received messages through\n * schema validation to subscribers. Invalid messages are dropped silently\n * (any context on the topic can post; only well-formed messages count).\n */\nexport class AccountBus {\n private transports: BusTransport[]\n private handlers = new Set<(message: BusMessage, from?: string) => void>()\n private peerLeftHandlers = new Set<(peerId: string) => void>()\n private transportUnsubscribers: (() => void)[]\n private closed = false\n\n constructor(transports: BusTransport[]) {\n this.transports = transports\n this.transportUnsubscribers = transports.flatMap((transport) =>\n this.listenTo(transport),\n )\n }\n\n /** Both subscriptions a transport offers, as one list of removers. */\n private listenTo(transport: BusTransport): (() => void)[] {\n const removers = [\n transport.subscribe((raw, from) => this.dispatch(raw, from)),\n ]\n const onPeerLeft = transport.onPeerLeft?.((peerId) => {\n for (const handler of this.peerLeftHandlers) {\n try {\n handler(peerId)\n } catch (error) {\n console.error(\"[AccountBus] Peer-left handler error:\", error)\n }\n }\n })\n if (onPeerLeft) removers.push(onPeerLeft)\n return removers\n }\n\n /**\n * `localOnly` keeps a message off transports that leave this browser\n * profile. Used for lane-scoped traffic (`utilization-updated`): the\n * counters are per-partition, every remote peer is a different device on a\n * different lane, so a remote copy is dropped at the receive guard anyway —\n * after paying for encryption and a frame the signaling server's payload cap\n * may refuse outright.\n */\n publish(message: BusMessageInput, options?: PublishOptions): void {\n if (this.closed) return\n for (const transport of this.transports) {\n if (options?.localOnly && !transport.local) continue\n transport.publish(message)\n }\n }\n\n /**\n * Attach a transport after construction (e.g. the signaling transport once\n * the account's derivation key is known). Returns a remover that detaches\n * and closes it. Duplicate delivery across transports is tolerated by\n * design: every current message kind applies idempotently (LWW snapshot\n * merges; monotonic bucket counters, folded only by a receiver on the\n * publisher's own slot lane — see `UtilizationUpdateMessageSchema`).\n *\n * On a closed bus the transport is closed instead of attached: every join is\n * fire-and-forget, so one whose key derivation was still in flight when the\n * context tore down arrives here holding a live socket, with no other handle\n * left to close it.\n */\n addTransport(transport: BusTransport): () => void {\n if (this.closed) {\n transport.close()\n return () => {}\n }\n this.transports.push(transport)\n // Through the same path as a constructor-time transport: the signaling one\n // is always attached here, so a departure hook wired only in the\n // constructor would never see the transport that has peers.\n const removers = this.listenTo(transport)\n this.transportUnsubscribers.push(...removers)\n return () => {\n for (const remove of removers) remove()\n transport.close()\n this.transports = this.transports.filter((t) => t !== transport)\n this.transportUnsubscribers = this.transportUnsubscribers.filter(\n (u) => !removers.includes(u),\n )\n }\n }\n\n subscribe(handler: (message: BusMessage, from?: string) => void): () => void {\n this.handlers.add(handler)\n return () => this.handlers.delete(handler)\n }\n\n /**\n * A peer left the room, by the name the delivering transport uses for it.\n * The signaling server sends this on socket close, its reaper included, so\n * it is the one departure signal that survives a tab being closed.\n */\n onPeerLeft(handler: (peerId: string) => void): () => void {\n this.peerLeftHandlers.add(handler)\n return () => this.peerLeftHandlers.delete(handler)\n }\n\n /** Idempotent, and terminal: a closed bus neither publishes nor attaches. */\n close(): void {\n this.closed = true\n for (const unsubscribe of this.transportUnsubscribers) {\n unsubscribe()\n }\n this.transportUnsubscribers = []\n this.handlers.clear()\n this.peerLeftHandlers.clear()\n for (const transport of this.transports) {\n transport.close()\n }\n this.transports = []\n }\n\n private dispatch(raw: unknown, from?: string): void {\n const result = BusMessageSchema.safeParse(raw)\n if (!result.success) return\n for (const handler of this.handlers) {\n try {\n handler(result.data, from)\n } catch (error) {\n console.error(\"[AccountBus] Handler error:\", error)\n }\n }\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generic Versioned Storage Utility\n *\n * Provides a framework-agnostic way to store and retrieve versioned data\n * with automatic migration support.\n */\n\nimport { z } from \"zod\"\n\n// ============================================================================\n// Types & Schemas\n// ============================================================================\n\n/**\n * Versioned storage wrapper schema\n * Used to check if data is in versioned format\n */\nexport const VersionedStorageSchema = z.object({\n version: z.number().int().nonnegative(),\n data: z.unknown(),\n})\n\nexport type VersionedStorage = z.infer<typeof VersionedStorageSchema>\n\n/**\n * Storage adapter interface - allows different storage backends\n */\nexport interface StorageAdapter {\n getItem(key: string): string | undefined\n setItem(key: string, value: string): void\n removeItem(key: string): void\n}\n\n/**\n * Version parser function - handles migration from one version to another\n */\nexport type VersionParser<T> = (data: unknown, version: number) => T[]\n\n/**\n * Serializer function - converts data to JSON-serializable format\n */\nexport type Serializer<T> = (data: T) => Record<string, unknown>\n\n/**\n * What a listener learns about a write besides the data. `noRepublish` is set\n * by a writer for a change peers must not hear about: one it folded from a\n * peer (a bus delta, a Swarm fold), a volatile field nobody else needs, or a\n * device-local sign-out. Another instance in the same window must not publish\n * that write to the room (#707). Absent for a write from another window,\n * whose origin the `storage` event cannot carry.\n */\nexport interface StorageChange {\n noRepublish: boolean\n}\n\n/** Listener for storage change events, from other windows or this one. */\nexport type StorageChangeListener<T> = (\n data: T[],\n change?: StorageChange,\n) => void\n\n/**\n * Encryption of fields inside the stored records. Sealing is asynchronous and\n * reading storage is not, so `prime` opens what is stored into memory and\n * `open` then reads it synchronously. What `prime` could not open, `open`\n * treats as absent. `seal` is handed what is stored at the time of the write,\n * so it can keep a stored record this page could not read.\n */\nexport interface RecordSealing {\n seal(\n records: Record<string, unknown>[],\n stored: unknown,\n ): Promise<Record<string, unknown>[]>\n open(data: unknown): unknown\n prime(data: unknown): Promise<void>\n}\n\n/**\n * Options for versioned storage\n */\nexport interface VersionedStorageOptions<T> {\n /** Storage key */\n key: string\n\n /** Current version number */\n currentVersion: number\n\n /** Storage adapter (e.g., localStorage) */\n storage: StorageAdapter\n\n /** Version parsers - map of version to parser function */\n parsers: Record<number, VersionParser<T>>\n\n /** Optional serializer for complex types */\n serializer?: Serializer<T>\n\n /** Logger name for error messages */\n loggerName?: string\n\n /**\n * Seals fields of every record on write. With it, writes are asynchronous\n * and queued per key in this window, a load reads only what `prime` opened,\n * and listeners hear of a change once it is primed.\n */\n sealing?: RecordSealing\n}\n\n// ============================================================================\n// Browser Storage Adapters\n// ============================================================================\n\n/**\n * LocalStorage adapter for browser environments\n */\nexport class LocalStorageAdapter implements StorageAdapter {\n getItem(key: string): string | undefined {\n if (typeof window === \"undefined\" || !window.localStorage) {\n return undefined\n }\n return window.localStorage.getItem(key) ?? undefined\n }\n\n setItem(key: string, value: string): void {\n if (typeof window === \"undefined\" || !window.localStorage) {\n return\n }\n window.localStorage.setItem(key, value)\n }\n\n removeItem(key: string): void {\n if (typeof window === \"undefined\" || !window.localStorage) {\n return\n }\n window.localStorage.removeItem(key)\n }\n}\n\n/**\n * In-memory storage adapter (for testing or non-browser environments)\n */\nexport class MemoryStorageAdapter implements StorageAdapter {\n private storage = new Map<string, string>()\n\n getItem(key: string): string | undefined {\n return this.storage.get(key)\n }\n\n setItem(key: string, value: string): void {\n this.storage.set(key, value)\n }\n\n removeItem(key: string): void {\n this.storage.delete(key)\n }\n\n clear(): void {\n this.storage.clear()\n }\n}\n\n// ============================================================================\n// Core Versioned Storage Class\n// ============================================================================\n\n/**\n * Window-scoped event broadcast on `save()`/`clear()` so manager instances in\n * the SAME window see each other's writes — the browser's `storage` event only\n * fires in other windows.\n */\nconst SAME_WINDOW_WRITE_EVENT = \"swarm-id-storage-write\"\n\n/**\n * Writes to a sealed key wait for sealing, so they are queued per key across\n * every manager instance in the window, in call order. Otherwise a write that\n * started first could land last. Across windows the Web Lock below orders them.\n */\nconst writeQueues = new Map<string, Promise<void>>()\n\n/**\n * The queue orders writes within one window. The identity tab and a frame of\n * the same origin are two windows over one `localStorage`, so a read, the wait\n * for sealing and the write also hold a Web Lock named for the key, or one\n * window's write lands between the other's read and write and is lost.\n * Without the Web Locks API, or when the lock request itself is refused (a\n * SecurityError where the page may not use it), only the window's own queue\n * orders them.\n */\nasync function withStorageWriteLock(\n key: string,\n task: () => Promise<void>,\n): Promise<void> {\n if (typeof navigator === \"undefined\" || !navigator.locks) return task()\n let started = false\n try {\n await navigator.locks.request(\n `swarm-id-storage-write-${key}`,\n { mode: \"exclusive\" },\n () => {\n started = true\n return task()\n },\n )\n } catch (error) {\n if (started) throw error\n await task()\n }\n}\n\ninterface SameWindowWriteDetail {\n key: string\n /** The writing manager, so it can skip notifying itself. */\n source: object\n /** See `StorageChange.noRepublish`. */\n noRepublish?: boolean\n}\n\n/**\n * Generic versioned storage manager\n */\nexport class VersionedStorageManager<T> {\n private options: VersionedStorageOptions<T>\n private listeners: Set<StorageChangeListener<T>> = new Set()\n private boundStorageHandler: ((event: StorageEvent) => void) | undefined\n private boundSameWindowHandler: ((event: Event) => void) | undefined\n\n constructor(options: VersionedStorageOptions<T>) {\n this.options = options\n }\n\n /**\n * Subscribe to writes to this key that this instance did not make: other\n * windows' through the browser's `storage` event, and other manager\n * instances' in THIS window through the `swarm-id-storage-write` event that\n * `save()` and `clear()` dispatch — the `storage` event never fires in the\n * window that wrote. This instance's own writes are skipped.\n *\n * @param listener - Callback function that receives the updated data\n * @returns Unsubscribe function to remove the listener\n */\n subscribe(listener: StorageChangeListener<T>): () => void {\n this.listeners.add(listener)\n\n // Set up storage event listener on first subscription\n if (!this.boundStorageHandler) {\n this.setupStorageEventListener()\n }\n\n // Return unsubscribe function\n return () => {\n this.listeners.delete(listener)\n if (this.listeners.size === 0) {\n this.cleanupStorageEventListener()\n }\n }\n }\n\n /**\n * Set up the browser storage event listener\n * Only listens for changes to this manager's specific key\n */\n private setupStorageEventListener(): void {\n if (typeof window === \"undefined\") return\n\n this.boundStorageHandler = (event: StorageEvent) => {\n // Only handle events for our specific key\n if (event.key !== this.options.key) return\n\n this.reloadAndNotify()\n }\n\n window.addEventListener(\"storage\", this.boundStorageHandler)\n\n // Same-window writes by OTHER manager instances (the `storage` event never\n // fires in the window that wrote).\n this.boundSameWindowHandler = (event: Event) => {\n const detail = (event as CustomEvent<SameWindowWriteDetail>).detail\n if (!detail || detail.key !== this.options.key || detail.source === this)\n return\n\n this.reloadAndNotify(\n detail.noRepublish ? { noRepublish: true } : undefined,\n )\n }\n\n window.addEventListener(\n SAME_WINDOW_WRITE_EVENT,\n this.boundSameWindowHandler,\n )\n }\n\n /**\n * Clean up the storage event listener when no more subscribers\n */\n private cleanupStorageEventListener(): void {\n if (typeof window === \"undefined\") return\n if (this.boundStorageHandler) {\n window.removeEventListener(\"storage\", this.boundStorageHandler)\n this.boundStorageHandler = undefined\n }\n if (this.boundSameWindowHandler) {\n window.removeEventListener(\n SAME_WINDOW_WRITE_EVENT,\n this.boundSameWindowHandler,\n )\n this.boundSameWindowHandler = undefined\n }\n }\n\n /**\n * Broadcast a write to other manager instances in this window.\n */\n private notifySameWindow(noRepublish?: boolean): void {\n if (typeof window === \"undefined\") return\n window.dispatchEvent(\n new CustomEvent<SameWindowWriteDetail>(SAME_WINDOW_WRITE_EVENT, {\n detail: { key: this.options.key, source: this, noRepublish },\n }),\n )\n }\n\n private reloadAndNotify(change?: StorageChange): void {\n if (!this.options.sealing) {\n this.notifyListeners(this.load(), change)\n return\n }\n void this.prime().then(() => this.notifyListeners(this.load(), change))\n }\n\n /**\n * Notify all listeners of data changes\n */\n private notifyListeners(data: T[], change?: StorageChange): void {\n for (const listener of this.listeners) {\n try {\n listener(data, change)\n } catch (e) {\n console.error(\n `[${this.options.loggerName ?? \"Storage\"}] Listener error:`,\n e,\n )\n }\n }\n }\n\n /**\n * Load data from storage\n */\n load(): T[] {\n const stored = this.options.storage.getItem(this.options.key)\n\n if (!stored) {\n return []\n }\n\n try {\n const parsed: unknown = JSON.parse(stored)\n return this.parse(parsed)\n } catch (e) {\n console.error(`[${this.options.loggerName ?? \"Storage\"}] Load failed:`, e)\n return []\n }\n }\n\n /**\n * Parse versioned data and migrate if needed\n */\n private parse(parsed: unknown): T[] {\n // Try to parse as versioned data\n const versioned = VersionedStorageSchema.safeParse(parsed)\n const version = versioned.success ? versioned.data.version : 0\n const stored = versioned.success ? versioned.data.data : parsed\n const data = this.options.sealing\n ? this.options.sealing.open(stored)\n : stored\n\n // Find appropriate parser\n const parser = this.options.parsers[version]\n\n if (!parser) {\n console.error(\n `[${this.options.loggerName ?? \"Storage\"}] No parser for version ${version}`,\n )\n return []\n }\n\n return parser(data, version)\n }\n\n /**\n * Open the sealed fields of what is stored into memory, so `load` can read\n * them. A no-op without sealing.\n */\n async prime(): Promise<void> {\n const sealing = this.options.sealing\n if (!sealing) return\n try {\n const stored = this.storedData()\n if (stored === undefined) return\n await sealing.prime(stored)\n } catch (e) {\n console.error(\n `[${this.options.loggerName ?? \"Storage\"}] Prime failed:`,\n e,\n )\n }\n }\n\n /** The records as stored, still sealed, or `undefined` when there are none. */\n private storedData(): unknown {\n const stored = this.options.storage.getItem(this.options.key)\n if (!stored) return undefined\n const parsed: unknown = JSON.parse(stored)\n const versioned = VersionedStorageSchema.safeParse(parsed)\n return versioned.success ? versioned.data.data : parsed\n }\n\n /**\n * Save data to storage. Resolves once written, which with sealing is after\n * every earlier write to this key in this window. With sealing a write that\n * fails rejects, so the caller can tell the person. Without it the failure\n * is only logged.\n */\n save(data: T[], options?: { noRepublish?: boolean }): Promise<void> {\n let serialized: Record<string, unknown>[]\n try {\n // Serialized now, so a caller changing `data` after the call cannot\n // change what is written.\n serialized = this.serialize(data)\n } catch (e) {\n console.error(`[${this.options.loggerName ?? \"Storage\"}] Save failed:`, e)\n return Promise.resolve()\n }\n if (!this.options.sealing) {\n try {\n this.write(serialized, options)\n } catch (e) {\n console.error(\n `[${this.options.loggerName ?? \"Storage\"}] Save failed:`,\n e,\n )\n }\n return Promise.resolve()\n }\n return this.enqueue(async () => {\n this.write(await this.seal(serialized), options)\n })\n }\n\n /**\n * Read, change and write back as one step: the change sees what is stored\n * when its turn comes, after every earlier write to this key in this window.\n * A change that returns `undefined` writes nothing. Rejects when the write\n * fails.\n */\n update(\n mutate: (stored: T[]) => T[] | undefined,\n options?: { noRepublish?: boolean },\n ): Promise<void> {\n return this.enqueue(async () => {\n await this.prime()\n const next = mutate(this.load())\n if (next === undefined) return\n this.write(await this.seal(this.serialize(next)), options)\n })\n }\n\n /**\n * Clear data from storage\n */\n clear(): Promise<void> {\n const remove = () => {\n this.options.storage.removeItem(this.options.key)\n this.notifySameWindow()\n }\n if (!this.options.sealing) {\n remove()\n return Promise.resolve()\n }\n return this.enqueue(async () => remove())\n }\n\n private serialize(data: T[]): Record<string, unknown>[] {\n return (\n this.options.serializer ? data.map(this.options.serializer) : data\n ) as Record<string, unknown>[]\n }\n\n private seal(\n records: Record<string, unknown>[],\n ): Promise<Record<string, unknown>[]> {\n return this.options.sealing\n ? this.options.sealing.seal(records, this.storedData())\n : Promise.resolve(records)\n }\n\n private write(\n records: Record<string, unknown>[],\n options?: { noRepublish?: boolean },\n ): void {\n const wrapped: VersionedStorage = {\n version: this.options.currentVersion,\n data: records,\n }\n this.options.storage.setItem(this.options.key, JSON.stringify(wrapped))\n this.notifySameWindow(options?.noRepublish)\n }\n\n /** The returned turn rejects on failure. The queue moves on regardless. */\n private enqueue(task: () => Promise<void>): Promise<void> {\n const key = this.options.key\n const run = (writeQueues.get(key) ?? Promise.resolve()).then(() =>\n withStorageWriteLock(key, task),\n )\n const settled = run.catch((e) => {\n console.error(`[${this.options.loggerName ?? \"Storage\"}] Save failed:`, e)\n })\n writeQueues.set(key, settled)\n return run\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Create a versioned storage manager with localStorage\n */\nexport function createLocalStorageManager<T>(\n options: Omit<VersionedStorageOptions<T>, \"storage\">,\n): VersionedStorageManager<T> {\n return new VersionedStorageManager({\n ...options,\n storage: new LocalStorageAdapter(),\n })\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Key material at rest, sealed so it is not plain text on disk.\n *\n * A value is encrypted with AES-GCM under a wrapping key that WebCrypto\n * created non-extractable and that lives in IndexedDB in the same storage as\n * the value: the same origin, and in a partitioned frame the same partition.\n * Script on this origin can still use the key, so this is not a wall against\n * that. What it stops is the plain key being read off disk, out of a storage\n * dump or a backup of the profile, where the wrapping key cannot be taken\n * along as bytes.\n *\n * Opening is asynchronous, reading storage is not, so a page opens what it\n * will read first (`primeSealedValues`) and then reads it from memory\n * (`openedValue`) for the rest of the page's life. A value that cannot be\n * opened, because the wrapping key is gone or the value was altered, stays\n * unopened: callers treat the key it held as absent, never as plain text.\n * A value whose wrapping key could not be read at all, because IndexedDB\n * failed, is unavailable rather than lost (`isSealedValueUnavailable`).\n */\n\nimport { hexToUint8Array, uint8ArrayToHex } from \"./hex\"\n\n/** Marks a stored string as sealed, and names the format. */\nexport const SEALED_VALUE_PREFIX = \"sealed:v1:\"\n\nconst KEY_DB_NAME = \"swarm-id-keys\"\nconst KEY_DB_VERSION = 1\nconst KEY_STORE = \"wrapping-keys\"\nconst WRAPPING_KEY_ID = \"at-rest-v1\"\nconst IV_BYTES = 12\n/**\n * How long an IndexedDB open may take before it counts as failed. A hung open\n * would otherwise hold the storage write lock until the window closes.\n */\nconst KEY_DB_OPEN_TIMEOUT_MS = 10_000\n\n/** Where the wrapping key is kept. */\ninterface WrappingKeyStore {\n /** The stored key, or this one stored when there is none yet. */\n getOrAdd(candidate: CryptoKey): Promise<CryptoKey>\n get(): Promise<CryptoKey | undefined>\n}\n\nfunction idbRequest<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result)\n request.onerror = () => reject(request.error)\n })\n}\n\n/**\n * Opens the key database, or rejects when the open fails, is blocked, or takes\n * longer than `KEY_DB_OPEN_TIMEOUT_MS`. An open that succeeds after giving up\n * is closed at once.\n */\nfunction openKeyDatabase(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n let settled = false\n const fail = (error: unknown) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n reject(error)\n }\n const timer = setTimeout(\n () => fail(new Error(\"IndexedDB open timed out\")),\n KEY_DB_OPEN_TIMEOUT_MS,\n )\n const request = indexedDB.open(KEY_DB_NAME, KEY_DB_VERSION)\n request.onupgradeneeded = () => {\n if (!request.result.objectStoreNames.contains(KEY_STORE)) {\n request.result.createObjectStore(KEY_STORE)\n }\n }\n request.onsuccess = () => {\n if (settled) {\n request.result.close()\n return\n }\n settled = true\n clearTimeout(timer)\n resolve(request.result)\n }\n request.onerror = () => fail(request.error)\n request.onblocked = () => fail(new Error(\"IndexedDB open was blocked\"))\n })\n}\n\nfunction indexedDbWrappingKeyStore(): WrappingKeyStore {\n return {\n async get() {\n const db = await openKeyDatabase()\n try {\n const store = db\n .transaction(KEY_STORE, \"readonly\")\n .objectStore(KEY_STORE)\n return (await idbRequest(store.get(WRAPPING_KEY_ID))) as\n | CryptoKey\n | undefined\n } finally {\n db.close()\n }\n },\n // One read-write transaction for the read and the add, so two windows\n // making their first key at once keep one of them, not one each.\n async getOrAdd(candidate) {\n const db = await openKeyDatabase()\n try {\n return await new Promise<CryptoKey>((resolve, reject) => {\n const tx = db.transaction(KEY_STORE, \"readwrite\")\n const store = tx.objectStore(KEY_STORE)\n let kept: CryptoKey = candidate\n const read = store.get(WRAPPING_KEY_ID)\n read.onsuccess = () => {\n if (read.result) {\n kept = read.result as CryptoKey\n } else {\n store.add(candidate, WRAPPING_KEY_ID)\n }\n }\n tx.oncomplete = () => resolve(kept)\n tx.onerror = () => reject(tx.error)\n tx.onabort = () => reject(tx.error)\n })\n } finally {\n db.close()\n }\n },\n }\n}\n\n/**\n * Without IndexedDB, or with one that cannot be opened, there is nowhere to\n * keep a key beside the data, so the key lasts as long as the page. What it\n * sealed cannot be opened by the next load, which then reads as signed out:\n * never plain text instead.\n */\nfunction memoryWrappingKeyStore(): WrappingKeyStore {\n let key: CryptoKey | undefined\n return {\n async get() {\n return key\n },\n async getOrAdd(candidate) {\n key ??= candidate\n return key\n },\n }\n}\n\ninterface ChosenKeyStore {\n store: WrappingKeyStore\n /**\n * The page keeps its key in memory in place of the browser's IndexedDB,\n * because it is turned off (null) or could not be opened. The stored values\n * may still open on a later load, so one that does not open with this key\n * is unavailable, not lost.\n */\n standsIn: boolean\n}\n\nlet keyStore: Promise<ChosenKeyStore> | undefined\nlet wrappingKey: Promise<CryptoKey> | undefined\n\nasync function chooseKeyStore(): Promise<ChosenKeyStore> {\n if (typeof indexedDB === \"undefined\") {\n return { store: memoryWrappingKeyStore(), standsIn: false }\n }\n // Some browsers set indexedDB to null where it is turned off.\n if (!indexedDB) return { store: memoryWrappingKeyStore(), standsIn: true }\n try {\n const db = await openKeyDatabase()\n db.close()\n return { store: indexedDbWrappingKeyStore(), standsIn: false }\n } catch {\n return { store: memoryWrappingKeyStore(), standsIn: true }\n }\n}\n\n/** Chosen once per page. */\nfunction currentKeyStore(): Promise<ChosenKeyStore> {\n keyStore ??= chooseKeyStore()\n return keyStore\n}\n\n/**\n * Whether this page seals with a key of its own in place of the browser's\n * IndexedDB. What it seals now no later load can open.\n */\nexport async function sealsWithPageKey(): Promise<boolean> {\n return (await currentKeyStore()).standsIn\n}\n\n/** The wrapping key, created on first use. */\nfunction getWrappingKey(): Promise<CryptoKey> {\n wrappingKey ??= (async () => {\n const { store } = await currentKeyStore()\n const existing = await store.get()\n if (existing) return existing\n const candidate = await crypto.subtle.generateKey(\n { name: \"AES-GCM\", length: 256 },\n false,\n [\"encrypt\", \"decrypt\"],\n )\n return store.getOrAdd(candidate)\n })()\n // A failed attempt is not remembered, so a later use tries again.\n wrappingKey.catch(() => {\n wrappingKey = undefined\n })\n return wrappingKey\n}\n\nconst openedBySealed = new Map<string, string>()\nconst sealedByPlain = new Map<string, string>()\nconst pendingOpens = new Map<string, Promise<void>>()\n\nexport function isSealedValue(value: unknown): value is string {\n return typeof value === \"string\" && value.startsWith(SEALED_VALUE_PREFIX)\n}\n\n/**\n * Seal a value for storage. One value seals to one sealed form for the life of\n * the page, so rewriting an unchanged record writes the same bytes.\n */\nexport async function sealValue(plaintext: string): Promise<string> {\n const known = sealedByPlain.get(plaintext)\n if (known) return known\n const key = await getWrappingKey()\n const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES))\n const ciphertext = new Uint8Array(\n await crypto.subtle.encrypt(\n { name: \"AES-GCM\", iv },\n key,\n new TextEncoder().encode(plaintext),\n ),\n )\n const sealed = `${SEALED_VALUE_PREFIX}${uint8ArrayToHex(iv)}${uint8ArrayToHex(ciphertext)}`\n sealedByPlain.set(plaintext, sealed)\n openedBySealed.set(sealed, plaintext)\n return sealed\n}\n\n/**\n * Values this page could not reach the wrapping key for. They may open on a\n * later load, so what holds them must not be written over in this one.\n */\nconst unavailableThisPage = new Set<string>()\n\nasync function openOne(sealed: string): Promise<void> {\n let key: CryptoKey\n try {\n key = await getWrappingKey()\n } catch {\n unavailableThisPage.add(sealed)\n return\n }\n try {\n const bytes = hexToUint8Array(sealed.slice(SEALED_VALUE_PREFIX.length))\n const plaintext = new TextDecoder().decode(\n await crypto.subtle.decrypt(\n { name: \"AES-GCM\", iv: bytes.slice(0, IV_BYTES) },\n key,\n bytes.slice(IV_BYTES),\n ),\n )\n openedBySealed.set(sealed, plaintext)\n sealedByPlain.set(plaintext, sealed)\n } catch {\n // Left unopened: the wrapping key is gone, or the value was altered.\n if ((await currentKeyStore()).standsIn) unavailableThisPage.add(sealed)\n }\n}\n\n/**\n * Whether this page failed to reach the wrapping key for this value, as\n * opposed to reaching it and finding the value will not open. Stays true for\n * the rest of the page even if a later try opens it.\n */\nexport function isSealedValueUnavailable(sealed: string): boolean {\n return unavailableThisPage.has(sealed)\n}\n\n/** Open these sealed values into memory, so `openedValue` can read them. */\nexport async function primeSealedValues(\n sealed: Iterable<string>,\n): Promise<void> {\n const opens: Promise<void>[] = []\n for (const value of sealed) {\n if (!isSealedValue(value) || openedBySealed.has(value)) continue\n let pending = pendingOpens.get(value)\n if (!pending) {\n pending = openOne(value).finally(() => pendingOpens.delete(value))\n pendingOpens.set(value, pending)\n }\n opens.push(pending)\n }\n await Promise.all(opens)\n}\n\n/** The plain value, if this page has opened it. */\nexport function openedValue(sealed: string): string | undefined {\n return openedBySealed.get(sealed)\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * The one place a caught failure becomes what the bridge carries (#761). The\n * proxy's own refusals are thrown as `SwarmIdError` and pass through; every\n * other class the proxy can meet is named here, and the rest is `internal`\n * with its message kept.\n */\n\nimport { BeeResponseError } from \"@ethersphere/bee-js\"\nimport { ZodError } from \"zod\"\nimport { SwarmIdError } from \"../errors\"\nimport type { WireError } from \"../types\"\n\n// The lib's own classes are told apart by `name`, which each sets, rather\n// than imported for `instanceof`: importing the coordinator and the\n// utilization module from here closed a cycle back through\n// `partition-state`, whose schema literal then read `NUM_BUCKETS` as\n// undefined. This module must stay a leaf.\nconst LIB_ERROR_CODES: Record<string, WireError[\"code\"]> = {\n AppSliceFullError: \"upload-unavailable\",\n PartitionContendedError: \"partition-contended\",\n PartitionLeaseLostError: \"lease-lost\",\n ReservedSlotError: \"invalid-request\",\n TimeoutError: \"timeout\",\n}\n\nconst LIB_ERROR_REASONS: Record<string, WireError[\"reason\"]> = {\n AppSliceFullError: \"app-slice-full\",\n}\n\n/** Bee's error body is `{ code, message }`; the gateway may answer plain text */\nfunction beeMessageOf(body: unknown): string | undefined {\n if (typeof body === \"string\") return body || undefined\n if (body && typeof body === \"object\" && \"message\" in body) {\n const { message } = body as { message: unknown }\n if (typeof message === \"string\") return message\n }\n return undefined\n}\n\n/**\n * The endpoint that refused, without its host: bee-js joins the node's base\n * URL in, and the user's node — a LAN address, an internal hostname — is\n * theirs, not the dApp's to learn from an error.\n */\nexport function refusedPath(url: string): string | undefined {\n try {\n return new URL(url).pathname\n } catch {\n return undefined\n }\n}\n\nexport function toWireError(error: unknown, fallback: string): WireError {\n if (error instanceof SwarmIdError) return error.toWire()\n if (!(error instanceof Error)) return { error: fallback, code: \"internal\" }\n const wire: WireError = { error: error.message, code: \"internal\" }\n if (error instanceof BeeResponseError) {\n wire.code = \"bee-rejected\"\n if (error.status !== undefined) wire.status = error.status\n const beeMessage = beeMessageOf(error.responseBody)\n if (beeMessage !== undefined) wire.beeMessage = beeMessage\n const path = refusedPath(error.url)\n if (path !== undefined) wire.url = path\n } else if (error.name === \"SocUploadError\") {\n const { status, body } = error as Error & { status: number; body: string }\n wire.code = \"bee-rejected\"\n wire.status = status\n wire.beeMessage = body\n } else if (error.name in LIB_ERROR_CODES) {\n wire.code = LIB_ERROR_CODES[error.name]\n const reason = LIB_ERROR_REASONS[error.name]\n if (reason !== undefined) wire.reason = reason\n } else if (error instanceof ZodError) {\n wire.code = \"invalid-request\"\n } else if (error instanceof TypeError && /fetch/i.test(error.message)) {\n // `fetch` rejects with a TypeError and no response when the request\n // never completed: DNS, a refused connection, or a CORS preflight the\n // gateway rejected (#752).\n wire.code = \"network\"\n }\n return wire\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Browser-compatible WebSocket chunk upload stream for Bee's /chunks/stream endpoint.\n *\n * Uses per-chunk stamping: each message is stamp[113 bytes] + chunkData.\n * The caller is responsible for prepending the marshaled stamp before calling uploadChunk().\n *\n * Concurrency is controlled via a semaphore pattern: up to `concurrency` chunks\n * can be in-flight (sent but not yet acknowledged). WebSocket message ordering\n * guarantees FIFO ack matching.\n */\n\nconst WS_CHUNK_STREAM_PATH = \"/chunks/stream\"\nconst WS_DEFAULT_CONCURRENCY = 32\nconst WS_ACK_TIMEOUT_MS = 900_000 // 15 minutes, matching Bee's streamReadTimeout\n\ninterface PendingAck {\n resolve: () => void\n reject: (error: Error) => void\n timer: ReturnType<typeof setTimeout>\n}\n\nexport interface ChunkUploadStreamOptions {\n tag?: number\n concurrency?: number\n}\n\nexport class ChunkUploadStream {\n private ws: WebSocket | undefined\n private pendingAcks: PendingAck[] = []\n private concurrency: number\n private closed = false\n private errorMessage: string | undefined\n\n constructor(\n private beeUrl: string,\n private options?: ChunkUploadStreamOptions,\n ) {\n this.concurrency = options?.concurrency ?? WS_DEFAULT_CONCURRENCY\n }\n\n getConcurrency(): number {\n return this.concurrency\n }\n\n /**\n * Open the WebSocket connection to Bee's chunk stream endpoint.\n * Resolves when the connection is established.\n */\n open(): Promise<void> {\n return new Promise((resolve, reject) => {\n const wsUrl = this.buildWsUrl()\n this.ws = new WebSocket(wsUrl)\n this.ws.binaryType = \"arraybuffer\"\n\n this.ws.onopen = () => {\n resolve()\n }\n\n this.ws.onerror = () => {\n const msg = \"WebSocket connection error\"\n this.errorMessage = msg\n this.rejectAllPending(new Error(msg))\n reject(new Error(msg))\n }\n\n this.ws.onmessage = () => {\n // Each ack from Bee is an empty binary message\n const pending = this.pendingAcks.shift()\n if (pending) {\n clearTimeout(pending.timer)\n pending.resolve()\n }\n }\n\n this.ws.onclose = (event) => {\n if (!this.closed) {\n const msg = `WebSocket closed unexpectedly: ${event.code} ${event.reason}`\n this.errorMessage = msg\n this.rejectAllPending(new Error(msg))\n }\n }\n })\n }\n\n /**\n * Send a stamped chunk over the WebSocket.\n * The data should already have the 113-byte stamp prepended.\n *\n * Respects concurrency limits — will wait if too many chunks are in-flight.\n */\n async uploadChunk(stampedData: Uint8Array): Promise<void> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error(this.errorMessage ?? \"WebSocket is not open\")\n }\n\n // Wait for a concurrency slot if at capacity\n while (this.pendingAcks.length >= this.concurrency) {\n await this.waitForAck()\n }\n\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error(\"WebSocket chunk ack timeout\"))\n }, WS_ACK_TIMEOUT_MS)\n\n this.pendingAcks.push({ resolve, reject, timer })\n try {\n this.ws!.send(stampedData)\n } catch (err) {\n // send() failed (e.g. buffer full) — remove pending ack and reject\n this.pendingAcks.pop()\n clearTimeout(timer)\n reject(err instanceof Error ? err : new Error(\"WebSocket send failed\"))\n }\n })\n }\n\n /**\n * Wait for all pending acks to resolve, then close the WebSocket.\n */\n async close(): Promise<void> {\n // Wait for all in-flight chunks to be acknowledged\n while (this.pendingAcks.length > 0) {\n await this.waitForAck()\n }\n\n this.closed = true\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.close()\n }\n }\n\n private buildWsUrl(): string {\n // Convert http(s) to ws(s)\n let base = this.beeUrl\n if (base.startsWith(\"https://\")) {\n base = \"wss://\" + base.slice(\"https://\".length)\n } else if (base.startsWith(\"http://\")) {\n base = \"ws://\" + base.slice(\"http://\".length)\n }\n\n // Remove trailing slash\n if (base.endsWith(\"/\")) {\n base = base.slice(0, -1)\n }\n\n let url = base + WS_CHUNK_STREAM_PATH\n\n // Add tag as query parameter if provided\n if (this.options?.tag) {\n url += `?swarm-tag=${this.options.tag}`\n }\n\n return url\n }\n\n /**\n * Wait for the next ack to arrive (used for concurrency back-pressure).\n */\n private waitForAck(): Promise<void> {\n const oldest = this.pendingAcks[0]\n if (!oldest) return Promise.resolve()\n\n return new Promise((resolve, reject) => {\n const originalResolve = oldest.resolve\n oldest.resolve = () => {\n originalResolve()\n resolve()\n }\n const originalReject = oldest.reject\n oldest.reject = (error: Error) => {\n originalReject(error)\n reject(error)\n }\n })\n }\n\n private rejectAllPending(error: Error): void {\n for (const pending of this.pendingAcks) {\n clearTimeout(pending.timer)\n pending.reject(error)\n }\n this.pendingAcks = []\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { Bee } from \"@ethersphere/bee-js\"\n\n/**\n * Nodes that have already 404'd `POST /tags` (e.g. public gateways). Keyed per\n * `Bee` so the verdict is scoped to the node URL — pointing at a real node\n * later uses a different `Bee` and re-probes. Without this, every upload retries\n * the same failing round-trip on the critical path.\n */\nconst tagsUnsupported = new WeakMap<Bee, boolean>()\n\n/** HTTP status for an unimplemented endpoint. */\nconst NOT_FOUND = 404\n\n/** True when `error` carries a numeric HTTP `status` equal to `status`\n * (`BeeResponseError.status`); false for any error without that field. */\nfunction isStatus(error: unknown, status: number): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as { status?: unknown }).status === status\n )\n}\n\n/**\n * Attempt to create a tag for upload progress tracking.\n * Returns the tag UID on success, or undefined if the node\n * does not support tags (e.g., gateway nodes return 404).\n *\n * Only a definitive 404 (the endpoint is unsupported) is cached per `Bee`; a\n * transient failure (5xx / network / timeout) is NOT cached, so a later upload\n * re-probes the node instead of being permanently tag-less after one blip.\n */\nexport async function tryCreateTag(bee: Bee): Promise<number | undefined> {\n if (tagsUnsupported.get(bee)) {\n return undefined\n }\n try {\n const tagResponse = await bee.tag.create()\n return tagResponse.uid\n } catch (error) {\n // A 404 means the node doesn't implement `POST /tags` — cache it so every\n // subsequent upload skips the round-trip. Anything else is transient: skip\n // the tag this once, but leave the cache clear so we re-probe next time.\n // Decide on the response STATUS (`BeeResponseError.status`), never the\n // message text — a 5xx whose body merely mentions \"404\" must not poison the\n // cache.\n const unsupported = isStatus(error, NOT_FOUND)\n if (unsupported) {\n tagsUnsupported.set(bee, true)\n }\n console.warn(\n `[tryCreateTag] Tag creation failed — proceeding without tag${\n unsupported\n ? \" (node does not support tags; cached, will not retry on this node)\"\n : \" (transient; will re-probe on the next upload)\"\n }`,\n )\n return undefined\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Unified upload API for Swarm data uploads.\n *\n * This module provides a single entry point for all upload operations:\n * - Plain or encrypted data uploads\n * - Stamper-based (user has postage batch) or subsidised gateway (gateway handles stamps)\n * - HTTP or WebSocket transport\n *\n * Usage:\n * const target: UploadTarget = { mode: \"stamper\", bee, stamper }\n * const result = await uploadData(target, data, { encryptionKey: true })\n */\n\nimport { SwarmIdError } from \"../errors\"\nimport { refusedPath } from \"./errors\"\nimport { Reference, PrivateKey, Identifier, Span } from \"@ethersphere/bee-js\"\nimport type {\n Bee,\n BeeRequestOptions,\n Stamper,\n UploadOptions,\n EnvelopeWithBatchId,\n} from \"@ethersphere/bee-js\"\nimport { Binary } from \"cafe-utility\"\nimport {\n makeContentAddressedChunk,\n makeEncryptedContentAddressedChunk,\n calculateChunkAddress,\n newChunkEncrypter,\n type ContentAddressedChunk,\n type EncryptedChunk,\n DEFAULT_UPLOAD_CONCURRENCY,\n} from \"../chunk\"\nimport {\n splitDataIntoChunks,\n CHUNK_SIZE,\n ENCRYPTED_REFS_PER_CHUNK,\n PLAIN_REFS_PER_CHUNK,\n} from \"./chunking\"\nimport { ChunkUploadStream } from \"./chunk-upload-stream\"\nimport { convertEnvelopeToMarshaledStamp } from \"@ethersphere/core-sdk\"\nimport type { ChunkReference, UploadProgress } from \"./types\"\nimport type { StampWorkerPool } from \"./stamp-worker-pool\"\nimport { tryCreateTag } from \"../utils/tag\"\nimport { uint8ArrayToHex } from \"../utils/hex\"\nimport { normalizeUrl } from \"../utils/url\"\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Thrown when a SOC upload gets a non-OK HTTP response. Carries the numeric\n * `status` so callers can distinguish a definitive stamp rejection (400) from a\n * transient / not-yet-synced condition (404 \"batch not found\", 422 \"not usable\n * yet\") without regex-matching the message — see `verifyBatchStampable`.\n */\nexport class SocUploadError extends Error {\n readonly status: number\n /** What the node or gateway answered, for the bridge's `beeMessage` (#761) */\n readonly body: string\n\n constructor(status: number, statusText: string, body: string) {\n super(`SOC upload failed: ${status} ${statusText} - ${body}`)\n this.name = \"SocUploadError\"\n this.status = status\n this.body = body\n }\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Upload target: stamper-based (user has postage batch) or subsidised gateway\n */\nexport type UploadTarget =\n | {\n mode: \"stamper\"\n bee: Bee\n stamper: Stamper\n workerPool?: StampWorkerPool\n }\n | {\n mode: \"subsidised\"\n gatewayUrl: string\n }\n\n/**\n * Type guard for stamper target\n */\nexport function isStamperTarget(\n target: UploadTarget,\n): target is UploadTarget & { mode: \"stamper\" } {\n return target.mode === \"stamper\"\n}\n\n/**\n * Type guard for subsidised target\n */\nexport function isSubsidisedTarget(\n target: UploadTarget,\n): target is UploadTarget & { mode: \"subsidised\" } {\n return target.mode === \"subsidised\"\n}\n\n/**\n * Unified options for data upload\n */\nexport interface UploadDataOptions {\n /**\n * Encryption key: undefined = plain, Uint8Array = use this key, true =\n * auto-generate. An explicit key only applies to single-chunk data\n * (≤ 4096 bytes); larger data throws — pass true instead.\n */\n encryptionKey?: Uint8Array | true\n /** Pin the uploaded data (stamper mode only; a subsidised gateway never sees it, #752) */\n pin?: boolean\n /** Use deferred upload mode */\n deferred?: boolean\n /** Tag for tracking upload progress */\n tag?: number\n /** WebSocket upload options (stamper mode only) */\n webSocket?: { concurrency?: number }\n /** HTTP concurrency for parallel uploads */\n httpConcurrency?: number\n /** Progress callback */\n onProgress?: (progress: UploadProgress) => void\n /** Bee request options (stamper mode only) */\n requestOptions?: BeeRequestOptions\n}\n\n/**\n * Result of uploading data\n */\nexport interface UploadDataResult {\n /** Reference to the uploaded data (32-byte hex for plain, 64-byte for encrypted) */\n reference: string\n /** Tag UID if one was created */\n tagUid?: number\n /** Addresses of all uploaded chunks (encrypted mode only) */\n chunkAddresses?: Uint8Array[]\n}\n\n/**\n * Options for SOC upload\n */\nexport interface UploadSOCOptions {\n /** Encryption key: undefined = plain SOC, Uint8Array = use this key, true = auto-generate */\n encryptionKey?: Uint8Array | true\n /** Pin the uploaded SOC (stamper mode only; a subsidised gateway never sees it, #752) */\n pin?: boolean\n /** Use deferred upload mode */\n deferred?: boolean\n /** Tag for tracking */\n tag?: number\n /** Bee request options (stamper mode only) */\n requestOptions?: BeeRequestOptions\n}\n\n/**\n * Result of uploading a SOC\n */\nexport interface UploadSOCResult {\n /** SOC address (32 bytes) */\n socAddress: Uint8Array\n /** Encryption key (only for encrypted SOCs) */\n encryptionKey?: Uint8Array\n /** Tag UID if one was created */\n tagUid?: number\n}\n\n/**\n * Options for single chunk upload\n */\nexport interface UploadChunkOptions {\n /** Pin the uploaded chunk (stamper mode only; a subsidised gateway never sees it, #752) */\n pin?: boolean\n /** Use deferred upload mode */\n deferred?: boolean\n /** Tag for tracking */\n tag?: number\n /** Bee request options (stamper mode only) */\n requestOptions?: BeeRequestOptions\n}\n\n/**\n * Result of uploading a chunk\n */\nexport interface UploadChunkResult {\n /** Chunk reference (address) */\n reference: string\n}\n\n// ============================================================================\n// Internal Utilities\n// ============================================================================\n\n/**\n * Async stamp function - abstraction over sync Stamper and parallel StampWorkerPool.\n */\ntype StampChunkFn = (address: Uint8Array) => Promise<EnvelopeWithBatchId>\n\n/**\n * Create a StampChunkFn from a Stamper and optional worker pool.\n */\nfunction makeStampFn(\n stamper: Stamper,\n workerPool?: StampWorkerPool,\n): StampChunkFn {\n if (workerPool) {\n return (address) => workerPool.stampChunkData(address)\n }\n return (address) => Promise.resolve(stamper.stamp(address))\n}\n\n// ============================================================================\n// Encrypted Chunk Helpers\n// ============================================================================\n\ninterface EncryptedChunkRef {\n address: Uint8Array\n key: Uint8Array\n span: bigint\n}\n\n/**\n * Encrypt all payloads into EncryptedChunks and collect their references.\n *\n * An explicit key is only valid for a single payload: every chunk of a\n * multi-chunk upload carries its own key, so honouring one shared key is\n * impossible and substituting random keys would silently break the caller's\n * expectation of a key-addressed reference.\n */\nfunction encryptChunkPayloads(\n payloads: Uint8Array[],\n encryptionKey?: Uint8Array,\n): { chunks: EncryptedChunk[]; refs: EncryptedChunkRef[] } {\n if (encryptionKey && payloads.length > 1) {\n throw new Error(\n `Explicit encryption key is only supported for single-chunk data (up to ${CHUNK_SIZE} bytes), got ${payloads.length} chunks — pass encryptionKey: true to auto-generate per-chunk keys`,\n )\n }\n\n const chunks: EncryptedChunk[] = []\n const refs: EncryptedChunkRef[] = []\n\n for (const payload of payloads) {\n const chunk = makeEncryptedContentAddressedChunk(payload, encryptionKey)\n chunks.push(chunk)\n refs.push({\n address: chunk.address.toUint8Array(),\n key: chunk.encryptionKey,\n span: BigInt(payload.length),\n })\n }\n\n return { chunks, refs }\n}\n\n// ============================================================================\n// HTTP Transport\n// ============================================================================\n\n/**\n * Upload a single stamped chunk via HTTP (stamper mode).\n */\nasync function uploadStampedChunkViaHttp(\n bee: Bee,\n stamp: StampChunkFn,\n chunkData: Uint8Array,\n address: Uint8Array,\n options: UploadOptions,\n requestOptions?: BeeRequestOptions,\n): Promise<void> {\n const envelope = await stamp(address)\n await bee.chunk.upload(envelope, chunkData, options, requestOptions)\n}\n\n/**\n * The headers `bee.chunk.upload` would send: the `Bee` instance's own, then\n * the call's `requestOptions` on top — for the one hand-rolled `fetch` sent\n * to a `Bee` instance rather than through a bee-js call (#819). bee-js keeps the\n * instance options private, so this reads the field it stores them in.\n */\nfunction beeHeaders(\n bee: Bee,\n requestOptions?: BeeRequestOptions,\n): Record<string, string> {\n const instance = (bee as unknown as { requestOptions?: BeeRequestOptions })\n .requestOptions\n return { ...instance?.headers, ...requestOptions?.headers }\n}\n\n/**\n * Upload a chunk via subsidised gateway (no stamp required).\n */\nasync function uploadChunkViaSubsidisedGatewayInternal(\n gatewayUrl: string,\n chunkData: Uint8Array,\n options?: { deferred?: boolean },\n): Promise<{ reference: string }> {\n const url = `${normalizeUrl(gatewayUrl)}/chunks`\n\n const headers: Record<string, string> = {\n \"content-type\": \"application/octet-stream\",\n }\n\n if (options?.deferred !== undefined) {\n headers[\"swarm-deferred-upload\"] = options.deferred.toString()\n }\n\n const response = await fetch(url, {\n method: \"POST\",\n headers,\n body: chunkData,\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new SwarmIdError(\n \"bee-rejected\",\n `Subsidised chunk upload failed: ${response.status} ${response.statusText} - ${errorText}`,\n {\n status: response.status,\n beeMessage: errorText,\n url: refusedPath(response.url),\n },\n )\n }\n\n const result = await response.json()\n return { reference: result.reference }\n}\n\n/**\n * Upload multiple chunks via HTTP with sliding-window concurrency.\n */\nasync function uploadChunksViaHttp(\n bee: Bee,\n stamp: StampChunkFn,\n chunks: Array<{ data: Uint8Array; address: Uint8Array }>,\n options: UploadOptions,\n requestOptions: BeeRequestOptions | undefined,\n onUploaded: (address: Uint8Array) => void,\n concurrency: number = DEFAULT_UPLOAD_CONCURRENCY,\n): Promise<void> {\n let nextIndex = 0\n\n const worker = async () => {\n while (nextIndex < chunks.length) {\n const chunk = chunks[nextIndex++]\n await uploadStampedChunkViaHttp(\n bee,\n stamp,\n chunk.data,\n chunk.address,\n options,\n requestOptions,\n )\n onUploaded(chunk.address)\n }\n }\n\n await Promise.all(\n Array.from({ length: Math.min(concurrency, chunks.length) }, () =>\n worker(),\n ),\n )\n}\n\n// ============================================================================\n// WebSocket Transport\n// ============================================================================\n\n/**\n * Upload a single stamped chunk via WebSocket.\n */\nasync function uploadStampedChunkViaWebSocket(\n stamp: StampChunkFn,\n stream: ChunkUploadStream,\n chunkData: Uint8Array,\n address: Uint8Array,\n): Promise<void> {\n const envelope = await stamp(address)\n const stamped = Binary.concatBytes(\n convertEnvelopeToMarshaledStamp(envelope).toUint8Array(),\n chunkData,\n )\n await stream.uploadChunk(stamped)\n}\n\n/**\n * Upload multiple chunks via WebSocket with per-chunk stamping.\n */\nasync function uploadChunksViaWebSocket(\n stamp: StampChunkFn,\n stream: ChunkUploadStream,\n chunks: Array<{ data: Uint8Array; address: Uint8Array }>,\n onUploaded: (address: Uint8Array) => void,\n): Promise<void> {\n let nextIndex = 0\n\n const worker = async () => {\n while (nextIndex < chunks.length) {\n const chunk = chunks[nextIndex++]\n await uploadStampedChunkViaWebSocket(\n stamp,\n stream,\n chunk.data,\n chunk.address,\n )\n onUploaded(chunk.address)\n }\n }\n\n const workerCount = stream.getConcurrency()\n await Promise.all(Array.from({ length: workerCount }, () => worker()))\n}\n\n// ============================================================================\n// Merkle Tree Building\n// ============================================================================\n\n/**\n * Build merkle tree from plain chunk references.\n * Returns 32-byte root reference.\n */\nasync function buildPlainMerkleTree(\n chunkRefs: ChunkReference[],\n uploadIntermediate: (chunk: ContentAddressedChunk) => Promise<void>,\n): Promise<Reference> {\n if (chunkRefs.length === 1) {\n return new Reference(chunkRefs[0].address)\n }\n\n const intermediateRefs: ChunkReference[] = []\n\n for (let i = 0; i < chunkRefs.length; i += PLAIN_REFS_PER_CHUNK) {\n const refs = chunkRefs.slice(\n i,\n Math.min(i + PLAIN_REFS_PER_CHUNK, chunkRefs.length),\n )\n\n // Pass through single-ref batches without wrapping in an intermediate\n // (Bee's \"carrier chunk\" rule). A ghost intermediate with span ≤ 4096\n // would be misidentified as a leaf by the download path's span-based\n // leaf check.\n if (refs.length === 1) {\n intermediateRefs.push(refs[0])\n continue\n }\n\n // Build payload with 32-byte references\n const payload = new Uint8Array(refs.length * 32)\n refs.forEach((ref, idx) => {\n payload.set(ref.address, idx * 32)\n })\n\n // An intermediate chunk's span is the total data length covered by its\n // children, not the length of its references payload. The downloader\n // relies on this to tell intermediate chunks from leaves.\n const totalSpan = refs.reduce((sum, ref) => sum + (ref.span ?? 0n), 0n)\n\n const chunk = makeContentAddressedChunk(payload, totalSpan)\n intermediateRefs.push({\n address: chunk.address.toUint8Array(),\n span: totalSpan,\n })\n await uploadIntermediate(chunk)\n }\n\n if (intermediateRefs.length > 1) {\n return buildPlainMerkleTree(intermediateRefs, uploadIntermediate)\n }\n\n return new Reference(intermediateRefs[0].address)\n}\n\n/**\n * Build merkle tree from encrypted chunk references.\n * Returns 64-byte root reference (address + encryption key).\n */\nasync function buildEncryptedMerkleTree(\n refs: EncryptedChunkRef[],\n uploadIntermediate: (chunkData: Uint8Array) => Promise<void>,\n): Promise<Reference> {\n if (refs.length === 1) {\n const ref = new Uint8Array(64)\n ref.set(refs[0].address, 0)\n ref.set(refs[0].key, 32)\n return new Reference(ref)\n }\n\n const intermediateChunks: EncryptedChunkRef[] = []\n\n for (let i = 0; i < refs.length; i += ENCRYPTED_REFS_PER_CHUNK) {\n const batch = refs.slice(\n i,\n Math.min(i + ENCRYPTED_REFS_PER_CHUNK, refs.length),\n )\n\n // Pass through single-ref batches without wrapping in an intermediate.\n // A ghost intermediate with span ≤ 4096 would be misidentified as a leaf\n // by the download path's span-based leaf check.\n if (batch.length === 1) {\n intermediateChunks.push(batch[0])\n continue\n }\n\n // Calculate total span from all children\n const totalSpan = batch.reduce((sum, ref) => sum + ref.span, 0n)\n\n // Build intermediate chunk payload containing all 64-byte references\n // Pad to 4096 bytes with zeros BEFORE encryption\n const payload = new Uint8Array(4096)\n batch.forEach((ref, idx) => {\n payload.set(ref.address, idx * 64)\n payload.set(ref.key, idx * 64 + 32)\n })\n\n // Create chunk with correct span + payload\n const spanBytes = Span.fromBigInt(totalSpan).toUint8Array()\n const chunkData = Binary.concatBytes(spanBytes, payload)\n\n // Encrypt the chunk\n const encrypter = newChunkEncrypter()\n const { key, encryptedSpan, encryptedData } =\n encrypter.encryptChunk(chunkData)\n const encryptedChunkData = Binary.concatBytes(encryptedSpan, encryptedData)\n\n // Calculate address from encrypted chunk\n const address = await calculateChunkAddress(encryptedChunkData)\n\n await uploadIntermediate(encryptedChunkData)\n\n intermediateChunks.push({\n address: address.toUint8Array(),\n key,\n span: totalSpan,\n })\n }\n\n if (intermediateChunks.length > 1) {\n return buildEncryptedMerkleTree(intermediateChunks, uploadIntermediate)\n }\n\n const rootRef = new Uint8Array(64)\n rootRef.set(intermediateChunks[0].address, 0)\n rootRef.set(intermediateChunks[0].key, 32)\n return new Reference(rootRef)\n}\n\n// ============================================================================\n// Public API: uploadData\n// ============================================================================\n\n/**\n * Upload data to Swarm.\n *\n * This is the unified entry point for all data uploads:\n * - Plain or encrypted (via encryptionKey option)\n * - Stamper-based or subsidised gateway (via target)\n * - HTTP or WebSocket transport (via webSocket option)\n *\n * @param target - Upload target (stamper or subsidised gateway)\n * @param data - Data to upload\n * @param options - Upload options\n * @returns Upload result with reference\n */\nexport async function uploadData(\n target: UploadTarget,\n data: Uint8Array,\n options?: UploadDataOptions,\n): Promise<UploadDataResult> {\n const isEncrypted = options?.encryptionKey !== undefined\n\n if (isEncrypted) {\n return uploadDataEncrypted(target, data, options)\n } else {\n return uploadDataPlain(target, data, options)\n }\n}\n\n/**\n * Upload plain (unencrypted) data.\n */\nasync function uploadDataPlain(\n target: UploadTarget,\n data: Uint8Array,\n options?: UploadDataOptions,\n): Promise<UploadDataResult> {\n const chunkPayloads = splitDataIntoChunks(data)\n\n // Create CAC for each payload\n const chunks: ContentAddressedChunk[] = []\n const chunkRefs: ChunkReference[] = []\n\n for (const payload of chunkPayloads) {\n const chunk = makeContentAddressedChunk(payload)\n chunks.push(chunk)\n chunkRefs.push({\n address: chunk.address.toUint8Array(),\n span: BigInt(payload.length),\n })\n }\n\n // Progress tracking\n let totalChunks = chunks.length\n let processedChunks = 0\n const reportProgress = () =>\n options?.onProgress?.({ total: totalChunks, processed: processedChunks })\n\n if (isSubsidisedTarget(target)) {\n // Subsidised gateway mode\n const uploadOptions = { deferred: options?.deferred }\n\n for (const chunk of chunks) {\n await uploadChunkViaSubsidisedGatewayInternal(\n target.gatewayUrl,\n chunk.data,\n uploadOptions,\n )\n processedChunks++\n reportProgress()\n }\n\n // Build merkle tree if multiple chunks\n let rootReference: Reference\n\n if (chunkRefs.length === 1) {\n rootReference = new Reference(chunkRefs[0].address)\n } else {\n rootReference = await buildPlainMerkleTree(chunkRefs, async (chunk) => {\n await uploadChunkViaSubsidisedGatewayInternal(\n target.gatewayUrl,\n chunk.data,\n uploadOptions,\n )\n totalChunks++\n processedChunks++\n reportProgress()\n })\n }\n\n return { reference: rootReference.toHex() }\n } else {\n // Stamper mode\n const { bee, stamper, workerPool } = target\n const tag = options?.tag ?? (await tryCreateTag(bee))\n const uploadOptions = {\n ...options,\n tag,\n deferred: options?.deferred ?? false,\n }\n\n const stamp = makeStampFn(stamper, workerPool)\n\n // Upload leaf chunks\n for (const chunk of chunks) {\n await uploadStampedChunkViaHttp(\n bee,\n stamp,\n chunk.data,\n chunk.address.toUint8Array(),\n uploadOptions,\n options?.requestOptions,\n )\n processedChunks++\n reportProgress()\n }\n\n // Build merkle tree if multiple chunks\n let rootReference: Reference\n\n if (chunkRefs.length === 1) {\n rootReference = new Reference(chunkRefs[0].address)\n } else {\n rootReference = await buildPlainMerkleTree(chunkRefs, async (chunk) => {\n await uploadStampedChunkViaHttp(\n bee,\n stamp,\n chunk.data,\n chunk.address.toUint8Array(),\n uploadOptions,\n options?.requestOptions,\n )\n totalChunks++\n processedChunks++\n reportProgress()\n })\n }\n\n return { reference: rootReference.toHex(), tagUid: tag }\n }\n}\n\n/**\n * Upload encrypted data.\n */\nasync function uploadDataEncrypted(\n target: UploadTarget,\n data: Uint8Array,\n options?: UploadDataOptions,\n): Promise<UploadDataResult> {\n // Determine encryption key\n const encryptionKey =\n options?.encryptionKey === true ? undefined : options?.encryptionKey\n\n // Split and encrypt\n const payloads = splitDataIntoChunks(data)\n const { chunks: encryptedChunks, refs: encryptedChunkRefs } =\n encryptChunkPayloads(payloads, encryptionKey)\n\n // Prepare chunk data for upload\n const chunksToUpload = encryptedChunks.map((chunk) => ({\n data: chunk.data,\n address: chunk.address.toUint8Array(),\n }))\n\n // Progress tracking\n let totalChunks = encryptedChunks.length\n let processedChunks = 0\n const uploadedChunkAddresses: Uint8Array[] = []\n\n const onChunkUploaded = (address: Uint8Array) => {\n uploadedChunkAddresses.push(address)\n processedChunks++\n options?.onProgress?.({ total: totalChunks, processed: processedChunks })\n }\n\n if (isSubsidisedTarget(target)) {\n // Subsidised gateway mode\n const uploadOptions = { deferred: options?.deferred }\n\n for (const chunk of chunksToUpload) {\n await uploadChunkViaSubsidisedGatewayInternal(\n target.gatewayUrl,\n chunk.data,\n uploadOptions,\n )\n onChunkUploaded(chunk.address)\n }\n\n // Build merkle tree\n const rootReference = await buildEncryptedMerkleTree(\n encryptedChunkRefs,\n async (chunkData) => {\n await uploadChunkViaSubsidisedGatewayInternal(\n target.gatewayUrl,\n chunkData,\n uploadOptions,\n )\n const address = await calculateChunkAddress(chunkData)\n uploadedChunkAddresses.push(address.toUint8Array())\n totalChunks++\n processedChunks++\n options?.onProgress?.({\n total: totalChunks,\n processed: processedChunks,\n })\n },\n )\n\n return {\n reference: rootReference.toHex(),\n chunkAddresses: uploadedChunkAddresses,\n }\n } else {\n // Stamper mode\n const { bee, stamper, workerPool } = target\n const tag = options?.tag ?? (await tryCreateTag(bee))\n const uploadOptions = {\n ...options,\n tag,\n deferred: options?.deferred ?? false,\n }\n const stamp = makeStampFn(stamper, workerPool)\n\n // Open WebSocket stream if requested\n let wsStream: ChunkUploadStream | undefined\n if (options?.webSocket) {\n wsStream = new ChunkUploadStream(bee.url, {\n tag,\n concurrency: options.webSocket.concurrency,\n })\n await wsStream.open()\n }\n\n try {\n // Upload chunks\n if (wsStream) {\n await uploadChunksViaWebSocket(\n stamp,\n wsStream,\n chunksToUpload,\n onChunkUploaded,\n )\n } else {\n await uploadChunksViaHttp(\n bee,\n stamp,\n chunksToUpload,\n uploadOptions,\n options?.requestOptions,\n onChunkUploaded,\n options?.httpConcurrency,\n )\n }\n\n // Build merkle tree\n const rootReference = await buildEncryptedMerkleTree(\n encryptedChunkRefs,\n async (chunkData) => {\n const address = await calculateChunkAddress(chunkData)\n uploadedChunkAddresses.push(address.toUint8Array())\n\n if (wsStream) {\n await uploadStampedChunkViaWebSocket(\n stamp,\n wsStream,\n chunkData,\n address.toUint8Array(),\n )\n } else {\n await uploadStampedChunkViaHttp(\n bee,\n stamp,\n chunkData,\n address.toUint8Array(),\n uploadOptions,\n options?.requestOptions,\n )\n }\n\n totalChunks++\n processedChunks++\n options?.onProgress?.({\n total: totalChunks,\n processed: processedChunks,\n })\n },\n )\n\n return {\n reference: rootReference.toHex(),\n tagUid: tag,\n chunkAddresses: uploadedChunkAddresses,\n }\n } finally {\n if (wsStream) {\n await wsStream.close()\n }\n }\n }\n}\n\n// ============================================================================\n// Public API: uploadSOC\n// ============================================================================\n\n/**\n * Upload a Single Owner Chunk (SOC).\n *\n * @param target - Upload target (stamper or subsidised gateway)\n * @param signer - Private key for signing the SOC\n * @param identifier - SOC identifier\n * @param data - Chunk payload (1-4096 bytes)\n * @param options - Upload options\n * @returns SOC address and optional encryption key\n */\nexport async function uploadSOC(\n target: UploadTarget,\n signer: PrivateKey,\n identifier: Identifier,\n data: Uint8Array,\n options?: UploadSOCOptions,\n): Promise<UploadSOCResult> {\n // Validate data size\n if (data.length < 1 || data.length > 4096) {\n throw new SwarmIdError(\n \"invalid-request\",\n `Invalid data length: ${data.length} (expected 1-4096)`,\n )\n }\n\n const isEncrypted = options?.encryptionKey !== undefined\n\n // Build chunk and determine what to sign/send\n let chunkAddress: Uint8Array\n let socBody: Uint8Array\n let encryptionKeyResult: Uint8Array | undefined\n\n if (isEncrypted) {\n // For encrypted SOC: use makeEncryptedContentAddressedChunk\n // This encrypts both span and payload, giving us the correct address for signing\n const encryptionKey =\n options.encryptionKey === true ? undefined : options.encryptionKey\n const encryptedChunk = makeEncryptedContentAddressedChunk(\n data,\n encryptionKey,\n )\n\n chunkAddress = encryptedChunk.address.toUint8Array()\n socBody = encryptedChunk.data\n encryptionKeyResult = encryptedChunk.encryptionKey\n } else {\n // For plain SOC: use unencrypted CAC\n const cac = makeContentAddressedChunk(data)\n chunkAddress = cac.address.toUint8Array()\n socBody = cac.data\n }\n\n const owner = signer.publicKey().address()\n\n // Sign: hash(identifier + chunkAddress)\n // For encrypted SOCs, this is the encrypted chunk address\n // For plain SOCs, this is the unencrypted chunk address\n const toSign = Binary.concatBytes(identifier.toUint8Array(), chunkAddress)\n const signature = signer.sign(toSign)\n\n // Calculate SOC address\n const socAddressBytes = Binary.keccak256(\n Binary.concatBytes(identifier.toUint8Array(), owner.toUint8Array()),\n )\n\n if (isSubsidisedTarget(target)) {\n // Subsidised gateway mode - no stamp needed\n const url = `${normalizeUrl(target.gatewayUrl)}/soc/${owner.toHex()}/${identifier.toHex()}?sig=${signature.toHex()}`\n\n const headers: Record<string, string> = {\n \"content-type\": \"application/octet-stream\",\n }\n\n if (options?.deferred !== undefined) {\n headers[\"swarm-deferred-upload\"] = options.deferred.toString()\n }\n\n const response = await fetch(url, {\n method: \"POST\",\n headers,\n body: socBody,\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new SocUploadError(\n response.status,\n `${response.statusText} (subsidised)`,\n errorText,\n )\n }\n\n return {\n socAddress: socAddressBytes,\n encryptionKey: encryptionKeyResult,\n }\n } else {\n // Stamper mode\n const { bee, stamper } = target\n const tag = options?.tag ?? (await tryCreateTag(bee))\n\n const envelope = stamper.stamp(socAddressBytes)\n\n const stampHex = convertEnvelopeToMarshaledStamp(envelope).toHex()\n const url = `${bee.url}/soc/${owner.toHex()}/${identifier.toHex()}?sig=${signature.toHex()}`\n\n const headers: Record<string, string> = {\n ...beeHeaders(bee, options?.requestOptions),\n \"content-type\": \"application/octet-stream\",\n \"swarm-postage-stamp\": stampHex,\n }\n if (tag) headers[\"swarm-tag\"] = tag.toString()\n if (options?.deferred !== undefined) {\n headers[\"swarm-deferred-upload\"] = options.deferred.toString()\n }\n if (options?.pin !== undefined) {\n headers[\"swarm-pin\"] = options.pin.toString()\n }\n\n const response = await fetch(url, {\n method: \"POST\",\n headers,\n body: socBody,\n signal: options?.requestOptions?.signal,\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new SocUploadError(response.status, response.statusText, errorText)\n }\n\n return {\n socAddress: socAddressBytes,\n encryptionKey: encryptionKeyResult,\n tagUid: tag,\n }\n }\n}\n\n// ============================================================================\n// Public API: uploadChunk\n// ============================================================================\n\n/**\n * Upload a single pre-built chunk.\n *\n * @param target - Upload target (stamper or subsidised gateway)\n * @param chunkData - Full chunk data (span + payload)\n * @param options - Upload options\n * @returns Chunk reference\n */\nexport async function uploadChunk(\n target: UploadTarget,\n chunkData: Uint8Array,\n options?: UploadChunkOptions,\n): Promise<UploadChunkResult> {\n if (isSubsidisedTarget(target)) {\n return uploadChunkViaSubsidisedGatewayInternal(\n target.gatewayUrl,\n chunkData,\n { deferred: options?.deferred },\n )\n } else {\n const { bee, stamper } = target\n const address = await calculateChunkAddress(chunkData)\n const stamp = makeStampFn(stamper)\n\n await uploadStampedChunkViaHttp(\n bee,\n stamp,\n chunkData,\n address.toUint8Array(),\n {\n deferred: options?.deferred ?? false,\n pin: options?.pin,\n tag: options?.tag,\n },\n options?.requestOptions,\n )\n\n return { reference: uint8ArrayToHex(address.toUint8Array()) }\n }\n}\n\n// ============================================================================\n// Internal exports for legacy wrappers\n// ============================================================================\n\n// These are exposed for the legacy wrapper module to use\nexport {\n uploadChunkViaSubsidisedGatewayInternal,\n uploadStampedChunkViaHttp,\n makeStampFn,\n buildPlainMerkleTree,\n buildEncryptedMerkleTree,\n encryptChunkPayloads,\n type EncryptedChunkRef,\n type StampChunkFn,\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Address derivation for the per-partition lock SOC. Lives in `utils/` (not\n * `sync/`) so it can be imported by both `sync/partition-lock.ts` (the lock\n * SOC reader/writer) and `utils/batch-utilization.ts` (the stamper that\n * routes lock-SOC overstamps to the reserved slot) without a circular\n * dependency through `toBucket`.\n */\n\nimport { Identifier, type BatchId, type EthAddress } from \"@ethersphere/bee-js\"\nimport { Binary } from \"cafe-utility\"\n\n/** Domain separation tag for the lock-SOC identifier. */\nconst PARTITION_LOCK_DOMAIN = \"swarm-id-partition-lock-v1\"\n\n/**\n * Build the lock-SOC identifier for one partition of one batch. Universal\n * across accounts — the per-account `owner` carries that separation — so the\n * identifier encodes what remains: which batch, and which of its lanes.\n *\n * The batch is in here because it is in everything the lock protects. A lane's\n * slot space is computed WITHIN a batch (`dataSlot`), and the state riding it\n * is published under `makePartitionStateTopic(batchId, partition)`. Keying the\n * lock on the partition alone made lane p of batch X and lane p of batch Y one\n * chunk address, so two coordinators that share no slot took turns anyway —\n * and per-app stamp overrides put two live coordinators on two batches without\n * any multi-batch feature (#589).\n */\nexport function makePartitionLockIdentifier(\n batchId: BatchId,\n partition: number,\n): Identifier {\n const hash = Binary.keccak256(\n new TextEncoder().encode(\n `${PARTITION_LOCK_DOMAIN}:${batchId.toHex()}:${partition}`,\n ),\n )\n return new Identifier(hash)\n}\n\n/**\n * 32-byte SOC chunk address for the (batch, partition, owner) lock SOC. Used\n * by the stamper to detect \"this stamp is the lock SOC's overstamp\" and route\n * it to the reserved slot.\n */\nexport function lockSocAddress(\n batchId: BatchId,\n partition: number,\n owner: EthAddress,\n): Uint8Array {\n const identifier = makePartitionLockIdentifier(batchId, partition)\n return Binary.keccak256(\n Binary.concatBytes(identifier.toUint8Array(), owner.toUint8Array()),\n )\n}\n\n/**\n * Bucket index that the partition-lock SOC for (batch, partition, owner) lands\n * in. Lock-SOC writes overstamp this single fixed bucket; the partition stamper\n * reserves the bucket from data uploads so the tight heartbeat cadence\n * doesn't exhaust the bucket's slot budget.\n */\nexport function lockSocBucket(\n batchId: BatchId,\n partition: number,\n owner: EthAddress,\n): number {\n const addr = lockSocAddress(batchId, partition, owner)\n // Inline `toBucket` (first 2 bytes as big-endian uint16) to avoid a\n // circular import from `batch-utilization.ts`.\n return (addr[0] << 8) | addr[1]\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Cross-module timing constants for the multi-device sync path.\n *\n * This module has NO imports on purpose: it is the single source of truth for\n * values several sync modules share, so it can be imported by any of them\n * (including `utils/batch-utilization`) without a circular import. Keeping the\n * values here removes the drift risk of the same literal defined independently\n * in each module.\n */\n\n/**\n * Client-side timeout for a single cross-device SOC/feed read (roster entry,\n * partition-intent, lock SOC, state-pointer). Bee has no fast authoritative\n * \"not found\" — a retrieval of an absent chunk fails only after exhausting\n * peers — so an absent target would otherwise block for tens of seconds. A\n * present chunk in its neighborhood retrieves well under this; a timed-out read\n * is treated as inconclusive (see each caller's sentinel handling).\n */\nexport const SYNC_READ_TIMEOUT_MS = 2500\n\n/**\n * Partition-lease lifetime. A device holds its partition for this long before\n * the lease must be refreshed; if it crashes without refreshing, peers can\n * reclaim the partition.\n */\nexport const LEASE_TTL_MS = 30 * 1000 // 30 seconds\n\n/**\n * Epoch length for the rotating intent/occupancy/state-pointer addresses.\n * TTL-sized (`= LEASE_TTL_MS`) so a contention round and its immediate retries\n * share a bucket (and the previous bucket covers the boundary), while every\n * fresh round rotates to an address no node has cached. The two genuinely track\n * each other — sharing this source keeps them from drifting.\n */\nexport const INTENT_EPOCH_MS = LEASE_TTL_MS\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Batch Utilization Tracking for Swarm Storage\n *\n * Tracks slot usage for mutable postage batches. Each of the 65,536 buckets\n * has a uint32 in-memory counter (`dataCounters[bucket]`) representing the\n * next free slot. The counter is initialised at `DATA_COUNTER_START` so the\n * first slots of every bucket are reserved as headroom for utilisation\n * chunks that may incidentally land there — utilisation and data chunks\n * share the same slot space.\n *\n * Per-chunk encryption keys are derived deterministically from the account's\n * `swarmEncryptionKey` plus the chunk index and a small nonce. The nonce is\n * bumped only when an upload would land in the same bucket as a lower-index\n * chunk in the same save, so the on-chain placement of utilisation chunks is\n * tidy and dedup stays stable across re-saves.\n */\n\nimport {\n type Stamper,\n BatchId,\n type Bee,\n EthAddress,\n PrivateKey,\n type EnvelopeWithBatchId,\n} from \"@ethersphere/bee-js\"\nimport {\n makeEncryptedContentAddressedChunk,\n type ContentAddressedChunk,\n} from \"../chunk\"\nimport { stamp as stampAtSlot } from \"@ethersphere/core-sdk\"\nimport { Binary } from \"cafe-utility\"\nimport type { UtilizationStoreDB } from \"../storage/utilization-store\"\nimport { uploadChunk, type UploadTarget } from \"../proxy/upload\"\nimport { tryCreateTag } from \"./tag\"\nimport { lockSocAddress } from \"./lock-soc\"\nimport { deriveSecret, BACKUP_KEY_LABEL } from \"./key-derivation\"\nimport { uint8ArrayToHex } from \"./hex\"\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Thrown by `UtilizationAwareStamper.stamp()` when the partition lease was\n * invalidated (peer took over the partition) between binding and stamping.\n * Callers should surface this as \"your partition was reclaimed\" — the\n * upload cannot complete and any in-flight chunks would silently overwrite\n * the peer's data if stamped anyway.\n */\nexport class PartitionLeaseLostError extends Error {\n constructor(message = \"Partition lease was reclaimed by another device.\") {\n super(message)\n this.name = \"PartitionLeaseLostError\"\n }\n}\n\n/**\n * Thrown by `UtilizationAwareStamper.stampDataChunk()` when the address is one\n * the stamper routes to a reserved slot: a lock SOC, a counter or state chunk,\n * or an intent SOC. Those slots hold the account's own bookkeeping, so their\n * stamp is never handed to an app.\n */\nexport class ReservedSlotError extends Error {\n constructor(\n message = \"This chunk address belongs to the account's own bookkeeping and cannot be stamped for an app.\",\n ) {\n super(message)\n this.name = \"ReservedSlotError\"\n }\n}\n\n/**\n * Thrown by `UtilizationAwareStamper.stampDataChunk()` when the chunk's bucket\n * has no slot left in the app slice: the rest of that bucket's lane is kept\n * for the account's own uploads.\n */\nexport class AppSliceFullError extends Error {\n constructor(\n message = \"This bucket's share for app stamps is used up. The rest of it is kept for the account's own uploads.\",\n ) {\n super(message)\n this.name = \"AppSliceFullError\"\n }\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Number of buckets in a postage batch (2^16) */\nexport const NUM_BUCKETS = 65536\n\n/** Bucket depth parameter (determines bucket count) */\nexport const BUCKET_DEPTH = 16\n\n/**\n * Number of partitions the per-bucket slot space is divided into for\n * multi-device sharing of a single postage batch. With K=2, partition 0\n * uses data slots `{DATA_COUNTER_START + 0, DATA_COUNTER_START + 2, …}` and\n * partition 1 uses `{DATA_COUNTER_START + 1, DATA_COUNTER_START + 3, …}`.\n * See docs/Postage-Batch-Partitioning.md.\n */\nexport const PARTITION_COUNT = 2\n\n/**\n * Share of each bucket's data lane, counted from its top, that only the\n * account's own uploads may use. An app stamp (`stampDataChunk`) is refused\n * once the lane's counter reaches the rest, the app slice. Apps and the\n * account draw from the one per-partition counter, so no slot is ever issued\n * twice, and the slice needs no state of its own: it is read from the counter\n * that is saved, published and handed over already.\n */\nexport const ACCOUNT_RESERVED_LANE_SHARE = 1 / 2\n\n/** First data slot index; reserved slots `[0, PARTITION_COUNT)` precede it. */\nexport const DATA_COUNTER_START = PARTITION_COUNT\n\n// Partition-lease lifetime — single source in `sync/timing-constants` (shared\n// with `partition-intent`'s epoch, which tracks it). Re-exported here so the\n// many `import { LEASE_TTL_MS } from \"../utils/batch-utilization\"` callers are\n// unchanged.\nexport { LEASE_TTL_MS } from \"../sync/timing-constants\"\n\n/**\n * How often the holder re-writes its partition lock SOC to bump\n * `leasedUntil`. One third of the TTL gives three \"safety net\" refresh\n * attempts before a peer would consider the lease expired.\n */\nexport const LEASE_REFRESH_MS = 10 * 1000 // 10 seconds\n\n/**\n * Local-clock margin for treating a lease as \"could have lapsed\". A holder must\n * stop writing (and re-validating before ack) once `now >= leasedUntil - margin`\n * so it ceases before a peer — reading the same `leasedUntil` from the lock SOC\n * — could validly take over. Bounds the write/ack exposure to NTP-level skew,\n * the same assumption the lock protocol already makes. Consumed by the stamper's\n * local-lapse fence and the coordinator's reverse-clobber guard.\n */\nexport const LEASE_SKEW_MARGIN_MS = 2000\n\n/**\n * How long a holder may go without an upload before it voluntarily yields\n * its partition (writes the release sentinel + publishes its final counter)\n * so a waiting device can take the slot without waiting out the full TTL.\n * The yielded device re-acquires transparently on its next upload. Enables\n * turn-taking among 3+ devices sharing `PARTITION_COUNT` slots.\n */\nexport const IDLE_YIELD_MS = 30 * 1000 // 30 seconds\n\n/** Size of each chunk in bytes */\nexport const CHUNK_SIZE = 4096\n\n/**\n * Maximum batch depth at which a uint16 per-bucket counter is sufficient.\n *\n * At depth D the post-stamp counter caps at 2^(D-16). uint16 can hold\n * 0..65535; depth 31 → 32768 (fits), depth 32 → 65536 (overflows).\n * For depth > UINT16_COUNTER_MAX_DEPTH the serializer falls back to uint32.\n *\n * Issue: https://github.com/snaha/swarm-id/issues/243\n */\nexport const UINT16_COUNTER_MAX_DEPTH = 31\n\n/** uint16 serialization size in bytes */\nconst COUNTER_BYTES_UINT16 = 2\n\n/** uint32 serialization size in bytes */\nconst COUNTER_BYTES_UINT32 = 4\n\n/** Largest value representable in an unsigned 16-bit integer */\nconst UINT16_MAX = 0xffff\n\n/**\n * Per-batch-depth chunk layout for the utilization state.\n *\n * The on-chunk counter representation switches between uint16 and uint32\n * based on whether the maximum slot index for the given batch depth fits\n * in 16 bits. Each utilization chunk is always CHUNK_SIZE bytes; the\n * number of buckets it covers — and thus the number of chunks needed —\n * doubles when counters shrink from 4 bytes to 2.\n */\nexport interface ChunkLayout {\n /** Bytes per per-bucket counter on disk / on the wire */\n counterByteSize: 2 | 4\n /** Number of buckets packed into a single CHUNK_SIZE-byte chunk */\n bucketsPerChunk: number\n /** Total number of utilization chunks for one batch */\n numUtilizationChunks: number\n}\n\n/**\n * Pick the on-disk chunk layout for a given batch depth.\n */\nexport function getChunkLayout(batchDepth: number): ChunkLayout {\n const counterByteSize =\n batchDepth <= UINT16_COUNTER_MAX_DEPTH\n ? COUNTER_BYTES_UINT16\n : COUNTER_BYTES_UINT32\n const bucketsPerChunk = CHUNK_SIZE / counterByteSize\n const numUtilizationChunks = NUM_BUCKETS / bucketsPerChunk\n return { counterByteSize, bucketsPerChunk, numUtilizationChunks }\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Metadata for a single utilization chunk\n */\nexport interface ChunkMetadata {\n /** Chunk index within the depth-dependent layout */\n index: number\n\n /**\n * Content hash / CAC reference (same thing for content-addressed chunks)\n * Empty string means never uploaded\n */\n contentHash: string\n\n /** Last upload timestamp */\n lastUpload: number\n\n /** Whether this chunk needs uploading */\n dirty: boolean\n\n /**\n * Encryption-key nonce that produced this chunk's `contentHash`. Used as\n * the starting point for the next save's bucket-collision search so\n * unchanged plaintexts reuse the same key (and skip re-upload).\n */\n nonce: number\n}\n\n/**\n * Utilization state for a postage batch\n *\n * Utilization data is stored as 32 chunks (uint16 codec, depth ≤ 31) or\n * 64 chunks (uint32 codec, depth ≥ 32) on Swarm, with IndexedDB caching\n * for performance.\n */\nexport interface BatchUtilizationState {\n /** Batch ID this state belongs to */\n batchId: BatchId\n\n /** Batch depth — determines on-disk chunk layout (uint16 vs uint32) */\n batchDepth: number\n\n /**\n * Per-partition local counter (65,536 entries) — the 0-based `j` in\n * `slot = partitionCount + partition + partitionCount·j` for the partition\n * this state represents (partition 0 in single-device mode). This is the\n * single counter of record: slot selection, persistence, reporting, and\n * cross-device handoff all derive from it. Always uint32 in memory\n * regardless of the on-disk codec; narrowing happens at the serialize\n * boundary.\n */\n dataCounters: Uint32Array // [65536]\n\n /** Metadata for each utilization chunk (32 for uint16, 64 for uint32) */\n chunks: ChunkMetadata[]\n\n /** Last sync timestamp */\n lastSync: number\n}\n\n// ============================================================================\n// Bucket Mapping\n// ============================================================================\n\n/**\n * Calculate which bucket a chunk belongs to based on its address.\n * Uses the first 2 bytes of the chunk address as a big-endian uint16.\n *\n * This matches bee-js Stamper implementation.\n *\n * @param chunkAddress - The chunk's content address (32 bytes)\n * @returns Bucket index (0-65535)\n */\nexport function toBucket(chunkAddress: Uint8Array): number {\n if (chunkAddress.length < 2) {\n throw new Error(\"Chunk address must be at least 2 bytes\")\n }\n\n // First 2 bytes as big-endian uint16\n return (chunkAddress[0] << 8) | chunkAddress[1]\n}\n\n// ============================================================================\n// Chunk Mapping for Swarm Storage\n// ============================================================================\n\n/**\n * Calculate which utilization chunk a bucket belongs to.\n *\n * @param bucketIndex - Bucket index (0-65535)\n * @param batchDepth - Batch depth, drives the chunk layout\n * @returns Chunk index in the layout for this depth\n */\nexport function getChunkIndexForBucket(\n bucketIndex: number,\n batchDepth: number,\n): number {\n if (bucketIndex < 0 || bucketIndex >= NUM_BUCKETS) {\n throw new Error(\n `Invalid bucket index: ${bucketIndex} (must be 0-${NUM_BUCKETS - 1})`,\n )\n }\n const { bucketsPerChunk } = getChunkLayout(batchDepth)\n return Math.floor(bucketIndex / bucketsPerChunk)\n}\n\n/**\n * Extract a CHUNK_SIZE-byte chunk from the dataCounters array using the\n * codec implied by the batch depth.\n *\n * @param dataCounters - Full array of 65,536 counters (kept as Uint32Array\n * in memory regardless of serialization size)\n * @param chunkIndex - Index of chunk to extract\n * @param batchDepth - Batch depth, drives the chunk layout\n * @returns CHUNK_SIZE byte Uint8Array containing serialized counters\n */\nexport function extractChunk(\n dataCounters: Uint32Array,\n chunkIndex: number,\n batchDepth: number,\n): Uint8Array {\n const { counterByteSize, bucketsPerChunk, numUtilizationChunks } =\n getChunkLayout(batchDepth)\n\n if (chunkIndex < 0 || chunkIndex >= numUtilizationChunks) {\n throw new Error(\n `Invalid chunk index: ${chunkIndex} (must be 0-${numUtilizationChunks - 1})`,\n )\n }\n\n const startBucket = chunkIndex * bucketsPerChunk\n const endBucket = startBucket + bucketsPerChunk\n const chunkCounters = dataCounters.slice(startBucket, endBucket)\n\n return counterByteSize === COUNTER_BYTES_UINT16\n ? serializeUint16Array(chunkCounters)\n : serializeUint32Array(chunkCounters)\n}\n\n/**\n * Merge a CHUNK_SIZE-byte chunk back into the dataCounters array using the\n * codec implied by the batch depth.\n *\n * @param dataCounters - Full array of 65,536 counters (modified in place)\n * @param chunkIndex - Index of chunk to merge\n * @param chunkData - CHUNK_SIZE byte Uint8Array containing serialized counters\n * @param batchDepth - Batch depth, drives the chunk layout\n */\nexport function mergeChunk(\n dataCounters: Uint32Array,\n chunkIndex: number,\n chunkData: Uint8Array,\n batchDepth: number,\n): void {\n const { counterByteSize, bucketsPerChunk, numUtilizationChunks } =\n getChunkLayout(batchDepth)\n\n if (chunkIndex < 0 || chunkIndex >= numUtilizationChunks) {\n throw new Error(\n `Invalid chunk index: ${chunkIndex} (must be 0-${numUtilizationChunks - 1})`,\n )\n }\n\n if (chunkData.length !== CHUNK_SIZE) {\n throw new Error(\n `Invalid chunk data length: ${chunkData.length} (expected ${CHUNK_SIZE})`,\n )\n }\n\n const chunkCounters =\n counterByteSize === COUNTER_BYTES_UINT16\n ? deserializeUint16Array(chunkData)\n : deserializeUint32Array(chunkData)\n\n if (chunkCounters.length !== bucketsPerChunk) {\n throw new Error(\n `Invalid chunk counters length: ${chunkCounters.length} (expected ${bucketsPerChunk})`,\n )\n }\n\n const startBucket = chunkIndex * bucketsPerChunk\n dataCounters.set(chunkCounters, startBucket)\n}\n\n// ============================================================================\n// Dirty Chunk Tracking\n// ============================================================================\n\n/**\n * Tracks which utilization chunks have been modified and need uploading\n */\nexport class DirtyChunkTracker {\n private dirtyChunks: Set<number>\n private batchDepth: number\n\n constructor(batchDepth: number) {\n this.dirtyChunks = new Set()\n this.batchDepth = batchDepth\n }\n\n /**\n * Mark a bucket as dirty (marks its containing chunk)\n * @param bucketIndex - Bucket index (0-65535)\n */\n markDirty(bucketIndex: number): void {\n const chunkIndex = getChunkIndexForBucket(bucketIndex, this.batchDepth)\n this.dirtyChunks.add(chunkIndex)\n }\n\n /**\n * Mark a chunk as clean (uploaded successfully)\n * @param chunkIndex - Chunk index\n */\n markClean(chunkIndex: number): void {\n this.dirtyChunks.delete(chunkIndex)\n }\n\n /**\n * Get array of dirty chunk indices\n * @returns Sorted array of chunk indices that need uploading\n */\n getDirtyChunks(): number[] {\n return Array.from(this.dirtyChunks).sort((a, b) => a - b)\n }\n\n /**\n * Check if there are any dirty chunks\n * @returns true if there are chunks waiting to be uploaded\n */\n hasDirtyChunks(): boolean {\n return this.dirtyChunks.size > 0\n }\n\n /**\n * Clear all dirty markers\n */\n clear(): void {\n this.dirtyChunks.clear()\n }\n\n /**\n * Get number of dirty chunks\n * @returns Count of chunks waiting to be uploaded\n */\n get count(): number {\n return this.dirtyChunks.size\n }\n}\n\n// ============================================================================\n// Per-chunk Encryption Key Derivation\n// ============================================================================\n\n/** Domain-separation tag for utilisation-chunk key derivation. */\nconst UTIL_CHUNK_KEY_DOMAIN = \"swarm-id-util-chunk-v1\"\n\n/** Length of the SHA-256 HMAC output in bytes (also the chunk key length). */\nconst KEY_LENGTH = 32\n\n/** Bytes to encode `chunkIndex` and `nonce` (each big-endian uint32). */\nconst UINT32_BYTES = 4\n\n/**\n * Resolved per-chunk-index encryption material.\n *\n * `nonce` is the smallest non-negative integer that placed the chunk in a\n * bucket distinct from every chunk with a lower `chunkIndex` during the\n * current save. `key` is the HMAC-derived 32-byte key used to produce the\n * encrypted-CAC address (and therefore the chunk's bucket).\n */\nexport interface UtilizationChunkKey {\n key: Uint8Array\n nonce: number\n}\n\n/**\n * Derive the per-chunk encryption key used for utilisation chunks.\n *\n * `chunkKey = HMAC-SHA256(swarmEncryptionKey, \"swarm-id-util-chunk-v1\" || batchId || chunkIndex || nonce)`\n *\n * Same inputs → same key, so dedup at the upload path stays stable across\n * re-saves whenever neither the plaintext nor the chosen nonce changes.\n */\nexport async function deriveUtilizationChunkKey(\n swarmEncryptionKey: Uint8Array,\n batchId: BatchId,\n chunkIndex: number,\n nonce: number,\n): Promise<Uint8Array> {\n if (swarmEncryptionKey.length !== KEY_LENGTH) {\n throw new Error(\n `Invalid swarmEncryptionKey length: ${swarmEncryptionKey.length} (expected ${KEY_LENGTH})`,\n )\n }\n\n const domain = new TextEncoder().encode(UTIL_CHUNK_KEY_DOMAIN)\n const batchIdBytes = batchId.toUint8Array()\n const indexBytes = new Uint8Array(UINT32_BYTES)\n new DataView(indexBytes.buffer).setUint32(0, chunkIndex, false)\n const nonceBytes = new Uint8Array(UINT32_BYTES)\n new DataView(nonceBytes.buffer).setUint32(0, nonce, false)\n const message = Binary.concatBytes(\n domain,\n batchIdBytes,\n indexBytes,\n nonceBytes,\n )\n\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n swarmEncryptionKey,\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n )\n const signature = await crypto.subtle.sign(\"HMAC\", cryptoKey, message)\n return new Uint8Array(signature)\n}\n\n/**\n * Resolve the encryption key for a single utilisation chunk so it lands in a\n * bucket distinct from every other chunk in the same save, against a set of\n * already-claimed buckets. Mutates `claimedBuckets` to add the chosen one.\n *\n * Stability rule (matters for upload dedup): callers process chunks in\n * ascending `chunkIndex` order, and the first chunk to claim a bucket keeps\n * it. Each chunk starts its search from its `priorNonce`, so unchanged\n * plaintexts whose previously-chosen nonce still produces a distinct bucket\n * reuse the same key (and therefore the same CAC address) across saves —\n * dedup at `saveUtilizationState` then skips the upload.\n */\nasync function chooseUtilizationChunkKey(args: {\n swarmEncryptionKey: Uint8Array\n batchId: BatchId\n chunkIndex: number\n plaintext: Uint8Array\n priorNonce: number\n claimedBuckets: Set<number>\n}): Promise<{\n key: Uint8Array\n nonce: number\n bucket: number\n address: Uint8Array\n}> {\n const { swarmEncryptionKey, batchId, chunkIndex, plaintext, claimedBuckets } =\n args\n let nonce = args.priorNonce\n while (true) {\n const key = await deriveUtilizationChunkKey(\n swarmEncryptionKey,\n batchId,\n chunkIndex,\n nonce,\n )\n const encrypted = makeEncryptedContentAddressedChunk(plaintext, key)\n const address = encrypted.address.toUint8Array()\n const bucket = toBucket(address)\n if (!claimedBuckets.has(bucket)) {\n claimedBuckets.add(bucket)\n return { key, nonce, bucket, address }\n }\n nonce++\n }\n}\n\n/**\n * Collect the set of buckets currently claimed by clean utilisation chunks\n * (those whose `contentHash` already reflects the upload). Dirty chunks\n * must steer clear of these buckets when picking a key.\n */\nfunction claimedBucketsForCleanChunks(\n state: BatchUtilizationState,\n): Set<number> {\n const claimed = new Set<number>()\n for (const meta of state.chunks) {\n if (!meta.dirty && meta.contentHash) {\n claimed.add(toBucket(Binary.hexToUint8Array(meta.contentHash)))\n }\n }\n return claimed\n}\n\n// ============================================================================\n// Chunk Upload/Download for Swarm Storage\n// ============================================================================\n\n/**\n * Upload an encrypted utilization chunk to Swarm as CAC\n *\n * Architecture: Just upload encrypted chunk data as CAC (immutable)\n *\n * @param bee - Bee client instance\n * @param stamper - Stamper for signing\n * @param data - Chunk data to upload (4KB)\n * @param encryptionKey - Encryption key (32 bytes)\n * @returns CAC reference\n */\nexport async function uploadUtilizationChunk(\n bee: Bee,\n stamper: Stamper,\n data: Uint8Array,\n encryptionKey: Uint8Array,\n): Promise<Uint8Array> {\n // Validate inputs\n if (data.length < 1 || data.length > 4096) {\n throw new Error(`Invalid data length: ${data.length} (expected 1-4096)`)\n }\n if (encryptionKey.length !== KEY_LENGTH) {\n throw new Error(\n `Invalid encryption key length: ${encryptionKey.length} (expected ${KEY_LENGTH})`,\n )\n }\n\n // Calculate CAC reference first (before upload)\n const encryptedChunk = makeEncryptedContentAddressedChunk(data, encryptionKey)\n const cacReference = encryptedChunk.address.toUint8Array()\n\n const tag = await tryCreateTag(bee)\n\n const target: UploadTarget = {\n mode: \"stamper\",\n bee,\n stamper,\n }\n\n // Upload the exact encrypted bytes the reference was computed from —\n // re-encrypting would randomize the padding of sub-4096 plaintexts and\n // change the address (deferred: false for fast return)\n await uploadChunk(target, encryptedChunk.data, {\n deferred: false,\n tag,\n })\n\n return cacReference\n}\n\n// ============================================================================\n// Serialization\n// ============================================================================\n\n/**\n * Serialize Uint32Array to bytes (little-endian)\n */\nexport function serializeUint32Array(arr: Uint32Array): Uint8Array {\n const buffer = new ArrayBuffer(arr.length * 4)\n const view = new DataView(buffer)\n\n for (let i = 0; i < arr.length; i++) {\n view.setUint32(i * 4, arr[i], true) // true = little-endian\n }\n\n return new Uint8Array(buffer)\n}\n\n/**\n * Deserialize bytes to Uint32Array (little-endian)\n */\nexport function deserializeUint32Array(bytes: Uint8Array): Uint32Array {\n if (bytes.length % 4 !== 0) {\n throw new Error(\"Byte array length must be a multiple of 4\")\n }\n\n const arr = new Uint32Array(bytes.length / 4)\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)\n\n for (let i = 0; i < arr.length; i++) {\n arr[i] = view.getUint32(i * 4, true) // true = little-endian\n }\n\n return arr\n}\n\n/**\n * Serialize a Uint32Array to bytes as little-endian uint16 values.\n *\n * Counters exceeding uint16 throw — callers must use the uint32 codec\n * (i.e. batchDepth >= UINT16_COUNTER_MAX_DEPTH + 1) for those depths.\n */\nexport function serializeUint16Array(arr: Uint32Array): Uint8Array {\n const buffer = new ArrayBuffer(arr.length * COUNTER_BYTES_UINT16)\n const view = new DataView(buffer)\n\n for (let i = 0; i < arr.length; i++) {\n const v = arr[i]\n if (v > UINT16_MAX) {\n throw new Error(\n `Counter at index ${i} (${v}) exceeds uint16 range; use uint32 codec`,\n )\n }\n view.setUint16(i * COUNTER_BYTES_UINT16, v, true) // true = little-endian\n }\n\n return new Uint8Array(buffer)\n}\n\n/**\n * Deserialize little-endian uint16 bytes back into a Uint32Array (the\n * in-memory representation stays uint32 even when the on-disk codec is\n * narrower).\n */\nexport function deserializeUint16Array(bytes: Uint8Array): Uint32Array {\n if (bytes.length % COUNTER_BYTES_UINT16 !== 0) {\n throw new Error(\"Byte array length must be a multiple of 2\")\n }\n\n const arr = new Uint32Array(bytes.length / COUNTER_BYTES_UINT16)\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)\n\n for (let i = 0; i < arr.length; i++) {\n arr[i] = view.getUint16(i * COUNTER_BYTES_UINT16, true) // true = little-endian\n }\n\n return arr\n}\n\n// ============================================================================\n// Utilization State Management\n// ============================================================================\n\n/**\n * Initialize a new batch utilization state\n *\n * Seeds `dataCounters[bucket]` at `DATA_COUNTER_START` for every bucket so\n * the first stamping operations naturally skip the headroom slots.\n *\n * With 65,536 buckets and ~32–64 utilization chunks per save, the\n * probability of any bucket getting 3+ utilisation chunks in one save is\n * < 10⁻⁷, so a headroom of 2 leaves ample margin.\n */\nexport function initializeBatchUtilization(\n batchId: BatchId,\n batchDepth: number,\n): BatchUtilizationState {\n // Per-partition local counter `j`, 0-based. Slot = partitionCount +\n // partition + partitionCount·j, so j=0 maps to the first data slot above\n // the reserved range; no headroom offset is baked into the counter itself.\n const dataCounters = new Uint32Array(NUM_BUCKETS)\n\n const { numUtilizationChunks } = getChunkLayout(batchDepth)\n\n // Initialize metadata for all utilization chunks\n const chunks: ChunkMetadata[] = []\n for (let i = 0; i < numUtilizationChunks; i++) {\n chunks.push({\n index: i,\n contentHash: \"\", // Will be set on first upload\n lastUpload: 0, // Never uploaded\n dirty: true, // Mark as dirty for initial upload\n nonce: 0,\n })\n }\n\n return {\n batchId,\n batchDepth,\n dataCounters,\n chunks,\n lastSync: Date.now(),\n }\n}\n\n/**\n * Calculate max slots per bucket based on batch depth\n */\nexport function calculateMaxSlotsPerBucket(batchDepth: number): number {\n return Math.pow(2, batchDepth - BUCKET_DEPTH)\n}\n\n/**\n * Check if a bucket has capacity for more chunks.\n *\n * `dataCounter` is the per-partition `j`, so it must be compared against the\n * partition's lane capacity — not the raw slots-per-bucket. Comparing against\n * the latter let `j` run past the lane and only surfaced as a mid-stamp bee-js\n * throw with counters already incremented (#416).\n */\nexport function hasBucketCapacity(\n dataCounter: number,\n batchDepth: number,\n partitionCount: number = PARTITION_COUNT,\n): boolean {\n return dataCounter < partitionCapacity(batchDepth, partitionCount)\n}\n\n/**\n * Physical slot index for partition `partition`'s `j`-th data chunk in a\n * bucket: `partitionCount + partition + partitionCount·j`. The first\n * `partitionCount` slots `[0, partitionCount)` are reserved (one per\n * partition, index = partition) for that partition's lock SOC / counter\n * chunk, so data starts at `partitionCount` and the partitions' data lanes\n * interleave without ever colliding. Legacy single-device is `partitionCount\n * = 1, partition = 0` (reserved slot 0, data from slot 1).\n * See docs/Postage-Batch-Partitioning.md.\n */\nexport function dataSlot(\n partition: number,\n j: number,\n partitionCount: number,\n): number {\n return partitionCount + partition + partitionCount * j\n}\n\n/**\n * Number of data chunks a single partition may write into one bucket, i.e.\n * the count of distinct `j` values: `floor(slotsPerBucket / partitionCount)\n * - 1` (the `-1` is the partition's reserved slot). Each partition's data\n * lane is independent, so this is the per-partition per-bucket capacity.\n */\nexport function partitionCapacity(\n batchDepth: number,\n partitionCount: number,\n): number {\n const slotsPerBucket = calculateMaxSlotsPerBucket(batchDepth)\n return Math.floor(slotsPerBucket / partitionCount) - 1\n}\n\n/**\n * Fewest data chunks a partition's bucket lane must hold for a batch to be\n * worth buying. At 1 the first chunk written to a bucket already fills that\n * lane, so the batch reports 100% used while effectively empty (#538); at 3\n * some bucket fills after ~1 MB of uniformly placed chunks, a drive too small\n * to be worth its purchase (#566). 7 (depth 20) holds ~66 MB.\n */\nexport const MIN_USABLE_PARTITION_CAPACITY = 4\n\n/**\n * Smallest batch depth with a usable data lane: the first depth whose\n * {@link partitionCapacity} reaches {@link MIN_USABLE_PARTITION_CAPACITY}.\n * Searched rather than written out, so it follows BUCKET_DEPTH,\n * PARTITION_COUNT and the reserved-slot term; capacity doubles with every\n * depth, so the search terminates. With BUCKET_DEPTH 16 and PARTITION_COUNT 2\n * it is 20 — depth 17 has no data slot at all, 18 exactly one, 19 three.\n */\nexport const MIN_USABLE_BATCH_DEPTH = ((): number => {\n let batchDepth = BUCKET_DEPTH\n while (\n partitionCapacity(batchDepth, PARTITION_COUNT) <\n MIN_USABLE_PARTITION_CAPACITY\n ) {\n batchDepth += 1\n }\n return batchDepth\n})()\n\n// ============================================================================\n// Storage Operations (Async with Cache Hierarchy)\n// ============================================================================\n\n/**\n * Load utilization state with cache hierarchy\n *\n * Load order:\n * 1. Try IndexedDB cache (all chunks for the depth's layout)\n * 2. If a bucket has no cached chunk, seed it with a zeroed default\n * 3. If nothing is cached, initialize new state\n *\n * The on-Swarm counter is resumed via the partition-state feed (see\n * `sync/partition-state.ts`), not here — this only reads the local cache.\n *\n * @param batchId - Batch ID\n * @param options - Load options (local IndexedDB cache)\n * @returns Utilization state\n */\nexport async function loadUtilizationState(\n batchId: BatchId,\n batchDepth: number,\n options: {\n cache: UtilizationStoreDB\n },\n): Promise<BatchUtilizationState> {\n const { cache } = options\n\n const { counterByteSize, bucketsPerChunk, numUtilizationChunks } =\n getChunkLayout(batchDepth)\n\n // Step 1: Try loading from IndexedDB cache\n const cachedChunks = await cache.getAllChunks(batchId.toHex())\n\n // Step 2: If we have all chunks in cache, reconstruct state\n if (cachedChunks.length === numUtilizationChunks) {\n try {\n const dataCounters = new Uint32Array(NUM_BUCKETS)\n const chunks: ChunkMetadata[] = []\n\n // Reconstruct dataCounters from cached chunks\n for (const cached of cachedChunks) {\n mergeChunk(dataCounters, cached.chunkIndex, cached.data, batchDepth)\n\n chunks.push({\n index: cached.chunkIndex,\n contentHash: cached.contentHash,\n lastUpload: cached.lastAccess, // Use lastAccess as lastUpload\n dirty: false, // Not dirty if loaded from cache\n nonce: cached.nonce,\n })\n }\n\n return {\n batchId,\n batchDepth,\n dataCounters,\n chunks,\n lastSync: Date.now(),\n }\n } catch (error) {\n console.warn(`[BatchUtil] Failed to reconstruct from cache:`, error)\n // Fall through to per-chunk seeding below.\n }\n }\n\n // Step 2: Seed any bucket without a cached chunk with a zeroed default.\n\n const dataCounters = new Uint32Array(NUM_BUCKETS)\n const chunks: ChunkMetadata[] = []\n\n // Seed a default chunk's worth of counters once; reused via mergeChunk.\n // The per-partition counter is 0-based `j`, so a never-written bucket\n // starts at 0.\n const defaultCounters = new Uint32Array(bucketsPerChunk)\n const defaultChunkData =\n counterByteSize === COUNTER_BYTES_UINT16\n ? serializeUint16Array(defaultCounters)\n : serializeUint32Array(defaultCounters)\n\n for (let i = 0; i < numUtilizationChunks; i++) {\n // Check if we have this chunk in cache\n const cached = cachedChunks.find((c) => c.chunkIndex === i)\n\n if (cached) {\n // Use cached chunk\n mergeChunk(dataCounters, i, cached.data, batchDepth)\n\n chunks.push({\n index: i,\n contentHash: cached.contentHash,\n lastUpload: cached.lastAccess,\n dirty: false,\n nonce: cached.nonce,\n })\n continue\n }\n\n // No cached chunk for this bucket range — seed a zeroed default.\n mergeChunk(dataCounters, i, defaultChunkData, batchDepth)\n\n chunks.push({\n index: i,\n contentHash: \"\", // Will be set on first upload\n lastUpload: 0,\n dirty: true, // Mark as dirty for upload\n nonce: 0,\n })\n }\n\n return {\n batchId,\n batchDepth,\n dataCounters,\n chunks,\n lastSync: Date.now(),\n }\n}\n\n/**\n * Save utilization state with incremental upload\n *\n * Only uploads dirty chunks to minimize network traffic.\n * Updates IndexedDB cache with new chunk data.\n *\n * @param state - Current utilization state (modified in place)\n * @param options - Save options\n */\nexport async function saveUtilizationState(\n state: BatchUtilizationState,\n options: {\n bee: Bee\n stamper: Stamper\n encryptionKey: Uint8Array\n cache: UtilizationStoreDB\n tracker: DirtyChunkTracker\n reservedBuckets?: ReadonlySet<number>\n },\n): Promise<void> {\n const { bee, stamper, encryptionKey, cache, tracker, reservedBuckets } =\n options\n\n // Get dirty chunks from tracker\n const dirtyChunkIndices = tracker.getDirtyChunks()\n\n if (dirtyChunkIndices.length === 0) {\n return\n }\n\n // Treat the buckets currently occupied by clean utilisation chunks as\n // already claimed — dirty chunks must avoid them so utilisation chunks\n // land in distinct buckets within a save. Also avoid `reservedBuckets`\n // (e.g. per-partition lock SOCs) so utilisation chunks don't land on a\n // slot the lock SOC overstamps every refresh.\n const claimedBuckets = claimedBucketsForCleanChunks(state)\n if (reservedBuckets) {\n for (const bucket of reservedBuckets) {\n claimedBuckets.add(bucket)\n }\n }\n\n // Counter chunks overstamp this partition's reserved slot rather than a data\n // slot (see `UtilizationAwareStamper.stamp`). Their addresses are\n // content-derived, so register each before uploading and clear them after.\n const isUtilStamper = stamper instanceof UtilizationAwareStamper\n\n for (const chunkIndex of dirtyChunkIndices) {\n const chunkMetadata = state.chunks[chunkIndex]\n\n // Extract chunk data from dataCounters\n const chunkData = extractChunk(\n state.dataCounters,\n chunkIndex,\n state.batchDepth,\n )\n\n // Search for a derived key whose bucket isn't already claimed. Start\n // from the previously-chosen nonce so unchanged plaintexts reuse their\n // key (and skip upload via dedup below).\n const resolved = await chooseUtilizationChunkKey({\n swarmEncryptionKey: encryptionKey,\n batchId: state.batchId,\n chunkIndex,\n plaintext: chunkData,\n priorNonce: chunkMetadata.nonce,\n claimedBuckets,\n })\n\n if (isUtilStamper) {\n stamper.markReservedUtilizationChunk(resolved.address)\n }\n\n try {\n // Upload to Swarm as encrypted CAC with the per-chunk derived key\n const cacReference = await uploadUtilizationChunk(\n bee,\n stamper,\n chunkData,\n resolved.key,\n )\n\n const cacReferenceHex = Binary.uint8ArrayToHex(cacReference)\n\n // Skip if reference unchanged (deduplication)\n if (\n chunkMetadata.contentHash === cacReferenceHex &&\n chunkMetadata.nonce === resolved.nonce\n ) {\n tracker.markClean(chunkIndex)\n continue\n }\n\n // Update metadata\n chunkMetadata.contentHash = cacReferenceHex\n chunkMetadata.lastUpload = Date.now()\n chunkMetadata.dirty = false\n chunkMetadata.nonce = resolved.nonce\n\n // Update IndexedDB cache\n await cache.putChunk({\n batchId: state.batchId.toHex(),\n chunkIndex,\n data: chunkData,\n contentHash: cacReferenceHex,\n nonce: resolved.nonce,\n lastAccess: Date.now(),\n })\n\n // Mark chunk as clean\n tracker.markClean(chunkIndex)\n } catch (error) {\n console.error(`[BatchUtil] Failed to upload chunk ${chunkIndex}:`, error)\n if (isUtilStamper) stamper.clearReservedUtilizationChunks()\n // Keep it marked as dirty for retry\n throw error\n }\n }\n\n if (isUtilStamper) stamper.clearReservedUtilizationChunks()\n\n // Update lastSync timestamp\n state.lastSync = Date.now()\n}\n\n// ============================================================================\n// High-level API\n// ============================================================================\n\n/**\n * Update utilization state after writing data chunks\n *\n * This function:\n * 1. Loads current state from the local cache\n * 2. Updates bucket counters for new data chunks\n * 3. Marks affected utilization chunks as dirty\n * 4. Returns state and tracker for later upload\n *\n * @param batchId - Batch ID\n * @param dataChunks - Data chunks that were written\n * @param batchDepth - Batch depth parameter\n * @param options - Load options (local IndexedDB cache)\n * @returns Updated state and dirty chunk tracker\n */\nexport async function updateAfterWrite(\n batchId: BatchId,\n dataChunks: ContentAddressedChunk[],\n batchDepth: number,\n options: {\n cache: UtilizationStoreDB\n },\n): Promise<{\n state: BatchUtilizationState\n tracker: DirtyChunkTracker\n}> {\n // Load current state\n const state = await loadUtilizationState(batchId, batchDepth, options)\n\n // Create tracker for dirty chunks\n const tracker = new DirtyChunkTracker(batchDepth)\n\n // Assign buckets and slots to data chunks\n for (const chunk of dataChunks) {\n const bucket = toBucket(chunk.address.toUint8Array())\n const slot = state.dataCounters[bucket]\n\n // Check capacity\n if (!hasBucketCapacity(slot, batchDepth)) {\n throw new Error(`Bucket ${bucket} is full (slot ${slot})`)\n }\n\n // Increment counter\n state.dataCounters[bucket]++\n\n // Mark the utilization chunk containing this bucket as dirty\n tracker.markDirty(bucket)\n }\n\n // Log dirty chunks\n const dirtyChunks = tracker.getDirtyChunks()\n\n // Mark chunks as dirty in state metadata\n for (const chunkIndex of dirtyChunks) {\n state.chunks[chunkIndex].dirty = true\n }\n\n return {\n state,\n tracker,\n }\n}\n\n/**\n * Calculate current utilization fraction for a batch\n *\n * @param state - Current utilization state\n * @param batchDepth - Batch depth parameter\n * @returns Utilization as decimal fraction (0-1)\n */\nexport function calculateUtilization(\n state: BatchUtilizationState,\n batchDepth: number,\n partitionCount: number = PARTITION_COUNT,\n): number {\n // dataCounters holds the per-partition 0-based `j`. A partition is full\n // when `j` reaches its per-partition capacity, so fill is measured against\n // that, not the raw slot count.\n const capacity = partitionCapacity(batchDepth, partitionCount)\n // `dataCounters` has NUM_BUCKETS (65,536) entries — spreading it into\n // Math.max(...) would blow the argument-count limit, so scan with a loop.\n let maxBucketUsage = 0\n for (const count of state.dataCounters) {\n if (count > maxBucketUsage) maxBucketUsage = count\n }\n\n // Utilization is based on the fullest bucket within the partition's lane.\n return capacity <= 0 ? 1 : Math.min(1, maxBucketUsage / capacity)\n}\n\n// ============================================================================\n// Utilization-Aware Stamper (Wrapper with Auto-Tracking)\n// ============================================================================\n\n/**\n * Decides which slot of its bucket a chunk is stamped at. Assigning a slot\n * spends it, so a stamp has exactly one assigner however many things can sign.\n */\nexport interface SlotAssigner {\n readonly batchId: BatchId\n assignSlot(chunkAddress: Uint8Array): number\n}\n\n/**\n * Stamper that owns slot assignment for a postage batch\n *\n * - Loads the per-bucket counters from cached utilization data on creation\n * - `assignSlot()` decides where a chunk's stamp goes: a reserved slot for the\n * lock / intent SOCs and utilisation chunks, otherwise the next slot of this\n * device's partition lane, behind the lease fence\n * - `stamp()` is `assignSlot()` plus core-sdk's stateless signing. Anything\n * that signs elsewhere (`StampWorkerPool`) takes its slot from `assignSlot()`\n * too, so there is one slot owner whoever signs\n * - Provides a flush() method to persist updates back to cache\n */\nexport class UtilizationAwareStamper implements Stamper, SlotAssigner {\n private utilizationState: BatchUtilizationState\n private cache: UtilizationStoreDB\n private readonly encryptionKey: Uint8Array\n private dirty: boolean = false\n private dirtyBuckets: Set<number> = new Set()\n\n /**\n * Partition this device holds within the shared postage batch. `undefined`\n * means the legacy single-device path, stamped as partition 0 of 1: the\n * behaviour for every account created before the partition-lease shipped.\n */\n private partition: number | undefined = undefined\n\n /**\n * Number of partitions the slot space is divided into. `1` is the\n * legacy/no-partitioning value and goes hand-in-hand with `partition`\n * being `undefined`.\n */\n private partitionCountValue: number = 1\n\n /**\n * Per-account lock-SOC addresses (one per partition). When `stamp()` sees\n * a chunk whose address matches one of these, it routes the write to the\n * lock SOC's reserved slot (= the partition index) within the same bucket,\n * bypassing the partition data-slot formula and the local counter bump.\n * Overstamping the same SOC at the same slot does not consume new slot\n * budget, so the heartbeat cadence is sustainable.\n *\n * Populated by `bindLockSocs()` once per stamper lifetime (deterministic\n * from accountId + partitionCount + owner). Independent of `partition`\n * binding so lock-SOC writes work both before and after `bindPartition`.\n */\n private lockSocs:\n | ReadonlyArray<{ partition: number; address: Uint8Array }>\n | undefined = undefined\n\n /**\n * Hex addresses of the utilisation (counter) chunks for the in-flight save,\n * mapped to the reserved SLOT each must overstamp. `stamp()` routes these to\n * that slot instead of a data slot. The slot is recorded at marking time\n * (not read from `this.partition` at stamp time) because the teardown\n * release publishes through an UNBOUND stamper — `partition` is already\n * undefined there, and falling back to slot 0 would overstamp the OTHER\n * partition's reserved chunks. Unlike the lock SOC, a counter chunk's\n * address changes whenever the counter changes, so writers register the\n * current set via `markReservedUtilizationChunk` and clear it with\n * `clearReservedUtilizationChunks`.\n */\n private reservedUtilizationChunks: Map<string, number> | undefined = undefined\n\n /**\n * The per-partition intent SOC for an in-flight intent round (Phase 2 —\n * `partition-intent.ts`), mapped to the reserved SLOT it must overstamp (the\n * contended partition index). Routed like the lock SOC / utilisation chunks:\n * `stamp()` places it in the partition's reserved slot, so it NEVER lands in\n * a data lane (`>= DATA_COUNTER_START`) and cannot overstamp user data.\n * Single-entry and self-clearing (set immediately before the upload, cleared\n * in a `finally`), so it stays isolated from the `reservedUtilizationChunks`\n * save lifecycle and never grows. See `reserveIntentSocSlot`.\n */\n private intentSoc: { addressHex: string; slot: number } | undefined =\n undefined\n\n /**\n * Promise-chain mutex serializing {@link withIntentSocSlot} critical sections.\n * The intent / occupancy / state-pointer SOC writes all share the single\n * {@link intentSoc} reservation, but their callers run concurrently (the\n * off-lock refresh-tick presence/occupancy beacon vs. an under-lock upload's\n * state-pointer publish). Without this, the two reserve→stamp windows overlap:\n * the second `reserveIntentSocSlot` clobbers the first's address, so the\n * first `stamp()` no longer matches `intentSoc` and falls through to a DATA\n * slot — consuming data budget and bumping the counter under the very publish\n * that is diffing it. The mutex makes each writer wait for the previous to\n * clear its reservation.\n */\n private intentSocLock: Promise<void> = Promise.resolve()\n\n /**\n * Circuit breaker for in-flight uploads. Flipped to `true` when the proxy\n * detects displacement on a refresh tick (or upload-start lease check), or\n * tears the lease down; subsequent data `stamp()` calls throw\n * `PartitionLeaseLostError` to abort the upload cleanly instead of silently\n * corrupting the peer's slot space. Outlives `unbindPartition` — an unbound\n * stamper would otherwise stamp the rest of that upload at legacy slots —\n * and is cleared only by the next `bindPartition`.\n */\n private leaseStale: boolean = false\n\n /**\n * Wall-clock ms this device's lease is valid until (from the lock SOC's\n * `leasedUntil`, pushed by the coordinator on bind and every renewal). While a\n * multi-chunk upload runs, each `stamp()` re-checks it locally (no network) and\n * throws once `Date.now()` passes it (minus a skew margin), so an un-renewable\n * holder stops writing mid-op the instant its own clock says the lease lapsed —\n * closing the \"ack-safe, not write-safe\" residual to the irreducible skew bound\n * (Postage-Batch-Partitioning.md §12). `undefined` = not fenced (legacy mode,\n * or between bind and the coordinator's first push).\n */\n private leaseValidUntil: number | undefined = undefined\n\n /**\n * This device's clock, injectable for tests (#385). The local-lapse fence\n * compares it against `leaseValidUntil`, so a test MUST drive the stamper with\n * the SAME clock it gives the `PartitionLease` (whose `leasedUntil` feeds\n * `leaseValidUntil`) — otherwise the two disagree and the fence mis-fires.\n * Defaults to `Date.now()` (production: one wall clock everywhere).\n */\n private readonly now: () => number\n\n /**\n * Per-partition buckets of the latest published partition-state chunks.\n * Utilisation saves must not place chunks in these buckets (same reserved\n * slot → overstamp → the published resume point is evicted from the\n * reserve). Mirrors `BatchMetadata.stateChunkBuckets`; restored on create.\n */\n private protectedStateBuckets = new Map<number, ReadonlySet<number>>()\n\n readonly signer: PrivateKey\n readonly batchId: BatchId\n readonly depth: number\n readonly maxSlot: number\n\n /**\n * The next physical data slot per bucket, as core-sdk's `Stamper` means it.\n * A snapshot: the counters of record are lane-relative (`dataCounters`), so\n * writing here moves nothing — take a slot with {@link assignSlot}.\n * Allocates a fresh NUM_BUCKETS-entry array per call.\n */\n get buckets(): Uint32Array {\n return this.utilizationState.dataCounters.map((j) =>\n dataSlot(this.partition ?? 0, j, this.partitionCountValue),\n )\n }\n\n /** Current partition (undefined in single-device legacy mode). */\n get currentPartition(): number | undefined {\n return this.partition\n }\n\n /** Total partition count (1 in legacy mode). */\n get partitionCount(): number {\n return this.partitionCountValue\n }\n\n /**\n * Per-bucket local stamping counter, exposed so the lease orchestrator\n * can publish it to the partition-state feed on release. Returns\n * undefined in legacy mode.\n */\n getLocalCounter(): Uint32Array | undefined {\n return this.partition !== undefined\n ? this.utilizationState.dataCounters\n : undefined\n }\n\n /**\n * Buckets that contain a per-partition lock SOC. External callers that\n * persist utilisation chunks (e.g. `saveUtilizationState` from the sync\n * path) should treat these as already claimed so utilisation-chunk key\n * search avoids dropping a chunk on the lock SOC's bucket.\n */\n getLockSocBuckets(): ReadonlySet<number> {\n const set = new Set<number>()\n for (const soc of this.lockSocs ?? []) {\n set.add(toBucket(soc.address))\n }\n return set\n }\n\n /**\n * All buckets a utilisation save must avoid: the lock-SOC buckets plus the\n * buckets of the latest published partition-state chunks for the partition\n * this stamper writes reserved slots in (`partition ?? 0`). A utilisation\n * chunk landing in a published state chunk's bucket would overstamp the\n * same reserved slot and evict the published chunk from the reserve —\n * destroying the resume point a later takeover depends on.\n */\n getProtectedBuckets(): ReadonlySet<number> {\n const set = new Set<number>(this.getLockSocBuckets())\n const stateBuckets = this.protectedStateBuckets.get(this.partition ?? 0)\n for (const bucket of stateBuckets ?? []) {\n set.add(bucket)\n }\n return set\n }\n\n /**\n * Buckets occupied by this stamper's current CLEAN utilisation chunks.\n * The partition-state publish avoids them so a randomly-keyed state chunk\n * does not evict a live utilisation chunk from the reserve.\n */\n getCleanChunkBuckets(): ReadonlySet<number> {\n return claimedBucketsForCleanChunks(this.utilizationState)\n }\n\n /**\n * Record the buckets occupied by the latest published partition-state\n * chunks for `partition` (in memory + persisted in the batch metadata, so\n * a reload keeps avoiding them). Called after every successful publish\n * (`PartitionLease.release`) and after every successful state read\n * (`claimPartition`) — the set is replaced wholesale; only the latest\n * publish needs protection, superseded chunks are dead.\n */\n async setProtectedStateBuckets(\n partition: number,\n buckets: number[],\n ): Promise<void> {\n this.protectedStateBuckets.set(partition, new Set(buckets))\n const batchId = this.batchId.toHex()\n const existing = await this.cache.getMetadata(batchId)\n await this.cache.putMetadata({\n batchId,\n lastSync: existing?.lastSync ?? Date.now(),\n chunkCount: existing?.chunkCount ?? 0,\n syncedReferences: existing?.syncedReferences,\n stateChunkBuckets: {\n ...(existing?.stateChunkBuckets ?? {}),\n [partition]: buckets,\n },\n })\n }\n\n /**\n * Bind this stamper to a leased partition. Called once by the lease\n * orchestrator after `acquire()` succeeds. `localCounter` is the\n * starting per-bucket count for THIS device — fresh zeros for Case A,\n * the resumed high-water from the state feed for Cases B/D.\n */\n bindPartition(opts: {\n partition: number\n partitionCount: number\n localCounter: Uint32Array\n }): void {\n if (opts.localCounter.length !== NUM_BUCKETS) {\n throw new Error(\n `localCounter must have ${NUM_BUCKETS} entries, got ${opts.localCounter.length}`,\n )\n }\n this.partition = opts.partition\n this.partitionCountValue = opts.partitionCount\n // The seeded per-partition counter (`j`, resumed high-water for Cases\n // B/D) becomes the single counter of record for this held partition.\n this.utilizationState.dataCounters = opts.localCounter\n this.leaseStale = false\n // Presumed valid until the coordinator pushes the real `leasedUntil` (it\n // does so synchronously right after bind). Mirrors the `leaseStale` reset.\n this.leaseValidUntil = undefined\n }\n\n /**\n * Bind the per-partition lock-SOC addresses. Called once per stamper\n * lifetime (the addresses are deterministic from the account). Routes\n * lock-SOC overstamps to a fixed reserved slot per partition so the tight\n * heartbeat cadence doesn't consume new slot budget every refresh.\n *\n * Independent of `bindPartition` — can be called before the partition\n * lease is acquired (the first lock-SOC write inside `acquirePartitionLock`\n * needs this routing to already be in place).\n */\n bindLockSocs(\n socs: ReadonlyArray<{ partition: number; address: Uint8Array }>,\n ): void {\n this.lockSocs = socs\n }\n\n /**\n * Register a utilisation/state chunk address so the next `stamp()` of it\n * routes to a reserved slot instead of a data slot. `slot` defaults to the\n * currently bound partition — pass it explicitly when the stamper may be\n * unbound at stamp time (the teardown release publishes state chunks after\n * `unbindPartition()`, and a default of 0 would overstamp partition 0's\n * reserved chunks). Idempotent per address.\n */\n markReservedUtilizationChunk(address: Uint8Array, slot?: number): void {\n if (!this.reservedUtilizationChunks) {\n this.reservedUtilizationChunks = new Map()\n }\n this.reservedUtilizationChunks.set(\n uint8ArrayToHex(address),\n slot ?? this.partition ?? 0,\n )\n }\n\n /** Clear the registered utilisation-chunk addresses after a save. */\n clearReservedUtilizationChunks(): void {\n this.reservedUtilizationChunks = undefined\n }\n\n /**\n * Register the per-partition intent SOC so `stamp()` routes it to `slot`\n * (the contended partition's reserved index) instead of a data slot — never\n * consuming data budget or bumping the counter. `slot` is passed explicitly\n * because the intent round runs BEFORE `bindPartition`, so `this.partition`\n * is not yet set. Call immediately before the upload; pair with\n * `clearIntentSocSlot` in a `finally`.\n */\n reserveIntentSocSlot(address: Uint8Array, slot: number): void {\n this.intentSoc = { addressHex: uint8ArrayToHex(address), slot }\n }\n\n /** Clear the registered intent SOC after its upload. */\n clearIntentSocSlot(): void {\n this.intentSoc = undefined\n }\n\n /**\n * Reserve the intent-SOC slot for `address`, run `fn` (the upload), then\n * clear it — serialized against every other `withIntentSocSlot` call on this\n * stamper so concurrent intent/occupancy/state-pointer writes can't clobber\n * one another's reservation (see {@link intentSocLock}). Production callers\n * (`writeReservedPartitionSoc`, `writeStatePointer`) MUST use this instead of\n * the bare `reserveIntentSocSlot`/`clearIntentSocSlot` pair. `partition` is the\n * reserved slot index the SOC routes to (the contended partition's index).\n */\n async withIntentSocSlot<T>(\n address: Uint8Array,\n partition: number,\n fn: () => Promise<T>,\n ): Promise<T> {\n const previous = this.intentSocLock\n let release!: () => void\n this.intentSocLock = new Promise<void>((resolve) => (release = resolve))\n await previous\n // `reserveIntentSocSlot` inside the try so `release()` still runs if it (or\n // `fn`) ever throws — otherwise `intentSocLock` would stay pending forever\n // and every later intent/occupancy/state-pointer write on this stamper would\n // deadlock on `await previous`.\n try {\n this.reserveIntentSocSlot(address, partition)\n return await fn()\n } finally {\n this.clearIntentSocSlot()\n release()\n }\n }\n\n /**\n * Inverse of `bindPartition` — clears partition slot state on demote.\n * Leaves `lockSocs` intact (still valid for the account; refresh/yield\n * writes may need them), and leaves an invalidated lease invalidated: see\n * `leaseStale`.\n */\n unbindPartition(): void {\n this.partition = undefined\n this.partitionCountValue = 1\n this.leaseValidUntil = undefined\n }\n\n /**\n * Circuit-break: mark the bound lease as stale. Every data `stamp()` from\n * here to the next `bindPartition` throws `PartitionLeaseLostError`, so an\n * in-flight upload aborts cleanly mid-stream when a peer takes our partition\n * or the lease is torn down under it. No-op when no partition is bound:\n * there is no lease to lose, and legacy stamping must stay unfenced.\n */\n invalidateLease(): void {\n if (this.partition === undefined) return\n this.leaseStale = true\n }\n\n /**\n * Record the wall-clock ms this device's lease is valid until (the lock SOC's\n * `leasedUntil`). The coordinator pushes it on bind and every successful\n * renewal; `undefined` disables the local-lapse fence. See `leaseValidUntil`.\n */\n setLeaseValidUntil(ms: number | undefined): void {\n this.leaseValidUntil = ms\n }\n\n /**\n * True when this device's own clock says the lease has lapsed (or is within\n * the skew margin of it). Pure/local — no network — so it can gate every\n * `stamp()` on the hot path for free. `undefined` deadline → not fenced.\n */\n private leaseLocallyLapsed(): boolean {\n return (\n this.leaseValidUntil !== undefined &&\n this.now() >= this.leaseValidUntil - LEASE_SKEW_MARGIN_MS\n )\n }\n\n private constructor(\n signer: PrivateKey,\n batchId: BatchId,\n depth: number,\n cache: UtilizationStoreDB,\n encryptionKey: Uint8Array,\n utilizationState: BatchUtilizationState,\n now: () => number,\n ) {\n this.signer = signer\n this.batchId = batchId\n this.depth = depth\n this.maxSlot = calculateMaxSlotsPerBucket(depth)\n this.cache = cache\n this.encryptionKey = encryptionKey\n this.utilizationState = utilizationState\n this.now = now\n }\n\n /**\n * Create a UtilizationAwareStamper with bucket state from cache\n *\n * @param privateKey - Signer private key\n * @param batchId - Postage batch ID\n * @param depth - Batch depth\n * @param cache - Utilization cache database\n * @param _owner - Owner address (required for validation, reserved for future Swarm upload)\n * @param encryptionKey - Encryption key used to compute the canonical Swarm chunk address on local flush. Both call sites already pass the account's encryption key; \"anything stable\" works for now, will be tied to multi-device collision avoidance later.\n * @returns New UtilizationAwareStamper instance\n */\n static async create(\n privateKey: Uint8Array | string,\n batchId: BatchId,\n depth: number,\n cache: UtilizationStoreDB,\n owner: EthAddress,\n encryptionKey: Uint8Array,\n now: () => number = () => Date.now(),\n ): Promise<UtilizationAwareStamper> {\n // Initialize utilization state (always, since owner is now required)\n const utilizationState = initializeBatchUtilization(batchId, depth)\n\n // Try to load utilization state from cache\n try {\n const cachedChunks = await cache.getAllChunks(batchId.toHex())\n const { numUtilizationChunks } = getChunkLayout(depth)\n // Merge cached chunks into state; ignore any that fall outside the\n // current depth's layout (e.g. stale entries from a different depth)\n for (const cached of cachedChunks) {\n if (cached.chunkIndex >= numUtilizationChunks) continue\n mergeChunk(\n utilizationState.dataCounters,\n cached.chunkIndex,\n cached.data,\n depth,\n )\n }\n } catch (error) {\n console.warn(\n `[UtilizationAwareStamper] Failed to load state from cache, starting with fresh state:`,\n error,\n )\n }\n\n const instance = new UtilizationAwareStamper(\n new PrivateKey(privateKey),\n batchId,\n depth,\n cache,\n encryptionKey,\n utilizationState,\n now,\n )\n\n // Restore the protected partition-state buckets so saves keep avoiding\n // the published resume point across reloads. Tolerates minimal caches\n // (tests stub only getAllChunks/putChunk).\n try {\n const meta = await cache.getMetadata?.(batchId.toHex())\n for (const [p, buckets] of Object.entries(\n meta?.stateChunkBuckets ?? {},\n )) {\n instance.protectedStateBuckets.set(Number(p), new Set(buckets))\n }\n } catch (error) {\n console.warn(\n `[UtilizationAwareStamper] Failed to restore protected state buckets:`,\n error,\n )\n }\n\n // Auto-bind lock SOCs for all partitions. The lock-SOC owner is the\n // BACKUP signer's address — same value `writePartitionLock` derives\n // internally — NOT the `owner` parameter callers pass (which is the\n // account / postage-signer address depending on call site). Deriving\n // here from `encryptionKey` (= swarmEncryptionKey at every call site)\n // makes the routing self-consistent regardless of caller. Harmless for\n // single-device legacy accounts (the lock SOCs are never written).\n void owner // currently unused; reserved for a future Swarm upload path.\n const swarmEncryptionKeyHex = uint8ArrayToHex(encryptionKey)\n const backupKeyHex = await deriveSecret(\n swarmEncryptionKeyHex,\n BACKUP_KEY_LABEL,\n )\n const backupSigner = new PrivateKey(backupKeyHex)\n const backupOwner = backupSigner.publicKey().address()\n instance.lockSocs = Array.from({ length: PARTITION_COUNT }, (_, p) => ({\n partition: p,\n address: lockSocAddress(batchId, p, backupOwner),\n }))\n\n return instance\n }\n\n /**\n * The reserved slot `chunkAddress` overstamps, if it is one of the account's\n * own bookkeeping chunks, else undefined.\n */\n private reservedSlotFor(chunkAddress: Uint8Array): number | undefined {\n // Lock-SOC short-circuit: when stamping our own per-partition lock SOC,\n // overstamp the fixed reserved slot (= partition index, 0 or 1) within\n // its bucket. Doesn't consume new slot budget, doesn't bump our local\n // counter — the SOC address is the same on every refresh, so the slot\n // it occupies stays the same too.\n const lockSoc = this.lockSocs?.find((soc) =>\n Binary.equals(soc.address, chunkAddress),\n )\n if (lockSoc) {\n return lockSoc.partition\n }\n\n // Utilisation-chunk short-circuit: a counter/state chunk overstamps its\n // bucket's reserved slot (recorded at marking time), just like the lock\n // SOC. Persisting the counter therefore never consumes a data slot or\n // bumps the counter. The addresses are content-derived (they change as the\n // counter changes), so writers register the current save's addresses via\n // `markReservedUtilizationChunk` before uploading them.\n const addressHex = uint8ArrayToHex(chunkAddress)\n const utilizationSlot = this.reservedUtilizationChunks?.get(addressHex)\n if (utilizationSlot !== undefined) {\n return utilizationSlot\n }\n\n // Intent-SOC short-circuit: a partition-intent chunk (Phase 2) overstamps\n // the contended partition's reserved slot — like the lock SOC, below the\n // data lanes — so it can never collide with user data. Doesn't bump the\n // counter. Not gated by `leaseStale`: the intent round runs before binding,\n // while no lease is held.\n if (this.intentSoc?.addressHex === addressHex) {\n return this.intentSoc.slot\n }\n return undefined\n }\n\n /**\n * Partition lease was reclaimed: abort cleanly before a data stamp lands in\n * slot space the peer now controls. Single-device legacy mode has no lease\n * to invalidate, so neither condition can hold there. Two conditions:\n * - `leaseStale`: the refresh tick CONFIRMED a peer took over (write-verify\n * failed / displacement beacon seen), or the coordinator tore the lease\n * down. Holds across the unbind that follows, where the fall-through\n * in `assignSlot` would stamp at legacy slots.\n * - `leaseLocallyLapsed()`: this device's own clock says the lease expired\n * (skew margin applied). Catches the un-renewable case a disjoint-gateway\n * refresh can't confirm by reading, stopping a long op mid-stream the\n * moment the lease lapses locally, so the data write is fenced to the same\n * skew bound as the ack (Postage-Batch-Partitioning.md §12, \"ack-safe\").\n *\n * @throws PartitionLeaseLostError\n */\n private requireLiveLease(): void {\n if (\n this.leaseStale ||\n (this.partition !== undefined && this.leaseLocallyLapsed())\n ) {\n throw new PartitionLeaseLostError()\n }\n }\n\n /**\n * Pick the slot `chunkAddress` is stamped at within its bucket, and spend it.\n *\n * Synchronous on purpose: callers fire stamps concurrently, and with no await\n * in here each assignment runs to completion before the next starts. Whoever\n * signs — {@link stamp} here, a `StampWorkerPool` worker — must take the slot\n * from this method and nowhere else.\n *\n * @throws PartitionLeaseLostError when the bound partition's lease is gone\n */\n assignSlot(chunkAddress: Uint8Array): number {\n // The reserved-slot return below skips the `maxSlot` bound: every reserved\n // slot is below `partitionCount`, so it always fits. A data slot must never\n // be returned from there, those take the bounded path at the end.\n const reservedSlot = this.reservedSlotFor(chunkAddress)\n if (reservedSlot !== undefined) {\n return reservedSlot\n }\n\n this.requireLiveLease()\n\n // This device's next slot in its partition's lane: slot = partitionCount +\n // partition + partitionCount·j, where j is the per-partition counter.\n // Legacy single-device is partition 0, K=1 (data from slot 1, reserved\n // slot 0).\n const bucket = toBucket(chunkAddress)\n const slot = dataSlot(\n this.partition ?? 0,\n this.utilizationState.dataCounters[bucket],\n this.partitionCountValue,\n )\n if (slot >= this.maxSlot) {\n throw new Error(\"Bucket is full\")\n }\n\n // Advance the per-partition counter so the next stamp into this bucket\n // lands on this partition's next slot.\n this.utilizationState.dataCounters[bucket]++\n\n // Mark bucket as dirty for eventual flush\n this.dirtyBuckets.add(bucket)\n this.dirty = true\n\n return slot\n }\n\n /**\n * Stamp a chunk address (implements Stamper interface)\n *\n * @param chunkAddress - Address of the chunk to stamp\n * @param timestampMs - Stamp timestamp, defaults to now\n * @returns Envelope with batch ID and signature\n */\n stamp(chunkAddress: Uint8Array, timestampMs?: number): EnvelopeWithBatchId {\n return stampAtSlot(\n this.signer,\n this.batchId,\n chunkAddress,\n this.assignSlot(chunkAddress),\n timestampMs,\n )\n }\n\n /**\n * Stamp a chunk an app will send through its own Swarm access. Always a\n * data slot: an address the stamper would route to a reserved slot is\n * refused, so an app can never be handed the stamp that overwrites the\n * account's lock, counter, state or intent chunks. And always inside the\n * bucket's app slice, so apps can never take the slots the account keeps\n * for its own uploads.\n *\n * @throws ReservedSlotError for one of the account's own bookkeeping chunks\n * @throws AppSliceFullError when the bucket's app slice is used up\n * @throws PartitionLeaseLostError when the bound partition's lease is gone\n */\n stampDataChunk(\n chunkAddress: Uint8Array,\n timestampMs?: number,\n ): EnvelopeWithBatchId {\n if (this.reservedSlotFor(chunkAddress) !== undefined) {\n throw new ReservedSlotError()\n }\n const bucket = toBucket(chunkAddress)\n if (this.utilizationState.dataCounters[bucket] >= this.appSliceCapacity()) {\n throw new AppSliceFullError()\n }\n return this.stamp(chunkAddress, timestampMs)\n }\n\n /**\n * Whether `slot` is a data slot this device's current lane has already\n * taken in `chunkAddress`'s bucket, so stamping `chunkAddress` there again\n * replaces only a chunk this lane put there. A reserved chunk's address is\n * never one.\n */\n isOwnDataSlot(chunkAddress: Uint8Array, slot: number): boolean {\n if (this.reservedSlotFor(chunkAddress) !== undefined) return false\n const lane = this.partitionCountValue\n const offset = slot - dataSlot(this.partition ?? 0, 0, lane)\n if (offset < 0 || offset % lane !== 0) return false\n const j = offset / lane\n return j < this.utilizationState.dataCounters[toBucket(chunkAddress)]\n }\n\n /**\n * Stamp a single-owner chunk an app had stamped before at the same slot,\n * with a newer timestamp, so a mutable batch replaces the old chunk at that\n * slot rather than spending a new one. No slot is taken and the counter\n * does not move.\n *\n * @param slot - A slot {@link isOwnDataSlot} accepts for `chunkAddress`\n * @param timestampMs - Must be newer than the last stamp at this slot\n * @throws ReservedSlotError for one of the account's own bookkeeping chunks\n * @throws PartitionLeaseLostError when the bound partition's lease is gone\n */\n restampDataChunk(\n chunkAddress: Uint8Array,\n slot: number,\n timestampMs?: number,\n ): EnvelopeWithBatchId {\n if (this.reservedSlotFor(chunkAddress) !== undefined) {\n throw new ReservedSlotError()\n }\n this.requireLiveLease()\n if (!this.isOwnDataSlot(chunkAddress, slot)) {\n throw new Error(\n `Slot ${slot} is not a slot this device took in this chunk's bucket.`,\n )\n }\n return stampAtSlot(\n this.signer,\n this.batchId,\n chunkAddress,\n slot,\n timestampMs,\n )\n }\n\n /** How many data chunks this device's partition lane holds in one bucket */\n laneCapacity(): number {\n return partitionCapacity(this.depth, this.partitionCountValue)\n }\n\n /**\n * How far up a bucket's lane an app stamp may go: the lane less the share\n * {@link ACCOUNT_RESERVED_LANE_SHARE} keeps for the account's own uploads.\n */\n appSliceCapacity(): number {\n return Math.floor(this.laneCapacity() * (1 - ACCOUNT_RESERVED_LANE_SHARE))\n }\n\n /**\n * Get bucket state (implements Stamper interface) — see {@link buckets}.\n */\n getState(): Uint32Array {\n return this.buckets\n }\n\n /**\n * Flush dirty utilization chunks to cache\n *\n * This persists any bucket state changes made during stamping.\n * Should be called after all stamping operations are complete.\n */\n async flush(): Promise<void> {\n if (!this.dirty) {\n return\n }\n\n // Mark utilization chunks as dirty for the affected buckets\n const dirtyChunkIndexes = new Set<number>()\n for (const bucket of this.dirtyBuckets) {\n const chunkIndex = getChunkIndexForBucket(bucket, this.depth)\n dirtyChunkIndexes.add(chunkIndex)\n this.utilizationState.chunks[chunkIndex].dirty = true\n }\n\n // Save dirty chunks to cache. We do not upload here, but we still record\n // the contentHash that an upload *would* assign — i.e. the BMT address of\n // the encrypted chunk for this plaintext + the per-chunk derived key. That\n // is what the upload-path dedup at saveUtilizationState compares against,\n // so storing anything else (or using a different key) would force a\n // redundant re-upload of every flushed chunk on the next sync.\n try {\n const claimedBuckets = claimedBucketsForCleanChunks(this.utilizationState)\n // Reserve each partition's lock-SOC bucket so utilisation-chunk key\n // search picks a different nonce. Otherwise a utilisation chunk could\n // land in the same bucket and try to claim slot 0/1 — the slot the\n // lock SOC overstamps every refresh.\n for (const soc of this.lockSocs ?? []) {\n claimedBuckets.add(toBucket(soc.address))\n }\n // Also reserve the latest published partition-state chunks' buckets —\n // they share this partition's reserved slot, and overstamping one\n // evicts the resume point a later takeover depends on.\n for (const bucket of this.protectedStateBuckets.get(\n this.partition ?? 0,\n ) ?? []) {\n claimedBuckets.add(bucket)\n }\n const sortedDirtyChunkIndexes = Array.from(dirtyChunkIndexes).sort(\n (a, b) => a - b,\n )\n for (const chunkIndex of sortedDirtyChunkIndexes) {\n const chunkData = extractChunk(\n this.utilizationState.dataCounters,\n chunkIndex,\n this.depth,\n )\n\n const resolved = await chooseUtilizationChunkKey({\n swarmEncryptionKey: this.encryptionKey,\n batchId: this.batchId,\n chunkIndex,\n plaintext: chunkData,\n priorNonce: this.utilizationState.chunks[chunkIndex].nonce,\n claimedBuckets,\n })\n const contentHash = Binary.uint8ArrayToHex(resolved.address)\n\n await this.cache.putChunk({\n batchId: this.batchId.toHex(),\n chunkIndex,\n data: chunkData,\n contentHash,\n nonce: resolved.nonce,\n lastAccess: Date.now(),\n })\n this.utilizationState.chunks[chunkIndex].contentHash = contentHash\n this.utilizationState.chunks[chunkIndex].nonce = resolved.nonce\n this.utilizationState.chunks[chunkIndex].dirty = false\n }\n\n // Update lastSync timestamp\n this.utilizationState.lastSync = Date.now()\n } catch (error) {\n console.error(\n `[UtilizationAwareStamper] Failed to flush to cache:`,\n error,\n )\n throw error\n }\n\n // Clear dirty flags\n this.dirtyBuckets.clear()\n this.dirty = false\n }\n\n /**\n * Get current utilization state\n *\n * @returns Current utilization state\n */\n getUtilizationState(): BatchUtilizationState {\n return this.utilizationState\n }\n\n /**\n * Apply utilization update from another tab.\n * Updates local bucket counters to match leader's state.\n *\n * @param buckets Array of bucket indices and their new counter values\n */\n applyUtilizationUpdate(\n buckets: Array<{ index: number; value: number }>,\n ): void {\n for (const { index, value } of buckets) {\n if (index >= 0 && index < NUM_BUCKETS) {\n // Only update if the incoming value is higher (monotonic increase)\n if (value > this.utilizationState.dataCounters[index]) {\n this.utilizationState.dataCounters[index] = value\n }\n }\n }\n\n // Note: Do NOT clear dirtyBuckets here - those represent local writes\n // that still need to be flushed. Only flush() should clear them.\n }\n\n /**\n * Build the partition-local counter from the stamper's current\n * `dataCounters` — i.e. resume exactly where this device left off. The\n * lease orchestrator passes the result into `bindPartition` as the seed\n * counter for a held partition.\n */\n buildLeaseLocalCounter(): Uint32Array {\n return this.utilizationState.dataCounters.slice()\n }\n\n /**\n * Read the cached \"synced reference\" for `partition` — the partition-state\n * feed reference this device's local counter was last in sync with. Used by\n * the acquire path to skip re-downloading unchanged counter chunks.\n */\n async getSyncedReference(partition: number): Promise<string | undefined> {\n const meta = await this.cache.getMetadata(this.batchId.toHex())\n return meta?.syncedReferences?.[partition]\n }\n\n /**\n * Record the partition-state feed reference this device's local counter is\n * now in sync with (after a download or a publish). Merges into the existing\n * batch metadata so other fields / partitions are preserved.\n */\n async setSyncedReference(\n partition: number,\n referenceHex: string,\n ): Promise<void> {\n const batchId = this.batchId.toHex()\n const existing = await this.cache.getMetadata(batchId)\n await this.cache.putMetadata({\n batchId,\n lastSync: existing?.lastSync ?? Date.now(),\n chunkCount: existing?.chunkCount ?? 0,\n syncedReferences: {\n ...(existing?.syncedReferences ?? {}),\n [partition]: referenceHex,\n },\n stateChunkBuckets: existing?.stateChunkBuckets,\n })\n }\n\n /**\n * Get bucket counter values for broadcasting to other tabs.\n * Returns only the dirty buckets with their current values.\n *\n * IMPORTANT: Call this BEFORE flush() as flush() clears dirtyBuckets.\n *\n * @returns Array of bucket index/value pairs for broadcasting\n */\n getBucketUpdatesForBroadcast(): Array<{ index: number; value: number }> {\n return Array.from(this.dirtyBuckets).map((index) => ({\n index,\n value: this.utilizationState.dataCounters[index],\n }))\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Pre-configured Storage Managers for Entity Types\n *\n * The account is the single nested document of record: it owns its connected\n * apps and postage stamps inline (no separate per-collection storage keys).\n * Only the accounts document and the network-settings singleton are persisted.\n */\n\nimport { z } from \"zod\"\nimport {\n VersionedStorageManager,\n createLocalStorageManager,\n type RecordSealing,\n type VersionParser,\n} from \"./versioned-storage\"\nimport {\n isSealedValue,\n isSealedValueUnavailable,\n openedValue,\n primeSealedValues,\n sealValue,\n sealsWithPageKey,\n} from \"./sealed-values\"\nimport type {\n Account,\n SyncedAccount,\n ConnectedApp,\n PostageStamp,\n} from \"../types\"\nimport { STORAGE_KEY_ACCOUNTS, STORAGE_KEY_NETWORK_SETTINGS } from \"../types\"\nimport {\n LocalAccountSchemaV1,\n NetworkSettingsSchemaV1,\n isSignedOutAccount,\n type NetworkSettings,\n} from \"../schemas\"\nimport { PARTITION_COUNT } from \"./batch-utilization\"\n\n// ============================================================================\n// Parsers (Zod transforms handle primitive → bee-js conversion)\n// ============================================================================\n\n/**\n * Parse accounts - Zod transforms handle type conversion.\n *\n * Records are validated one by one: an invalid record is skipped (logged), not\n * allowed to fail the whole document. Dropping every account for one bad record\n * would present an empty list, and the next save would erase the document for\n * real.\n */\nconst parseAccountsV1: VersionParser<Account> = (data: unknown) => {\n const records = z.array(z.unknown()).safeParse(data)\n\n if (!records.success) {\n console.error(\"Parse failed:\", records.error.format())\n return []\n }\n\n const accounts: Account[] = []\n for (const record of records.data) {\n // The try is the last line of defence: a throw inside a transform escapes\n // `safeParse`, and must still cost one record, never the document.\n try {\n const result = LocalAccountSchemaV1.safeParse(record)\n if (!result.success) {\n console.error(\"Skipping invalid account record:\", result.error.format())\n continue\n }\n accounts.push(result.data)\n } catch (e) {\n console.error(\"Skipping invalid account record:\", e)\n }\n }\n\n return accounts.map((account) => {\n if (\n !isSignedOutAccount(account) &&\n account.partitionCount === undefined &&\n account.devices.length > 0\n ) {\n return {\n ...account,\n partitionCount: PARTITION_COUNT,\n }\n }\n return account\n })\n}\n\n// ============================================================================\n// Serializers\n// ============================================================================\n\n/**\n * Serialize the portable projection of an account (`SyncedAccount`) — what\n * backup files and the sync feed carry off the device. Beyond the schema-level\n * absence of the seed vault, it strips the live per-app session material:\n * `appSecret` (the secret the dApp proxy authenticates from, re-derivable from\n * the master key on the next connect) and `connectedUntil` (meaningless without\n * the secret — keeping it would show a \"Connected\" app that can no longer\n * authenticate).\n */\nexport function serializeSyncedAccount(\n data: SyncedAccount,\n): Record<string, unknown> {\n return {\n id: data.id.toString(),\n name: data.name,\n createdAt: data.createdAt,\n derivationKey: data.derivationKey,\n publicKey: data.publicKey,\n defaultPostageStampBatchID: data.defaultPostageStampBatchID?.toString(),\n devices: data.devices,\n connectedApps: data.connectedApps.map((app) =>\n portableConnectedApp(serializeConnectedApp(app)),\n ),\n postageStamps: data.postageStamps.map(serializePostageStamp),\n settings: data.settings,\n accountNameAt: data.accountNameAt,\n defaultStampAt: data.defaultStampAt,\n settingsAt: data.settingsAt,\n lastModified: data.lastModified,\n partitionCount: data.partitionCount,\n }\n}\n\n/**\n * Serialize Account for storage (nested: includes its connected apps and\n * postage stamps inline). A signed-out account persists only its minimal\n * remnant — display fields, the encrypted vault, and the encrypted state\n * snapshot — so no synced data (in particular the plaintext `derivationKey`)\n * stays on disk in the clear while signed out.\n */\nexport function serializeAccount(account: Account): Record<string, unknown> {\n if (isSignedOutAccount(account)) {\n return {\n id: account.id.toString(),\n name: account.name,\n createdAt: account.createdAt,\n access: account.access,\n encryptedSeed: account.encryptedSeed,\n encryptedState: account.encryptedState,\n storageWarning: account.storageWarning,\n soonestDriveExpiry: account.soonestDriveExpiry,\n signedOutAt: account.signedOutAt,\n }\n }\n return {\n ...serializeSyncedAccount(account),\n // Local storage keeps the live per-app session secrets the portable\n // projection strips.\n connectedApps: account.connectedApps.map(serializeConnectedApp),\n // The device-local tail of the record: the seed vault (the account's only\n // secret material — plain JSON, no byte classes). This group stays on the\n // device: the portable copy that backups and the sync feed publish is\n // `SyncedAccount` — the record without it.\n access: account.access,\n encryptedSeed: account.encryptedSeed,\n }\n}\n\n/**\n * Serialize ConnectedApp for storage\n */\nexport function serializeConnectedApp(\n app: ConnectedApp,\n): Record<string, unknown> {\n return {\n appUrl: app.appUrl,\n appName: app.appName,\n lastConnectedAt: app.lastConnectedAt,\n appIcon: app.appIcon,\n appDescription: app.appDescription,\n connectedUntil: app.connectedUntil,\n appSecret: app.appSecret,\n postageStampBatchID: app.postageStampBatchID?.toString(),\n postageStampBatchIDAt: app.postageStampBatchIDAt,\n revokedAt: app.revokedAt,\n disconnectedAt: app.disconnectedAt,\n chatBinding: app.chatBinding,\n }\n}\n\n/**\n * A serialized connected app without its per-context session material.\n *\n * `appSecret` is the app's own credential and `connectedUntil` is the deadline\n * of the session that established it — both belong to one context, and neither\n * survives leaving it. Every channel that carries an account to another\n * context strips them here: the popup handshake (`serializeSyncedAccount`)\n * and the account bus (`accountDeltaSnapshot`). The bus receive schema\n * (`BusConnectedAppSchema` in `bus/messages.ts`) omits the same two fields\n * again, so a publisher that forgets this strip is still caught on receive.\n */\nexport function portableConnectedApp(\n app: Record<string, unknown>,\n): Record<string, unknown> {\n const {\n appSecret: _appSecret,\n connectedUntil: _connectedUntil,\n ...rest\n } = app\n return rest\n}\n\n/**\n * Serialize PostageStamp for storage\n */\nexport function serializePostageStamp(\n stamp: PostageStamp,\n): Record<string, unknown> {\n return {\n batchID: stamp.batchID.toString(),\n signerKey: stamp.signerKey.toString(),\n name: stamp.name,\n nameUpdatedAt: stamp.nameUpdatedAt,\n utilization: stamp.utilization,\n usable: stamp.usable,\n depth: stamp.depth,\n amount: stamp.amount.toString(), // Convert bigint to string for JSON\n bucketDepth: stamp.bucketDepth,\n blockNumber: stamp.blockNumber,\n immutableFlag: stamp.immutableFlag,\n exists: stamp.exists,\n batchTTL: stamp.batchTTL,\n createdAt: stamp.createdAt,\n updatedAt: stamp.updatedAt,\n deletedAt: stamp.deletedAt,\n }\n}\n\n// ============================================================================\n// Keys at rest, sealed so none is plain text on disk\n// ============================================================================\n\n/**\n * The key material in a stored account, each sealed with `sealValue`: the\n * derivation key, every connected app's secret, and every stamp's signer key.\n * A signed-out record holds none of them (its vault is already encrypted).\n */\ntype JsonRecord = Record<string, unknown>\n\nfunction isRecord(value: unknown): value is JsonRecord {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction recordsOf(value: unknown): JsonRecord[] {\n return Array.isArray(value) ? value.filter(isRecord) : []\n}\n\nfunction holdsKeys(record: JsonRecord): boolean {\n return record.signedOutAt === undefined\n}\n\n/** Every stored key of a serialized account, in a fixed order. */\nfunction storedKeys(record: JsonRecord): unknown[] {\n if (!holdsKeys(record)) return []\n return [\n record.derivationKey,\n ...recordsOf(record.connectedApps).map((app) => app.appSecret),\n ...recordsOf(record.postageStamps).map((stamp) => stamp.signerKey),\n ].filter((value) => value !== undefined)\n}\n\n/**\n * Rewrite every key of a serialized account through `transform`, keeping the\n * rest of the record as it is.\n */\nasync function mapStoredKeys(\n record: JsonRecord,\n transform: (value: string) => Promise<string>,\n): Promise<JsonRecord> {\n const key = async (value: unknown) =>\n typeof value === \"string\" ? transform(value) : value\n return {\n ...record,\n derivationKey: await key(record.derivationKey),\n connectedApps: await Promise.all(\n recordsOf(record.connectedApps).map(async (app) =>\n app.appSecret === undefined\n ? app\n : { ...app, appSecret: await key(app.appSecret) },\n ),\n ),\n postageStamps: await Promise.all(\n recordsOf(record.postageStamps).map(async (stamp) => ({\n ...stamp,\n signerKey: await key(stamp.signerKey),\n })),\n ),\n }\n}\n\n/**\n * The sign-out remnant of an account whose keys could not be opened: the\n * vault survives, so the person signs back in with their access method, and\n * nothing else is read. There is no `encryptedState` snapshot to keep, so the\n * sign-in restores the account from Swarm.\n */\nfunction signedOutForUnreadableKeys(record: JsonRecord): JsonRecord {\n return {\n id: record.id,\n name: record.name,\n createdAt: record.createdAt,\n access: record.access,\n encryptedSeed: record.encryptedSeed,\n signedOutAt: Date.now(),\n }\n}\n\n/**\n * The opened account, or its sign-out remnant when any key in it is not a\n * sealed value this page opened. A key stored in plain text counts as not\n * opened: there is no plain-text way in.\n */\nfunction openStoredKeys(record: JsonRecord): JsonRecord {\n if (!holdsKeys(record)) return record\n const opened = (value: unknown): string | undefined =>\n isSealedValue(value) ? openedValue(value) : undefined\n if (storedKeys(record).some((value) => opened(value) === undefined)) {\n return signedOutForUnreadableKeys(record)\n }\n const open = (value: unknown) =>\n value === undefined ? undefined : opened(value)\n return {\n ...record,\n derivationKey: open(record.derivationKey),\n connectedApps: recordsOf(record.connectedApps).map((app) =>\n app.appSecret === undefined\n ? app\n : { ...app, appSecret: open(app.appSecret) },\n ),\n postageStamps: recordsOf(record.postageStamps).map((stamp) => ({\n ...stamp,\n signerKey: open(stamp.signerKey),\n })),\n }\n}\n\n/** Whether this page could not reach the wrapping key for a key of this record. */\nfunction holdsUnavailableKeys(record: JsonRecord): boolean {\n return storedKeys(record).some(\n (value) => isSealedValue(value) && isSealedValueUnavailable(value),\n )\n}\n\n/**\n * The record to store for one being written. A signed-out record standing for\n * a stored one whose keys this page could not reach is the in-memory remnant\n * of `openStoredKeys`, and writing it would delete keys a later load can still\n * open, so the stored record is kept as it is. Keys this page seals with its\n * own key would replace them with keys no later load can open, so that write\n * fails instead and the stored record stays.\n */\nasync function recordToStore(\n record: JsonRecord,\n storedById: Map<unknown, JsonRecord>,\n): Promise<JsonRecord> {\n const stored = storedById.get(record.id)\n const keepsUnavailableKeys =\n stored !== undefined && holdsUnavailableKeys(stored)\n if (!holdsKeys(record)) return keepsUnavailableKeys ? stored : record\n if (keepsUnavailableKeys && (await sealsWithPageKey())) {\n throw new Error(\n \"This page cannot keep keys for a later visit, and the stored keys may still open on one\",\n )\n }\n return mapStoredKeys(record, sealValue)\n}\n\nconst accountKeySealing: RecordSealing = {\n seal: (records, stored) => {\n const storedById = new Map(\n recordsOf(stored).map((record) => [record.id, record]),\n )\n return Promise.all(\n records.map((record) => recordToStore(record, storedById)),\n )\n },\n open: (data) =>\n Array.isArray(data)\n ? data.map((record) =>\n isRecord(record) ? openStoredKeys(record) : record,\n )\n : data,\n prime: (data) =>\n primeSealedValues(\n recordsOf(data)\n .flatMap(storedKeys)\n .filter((value): value is string => isSealedValue(value)),\n ),\n}\n\n// ============================================================================\n// Storage Manager Factories\n// ============================================================================\n\n/**\n * Create storage manager for accounts\n */\nexport function createAccountsStorageManager(): VersionedStorageManager<Account> {\n return createLocalStorageManager<Account>({\n key: STORAGE_KEY_ACCOUNTS,\n currentVersion: 1,\n parsers: {\n 1: parseAccountsV1,\n },\n serializer: serializeAccount,\n loggerName: \"AccountsStorage\",\n sealing: accountKeySealing,\n })\n}\n\n// ============================================================================\n// Network Settings Storage (Singleton)\n// ============================================================================\n\n/**\n * Parse network settings - Zod validates URL format\n */\nfunction parseNetworkSettingsV1(data: unknown): NetworkSettings | undefined {\n const result = NetworkSettingsSchemaV1.safeParse(data)\n\n if (!result.success) {\n console.error(\n \"[NetworkSettingsStorage] Parse failed:\",\n result.error.format(),\n )\n return undefined\n }\n\n return result.data\n}\n\n/**\n * Serialize NetworkSettings for storage\n */\nexport function serializeNetworkSettings(\n settings: NetworkSettings,\n): Record<string, unknown> {\n return {\n beeNodeUrl: settings.beeNodeUrl,\n gnosisRpcUrl: settings.gnosisRpcUrl,\n }\n}\n\n/**\n * Singleton storage manager interface for network settings\n */\nexport interface NetworkSettingsStorageManager {\n load(): NetworkSettings | undefined\n save(settings: NetworkSettings): void\n clear(): void\n}\n\n/**\n * Create storage manager for network settings (singleton)\n * Unlike other storage managers, this stores a single object, not an array\n */\nexport function createNetworkSettingsStorageManager(): NetworkSettingsStorageManager {\n return {\n load(): NetworkSettings | undefined {\n if (typeof localStorage === \"undefined\") {\n return undefined\n }\n\n const raw = localStorage.getItem(STORAGE_KEY_NETWORK_SETTINGS)\n if (!raw) {\n return undefined\n }\n\n try {\n const parsed = JSON.parse(raw)\n return parseNetworkSettingsV1(parsed)\n } catch (e) {\n console.error(\n \"[NetworkSettingsStorage] Failed to parse stored data:\",\n e,\n )\n return undefined\n }\n },\n\n save(settings: NetworkSettings): void {\n if (typeof localStorage === \"undefined\") {\n console.warn(\"[NetworkSettingsStorage] localStorage not available\")\n return\n }\n\n const serialized = serializeNetworkSettings(settings)\n localStorage.setItem(\n STORAGE_KEY_NETWORK_SETTINGS,\n JSON.stringify(serialized),\n )\n },\n\n clear(): void {\n if (typeof localStorage === \"undefined\") {\n return\n }\n\n localStorage.removeItem(STORAGE_KEY_NETWORK_SETTINGS)\n },\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Account State Snapshot Module\n *\n * The in-memory account-state snapshot: what sync captures before building a\n * device view, and what the account bus carries as an `account-delta`.\n * `serializeAccountStateSnapshot` is the delta's wire form; the receiver\n * validates it with `AccountStateSnapshotSchemaV1` (`bus/messages.ts`).\n */\n\nimport type {\n ConnectedApp,\n PostageStamp,\n AccountMetadata,\n AccountStateSnapshot,\n SyncedAccount,\n} from \"../schemas\"\nimport {\n serializeConnectedApp,\n serializePostageStamp,\n} from \"./storage-managers\"\n\nexport type { AccountStateSnapshot } from \"../schemas\"\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst ACCOUNT_STATE_SNAPSHOT_VERSION = 1\n\n// ============================================================================\n// Build\n// ============================================================================\n\n/**\n * The account record as an account-state snapshot — the ONE assembly every\n * publisher shares (the sync coordinator's feed write, the proxy's feed write,\n * the proxy's `account-delta`, and the fold that consumes one).\n *\n * `timestamp` is the caller's clock for this snapshot, used for both the\n * envelope and `metadata.lastModified`: a publisher passes `Date.now()`, a\n * caller re-reading a record it did not just change passes that record's own\n * `lastModified` rather than restamping it.\n *\n * Every never-edited scalar clock falls back to the account's STABLE\n * `createdAt`, NEVER a fresh `Date.now()`: re-stamping an unchanged name /\n * default-stamp / settings on every publish would let a device that never\n * touched the field clobber a peer's genuine concurrent edit under per-field\n * LWW (docs/Account-State.md, \"Convergence rules\"). `createdAt` is identical\n * across devices and predates every edit,\n * so any real change still wins.\n *\n * `defaultPostageStampBatchID` may be absent: an account with no drives is a\n * supported state, and only the feed write (which needs a stamp to pay for\n * itself) may treat that as a reason not to publish.\n */\nexport function accountToStateSnapshot(\n account: SyncedAccount,\n accountId: string,\n timestamp: number,\n): AccountStateSnapshot {\n return {\n version: ACCOUNT_STATE_SNAPSHOT_VERSION,\n timestamp,\n accountId,\n metadata: {\n accountName: account.name,\n defaultPostageStampBatchID: account.defaultPostageStampBatchID?.toHex(),\n publicKey: account.publicKey,\n settings: account.settings,\n accountNameAt: account.accountNameAt ?? account.createdAt,\n defaultStampAt: account.defaultStampAt ?? account.createdAt,\n settingsAt: account.settingsAt ?? account.createdAt,\n createdAt: account.createdAt,\n lastModified: timestamp,\n devices: account.devices,\n partitionCount: account.partitionCount ?? 1,\n },\n connectedApps: account.connectedApps,\n postageStamps: account.postageStamps,\n }\n}\n\n// ============================================================================\n// Serialize\n// ============================================================================\n\n/**\n * Serialize account data into a plain object suitable for JSON encoding.\n */\nexport function serializeAccountStateSnapshot(input: {\n accountId: string\n metadata: AccountMetadata\n connectedApps: ConnectedApp[]\n postageStamps: PostageStamp[]\n timestamp: number\n}): Record<string, unknown> {\n return {\n version: ACCOUNT_STATE_SNAPSHOT_VERSION,\n timestamp: input.timestamp,\n accountId: input.accountId,\n metadata: {\n accountName: input.metadata.accountName,\n defaultPostageStampBatchID: input.metadata.defaultPostageStampBatchID,\n publicKey: input.metadata.publicKey,\n settings: input.metadata.settings,\n accountNameAt: input.metadata.accountNameAt,\n defaultStampAt: input.metadata.defaultStampAt,\n settingsAt: input.metadata.settingsAt,\n createdAt: input.metadata.createdAt,\n lastModified: input.metadata.lastModified,\n devices: input.metadata.devices,\n partitionCount: input.metadata.partitionCount,\n },\n connectedApps: input.connectedApps.map(serializeConnectedApp),\n postageStamps: input.postageStamps.map(serializePostageStamp),\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * What an `account-delta` carries, and what it deliberately does not.\n *\n * The two halves live together because they are one rule read forwards and\n * backwards: the publisher omits the per-context session fields, so every\n * receiver has to put its own back after the merge.\n */\n\nimport {\n accountToStateSnapshot,\n serializeAccountStateSnapshot,\n} from \"../utils/account-state-snapshot\"\nimport type { AccountStateSnapshot } from \"../utils/account-state-snapshot\"\nimport { portableConnectedApp } from \"../utils/storage-managers\"\nimport type { ConnectedApp, SyncedAccount } from \"../schemas\"\nimport type { AccountDeltaInput } from \"./messages\"\n\n/**\n * The wire form of an account snapshot: everything except the per-context\n * session material on each connected app.\n *\n * `appSecret` is stripped because the receivers are the account's other live\n * contexts, and one of them is a proxy iframe embedded by an arbitrary dApp\n * page. Handing dApp A's iframe dApp B's secret would give it strictly more\n * than the popup handshake does — `serializeSyncedAccount` strips exactly these\n * two fields for the same reason. `connectedUntil` goes with it: a session\n * deadline is local to the context that established it, and a peer's copy is\n * meaningless (worse, applying it would extend or truncate someone else's\n * session).\n *\n * No chat binding rides it either. Each one links the account to its chat on\n * one site, and the room is shared with frames embedded by every connected\n * site. The account's windows get bindings from storage and the synced feed,\n * and `mergeConnectedApps` keeps a binding an incoming entry lacks.\n *\n * Stamp signer keys stay, and so does the whole collection: the publisher does\n * not know which app each receiver is embedded by, so it cannot narrow. The\n * narrowing happens on receive instead — `applyAccountDelta` keeps only the\n * stamps the folded pointers name (#578) — which bounds what a partitioned\n * session HOLDS, not what reaches its room. Per-app deltas in a per-app room\n * would be the latter.\n */\nexport function accountDeltaSnapshot(\n account: SyncedAccount,\n): AccountDeltaInput[\"snapshot\"] {\n return accountDeltaFromSnapshot(\n accountToStateSnapshot(account, account.id.toHex(), Date.now()),\n )\n}\n\n/** The same wire form from a snapshot already assembled (the proxy's path). */\nexport function accountDeltaFromSnapshot(\n snapshot: AccountStateSnapshot,\n): AccountDeltaInput[\"snapshot\"] {\n const wire = serializeAccountStateSnapshot(snapshot)\n return {\n ...wire,\n connectedApps: (wire.connectedApps as Record<string, unknown>[]).map(\n (app) => {\n const { chatBinding: _chatBinding, ...rest } = portableConnectedApp(app)\n return rest\n },\n ),\n // Hand-written serialization, so the compiler cannot verify this matches\n // the schema's shape — a mismatched delta is dropped silently on receive.\n } as unknown as AccountDeltaInput[\"snapshot\"]\n}\n\n/**\n * Put this context's own session fields back after a merge.\n *\n * `mergeConnectedApps` is last-writer-wins on the whole entry, so a merely\n * newer incoming entry (a rename, an icon change, a reconnect elsewhere)\n * would otherwise carry the wire's stripped `appSecret`/`connectedUntil` and\n * silently log this session out of an app nobody revoked. Local session\n * fields win — unless the incoming entry marks the session as ended: a\n * `revokedAt` tombstone (\"Remove\"), or a `disconnectedAt` newer than our own\n * `lastConnectedAt` (a plain \"Disconnect\", which is not a tombstone but is\n * newer than the session we hold). Either way the incoming entry's cleared\n * fields stand and the session ends.\n *\n * Ours WINS over anything incoming, rather than filling a gap. Under the wire\n * contract there is nothing to lose — the incoming entry has neither field —\n * but a session's own credential and deadline are not a peer's to set, so\n * preferring the incoming value would put a forgetful publisher (or anything\n * that reached the room) one message away from replacing them. The receive\n * schema already drops both; this is the same rule stated where it is used.\n */\nexport function restoreLocalSessionFields(\n merged: ConnectedApp[],\n local: ConnectedApp[],\n): ConnectedApp[] {\n const mine = new Map(local.map((app) => [app.appUrl, app]))\n return merged.map((app) => {\n if (app.revokedAt) return app\n const ours = mine.get(app.appUrl)\n if (!ours) return app\n if (\n app.disconnectedAt !== undefined &&\n app.disconnectedAt > ours.lastConnectedAt\n ) {\n return app\n }\n return {\n ...app,\n appSecret: ours.appSecret ?? app.appSecret,\n connectedUntil: ours.connectedUntil ?? app.connectedUntil,\n }\n })\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Account-derived bus context for off-origin transports.\n *\n * The topic names the account's room on the signaling server without being\n * linkable to the account (HMAC of the derivation key), and the encryption\n * key end-to-end encrypts every envelope — the server only ever sees\n * ciphertext. Both are derivable by exactly the contexts that hold the\n * account's `derivationKey`.\n */\n\nimport { deriveAesGcmKey, deriveSecret } from \"../utils/key-derivation\"\n\nconst BUS_TOPIC_CONTEXT = \"swarm-id/account-bus/topic/v1\"\nconst BUS_ENCRYPTION_CONTEXT = \"swarm-id/account-bus/encryption/v1\"\n\nexport interface BusContext {\n /** 64-hex room identifier, unlinkable to the account id. */\n topic: string\n /** AES-GCM-256 key for envelope encryption. */\n encryptionKey: CryptoKey\n}\n\nexport async function deriveBusContext(\n derivationKey: string,\n): Promise<BusContext> {\n const topic = await deriveSecret(derivationKey, BUS_TOPIC_CONTEXT)\n const encryptionKey = await deriveAesGcmKey(\n derivationKey,\n BUS_ENCRYPTION_CONTEXT,\n )\n return { topic, encryptionKey }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Who is live on the account bus, as seen from one context.\n *\n * Every context in the room publishes a `presence` on join and every\n * `PRESENCE_INTERVAL_MS`; a receiver notes the time it heard each device. A\n * device unheard for `PRESENCE_MAX_AGE_MS` is no longer live. That is the whole\n * mechanism: the bus already knows who is in the room, this only ties room\n * membership to a `deviceId`.\n *\n * A clean departure does not wait for the window. The signaling server sends\n * `peer-left` when a socket closes — including one its own reaper terminated,\n * so a crash and a dropped network count too — and `forgetPeer` drops the\n * device that socket carried (#572). Ageing is the backstop for what that\n * cannot see: a peer the server has not yet reaped, and a device heard only\n * over the local transport. A leave message of our own is not an option — a\n * publish encrypts before it sends, and a page being torn down never gets\n * back to the send.\n *\n * In memory only, and deliberately never persisted or published as state. A\n * `lastSeenAt` that rode the account snapshot would make every publish a byte\n * change to fold and re-persist, and the unpartitioned proxy answers every\n * storage event with a bus delta and a stamped feed write — a loop that never\n * settles, even on one device. Durable truth (`lastSignedInAt`) keeps meaning\n * \"signed in\"; liveness lives here and is rebuilt from scratch on every load.\n */\n\nexport const PRESENCE_INTERVAL_MS = 20_000\n\n/** What a hidden tab's interval decays to: Chrome clamps a backgrounded page's\n * timers to about once a minute (intensive throttling). */\nconst THROTTLED_INTERVAL_MS = 60_000\n\n/**\n * Three missed beats — of the THROTTLED interval, not ours. A window of three\n * of our own beats sits exactly at a hidden tab's clamped cadence, so a\n * backgrounded-but-live device would flap in and out of its peers' live sets:\n * a blinking badge, and a coordinator repeatedly re-including the same peer in\n * an idle-yield round. Being slow to drop a device that really left costs only\n * an absent intent read, which is why the window errs long. Still well past\n * `LEASE_TTL_MS` and far inside `KNOWN_DEVICE_MAX_AGE_MS`.\n */\nexport const PRESENCE_MAX_AGE_MS = 3 * THROTTLED_INTERVAL_MS\n\n/** When a device was last heard, and over which of the room's sockets. */\ninterface Seen {\n at: number\n peers: Set<string>\n}\n\nexport class PresenceTracker {\n private seen = new Map<string, Seen>()\n\n /**\n * Note a beat. `peerId` is the transport's own name for whoever sent it —\n * present on the signaling transport, absent on the local one, which has no\n * peers. It is what lets a departure be attributed to a device (#572).\n */\n observe(deviceId: string, nowMs: number, peerId?: string): void {\n const entry = this.seen.get(deviceId) ?? { at: nowMs, peers: new Set() }\n entry.at = nowMs\n if (peerId !== undefined) entry.peers.add(peerId)\n this.seen.set(deviceId, entry)\n }\n\n /**\n * A socket left the room, so whatever it carried is gone with it.\n *\n * Only when it was the device's LAST socket: two tabs of one dApp share a\n * partition, and therefore a `deviceId`, while holding a socket each — one\n * of them closing is not the device leaving. A device heard only over the\n * local transport has no socket to lose and is left to age out.\n *\n * Every entry is checked rather than stopping at the first match. Nothing\n * here enforces that one socket only ever beats under one `deviceId`, and a\n * device left holding a dead socket id could only age out — the three-minute\n * wait this exists to remove. The loop is over a handful of entries.\n */\n forgetPeer(peerId: string): void {\n for (const [deviceId, entry] of this.seen) {\n if (!entry.peers.delete(peerId)) continue\n if (entry.peers.size === 0) this.seen.delete(deviceId)\n }\n }\n\n /** Devices heard within `PRESENCE_MAX_AGE_MS` of `nowMs`. */\n liveDeviceIds(nowMs: number): string[] {\n const cutoff = nowMs - PRESENCE_MAX_AGE_MS\n return [...this.seen]\n .filter(([, entry]) => entry.at >= cutoff)\n .map(([deviceId]) => deviceId)\n }\n\n clear(): void {\n this.seen.clear()\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * The signaling wire protocol, shared by both ends (#700): the server parses\n * `ClientMessageSchema` and composes `ServerMessage`, the client transport in\n * `lib/src/bus/signaling-transport.ts` does the reverse. Zod only — no `ws`,\n * no `node:*` — so the browser bundle can inline it.\n */\n\nimport { z } from 'zod'\n\nexport const ClientMessageSchema = z.discriminatedUnion('type', [\n z.object({ type: z.literal('join'), topic: z.string() }),\n z.object({ type: z.literal('relay'), to: z.string(), payload: z.string() }),\n z.object({ type: z.literal('signal'), to: z.string(), payload: z.unknown() }),\n])\nexport type ClientMessage = z.infer<typeof ClientMessageSchema>\n\nexport const ServerMessageSchema = z.discriminatedUnion('type', [\n z.object({ type: z.literal('welcome'), peerId: z.string(), peers: z.array(z.string()) }),\n z.object({ type: z.literal('peer-joined'), peerId: z.string() }),\n z.object({ type: z.literal('peer-left'), peerId: z.string() }),\n z.object({ type: z.literal('relay'), from: z.string(), payload: z.string() }),\n z.object({ type: z.literal('signal'), from: z.string(), payload: z.unknown() }),\n])\nexport type ServerMessage = z.infer<typeof ServerMessageSchema>\n\n/**\n * The client treats this as permanent and stops reconnecting for good, so it is\n * reserved for the one thing that will never come right: a topic this server\n * refuses and would refuse identically on every retry. Everything else — load,\n * an exhausted budget — closes with 1013.\n */\nexport const WS_CLOSE_POLICY_VIOLATION = 1008\n/** Transient — the client backs off and comes back. */\nexport const WS_CLOSE_TRY_AGAIN_LATER = 1013\n/**\n * A socket that never named a room (`preJoinTimeoutMs`). Its own code rather\n * than 1008, because 1008 already means \"this server predates the join frame\"\n * to a client, which answers it by putting the topic back in the URL for the\n * life of the page — the exact thing #577 removes. Application range (4000+),\n * numbered after HTTP 408 so it reads as what it is; a client that does not\n * know it backs off and retries, which is the right answer for a socket that\n * failed to speak in time.\n */\nexport const WS_CLOSE_JOIN_TIMEOUT = 4408\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * AES-GCM-256 envelope for the account-bus signaling transport: every payload\n * that crosses the relay is sealed under the bus encryption key from\n * `deriveBusContext`, so the signaling server only ever carries ciphertext.\n */\n\nconst IV_LENGTH_BYTES = 12\n\n/**\n * Encrypt a plaintext string. Returns base64-encoded [IV (12 bytes) || ciphertext+tag].\n */\nexport async function encryptEnvelope(\n plaintext: string,\n key: CryptoKey,\n): Promise<string> {\n const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH_BYTES))\n const encoded = new TextEncoder().encode(plaintext)\n\n const ciphertextBuffer = await crypto.subtle.encrypt(\n { name: \"AES-GCM\", iv },\n key,\n encoded,\n )\n\n const combined = new Uint8Array(iv.length + ciphertextBuffer.byteLength)\n combined.set(iv, 0)\n combined.set(new Uint8Array(ciphertextBuffer), iv.length)\n\n return uint8ArrayToBase64(combined)\n}\n\n/**\n * Decrypt a base64-encoded [IV (12 bytes) || ciphertext+tag]. Throws on a wrong\n * key or a tampered payload, as AES-GCM does.\n */\nexport async function decryptEnvelope(\n ciphertextBase64: string,\n key: CryptoKey,\n): Promise<string> {\n const combined = base64ToUint8Array(ciphertextBase64)\n const iv = combined.slice(0, IV_LENGTH_BYTES)\n const ciphertext = combined.slice(IV_LENGTH_BYTES)\n\n const plaintextBuffer = await crypto.subtle.decrypt(\n { name: \"AES-GCM\", iv },\n key,\n ciphertext,\n )\n\n return new TextDecoder().decode(plaintextBuffer)\n}\n\nfunction uint8ArrayToBase64(bytes: Uint8Array): string {\n let binary = \"\"\n for (const byte of bytes) {\n binary += String.fromCharCode(byte)\n }\n return btoa(binary)\n}\n\nfunction base64ToUint8Array(base64: string): Uint8Array {\n const binary = atob(base64)\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Cross-partition/cross-device bus transport via the self-hosted signaling\n * server (docs/Account-Bus.md, phase 2; server: `signaling/`).\n *\n * Every envelope is end-to-end AES-GCM encrypted with the account-derived bus\n * key before it leaves the context — the server relays ciphertext only. When a\n * `createPeerConnection` factory is provided, peer pairs opportunistically\n * upgrade to a WebRTC DataChannel (the newcomer initiates, so there is no\n * offer glare) and envelopes flow peer-to-peer; the relay remains the per-peer\n * fallback whenever no channel is open.\n */\n\nimport { z } from \"zod\"\nimport {\n ServerMessageSchema,\n WS_CLOSE_POLICY_VIOLATION,\n} from \"@swarm-id/signaling/protocol\"\nimport type { ClientMessage } from \"@swarm-id/signaling/protocol\"\n\nimport type { BusTransport } from \"./account-bus\"\nimport type { BusMessageInput } from \"./messages\"\nimport { encryptEnvelope, decryptEnvelope } from \"./envelope\"\n\nconst SignalPayloadSchema = z.discriminatedUnion(\"kind\", [\n z.object({ kind: z.literal(\"offer\"), sdp: z.string() }),\n z.object({ kind: z.literal(\"answer\"), sdp: z.string() }),\n z.object({ kind: z.literal(\"ice\"), candidate: z.unknown() }),\n])\n\nconst RECONNECT_BASE_DELAY_MS = 1000\nconst RECONNECT_MAX_DELAY_MS = 30000\nconst DATA_CHANNEL_LABEL = \"bus\"\n\nexport interface SignalingTransportOptions {\n /** Signaling server URL, e.g. `wss://bus.example.com/bus`. */\n url: string\n /** Account-derived room topic (`deriveBusContext`). */\n topic: string\n /** Account-derived AES-GCM envelope key (`deriveBusContext`). */\n encryptionKey: CryptoKey\n /**\n * Factory for the WebRTC upgrade; omit to stay relay-only (e.g. in\n * environments without RTCPeerConnection). No STUN/TURN is configured by\n * default: same-device loopback and LAN peers connect via host candidates,\n * everything else falls back to the relay.\n */\n createPeerConnection?: () => RTCPeerConnection\n}\n\ninterface PeerState {\n connection?: RTCPeerConnection\n dataChannel?: RTCDataChannel\n /** True once this connection's remote description is set. `addIceCandidate`\n * rejects before that, so candidates arriving first have to wait. */\n remoteDescriptionSet: boolean\n /** Candidates that arrived before the remote description, replayed once it\n * lands. Signals are handled concurrently (`void this.handleSignal(...)`),\n * so an `ice` message routinely reaches us mid-negotiation — without this\n * the candidate was logged and dropped, and the pair silently fell back to\n * the server relay it was trying to stop using.\n *\n * Neither field is optional: a `?.push` on the queue would drop a candidate\n * in exactly the state the queue is for, so every `PeerState` is born with\n * an empty one. */\n pendingCandidates: RTCIceCandidateInit[]\n}\n\n/** A peer we have just heard of: no connection yet, and an empty queue rather\n * than an absent one, so a candidate arriving first has somewhere to go. */\nfunction newPeerState(): PeerState {\n return { remoteDescriptionSet: false, pendingCandidates: [] }\n}\n\nexport class SignalingTransport implements BusTransport {\n readonly local = false\n private options: SignalingTransportOptions\n private socket: WebSocket | undefined\n private peers = new Map<string, PeerState>()\n private handlers = new Set<(raw: unknown, from?: string) => void>()\n private peerLeftHandlers = new Set<(peerId: string) => void>()\n private closed = false\n private reconnectDelayMs = RECONNECT_BASE_DELAY_MS\n private reconnectTimer: ReturnType<typeof setTimeout> | undefined\n /** In-flight `deliver` calls, awaited by `close()` so a teardown\n * announcement published in the closing tick still reaches the wire. */\n private pendingDeliveries = new Set<Promise<void>>()\n\n constructor(options: SignalingTransportOptions) {\n this.options = options\n this.connect()\n }\n\n /** Live peers currently visible in the room (test/debug aid). */\n get peerCount(): number {\n return this.peers.size\n }\n\n publish(message: BusMessageInput): void {\n const delivery = this.deliver(message).catch((error) => {\n console.error(\"[SignalingTransport] Publish failed:\", error)\n })\n this.pendingDeliveries.add(delivery)\n void delivery.finally(() => this.pendingDeliveries.delete(delivery))\n }\n\n subscribe(handler: (raw: unknown, from?: string) => void): () => void {\n this.handlers.add(handler)\n return () => this.handlers.delete(handler)\n }\n\n onPeerLeft(handler: (peerId: string) => void): () => void {\n this.peerLeftHandlers.add(handler)\n return () => this.peerLeftHandlers.delete(handler)\n }\n\n /**\n * Stops reconnecting at once, but lets in-flight publishes reach the wire\n * first: `deliver` awaits envelope encryption, and callers announce a\n * teardown (`lease-released`) and close the bus in the same tick, so tearing\n * the socket down synchronously would drop exactly the message a waiting\n * peer is owed. Each pending delivery is a single encrypt plus synchronous\n * sends, so this settles promptly — it is not a flush of a send queue.\n */\n close(): void {\n this.closed = true\n if (this.reconnectTimer !== undefined) {\n clearTimeout(this.reconnectTimer)\n }\n if (this.pendingDeliveries.size === 0) {\n this.shutdown()\n return\n }\n void Promise.allSettled([...this.pendingDeliveries]).then(() =>\n this.shutdown(),\n )\n }\n\n private shutdown(): void {\n this.handlers.clear()\n // Our own socket going is not a report that anyone else left, so these are\n // dropped rather than fired: the tracker ages the room out, and a\n // reconnect relearns it from the peers' own beats.\n this.peerLeftHandlers.clear()\n for (const peer of this.peers.values()) {\n peer.connection?.close()\n }\n this.peers.clear()\n this.socket?.close()\n }\n\n // ==========================================================================\n // Socket lifecycle\n // ==========================================================================\n\n private connect(): void {\n // The topic travels in the first frame, not the query string: query strings\n // are recorded by every ingress and proxy access log as a matter of course,\n // and the topic IS the room capability — anything reading those logs can\n // join (#577). Payload confidentiality is unaffected either way (topic and\n // envelope key come from separate HMAC contexts); what a topic buys is\n // membership: presence, traffic patterns, ciphertext to keep, and a\n // position to inject from.\n const socket = new WebSocket(this.options.url)\n this.socket = socket\n\n socket.addEventListener(\"open\", () => {\n this.sendToServer({ type: \"join\", topic: this.options.topic })\n })\n\n socket.addEventListener(\"message\", (event) => {\n // Backoff resets here, not on `open`. The server accepts the socket and\n // only then closes it on a policy or payload violation, so resetting on\n // `open` made every such failure reconnect at the 1 s floor forever. A\n // message received is proof the connection was actually useful.\n this.reconnectDelayMs = RECONNECT_BASE_DELAY_MS\n void this.handleServerMessage(String(event.data))\n })\n socket.addEventListener(\"close\", (event) => {\n // A policy close is permanent for this topic (the server only sends it\n // for a topic it will reject identically next time); retrying is a hot\n // loop against an answer that will not change.\n if (event.code === WS_CLOSE_POLICY_VIOLATION) {\n console.error(\n \"[SignalingTransport] Signaling server rejected the topic; not reconnecting.\",\n )\n this.closed = true\n return\n }\n this.scheduleReconnect()\n })\n socket.addEventListener(\"error\", () => {\n // The close event follows and drives the reconnect.\n })\n }\n\n private scheduleReconnect(): void {\n if (this.closed) return\n for (const peer of this.peers.values()) {\n peer.connection?.close()\n }\n this.peers.clear()\n // Equal jitter. Every client connected when the service restarts — a\n // deploy, an OOM — schedules its retry from the same instant, so an\n // undithered delay brings them all back in lockstep at exactly the moment\n // the server can least take it. Spread the wait over the second half of\n // the window; the undithered sequence stays the backoff of record so the\n // doubling is still 1s → 2s → 4s.\n const jittered =\n this.reconnectDelayMs / 2 + Math.random() * (this.reconnectDelayMs / 2)\n this.reconnectTimer = setTimeout(() => this.connect(), jittered)\n this.reconnectDelayMs = Math.min(\n this.reconnectDelayMs * 2,\n RECONNECT_MAX_DELAY_MS,\n )\n }\n\n // ==========================================================================\n // Sending\n // ==========================================================================\n\n private async deliver(message: BusMessageInput): Promise<void> {\n if (this.peers.size === 0) return\n const ciphertext = await encryptEnvelope(\n JSON.stringify(message),\n this.options.encryptionKey,\n )\n for (const [peerId, peer] of this.peers) {\n // Per peer, and never fatal to the rest of the fan-out: a `send` that\n // throws (SCTP size limit, a channel torn down between the readyState\n // read and the call) used to escape this loop and starve every peer\n // after it — for a teardown `lease-released` that costs the waiter its\n // whole poll interval. A failed channel falls through to the relay.\n if (peer.dataChannel?.readyState === \"open\") {\n try {\n peer.dataChannel.send(ciphertext)\n continue\n } catch (error) {\n console.warn(\n \"[SignalingTransport] DataChannel send failed; relaying:\",\n error,\n )\n }\n }\n this.sendToServer({ type: \"relay\", to: peerId, payload: ciphertext })\n }\n }\n\n private sendToServer(message: ClientMessage): void {\n if (this.socket?.readyState === WebSocket.OPEN) {\n this.socket.send(JSON.stringify(message))\n }\n }\n\n // ==========================================================================\n // Receiving\n // ==========================================================================\n\n private async handleServerMessage(data: string): Promise<void> {\n let parsed: unknown\n try {\n parsed = JSON.parse(data)\n } catch {\n return\n }\n const result = ServerMessageSchema.safeParse(parsed)\n if (!result.success) return\n const message = result.data\n\n switch (message.type) {\n case \"welcome\":\n for (const peerId of message.peers) {\n this.peers.set(peerId, newPeerState())\n // The newcomer (us) initiates toward every existing peer.\n void this.initiatePeerConnection(peerId)\n }\n return\n case \"peer-joined\":\n // The joiner initiates; we just track it.\n this.peers.set(message.peerId, newPeerState())\n return\n case \"peer-left\": {\n const peer = this.peers.get(message.peerId)\n peer?.connection?.close()\n this.peers.delete(message.peerId)\n // Reported, not just cleaned up: this is the only departure signal a\n // closing tab produces (why, in `PresenceTracker`'s header). The\n // server's reaper closes a dead socket too, so a crash and a dropped\n // network arrive here as well.\n for (const handler of this.peerLeftHandlers) {\n handler(message.peerId)\n }\n return\n }\n case \"relay\":\n await this.receiveEnvelope(message.payload, message.from)\n return\n case \"signal\":\n await this.handleSignal(message.from, message.payload)\n return\n }\n }\n\n private async receiveEnvelope(\n ciphertext: string,\n from: string,\n ): Promise<void> {\n let plaintext: string\n try {\n plaintext = await decryptEnvelope(ciphertext, this.options.encryptionKey)\n } catch {\n // Wrong key or corrupted payload — not for us.\n return\n }\n let raw: unknown\n try {\n raw = JSON.parse(plaintext)\n } catch {\n return\n }\n for (const handler of this.handlers) {\n handler(raw, from)\n }\n }\n\n // ==========================================================================\n // WebRTC upgrade\n // ==========================================================================\n\n private async initiatePeerConnection(peerId: string): Promise<void> {\n const create = this.options.createPeerConnection\n if (!create) return\n try {\n const connection = create()\n const peer = this.peers.get(peerId)\n if (!peer) return\n this.adoptConnection(peer, connection)\n this.adoptDataChannel(\n connection.createDataChannel(DATA_CHANNEL_LABEL),\n peer,\n peerId,\n )\n this.forwardIceCandidates(connection, peerId)\n const offer = await connection.createOffer()\n await connection.setLocalDescription(offer)\n this.sendSignal(peerId, { kind: \"offer\", sdp: offer.sdp ?? \"\" })\n } catch (error) {\n console.error(\"[SignalingTransport] WebRTC initiate failed:\", error)\n }\n }\n\n private async handleSignal(from: string, payload: unknown): Promise<void> {\n const create = this.options.createPeerConnection\n if (!create) return\n const result = SignalPayloadSchema.safeParse(payload)\n if (!result.success) return\n const signal = result.data\n const peer = this.peers.get(from)\n if (!peer) return\n\n try {\n if (signal.kind === \"offer\") {\n const connection = create()\n this.adoptConnection(peer, connection)\n connection.ondatachannel = (event) => {\n this.adoptDataChannel(event.channel, peer, from)\n }\n this.forwardIceCandidates(connection, from)\n await connection.setRemoteDescription({\n type: \"offer\",\n sdp: signal.sdp,\n })\n await this.flushPendingCandidates(peer, connection)\n const answer = await connection.createAnswer()\n await connection.setLocalDescription(answer)\n this.sendSignal(from, { kind: \"answer\", sdp: answer.sdp ?? \"\" })\n return\n }\n if (signal.kind === \"answer\") {\n // An answer for no connection describes nothing, so it must not mark\n // the queue as flushable either — the optional chain used to make this\n // a no-op that still set the flag.\n const connection = peer.connection\n if (!connection) return\n await connection.setRemoteDescription({\n type: \"answer\",\n sdp: signal.sdp,\n })\n await this.flushPendingCandidates(peer, connection)\n return\n }\n // Queue rather than apply while the sibling offer/answer handler is\n // still awaiting its `setRemoteDescription`. The check and the push are\n // one synchronous step, so they cannot interleave with the flush.\n if (!peer.remoteDescriptionSet) {\n peer.pendingCandidates.push(signal.candidate as RTCIceCandidateInit)\n return\n }\n await peer.connection?.addIceCandidate(\n signal.candidate as RTCIceCandidateInit,\n )\n } catch (error) {\n console.error(\"[SignalingTransport] WebRTC signal failed:\", error)\n }\n }\n\n /**\n * Install a freshly created connection on a peer, closing whatever was there.\n * A duplicate or renegotiated offer used to overwrite the field and leak the\n * previous `RTCPeerConnection`, which keeps its ICE agent and sockets alive.\n * The candidate queue resets with it: the new connection has no remote\n * description, and candidates gathered for the old one are meaningless.\n */\n private adoptConnection(\n peer: PeerState,\n connection: RTCPeerConnection,\n ): void {\n peer.connection?.close()\n peer.connection = connection\n peer.remoteDescriptionSet = false\n peer.pendingCandidates = []\n }\n\n /**\n * Replay the candidates that arrived before the remote description. One bad\n * candidate must not strand the rest, so each is applied on its own.\n *\n * Takes the connection whose description just landed, and does nothing if the\n * peer has moved on to another: signals are handled concurrently, so a second\n * offer can be adopted while this one is still awaiting\n * `setRemoteDescription`, and marking the flag then would describe a\n * connection that has no remote description — re-opening the drop window this\n * queue exists to close, for candidates that arrive before the new\n * negotiation finishes.\n */\n private async flushPendingCandidates(\n peer: PeerState,\n connection: RTCPeerConnection,\n ): Promise<void> {\n if (peer.connection !== connection) return\n peer.remoteDescriptionSet = true\n const pending = peer.pendingCandidates\n peer.pendingCandidates = []\n for (const candidate of pending) {\n try {\n await connection.addIceCandidate(candidate)\n } catch (error) {\n console.warn(\"[SignalingTransport] ICE candidate rejected:\", error)\n }\n }\n }\n\n private adoptDataChannel(\n channel: RTCDataChannel,\n peer: PeerState,\n peerId: string,\n ): void {\n peer.dataChannel = channel\n channel.onmessage = (event) => {\n // One channel per peer, so who sent it is settled by which channel it\n // arrived on — the relay path is told by the server instead.\n void this.receiveEnvelope(String(event.data), peerId)\n }\n }\n\n private forwardIceCandidates(\n connection: RTCPeerConnection,\n peerId: string,\n ): void {\n connection.onicecandidate = (event) => {\n if (event.candidate) {\n this.sendSignal(peerId, {\n kind: \"ice\",\n candidate: event.candidate.toJSON(),\n })\n }\n }\n }\n\n private sendSignal(\n to: string,\n payload: z.infer<typeof SignalPayloadSchema>,\n ): void {\n this.sendToServer({ type: \"signal\", to, payload })\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Debounced Utilization Uploader\n *\n * Batches multiple utilization updates together to minimize upload frequency.\n * Uses a per-batch debounce mechanism with configurable delay.\n */\n\nimport type { DirtyChunkTracker } from \"../utils/batch-utilization\"\n\n/**\n * Default debounce delay in milliseconds\n */\nconst DEFAULT_DEBOUNCE_DELAY_MS = 1000\n\n/**\n * Pending upload task for a batch\n */\ninterface PendingUpload {\n /** Dirty chunk tracker accumulating changes */\n tracker: DirtyChunkTracker\n\n /** Timer ID for debounce */\n timerId: ReturnType<typeof setTimeout> | undefined\n\n /** Upload function to execute */\n uploadFn: () => Promise<void>\n\n /** Promise resolvers for awaiting completion */\n promise: { resolve: () => void; reject: (error: Error) => void }\n}\n\n/**\n * Debounced uploader for batch utilization data\n *\n * Batches multiple updates within a time window and uploads only once.\n * Each batch has its own independent debounce timer.\n */\nexport class DebouncedUtilizationUploader {\n private pendingUploads = new Map<string, PendingUpload>()\n private defaultDelay: number\n\n /**\n * Create a new debounced uploader\n * @param delay - Debounce delay in milliseconds (default: 1000ms = 1s)\n */\n constructor(delay = DEFAULT_DEBOUNCE_DELAY_MS) {\n this.defaultDelay = delay\n }\n\n /**\n * Schedule an upload for a batch (debounced)\n *\n * If multiple updates occur within the debounce window, they are merged\n * and only one upload is performed.\n *\n * @param batchId - Batch ID (hex string)\n * @param tracker - Dirty chunk tracker with current changes\n * @param uploadFn - Function to execute upload\n * @param delay - Optional custom delay for this upload\n * @returns Promise that resolves when upload completes\n */\n scheduleUpload(\n batchId: string,\n tracker: DirtyChunkTracker,\n uploadFn: () => Promise<void>,\n delay?: number,\n ): Promise<void> {\n const actualDelay = delay ?? this.defaultDelay\n\n // Cancel existing timer if present\n const existing = this.pendingUploads.get(batchId)\n if (existing?.timerId) {\n clearTimeout(existing.timerId)\n // Reject old promise\n existing.promise.reject(new Error(\"Upload cancelled by new request\"))\n }\n\n // Merge with existing tracker or create new\n const mergedTracker = existing?.tracker ?? tracker\n\n // If we're merging with an existing tracker, copy dirty chunks\n if (existing && existing.tracker !== tracker) {\n for (const chunkIndex of tracker.getDirtyChunks()) {\n mergedTracker.markDirty(chunkIndex * 1024) // Mark any bucket in the chunk\n }\n }\n\n // Create new promise\n return new Promise<void>((resolve, reject) => {\n // Schedule new upload\n const timerId = setTimeout(async () => {\n try {\n await uploadFn()\n resolve()\n } catch (error) {\n console.error(\n `[DebouncedUploader] Upload failed for batch ${batchId}:`,\n error,\n )\n reject(error instanceof Error ? error : new Error(String(error)))\n } finally {\n // Remove from pending\n this.pendingUploads.delete(batchId)\n }\n }, actualDelay)\n\n // Store pending upload\n this.pendingUploads.set(batchId, {\n tracker: mergedTracker,\n timerId,\n uploadFn,\n promise: { resolve, reject },\n })\n })\n }\n\n /**\n * Flush pending upload for a batch immediately (cancel debounce)\n * @param batchId - Batch ID to flush\n */\n async flush(batchId: string): Promise<void> {\n const pending = this.pendingUploads.get(batchId)\n if (!pending) {\n return\n }\n\n // Cancel timer\n if (pending.timerId) {\n clearTimeout(pending.timerId)\n }\n\n // Execute immediately\n try {\n await pending.uploadFn()\n } finally {\n this.pendingUploads.delete(batchId)\n }\n }\n\n /**\n * Flush all pending uploads immediately\n */\n async flushAll(): Promise<void> {\n const batchIds = Array.from(this.pendingUploads.keys())\n\n await Promise.all(batchIds.map((batchId) => this.flush(batchId)))\n }\n\n /**\n * Cancel pending upload for a batch (discard changes)\n * @param batchId - Batch ID to cancel\n */\n cancel(batchId: string): void {\n const pending = this.pendingUploads.get(batchId)\n if (!pending) {\n return\n }\n\n if (pending.timerId) {\n clearTimeout(pending.timerId)\n }\n\n this.pendingUploads.delete(batchId)\n }\n\n /**\n * Cancel all pending uploads (discard all changes)\n */\n cancelAll(): void {\n for (const pending of this.pendingUploads.values()) {\n if (pending.timerId) {\n clearTimeout(pending.timerId)\n }\n }\n\n this.pendingUploads.clear()\n }\n\n /**\n * Get count of pending uploads\n */\n getPendingCount(): number {\n return this.pendingUploads.size\n }\n\n /**\n * Check if a batch has a pending upload\n */\n hasPending(batchId: string): boolean {\n return this.pendingUploads.has(batchId)\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Seed encryption at rest. The BIP-39 entropy is AES-GCM encrypted with a key\n * derived from the chosen access method (passkey PRF, wallet signature, or\n * password); the access method can always re-derive the key, so only the\n * ciphertext is persisted.\n *\n * Lives in the lib, not the UI, because the ciphertext is part of the on-disk\n * account record (`LocalVaultSchemaV1.encryptedSeed`) and a host app that\n * writes that record from outside the browser (an Electron shell that creates\n * an account for the user, #815) has to produce the same bytes. The\n * parameters below are the contract the docs' \"Storage layout\" page states.\n * One input is normalised elsewhere: the canonical form of a wallet signature\n * (`v` as 27/28) is ui's `crypto/signature.ts`, since it needs viem.\n */\nimport { hexToUint8Array, uint8ArrayToHex } from \"./hex\"\n\nconst AES_GCM = \"AES-GCM\"\nconst AES_KEY_BITS = 256\nconst IV_LENGTH = 12\nconst SALT_LENGTH = 32\nconst PBKDF2_ITERATIONS = 600_000\nconst SEED_ENCRYPTION_INFO = \"swarm-id-seed-encryption-v1\"\n\nexport function randomSalt(): Uint8Array {\n const salt = new Uint8Array(SALT_LENGTH)\n crypto.getRandomValues(salt)\n return salt\n}\n\n/** Encrypt bytes; returns hex of IV || ciphertext. */\nexport async function encryptSeed(\n data: Uint8Array,\n key: CryptoKey,\n): Promise<string> {\n const iv = new Uint8Array(IV_LENGTH)\n crypto.getRandomValues(iv)\n const ciphertext = await crypto.subtle.encrypt(\n { name: AES_GCM, iv: iv as BufferSource },\n key,\n data as BufferSource,\n )\n const payload = new Uint8Array(IV_LENGTH + ciphertext.byteLength)\n payload.set(iv)\n payload.set(new Uint8Array(ciphertext), IV_LENGTH)\n return uint8ArrayToHex(payload)\n}\n\nexport async function decryptSeed(\n payloadHex: string,\n key: CryptoKey,\n): Promise<Uint8Array> {\n const payload = hexToUint8Array(payloadHex)\n const iv = payload.slice(0, IV_LENGTH)\n const ciphertext = payload.slice(IV_LENGTH)\n const data = await crypto.subtle.decrypt(\n { name: AES_GCM, iv: iv as BufferSource },\n key,\n ciphertext as BufferSource,\n )\n return new Uint8Array(data)\n}\n\n/**\n * Derive an AES-GCM key from secret input keying material (HKDF-SHA256).\n * The IKM must be secret — anything recoverable by a third party (addresses,\n * public keys) gives no protection.\n */\nexport async function deriveKeyFromSecret(\n ikm: Uint8Array,\n salt: Uint8Array,\n info: string,\n): Promise<CryptoKey> {\n const key = await crypto.subtle.importKey(\n \"raw\",\n ikm as BufferSource,\n \"HKDF\",\n false,\n [\"deriveKey\"],\n )\n return crypto.subtle.deriveKey(\n {\n name: \"HKDF\",\n hash: \"SHA-256\",\n salt: salt as BufferSource,\n info: new TextEncoder().encode(info),\n },\n key,\n { name: AES_GCM, length: AES_KEY_BITS },\n false,\n [\"encrypt\", \"decrypt\"],\n )\n}\n\n/** Derive the seed-encryption key from a passkey PRF output (HKDF). */\nexport function deriveKeyFromPrf(prfOutput: Uint8Array): Promise<CryptoKey> {\n return deriveKeyFromSecret(prfOutput, new Uint8Array(), SEED_ENCRYPTION_INFO)\n}\n\n/** Derive the seed-encryption key from a wallet message signature + stored salt (HKDF). */\nexport function deriveKeyFromSignature(\n signature: Uint8Array,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n return deriveKeyFromSecret(signature, salt, SEED_ENCRYPTION_INFO)\n}\n\n/** Derive the seed-encryption key from a password + stored salt (PBKDF2). */\nexport async function deriveKeyFromPassword(\n password: string,\n salt: Uint8Array,\n iterations: number = PBKDF2_ITERATIONS,\n): Promise<CryptoKey> {\n const ikm = await crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(password),\n \"PBKDF2\",\n false,\n [\"deriveKey\"],\n )\n return crypto.subtle.deriveKey(\n { name: \"PBKDF2\", hash: \"SHA-256\", salt: salt as BufferSource, iterations },\n ikm,\n { name: AES_GCM, length: AES_KEY_BITS },\n false,\n [\"encrypt\", \"decrypt\"],\n )\n}\n\nexport const PASSWORD_KDF_ITERATIONS = PBKDF2_ITERATIONS\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * IndexedDB Cache for Batch Utilization Chunks\n *\n * Provides fast local caching of 4KB utilization chunks to avoid\n * repeated Swarm downloads. Each chunk is 4096 bytes containing either\n * 2048 uint16 or 1024 uint32 bucket counters, depending on batch depth.\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Cache entry for a single utilization chunk\n */\nexport interface ChunkCacheEntry {\n /** Batch ID (hex string) */\n batchId: string\n\n /** Chunk index within the depth-dependent layout */\n chunkIndex: number\n\n /** Serialized chunk data (4KB) */\n data: Uint8Array\n\n /** Content hash for change detection */\n contentHash: string\n\n /** SOC reference if uploaded to Swarm */\n socReference?: string\n\n /**\n * Per-chunk-index encryption nonce that was used to derive this chunk's\n * encryption key. Lets a re-save start its bucket-collision search from\n * the previously-chosen value, so unchanged plaintexts keep the same\n * derived key (and therefore the same `contentHash`) across saves.\n *\n * Required: every row is written by a bundle that sets it, and an upgrade\n * drops the stores rather than carrying rows forward (#662).\n */\n nonce: number\n\n /** Last access timestamp (for eviction) */\n lastAccess: number\n}\n\n/**\n * Metadata for a batch's utilization state\n */\nexport interface BatchMetadata {\n batchId: string\n lastSync: number\n chunkCount: number\n /**\n * Per-partition \"synced reference\": the partition-state feed reference\n * (hex) this device's local counter was last in sync with. On a cold\n * acquire, if the feed still points here the counter-chunk download is\n * skipped and the local counter reused. Keyed by partition index.\n */\n syncedReferences?: Record<number, string>\n /**\n * Per-partition buckets occupied by the LATEST published partition-state\n * chunks (counter chunks + reference chunk). Those chunks overstamp the\n * partition's reserved slot in their buckets, so utilization saves must\n * avoid placing chunks there — a collision would evict the published\n * state from the reserve (Bee replaces the older chunk at a colliding\n * stamp index), and a later takeover would fail to read its resume point.\n * Keyed by partition index; replaced wholesale on every publish/read.\n */\n stateChunkBuckets?: Record<number, number[]>\n}\n\n// ============================================================================\n// IndexedDB Cache Manager\n// ============================================================================\n\nconst DB_NAME = \"swarm-utilization-store\"\n/**\n * Bumped whenever a stored row's shape changes, because an upgrade DROPS the\n * stores rather than migrating them: this is a cache of utilisation counters\n * that re-derives itself on next use, and 0.x owes no migration (#661).\n */\nconst DB_VERSION = 4\nconst CHUNKS_STORE = \"chunks\"\nconst METADATA_STORE = \"metadata\"\n\n/**\n * Manages IndexedDB cache for batch utilization chunks\n */\nexport class UtilizationStoreDB {\n private db: IDBDatabase | undefined\n\n /**\n * Open the IndexedDB database\n */\n async open(): Promise<void> {\n if (this.db) return\n\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION)\n\n request.onerror = () => {\n reject(new Error(`Failed to open IndexedDB: ${request.error}`))\n }\n\n // Fires when an upgrade is held up by another open connection (e.g.\n // a second tab on the trusted domain). The upgrade proceeds once that\n // connection closes — see the onversionchange handler below.\n request.onblocked = () => {\n console.warn(\n \"[UtilizationStore] IndexedDB upgrade blocked by another open connection; waiting for it to close\",\n )\n }\n\n request.onsuccess = () => {\n this.db = request.result\n // Release this connection if another tab needs to upgrade, so its\n // open() does not block indefinitely on us.\n this.db.onversionchange = () => {\n this.close()\n }\n resolve()\n }\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result\n\n // Any upgrade drops and recreates: rows from an older version are not\n // migrated, and utilization tracking re-initializes to defaults on next\n // use. See `DB_VERSION`.\n if (db.objectStoreNames.contains(CHUNKS_STORE)) {\n db.deleteObjectStore(CHUNKS_STORE)\n }\n if (db.objectStoreNames.contains(METADATA_STORE)) {\n db.deleteObjectStore(METADATA_STORE)\n }\n\n const chunksStore = db.createObjectStore(CHUNKS_STORE, {\n keyPath: [\"batchId\", \"chunkIndex\"],\n })\n\n // Index for querying by batchId\n chunksStore.createIndex(\"batchId\", \"batchId\", { unique: false })\n\n // Metadata store\n db.createObjectStore(METADATA_STORE, { keyPath: \"batchId\" })\n }\n })\n }\n\n /**\n * Get a chunk from cache\n * @param batchId - Batch ID (hex string)\n * @param chunkIndex - Chunk index\n * @returns Cache entry or undefined if not found\n */\n async getChunk(\n batchId: string,\n chunkIndex: number,\n ): Promise<ChunkCacheEntry | undefined> {\n await this.open()\n\n return new Promise((resolve, reject) => {\n const transaction = this.db!.transaction([CHUNKS_STORE], \"readonly\")\n const store = transaction.objectStore(CHUNKS_STORE)\n const request = store.get([batchId, chunkIndex])\n\n request.onsuccess = () => {\n const entry = request.result as ChunkCacheEntry | undefined\n\n if (entry) {\n // Update lastAccess asynchronously (don't wait)\n this.touchChunk(batchId, chunkIndex).catch((err) => {\n console.warn(\"[UtilizationStore] Failed to update lastAccess:\", err)\n })\n }\n\n resolve(entry)\n }\n\n request.onerror = () => {\n reject(new Error(`Failed to get chunk: ${request.error}`))\n }\n })\n }\n\n /**\n * Store a chunk in cache\n * @param entry - Cache entry to store\n */\n async putChunk(entry: ChunkCacheEntry): Promise<void> {\n await this.open()\n\n return new Promise((resolve, reject) => {\n const transaction = this.db!.transaction([CHUNKS_STORE], \"readwrite\")\n const store = transaction.objectStore(CHUNKS_STORE)\n\n // Update lastAccess before storing\n const entryWithAccess = {\n ...entry,\n lastAccess: Date.now(),\n }\n\n const request = store.put(entryWithAccess)\n\n request.onsuccess = () => resolve()\n request.onerror = () => {\n reject(new Error(`Failed to put chunk: ${request.error}`))\n }\n })\n }\n\n /**\n * Get all chunks for a batch\n * @param batchId - Batch ID (hex string)\n * @returns Array of cache entries (sorted by chunkIndex)\n */\n async getAllChunks(batchId: string): Promise<ChunkCacheEntry[]> {\n await this.open()\n\n return new Promise((resolve, reject) => {\n const transaction = this.db!.transaction([CHUNKS_STORE], \"readonly\")\n const store = transaction.objectStore(CHUNKS_STORE)\n const index = store.index(\"batchId\")\n const request = index.getAll(batchId)\n\n request.onsuccess = () => {\n const entries = request.result as ChunkCacheEntry[]\n // Sort by chunkIndex for predictable order\n entries.sort((a, b) => a.chunkIndex - b.chunkIndex)\n resolve(entries)\n }\n\n request.onerror = () => {\n reject(new Error(`Failed to get all chunks: ${request.error}`))\n }\n })\n }\n\n /**\n * Clear all chunks for a batch\n * @param batchId - Batch ID (hex string)\n */\n async clearBatch(batchId: string): Promise<void> {\n await this.open()\n\n return new Promise((resolve, reject) => {\n const transaction = this.db!.transaction(\n [CHUNKS_STORE, METADATA_STORE],\n \"readwrite\",\n )\n const chunksStore = transaction.objectStore(CHUNKS_STORE)\n const metadataStore = transaction.objectStore(METADATA_STORE)\n\n // Delete all chunks for this batch\n const chunksIndex = chunksStore.index(\"batchId\")\n const chunksRequest = chunksIndex.openCursor(batchId)\n\n chunksRequest.onsuccess = () => {\n const cursor = chunksRequest.result\n if (cursor) {\n cursor.delete()\n cursor.continue()\n }\n }\n\n // Delete metadata\n metadataStore.delete(batchId)\n\n transaction.oncomplete = () => resolve()\n transaction.onerror = () => {\n reject(new Error(`Failed to clear batch: ${transaction.error}`))\n }\n })\n }\n\n /**\n * Update lastAccess timestamp for a chunk\n * @param batchId - Batch ID (hex string)\n * @param chunkIndex - Chunk index\n */\n private async touchChunk(batchId: string, chunkIndex: number): Promise<void> {\n await this.open()\n\n return new Promise((resolve, reject) => {\n const transaction = this.db!.transaction([CHUNKS_STORE], \"readwrite\")\n const store = transaction.objectStore(CHUNKS_STORE)\n const request = store.get([batchId, chunkIndex])\n\n request.onsuccess = () => {\n const entry = request.result as ChunkCacheEntry | undefined\n if (entry) {\n entry.lastAccess = Date.now()\n store.put(entry)\n }\n }\n\n transaction.oncomplete = () => resolve()\n transaction.onerror = () => {\n reject(new Error(`Failed to touch chunk: ${transaction.error}`))\n }\n })\n }\n\n /**\n * Get batch metadata\n * @param batchId - Batch ID (hex string)\n * @returns Metadata or undefined if not found\n */\n async getMetadata(batchId: string): Promise<BatchMetadata | undefined> {\n await this.open()\n\n return new Promise((resolve, reject) => {\n const transaction = this.db!.transaction([METADATA_STORE], \"readonly\")\n const store = transaction.objectStore(METADATA_STORE)\n const request = store.get(batchId)\n\n request.onsuccess = () =>\n resolve(request.result as BatchMetadata | undefined)\n request.onerror = () => {\n reject(new Error(`Failed to get metadata: ${request.error}`))\n }\n })\n }\n\n /**\n * Update batch metadata\n * @param metadata - Metadata to store\n */\n async putMetadata(metadata: BatchMetadata): Promise<void> {\n await this.open()\n\n return new Promise((resolve, reject) => {\n const transaction = this.db!.transaction([METADATA_STORE], \"readwrite\")\n const store = transaction.objectStore(METADATA_STORE)\n const request = store.put(metadata)\n\n request.onsuccess = () => resolve()\n request.onerror = () => {\n reject(new Error(`Failed to put metadata: ${request.error}`))\n }\n })\n }\n\n /**\n * Close the database connection\n */\n close(): void {\n if (this.db) {\n this.db.close()\n this.db = undefined\n }\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Stamp and return: the frame stamps a chunk with the account's own stamp\n * and hands the stamp back, so the app sends the chunk through the Swarm\n * access it chose. The app sends the whole chunk and the frame computes its\n * address, so a stamp is only ever issued for bytes the frame has seen, and a\n * single-owner chunk is only ever one the frame signed in the same request.\n */\n\nimport { EthAddress, PrivateKey } from \"@ethersphere/bee-js\"\nimport type { EnvelopeWithBatchId, Signature } from \"@ethersphere/bee-js\"\nimport { convertEnvelopeToMarshaledStamp } from \"@ethersphere/core-sdk\"\nimport { Binary } from \"cafe-utility\"\nimport { SwarmIdError } from \"../errors\"\nimport {\n calculateChunkAddress,\n MAX_PAYLOAD_SIZE,\n MIN_PAYLOAD_SIZE,\n SPAN_SIZE,\n} from \"../chunk\"\nimport {\n ACT_SHARING_LABEL,\n BACKUP_KEY_LABEL,\n POSTAGE_SIGNER_LABEL,\n SWARM_ENCRYPTION_LABEL,\n deriveSecretSync,\n} from \"../utils/key-derivation\"\nimport { hexToUint8Array } from \"../utils/hex\"\nimport type { StampChunkRequest } from \"../types\"\n\n/** What the frame stamps: the address it computed, and a SOC's owner and signature */\nexport interface ChunkToStamp {\n address: Uint8Array\n owner?: EthAddress\n signature?: Signature\n}\n\n/** The account keys the internal owners are derived from */\nexport interface InternalOwnerKeys {\n /** The account's own address */\n accountAddress: string\n derivationKey: string\n /** The resolved stamp's signer, which an imported stamp may not derive */\n postageSignerKey?: string\n}\n\n/**\n * Every owner address the account's own chunks may carry, lowercase hex. A\n * single-owner chunk owned by one of these is refused, so an app can never be\n * handed a stamp for the account's lock, partition state, device state or\n * roster chunks (owned by the backup signer), nor sign as any other key the\n * frame holds for the account.\n */\nexport function accountInternalOwners(keys: InternalOwnerKeys): Set<string> {\n const { derivationKey } = keys\n const swarmEncryptionKey = deriveSecretSync(\n derivationKey,\n SWARM_ENCRYPTION_LABEL,\n )\n const internalKeys = [\n derivationKey,\n swarmEncryptionKey,\n deriveSecretSync(swarmEncryptionKey, BACKUP_KEY_LABEL),\n deriveSecretSync(derivationKey, POSTAGE_SIGNER_LABEL),\n deriveSecretSync(derivationKey, ACT_SHARING_LABEL),\n ]\n if (keys.postageSignerKey) internalKeys.push(keys.postageSignerKey)\n\n const owners = new Set(\n internalKeys.map((key) => ownerOf(new PrivateKey(key))),\n )\n owners.add(new EthAddress(keys.accountAddress).toHex().toLowerCase())\n return owners\n}\n\nfunction ownerOf(key: PrivateKey): string {\n return key.publicKey().address().toHex().toLowerCase()\n}\n\nfunction requireChunkData(data: Uint8Array): void {\n const min = SPAN_SIZE + MIN_PAYLOAD_SIZE\n const max = SPAN_SIZE + MAX_PAYLOAD_SIZE\n if (data.length < min || data.length > max) {\n throw new SwarmIdError(\n \"invalid-request\",\n `Invalid chunk length: ${data.length} bytes (expected span plus payload, ${min} to ${max})`,\n )\n }\n}\n\n/**\n * Compute the address of the chunk to stamp, and sign it when it is a\n * single-owner chunk.\n *\n * @throws SwarmIdError `invalid-request` for a chunk of the wrong length, or a\n * single-owner chunk whose owner is one of `internalOwners`\n */\nexport function prepareChunkToStamp(\n chunk: StampChunkRequest,\n context: { appSecret: string; internalOwners: ReadonlySet<string> },\n): ChunkToStamp {\n requireChunkData(chunk.data)\n const contentAddress = calculateChunkAddress(chunk.data).toUint8Array()\n if (chunk.kind === \"content\") {\n return { address: contentAddress }\n }\n\n const signer = new PrivateKey(\n chunk.signer === \"app\" ? context.appSecret : chunk.signer.privateKey,\n )\n if (context.internalOwners.has(ownerOf(signer))) {\n throw new SwarmIdError(\n \"invalid-request\",\n \"This single-owner chunk would be owned by one of the account's own keys, which never signs for an app.\",\n )\n }\n\n const identifier = hexToUint8Array(chunk.identifier)\n const owner = signer.publicKey().address()\n return {\n address: Binary.keccak256(\n Binary.concatBytes(identifier, owner.toUint8Array()),\n ),\n owner,\n signature: signer.sign(Binary.concatBytes(identifier, contentAddress)),\n }\n}\n\n/** A stamp in the form Bee's `swarm-postage-stamp` upload header takes */\nexport function marshalStamp(envelope: EnvelopeWithBatchId): string {\n return convertEnvelopeToMarshaledStamp(envelope).toHex()\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * What one app may stamp with the account's stamp through `stampChunk`. A\n * hacked or greedy app could otherwise fill the person's stamp, so each app\n * origin gets a count per stamp, a rate, and a share of any one bucket. The\n * frame keeps the usage per app origin and per stamp in its own storage, so a\n * reload of the app does not reset it. A new stamp starts a new count.\n */\n\nimport { z } from \"zod\"\nimport { SwarmIdError } from \"../errors\"\n\n/** Slots one app may spend from one of the account's stamps, ever. A stamp again at a slot already held spends none */\nexport const APP_STAMP_COUNT_LIMIT = 10_000\n\n/** Stamps one app may take within one {@link APP_STAMP_RATE_WINDOW_MS} */\nexport const APP_STAMP_RATE_LIMIT = 60\n\n/** The rate limit's window. A window starts at the first stamp after the last one ended */\nexport const APP_STAMP_RATE_WINDOW_MS = 60 * 1000\n\n/**\n * Share of a bucket's data lane one app may use. Rounded down, so at the\n * smallest usable depth (lane of 7) an app gets 2 slots in any one bucket.\n */\nexport const APP_BUCKET_SHARE = 1 / 3\n\nconst MS_PER_SECOND = 1000\n\nconst AppStampUsageSchema = z.object({\n count: z.number().int().nonnegative(),\n windowStart: z.number(),\n windowCount: z.number().int().nonnegative(),\n /** Stamps per bucket index */\n buckets: z.record(z.string(), z.number().int().nonnegative()),\n})\n\n/** What one app has stamped with one of the account's stamps */\nexport type AppStampUsage = z.infer<typeof AppStampUsageSchema>\n\nexport function emptyAppStampUsage(): AppStampUsage {\n return { count: 0, windowStart: 0, windowCount: 0, buckets: {} }\n}\n\n/**\n * The stored usage, or an empty one when there is none. Only the frame writes\n * the record, so a malformed one is a bug of ours and starts over rather than\n * locking the app out for good.\n */\nexport function parseAppStampUsage(raw: string | null): AppStampUsage {\n if (raw === null) return emptyAppStampUsage()\n try {\n const parsed = AppStampUsageSchema.safeParse(JSON.parse(raw))\n return parsed.success ? parsed.data : emptyAppStampUsage()\n } catch {\n return emptyAppStampUsage()\n }\n}\n\n/** Slots one app may use in one bucket of a lane this size */\nexport function appBucketCap(laneCapacity: number): number {\n return Math.floor(laneCapacity * APP_BUCKET_SHARE)\n}\n\nfunction windowIsCurrent(usage: AppStampUsage, now: number): boolean {\n return (\n now >= usage.windowStart &&\n now - usage.windowStart < APP_STAMP_RATE_WINDOW_MS\n )\n}\n\nfunction limitRefusal(\n reason: \"app-stamp-count\" | \"app-stamp-rate\" | \"app-bucket-share\",\n message: string,\n): SwarmIdError {\n return new SwarmIdError(\"upload-unavailable\", message, { reason })\n}\n\n/**\n * Why stamping a single-owner chunk again at the slot it already holds is\n * refused for this app, or undefined when it is allowed. No slot is spent, so\n * only the count and the rate apply, not the bucket share.\n */\nexport function appRestampRefusal(\n usage: AppStampUsage,\n now: number,\n): SwarmIdError | undefined {\n if (usage.count >= APP_STAMP_COUNT_LIMIT) {\n return limitRefusal(\n \"app-stamp-count\",\n `This app has used its count limit of ${APP_STAMP_COUNT_LIMIT} stamps on this stamp.`,\n )\n }\n if (\n windowIsCurrent(usage, now) &&\n usage.windowCount >= APP_STAMP_RATE_LIMIT\n ) {\n return limitRefusal(\n \"app-stamp-rate\",\n `This app has reached its rate limit of ${APP_STAMP_RATE_LIMIT} stamps per ${APP_STAMP_RATE_WINDOW_MS / MS_PER_SECOND} seconds. Try again shortly.`,\n )\n }\n return undefined\n}\n\n/**\n * Why the next stamp for this app is refused, or undefined when it is allowed.\n *\n * @param target.bucket - The bucket the chunk's address lands in\n * @param target.laneCapacity - Data chunks the stamper's lane holds per bucket\n */\nexport function appStampRefusal(\n usage: AppStampUsage,\n target: { bucket: number; laneCapacity: number; now: number },\n): SwarmIdError | undefined {\n const refusal = appRestampRefusal(usage, target.now)\n if (refusal) return refusal\n const cap = appBucketCap(target.laneCapacity)\n if ((usage.buckets[target.bucket] ?? 0) >= cap) {\n return limitRefusal(\n \"app-bucket-share\",\n `This app has used its bucket share limit of ${cap} stamps in this chunk's bucket.`,\n )\n }\n return undefined\n}\n\n/**\n * The usage once one more stamp at a slot already held is counted. It spends\n * no slot, and the count bounds slots spent, so only the rate counts it.\n */\nexport function recordAppRestamp(\n usage: AppStampUsage,\n now: number,\n): AppStampUsage {\n const current = windowIsCurrent(usage, now)\n return {\n ...usage,\n windowStart: current ? usage.windowStart : now,\n windowCount: current ? usage.windowCount + 1 : 1,\n }\n}\n\n/** The usage once one more stamp in `bucket` is counted */\nexport function recordAppStamp(\n usage: AppStampUsage,\n bucket: number,\n now: number,\n): AppStampUsage {\n return {\n ...recordAppRestamp(usage, now),\n count: usage.count + 1,\n buckets: { ...usage.buckets, [bucket]: (usage.buckets[bucket] ?? 0) + 1 },\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Which slot each single-owner chunk an app had stamped went to. A chat sends\n * every message as a new version of one inbox chunk, whose address never\n * changes, and a mutable batch replaces a chunk stamped at the same slot with\n * a newer timestamp. So stamping that address again reuses its slot instead\n * of filling the bucket. The record is per app origin and per stamp, in the\n * frame's own storage, and belongs to one lane of the stamp: when this device\n * holds another lane the record starts empty, and a new slot is taken.\n */\n\nimport { z } from \"zod\"\n\n/** Single-owner chunk addresses remembered per app and stamp. Past it a new address takes a new slot each time */\nexport const SOC_RESTAMP_SLOT_LIMIT = 256\n\nconst SLOT_OFFSET = 4\n\n/** One lane of the stamp: this device's partition and the account's partition count */\nexport interface StampLane {\n partition: number\n partitionCount: number\n}\n\nconst SocSlotSchema = z.object({\n slot: z.number().int().nonnegative(),\n /** The newest stamp's timestamp at this slot, in milliseconds */\n timestampMs: z.number().int().nonnegative(),\n})\n\nconst SocRestampSlotsSchema = z.object({\n partition: z.number().int().nonnegative(),\n partitionCount: z.number().int().positive(),\n /** By chunk address, lowercase hex */\n slots: z.record(z.string(), SocSlotSchema),\n})\n\n/** The slots one app's single-owner chunks hold in one lane of one stamp */\nexport type SocRestampSlots = z.infer<typeof SocRestampSlotsSchema>\n\nexport function emptySocRestampSlots(lane: StampLane): SocRestampSlots {\n return {\n partition: lane.partition,\n partitionCount: lane.partitionCount,\n slots: {},\n }\n}\n\n/**\n * The stored record for `lane`, or an empty one when there is none, it\n * belongs to another lane, or it is malformed. Only the frame writes it, so a\n * malformed one is a bug of ours and starts over, which only costs new slots.\n */\nexport function parseSocRestampSlots(\n raw: string | null,\n lane: StampLane,\n): SocRestampSlots {\n if (raw === null) return emptySocRestampSlots(lane)\n try {\n const parsed = SocRestampSlotsSchema.safeParse(JSON.parse(raw))\n if (\n !parsed.success ||\n parsed.data.partition !== lane.partition ||\n parsed.data.partitionCount !== lane.partitionCount\n ) {\n return emptySocRestampSlots(lane)\n }\n return parsed.data\n } catch {\n return emptySocRestampSlots(lane)\n }\n}\n\n/**\n * The record once `addressHex` holds `slot` with a stamp at `timestampMs`.\n * Unchanged when the record is full and the address is new.\n */\nexport function recordSocSlot(\n record: SocRestampSlots,\n addressHex: string,\n slot: number,\n timestampMs: number,\n): SocRestampSlots {\n const known = addressHex in record.slots\n if (!known && Object.keys(record.slots).length >= SOC_RESTAMP_SLOT_LIMIT) {\n return record\n }\n return {\n ...record,\n slots: { ...record.slots, [addressHex]: { slot, timestampMs } },\n }\n}\n\n/**\n * The timestamp for a stamp again at a slot whose last stamp had\n * `previousMs`. Bee keeps the chunk with the newer stamp, so when the clock\n * went back it is one millisecond past the last rather than now.\n */\nexport function restampTimestamp(previousMs: number, now: number): number {\n return Math.max(now, previousMs + 1)\n}\n\n/** The slot in a stamp's 8-byte index, which is the bucket then the slot, big-endian */\nexport function slotOfStampIndex(index: Uint8Array): number {\n return new DataView(\n index.buffer,\n index.byteOffset,\n index.byteLength,\n ).getUint32(SLOT_OFFSET, false)\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Web Worker pool for parallel stamp signing.\n *\n * Architecture:\n * - Main thread: slot assignment (fast, stateful, sequential)\n * - Workers: ECDSA signing (slow, stateless, parallel)\n *\n * The pool only signs. The slot comes from the stamper it was created for — a\n * `SlotAssigner`'s `assignSlot()`, or a plain core-sdk `Stamper`'s `buckets` —\n * so a pooled stamp lands where `stamper.stamp()` would have put it.\n */\n\nimport type { Stamper, EnvelopeWithBatchId, BatchId } from \"@ethersphere/bee-js\"\nimport { EthAddress, Signature } from \"@ethersphere/bee-js\"\nimport { Binary } from \"cafe-utility\"\nimport type { SlotAssigner } from \"../utils/batch-utilization\"\nimport type {\n StampWorkerReadyResponse,\n StampWorkerSignedResponse,\n} from \"./stamp-worker-handler\"\n\n// Imported at build time via virtual module (see rollup.config.js)\nimport stampWorkerCode from \"virtual:stamp-worker-code\"\n\ninterface PendingSign {\n resolve: (signatureHex: string) => void\n reject: (error: Error) => void\n}\n\ninterface WorkerHandle {\n worker: Worker\n pending: Map<number, PendingSign>\n}\n\nconst DEFAULT_WORKER_COUNT = 4\nconst WORKER_INIT_TIMEOUT_MS = 5_000\nconst NANOSECONDS_PER_MILLISECOND = 1_000_000n\nconst POOL_TERMINATED_MESSAGE = \"Stamp worker pool is terminated\"\n\n/** core-sdk's `Stamper#stamp` slot bookkeeping, read off the live `buckets`. */\nfunction nextBucketSlot(stamper: Stamper, address: Uint8Array): number {\n const bucket = (address[0] << 8) | address[1]\n const height = stamper.buckets[bucket]\n if (height >= stamper.maxSlot) {\n throw new Error(\"Bucket is full\")\n }\n stamper.buckets[bucket] = height + 1\n return height\n}\n\nexport class StampWorkerPool {\n private workers: WorkerHandle[]\n private nextWorker = 0\n private nextId = 0\n private issuer: Uint8Array // 20 bytes\n private batchId: BatchId\n private assignSlot: (address: Uint8Array) => number\n\n readonly size: number\n /** The stamper this pool signs for: a pool must not outlive it. */\n readonly stamper: Stamper | SlotAssigner\n\n private constructor(\n workers: WorkerHandle[],\n issuer: Uint8Array,\n stamper: Stamper | SlotAssigner,\n ) {\n this.workers = workers\n this.size = workers.length\n this.issuer = issuer\n this.stamper = stamper\n this.batchId = stamper.batchId\n this.assignSlot =\n \"assignSlot\" in stamper\n ? (address) => stamper.assignSlot(address)\n : (address) => nextBucketSlot(stamper, address)\n }\n\n /**\n * Create a worker pool from an existing stamper.\n *\n * @param signerKeyHex - Signer private key as hex (proxy already has this)\n * @param stamper - Stamper the pool signs for; it stays the slot owner\n * @param workerCount - Number of workers (defaults to 4)\n */\n static async create(\n signerKeyHex: string,\n stamper: Stamper | SlotAssigner,\n workerCount?: number,\n ): Promise<StampWorkerPool> {\n const count = workerCount ?? DEFAULT_WORKER_COUNT\n\n // Create workers from inline Blob URL\n const blob = new Blob([stampWorkerCode], { type: \"application/javascript\" })\n const workerUrl = URL.createObjectURL(blob)\n\n const handles: WorkerHandle[] = []\n const initPromises: Promise<Uint8Array>[] = []\n\n for (let i = 0; i < count; i++) {\n const worker = new Worker(workerUrl, { type: \"classic\" })\n const handle: WorkerHandle = { worker, pending: new Map() }\n\n worker.onmessage = (\n event: MessageEvent<\n StampWorkerReadyResponse | StampWorkerSignedResponse\n >,\n ) => {\n const msg = event.data\n if (msg.type === \"signed\") {\n const p = handle.pending.get(msg.id)\n if (p) {\n handle.pending.delete(msg.id)\n p.resolve(msg.signatureHex)\n }\n }\n }\n\n worker.onerror = (event) => {\n // Reject all pending for this worker\n for (const [, p] of handle.pending) {\n p.reject(new Error(`Stamp worker error: ${event.message}`))\n }\n handle.pending.clear()\n }\n\n handles.push(handle)\n\n // Init worker with private key, collect issuer from ready response\n const initPromise = new Promise<Uint8Array>((resolve, reject) => {\n const origHandler = worker.onmessage\n const origErrorHandler = worker.onerror\n const timer = setTimeout(() => {\n reject(new Error(\"Stamp worker init timed out\"))\n }, WORKER_INIT_TIMEOUT_MS)\n worker.onmessage = (\n event: MessageEvent<\n StampWorkerReadyResponse | StampWorkerSignedResponse\n >,\n ) => {\n if (event.data.type === \"ready\") {\n clearTimeout(timer)\n worker.onmessage = origHandler\n worker.onerror = origErrorHandler\n resolve(\n Binary.hexToUint8Array(\n (event.data as StampWorkerReadyResponse).issuerHex,\n ),\n )\n }\n }\n worker.onerror = () => {\n clearTimeout(timer)\n reject(new Error(\"Worker init failed\"))\n }\n worker.postMessage({ type: \"init\", signerKeyHex })\n })\n\n initPromises.push(initPromise)\n }\n\n // Wait for all workers to initialize\n const issuers = await Promise.all(initPromises)\n const issuer = issuers[0]\n\n // Revoke Blob URL (workers already loaded)\n URL.revokeObjectURL(workerUrl)\n\n return new StampWorkerPool(handles, issuer, stamper)\n }\n\n /**\n * Stamp a chunk address using parallel worker signing.\n *\n * Slot assignment happens on the main thread, before the first await, so\n * concurrent calls cannot interleave inside it. ECDSA signing is dispatched\n * to a worker (slow, parallel).\n *\n * Takes the same `timestampMs` as core-sdk's `Stamper.stamp`, and produces\n * the same envelope for the same inputs — `stamp-worker-pool.test.ts` holds\n * the two to that, field by field.\n */\n async stampChunkData(\n address: Uint8Array,\n timestampMs: number = Date.now(),\n ): Promise<EnvelopeWithBatchId> {\n // Checked before a slot is spent on a stamp nothing will sign\n if (this.workers.length === 0) {\n throw new Error(POOL_TERMINATED_MESSAGE)\n }\n\n // 1. Slot assignment (main thread) — the stamper's, never the pool's own\n const bucket = (address[0] << 8) | address[1]\n const slot = this.assignSlot(address)\n\n // Build index (8 bytes): bucket(4 BE) + slot(4 BE)\n const index = new Uint8Array(8)\n const indexView = new DataView(index.buffer)\n indexView.setUint32(0, bucket, false)\n indexView.setUint32(4, slot, false)\n\n // Build timestamp (8 bytes): uint64 BE in nanoseconds — the unit Bee and\n // core-sdk's `Stamper` use, and what Bee compares on an index collision\n const timestamp = new Uint8Array(8)\n const tsView = new DataView(timestamp.buffer)\n tsView.setBigUint64(\n 0,\n BigInt(timestampMs) * NANOSECONDS_PER_MILLISECOND,\n false,\n )\n\n // 2. Construct signing message (80 bytes)\n const message = Binary.concatBytes(\n address,\n this.batchId.toUint8Array(),\n index,\n timestamp,\n )\n\n // 3. Dispatch signing to worker (round-robin)\n const signatureHex = await this.dispatchSign(message)\n const signature = Binary.hexToUint8Array(signatureHex)\n\n // 4. Return envelope\n return {\n batchId: this.batchId,\n index,\n issuer: new EthAddress(this.issuer),\n signature: new Signature(signature),\n timestamp,\n }\n }\n\n private dispatchSign(message: Uint8Array): Promise<string> {\n return new Promise((resolve, reject) => {\n const id = this.nextId++\n const handle = this.workers[this.nextWorker++ % this.workers.length]\n handle.pending.set(id, { resolve, reject })\n handle.worker.postMessage({ type: \"sign\", id, message })\n })\n }\n\n /**\n * Terminate all workers and release resources. Signs still in flight are\n * rejected: a terminated worker never answers, and an upload awaiting one\n * would hang.\n */\n terminate(): void {\n for (const handle of this.workers) {\n handle.worker.terminate()\n for (const [, p] of handle.pending) {\n p.reject(new Error(POOL_TERMINATED_MESSAGE))\n }\n handle.pending.clear()\n }\n this.workers = []\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Folder upload and listing (#750): one manifest with a fork per file, the\n * shape a gateway serves at `/bzz/<reference>/<path>`. Proxy-side, so the\n * caller never touches a Mantaray node.\n */\n\nimport { NULL_ADDRESS } from \"@ethersphere/bee-js\"\nimport type { Bee, BeeRequestOptions } from \"@ethersphere/bee-js\"\nimport { MantarayNode } from \"@ethersphere/core-sdk\"\nimport { uploadData, uploadChunk, type UploadTarget } from \"./upload\"\nimport { saveMantarayTree, loadMantarayTreeWithChunkAPI } from \"./mantaray\"\nimport { hexToUint8Array, uint8ArrayToHex } from \"../utils/hex\"\nimport type { UploadProgress, CollectionFile, CollectionEntry } from \"../types\"\n\nconst DEFAULT_CONTENT_TYPE = \"application/octet-stream\"\n\nexport interface UploadCollectionOptions {\n /** Encrypt file content (default true). `false` also leaves the manifest plain */\n encrypt?: boolean\n /** Encrypt the manifest too, so the root reference carries a key (default true) */\n encryptManifest?: boolean\n pin?: boolean\n deferred?: boolean\n tag?: number\n /** Served for the bare `/bzz/<reference>/` (`website-index-document`) */\n indexDocument?: string\n /** Served for a path that is not in the folder (`website-error-document`) */\n errorDocument?: string\n /** Called once per file */\n onProgress?: (progress: UploadProgress) => void\n requestOptions?: BeeRequestOptions\n}\n\n/**\n * What `uploadFile` and `uploadFiles` encrypt: everything by default, so the\n * root reference carries a key. `encryptManifest: false` bares the manifest\n * over encrypted content; `encrypt: false` bares everything — a plain manifest\n * over plain content is the only shape a bare-manifest reader can use.\n */\nexport function manifestEncryption(\n options: { encrypt?: boolean; encryptManifest?: boolean } | undefined,\n): { content: boolean; manifest: boolean } {\n const content = options?.encrypt !== false\n return { content, manifest: content && options?.encryptManifest !== false }\n}\n\nexport async function uploadCollection(\n target: UploadTarget,\n files: CollectionFile[],\n options?: UploadCollectionOptions,\n): Promise<{ reference: string; tagUid?: number }> {\n const { content: encryptContent, manifest: encryptManifest } =\n manifestEncryption(options)\n const chunkOptions = {\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n requestOptions: options?.requestOptions,\n }\n\n // A repeated path would silently replace its earlier fork: the manifest\n // would hold fewer files than were sent while progress still counted them all.\n const paths = new Set<string>()\n for (const { path } of files) {\n if (paths.has(path)) throw new Error(`Duplicate path in folder: ${path}`)\n paths.add(path)\n }\n\n const manifest = new MantarayNode()\n let processed = 0\n for (const file of files) {\n const { reference } = await uploadData(target, file.data, {\n ...chunkOptions,\n encryptionKey: encryptContent ? true : undefined,\n })\n manifest.addFork(file.path, hexToUint8Array(reference), {\n \"Content-Type\": file.contentType ?? DEFAULT_CONTENT_TYPE,\n Filename: file.path.split(\"/\").pop() ?? file.path,\n })\n processed++\n options?.onProgress?.({ total: files.length, processed })\n }\n\n const docs: Record<string, string> = {}\n if (options?.indexDocument) {\n docs[\"website-index-document\"] = options.indexDocument\n }\n if (options?.errorDocument) {\n docs[\"website-error-document\"] = options.errorDocument\n }\n if (Object.keys(docs).length > 0) manifest.addFork(\"/\", NULL_ADDRESS, docs)\n\n const { rootReference, tagUid } = await saveMantarayTree(\n manifest,\n async (chunkData, isRoot) => {\n await uploadChunk(target, chunkData, chunkOptions)\n return { tagUid: isRoot ? options?.tag : undefined }\n },\n { encrypt: encryptManifest },\n )\n return { reference: rootReference, tagUid }\n}\n\n/** The files of a manifest: every node with content, the docs fork excluded. */\nexport async function listCollection(\n bee: Bee,\n reference: string,\n requestOptions?: BeeRequestOptions,\n): Promise<CollectionEntry[]> {\n const manifest = await loadMantarayTreeWithChunkAPI(\n bee,\n reference,\n requestOptions,\n )\n return manifest\n .collect()\n .filter((node) => node.fullPathString !== \"/\")\n .map((node) => ({\n path: node.fullPathString,\n reference: uint8ArrayToHex(node.targetAddress),\n contentType: node.metadata?.[\"Content-Type\"],\n }))\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { ConnectedApp, PostageStamp, SignedInAccount } from \"../schemas\"\n\n/**\n * Resolve which postage stamp an app should upload with.\n *\n * An app may carry its own batch override (`postageStampBatchID`); otherwise it\n * falls back to the account's default stamp. The account owns the stamp set, so\n * these pointers are the single source of truth.\n *\n * The lookup is existence-checked: a pointer whose stamp is missing (e.g. a\n * removed or replaced batch) is skipped so resolution falls through to the next\n * candidate instead of failing.\n *\n * Paired with `stampsReachableByApp` below, which decides what a partitioned\n * session may HOLD while this decides what it SPENDS — over the same two\n * pointers, and deliberately differing on tombstones: this one skips them (a\n * deleted stamp cannot be spent), that one keeps them (a session learns its\n * stamp is deleted no other way). Change the pointer rule in one and the other\n * has to follow, or a session holds a stamp it cannot resolve, or resolves one\n * it was never handed.\n */\nexport function resolveStampForApp(\n app: Pick<ConnectedApp, \"postageStampBatchID\">,\n account: Pick<SignedInAccount, \"defaultPostageStampBatchID\">,\n stamps: PostageStamp[],\n): PostageStamp | undefined {\n const candidates = [\n app.postageStampBatchID,\n account.defaultPostageStampBatchID,\n ]\n for (const batchId of candidates) {\n if (!batchId) continue\n // Skip deleted stamps (tombstones): a deleted default/override must fall\n // through to the next candidate, same as a missing one. An EXPIRED stamp\n // deliberately does not: it can be renewed, and falling through would\n // silently charge a drive the user never picked for this app (#745).\n const stamp = stamps.find((s) => !s.deletedAt && s.batchID.equals(batchId))\n if (stamp) return stamp\n }\n return undefined\n}\n\n/**\n * The stamps a connected app's session can ever reach: the ones its pointers\n * name — its own batch override, and the account default it falls through to.\n *\n * This is what a PARTITIONED session is handed and allowed to keep (#578). It\n * holds its account view in memory, in a context embedded by an arbitrary dApp\n * page, and it can only ever spend the stamp `resolveStampForApp` picks — so\n * the rest of the collection is signer keys it has no use for. Least privilege\n * rather than a broken boundary: an unpartitioned proxy reads the same material\n * out of shared storage, but it does not have to be handed it.\n *\n * BOTH pointers, not just the winner: the default is the fallthrough\n * `resolveStampForApp` takes when the override's stamp is gone, so shipping\n * only the override would turn a recoverable stale pointer into a session that\n * cannot upload at all. Tombstones its pointers still name are kept too — a\n * tombstone is how the session learns the stamp it was spending is deleted.\n */\nexport function stampsReachableByApp(\n app: Pick<ConnectedApp, \"postageStampBatchID\">,\n account: Pick<SignedInAccount, \"defaultPostageStampBatchID\">,\n stamps: PostageStamp[],\n): PostageStamp[] {\n const reachable = [\n app.postageStampBatchID,\n account.defaultPostageStampBatchID,\n ]\n .filter((batchId) => batchId !== undefined)\n .map((batchId) => batchId.toHex())\n return stamps.filter((stamp) => reachable.includes(stamp.batchID.toHex()))\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * A drive's lifetime, from the fields already on its stored record.\n *\n * `batchTTL` is a remaining-seconds SNAPSHOT measured when the record was\n * last written — `updatedAt` (a node operation re-measured it), else\n * `createdAt` (purchase / attach measured it too). Aging it here is what lets\n * the proxy refuse a drive the identity UI already shows as expired, without\n * a network round trip (#745).\n */\n\nconst MS_PER_SECOND = 1000\n\n/** The subset of a stamp record the readiness rule is derived from. */\nexport interface StampLifetimeFields {\n batchTTL?: number\n createdAt: number\n updatedAt?: number\n /** What the node last said: absent on a record that predates the fields */\n usable?: boolean\n exists?: boolean\n}\n\n/** Why the session refuses to stamp with the record, or `undefined` if it will. */\nexport type StampRefusal = \"stamp-expired\" | \"stamp-not-usable\"\n\n/**\n * The readiness rule. Expiry first, because it is the older and the more\n * specific answer; then what the record says about the batch (#765): one the\n * node does not have (`exists: false`) or cannot use (`usable: false`) has its\n * first stamped write refused, which a dApp gating on `canUpload` alone met\n * 30 s later as a timeout. The fields are what the writer last stored — the\n * chain reconcile on drive open refreshes them — not a live reading.\n */\nexport function stampRefusal(\n stamp: StampLifetimeFields,\n now = Date.now(),\n): StampRefusal | undefined {\n if (isStampExpired(stamp, now)) return \"stamp-expired\"\n if (stamp.usable === false || stamp.exists === false) {\n return \"stamp-not-usable\"\n }\n return undefined\n}\n\n/**\n * Remaining lifespan in seconds at `now`: negative once expired, `undefined`\n * when the TTL was never measured.\n */\nexport function remainingLifespanSeconds(\n stamp: StampLifetimeFields,\n now = Date.now(),\n): number | undefined {\n if (stamp.batchTTL === undefined) {\n return undefined\n }\n const measuredAt = stamp.updatedAt ?? stamp.createdAt\n return (\n stamp.batchTTL - Math.max(0, Math.floor((now - measuredAt) / MS_PER_SECOND))\n )\n}\n\n/**\n * Whether the stored lifetime has run out. An unknown lifetime is not\n * expired: refusing to stamp on missing data would turn every legacy record\n * read-only.\n */\nexport function isStampExpired(\n stamp: StampLifetimeFields,\n now = Date.now(),\n): boolean {\n const remaining = remainingLifespanSeconds(stamp, now)\n return remaining !== undefined && remaining <= 0\n}\n\n/**\n * Whether two records would age to the same lifetime and refuse the same way\n * — the check a refresh needs to notice a renewal of the SAME batch, which\n * moves only `batchTTL` and `updatedAt`, or a node that has since accepted it.\n */\nexport function sameStampLifetime(\n a: StampLifetimeFields | undefined,\n b: StampLifetimeFields | undefined,\n): boolean {\n if (a === undefined || b === undefined) return a === b\n return (\n a.batchTTL === b.batchTTL &&\n (a.updatedAt ?? a.createdAt) === (b.updatedAt ?? b.createdAt) &&\n a.usable === b.usable &&\n a.exists === b.exists\n )\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Merge a locally-built account snapshot with the most recent on-Swarm\n * snapshot before publishing.\n *\n * Sync overwrites the feed entry on each write, so a naïve \"publish local\n * state\" stomps anything a peer just wrote. The fix is to fold the remote\n * snapshot into the local one and publish the union.\n *\n * Strategy per field:\n *\n * - `devices`: last-writer-wins per `deviceId` with a `removedAt` tombstone.\n * The recency clock is `max(removedAt, lastSignedInAt)`, so a removal beats an\n * older sign-in and a newer sign-in re-activates a removed device. The name\n * overlays on its own `nameUpdatedAt` clock, since a rename moves neither.\n * - `connectedApps`: last-writer-wins per `appUrl` (with `revokedAt` tombstone).\n * The chat binding overlays on its own signed date, since a copy handed to a\n * proxy frame carries only that frame's site's binding.\n * - `postageStamps`: last-writer-wins per `batchID` with a `deletedAt`\n * tombstone (recency `max(deletedAt, createdAt)`), so deletions propagate and\n * a fresh re-add (newer `createdAt`) re-activates a deleted stamp.\n * - `metadata.accountName`, `metadata.defaultPostageStampBatchID`,\n * `metadata.partitionCount`: local wins. These are scalar fields set\n * by user actions.\n * - `metadata.createdAt`: keep local's (it's the account's birth date,\n * should be identical anyway).\n * - `timestamp` / `metadata.lastModified`: refreshed to `Date.now()`.\n *\n * The function is pure and deterministic so it's easy to unit-test. The\n * per-collection primitives are exported so the read/refresh path can reuse the\n * exact same rules instead of redefining them.\n */\n\nimport type { AccountStateSnapshot } from \"../utils/account-state-snapshot\"\nimport { newerChatBinding } from \"../utils/chat-binding\"\nimport type {\n Device,\n ConnectedApp,\n PostageStamp,\n AccountMetadata,\n} from \"../schemas\"\n\nexport function mergeSnapshotWithRemote(\n local: AccountStateSnapshot,\n remote: AccountStateSnapshot | undefined,\n): AccountStateSnapshot {\n if (!remote) return local\n\n const metadata: AccountMetadata = {\n ...local.metadata,\n lastModified: Date.now(),\n devices: mergeDevicesList(local.metadata.devices, remote.metadata.devices),\n }\n\n return {\n ...local,\n timestamp: Date.now(),\n metadata,\n connectedApps: mergeConnectedApps(\n local.connectedApps,\n remote.connectedApps,\n local.accountId,\n ),\n postageStamps: mergePostageStamps(\n local.postageStamps,\n remote.postageStamps,\n ),\n }\n}\n\nexport function mergeDevicesList(local: Device[], remote: Device[]): Device[] {\n // Last-writer-wins per device on TWO clocks, exactly as stamps merge below:\n //\n // - Membership (the whole record) merges on `max(removedAt, lastSignedInAt)`,\n // so a removal beats an older sign-in and a newer sign-in beats an older\n // removal (re-activating the device). With nothing removed this reduces to\n // the old \"larger lastSignedInAt wins\". Removed devices are kept (the\n // tombstone keeps propagating).\n // - The name overlays on its own `nameUpdatedAt` clock. A rename moves\n // neither of the membership fields, so without this it tied with every\n // stale copy of itself and lost — #643's correction never left the device\n // that made it (#663). Riding its own clock also means it cannot drag a\n // stale record's sign-in state along or resurrect a tombstone.\n const recency = (d: Device) =>\n Math.max(d.removedAt ?? 0, d.lastSignedInAt ?? 0)\n const nameRecency = (d: Device) => d.nameUpdatedAt ?? 0\n const merged = new Map<string, Device>()\n // Process remote first, then local, so a tie favours local (most recent\n // observation here); a strictly-newer entry on either side wins.\n for (const d of [...remote, ...local]) {\n const existing = merged.get(d.deviceId)\n if (!existing) {\n merged.set(d.deviceId, d)\n continue\n }\n const winner = recency(d) >= recency(existing) ? d : existing\n const loser = winner === d ? existing : d\n merged.set(\n d.deviceId,\n nameRecency(loser) > nameRecency(winner)\n ? { ...winner, name: loser.name, nameUpdatedAt: loser.nameUpdatedAt }\n : winner,\n )\n }\n return Array.from(merged.values())\n}\n\n/**\n * @param accountId - the account whose connected apps these are, the only\n * signer whose chat binding a merge ranks by date over another\n */\nexport function mergeConnectedApps(\n local: ConnectedApp[],\n remote: ConnectedApp[],\n accountId: string,\n): ConnectedApp[] {\n const keyOf = (a: ConnectedApp) => a.appUrl\n // Last-writer-wins per app on TWO clocks, the way devices and stamps merge:\n //\n // - Membership (the whole entry) merges on the fields that STATE membership,\n // `max(revokedAt, disconnectedAt, lastConnectedAt)`. A removal beats an\n // older connect, and a genuine reconnect — `connectApp` clears both markers\n // and sets a fresh `lastConnectedAt` — beats an older removal. One clock\n // bumped by every write ranked the entry instead, so a drive picked on a\n // stale copy outranked a revoke and restored the credential it had cleared\n // (#681).\n // - The stamp pointer overlays on its own `postageStampBatchIDAt`. It moves\n // no membership field, so without a clock of its own it ties with every\n // stale copy of the entry and loses — the same correction #663 made for a\n // device's name.\n // - The chat binding overlays on its own signed date, a binding the\n // account signed for the entry's site first. A proxy frame holds only its own\n // site's binding and the bus carries none, so an entry that wins\n // membership can arrive without the binding its peer kept, and a binding\n // is never withdrawn, only replaced (docs/Chat-Binding.md).\n //\n // Distinct apps still both survive (union).\n const recency = (a: ConnectedApp) =>\n Math.max(a.revokedAt ?? 0, a.disconnectedAt ?? 0, a.lastConnectedAt ?? 0)\n const stampRecency = (a: ConnectedApp) => a.postageStampBatchIDAt ?? 0\n const merged = new Map<string, ConnectedApp>()\n // Process remote first, then local, so a tie favours local (most recent\n // observation here); a strictly-newer entry on either side wins.\n for (const a of [...remote, ...local]) {\n const existing = merged.get(keyOf(a))\n if (!existing) {\n merged.set(keyOf(a), a)\n continue\n }\n const winner = recency(a) >= recency(existing) ? a : existing\n const loser = winner === a ? existing : a\n const withStamp =\n stampRecency(loser) > stampRecency(winner)\n ? {\n ...winner,\n postageStampBatchID: loser.postageStampBatchID,\n postageStampBatchIDAt: loser.postageStampBatchIDAt,\n }\n : winner\n const chatBinding = newerChatBinding(\n winner.chatBinding,\n loser.chatBinding,\n winner.appUrl,\n accountId,\n )\n merged.set(\n keyOf(a),\n chatBinding === winner.chatBinding\n ? withStamp\n : { ...withStamp, chatBinding },\n )\n }\n return Array.from(merged.values())\n}\n\nexport function mergePostageStamps(\n local: PostageStamp[],\n remote: PostageStamp[],\n): PostageStamp[] {\n const keyOf = (s: PostageStamp) => s.batchID.toHex()\n // Last-writer-wins per batch so deletions AND edits propagate, on TWO clocks:\n //\n // - Node state (the whole record) merges on `max(deletedAt, updatedAt,\n // createdAt)` — a delete beats an older add/edit, a dilute/top-up\n // (`updatedAt`) beats a stale copy, and a fresh re-add (new `createdAt`)\n // re-activates the stamp, matching device resurrection. Deleted stamps are\n // kept (the tombstone keeps propagating until a re-add or newer node edit\n // supersedes it).\n // - The name overlays on its own `nameUpdatedAt` clock. A rename made over a\n // stale copy carries no node-state recency, so it can neither drag old\n // depth/amount/batchTTL past a concurrent dilute/top-up nor resurrect a\n // tombstone — but the newest name still wins.\n const recency = (s: PostageStamp) =>\n Math.max(s.deletedAt ?? 0, s.updatedAt ?? 0, s.createdAt)\n const nameRecency = (s: PostageStamp) => s.nameUpdatedAt ?? 0\n const merged = new Map<string, PostageStamp>()\n // Process remote first, then local, so a tie favours local (most recent\n // observation here); a strictly-newer entry on either side wins.\n for (const s of [...remote, ...local]) {\n const existing = merged.get(keyOf(s))\n if (!existing) {\n merged.set(keyOf(s), s)\n continue\n }\n const winner = recency(s) >= recency(existing) ? s : existing\n const loser = winner === s ? existing : s\n merged.set(\n keyOf(s),\n nameRecency(loser) > nameRecency(winner)\n ? { ...winner, name: loser.name, nameUpdatedAt: loser.nameUpdatedAt }\n : winner,\n )\n }\n return Array.from(merged.values())\n}\n\nfunction listContainsAll<T>(\n latest: T[],\n mine: T[],\n key: (t: T) => string,\n): boolean {\n const present = new Set(latest.map(key))\n return mine.every((item) => present.has(key(item)))\n}\n\n/**\n * True when `latest` already includes every device / connected-app /\n * postage-stamp present in `mine` (compared by their natural keys — the same\n * keys `mergeSnapshotWithRemote` unions on; timestamps and scalar metadata are\n * ignored).\n *\n * Used by the publish path to tell a *real* last-writer-wins loss (our entities\n * were dropped) apart from a harmless co-writer that published the same account\n * (e.g. the proxy announcing this device while the UI also syncs it) — whose\n * union still contains us, so convergence held and there is nothing to retry.\n *\n * Note: this is membership-only. Tombstone/scalar nuances (a revoke or an\n * account rename stomped by a co-writer) are NOT detected here; the union\n * merge's recency rules reconcile those on the next sync.\n */\nexport function snapshotContainsContribution(\n mine: AccountStateSnapshot,\n latest: AccountStateSnapshot,\n): boolean {\n return (\n listContainsAll(\n latest.metadata.devices,\n mine.metadata.devices,\n (d) => d.deviceId,\n ) &&\n listContainsAll(\n latest.connectedApps,\n mine.connectedApps,\n (a) => a.appUrl,\n ) &&\n listContainsAll(latest.postageStamps, mine.postageStamps, (s) =>\n s.batchID.toHex(),\n )\n )\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Run `op` under an exclusive, cross-tab Web Lock keyed on the postage batch.\n *\n * All writers to a given batch — every same-origin context: dApp proxy iframes\n * and the SwarmID UI — request the same lock name, so only one write runs at a\n * time across tabs. This is the single definition of the `swarm-write-<batchId>`\n * convention (the proxy and the account-sync path both use it).\n *\n * Falls back to running `op` directly when the Web Locks API is unavailable\n * (Node / test environments).\n */\nexport function withBatchWriteLock<T>(\n batchIdHex: string | undefined,\n op: () => Promise<T>,\n): Promise<T> {\n const lockName = `swarm-write-${batchIdHex}`\n if (typeof navigator !== \"undefined\" && navigator.locks) {\n return navigator.locks.request(lockName, { mode: \"exclusive\" }, op)\n }\n return op()\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Per-partition lock SOC for multi-device postage-batch sharing.\n *\n * A single shared SOC per partition is the cross-device authority for \"who\n * holds this partition\". Multiple devices share the `backupSigner` so any of\n * them can write the SOC; concurrent writers are ordered deterministically by\n * a (timestampMs, tiebreaker) fencing token.\n *\n * identifier = keccak256(\"swarm-id-partition-lock-v1:\" || batchIdHex || \":\" || partition)\n * owner = backup signer (shared across the account's devices)\n * encryption = swarmEncryptionKey\n *\n * The identifier carries the batch and the partition, but not the account —\n * domain separation across accounts comes from the per-account `owner`\n * (derived from each account's `derivationKey`), so the SOC address still\n * differs per account without accountId in the hash. The batch IS in there:\n * keying on the partition alone made lane p of batch X and lane p of batch Y\n * one address, so two coordinators sharing no slot took turns anyway (#589).\n * See `makePartitionLockIdentifier` in `../utils/lock-soc`.\n *\n * Protocol (acquire / takeover / refresh):\n * 1. Read the lock SOC.\n * 2. If a live foreign holder exists → return \"blocked\" without writing.\n * 3. Otherwise write our claim, wait `guardMs`, re-read.\n * 4. If our generation is still the latest visible → \"acquired\".\n * If a higher generation was written during the guard → \"lost-race\".\n *\n * See the \"Multi-Device Postage Batches\" page in docs-site for the full design.\n */\n\nimport {\n Bee,\n PrivateKey,\n type BatchId,\n type Stamper,\n} from \"@ethersphere/bee-js\"\nimport { Binary } from \"cafe-utility\"\nimport { downloadEncryptedSOC } from \"../proxy/download-data\"\nimport { uploadSOC, type UploadTarget } from \"../proxy/upload\"\nimport { sleep, withTimeout } from \"../utils/promise\"\nimport { SYNC_READ_TIMEOUT_MS } from \"./timing-constants\"\nimport { makePartitionLockIdentifier } from \"../utils/lock-soc\"\nimport {\n PartitionLockPayloadSchemaV1,\n type PartitionLockGeneration,\n type PartitionLockPayload,\n} from \"../schemas\"\n\n// Re-export the wire-format types (the Zod schema is the source of truth, in\n// `../schemas`) so existing callers keep importing them from this module.\nexport type { PartitionLockGeneration, PartitionLockPayload }\n\n// Re-export so external callers continue to import these from the\n// partition-lock module (the implementations live in utils/lock-soc.ts to\n// avoid a circular dependency with batch-utilization.ts).\nexport {\n lockSocAddress,\n lockSocBucket,\n makePartitionLockIdentifier,\n} from \"../utils/lock-soc\"\n\n/** Number of bytes from `keccak256(deviceId)` to use as the tiebreaker. */\nconst TIEBREAKER_BYTES = 8\n\n/**\n * Sentinel value placed in `holderDeviceId` to indicate \"no current holder\"\n * (e.g. after an explicit release).\n */\nexport const NO_HOLDER_DEVICE_ID = \"\"\n\n/**\n * Outcome of an `acquirePartitionLock` call.\n *\n * - `\"acquired\"` — the lock is now held by this device (write + verify both\n * succeeded).\n * - `\"blocked\"` — a live foreign holder exists; no write was performed.\n * - `\"lost-race\"` — we wrote, but a higher-generation writer appeared\n * during the guard window. The verify-read returned that other write.\n * - `\"aborted\"` — `shouldAbort` flipped before the claim write (the owning\n * lease session closed, e.g. a release started); no write was performed.\n */\nexport interface AcquirePartitionLockResult {\n outcome: \"acquired\" | \"blocked\" | \"lost-race\" | \"aborted\"\n /** Latest payload observed by this call (after the verify-read). */\n payload: PartitionLockPayload | undefined\n}\n\n/**\n * Outcome of a `releasePartitionLock` call.\n *\n * - `\"released\"` — the release sentinel was written.\n * - `\"skipped\"` — the lock no longer carries the claim being released\n * (successor claim, peer claim, or an existing sentinel); writing the\n * sentinel would clobber state that is not ours to release. The lock is\n * left untouched.\n */\nexport interface ReleasePartitionLockResult {\n outcome: \"released\" | \"skipped\"\n /** Payload observed by the pre-write read (undefined when missing/unreadable). */\n observed?: PartitionLockPayload\n}\n\n/**\n * Compute the per-device tiebreaker: first 8 bytes of `keccak256(deviceId)`\n * as a hex string. Two different deviceIds collide with probability 2⁻⁶⁴.\n */\nexport function makeDeviceTiebreaker(deviceId: string): string {\n const hash = Binary.keccak256(new TextEncoder().encode(deviceId))\n return Binary.uint8ArrayToHex(hash.slice(0, TIEBREAKER_BYTES))\n}\n\n/**\n * Deterministic per-device starting partition: `keccak256(deviceId) mod\n * partitionCount`. Distinct devices map to distinct home partitions with high\n * probability, so they spread across slots even when none can read peers' lock\n * SOCs (the disjoint-gateway frozen-cache case — see\n * docs/Partition-Lock-Hardening-Plan.md). Only changes the selection scan\n * ORDER; visibly-held slots are still skipped, so behaviour under working\n * reads is unchanged.\n *\n * ponytail: probabilistic spread, not mutual exclusion — colliding home\n * partitions still race. The complete cross-gateway fix is the Phase 2\n * intent-SOC protocol in the hardening plan.\n */\nexport function deviceHomePartition(\n deviceId: string,\n partitionCount: number,\n): number {\n const hash = Binary.keccak256(new TextEncoder().encode(deviceId))\n const n = ((hash[0] << 24) | (hash[1] << 16) | (hash[2] << 8) | hash[3]) >>> 0\n return n % partitionCount\n}\n\n/**\n * Lexicographic comparison of two generations: timestamp first, then\n * tiebreaker. Returns -1 / 0 / 1.\n */\nexport function compareGenerations(\n a: PartitionLockGeneration,\n b: PartitionLockGeneration,\n): -1 | 0 | 1 {\n if (a.timestampMs !== b.timestampMs) {\n return a.timestampMs < b.timestampMs ? -1 : 1\n }\n if (a.tiebreaker !== b.tiebreaker) {\n return a.tiebreaker < b.tiebreaker ? -1 : 1\n }\n return 0\n}\n\n/**\n * Read the partition-lock SOC. Returns `undefined` when the SOC has never\n * been written (the underlying chunk is missing on the Bee node).\n */\nexport async function readPartitionLock(opts: {\n bee: Bee\n backupSigner: PrivateKey\n swarmEncryptionKey: Uint8Array\n batchId: BatchId\n partition: number\n}): Promise<PartitionLockPayload | undefined> {\n const { bee, backupSigner, swarmEncryptionKey, batchId, partition } = opts\n const identifier = makePartitionLockIdentifier(batchId, partition)\n const owner = backupSigner.publicKey().address()\n try {\n const soc = await downloadEncryptedSOC(\n bee,\n owner,\n identifier,\n swarmEncryptionKey,\n )\n const parsed = PartitionLockPayloadSchemaV1.safeParse(\n JSON.parse(new TextDecoder().decode(soc.payload)),\n )\n // A payload that doesn't match the schema (foreign/corrupt/old format)\n // is treated the same as a missing lock — see the catch below.\n if (!parsed.success) {\n console.debug(\n `[partition-lock] read p=${partition}: SOC present but payload failed schema validation — treating as no lock`,\n )\n return undefined\n }\n return parsed.data\n } catch (error) {\n // Missing chunk, decryption failure, or malformed JSON — treat all\n // as \"lock unobserved\". Callers (acquirePartitionLock) treat undefined\n // as \"first acquire\". Log the reason: a gateway read failure (404/timeout)\n // for a lock another device DID write is the suspected cross-device\n // dual-acquire cause, and must be distinguishable from a genuine \"no lock\".\n const reason = error instanceof Error ? error.message : String(error)\n console.debug(\n `[partition-lock] read p=${partition}: unreadable — ${reason}`,\n )\n return undefined\n }\n}\n\n/**\n * Overwrite the partition-lock SOC with `payload`. LWW per SOC identifier\n * — any prior chunk at this address is replaced (modulo Swarm propagation\n * delay).\n */\nexport async function writePartitionLock(opts: {\n bee: Bee\n stamper: Stamper\n backupSigner: PrivateKey\n swarmEncryptionKey: Uint8Array\n batchId: BatchId\n partition: number\n payload: PartitionLockPayload\n}): Promise<void> {\n const {\n bee,\n stamper,\n backupSigner,\n swarmEncryptionKey,\n batchId,\n partition,\n payload,\n } = opts\n const identifier = makePartitionLockIdentifier(batchId, partition)\n const data = new TextEncoder().encode(JSON.stringify(payload))\n const target: UploadTarget = { mode: \"stamper\", bee, stamper }\n // No `deferred` flag: Bee's /soc handler hardcodes Deferred:false and ignores\n // the Swarm-Deferred-Upload header (bee 2.8.0 pkg/api/soc.go), so SOC writes\n // are always synchronous / receipt-backed regardless. (Replication still\n // depends on node reachability — see .claude/rules/bee-cluster.md.)\n await uploadSOC(target, backupSigner, identifier, data, {\n encryptionKey: swarmEncryptionKey,\n })\n}\n\n/**\n * Acquire (or refresh) the partition lock for this device.\n *\n * @returns `\"acquired\"` if we now hold the lock,\n * `\"blocked\"` if a live foreign holder exists (no write performed),\n * `\"lost-race\"` if a higher-generation writer superseded our write\n * inside the guard window.\n */\nexport async function acquirePartitionLock(opts: {\n bee: Bee\n stamper: Stamper\n backupSigner: PrivateKey\n swarmEncryptionKey: Uint8Array\n batchId: BatchId\n partition: number\n deviceId: string\n ttlMs: number\n guardMs: number\n now?: () => number\n wait?: (ms: number) => Promise<void>\n /**\n * Pre-built generation to claim with. Pass the SAME generation already\n * advertised in a Phase-2 intent round so the winner's lock claim and its\n * intent order identically. Defaults to a fresh `(now, tiebreaker)`.\n */\n generation?: PartitionLockGeneration\n /**\n * Checked immediately before the claim write. When it returns true the\n * acquire aborts WITHOUT writing — used by `PartitionLease` to stop a\n * refresh that overlaps a `release()` from minting a ghost claim the\n * generation-fenced release would then refuse to clear (peers would have\n * to wait out the TTL).\n */\n shouldAbort?: () => boolean\n /**\n * Skip the pre-write read entirely. Pass when the caller ALREADY read this\n * partition's lock (timeout-bounded) moments ago and classified it as\n * claimable — `PartitionLease.acquire`'s scan — so the duplicate read (an\n * absent chunk that can block for tens of seconds on a gateway) stays off\n * the acquire path. The live-foreign-holder \"blocked\" check is the caller's\n * responsibility; the guard-window verify below still catches races.\n */\n skipInitialRead?: boolean\n /**\n * Bound for the initial + verify reads (default {@link SYNC_READ_TIMEOUT_MS}).\n * Bee has no fast authoritative \"not found\", so an absent lock would\n * otherwise block on peer exhaustion. A timed-out read maps to the same\n * \"unobserved\" handling as any other read failure: initial → treated absent\n * (claim proceeds; guard+verify arbitrate), verify → the optimistic\n * \"couldn't confirm our own write\" branch.\n */\n readTimeoutMs?: number\n}): Promise<AcquirePartitionLockResult> {\n const now = opts.now ?? Date.now\n const wait = opts.wait ?? sleep\n const readBounded = () =>\n withTimeout(\n readPartitionLock(opts),\n opts.readTimeoutMs ?? SYNC_READ_TIMEOUT_MS,\n \"lock read timed out\",\n ).catch(() => undefined)\n\n const current = opts.skipInitialRead ? undefined : await readBounded()\n const t = now()\n\n // Live foreign holder — refuse without writing.\n if (\n current &&\n current.holderDeviceId !== opts.deviceId &&\n current.holderDeviceId !== NO_HOLDER_DEVICE_ID &&\n current.leasedUntil > t\n ) {\n return { outcome: \"blocked\", payload: current }\n }\n\n // The owning lease session closed while we were reading — do not write.\n if (opts.shouldAbort?.()) {\n return { outcome: \"aborted\", payload: current }\n }\n\n // Lock is empty, expired, released, or already ours. Write our claim\n // and verify after the guard window.\n const ourGeneration: PartitionLockGeneration = opts.generation ?? {\n timestampMs: t,\n tiebreaker: makeDeviceTiebreaker(opts.deviceId),\n }\n const ourPayload: PartitionLockPayload = {\n holderDeviceId: opts.deviceId,\n generation: ourGeneration,\n acquiredAt: t,\n leasedUntil: t + opts.ttlMs,\n }\n await writePartitionLock({ ...opts, payload: ourPayload })\n\n await wait(opts.guardMs)\n\n const verified = await readBounded()\n if (!verified) {\n // The verify-read couldn't confirm our write — e.g. the Bee node is\n // transiently 500ing on the just-written chunk, or it hasn't propagated\n // yet. We DID write our claim, and observed no live foreign holder before\n // writing (a live one returns \"blocked\" above), so a failed read is not\n // proof of a race. Optimistically hold; the periodic refresh reconciles\n // and demotes only on a CONFIRMED live foreign holder. Without this, a\n // read-path hiccup forces a false lost-race → read-only → slot-wait,\n // delaying activation by ~a minute even for a single device.\n return { outcome: \"acquired\", payload: ourPayload }\n }\n if (compareGenerations(verified.generation, ourGeneration) > 0) {\n return { outcome: \"lost-race\", payload: verified }\n }\n // A non-greater generation from a *different* holder still means our claim\n // stands — don't source our self-lease timestamps (acquiredAt/leasedUntil)\n // from a peer's payload.\n if (verified.holderDeviceId !== opts.deviceId) {\n return { outcome: \"acquired\", payload: ourPayload }\n }\n // Our own claim, read back at our generation — or an OLDER frozen-cache copy\n // of it. Refresh now REUSES the claim's generation (issue #413), so a stale\n // gateway read of a prior tick compares EQUAL, not older; keying on `< 0`\n // alone would let it through with a stale `leasedUntil`, silently stalling\n // the lease (the holder believes it holds while peers see it expire and take\n // the slot — a dual-acquire). Trust the freshest lease we know either way.\n return {\n outcome: \"acquired\",\n payload:\n verified.leasedUntil >= ourPayload.leasedUntil ? verified : ourPayload,\n }\n}\n\n/**\n * Write the release sentinel for a SPECIFIC claim — generation-fenced.\n *\n * A release is an action on the claim identified by `releasedGeneration`,\n * not on the partition as such. The sentinel therefore carries that\n * generation (it does NOT mint a fresh one), and it is only written when the\n * lock still visibly carries the claim being released. Without the fence, a\n * detached release that outlives a re-acquire writes its sentinel LAST —\n * and Bee replaces a same-address SOC whenever the new chunk's postage\n * stamp timestamp is newer, so the stale sentinel would deterministically\n * clobber the successor's claim and peers would treat the partition as free\n * while the successor still believes it holds it (issue #349).\n *\n * Skip rules (never write a sentinel over a claim that is not the one being\n * released):\n * - an existing sentinel (any generation) — already released; rewriting\n * would mint a fresh stamp that could clobber a claim landing in between.\n * Checked BEFORE the generation compare: a prior sentinel for this same\n * claim carries the same generation and would otherwise match \"ours\".\n * - a foreign claim, live or expired — never clobber a peer; an expired\n * claim already reads as takeable.\n * - our own claim with a NEWER generation — a successor re-acquired while\n * this release was publishing (#349), or a release-overlapping refresh.\n * - our own claim at a non-newer generation writes (`<=`, not `==`): a\n * stale read returning an older refresh of the same holdership must not\n * suppress the release.\n * - a missing/unreadable lock writes (best-effort release; the sentinel is\n * generation-fenced, so readers and successors can order it correctly).\n *\n * Residual (documented, accepted): a skip decision is only as fresh as the\n * pre-write read. The same-device #349 race reads through the same Bee node\n * (read-your-writes holds), so the fence is dependable there; cross-node\n * staleness falls back to the displacement/TTL machinery.\n */\nexport async function releasePartitionLock(opts: {\n bee: Bee\n stamper: Stamper\n backupSigner: PrivateKey\n swarmEncryptionKey: Uint8Array\n batchId: BatchId\n partition: number\n deviceId: string\n /** Generation of the claim being released — fences the sentinel. */\n releasedGeneration: PartitionLockGeneration\n /** `acquiredAt` of the claim being released (carried in the sentinel). */\n acquiredAt: number\n now?: () => number\n}): Promise<ReleasePartitionLockResult> {\n const now = opts.now ?? Date.now\n\n const current = await readPartitionLock(opts)\n if (current) {\n const skip = (reason: string): ReleasePartitionLockResult => {\n console.info(\n `[partition-lock] Skipping release sentinel for partition ${opts.partition}: ${reason}.`,\n current,\n )\n return { outcome: \"skipped\", observed: current }\n }\n if (current.holderDeviceId === NO_HOLDER_DEVICE_ID) {\n return skip(\"already released\")\n }\n if (current.holderDeviceId !== opts.deviceId) {\n return skip(\"a peer holds the partition\")\n }\n if (compareGenerations(current.generation, opts.releasedGeneration) > 0) {\n return skip(\"a newer own claim exists (successor re-acquired)\")\n }\n }\n\n const releasePayload: PartitionLockPayload = {\n holderDeviceId: NO_HOLDER_DEVICE_ID,\n generation: opts.releasedGeneration,\n acquiredAt: opts.acquiredAt,\n leasedUntil: now(),\n }\n await writePartitionLock({ ...opts, payload: releasePayload })\n return { outcome: \"released\", observed: current }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Per-device partition-INTENT SOCs — Phase 2 of the partition-lock hardening\n * (see docs/Partition-Lock-Hardening-Plan.md, \"Extension that covers the\n * symmetric case: per-device intent SOCs\").\n *\n * The lock SOC lives at a single static address. Across disjoint gateways a\n * node serves that address from a frozen cache (no TTL, no invalidation), so\n * contenders for a *free* partition never see each other and all bind it —\n * the disjoint-gateway dual-acquire bug. Tweaking the guard/TTL timeouts can't\n * help: the failure is non-visibility, not propagation latency.\n *\n * Intent SOCs convert \"mutable-SOC cache coherence\" (which Swarm doesn't\n * guarantee) into \"push-sync receipt + retrieval routing\" (which it does):\n *\n * identifier = keccak256(\"swarm-id-partition-intent-v1:\" || partition\n * || \":\" || deviceId || \":\" || epochBucket)\n * owner = backup signer (shared across the account's devices)\n * encryption = swarmEncryptionKey\n *\n * Each contender writes ONLY its own intent address and reads every other\n * known device's intent address. A read of an address your node has never\n * written (and, because the address rotates per epoch, never retrieved\n * before) cannot be short-circuited from the local store — it forces a\n * network retrieval to the address's storage neighborhood, where the rival's\n * receipt-backed push-sync placed its replicas. So contenders see each other,\n * order themselves by `generation`, and only the unique global-minimum binds\n * the lock.\n *\n * Irreducible limit (documented): absence is unprovable on Swarm — a retrieval\n * timeout can't distinguish \"no rival intent\" from \"the network failed to find\n * it\". We treat a timed-out read as \"no intent\" to keep liveness (a single\n * flaky device must not deadlock), so the guarantee is \"when the network\n * works, rivals are seen\"; genuine network partition still falls back to the\n * existing guard + TTL + refresh backstop.\n *\n * Slot safety: an intent is stamped into the CONTENDED partition's reserved\n * slot (= the partition index, `< DATA_COUNTER_START`), via\n * `UtilizationAwareStamper.reserveIntentSocSlot`. It therefore can NEVER share\n * a postage `(bucket, slot)` with user data (data lives at\n * `>= DATA_COUNTER_START`) — a deterministic guarantee, not probabilistic.\n *\n * Two residuals remain (both ~1/65536 per contending pair per round, far rarer\n * than the systematic disjoint-gateway failure, and NOT closed here):\n * - Two contenders for the same partition whose per-epoch intent addresses\n * hash into the same bucket both route to that partition's reserved slot;\n * the older loses its stamp and reads as \"no intent\", so on disjoint\n * gateways both may \"win\" → a rare dual-acquire. (A verify-own-intent\n * back-off or per-device slot would close it.)\n * - An intent may overstamp the contended partition's own (stale) lock SOC\n * (harmless — re-acquired) or a published state chunk (→ `readFailed` →\n * read-only fallback). Liveness-only, never data loss.\n */\n\nimport {\n Bee,\n Identifier,\n PrivateKey,\n type BatchId,\n type EthAddress,\n type Stamper,\n} from \"@ethersphere/bee-js\"\nimport { Binary } from \"cafe-utility\"\nimport { downloadEncryptedSOC } from \"../proxy/download-data\"\nimport { uploadSOC, type UploadTarget } from \"../proxy/upload\"\nimport { UtilizationAwareStamper } from \"../utils/batch-utilization\"\nimport { TimeoutError, sleep, withTimeout } from \"../utils/promise\"\nimport { INTENT_EPOCH_MS, SYNC_READ_TIMEOUT_MS } from \"./timing-constants\"\nimport {\n PartitionIntentPayloadSchemaV1,\n type PartitionIntentPayload,\n} from \"../schemas\"\nimport {\n compareGenerations,\n type PartitionLockGeneration,\n} from \"./partition-lock\"\n\nexport type { PartitionIntentPayload }\n\n/** Domain-separation tag for the intent-SOC identifier. */\nconst PARTITION_INTENT_DOMAIN = \"swarm-id-partition-intent-v1\"\n\n/**\n * Domain tag for the per-(partition, epoch) OCCUPANCY beacon — deliberately\n * NOT keyed by deviceId. A holder publishes it on claim/refresh; any contender\n * reads it during `acquire` to detect a live holder **it has never heard of**\n * (the registry-lag dual-acquire: a re-claiming device whose roster hasn't yet\n * synced a peer is blind to that peer on every deviceId-keyed channel). The\n * address still rotates per epoch, so the read forces a network retrieval that\n * bypasses the frozen static-lock cache.\n */\nconst PARTITION_OCCUPANCY_DOMAIN = \"swarm-id-partition-occupancy-v1\"\n\n// Epoch length for the rotating intent address (TTL-sized so a round + its\n// retries share a bucket; every fresh round rotates to an uncached address).\n// Single source in `sync/timing-constants` (= `LEASE_TTL_MS`), which the\n// dependency-free module lets us share without a circular import.\nexport { INTENT_EPOCH_MS }\n\n// Client-side timeout for a single rival-intent read — the shared cross-device\n// read timeout (Bee has no fast authoritative \"not found\", so an absent rival\n// would otherwise block for tens of seconds; a timed-out read is treated as\n// \"no intent from that device\").\nexport const INTENT_READ_TIMEOUT_MS = SYNC_READ_TIMEOUT_MS\n\n/**\n * Total window an intent round polls rivals before binding a fresh claim.\n * Sized to the gateway's write→cross-device-retrieve propagation delay: a peer\n * that wrote its intent/beacon seconds ago isn't immediately readable, so a\n * single read races ahead of propagation and misses it. Polling over this\n * window lets the peer surface. Tunable per gateway (lease/coordinator opts +\n * the `swarm-id-partition-tuning` localStorage override).\n */\nexport const INTENT_GUARD_WINDOW_MS = 12_000\n\n/** Interval between rival re-reads within the guard window. */\nexport const INTENT_GUARD_POLL_MS = 2500\n\n/**\n * Grace applied to a found beacon's `leasedUntil` when deciding liveness. A\n * beacon is only ever read at the current/previous epoch bucket, so a found one\n * was written ≤ ~2 epochs ago — bounded-fresh by its rotating address. Its\n * `leasedUntil` (writeTime + lock TTL ≈ 30s) can still lapse before the holder's\n * NEXT beacon becomes cross-device retrievable, so a live holder whose freshest\n * beacon hasn't propagated would be misread as departed. Treating a beacon as\n * live while `leasedUntil > now - GRACE` bridges that propagation gap; the\n * bucket-bounded read window means this can never resurrect an aged-out beacon.\n */\nexport const INTENT_LIVENESS_GRACE_MS = INTENT_EPOCH_MS\n\n/** `floor(nowMs / INTENT_EPOCH_MS)` — the current intent epoch bucket. */\nexport function intentEpochBucket(nowMs: number): number {\n return Math.floor(nowMs / INTENT_EPOCH_MS)\n}\n\n/**\n * Build the per-(partition, device, epoch) intent-SOC identifier. The address\n * rotates with `epochBucket`, so it is fresh per contention round — neither\n * the writer's nor a reader's node has a cached copy.\n */\nexport function makePartitionIntentIdentifier(\n batchId: BatchId,\n partition: number,\n deviceId: string,\n epochBucket: number,\n): Identifier {\n const hash = Binary.keccak256(\n new TextEncoder().encode(\n `${PARTITION_INTENT_DOMAIN}:${batchId.toHex()}:${partition}:${deviceId}:${epochBucket}`,\n ),\n )\n return new Identifier(hash)\n}\n\n/**\n * 32-byte SOC chunk address for an intent — `keccak256(identifier ‖ owner)`,\n * the same derivation `uploadSOC` uses. Computable before the upload so the\n * stamper can reserve the chunk's slot (route it to the contended partition's\n * reserved index instead of a data lane).\n */\nexport function intentSocAddress(\n batchId: BatchId,\n partition: number,\n deviceId: string,\n epochBucket: number,\n owner: EthAddress,\n): Uint8Array {\n const identifier = makePartitionIntentIdentifier(\n batchId,\n partition,\n deviceId,\n epochBucket,\n )\n return Binary.keccak256(\n Binary.concatBytes(identifier.toUint8Array(), owner.toUint8Array()),\n )\n}\n\n/** Build the deviceId-INDEPENDENT occupancy identifier for (batch, partition,\n * epoch). Batch-scoped for the same reason the lock is (#589): a beacon on\n * lane p of batch X says nothing about lane p of batch Y. */\nexport function makePartitionOccupancyIdentifier(\n batchId: BatchId,\n partition: number,\n epochBucket: number,\n): Identifier {\n const hash = Binary.keccak256(\n new TextEncoder().encode(\n `${PARTITION_OCCUPANCY_DOMAIN}:${batchId.toHex()}:${partition}:${epochBucket}`,\n ),\n )\n return new Identifier(hash)\n}\n\n/** 32-byte SOC address for an occupancy beacon (for stamper slot reservation). */\nexport function partitionOccupancyAddress(\n batchId: BatchId,\n partition: number,\n epochBucket: number,\n owner: EthAddress,\n): Uint8Array {\n const identifier = makePartitionOccupancyIdentifier(\n batchId,\n partition,\n epochBucket,\n )\n return Binary.keccak256(\n Binary.concatBytes(identifier.toUint8Array(), owner.toUint8Array()),\n )\n}\n\n/**\n * Write this device's intent for (partition, epochBucket). The payload carries\n * the generation the device intends to claim with, so rivals can order against\n * it. Uses the shared backup signer as owner (same as the lock SOC); each\n * device only ever writes its own identifier.\n */\nexport async function writePartitionIntent(opts: {\n bee: Bee\n stamper: Stamper\n backupSigner: PrivateKey\n swarmEncryptionKey: Uint8Array\n batchId: BatchId\n partition: number\n deviceId: string\n epochBucket: number\n generation: PartitionLockGeneration\n /**\n * Present when this write is a holder PRESENCE BEACON (re-published each\n * refresh): lets a reading joiner apply a `leasedUntil > now` liveness test.\n * Omitted for a pre-claim intent (the symmetric-race announce).\n */\n leasedUntil?: number\n}): Promise<void> {\n const identifier = makePartitionIntentIdentifier(\n opts.batchId,\n opts.partition,\n opts.deviceId,\n opts.epochBucket,\n )\n const owner = opts.backupSigner.publicKey().address()\n const address = intentSocAddress(\n opts.batchId,\n opts.partition,\n opts.deviceId,\n opts.epochBucket,\n owner,\n )\n const payload: PartitionIntentPayload = {\n deviceId: opts.deviceId,\n generation: opts.generation,\n ...(opts.leasedUntil !== undefined\n ? { leasedUntil: opts.leasedUntil }\n : {}),\n }\n await writeReservedPartitionSoc({\n bee: opts.bee,\n stamper: opts.stamper,\n backupSigner: opts.backupSigner,\n swarmEncryptionKey: opts.swarmEncryptionKey,\n partition: opts.partition,\n identifier,\n address,\n payload,\n })\n}\n\n/**\n * Publish this device's OCCUPANCY beacon for (partition, epochBucket) — same\n * payload as a presence beacon but at the deviceId-independent occupancy\n * address, so a contender that doesn't know this device can still detect it\n * holds the partition. Always carries `leasedUntil` (it only ever announces a\n * live hold).\n */\nexport async function writePartitionOccupancy(opts: {\n bee: Bee\n stamper: Stamper\n backupSigner: PrivateKey\n swarmEncryptionKey: Uint8Array\n batchId: BatchId\n partition: number\n deviceId: string\n epochBucket: number\n generation: PartitionLockGeneration\n leasedUntil: number\n}): Promise<void> {\n const identifier = makePartitionOccupancyIdentifier(\n opts.batchId,\n opts.partition,\n opts.epochBucket,\n )\n const owner = opts.backupSigner.publicKey().address()\n const address = partitionOccupancyAddress(\n opts.batchId,\n opts.partition,\n opts.epochBucket,\n owner,\n )\n const payload: PartitionIntentPayload = {\n deviceId: opts.deviceId,\n generation: opts.generation,\n leasedUntil: opts.leasedUntil,\n }\n await writeReservedPartitionSoc({\n bee: opts.bee,\n stamper: opts.stamper,\n backupSigner: opts.backupSigner,\n swarmEncryptionKey: opts.swarmEncryptionKey,\n partition: opts.partition,\n identifier,\n address,\n payload,\n })\n}\n\n/**\n * Upload a partition SOC (intent / occupancy / beacon) routed to the contended\n * partition's RESERVED slot so it can never land in a data lane and overstamp\n * user data (see module header). Only the partition-aware stamper can reserve;\n * a plain stamper (legacy single-device) never contends, so a normal upload is\n * fine there.\n */\nasync function writeReservedPartitionSoc(opts: {\n bee: Bee\n stamper: Stamper\n backupSigner: PrivateKey\n swarmEncryptionKey: Uint8Array\n partition: number\n identifier: Identifier\n address: Uint8Array\n payload: PartitionIntentPayload\n}): Promise<void> {\n const data = new TextEncoder().encode(JSON.stringify(opts.payload))\n const target: UploadTarget = {\n mode: \"stamper\",\n bee: opts.bee,\n stamper: opts.stamper,\n }\n const stamper = opts.stamper\n if (stamper instanceof UtilizationAwareStamper) {\n // Serialized against concurrent intent/occupancy/pointer SOC writes so a\n // peer reservation can't clobber this one and mis-stamp it into a data slot.\n await stamper.withIntentSocSlot(opts.address, opts.partition, () =>\n uploadSOC(target, opts.backupSigner, opts.identifier, data, {\n encryptionKey: opts.swarmEncryptionKey,\n }),\n )\n return\n }\n await uploadSOC(target, opts.backupSigner, opts.identifier, data, {\n encryptionKey: opts.swarmEncryptionKey,\n })\n}\n\n/** A 5xx is a transient gateway error, not proof the chunk is absent. */\nconst isServerError = (error: unknown): boolean =>\n error instanceof Error && /status code 5\\d\\d/.test(error.message)\n\n/**\n * Download + parse an intent/occupancy SOC at `identifier`, bounded by\n * `timeoutMs`. Returns `undefined` when the chunk is missing, the read times\n * out (treated as \"no intent\" — see module header), or the payload is\n * malformed. A 5xx server error is RETRIED once before concluding absence: a\n * transient 500 is not evidence the chunk is gone, and treating it as such is\n * what let the dual-acquire slip through under a gateway 500 storm. A 404 /\n * timeout keeps the documented \"absence assumed\" (genuine for the rotating\n * address; preserves liveness).\n */\nasync function readPartitionSoc(opts: {\n bee: Bee\n owner: EthAddress\n identifier: Identifier\n swarmEncryptionKey: Uint8Array\n timeoutMs?: number\n logLabel: string\n /**\n * Retry once on a 5xx (default true). The intent ROUND polls — its own loop\n * already re-reads each sweep — so it passes false to avoid doubling per-read\n * latency + gateway load on a 500-storming node. Single-shot callers (holder\n * presence/occupancy reads) keep the retry so a transient 500 isn't misread as\n * \"absent\" (the dual-acquire guard).\n */\n retryOn5xx?: boolean\n}): Promise<PartitionIntentPayload | undefined> {\n const download = () =>\n withTimeout(\n downloadEncryptedSOC(\n opts.bee,\n opts.owner,\n opts.identifier,\n opts.swarmEncryptionKey,\n ),\n opts.timeoutMs ?? INTENT_READ_TIMEOUT_MS,\n \"intent read timed out\",\n )\n\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n const soc = await download()\n const parsed = PartitionIntentPayloadSchemaV1.safeParse(\n JSON.parse(new TextDecoder().decode(soc.payload)),\n )\n if (!parsed.success) {\n console.debug(`[partition-intent] ${opts.logLabel}: malformed`)\n return undefined\n }\n console.debug(\n `[partition-intent] ${opts.logLabel}: FOUND (gen ts=${parsed.data.generation.timestampMs} leasedUntil=${parsed.data.leasedUntil ?? \"none\"})`,\n )\n return parsed.data\n } catch (error) {\n // Retry once on a transient 5xx; a too-short timeout or a real 404 are\n // treated as absence so a single flaky device can't deadlock the round.\n if (isServerError(error) && attempt === 0 && opts.retryOn5xx !== false)\n continue\n const timedOut = error instanceof TimeoutError\n console.debug(\n `[partition-intent] ${opts.logLabel}: ${\n timedOut\n ? `TIMEOUT (>${opts.timeoutMs ?? INTENT_READ_TIMEOUT_MS}ms)`\n : `missing (${error instanceof Error ? error.message : String(error)})`\n }${isServerError(error) ? \" after 5xx retry\" : \"\"} — treated as no-intent`,\n )\n return undefined\n }\n }\n return undefined\n}\n\n/**\n * Read a single rival's intent for (partition, epochBucket). Returns `undefined`\n * when missing / timed out / malformed (treated as \"no intent\" — see header).\n */\nexport async function readPartitionIntent(opts: {\n bee: Bee\n backupSigner: PrivateKey\n swarmEncryptionKey: Uint8Array\n batchId: BatchId\n partition: number\n deviceId: string\n epochBucket: number\n timeoutMs?: number\n /** See `readPartitionSoc`. The polling round passes false. */\n retryOn5xx?: boolean\n}): Promise<PartitionIntentPayload | undefined> {\n return readPartitionSoc({\n bee: opts.bee,\n owner: opts.backupSigner.publicKey().address(),\n identifier: makePartitionIntentIdentifier(\n opts.batchId,\n opts.partition,\n opts.deviceId,\n opts.epochBucket,\n ),\n swarmEncryptionKey: opts.swarmEncryptionKey,\n timeoutMs: opts.timeoutMs,\n retryOn5xx: opts.retryOn5xx,\n logLabel: `read device=${opts.deviceId} p=${opts.partition} bucket=${opts.epochBucket}`,\n })\n}\n\n/**\n * Read the deviceId-INDEPENDENT occupancy beacon for (partition, epochBucket).\n * Lets a contender detect a live holder it doesn't know about.\n */\nexport async function readPartitionOccupancy(opts: {\n bee: Bee\n backupSigner: PrivateKey\n swarmEncryptionKey: Uint8Array\n batchId: BatchId\n partition: number\n epochBucket: number\n timeoutMs?: number\n}): Promise<PartitionIntentPayload | undefined> {\n return readPartitionSoc({\n bee: opts.bee,\n owner: opts.backupSigner.publicKey().address(),\n identifier: makePartitionOccupancyIdentifier(\n opts.batchId,\n opts.partition,\n opts.epochBucket,\n ),\n swarmEncryptionKey: opts.swarmEncryptionKey,\n timeoutMs: opts.timeoutMs,\n logLabel: `occupancy p=${opts.partition} bucket=${opts.epochBucket}`,\n })\n}\n\n/**\n * Run an intent round for a fresh claim of `partition`: announce our intent,\n * then read every other known device's intent for the current AND previous\n * epoch bucket (covering the rotation boundary). Returns whether this device\n * is the round's winner.\n *\n * Winner = the strictly-minimum generation (earliest `timestampMs`, then\n * smallest `tiebreaker`). We lose iff any rival's intent orders below ours.\n * `compareGenerations` is a total order and distinct devices never tie\n * (tiebreaker derives from `keccak256(deviceId)`), so exactly one contender\n * wins. With no known rivals we win by default (a brand-new device not yet in\n * the registry falls back to the existing guard + TTL behaviour).\n *\n * @returns `\"win\"` to proceed with the claim, `\"lose\"` to back off (read-only).\n */\nexport async function resolveIntentRound(opts: {\n bee: Bee\n stamper: Stamper\n backupSigner: PrivateKey\n swarmEncryptionKey: Uint8Array\n batchId: BatchId\n partition: number\n deviceId: string\n generation: PartitionLockGeneration\n knownDeviceIds: string[]\n now: number\n timeoutMs?: number\n /**\n * Poll rivals over this window before deciding (default 0 = a single read,\n * used by tests). Sized to the gateway propagation delay so a peer's recent\n * intent/beacon surfaces before we bind. See `INTENT_GUARD_WINDOW_MS`.\n */\n guardWindowMs?: number\n /** Interval between polls within the guard window. */\n guardPollMs?: number\n /**\n * Release WATERMARK for this partition, when the caller observed it freed by\n * an explicit release sentinel. A rival's live presence beacon at or below\n * this generation is the just-released holder's lingering GHOST (still within\n * its lease TTL at a live per-epoch address, but no longer a claim), so it\n * must NOT lose us the round. A genuine post-release claimant has a strictly\n * newer generation and still beats us. Omitted → every live beacon counts.\n */\n releasedGeneration?: PartitionLockGeneration\n}): Promise<\"win\" | \"lose\"> {\n const rivals = opts.knownDeviceIds.filter((id) => id !== opts.deviceId)\n if (rivals.length === 0) return \"win\"\n\n const currentBucket = intentEpochBucket(opts.now)\n const previousBucket = currentBucket - 1\n\n // Announce our intent in the current bucket BEFORE reading rivals, so a rival\n // racing us in the same round observes us too — no contender can both miss\n // every rival and bind.\n await writePartitionIntent({\n bee: opts.bee,\n stamper: opts.stamper,\n backupSigner: opts.backupSigner,\n swarmEncryptionKey: opts.swarmEncryptionKey,\n batchId: opts.batchId,\n partition: opts.partition,\n deviceId: opts.deviceId,\n epochBucket: currentBucket,\n generation: opts.generation,\n })\n\n // One sweep: read every rival's intent for both buckets in parallel (each\n // read individually time-bounded), and return the first rival that beats us:\n // - a LIVE holder (beacon with `leasedUntil > now - GRACE`) — already holds\n // it, regardless of generation; or\n // - a contender ordering strictly below us (a pre-claim intent, no\n // `leasedUntil`, smaller generation).\n // A beacon lapsed beyond the grace window is a departed holder — ignored. The\n // grace covers a live holder whose freshest beacon hasn't propagated yet (its\n // still-retrievable previous-bucket beacon's TTL lapsed seconds ago).\n const sweep = async (): Promise<\n { rivalId: string; reason: \"live-beacon\" | \"earlier-intent\" } | undefined\n > => {\n const observed = await Promise.all(\n rivals.flatMap((rivalId) =>\n [currentBucket, previousBucket].map(async (bucket) => ({\n rivalId,\n intent: await readPartitionIntent({\n bee: opts.bee,\n backupSigner: opts.backupSigner,\n swarmEncryptionKey: opts.swarmEncryptionKey,\n batchId: opts.batchId,\n partition: opts.partition,\n deviceId: rivalId,\n epochBucket: bucket,\n timeoutMs: opts.timeoutMs,\n // The round polls; don't double per-read latency/load with a retry.\n retryOn5xx: false,\n }),\n })),\n ),\n )\n for (const r of observed) {\n const intent = r.intent\n if (!intent) continue\n if (intent.leasedUntil !== undefined) {\n if (intent.leasedUntil > opts.now - INTENT_LIVENESS_GRACE_MS) {\n // A beacon at or below an observed release watermark is the\n // just-released holder's stale ghost — not a live claim. Ignore it so\n // a peer can take over the freed partition; a genuine post-release\n // claimant (strictly newer generation) still beats us below.\n if (\n opts.releasedGeneration &&\n compareGenerations(intent.generation, opts.releasedGeneration) <= 0\n )\n continue\n return { rivalId: r.rivalId, reason: \"live-beacon\" }\n }\n continue\n }\n if (compareGenerations(intent.generation, opts.generation) < 0)\n return { rivalId: r.rivalId, reason: \"earlier-intent\" }\n }\n return undefined\n }\n\n // Poll across the guard window. A peer's freshly-written intent/beacon takes\n // a few seconds to become cross-device-retrievable on a gateway, so a single\n // immediate read races ahead of propagation. We back off as soon as ANY sweep\n // finds a beating rival; we only win after the whole window stays clear.\n // Poll until an ABSOLUTE deadline, not a fixed count: each sweep's reads are\n // unbounded (an absent read on a flaky gateway blocks up to the read timeout),\n // so a fixed poll count lets one round overrun its window by multiples and\n // blow the acquire budget. A wall-clock deadline keeps the round ≈ the guard\n // window (+ one in-flight sweep) regardless of read latency or rival count.\n const guardWindowMs = opts.guardWindowMs ?? 0\n const guardPollMs = opts.guardPollMs ?? INTENT_GUARD_POLL_MS\n const end = Date.now() + guardWindowMs\n let beatenBy: Awaited<ReturnType<typeof sweep>>\n let polls = 0\n do {\n if (polls > 0) await sleep(guardPollMs)\n polls++\n beatenBy = await sweep()\n if (beatenBy) break\n } while (Date.now() < end)\n\n const outcome = beatenBy ? \"lose\" : \"win\"\n console.debug(\n `[partition-intent] round p=${opts.partition} self=${opts.deviceId} rivals=${rivals.length} polls=${polls} → ${outcome}` +\n (beatenBy\n ? ` (beaten by ${beatenBy.rivalId} via ${beatenBy.reason})`\n : \"\"),\n )\n\n return outcome\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Partition-state pointer for multi-device postage-batch sharing.\n *\n * The current holder publishes its per-partition counter on each upload (and on\n * release); the next holder reads it on acquire and resumes from exactly that\n * counter, so the two devices never race for the same `(bucket, slot)` pair\n * during an orderly hand-off.\n *\n * The counter is not a single blob: it is split into the same per-`chunkIndex`\n * chunks the utilisation store uses, each uploaded with a random key to the\n * partition's reserved slot (never a data slot), and a single \"reference chunk\"\n * lists their 64-byte encrypted references (address‖key).\n *\n * The address of the latest reference chunk is published as a **rotating\n * per-epoch state-pointer SOC** (`writeStatePointer`/`readStatePointer`): the\n * identifier is `keccak256(topic ‖ uint64(floor(now / STATE_POINTER_EPOCH_MS)))`,\n * so a reader COMPUTES the address directly — no epoch-tree walk — and the\n * rotating address bypasses a gateway's frozen static-cache. The holder\n * heartbeats the pointer on every refresh, so a taking-over device finds it at\n * the current/previous bucket around `now` or the gone holder's `leasedUntil`.\n * Mirrors the occupancy beacon (`partition-intent.ts`). See\n * docs/Postage-Batch-Partitioning.md (§8 cross-device handoff).\n *\n * Topic = keccak256(\"swarm-id-partition-state-v1\" ‖ batchId ‖ uint32(partition))\n * Owner = backup signer (`deriveSecret(swarmEncryptionKey, BACKUP_KEY_LABEL)`)\n */\n\nimport {\n Bee,\n BatchId,\n EthAddress,\n Identifier,\n PrivateKey,\n Reference,\n Topic,\n type Stamper,\n} from \"@ethersphere/bee-js\"\nimport { Binary } from \"cafe-utility\"\nimport { z } from \"zod\"\nimport {\n downloadDataWithChunkAPI,\n downloadEncryptedSOC,\n} from \"../proxy/download-data\"\nimport { uploadChunk, uploadSOC, type UploadTarget } from \"../proxy/upload\"\nimport { TimeoutError, withTimeout } from \"../utils/promise\"\nimport { makeEncryptedContentAddressedChunk } from \"../chunk\"\nimport {\n LEASE_TTL_MS,\n NUM_BUCKETS,\n UtilizationAwareStamper,\n extractChunk,\n getChunkLayout,\n mergeChunk,\n toBucket,\n} from \"../utils/batch-utilization\"\nimport { lockSocBucket } from \"../utils/lock-soc\"\nimport {\n INTENT_EPOCH_MS,\n intentEpochBucket,\n intentSocAddress,\n partitionOccupancyAddress,\n} from \"./partition-intent\"\nimport { SYNC_READ_TIMEOUT_MS } from \"./timing-constants\"\n\n/** Length of a Swarm encrypted reference: 32-byte address + 32-byte key. */\nconst ENCRYPTED_REFERENCE_BYTES = 64\n\n/**\n * All-zero reference sentinel for a counter chunk that is entirely zero (never\n * written). It is NOT uploaded — the reference chunk just carries the sentinel\n * at that index, and the reader defaults a sentinel to a zero chunk. Keeps the\n * first publish sparse (only the touched chunks) instead of writing all\n * `numUtilizationChunks`, most of which are zero. Safe sentinel: a real ref is\n * `keccak256-address ‖ random-key`, never all-zero.\n */\nconst ZERO_CHUNK_REF = new Uint8Array(ENCRYPTED_REFERENCE_BYTES)\n\n/** Domain-separation tag for partition-state-feed topics. */\nconst PARTITION_STATE_TOPIC_DOMAIN = \"swarm-id-partition-state-v1\"\n\n/** Bytes to encode `partition` as big-endian uint32. */\nconst UINT32_BYTES = 4\n\n/**\n * Schema for the *decoded* partition state: the per-bucket counter\n * reconstructed from the binary wire format (reference chunk → counter\n * chunks, see the module doc above for the byte layout). The wire format is\n * versioned by the feed topic domain (`PARTITION_STATE_TOPIC_DOMAIN`), not an\n * in-blob version byte — a future v2 publishes under a v2 topic and never\n * collides with v1 readers.\n *\n * `readPartitionState` validates the assembled counter against this schema\n * before returning it; a failure is reported as `readFailed` (fail-safe —\n * never a zero counter, which would re-issue used slots).\n * `writePartitionState` validates its input against it before publishing.\n */\nexport const PartitionStateSchemaV1 = z.object({\n counters: z.instanceof(Uint32Array).refine((c) => c.length === NUM_BUCKETS, {\n message: `counters must have ${NUM_BUCKETS} entries`,\n }),\n})\n\nexport type PartitionState = z.infer<typeof PartitionStateSchemaV1>\n\n/**\n * Build the per-partition state feed topic.\n *\n * Hashing rather than concatenating-to-string keeps the topic length\n * fixed at 32 bytes regardless of how many partitions we support later.\n */\nexport function makePartitionStateTopic(\n batchId: BatchId,\n partition: number,\n): Topic {\n const partitionBytes = new Uint8Array(UINT32_BYTES)\n new DataView(partitionBytes.buffer).setUint32(0, partition, false)\n const hash = Binary.keccak256(\n Binary.concatBytes(\n new TextEncoder().encode(PARTITION_STATE_TOPIC_DOMAIN),\n batchId.toUint8Array(),\n partitionBytes,\n ),\n )\n return new Topic(hash)\n}\n\n/**\n * Granularity of the rotating state-pointer address — the SAME epoch the\n * intent/occupancy beacons rotate on: a reader computes a writer's bucket from\n * this shared constant, so the two must never drift apart (this module already\n * imports from partition-intent; there is no import cycle).\n *\n * COUPLING: the takeover lookup (`readStatePointer`) scans the whole\n * `LEASE_TTL_MS`-wide bucket span. The reads run CONCURRENTLY (each\n * timeout-bounded), so widening the span costs more parallel reads but not more\n * wall-clock — the lookup latency stays ~one read timeout regardless of the\n * `STATE_POINTER_EPOCH_MS` vs `LEASE_TTL_MS` ratio.\n */\nexport const STATE_POINTER_EPOCH_MS = INTENT_EPOCH_MS\n\n/**\n * Extra epoch buckets the takeover lookup scans BELOW the lease-TTL span, to\n * tolerate a holder whose last few best-effort pointer heartbeats were dropped\n * (flaky gateway) while its lock SOC kept advancing `leasedUntil`. The lookup is\n * TTL-independent (see `readStatePointer`); this only sets how many dropped\n * final heartbeats it survives.\n */\nconst POINTER_LOOKUP_SLACK_BUCKETS = 2\n\n/** Bound a single pointer read so an absent SOC doesn't block on peer exhaustion. */\nconst STATE_POINTER_READ_TIMEOUT_MS = SYNC_READ_TIMEOUT_MS\n\n/**\n * Sentinel for a pointer-bucket read that TIMED OUT — distinct from `undefined`\n * (a clean miss / malformed blob). A timeout is INCONCLUSIVE: the bucket may hold\n * a pointer the slow gateway couldn't return, so \"no pointer found\" across the\n * scan is only authoritative when no candidate bucket timed out. Mirrors the\n * roster scan's `ROSTER_READ_TIMED_OUT`.\n */\nconst POINTER_READ_TIMED_OUT = Symbol(\"state-pointer-read-timed-out\")\ntype PointerBucketRead =\n | { referenceHex: string }\n | undefined\n | typeof POINTER_READ_TIMED_OUT\n\n/** Result of a `readStatePointer` lookup (see its doc for `inconclusive`). */\nexport interface StatePointerLookup {\n pointer?: { referenceHex: string }\n inconclusive: boolean\n}\n\n/** `floor(nowMs / STATE_POINTER_EPOCH_MS)` — the current state-pointer epoch bucket. */\nexport function statePointerEpochBucket(nowMs: number): number {\n return Math.floor(nowMs / STATE_POINTER_EPOCH_MS)\n}\n\n/**\n * The state-pointer payload: the 64-byte reference of the latest published\n * reference chunk (address‖key). The reader picks the highest-numbered present\n * bucket (a holder writes contiguous buckets up to its last heartbeat), so the\n * bucket index already orders writes — no in-payload timestamp is needed.\n */\nconst StatePointerPayloadSchemaV1 = z.object({\n referenceHex: z.string(),\n})\n\n/**\n * SOC identifier for the partition's state pointer at `epochBucket`:\n * `keccak256(topic ‖ uint64(bucket))`. The address rotates every\n * `STATE_POINTER_EPOCH_MS`, so a reader computes it directly (no feed walk) and\n * the fresh address forces a network read that bypasses a gateway's frozen\n * static-cache — the same property the occupancy beacon relies on.\n */\nfunction makeStatePointerIdentifier(\n topic: Topic,\n epochBucket: number,\n): Identifier {\n const bucketBytes = new Uint8Array(8)\n new DataView(bucketBytes.buffer).setBigUint64(0, BigInt(epochBucket), false)\n return new Identifier(\n Binary.keccak256(Binary.concatBytes(topic.toUint8Array(), bucketBytes)),\n )\n}\n\n/**\n * Publish the reference-chunk pointer at the current epoch bucket — a reserved-\n * slot, encrypted SOC (mirrors the occupancy beacon's `writeReservedPartitionSoc`).\n * Reserved-slot routed so it never consumes a data slot; overwrites within the\n * same 30 s bucket. Called per publish AND on the refresh-tick heartbeat so a\n * held partition always has a fresh pointer in the current bucket.\n */\nexport async function writeStatePointer(opts: {\n bee: Bee\n stamper: Stamper\n backupSigner: PrivateKey\n swarmEncryptionKey: Uint8Array\n batchId: BatchId\n partition: number\n referenceHex: string\n nowMs: number\n}): Promise<void> {\n const topic = makePartitionStateTopic(opts.batchId, opts.partition)\n const epochBucket = statePointerEpochBucket(opts.nowMs)\n const identifier = makeStatePointerIdentifier(topic, epochBucket)\n const owner = opts.backupSigner.publicKey().address()\n const address = makeStatePointerAddress(identifier, owner)\n const data = new TextEncoder().encode(\n JSON.stringify({\n referenceHex: opts.referenceHex,\n }),\n )\n const target: UploadTarget = {\n mode: \"stamper\",\n bee: opts.bee,\n stamper: opts.stamper,\n }\n if (opts.stamper instanceof UtilizationAwareStamper) {\n // Serialized against concurrent intent/occupancy/pointer SOC writes (the\n // off-lock refresh-tick beacon vs. this under-lock publish) so neither\n // clobbers the shared intent-SOC reservation and mis-stamps into a data slot.\n await opts.stamper.withIntentSocSlot(address, opts.partition, () =>\n uploadSOC(target, opts.backupSigner, identifier, data, {\n encryptionKey: opts.swarmEncryptionKey,\n }),\n )\n return\n }\n await uploadSOC(target, opts.backupSigner, identifier, data, {\n encryptionKey: opts.swarmEncryptionKey,\n })\n}\n\n/** 32-byte SOC address `keccak256(identifier ‖ owner)`. */\nfunction makeStatePointerAddress(\n identifier: Identifier,\n owner: EthAddress,\n): Uint8Array {\n return Binary.keccak256(\n Binary.concatBytes(identifier.toUint8Array(), owner.toUint8Array()),\n )\n}\n\n/**\n * The 32-byte state-pointer SOC address for `partition` at the epoch bucket\n * covering `nowMs`. Exposed so `writePartitionState` can exclude the pointer's\n * bucket from the reserved-slot buckets its counter/reference chunks may use\n * (both land at slot = partition; a collision would evict one). Same derivation\n * `writeStatePointer` writes at.\n */\nexport function statePointerAddress(\n batchId: BatchId,\n partition: number,\n owner: EthAddress,\n nowMs: number,\n): Uint8Array {\n const topic = makePartitionStateTopic(batchId, partition)\n const identifier = makeStatePointerIdentifier(\n topic,\n statePointerEpochBucket(nowMs),\n )\n return makeStatePointerAddress(identifier, owner)\n}\n\n/**\n * Read the latest state pointer by computing bucket addresses directly — no\n * feed walk. Checks (newest-first) the current/previous bucket around `now` and,\n * when given, the gone holder's lease span: every bucket from its last lock\n * refresh (`leasedUntil`) back through `leasedUntil - LEASE_TTL_MS` plus\n * {@link POINTER_LOOKUP_SLACK_BUCKETS} of slack. The holder heartbeats the\n * pointer at `floor(now / EPOCH)` on every refresh, so its last DURABLE pointer\n * lands somewhere in that span; scanning the whole span (rather than just the\n * `leasedUntil` bucket) makes the lookup independent of the\n * `STATE_POINTER_EPOCH_MS` vs `LEASE_TTL_MS` ratio AND tolerant of a couple of\n * dropped final heartbeats. Returns the freshest pointer found (`pointer`), or\n * `pointer: undefined` when none of the candidate buckets carry one.\n * `inconclusive` marks a verdict that is NOT authoritative: with no pointer, a\n * CLEAN scan (every candidate bucket read resolved) proves \"no published state\"\n * while ≥1 timed-out bucket leaves a pointer we couldn't fetch possible; with a\n * pointer found, a timed-out bucket NEWER than it may hide a fresher resume\n * point (a later acked publish), so the found pointer is only a stale\n * candidate. Either way the caller must fail safe on `inconclusive` rather\n * than resume. Ceiling: a holder dark for longer than\n * `LEASE_TTL_MS + POINTER_LOOKUP_SLACK_BUCKETS·EPOCH` past its last heartbeat\n * resumes from zero.\n */\nexport async function readStatePointer(opts: {\n bee: Bee\n owner: EthAddress\n swarmEncryptionKey: Uint8Array\n batchId: BatchId\n partition: number\n nowMs: number\n holderLeasedUntilMs?: number\n}): Promise<StatePointerLookup> {\n const topic = makePartitionStateTopic(opts.batchId, opts.partition)\n const buckets = new Set<number>()\n const cur = statePointerEpochBucket(opts.nowMs)\n buckets.add(cur)\n buckets.add(cur - 1)\n if (opts.holderLeasedUntilMs !== undefined) {\n // Scan the whole lease span: from the final lock refresh (`leasedUntil`)\n // back through the earliest this lease could have heartbeated\n // (`leasedUntil - LEASE_TTL_MS`), minus slack for dropped final heartbeats.\n const hi = statePointerEpochBucket(opts.holderLeasedUntilMs)\n const lo =\n statePointerEpochBucket(opts.holderLeasedUntilMs - LEASE_TTL_MS) -\n POINTER_LOOKUP_SLACK_BUCKETS\n for (let b = lo; b <= hi; b++) buckets.add(b)\n }\n // Read every candidate bucket CONCURRENTLY (each timeout-bounded), then pick\n // the freshest present pointer. Parallel rather than a serial newest-first\n // short-circuit: a cold takeover scanning the whole lease span pays ~one read\n // timeout instead of N of them. A holder writes contiguous buckets up to its\n // last heartbeat, so the highest-numbered present bucket is the freshest — and\n // `ordered`/`Promise.all` preserve that newest-first order, so returning the\n // first present entry yields the same pointer the serial scan would have.\n const ordered = Array.from(buckets).sort((a, b) => b - a)\n const found: PointerBucketRead[] = await Promise.all(\n ordered.map(async (bucket) => {\n const identifier = makeStatePointerIdentifier(topic, bucket)\n try {\n const soc = await withTimeout(\n downloadEncryptedSOC(\n opts.bee,\n opts.owner,\n identifier,\n opts.swarmEncryptionKey,\n ),\n STATE_POINTER_READ_TIMEOUT_MS,\n \"state-pointer read timed out\",\n )\n const parsed = StatePointerPayloadSchemaV1.safeParse(\n JSON.parse(new TextDecoder().decode(soc.payload)),\n )\n return parsed.success ? parsed.data : undefined\n } catch (error) {\n // A timeout is INCONCLUSIVE (the slot may hold a pointer the slow gateway\n // couldn't return); a clean miss / malformed blob is a conclusive absent.\n return error instanceof TimeoutError\n ? POINTER_READ_TIMED_OUT\n : undefined\n }\n }),\n )\n // `ordered` is newest-first, so the first present entry is the freshest\n // pointer — but only a pointer with NO timed-out bucket above it is\n // authoritative. A newer bucket that timed out may hide a fresher pointer (a\n // later acked publish); returning the older one as conclusive would resume\n // below the acked high-water and re-issue acked slots. Surface that as\n // `inconclusive` so the caller fails safe instead of trusting a stale\n // candidate.\n for (let i = 0; i < found.length; i++) {\n const read = found[i]\n if (read !== undefined && read !== POINTER_READ_TIMED_OUT) {\n const newerTimedOut = found\n .slice(0, i)\n .some((newer) => newer === POINTER_READ_TIMED_OUT)\n return { pointer: read, inconclusive: newerTimedOut }\n }\n }\n // No pointer anywhere. Only a CLEAN scan (no bucket timed out) authoritatively\n // means \"no state was published\"; a timed-out candidate leaves it ambiguous.\n return {\n pointer: undefined,\n inconclusive: found.some((read) => read === POINTER_READ_TIMED_OUT),\n }\n}\n\n/**\n * Read the latest partition-state-feed entry and reconstruct the\n * `localCounter` array — the previous holder's per-bucket high-water.\n *\n * Returns a fresh zero-filled array when no prior snapshot exists — i.e.\n * when a fresh partition is being seeded for the first time (Case A on a\n * brand-new account, or Case B when no peer has yet published on the\n * partition we're about to take). We resume at exactly the published\n * counter: an unclean-crash window where a peer stamped past its last\n * publish is not defended against — that data was never durably persisted\n * anyway, so overwriting those slots loses nothing of value.\n *\n * Pass `knownReferenceHex` (the feed reference this device's local counter was\n * last in sync with) to short-circuit: when the feed still points there,\n * returns `{ unchanged: true }` WITHOUT downloading the reference/counter\n * chunks, and the caller reuses its local counter. On a real read it returns\n * the reconstructed `localCounter` plus the current `referenceHex` for the\n * caller to cache.\n */\nexport async function readPartitionState(\n opts: {\n bee: Bee\n owner: EthAddress\n swarmEncryptionKey: Uint8Array\n batchId: BatchId\n partition: number\n batchDepth: number\n /** Gone holder's `leasedUntil` (from the lock SOC) — locates the pointer of\n * a partition that's been free for a while at its last-heartbeat bucket.\n * A holder heartbeats the pointer up to its `leasedUntil`, so reading that\n * bucket (and the one before) finds a published pointer durably, however\n * long ago: \"no pointer found\" then reliably means \"no state was published\". */\n holderLeasedUntilMs?: number\n /**\n * True when this partition's lock SOC could not be read conclusively (a\n * transient/hanging read, NOT a clean 404). If we then find no published\n * pointer we must NOT assume \"no state\" and resume from zero — the lock may\n * be hiding an expired holder whose acked slots we'd re-issue. Treat that as\n * `readFailed` (degrade to read-only + retry) instead. A genuinely fresh\n * partition has a cleanly-absent lock (`false`) and still zero-seeds.\n */\n lockUnreadable?: boolean\n /**\n * Wall-clock ms to anchor the state-pointer lookup to — the reader half of\n * the rendezvous. The lease passes its own clock (`this.now()`, = `Date.now()`\n * in production) so the reader computes the same buckets the writer wrote at.\n * Defaults to `Date.now()` for bare callers. Injectable for deterministic /\n * per-device-skew tests (#385).\n */\n nowMs?: number\n /**\n * A `readStatePointer` result the caller already fetched (speculatively,\n * overlapped with its lock/beacon scan) for this SAME\n * (owner, batch, partition) with the SAME `nowMs` and NO\n * `holderLeasedUntilMs` span. Only valid when `holderLeasedUntilMs` is\n * undefined here too — identical inputs make it exactly the lookup this\n * function would perform, so all fail-safe classification below applies\n * unchanged. When provided, the pointer lookup is skipped.\n */\n prefetchedPointer?: StatePointerLookup\n },\n knownReferenceHex?: string,\n): Promise<{\n localCounter?: Uint32Array\n referenceHex?: string\n unchanged: boolean\n /**\n * True when the feed HAS an entry but the reference chunk or a counter\n * chunk could not be read. The caller must NOT proceed with a zero\n * counter — the partition has a real resume point we failed to learn,\n * and stamping from zero would re-issue every used slot (each overstamp\n * evicts the existing data chunk from the reserve). Degrade to read-only\n * and retry instead.\n */\n readFailed?: boolean\n /**\n * Buckets occupied by the published state chunks (counter chunks + the\n * reference chunk) of the entry that was read. The caller hands these to\n * the stamper so utilisation saves avoid overstamping (and evicting) the\n * published resume point. Present only on a successful full read.\n */\n stateBuckets?: number[]\n /**\n * The N per-chunk refs (sentinels included) the resume state was assembled\n * from — i.e. the reference chunk's contents. The caller seeds these as the\n * lease's `publishedReferences` so its FIRST publish is incremental against\n * the resumed state (only the changed chunk + reference chunk), not a full\n * re-publish. Present on a full read and (best-effort) on a cache hit;\n * undefined when no prior state exists.\n */\n references?: Uint8Array[]\n}> {\n const {\n bee,\n owner,\n swarmEncryptionKey,\n batchId,\n partition,\n batchDepth,\n holderLeasedUntilMs,\n lockUnreadable,\n nowMs = Date.now(),\n } = opts\n\n // A prefetched lookup is only equivalent when no lease span widens the\n // bucket set — enforce rather than trust the caller.\n const prefetched =\n holderLeasedUntilMs === undefined ? opts.prefetchedPointer : undefined\n const { pointer, inconclusive: pointerInconclusive } =\n prefetched ??\n (await readStatePointer({\n bee,\n owner,\n swarmEncryptionKey,\n batchId,\n partition,\n nowMs,\n holderLeasedUntilMs,\n }))\n\n if (!pointer) {\n // No pointer in any candidate bucket (around `now` or the holder's\n // `leasedUntil`). \"No pointer\" is only proof of \"no state\" when the read\n // was CONCLUSIVE. Fail safe (degrade to read-only + retry) rather than\n // resume — which could re-issue a gone holder's acked slots, each overstamp\n // evicting its data chunk — whenever the verdict is not authoritative:\n // - `lockUnreadable`: the lock read itself hung, so the lock may hide an\n // expired holder whose state we'd resume over; OR\n // - a candidate pointer-bucket read TIMED OUT (`pointerInconclusive`) on a\n // partition that DID have a holder (`holderLeasedUntilMs` set — expired or\n // released), so a real pointer may exist we just couldn't fetch. This is\n // the read the rest of the handoff path guards everywhere else; without it\n // a fast-but-empty lock read + all-slow pointer reads would silently\n // zero-resume.\n // This guard runs BEFORE the local-synced-reference shortcut below: the\n // synced reference is per-device and never cleared by a PEER's activity, so\n // on an inconclusive scan it may be stale across a foreign holder's newer\n // publish — resuming `unchanged` from it would re-issue that holder's acked\n // slots just as surely as a zero-seed.\n if (\n lockUnreadable ||\n (pointerInconclusive && holderLeasedUntilMs !== undefined)\n ) {\n console.warn(\n `[partition-state] partition ${partition}: no pointer found but the read was inconclusive ` +\n `(lockUnreadable=${lockUnreadable === true} pointerTimedOut=${pointerInconclusive}); ` +\n \"degrading to read-only instead of resuming.\",\n )\n return { unchanged: false, readFailed: true }\n }\n // Conclusive no-pointer. A holder heartbeats its pointer up to\n // `leasedUntil`, so this reliably means no state was published — resume\n // from zero. (If we hold a local synced reference, keep it: same-device\n // reclaim within the trail.)\n if (knownReferenceHex !== undefined) {\n return { referenceHex: knownReferenceHex, unchanged: true }\n }\n // A genuinely fresh partition (no holder ever → `holderLeasedUntilMs`\n // undefined AND a cleanly-absent lock) still zero-seeds even if a pointer\n // read was slow: there is no prior state to protect, so first-claim proceeds.\n return { localCounter: new Uint32Array(NUM_BUCKETS), unchanged: false }\n }\n\n // A pointer WAS found but a bucket NEWER than it timed out: a fresher pointer\n // (a later acked publish) may be hidden behind the slow read, so this one is a\n // stale candidate, not the resume point. Resuming from it would re-issue the\n // acked slots published after it. The partition demonstrably HAS state, so\n // fail safe unconditionally (no fresh-partition exemption applies here).\n if (pointerInconclusive) {\n console.warn(\n `[partition-state] partition ${partition}: found a pointer but a newer ` +\n \"bucket read timed out (a fresher resume point may be hidden); \" +\n \"degrading to read-only instead of resuming from a stale candidate.\",\n )\n return { unchanged: false, readFailed: true }\n }\n\n const referenceHex = pointer.referenceHex\n const refBytes = new Reference(referenceHex).toUint8Array()\n\n // Cache hit: the pointer still names the reference this device's local counter\n // is already in sync with. Skip the COUNTER-chunk downloads — but still fetch\n // the single reference chunk so we can return its per-chunk refs, letting the\n // caller's FIRST publish be incremental against the resumed state rather than a\n // full re-publish. Best-effort: on failure omit `references` (the first publish\n // then falls back to the sparse-full path).\n if (knownReferenceHex !== undefined && referenceHex === knownReferenceHex) {\n const { numUtilizationChunks } = getChunkLayout(batchDepth)\n const references = await downloadReferenceChunkRefs(\n bee,\n refBytes,\n numUtilizationChunks,\n ).catch(() => undefined)\n return { referenceHex, unchanged: true, references }\n }\n\n const localCounter = new Uint32Array(NUM_BUCKETS)\n // The reference chunk and every counter chunk overstamp this partition's\n // reserved slot in their respective buckets — collect those buckets so the\n // caller can protect them from utilisation saves (an overstamp there would\n // evict the published resume point). The chunk address is the first 32\n // bytes of each 64-byte encrypted reference.\n const stateBuckets: number[] = [toBucket(refBytes.slice(0, 32))]\n\n // The feed points to the reference chunk: a single chunk holding the\n // 64-byte encrypted references (address‖key) of this partition's counter\n // chunks. Each reference carries its own decryption key, so no key\n // derivation is needed — downloadDataWithChunkAPI decrypts from the ref.\n //\n // Failure policy: the feed HAS an entry, so the partition has a real\n // resume point. If we cannot read it (transient network error, or a\n // published chunk evicted from the reserve), we must NOT fall back to a\n // zero counter — resuming at zero re-issues every used slot, and each\n // overstamp deterministically evicts the existing data chunk (Bee replaces\n // the older chunk at a colliding (bucket, slot) stamp index). Report\n // `readFailed` so the caller degrades to read-only and retries later.\n // Collected here so it survives the try for the success return. The full set\n // of per-chunk refs (sentinels included) lets the caller seed the lease's\n // `publishedReferences` for an incremental first publish.\n let references: Uint8Array[] = []\n try {\n const { numUtilizationChunks } = getChunkLayout(batchDepth)\n references = await downloadReferenceChunkRefs(\n bee,\n refBytes,\n numUtilizationChunks,\n )\n // Fire all counter-chunk downloads in one round — every address is already\n // in hand from the reference chunk, so serializing them would pay K×\n // gateway latency on the cold takeover path for nothing (mirrors the\n // parallelism the publish path uses). All-zero sentinel refs are chunks\n // that were never written: leave them zero, no download (sparse publish).\n const chunkReads = await Promise.all(\n references.map((ref, i) =>\n bytesAllZero(ref)\n ? undefined\n : downloadDataWithChunkAPI(bee, Binary.uint8ArrayToHex(ref)).then(\n (data) => ({ i, ref, data }),\n ),\n ),\n )\n for (const read of chunkReads) {\n if (read === undefined) continue\n mergeChunk(localCounter, read.i, read.data, batchDepth)\n stateBuckets.push(toBucket(read.ref.slice(0, 32)))\n }\n PartitionStateSchemaV1.parse({ counters: localCounter })\n } catch (err) {\n console.warn(\n `[partition-state] reading partition ${partition} counter failed; degrading to read-only:`,\n err,\n )\n // Don't return referenceHex — the read failed, so the caller must not\n // cache this reference as \"synced\" (it would skip a needed future download).\n return { unchanged: false, readFailed: true }\n }\n\n // No feed-SOC compensation needed: the state pointer is a reserved-slot SOC\n // (it overstamps the partition's reserved slot, like the counter chunks), so\n // — unlike the old data-slot epoch feed entry — it consumes no data slot that\n // the snapshot would have to account for.\n\n return {\n localCounter,\n referenceHex,\n unchanged: false,\n stateBuckets,\n references,\n }\n}\n\n/**\n * Download a partition's reference chunk and split it into its N per-chunk\n * encrypted refs (address‖key), sentinels included. Throws on a missing chunk\n * or a wrong-length reference chunk (callers map that to `readFailed` on the\n * full-read path, or to `undefined` references on the best-effort cache-hit path).\n */\nasync function downloadReferenceChunkRefs(\n bee: Bee,\n refBytes: Uint8Array,\n numUtilizationChunks: number,\n): Promise<Uint8Array[]> {\n const referenceChunk = await downloadDataWithChunkAPI(\n bee,\n new Reference(refBytes).toHex(),\n )\n const expectedLength = numUtilizationChunks * ENCRYPTED_REFERENCE_BYTES\n if (referenceChunk.length !== expectedLength) {\n throw new Error(\n `reference chunk has ${referenceChunk.length} bytes, expected ${expectedLength} (${numUtilizationChunks} × ${ENCRYPTED_REFERENCE_BYTES})`,\n )\n }\n return Array.from({ length: numUtilizationChunks }, (_, i) =>\n referenceChunk.slice(\n i * ENCRYPTED_REFERENCE_BYTES,\n (i + 1) * ENCRYPTED_REFERENCE_BYTES,\n ),\n )\n}\n\n/**\n * Publish the current `localCounter` to the partition-state feed.\n *\n * The counter is split into the same per-`chunkIndex` chunks the utilisation\n * store uses; each is uploaded with a random encryption key to this partition's\n * reserved slot (so it never consumes a data slot), yielding a 64-byte\n * encrypted reference (address‖key). A single \"reference chunk\" lists those\n * references and is itself uploaded the same way; the feed points at it. A\n * taking-over device follows the feed → reference chunk → counter chunks,\n * decrypting each from the embedded key.\n *\n * INCREMENTAL: pass `previousReferences` (the refs the last publish returned)\n * and `previousCounter` (the counter it published) to re-upload ONLY the chunks\n * that changed, reusing the retained refs for the rest. A typical upload changes\n * the data SOC's bucket plus the previous publish's feed-SOC bucket, so the\n * publish is ~3-4 chunks instead of the whole counter. Omit them (release, or\n * the first publish of a session) for a full publish.\n *\n * PARALLEL: every chunk address is content-derived, so all PUTs (counter chunks\n * + reference chunk + state-pointer SOC) are computed first and fired in a\n * single `Promise.all`. The pointer is written at the current rotating epoch\n * bucket — no feed walk to publish, and the reader computes its address directly.\n */\nexport async function writePartitionState(opts: {\n bee: Bee\n stamper: Stamper\n batchId: BatchId\n batchDepth: number\n partition: number\n localCounter: Uint32Array\n backupSigner: PrivateKey\n swarmEncryptionKey: Uint8Array\n /** Refs from the previous publish; with `previousCounter`, enables incremental. */\n previousReferences?: Uint8Array[]\n /** The counter the previous publish wrote; diffed to find changed chunks. */\n previousCounter?: Uint32Array\n /**\n * This device's id. When provided, the publish also avoids the buckets of\n * this device's current/previous-epoch intent beacons (the refresh tick\n * writes them off-lock at the same reserved slot). Omit for read-only / the\n * deviceId-independent paths — the pointer and occupancy buckets are avoided\n * regardless.\n */\n deviceId?: string\n /**\n * Wall-clock ms the rotating pointer/beacon rendezvous addresses are anchored\n * to. The lease passes its own clock (`this.now()`, = `Date.now()` in\n * production) so writer, reader, and the collision detector all agree (#385);\n * the reader anchors to the same clock. Defaults to `Date.now()` for bare\n * callers (direct unit tests). Injectable so a test can drive rendezvous\n * deterministically / model per-device skew.\n */\n nowMs?: number\n}): Promise<{\n /** Reference-chunk reference (hex) — the caller's \"synced reference\". */\n referenceHex: string\n /**\n * Buckets occupied by the current state chunks (retained + re-uploaded\n * counter chunks + the reference chunk). The caller hands these to the\n * stamper so subsequent utilisation saves avoid overstamping (evicting) the\n * publish.\n */\n stateBuckets: number[]\n /** The full ref set after this publish, for the caller to cache. */\n references: Uint8Array[]\n /** Snapshot of the counter this publish wrote (pass as `previousCounter` next). */\n publishedCounter: Uint32Array\n}> {\n const {\n bee,\n stamper,\n batchId,\n batchDepth,\n partition,\n localCounter,\n backupSigner,\n swarmEncryptionKey,\n previousReferences,\n previousCounter,\n deviceId,\n nowMs = Date.now(),\n } = opts\n\n PartitionStateSchemaV1.parse({ counters: localCounter })\n\n const { numUtilizationChunks } = getChunkLayout(batchDepth)\n const target: UploadTarget = { mode: \"stamper\", bee, stamper }\n const owner = backupSigner.publicKey().address()\n const reserve =\n stamper instanceof UtilizationAwareStamper ? stamper : undefined\n\n // Snapshot what we are about to publish BEFORE the feed SOC (uploaded below)\n // mutates the live counter — this is the exact resume point, cached by the\n // caller and diffed on the next publish.\n const publishedCounter = localCounter.slice()\n\n // Incremental only when we have a full previous ref set AND the counter it\n // published; otherwise re-upload every chunk (refs/diff unknown). Re-upload a\n // chunk iff its serialized bytes changed since the last publish — i.e. the\n // chunk(s) covering the bucket(s) this session's data upload(s) touched.\n // Reserved-slot writes (the prior publish's reference/pointer SOCs) never bump\n // the counter, so the data write is the only inter-publish counter delta.\n const incremental =\n previousReferences !== undefined &&\n previousReferences.length === numUtilizationChunks &&\n previousCounter !== undefined\n\n // KEYSTONE tripwire (fail loud, never silent slot reuse). The counter is a\n // monotonic per-bucket high-water, so any publish is always >= the one the last\n // publish recorded (see the SAFETY INVARIANT note in `PartitionLease.claimPartition`).\n // A regression means the stamper's counter/synced-ref persistence broke its\n // invariant. On the INCREMENTAL path it is actively dangerous — an unchanged-\n // looking chunk retains a ref ABOVE the live counter and a takeover resumes PAST\n // an acked slot into reuse. But it is a broken-persistence symptom on ANY path,\n // so guard on a known `previousCounter` ALONE, not `incremental`: the sparse-full\n // path is reachable with a previous counter but no refs (a cache-hit read whose\n // best-effort reference-chunk download failed seeds the lease that way), and a\n // regressed resume point must fail loud there too rather than ship silently.\n // Cheap: one pass over the 65536-entry counter, dwarfed by the ECDSA/upload work\n // below. (It catches the regression SYMPTOM; it cannot decode the retained refs'\n // own counters, so it is a tripwire, not a full proof.)\n if (previousCounter !== undefined) {\n for (let b = 0; b < NUM_BUCKETS; b++) {\n if (localCounter[b] < previousCounter[b]) {\n throw new Error(\n `[partition-state] publish p=${partition}: counter regressed at bucket ${b} ` +\n `(${localCounter[b]} < previous ${previousCounter[b]}) — monotonicity ` +\n \"violation; refusing to publish a state a takeover could resume past an acked slot.\",\n )\n }\n }\n }\n\n const allIndices = Array.from({ length: numUtilizationChunks }, (_, i) => i)\n // Which chunks to (re)upload:\n // - incremental: those whose serialized bytes changed since the last publish\n // (the chunk[s] covering the data write's bucket[s]; reserved-slot writes\n // don't bump the counter, so the data write is the only delta).\n // - full (first publish of a session): only the NON-ZERO chunks; zero chunks\n // get the all-zero sentinel ref (not uploaded), so the first publish is\n // sparse instead of writing all `numUtilizationChunks` (mostly zero).\n // Counters are monotonic high-water — a chunk never returns to zero — so a\n // real-ref chunk stays real and only never-written chunks are sentinels.\n const uploadIndices = incremental\n ? allIndices.filter(\n (i) =>\n !bytesEqual(\n extractChunk(localCounter, i, batchDepth),\n extractChunk(previousCounter, i, batchDepth),\n ),\n )\n : allIndices.filter(\n (i) => !bytesAllZero(extractChunk(localCounter, i, batchDepth)),\n )\n const uploadSet = new Set(uploadIndices)\n\n // Start from the retained refs (incremental) or all-zero sentinels (full).\n // Each sentinel is a fresh copy (not the shared `ZERO_CHUNK_REF` instance) so\n // the returned refs are mutually independent — an in-place byte write to one\n // zero slot can never bleed into another.\n const references: Uint8Array[] = incremental\n ? previousReferences.map((r) => r.slice())\n : allIndices.map(() => ZERO_CHUNK_REF.slice())\n\n // Every reserved chunk overstamps the partition's single reserved slot, so\n // they must land in distinct buckets, clear of the lock-SOC bucket, the live\n // utilisation chunks, and (incremental) the buckets of the REAL chunks we\n // retain (sentinel/zero chunks occupy no bucket).\n const claimedBuckets = new Set<number>([\n lockSocBucket(batchId, partition, owner),\n ...(reserve?.getCleanChunkBuckets() ?? []),\n ])\n if (incremental) {\n for (let i = 0; i < numUtilizationChunks; i++) {\n if (!uploadSet.has(i) && !bytesAllZero(references[i]))\n claimedBuckets.add(toBucket(references[i].slice(0, 32)))\n }\n }\n\n // Other reserved-slot writers for this partition also stamp slot = partition,\n // so a counter/reference chunk sharing one of their buckets would evict — or\n // be evicted by — them under Bee's newer-stamp-wins replacement. Exclude their\n // buckets (current + previous epoch, covering the rotation boundary):\n // - the state-pointer SOC this publish writes (phase 2 below)\n // (an intra-publish self-collision otherwise);\n // - the deviceId-independent occupancy beacon, and (when known) this device's\n // intent beacon, both re-written off-lock by the refresh tick concurrently.\n // A peer's intent beacon uses its own deviceId and can't be computed here; that\n // residual stays fail-safe (an evicted beacon self-heals next tick). The\n // converse — a beacon at a LATER epoch (beyond the two excluded here) evicting\n // a RETAINED (incremental) counter/reference chunk — is healed by the holder's\n // once-per-epoch FULL re-pin (`PartitionLease.flushState`), which re-uploads\n // every non-zero chunk clear of the current epoch's beacons; a long-retained\n // chunk is therefore exposed for ~1 epoch, not the whole session. The only\n // unhealed case is an eviction in the final epoch before the holder vanishes:\n // that stays fail-safe, not silent corruption — the reference chunk then fails\n // to download on the next takeover and `readPartitionState` reports\n // `readFailed` (→ read-only + retry), never a zero-counter resume that would\n // re-issue acked slots.\n // The lease's clock (`nowMs`), NOT a fresh `Date.now()`: the rotating pointer /\n // beacon addresses are a cross-device rendezvous, and a taking-over device\n // computes the same buckets from its OWN clock. In production the lease clock\n // IS `Date.now()`, so writer and reader still agree on wall-clock; threading it\n // (rather than reading `Date.now()` here) lets a test drive the rendezvous\n // deterministically and keeps this on the same clock as the caller's lease-\n // validity + collision-detection logic (#385).\n const now = nowMs\n for (const ptrMs of [now, now - STATE_POINTER_EPOCH_MS]) {\n claimedBuckets.add(\n toBucket(statePointerAddress(batchId, partition, owner, ptrMs)),\n )\n }\n const epoch = intentEpochBucket(now)\n for (const beaconBucket of [epoch, epoch - 1]) {\n claimedBuckets.add(\n toBucket(\n partitionOccupancyAddress(batchId, partition, beaconBucket, owner),\n ),\n )\n if (deviceId !== undefined) {\n claimedBuckets.add(\n toBucket(\n intentSocAddress(batchId, partition, deviceId, beaconBucket, owner),\n ),\n )\n }\n }\n\n // Precompute (CPU only) a distinct-bucket key/address for each chunk we will\n // upload, marking it reserved so its `stamp()` routes to the reserved slot.\n const prepared = uploadIndices.map((i) => {\n const plaintext = extractChunk(localCounter, i, batchDepth)\n const picked = pickReservedChunkKey(plaintext, claimedBuckets)\n references[i] = picked.reference\n reserve?.markReservedUtilizationChunk(picked.address, partition)\n return { data: picked.data }\n })\n\n // The reference chunk lists every ref (N·64 bytes; ≤ 4096 even at N=64).\n const referenceChunk = Binary.concatBytes(...references)\n const refPicked = pickReservedChunkKey(referenceChunk, claimedBuckets)\n reserve?.markReservedUtilizationChunk(refPicked.address, partition)\n\n const referenceHex = Binary.uint8ArrayToHex(refPicked.reference)\n\n // Invariant the parallel PUTs below rely on: every reserved-slot chunk this\n // publish uploads must occupy a DISTINCT bucket. They all land at\n // slot=partition, so a shared bucket evicts one under Bee's newer-stamp-wins\n // replacement. `claimedBuckets` + `pickReservedChunkKey` already guarantee\n // distinctness by construction; assert it here so a future change to that\n // bucket-selection can't silently reintroduce a collision. (This covers the\n // distinct-bucket half of the parallel-safety invariant; the synchronous\n // slot assignment half is `UtilizationAwareStamper.assignSlot`'s, see the\n // comment on the Promise.all.)\n const writtenBuckets = [\n ...uploadIndices.map((i) => toBucket(references[i].slice(0, 32))),\n toBucket(refPicked.address),\n ]\n if (new Set(writtenBuckets).size !== writtenBuckets.length) {\n throw new Error(\n `[partition-state] publish p=${partition}: reserved-slot chunks collided ` +\n \"on a bucket — parallel PUTs would race/evict. This is a bucket-\" +\n \"selection regression (pickReservedChunkKey / claimedBuckets).\",\n )\n }\n\n // TWO-PHASE PUBLISH (#414). Phase 1: the dirty counter chunks + the reference\n // chunk. Phase 2 (only after ALL of phase 1 acks): the state-pointer SOC that\n // NAMES the reference chunk. The pointer must never become durable before the\n // chunks it references — with `deferred: false` a chunk PUT can fail while the\n // pointer PUT succeeds; if the holder then crashes, every takeover finds the\n // fresh pointer, fails to read its missing chunk(s) → `readFailed` → read-only\n // with no older-pointer fallback (stuck until batch expiry). Ordering the\n // pointer after the chunk acks costs one extra round-trip and closes that gap:\n // a failed chunk PUT rejects phase 1 before any pointer is written.\n //\n // CONCURRENCY: the phase-1 PUTs share one `stamper`. They are safe to fire in\n // parallel ONLY because (a) every chunk here lands in a DISTINCT bucket\n // (`claimedBuckets` above guarantees it) and (b) the stamper's `assignSlot()`\n // is synchronous, so each slot assignment runs to completion on the event\n // loop before another starts. If a future change introduces an await inside\n // it, concurrent stamps would race on the counters — serialize the PUTs (or\n // guard the slot) before doing so.\n // Upload the EXACT encrypted bytes picked above — re-encrypting here would\n // randomize the padding (sub-4096 plaintexts, e.g. the reference chunk) and\n // land the chunk at a different address than the one already reserved and\n // written into `references` / the pointer.\n const publishStart = Date.now()\n await Promise.all([\n ...prepared.map((p) => uploadChunk(target, p.data, { deferred: false })),\n uploadChunk(target, refPicked.data, { deferred: false }),\n ])\n await writeStatePointer({\n bee,\n stamper,\n backupSigner,\n swarmEncryptionKey,\n batchId,\n partition,\n referenceHex,\n nowMs: now,\n })\n\n // Diagnostic (visible in the browser console): how many chunks this publish\n // actually wrote — counter chunks + the reference chunk + the pointer SOC —\n // and the batch latency. Shows whether a held-lease upload's publish is\n // sparse/incremental (~a few writes) or a full publish, and its gateway cost.\n console.debug(\n `[partition-state] publish p=${partition} incremental=${incremental} ` +\n `counterChunks=${prepared.length}/${numUtilizationChunks} ` +\n `writes=${prepared.length + 2} durationMs=${Date.now() - publishStart}`,\n )\n\n reserve?.clearReservedUtilizationChunks()\n\n // Only REAL (uploaded or retained) chunks occupy a reserved-slot bucket to\n // protect; sentinel (zero) chunks were never uploaded.\n const stateBuckets = references\n .filter((r) => !bytesAllZero(r))\n .map((r) => toBucket(r.slice(0, 32)))\n stateBuckets.push(toBucket(refPicked.address))\n\n return {\n referenceHex,\n stateBuckets,\n references,\n publishedCounter,\n }\n}\n\n/** True when every byte is zero (the zero-chunk / sentinel-ref test). */\nfunction bytesAllZero(b: Uint8Array): boolean {\n for (let i = 0; i < b.length; i++) {\n if (b[i] !== 0) return false\n }\n return true\n}\n\n/** Byte-wise equality for two equal-length chunk plaintexts. */\nfunction bytesEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false\n }\n return true\n}\n\n/**\n * Pick a random encryption key whose encrypted address lands in a bucket not\n * yet claimed (reserved chunks share one slot, so each must occupy a distinct\n * bucket), claim that bucket, and return the address + 64-byte reference +\n * encrypted chunk bytes. CPU only — the upload happens later, batched, and\n * must PUT these exact bytes: encryption padding is random, so re-encrypting\n * the plaintext would yield a different address. The bucket-distinctness retry\n * is why addresses are chosen in one synchronous pass before any PUT.\n */\nfunction pickReservedChunkKey(\n plaintext: Uint8Array,\n claimedBuckets: Set<number>,\n): { address: Uint8Array; reference: Uint8Array; data: Uint8Array } {\n let encrypted = makeEncryptedContentAddressedChunk(plaintext)\n while (claimedBuckets.has(toBucket(encrypted.address.toUint8Array()))) {\n encrypted = makeEncryptedContentAddressedChunk(plaintext)\n }\n const address = encrypted.address.toUint8Array()\n claimedBuckets.add(toBucket(address))\n return {\n address,\n reference: encrypted.reference.toUint8Array(),\n data: encrypted.data,\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Consolidated partition-lease state.\n *\n * This is the single in-code model of \"who holds which partition\" for one\n * account+batch. The per-partition lock SOC on Swarm\n * (`partition-lock.ts`) remains the cross-device authority; this class is\n * the one structure every code path consults, replacing the previous trio\n * of scattered representations (the `LeaseState` localStorage blob, the\n * `activeDevices` snapshot mirror, and ad-hoc `holderDeviceId` reads).\n *\n * It holds:\n * - `self` — this device's own lease (undefined when not holding)\n * - `holders` — observed holder per partition, derived from the lock\n * SOCs; this is what models the up-to-`partitionCount`\n * simultaneously-active devices\n * - admin — deviceId / batchId / partitionCount\n *\n * The proxy owns one instance, persists it as a *cache*\n * (`serialize`/`deserialize`), and re-validates against the lock SOC before\n * trusting `self`. Only the proxy mutates lock SOCs; readers (the SwarmID\n * UI) construct a read-only instance and call `refreshFromSwarm` +\n * `getHolders` to inspect the live holders.\n */\n\nimport {\n Bee,\n BatchId,\n PrivateKey,\n Reference,\n type Stamper,\n} from \"@ethersphere/bee-js\"\nimport { uint8ArrayToHex } from \"../utils/hex\"\nimport { withTimeout } from \"../utils/promise\"\nimport { SYNC_READ_TIMEOUT_MS } from \"./timing-constants\"\nimport { deriveSecret, BACKUP_KEY_LABEL } from \"../utils/key-derivation\"\nimport {\n LEASE_TTL_MS,\n NUM_BUCKETS,\n UtilizationAwareStamper,\n toBucket,\n} from \"../utils/batch-utilization\"\nimport { lockSocBucket } from \"../utils/lock-soc\"\nimport {\n acquirePartitionLock,\n compareGenerations,\n deviceHomePartition,\n makeDeviceTiebreaker,\n NO_HOLDER_DEVICE_ID,\n readPartitionLock,\n releasePartitionLock,\n type PartitionLockGeneration,\n type PartitionLockPayload,\n} from \"./partition-lock\"\nimport {\n INTENT_GUARD_POLL_MS,\n INTENT_GUARD_WINDOW_MS,\n INTENT_LIVENESS_GRACE_MS,\n intentEpochBucket,\n intentSocAddress,\n partitionOccupancyAddress,\n readPartitionIntent,\n readPartitionOccupancy,\n resolveIntentRound,\n writePartitionIntent,\n writePartitionOccupancy,\n type PartitionIntentPayload,\n} from \"./partition-intent\"\nimport {\n readPartitionState,\n readStatePointer,\n statePointerAddress,\n writePartitionState,\n writeStatePointer,\n type StatePointerLookup,\n} from \"./partition-state\"\n\n/** Default guard time δ for the lock protocol (Postage-Batch-Partitioning.md §7). */\nexport const PARTITION_LOCK_GUARD_MS = 2000\n\n/** True when a 64-byte reference is the all-zero sentinel (a never-written\n * counter chunk — see `ZERO_CHUNK_REF` in partition-state.ts). */\nfunction isZeroRef(ref: Uint8Array): boolean {\n for (let i = 0; i < ref.length; i++) if (ref[i] !== 0) return false\n return true\n}\n\n/**\n * Bound each lock-SOC read in `refreshFromSwarm` so a hanging read on a slow\n * gateway is classified as \"unreadable\" (a takeover then fails safe) rather than\n * silently treated as an absent lock. Matches the roster / state-pointer read\n * timeouts.\n */\nconst LOCK_READ_TIMEOUT_MS = SYNC_READ_TIMEOUT_MS\n\n/** This device's own lease over a partition. */\nexport interface SelfLease {\n partition: number\n generation: PartitionLockGeneration\n acquiredAt: number\n leasedUntil: number\n}\n\n/** An observed holder of a partition (from the lock SOC). */\nexport interface PartitionHolderEntry {\n deviceId: string\n generation: PartitionLockGeneration\n acquiredAt: number\n leasedUntil: number\n}\n\n/** Inputs the orchestrator needs to dispatch a case. */\nexport interface PartitionLeaseSnapshotInputs {\n partitionCount: number\n}\n\n/** Result of `acquire()` (or the legacy fall-through). */\nexport interface AcquireResult {\n /**\n * The partition this device holds, or `undefined` for legacy mode and\n * the read-only path (every partition is held by a live foreign holder).\n */\n partition: number | undefined\n partitionCount: number\n localCounter: Uint32Array\n isReadOnly: boolean\n /** Latest lock-SOC payload observed after acquire. Undefined when read-only. */\n lockPayload?: PartitionLockPayload\n}\n\n/**\n * Outcome of a `refresh()` tick. Distinguishes the two ways a refresh can fail\n * to keep the lease, because the coordinator must react to them differently:\n *\n * - `\"held\"` — the lease is still ours and was extended.\n * - `\"lost\"` — could not confirm via write+verify (no lease, or the lock\n * protocol returned `blocked`/`lost-race`/`aborted`). The\n * caller should re-check the static lock SOC before demoting\n * (a transient read-your-writes hiccup must not demote — the\n * Phase-1 optimism).\n * - `\"displaced\"` — a foreign PRESENCE BEACON with an earlier generation proved\n * a peer already holds this partition. The beacon is read at a\n * fresh per-epoch address — the channel that stays readable on\n * a disjoint gateway where the static lock SOC is frozen — so\n * this is a CONFIRMED displacement. The caller MUST demote\n * directly and must NOT re-gate on the static lock SOC: by the\n * time `refresh()` returns, it has already rewritten our own\n * claim to that address, so a re-read there would see SELF and\n * never confirm (the no-op the deconfliction backstop existed\n * to avoid).\n */\nexport type LeaseRefreshOutcome = \"held\" | \"lost\" | \"displaced\"\n\n/**\n * Serialised form of the lease state, used as the local persistence cache.\n * Never trusted on its own — callers re-validate `self` against the lock\n * SOC before acting on it.\n */\nexport interface PartitionLeaseStateSnapshot {\n deviceId: string\n batchId: string\n self?: SelfLease\n}\n\nexport class PartitionLease {\n private self: SelfLease | undefined\n /** Per-partition holders observed by the last `refreshFromSwarm()`. */\n private holders = new Map<number, PartitionHolderEntry>()\n /**\n * Partitions whose lock SOC carried an explicit NO_HOLDER release sentinel in\n * the last `refreshFromSwarm()`, mapped to the RELEASED claim's generation —\n * the release WATERMARK. An authoritative \"free\" — distinct from a\n * stale/expired lock — so the holder-detection paths\n * (`refreshHoldersFromPresence` / `refreshHoldersFromOccupancy`) must NOT let\n * a lingering beacon (the just-released holder's, still within TTL) re-mark it\n * held and block an immediate takeover. The generation watermark additionally\n * lets the intent round and the `refresh()` displacement checks\n * (`beaconOutranksRelease`) tell that holder's stale GHOST beacon (gen ≤\n * watermark) from a genuine post-release claimant (gen > watermark).\n */\n private releasedPartitions = new Map<number, PartitionLockGeneration>()\n /**\n * Last `leasedUntil` observed on a partition's lock SOC that is NOT a live\n * holder — an EXPIRED holder, or a release sentinel (whose `leasedUntil` is the\n * release time). The gone holder heartbeats its rotating state-pointer SOC up to\n * its `leasedUntil`, so this bounds the pointer's last bucket and lets a DELAYED\n * takeover compute the right address (the pointer ages out of the `now` buckets\n * otherwise — `holders` only carries LIVE holders' `leasedUntil`). Keyed by\n * partition; rebuilt each `refreshFromSwarm`.\n */\n private lastSeenLeasedUntil = new Map<number, number>()\n /**\n * Partitions whose lock SOC could not be read conclusively this refresh — the\n * read timed out / hung (a transient failure), NOT a clean \"absent\" 404. For\n * such a partition, `holderLeasedUntilMs` is unknown, so a takeover that finds\n * no pointer must fail safe (read-only) instead of resuming from zero and\n * re-issuing a gone holder's acked slots. Rebuilt each `refreshFromSwarm`.\n * A clean 404 (genuinely free partition) is NOT recorded here, so first-claim\n * from zero still works.\n */\n private unreadableLocks = new Set<number>()\n /**\n * Cached state from this session's last partition-state publish, so the next\n * publish re-uploads only the chunks that changed (incremental). Seeded on the\n * first publish (full), refreshed on every publish, and cleared by `acquire()`\n * (a new session resumes from a peer's counter, not our stale one).\n */\n private publishedReferences: Uint8Array[] | undefined\n private publishedCounter: Uint32Array | undefined\n /** Reference-chunk ref (hex) of this session's last publish — re-written to the\n * current rotating bucket on each refresh tick (heartbeat) so an idle holder\n * keeps a fresh state pointer the next reader can find. */\n private lastReferenceHex: string | undefined\n /**\n * Epoch bucket of the last FULL (non-incremental) publish. An incremental\n * publish leaves unchanged counter chunks in place across epochs, but the\n * rotating pointer/occupancy/intent SOCs share this partition's reserved slot\n * and a LATER epoch's (deterministic) bucket can collide with a long-retained\n * chunk and evict it — only the current+previous epoch's beacon buckets are\n * excluded at publish time (see `writePartitionState`). So we force one FULL\n * re-pin per epoch: every non-zero chunk is re-uploaded at a fresh bucket clear\n * of the current epoch's beacons, so any such eviction self-heals within ~1\n * epoch while the holder lives (only a collision in the final pre-departure\n * epoch is left for the takeover `readFailed` path). undefined after `acquire`.\n */\n private lastFullPublishEpoch: number | undefined\n /**\n * Set at the start of `release()` (the lease session is closing) and\n * cleared by the next `acquire()`. While set, an overlapping `refresh()`\n * aborts before its claim write — otherwise it could mint a ghost claim\n * NEWER than the generation the in-flight release is fenced to, which the\n * release would then refuse to clear (peers would wait out the TTL).\n */\n private closed = false\n /**\n * The claim-time presence/occupancy beacon publish, fired OFF the acquire\n * critical path (best-effort, never rejects — `publishPresenceBeacon`\n * swallows errors). `refresh()`/`release()` await it first so a later beacon\n * (fresher `leasedUntil`) can never be overwritten by the still-in-flight\n * claim beacon, and tests get a deterministic settle point.\n */\n private pendingBeaconPublish: Promise<void> = Promise.resolve()\n\n constructor(\n private readonly opts: {\n bee: Bee\n deviceId: string\n swarmEncryptionKey: Uint8Array\n /** Backup signer for the partition-lock SOC and partition-state feed. */\n backupSigner: PrivateKey\n /**\n * Which batch's lanes this lease is about. Required even for a reader:\n * every lock, intent and occupancy address is derived per (batch,\n * partition) since #589, so \"who holds partition p\" has no answer\n * without naming the batch.\n */\n batchId: BatchId\n /**\n * Stamper + depth are only needed for the WRITE paths (acquire,\n * refresh, release). They're optional so a reader — e.g. the SwarmID\n * UI's Devices tab — can build a lease purely to `refreshFromSwarm`\n * and inspect holders without a stamper.\n */\n batchDepth?: number\n /**\n * Stamper used to upload the small payload chunks (lock SOC, state\n * feed). In practice this is the `UtilizationAwareStamper` the proxy\n * uses for data uploads. Omitted for read-only use.\n */\n stamper?: Stamper\n /** Override for tests; defaults to `() => Date.now()`. */\n now?: () => number\n /** Override for tests; defaults to PARTITION_LOCK_GUARD_MS. */\n guardMs?: number\n /**\n * Override for the lock protocol's guard-window wait; defaults to\n * `sleep`. A test asserts the guard through this seam — on the `ms` it\n * is handed — rather than timing `acquire` against a real clock, where\n * the claim's own crypto/IO cost is indistinguishable from the guard.\n */\n wait?: (ms: number) => Promise<void>\n /**\n * Current known device IDs for this account (from the synced device\n * registry). Read fresh each round so it reflects late-announced\n * devices. When provided with ≥1 rival, a fresh claim of a free\n * partition first runs an intent round (Phase 2 — see\n * `partition-intent.ts`) so disjoint-gateway contenders agree on one\n * winner before binding. Omitted (or empty) → today's guard+TTL path.\n */\n knownDeviceIds?: () => string[]\n /** Override for tests; per-rival intent-read timeout. */\n intentReadTimeoutMs?: number\n /**\n * Fresh-claim intent-round poll window / interval (gateway-propagation\n * tunable). Default to the `INTENT_GUARD_*` constants; tests pass 0 for a\n * single immediate read.\n */\n intentGuardWindowMs?: number\n intentGuardPollMs?: number\n },\n ) {}\n\n /**\n * Build a `PartitionLease` whose `backupSigner` is derived from the\n * account's `swarmEncryptionKey` (same derivation as the partition-state\n * and partition-lock feeds). Pass `stamper`/`batchId`/`batchDepth` for the\n * write path; omit them for a read-only lease (UI display).\n */\n static async fromSwarmEncryptionKey(opts: {\n bee: Bee\n deviceId: string\n swarmEncryptionKey: Uint8Array\n batchId: BatchId\n batchDepth?: number\n stamper?: Stamper\n now?: () => number\n guardMs?: number\n wait?: (ms: number) => Promise<void>\n knownDeviceIds?: () => string[]\n intentReadTimeoutMs?: number\n intentGuardWindowMs?: number\n intentGuardPollMs?: number\n }): Promise<PartitionLease> {\n const swarmEncryptionKeyHex = uint8ArrayToHex(opts.swarmEncryptionKey)\n const backupKeyHex = await deriveSecret(\n swarmEncryptionKeyHex,\n BACKUP_KEY_LABEL,\n )\n const backupSigner = new PrivateKey(backupKeyHex)\n return new PartitionLease({\n ...opts,\n backupSigner,\n // Read through rather than copied by the spread: the coordinator passes\n // a getter that follows a change of node for the whole session.\n get bee() {\n return opts.bee\n },\n })\n }\n\n /**\n * Read every partition's lock SOC and populate `holders` with the live\n * (unexpired, non-released) ones. The single source of truth for \"who is\n * active\" — used by `isActive`, `heldPartition`, and `pickFreeOrExpired`.\n */\n async refreshFromSwarm(partitionCount: number): Promise<void> {\n const now = this.now()\n this.holders.clear()\n this.releasedPartitions.clear()\n this.lastSeenLeasedUntil.clear()\n this.unreadableLocks.clear()\n // Read every partition's lock SOC in parallel — each is an independent\n // multi-round-trip retrieval, so overlapping them collapses N×latency into\n // ≈1×. Classification below is order-independent (holders/releasedPartitions\n // are partition-keyed). Each read is bounded by a timeout we generate, so a\n // HANGING read (slow gateway) is an unambiguous \"unreadable\" signal — recorded\n // so a takeover of this partition fails safe rather than zero-seeding. A fast\n // `undefined` (clean 404) is genuinely absent and stays claimable.\n const locks = await Promise.all(\n Array.from({ length: partitionCount }, (_unused, p) =>\n withTimeout(\n readPartitionLock({\n bee: this.opts.bee,\n backupSigner: this.opts.backupSigner,\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n batchId: this.opts.batchId,\n partition: p,\n }),\n LOCK_READ_TIMEOUT_MS,\n \"lock read timed out\",\n ).catch(() => {\n this.unreadableLocks.add(p)\n return undefined\n }),\n ),\n )\n for (let p = 0; p < partitionCount; p++) {\n const lock = locks[p]\n // Per-partition outcome — this is the line that reveals whether a joining\n // device can actually SEE the current holder's lock SOC (the suspected\n // cross-device dual-acquire cause). \"no readable lock\" here for a\n // partition a peer holds = the lock SOC is invisible to us.\n if (!lock) {\n console.debug(`[PartitionLease] refresh p=${p}: no readable lock`)\n continue\n }\n if (lock.holderDeviceId === NO_HOLDER_DEVICE_ID) {\n console.debug(`[PartitionLease] refresh p=${p}: released sentinel`)\n this.releasedPartitions.set(p, lock.generation)\n // The sentinel's `leasedUntil` is the release time; the releaser's final\n // state-pointer write sits in that bucket, so a delayed takeover can find it.\n this.lastSeenLeasedUntil.set(p, lock.leasedUntil)\n continue\n }\n const self = lock.holderDeviceId === this.opts.deviceId ? \" (self)\" : \"\"\n if (lock.leasedUntil <= now) {\n console.debug(\n `[PartitionLease] refresh p=${p}: EXPIRED holder ${lock.holderDeviceId}${self} (until ${lock.leasedUntil} <= now ${now})`,\n )\n // The expired holder heartbeated its state pointer up to `leasedUntil`;\n // retain it so a takeover computes the pointer bucket even long after.\n this.lastSeenLeasedUntil.set(p, lock.leasedUntil)\n continue\n }\n console.debug(\n `[PartitionLease] refresh p=${p}: held by ${lock.holderDeviceId}${self} until ${lock.leasedUntil}`,\n )\n this.holders.set(p, {\n deviceId: lock.holderDeviceId,\n generation: lock.generation,\n acquiredAt: lock.acquiredAt,\n leasedUntil: lock.leasedUntil,\n })\n }\n const summary = Array.from(this.holders.entries())\n .map(([p, h]) => `${p}->${h.deviceId}`)\n .join(\",\")\n console.debug(\n `[PartitionLease] refresh: live holders={${summary}} self=${this.opts.deviceId}`,\n )\n }\n\n /**\n * Merge live holders detected from per-device PRESENCE BEACONS (the intent\n * SOCs holders re-publish each refresh) into `holders`. Unlike the static\n * lock SOC, these live at fresh per-epoch addresses, so reading them forces a\n * network retrieval that works on a gateway that 404s the static address —\n * this is what lets a joiner detect an existing holder. Call AFTER\n * `refreshFromSwarm` (union, not replace): either channel showing a live\n * foreign holder marks the partition held. No-op without known rivals.\n */\n async refreshHoldersFromPresence(partitionCount: number): Promise<void> {\n this.applyPresence(await this.readPresence(partitionCount))\n }\n\n /** Network half of `refreshHoldersFromPresence` — no state mutation, so it\n * can be overlapped with `refreshFromSwarm` (whose `releasedPartitions`\n * classification `applyPresence` consumes). */\n private async readPresence(partitionCount: number): Promise<\n {\n partition: number\n deviceId: string\n bucket: number\n intent: PartitionIntentPayload | undefined\n }[]\n > {\n const now = this.now()\n const rivals = (this.opts.knownDeviceIds?.() ?? []).filter(\n (id) => id !== this.opts.deviceId,\n )\n if (rivals.length === 0) return []\n\n const currentBucket = intentEpochBucket(now)\n const buckets = [currentBucket, currentBucket - 1]\n\n return Promise.all(\n Array.from({ length: partitionCount }).flatMap((_unused, p) =>\n rivals.flatMap((deviceId) =>\n buckets.map(async (bucket) => ({\n partition: p,\n deviceId,\n bucket,\n intent: await readPartitionIntent({\n bee: this.opts.bee,\n backupSigner: this.opts.backupSigner,\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n batchId: this.opts.batchId,\n partition: p,\n deviceId,\n epochBucket: bucket,\n timeoutMs: this.opts.intentReadTimeoutMs,\n }),\n })),\n ),\n ),\n )\n }\n\n /** Classification half of `refreshHoldersFromPresence`. Call AFTER\n * `refreshFromSwarm` has populated `releasedPartitions`/`holders`. */\n private applyPresence(\n results: Awaited<ReturnType<PartitionLease[\"readPresence\"]>>,\n ): void {\n const now = this.now()\n for (const { partition, deviceId, bucket, intent } of results) {\n if (!intent) continue\n // An explicit release sentinel on the lock authoritatively freed this\n // partition; ignore the just-released holder's lingering PRESENCE beacon\n // (still within TTL/grace) so a peer can take over immediately — mirrors\n // `refreshHoldersFromOccupancy`. Without this, the per-device presence\n // channel re-marks a released partition held for up to a full lease TTL,\n // defeating the fast-takeover the release sentinel exists to enable.\n if (this.releasedPartitions.has(partition)) continue\n // A holder beacon carries `leasedUntil`; a pre-claim intent (the\n // symmetric-race announce) does NOT. Only a beacon means \"this partition\n // is HELD\" — a bare intent is a contender, left for the intent round to\n // resolve, so it must NOT mark the partition held here. The grace covers a\n // live holder whose freshest beacon hasn't propagated (see\n // INTENT_LIVENESS_GRACE_MS).\n if (\n intent.leasedUntil === undefined ||\n intent.leasedUntil <= now - INTENT_LIVENESS_GRACE_MS\n )\n continue\n const leasedUntil = intent.leasedUntil\n\n const existing = this.holders.get(partition)\n // Keep a self-claim (from the lock SOC) — we hold it; don't let a foreign\n // beacon shadow it. Otherwise record/refresh the foreign holder.\n if (existing && existing.deviceId === this.opts.deviceId) continue\n if (!existing || leasedUntil > existing.leasedUntil) {\n this.holders.set(partition, {\n deviceId,\n generation: intent.generation,\n acquiredAt: existing?.acquiredAt ?? 0,\n leasedUntil,\n })\n console.debug(\n `[PartitionLease] presence p=${partition}: live holder ${deviceId} (bucket ${bucket}, leasedUntil ${leasedUntil})`,\n )\n }\n }\n }\n\n /**\n * Union in holders from the deviceId-INDEPENDENT occupancy beacons. For each\n * partition, read the current + previous epoch bucket (covering the rotation\n * boundary); a foreign beacon still in lease marks the partition held — even\n * when we don't know the holder's deviceId (the registry-lag case the per-device\n * channels miss). No-op once a self-claim is recorded for the partition.\n *\n * Liveness uses a STRICT `leasedUntil > now` (not the propagation GRACE the\n * per-device beacon uses): an actively-refreshing holder always has a current\n * beacon with `leasedUntil > now`, while a DEPARTED holder's last beacon has\n * genuinely lapsed — so a device legitimately taking over an expired slot\n * isn't falsely blocked by a lingering within-grace beacon (which the lock\n * SOC, when accurate, would already show as expired).\n */\n async refreshHoldersFromOccupancy(partitionCount: number): Promise<void> {\n this.applyOccupancy(await this.readOccupancy(partitionCount))\n }\n\n /** Network half of `refreshHoldersFromOccupancy` — see `readPresence`. */\n private async readOccupancy(\n partitionCount: number,\n ): Promise<\n { partition: number; occupancy: PartitionIntentPayload | undefined }[]\n > {\n const now = this.now()\n const currentBucket = intentEpochBucket(now)\n const buckets = [currentBucket, currentBucket - 1]\n\n return Promise.all(\n Array.from({ length: partitionCount }).flatMap((_unused, p) =>\n buckets.map(async (bucket) => ({\n partition: p,\n occupancy: await readPartitionOccupancy({\n bee: this.opts.bee,\n backupSigner: this.opts.backupSigner,\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n batchId: this.opts.batchId,\n partition: p,\n epochBucket: bucket,\n timeoutMs: this.opts.intentReadTimeoutMs,\n }),\n })),\n ),\n )\n }\n\n /** Classification half of `refreshHoldersFromOccupancy` — see `applyPresence`. */\n private applyOccupancy(\n results: Awaited<ReturnType<PartitionLease[\"readOccupancy\"]>>,\n ): void {\n const now = this.now()\n for (const { partition, occupancy } of results) {\n if (!occupancy || occupancy.leasedUntil === undefined) continue\n if (occupancy.leasedUntil <= now) continue // genuinely expired holder\n if (occupancy.deviceId === this.opts.deviceId) continue // our own beacon\n // An explicit release sentinel on the lock authoritatively freed this\n // partition; ignore the just-released holder's lingering occupancy beacon\n // so a peer can take over immediately (the lock release is the truth here,\n // unlike a stale/expired lock which the occupancy beacon is meant to\n // override).\n if (this.releasedPartitions.has(partition)) continue\n const existing = this.holders.get(partition)\n // Don't let a foreign beacon shadow our own live self-claim.\n if (existing && existing.deviceId === this.opts.deviceId) continue\n if (!existing || occupancy.leasedUntil > existing.leasedUntil) {\n this.holders.set(partition, {\n deviceId: occupancy.deviceId,\n generation: occupancy.generation,\n acquiredAt: existing?.acquiredAt ?? 0,\n leasedUntil: occupancy.leasedUntil,\n })\n console.debug(\n `[PartitionLease] occupancy p=${partition}: live holder ${occupancy.deviceId} (leasedUntil ${occupancy.leasedUntil})`,\n )\n }\n }\n }\n\n /** True if `deviceId` is a live holder of any partition (post-refresh). */\n isActive(deviceId: string): boolean {\n for (const holder of this.holders.values()) {\n if (holder.deviceId === deviceId) return true\n }\n return false\n }\n\n /** The partition this device currently holds per the lock SOCs, else undefined. */\n heldPartition(): number | undefined {\n for (const [partition, holder] of this.holders) {\n if (holder.deviceId === this.opts.deviceId) return partition\n }\n return undefined\n }\n\n /**\n * Live holders observed by the last `refreshFromSwarm`, one entry per\n * partition currently held. The single read view of \"who's active\" — the\n * SwarmID UI renders the Devices tab from this.\n */\n getHolders(): { partition: number; deviceId: string; leasedUntil: number }[] {\n return Array.from(this.holders.entries()).map(([partition, holder]) => ({\n partition,\n deviceId: holder.deviceId,\n leasedUntil: holder.leasedUntil,\n }))\n }\n\n /**\n * First partition with no live holder that isn't us, else `undefined`,\n * scanning from this device's deterministic home offset\n * (`deviceHomePartition`) and wrapping. Starting from a per-device offset\n * spreads devices across slots even when none can read peers' locks (the\n * disjoint-gateway frozen-cache case); the wrap keeps full coverage so a\n * genuinely free slot is never missed. Drives candidate selection in\n * `acquire`.\n */\n private pickFreeOrExpired(partitionCount: number): number | undefined {\n const start = deviceHomePartition(this.opts.deviceId, partitionCount)\n for (let i = 0; i < partitionCount; i++) {\n const p = (start + i) % partitionCount\n const holder = this.holders.get(p)\n if (!holder || holder.deviceId === this.opts.deviceId) return p\n }\n return undefined\n }\n\n /**\n * Acquire a partition. Always consults the lock SOCs (via\n * `refreshFromSwarm`) — there is no local-state shortcut. Refreshes our\n * existing partition when we already hold one; otherwise claims the first\n * free / expired one. Returns read-only when every partition is held by a\n * live foreign holder.\n */\n async acquire(\n snapshot: PartitionLeaseSnapshotInputs,\n ): Promise<AcquireResult> {\n const { partitionCount } = snapshot\n\n // A new acquisition opens a new lease session. Drop any cached publish\n // state from a previous session — this session resumes from the partition's\n // published counter, not our stale one (a peer may have held it meanwhile).\n this.closed = false\n this.publishedReferences = undefined\n this.publishedCounter = undefined\n this.lastReferenceHex = undefined\n this.lastFullPublishEpoch = undefined\n\n if (partitionCount <= 1) {\n return {\n partition: undefined,\n partitionCount: 1,\n localCounter: new Uint32Array(NUM_BUCKETS),\n isReadOnly: false,\n }\n }\n\n // All FOUR read channels are independent network reads — only their\n // interpretation is ordered — so fire them concurrently and the cold\n // acquire pays ~one read-timeout instead of stacking three or four:\n // - the lock-SOC scan (`refreshFromSwarm`, reads + classifies itself);\n // - the per-device presence beacons (fresh per-epoch reads that work on a\n // gateway where the static lock SOC is unreadable — what lets us detect\n // a peer that ALREADY holds a partition);\n // - the deviceId-INDEPENDENT occupancy beacons (detect a live holder we\n // have NEVER heard of — the registry-lag dual-acquire where a\n // re-claiming device hasn't synced a peer yet and is blind to it on\n // every keyed channel);\n // - a SPECULATIVE state-pointer read for this device's home partition —\n // the pointer lookup `claimPartition` would run serially afterwards.\n // The presence/occupancy classification (`applyPresence`/`applyOccupancy`)\n // runs strictly after the barrier because it consumes the released-sentinel\n // watermarks `refreshFromSwarm` populates.\n const nowMs = this.now()\n const home = deviceHomePartition(this.opts.deviceId, partitionCount)\n const [, presenceReads, occupancyReads, speculativePointer] =\n await Promise.all([\n this.refreshFromSwarm(partitionCount),\n this.readPresence(partitionCount),\n this.readOccupancy(partitionCount),\n this.speculativeStatePointer(home, nowMs),\n ])\n this.applyPresence(presenceReads)\n this.applyOccupancy(occupancyReads)\n\n // Prefer the partition we already hold; otherwise the first free/expired.\n // A re-acquire of our own partition is not a contended claim — skip the\n // intent round (which only guards taking a free/expired slot).\n const held = this.heldPartition()\n const chosenPartition = held ?? this.pickFreeOrExpired(partitionCount)\n\n // SOLO-CLEAN: no rival known and no trace of any other device on any\n // channel — no live/expired holder or release sentinel on any lock, every\n // lock read conclusive, no foreign beacon. The claim's guard window exists\n // to arbitrate two contenders racing the same lock write; with zero\n // evidence of a second device it only taxes the single-device cold path,\n // and the residual unknown-peer dual-acquire (a peer whose every channel\n // was invisible RIGHT NOW) is already backstopped by the occupancy-beacon\n // displacement check on the ≤10s refresh tick.\n const soloClean =\n !(this.opts.knownDeviceIds?.() ?? []).some(\n (id) => id !== this.opts.deviceId,\n ) &&\n Array.from(this.holders.values()).every(\n (h) => h.deviceId === this.opts.deviceId,\n ) &&\n this.releasedPartitions.size === 0 &&\n this.lastSeenLeasedUntil.size === 0 &&\n this.unreadableLocks.size === 0\n // Why this partition was chosen — `home` is our deterministic scan start,\n // `liveHolders` the count refreshFromSwarm saw. If we pick a partition a\n // peer actually holds, that means we couldn't read its lock SOC above.\n console.debug(\n `[PartitionLease] acquire: chosen=${chosenPartition} via=${held !== undefined ? \"held(re-acquire)\" : \"pickFreeOrExpired\"} home=${deviceHomePartition(this.opts.deviceId, partitionCount)} liveHolders=${this.holders.size} partitionCount=${partitionCount}`,\n )\n\n if (chosenPartition === undefined) {\n // Every partition is live + foreign-held.\n return {\n partition: undefined,\n partitionCount,\n localCounter: new Uint32Array(NUM_BUCKETS),\n isReadOnly: true,\n }\n }\n\n return this.claimPartition({\n partition: chosenPartition,\n partitionCount,\n freshClaim: held === undefined,\n nowMs,\n // Only valid for the partition it was read for.\n speculativePointer:\n chosenPartition === home ? speculativePointer : undefined,\n soloClean,\n })\n }\n\n /**\n * Speculative half of `claimPartition`'s pointer lookup, overlapped with the\n * `acquire` scan: read the state pointer for the partition we will most\n * likely claim (the home partition) at the `now`/`now−1` buckets — exactly\n * the lookup `readPartitionState` performs when the partition has no known\n * prior holder (`holderLeasedUntilMs` undefined; `readPartitionState`\n * enforces that before trusting the prefetch). Best-effort: `undefined` on a\n * thrown read → the claim falls back to its own serial lookup.\n */\n private async speculativeStatePointer(\n partition: number,\n nowMs: number,\n ): Promise<StatePointerLookup | undefined> {\n const { batchId } = this.opts\n try {\n return await readStatePointer({\n bee: this.opts.bee,\n owner: this.opts.backupSigner.publicKey().address(),\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n batchId,\n partition,\n nowMs,\n })\n } catch {\n return undefined\n }\n }\n\n /**\n * Read partition-state for counter seeding, then drive\n * `acquirePartitionLock` to claim + verify the partition.\n *\n * On a `lost-race` or `blocked` outcome we fall back to read-only.\n */\n private async claimPartition(args: {\n partition: number\n partitionCount: number\n /** A first-time claim of a free/expired slot (vs. a re-acquire of ours). */\n freshClaim: boolean\n /** Clock capture from `acquire` — one rendezvous instant for the whole\n * scan + claim (also what makes `speculativePointer` exactly equivalent\n * to the serial lookup). */\n nowMs: number\n /** Overlapped pointer lookup for THIS partition (see\n * `speculativeStatePointer`); undefined → serial lookup. */\n speculativePointer?: StatePointerLookup\n /** No rival known + no trace of another device on any channel — skip the\n * contention guard window (see the note in `acquire`). */\n soloClean: boolean\n }): Promise<AcquireResult> {\n const { partition, partitionCount, freshClaim } = args\n const { stamper, batchId, batchDepth } = this.requireWriteContext()\n\n // Cache-aware seed: pass the reference our local counter is already in\n // sync with. If the feed still points there, `readPartitionState` skips\n // the reference + counter-chunk downloads and we reuse the local counter.\n const knownReference =\n stamper instanceof UtilizationAwareStamper\n ? await stamper.getSyncedReference(partition)\n : undefined\n const stateResult = await readPartitionState(\n {\n bee: this.opts.bee,\n owner: this.opts.backupSigner.publicKey().address(),\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n batchId,\n partition,\n batchDepth,\n // Locate the pointer of a partition that's been free for a while at the\n // gone holder's last-heartbeat bucket (its `leasedUntil`), so a takeover\n // resumes at the right counter however long ago the holder stopped. A live\n // self/foreign holder's `leasedUntil` comes from `holders`; an EXPIRED or\n // RELEASED holder's (the actual takeover case — `pickFreeOrExpired` only\n // picks partitions with no live holder) comes from `lastSeenLeasedUntil`,\n // which `refreshFromSwarm` retains from the lock SOC.\n holderLeasedUntilMs:\n this.holders.get(partition)?.leasedUntil ??\n this.lastSeenLeasedUntil.get(partition),\n // If this partition's lock read was inconclusive (timed out), a\n // \"no pointer found\" result must fail safe rather than zero-seed.\n lockUnreadable: this.unreadableLocks.has(partition),\n // Reader half of the rendezvous — this device's clock (#385), captured\n // once in `acquire` so the speculative pointer lookup and this read\n // computed identical buckets.\n nowMs: args.nowMs,\n // The overlapped pointer lookup (ignored by `readPartitionState`\n // whenever a holder span applies).\n prefetchedPointer: args.speculativePointer,\n },\n knownReference,\n )\n\n // The partition has a published resume point we failed to read. Claiming\n // anyway would mean stamping from an unknown (effectively zero) counter —\n // re-issuing used slots and evicting the partition's existing data. Fall\n // back to read-only WITHOUT writing the lock SOC; the coordinator's\n // slot-wait / next-upload acquire retries shortly.\n if (stateResult.readFailed) {\n console.debug(\n `[PartitionLease] Partition ${partition} state read failed; falling back to read-only without claiming.`,\n )\n return {\n partition: undefined,\n partitionCount,\n localCounter: new Uint32Array(NUM_BUCKETS),\n isReadOnly: true,\n }\n }\n\n const localCounter =\n stateResult.unchanged && stamper instanceof UtilizationAwareStamper\n ? stamper.buildLeaseLocalCounter()\n : (stateResult.localCounter ?? new Uint32Array(NUM_BUCKETS))\n\n // The generation we will both advertise (intent round) and claim with, so\n // the round's winner binds the lock with the exact generation it announced.\n const generation: PartitionLockGeneration = {\n timestampMs: this.now(),\n tiebreaker: makeDeviceTiebreaker(this.opts.deviceId),\n }\n\n // Phase 2: for a fresh claim of a free/expired slot with known rivals, run\n // an intent round at a fresh per-epoch address (forces a network read that\n // bypasses the gateway's frozen lock-SOC cache). Only the round's winner\n // proceeds to bind; losers back off to read-only. See partition-intent.ts.\n const knownDeviceIds = this.opts.knownDeviceIds?.() ?? []\n const hasRival = knownDeviceIds.some((id) => id !== this.opts.deviceId)\n // Diagnostic: shows whether the intent round even runs, and on how many\n // peers. The dual-acquire failure is \"freshClaim + no rival seen at acquire\n // time\" (registry not yet synced) or \"round ran but found nothing\".\n console.debug(\n `[PartitionLease] claim p=${partition} self=${this.opts.deviceId} freshClaim=${freshClaim} knownDevices=${knownDeviceIds.length} intentRound=${freshClaim && hasRival}`,\n )\n if (freshClaim && hasRival) {\n const outcome = await resolveIntentRound({\n bee: this.opts.bee,\n stamper,\n backupSigner: this.opts.backupSigner,\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n batchId: this.opts.batchId,\n partition,\n deviceId: this.opts.deviceId,\n generation,\n knownDeviceIds,\n now: this.now(),\n timeoutMs: this.opts.intentReadTimeoutMs,\n guardWindowMs: this.opts.intentGuardWindowMs ?? INTENT_GUARD_WINDOW_MS,\n guardPollMs: this.opts.intentGuardPollMs ?? INTENT_GUARD_POLL_MS,\n // If the chosen partition was just released, its prior holder's lingering\n // presence beacon must not lose us the round — only a claim NEWER than\n // the release watermark counts as a live holder.\n releasedGeneration: this.releasedPartitions.get(partition),\n })\n if (outcome === \"lose\") {\n console.debug(\n `[PartitionLease] Lost intent round for partition ${partition}; falling back to read-only.`,\n )\n return {\n partition: undefined,\n partitionCount,\n localCounter: new Uint32Array(NUM_BUCKETS),\n isReadOnly: true,\n }\n }\n }\n\n const lockResult = await acquirePartitionLock({\n bee: this.opts.bee,\n stamper,\n backupSigner: this.opts.backupSigner,\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n batchId: this.opts.batchId,\n partition,\n deviceId: this.opts.deviceId,\n ttlMs: LEASE_TTL_MS,\n // Solo-clean: no rival on any channel — the guard window would only\n // arbitrate a contender that demonstrably isn't there. The verify read\n // still runs; the refresh-tick displacement backstop covers the residual.\n guardMs: args.soloClean\n ? 0\n : (this.opts.guardMs ?? PARTITION_LOCK_GUARD_MS),\n wait: this.opts.wait,\n generation,\n now: () => this.now(),\n // `acquire`'s scan read this lock (timeout-bounded) moments ago and\n // classified the partition claimable — don't pay the duplicate pre-write\n // read (an absent chunk can block for tens of seconds on a gateway).\n skipInitialRead: true,\n })\n\n if (lockResult.outcome !== \"acquired\" || !lockResult.payload) {\n console.debug(\n `[PartitionLease] Lock acquire for partition ${partition} outcome=${lockResult.outcome}; falling back to read-only.`,\n )\n return {\n partition: undefined,\n partitionCount,\n localCounter: new Uint32Array(NUM_BUCKETS),\n isReadOnly: true,\n }\n }\n\n // Lock acquired → the caller will bind this counter, so record the feed\n // reference we just downloaded as our \"synced reference\" (lets the next\n // acquire skip the download). Only on a real read — never on `unchanged`\n // (already cached) or a failed read (no `referenceHex`). Also protect the\n // published state chunks' buckets from our utilisation saves.\n if (\n !stateResult.unchanged &&\n stateResult.referenceHex &&\n stamper instanceof UtilizationAwareStamper\n ) {\n await stamper.setSyncedReference(partition, stateResult.referenceHex)\n if (stateResult.stateBuckets) {\n await stamper.setProtectedStateBuckets(\n partition,\n stateResult.stateBuckets,\n )\n }\n }\n\n // Seed the publish baseline from the state we just resumed, so the FIRST\n // publish of this session is incremental (only the bucket the upload touches\n // + the reference chunk) instead of a full re-publish. `publishedCounter`\n // must be a COPY: bindPartition aliases `localCounter` and the stamper\n // mutates it on stamp(), which would otherwise empty the diff. When the read\n // recovered no refs (fresh account / cache-miss), these stay undefined and\n // the first publish falls back to the sparse-full path.\n //\n // SAFETY INVARIANT (why a divergent seed is safe). On the cache-hit\n // (`unchanged`) branch `publishedReferences` describes the last *acked*\n // counter (the synced reference, advanced only on a successful publish/read)\n // while `publishedCounter` = `buildLeaseLocalCounter()`, which can be AHEAD\n // if a prior publish FAILED after the local counter advanced (withWrite's\n // `finally` still flushes the bumped counter). Counters are monotonic, so\n // `publishedCounter >= synced-ref counter` always — the refs never describe a\n // value ABOVE the counter. On the first incremental publish an unchanged\n // (ahead) chunk therefore RETAINS a ref describing the acked floor, and a\n // takeover resumes at ≤ the acked high-water: it only ever reissues *unacked*\n // (content-addressed → safe) slots, never an acked one. The dangerous\n // direction — a ref reporting above the counter, which could resume PAST an\n // acked slot into reuse — cannot occur. See the \"divergent seed\" regression\n // test in partition-lease.integration.test.ts.\n //\n // KEYSTONE (the one upstream fact this rests on): the stamper keeps its local\n // per-partition counter monotonic and >= its synced-reference counter. Every\n // guarantee above collapses if a future change to stamper-state persistence\n // lets the local counter fall BELOW the synced ref. `writePartitionState`'s\n // incremental path carries a cheap monotonicity tripwire that fails the\n // publish loudly (rather than silently retaining a ref above the counter) if\n // that invariant is ever broken — see the assert there.\n this.publishedReferences = stateResult.references\n this.publishedCounter = localCounter.slice()\n // Mark this epoch as already full-pinned so the FIRST publish of the session\n // stays incremental against the resumed refs (the cheap-first-publish win).\n // On the FULL-read branch every non-zero counter chunk was just downloaded,\n // so the retained refs are verified-present. On the CACHE-HIT branch only the\n // reference chunk was read (the counter chunks were not re-fetched), so a\n // retained chunk that was meanwhile evicted from the reserve is NOT detected\n // here — but that stays fail-safe: a takeover reading the missing chunk gets\n // `readFailed` (→ read-only + retry), never a zero-counter resume, and this\n // holder's own once-per-epoch full re-pin (`flushState`, below) re-uploads it\n // from local state once the epoch rolls over. Either way the re-pin bounds a\n // retained chunk's eviction exposure to ~1 epoch.\n this.lastFullPublishEpoch = intentEpochBucket(this.now())\n // Seed the heartbeat's pointer target from the state we resumed, so a holder\n // that never uploads still re-publishes the inherited resume pointer to the\n // current bucket each refresh tick (otherwise `heartbeatStatePointer` no-ops\n // until the first upload and the resume point ages out of the takeover lookup\n // span → resume-from-zero). Undefined on a genuinely fresh account (no prior\n // pointer to keep alive — the heartbeat correctly stays a no-op).\n this.lastReferenceHex = stateResult.referenceHex\n\n const payload = lockResult.payload\n this.self = {\n partition,\n generation: payload.generation,\n acquiredAt: payload.acquiredAt,\n leasedUntil: payload.leasedUntil,\n }\n this.holders.set(partition, {\n deviceId: this.opts.deviceId,\n generation: payload.generation,\n acquiredAt: payload.acquiredAt,\n leasedUntil: payload.leasedUntil,\n })\n\n // Publish presence immediately so a joiner appearing before our first\n // refresh tick still detects us (don't wait ~10s for the first heartbeat)\n // — but OFF the acquire critical path: it is best-effort (errors are\n // swallowed; a lost beacon self-heals next tick) and its two SOC writes\n // are internally serialized via `withIntentSocSlot`, so the claim result\n // doesn't need to block on them. Tracked in `pendingBeaconPublish` for\n // ordering with `refresh()`/`release()` (see the field doc).\n this.pendingBeaconPublish = this.publishPresenceBeacon(\n partition,\n payload.generation,\n payload.leasedUntil,\n )\n\n return {\n partition,\n partitionCount,\n localCounter,\n isReadOnly: false,\n lockPayload: payload,\n }\n }\n\n /**\n * Resolves when the claim-time detached beacon publish has settled. Never\n * rejects. Exposed so callers/tests that need the beacon to be readable\n * (e.g. before simulating a joiner) have a deterministic sync point.\n */\n get beaconPublishSettled(): Promise<void> {\n return this.pendingBeaconPublish\n }\n\n /**\n * Re-run the lock protocol on the currently-held partition to bump\n * `leasedUntil`. Returns `\"lost\"` when we don't hold a lease (legacy mode)\n * or the lock protocol returned `blocked` / `lost-race` / `aborted`,\n * `\"displaced\"` when a foreign presence beacon proves a peer holds the\n * partition (the deconfliction backstop), else `\"held\"`. See\n * `LeaseRefreshOutcome` for how the coordinator must react to each.\n */\n async refresh(): Promise<LeaseRefreshOutcome> {\n if (!this.self) return \"lost\"\n // Let a still-in-flight claim-time beacon settle first, so this tick's\n // fresher beacon can't be overwritten by it (same SOC addresses).\n await this.pendingBeaconPublish\n if (!this.self) return \"lost\"\n const partition = this.self.partition\n const { stamper } = this.requireWriteContext()\n const lockResult = await acquirePartitionLock({\n bee: this.opts.bee,\n stamper,\n backupSigner: this.opts.backupSigner,\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n batchId: this.opts.batchId,\n partition,\n deviceId: this.opts.deviceId,\n ttlMs: LEASE_TTL_MS,\n guardMs: this.opts.guardMs ?? PARTITION_LOCK_GUARD_MS,\n wait: this.opts.wait,\n // Reuse the held claim's generation — it identifies the claim SESSION.\n // Re-minting a fresh generation each tick stamps a later timestamp than\n // the rightful first claimant, inverting claim order (issue #413).\n generation: this.self.generation,\n now: () => this.now(),\n // A release closes the lease session; a refresh that overlaps it must\n // not mint a fresh ghost claim — the generation-fenced release would\n // refuse to clear it and peers would wait out the TTL.\n shouldAbort: () => this.closed,\n })\n // `release()` can complete during the guard window above and null\n // `this.self` — re-check before dereferencing it.\n if (!this.self) return \"lost\"\n if (lockResult.outcome !== \"acquired\" || !lockResult.payload) {\n console.debug(\n `[PartitionLease] Refresh on partition ${partition} returned ${lockResult.outcome}.`,\n )\n return \"lost\"\n }\n const payload = lockResult.payload\n this.self = {\n partition,\n generation: payload.generation,\n acquiredAt: payload.acquiredAt,\n leasedUntil: payload.leasedUntil,\n }\n // Re-publish our presence beacon so a joining device can detect us on the\n // gateway, where the static lock SOC isn't reliably retrievable. Best-effort\n // (a failed beacon self-heals next tick).\n await this.publishPresenceBeacon(\n partition,\n payload.generation,\n payload.leasedUntil,\n )\n\n // Deconfliction backstop: if a concurrent claim slipped past the acquire-time\n // poll-guard (propagation outran the window), two devices can briefly hold\n // the same partition. Once both beacons have propagated, the one with the\n // LATER generation yields here so the pair resolves to a single holder.\n // This is a CONFIRMED displacement (read via the fresh per-epoch beacon\n // address, which stays readable on a disjoint gateway) — reported as\n // `\"displaced\"` so the coordinator demotes WITHOUT re-checking the static\n // lock SOC, which we just rewrote with our own claim above and which is the\n // frozen read the beacon channel exists to bypass.\n if (\n (await this.foreignBeaconBeatsUs(partition, payload.generation)) ||\n (await this.occupancyBeaconBeatsUs(partition, payload.generation))\n ) {\n console.debug(\n `[PartitionLease] Refresh on partition ${partition}: an earlier-generation peer holds it; yielding.`,\n )\n return \"displaced\"\n }\n return \"held\"\n }\n\n /**\n * True if some OTHER device has a live presence beacon on `partition` whose\n * generation orders strictly before `ourGeneration` (i.e. it claimed first).\n * Reads the current+previous epoch bucket for each known rival (fresh\n * addresses → gateway-retrievable). No rivals known → false.\n */\n private async foreignBeaconBeatsUs(\n partition: number,\n ourGeneration: PartitionLockGeneration,\n ): Promise<boolean> {\n const now = this.now()\n const rivals = (this.opts.knownDeviceIds?.() ?? []).filter(\n (id) => id !== this.opts.deviceId,\n )\n if (rivals.length === 0) return false\n const currentBucket = intentEpochBucket(now)\n const results = await Promise.all(\n rivals.flatMap((deviceId) =>\n [currentBucket, currentBucket - 1].map((bucket) =>\n readPartitionIntent({\n bee: this.opts.bee,\n backupSigner: this.opts.backupSigner,\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n batchId: this.opts.batchId,\n partition,\n deviceId,\n epochBucket: bucket,\n timeoutMs: this.opts.intentReadTimeoutMs,\n }),\n ),\n ),\n )\n return results.some(\n (intent) =>\n intent?.leasedUntil !== undefined &&\n intent.leasedUntil > now - INTENT_LIVENESS_GRACE_MS &&\n compareGenerations(intent.generation, ourGeneration) < 0 &&\n this.beaconOutranksRelease(partition, intent.generation),\n )\n }\n\n /**\n * Whether a foreign live beacon represents a real claim rather than the\n * just-released holder's stale GHOST. A beacon outranks the partition's\n * observed release only if its generation is STRICTLY NEWER than the released\n * claim's (the watermark recorded by `refreshFromSwarm`). At or below the\n * watermark the beacon is the lingering presence/occupancy SOC the releaser\n * left behind — still within its lease TTL at a live per-epoch address, but no\n * longer a claim — so it must not block/displace a peer taking over the freed\n * partition. A genuine post-release claimant always has a newer generation\n * (current timestamp ≫ the old watermark under the clock-sync assumption), so\n * it is still honored — no dual-acquire regression. No watermark → outranks.\n */\n private beaconOutranksRelease(\n partition: number,\n beaconGeneration: PartitionLockGeneration,\n ): boolean {\n const releasedGen = this.releasedPartitions.get(partition)\n return (\n releasedGen === undefined ||\n compareGenerations(beaconGeneration, releasedGen) > 0\n )\n }\n\n /**\n * Like `foreignBeaconBeatsUs`, but reads the deviceId-INDEPENDENT occupancy\n * beacon (current + previous epoch bucket). This resolves a dual-acquire with a\n * peer that is NOT in `knownDeviceIds` — e.g. a live device pruned from the\n * rival set by `activeDeviceIds` (its `lastSignedInAt` aged past\n * `KNOWN_DEVICE_MAX_AGE_MS` even though it never signed out). Without this,\n * `refresh()`'s deviceId-keyed `foreignBeaconBeatsUs` is blind to that peer and\n * a symmetric fresh-claim dual-acquire would stay sticky.\n *\n * The occupancy SOC is a single shared address per (partition, epoch), so two\n * holders clobber each other's writes; only the later-generation loser yields,\n * and it converges within a few refresh ticks (whenever it reads a tick where\n * the earlier-generation winner wrote last). True when a foreign live occupancy\n * beacon orders strictly before our generation.\n */\n private async occupancyBeaconBeatsUs(\n partition: number,\n ourGeneration: PartitionLockGeneration,\n ): Promise<boolean> {\n const now = this.now()\n const currentBucket = intentEpochBucket(now)\n const results = await Promise.all(\n [currentBucket, currentBucket - 1].map((bucket) =>\n readPartitionOccupancy({\n bee: this.opts.bee,\n backupSigner: this.opts.backupSigner,\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n batchId: this.opts.batchId,\n partition,\n epochBucket: bucket,\n timeoutMs: this.opts.intentReadTimeoutMs,\n }),\n ),\n )\n return results.some(\n (occupancy) =>\n occupancy !== undefined &&\n occupancy.deviceId !== this.opts.deviceId &&\n occupancy.leasedUntil !== undefined &&\n occupancy.leasedUntil > now - INTENT_LIVENESS_GRACE_MS &&\n compareGenerations(occupancy.generation, ourGeneration) < 0 &&\n this.beaconOutranksRelease(partition, occupancy.generation),\n )\n }\n\n /**\n * Re-publish this device's holder presence beacon for `partition` at the\n * CURRENT epoch (the intent SOC, with `leasedUntil` set). A joiner's\n * `refreshHoldersFromPresence` reads this at a fresh per-epoch address (forced\n * network retrieval), which works on a gateway that 404s the static lock SOC.\n * Best-effort — never fails the refresh; the ~10s refresh cadence <\n * INTENT_EPOCH_MS (30s) keeps the current bucket continuously populated for a\n * live holder.\n *\n * Published UNCONDITIONALLY while holding (this method is only reached for a\n * K>1 partition). It must NOT be gated on knowing a rival: a device that\n * created the account hasn't synced its peer into the registry yet, so a\n * rival-gated beacon would leave that holder invisible and a peer who DOES\n * know it would find no beacon and dual-acquire the same partition.\n */\n private async publishPresenceBeacon(\n partition: number,\n generation: PartitionLockGeneration,\n leasedUntil: number,\n ): Promise<void> {\n const { stamper } = this.requireWriteContext()\n const epochBucket = intentEpochBucket(this.now())\n try {\n // Per-device beacon (generation tiebreak / displacement backstop) AND the\n // deviceId-independent occupancy beacon (lets a peer that doesn't know us\n // detect we hold this partition). Both unconditional while holding.\n await writePartitionIntent({\n bee: this.opts.bee,\n stamper,\n backupSigner: this.opts.backupSigner,\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n batchId: this.opts.batchId,\n partition,\n deviceId: this.opts.deviceId,\n epochBucket,\n generation,\n leasedUntil,\n })\n await writePartitionOccupancy({\n bee: this.opts.bee,\n stamper,\n backupSigner: this.opts.backupSigner,\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n batchId: this.opts.batchId,\n partition,\n deviceId: this.opts.deviceId,\n epochBucket,\n generation,\n leasedUntil,\n })\n } catch (error) {\n console.debug(\n `[PartitionLease] presence beacon publish failed for p=${partition} (self-heals next tick):`,\n error,\n )\n }\n }\n\n /**\n * Publish the final local counter on the partition-state feed, then\n * write the generation-fenced release sentinel to the lock SOC so peers\n * see an immediate, authoritative release. The sentinel carries the\n * generation of the claim being released (captured BEFORE the slow\n * publish) and is skipped entirely when the lock no longer carries that\n * claim — a detached release outliving a same-device re-acquire must not\n * clobber the successor's claim (#349). No-op when no lease is held.\n */\n async release(localCounter: Uint32Array): Promise<void> {\n if (!this.self) return\n // Capture the claim being released before anything slow happens: the\n // sentinel fences exactly this claim, and `closed` stops a concurrent\n // refresh from minting a newer ghost claim mid-release.\n const { partition, generation, acquiredAt } = this.self\n this.closed = true\n // Let the claim-time detached beacon settle before the release sequence —\n // a beacon landing after the sentinel would re-mark the freed partition\n // held for peers reading the presence channel.\n await this.pendingBeaconPublish\n const { stamper } = this.requireWriteContext()\n\n await this.flushState(partition, localCounter)\n\n await releasePartitionLock({\n bee: this.opts.bee,\n stamper,\n backupSigner: this.opts.backupSigner,\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n batchId: this.opts.batchId,\n partition,\n deviceId: this.opts.deviceId,\n releasedGeneration: generation,\n acquiredAt,\n now: () => this.now(),\n })\n\n // The lease session is over on BOTH outcomes — \"skipped\" means a\n // successor or peer owns the lock now, which releases us just the same.\n this.self = undefined\n this.holders.delete(partition)\n }\n\n /**\n * Publish the held partition's counter on the partition-state feed WITHOUT\n * releasing the lock — the commit point that makes an upload's slots durable\n * before its promise resolves (ack-after-publish). The new holder on takeover\n * resumes at exactly this counter, so an un-published (un-acked) in-flight\n * write is safe to overwrite while every acked write is preserved. No-op when\n * no lease is held. Same reserved-slot publish as `release()`, so it is safe\n * through a still-bound stamper.\n */\n async publishState(localCounter: Uint32Array): Promise<void> {\n if (!this.self) return\n await this.flushState(this.self.partition, localCounter)\n }\n\n /**\n * Publish `localCounter` on the partition-state feed and record it as our\n * synced reference + protected buckets. Shared by `release()` (final flush)\n * and `publishState()` (per-upload commit). Protecting the publish's buckets\n * stops a later utilisation save from overstamping (evicting) the resume\n * point from the reserve.\n */\n private async flushState(\n partition: number,\n localCounter: Uint32Array,\n opts?: { forceFull?: boolean },\n ): Promise<void> {\n const { stamper, batchId, batchDepth } = this.requireWriteContext()\n // Force a FULL (non-incremental) publish once per epoch: incremental\n // publishes leave unchanged chunks pinned across epochs, where a later\n // epoch's rotating pointer/beacon SOC (same reserved slot) can evict one and\n // the incremental path never re-uploads it. Re-pinning every epoch bounds a\n // retained chunk's exposure to ~1 epoch and self-heals any eviction (the\n // full publish re-uploads every non-zero chunk clear of the current beacons).\n // `opts.forceFull` additionally forces it off-epoch: the idle refresh-tick\n // `heartbeatStatePointer` asks for it the moment it DETECTS a retained-chunk\n // collision with this epoch's own pointer/beacon bucket, so the eviction is\n // repaired the same tick rather than waiting for the next upload/epoch.\n const currentEpoch = intentEpochBucket(this.now())\n const forceFullRepin =\n opts?.forceFull === true || currentEpoch !== this.lastFullPublishEpoch\n const published = await writePartitionState({\n bee: this.opts.bee,\n stamper,\n batchId,\n batchDepth,\n partition,\n localCounter,\n backupSigner: this.opts.backupSigner,\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n // Incremental inputs from this session's last publish (undefined on the\n // first → full publish, which seeds them). Nulled once per epoch to force a\n // full re-pin (see `lastFullPublishEpoch`).\n previousReferences: forceFullRepin ? undefined : this.publishedReferences,\n previousCounter: forceFullRepin ? undefined : this.publishedCounter,\n // Lets the publish also avoid this device's intent-beacon buckets (the\n // refresh tick re-writes them off-lock at the same reserved slot).\n deviceId: this.opts.deviceId,\n // Anchor the rendezvous addresses to this device's clock (#385): same\n // clock as the collision detector and lease validity; `Date.now()` in prod.\n nowMs: this.now(),\n })\n if (forceFullRepin) this.lastFullPublishEpoch = currentEpoch\n this.publishedReferences = published.references\n this.publishedCounter = published.publishedCounter\n this.lastReferenceHex = published.referenceHex\n if (stamper instanceof UtilizationAwareStamper) {\n await stamper.setSyncedReference(partition, published.referenceHex)\n await stamper.setProtectedStateBuckets(partition, published.stateBuckets)\n }\n }\n\n /**\n * Seed the heartbeat's pointer target (`lastReferenceHex`) without reading\n * partition-state. Used by the coordinator's cached-lease re-adopt fast path\n * (`adoptIfLive`), which binds from local state and never runs\n * `claimPartition` — where the cold acquire path seeds this. Without it a\n * reloaded holder that re-adopts its lease and then stays idle never\n * heartbeats the inherited resume pointer forward, so it ages out of the\n * takeover lookup span (resume-from-zero → re-issue acked slots). The caller\n * passes the partition's persisted synced reference\n * (`UtilizationAwareStamper.getSyncedReference`); `undefined` (fresh partition)\n * keeps the heartbeat a no-op.\n */\n seedReferenceHex(referenceHex: string | undefined): void {\n this.lastReferenceHex = referenceHex\n }\n\n /**\n * Re-write the state pointer to the CURRENT rotating bucket without\n * re-uploading the counter — the refresh-tick heartbeat. Keeps a fresh pointer\n * in the current bucket so a taking-over device finds the resume point even\n * after this holder has been idle (no uploads) for a while. No-op until the\n * first publish of the session has produced a reference to point at.\n *\n * COLLISION SELF-HEAL: when this epoch's own rotating pointer/beacon buckets\n * (or the constant lock-SOC bucket) now coincide with a RETAINED counter/\n * reference chunk — the ~1/65536-per-epoch case an incremental publish left\n * unrefreshed — a bare pointer write here would OVERSTAMP (evict) that chunk at\n * their shared reserved slot. `retainedChunksCollideThisEpoch` detects it (CPU\n * only, no network on the ~99.998% clear path) and, given the live counter,\n * relocates via a forced full re-pin (`flushState`) so the eviction is repaired\n * the same tick instead of waiting for the next upload. Without a counter to\n * re-pin (or on a SOC-vs-SOC collision, where neither party is movable) it\n * falls through to the plain pointer write — still fail-safe: the collision is\n * bounded to one epoch (the rotating addresses separate next epoch) and a\n * takeover reading a missing chunk gets `readFailed` (→ read-only + retry),\n * never a zero-counter resume that would re-issue acked slots.\n */\n async heartbeatStatePointer(localCounter?: Uint32Array): Promise<void> {\n if (!this.self || this.lastReferenceHex === undefined) return\n const { stamper, batchId } = this.requireWriteContext()\n if (\n localCounter !== undefined &&\n this.retainedChunksCollideThisEpoch(this.self.partition)\n ) {\n // Relocate the retained chunks clear of this epoch's reserved-slot writers.\n // `flushState({ forceFull: true })` re-pins every non-zero chunk (avoiding\n // the current epoch's pointer/beacon buckets) AND republishes the pointer,\n // so it subsumes the heartbeat's pointer write.\n await this.flushState(this.self.partition, localCounter, {\n forceFull: true,\n })\n return\n }\n await writeStatePointer({\n bee: this.opts.bee,\n stamper,\n backupSigner: this.opts.backupSigner,\n swarmEncryptionKey: this.opts.swarmEncryptionKey,\n batchId,\n partition: this.self.partition,\n referenceHex: this.lastReferenceHex,\n nowMs: this.now(),\n })\n }\n\n /**\n * True when a RETAINED published state chunk now shares a bucket with one of\n * THIS device's own reserved-slot writers for the current epoch — the rotating\n * state-pointer, the deviceId-independent occupancy beacon, this device's\n * intent beacon, or the constant lock SOC. All land at `slot = partition`, so a\n * shared bucket means one overstamps (evicts) the other. Computed from\n * `this.now()`-derived addresses — the one rendezvous clock shared by\n * `writePartitionState` (via `nowMs`) and the reader (#385), = `Date.now()` in\n * production — so a heartbeat detects exactly the collision its own write would\n * cause.\n *\n * The retained set is EVERY non-zero counter chunk (`publishedReferences`) AND\n * the reference chunk itself (`lastReferenceHex`) — the latter isn't in\n * `publishedReferences` but sits at the same reserved slot and is the very\n * chunk the pointer points at, so evicting it dangles the pointer just as\n * badly. (This is the full `stateBuckets` set the publish protects.) A peer's\n * intent beacon can't be computed here (its deviceId / address is unknown) —\n * that residual stays fail-safe (§12).\n */\n private retainedChunksCollideThisEpoch(partition: number): boolean {\n if (this.publishedReferences === undefined) return false\n const owner = this.opts.backupSigner.publicKey().address()\n const { batchId } = this.requireWriteContext()\n // `this.now()` — one clock for the whole rendezvous path (#385): the beacon/\n // heartbeat writes this guards, the publish it triggers (`writePartitionState`\n // via `nowMs`), and the reader all derive their buckets from this same clock.\n const nowMs = this.now()\n const epoch = intentEpochBucket(nowMs)\n // This device's own reserved-slot writers for the current epoch.\n const fixed = new Set<number>([\n lockSocBucket(batchId, partition, owner),\n toBucket(statePointerAddress(batchId, partition, owner, nowMs)),\n toBucket(partitionOccupancyAddress(batchId, partition, epoch, owner)),\n toBucket(\n intentSocAddress(batchId, partition, this.opts.deviceId, epoch, owner),\n ),\n ])\n // Retained chunks a rotating writer above could evict: the non-zero counter\n // chunks + the reference chunk (its bucket isn't in `publishedReferences`).\n for (const ref of this.publishedReferences) {\n if (!isZeroRef(ref) && fixed.has(toBucket(ref.slice(0, 32)))) return true\n }\n if (this.lastReferenceHex !== undefined) {\n const refChunkAddr = new Reference(this.lastReferenceHex)\n .toUint8Array()\n .slice(0, 32)\n if (fixed.has(toBucket(refChunkAddr))) return true\n }\n return false\n }\n\n /** Current partition (undefined when not holding a lease). */\n get currentPartition(): number | undefined {\n return this.self?.partition\n }\n\n /** Wall-clock ms this device's lease is valid until (undefined when none). */\n get leasedUntil(): number | undefined {\n return this.self?.leasedUntil\n }\n\n /**\n * Advance the locally-held lease's `leasedUntil` to `now + ttlMs` without\n * any Swarm write. The caller bumps this every refresh tick as a\n * \"this device is alive\" heartbeat, so the persisted cache stays fresh\n * even when the Swarm lock-SOC write is transiently failing. No-op when no\n * lease is held. (The cross-device lock SOC is refreshed separately by\n * `refresh()`; this only touches local state.)\n */\n bumpLocalLease(ttlMs: number): void {\n if (!this.self) return\n this.self = { ...this.self, leasedUntil: this.now() + ttlMs }\n }\n\n /**\n * Re-adopt a still-valid `self` from the persisted cache (after `hydrate`)\n * without any Swarm round-trip. Returns the held partition when the cached\n * lease is still within its TTL, else `undefined` (caller falls back to the\n * cold `acquire()` lock-SOC scan). On adoption it bumps `leasedUntil` and\n * mirrors `self` into `holders` so `getHolders()`/`heldPartition()` agree.\n *\n * This is what lets a reload keep the lease: the fresh page reconstructs the\n * binding from local state alone, surviving transient lock-SOC read/write\n * failures. The next `refresh()` reconciles with Swarm.\n */\n adoptIfLive(): number | undefined {\n if (!this.self || this.self.leasedUntil <= this.now()) return undefined\n this.self = { ...this.self, leasedUntil: this.now() + LEASE_TTL_MS }\n this.holders.set(this.self.partition, {\n deviceId: this.opts.deviceId,\n generation: this.self.generation,\n acquiredAt: this.self.acquiredAt,\n leasedUntil: this.self.leasedUntil,\n })\n return this.self.partition\n }\n\n /**\n * Serialise the lease state for the local persistence cache. The cache is\n * a fast hint only — callers re-validate `self` against the lock SOC\n * (`refresh()` / `acquire()`) before trusting it.\n */\n serialize(): PartitionLeaseStateSnapshot {\n return {\n deviceId: this.opts.deviceId,\n batchId: this.opts.batchId.toHex(),\n self: this.self,\n }\n }\n\n /**\n * Seed `self` from a persisted cache snapshot. No Swarm activity — the\n * next `refresh()`/`acquire()` re-validates against the lock SOC. Ignores\n * snapshots for a different device or batch.\n */\n hydrate(snapshot: PartitionLeaseStateSnapshot): void {\n if (snapshot.deviceId !== this.opts.deviceId) return\n if (snapshot.batchId !== this.opts.batchId.toHex()) return\n this.self = snapshot.self\n }\n\n /**\n * Assert the write context (stamper + batch) is present. Throws on a\n * read-only instance — acquire / refresh / release require it.\n */\n private requireWriteContext(): {\n stamper: Stamper\n batchId: BatchId\n batchDepth: number\n } {\n if (!this.opts.stamper || this.opts.batchDepth === undefined) {\n throw new Error(\n \"PartitionLease: write operation requires stamper + batchDepth (read-only instance)\",\n )\n }\n return {\n stamper: this.opts.stamper,\n batchId: this.opts.batchId,\n batchDepth: this.opts.batchDepth,\n }\n }\n\n private now(): number {\n return this.opts.now ? this.opts.now() : Date.now()\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * `BatchWriteCoordinator` — the write path for one account+batch.\n *\n * Owns everything needed to **write to a shared postage batch as a partition\n * holder**, and nothing about client communication:\n * - the cross-tab Web Lock (`withBatchWriteLock`),\n * - the partition-lease lifecycle (acquire / refresh / yield / demote),\n * - stamper-state flush under the lock.\n *\n * Two consumers share it:\n * - the proxy iframe — long-lived (`mode: \"persistent\"`): eager\n * `startLease()` + a refresh timer; an upload that can't get a partition\n * blocks for a slot and then throws.\n * - `sync-account` — one-off (`mode: \"oneshot\"`): no refresh timer; a write\n * claims a free/expired partition once and throws `PartitionContendedError`\n * when every partition is held by a live foreign device, so the caller\n * skips. The lease then lapses by TTL.\n *\n * The lock SOC on Swarm (`partition-lock.ts`) stays the cross-device authority;\n * this class is the lifecycle layer above `PartitionLease`. See\n * `docs/BatchWriteCoordinator.md`.\n *\n * ## Displacement-during-upload race (issue #336)\n *\n * `refreshTick` runs on a bare `setInterval`, NOT under the write lock, so it\n * can detect that a peer took our partition while an upload is mid-`stamp()`.\n * The fix is two-phase: on confirmed displacement we call\n * `stamper.invalidateLease()` **immediately** (partition stays bound →\n * `leaseStale=true` → the in-flight `stamp()` throws `PartitionLeaseLostError`\n * and the corrupting upload aborts), and only *then* run the\n * unbind/clear cleanup **under the write lock** so no `stamp()` runs in the\n * window between invalidate and unbind. Lock-synchronising the demote alone is\n * insufficient — it would merely order the demote after an upload that already\n * corrupted the peer's slot space.\n */\n\nimport { Bee, BatchId, PrivateKey } from \"@ethersphere/bee-js\"\nimport {\n LEASE_TTL_MS,\n LEASE_REFRESH_MS,\n LEASE_SKEW_MARGIN_MS,\n IDLE_YIELD_MS,\n UtilizationAwareStamper,\n} from \"../utils/batch-utilization\"\nimport { withBatchWriteLock } from \"../utils/batch-write-lock\"\nimport { withTimeout } from \"../utils/promise\"\nimport { PartitionLease } from \"./partition-lease\"\nimport type {\n LeaseRefreshOutcome,\n PartitionLeaseStateSnapshot,\n} from \"./partition-lease\"\nimport { readPartitionLock, NO_HOLDER_DEVICE_ID } from \"./partition-lock\"\nimport { STATE_POINTER_EPOCH_MS } from \"./partition-state\"\nimport type { UploadTarget } from \"../proxy/upload\"\nimport type { StampWorkerPool } from \"../proxy/stamp-worker-pool\"\n\n/** Hard cap on the acquire path (including any wait-for-slot retry). */\nconst PARTITION_LEASE_ACQUIRE_TIMEOUT_MS = 45000\n/** Max time the acquire spends polling for a slot when all are held. */\nexport const SLOT_WAIT_TIMEOUT_MS = 30000\n\n/**\n * Thrown by `withWrite` in `wait: \"skip\"` mode when every partition is held by\n * a live foreign device, i.e. there is genuinely no slot to claim. The caller\n * (`sync-account`) catches this and skips the publish — distinct from an\n * operational error (missing stamper, a lock-SOC write that threw), which\n * propagates so it can be logged as an error rather than as contention.\n */\nexport class PartitionContendedError extends Error {\n constructor(\n message = \"All partitions are held by other devices.\",\n readonly accountId?: string,\n ) {\n super(message)\n this.name = \"PartitionContendedError\"\n }\n}\n\n/**\n * A lock-SOC payload means this device has been displaced only if it names a\n * *different*, *live* device. A missing/unreadable payload (e.g. a Bee 500),\n * our own id, the release sentinel, or an *expired* foreign holder are all NOT\n * displacement — the holder keeps its lease and retries. Pure (no instance\n * state) so it can be unit-tested directly; mirrors the proxy's former\n * `isDisplaced`.\n */\nexport function isDisplaced(\n payload: { holderDeviceId: string; leasedUntil: number } | undefined,\n now: number,\n selfDeviceId: string,\n): boolean {\n return (\n payload !== undefined &&\n payload.holderDeviceId !== selfDeviceId &&\n payload.holderDeviceId !== NO_HOLDER_DEVICE_ID &&\n payload.leasedUntil > now\n )\n}\n\n/** Long-lived (proxy) vs one-off (`sync-account`) lease behaviour. */\nexport type CoordinatorMode = \"persistent\" | \"oneshot\"\n\n/** Per-transition lease snapshot handed to `onLeaseChange`. */\nexport interface LeaseChangeInfo {\n currentPartition: number | undefined\n isReadOnly: boolean\n}\n\nexport interface WithWriteOptions {\n useWorkers?: boolean\n workerCount?: number\n /** Override the mode default: persistent → \"block\", oneshot → \"skip\". */\n wait?: \"block\" | \"skip\"\n}\n\nexport interface BatchWriteCoordinatorDeps {\n /** Read on every use, so a getter here follows a change of node. */\n bee: Bee\n /** Postage batch id (hex) — the `withBatchWriteLock` key. */\n batchId: string\n /** Already created + account-bound by the caller; the coordinator only\n * binds/unbinds the held partition on it. */\n stamper: UtilizationAwareStamper\n /** This device's identity, captured once by the caller. */\n deviceId: string\n /**\n * Current known device IDs for this account (from the synced device\n * registry). Read fresh per acquire so it reflects late-announced devices.\n * Drives the Phase-2 intent round that lets disjoint-gateway contenders for\n * a free partition agree on one winner (see partition-intent.ts). Omit for\n * single-device / legacy callers — acquire falls back to guard+TTL only.\n */\n knownDeviceIds?: () => string[]\n /**\n * Pull the latest device registry from Swarm into local state before a fresh\n * acquire, so `knownDeviceIds` reflects a peer that signed in after this device\n * created the account (otherwise the claimer is \"blind\" — never reads the\n * peer's beacon — and dual-acquires). Best-effort; the provider throttles.\n * Omit for node/oneshot callers.\n */\n refreshKnownDeviceIds?: () => Promise<void>\n /**\n * Gateway-propagation tuning for the intent round (optional overrides; default\n * to the `INTENT_*` constants). Lets the proxy pass runtime-tunable values.\n */\n intentReadTimeoutMs?: number\n intentGuardWindowMs?: number\n intentGuardPollMs?: number\n accountId: string\n /** Owns the per-partition lock SOCs (derived from the account key). */\n backupSigner: PrivateKey\n swarmEncryptionKey: Uint8Array\n partitionCount: number\n mode: CoordinatorMode\n /** Persistent mode only: read/write the local lease-cache hint. */\n readLeaseCache?: () => PartitionLeaseStateSnapshot | undefined\n writeLeaseCache?: (snap: PartitionLeaseStateSnapshot | undefined) => void\n /** Flush stamper bucket state after a write (proxy: `saveStamperState`). */\n flushStamperState?: () => Promise<void>\n /** Build/reuse a parallel-signing worker pool (proxy: `getOrCreateWorkerPool`). */\n getWorkerPool?: (count?: number) => Promise<StampWorkerPool | undefined>\n /** Fired on every partition / read-only transition. */\n onLeaseChange?: (info: LeaseChangeInfo) => void\n /** Fired when a partition is (re)acquired (phase-5 publish trigger). */\n onLeaseAcquired?: (partition: number) => void\n /**\n * Fired each slot-wait poll round while a blocking acquire finds every\n * partition held by live peers. The proxy broadcasts a `lease-request` on\n * the account bus so an idle live holder yields immediately instead of\n * after `IDLE_YIELD_MS` (docs/Account-Bus.md, bus-accelerated leases).\n */\n onSlotWait?: () => void\n /**\n * Fired after every release of a held partition — the idle-timer yield, a\n * yield for a peer's `lease-request` (`requestId` echoes it), and teardown —\n * so announcing it on the bus is the coordinator's doing, not something each\n * release path remembers. The proxy publishes `lease-released`.\n */\n onLeaseReleased?: (partition: number, requestId?: string) => void\n}\n\n/**\n * Minimum idle time before a peer's `lease-request` makes this holder yield.\n * Much shorter than `IDLE_YIELD_MS` (the unprompted timer yield) — the request\n * proves a peer is actively waiting — but non-zero so a holder mid-burst\n * (between two uploads) doesn't ping-pong its partition away.\n */\nexport const PEER_YIELD_MIN_IDLE_MS = 3000\n\n/**\n * A `lease-released` reaches a waiter over the bus well before the released\n * slot's sentinel write is readable from its node, so the scan it wakes often\n * still reads the old holder (#724). Rather than sleep out a poll interval on\n * that — or re-ask, which draws a fresh request id nobody can stand down\n * against, so a second holder yields for the same waiter — the wait re-scans\n * this many times at this interval, asking nobody.\n */\nexport const ANNOUNCED_RESCAN_INTERVAL_MS = 1500\nexport const ANNOUNCED_RESCAN_ROUNDS = 3\n\n/** One slot-wait's poll state. Scoped to the wait rather than the coordinator\n * because waits overlap: `ensureLease` races this loop against a timeout and\n * cannot cancel the loser, so a detached wait's timer and its exit must not\n * be able to touch the live wait's. */\ntype SlotWaitState = {\n /** Resolves the current poll sleep early; set only while actually asleep. */\n wake?: () => void\n /** A peer announced a release. Sticky, because the announcement usually\n * lands during the `acquire()` at the top of the loop — holders yield in\n * reply to the request this loop broadcast one line earlier — where there\n * is no sleep to wake, and the scan it interrupts already read the state\n * from before the release. */\n maybeFree: boolean\n /** Short re-scans still owed to the announced release: its sentinel write\n * may take a few seconds to become readable from this node (#724), and\n * asking again in the meantime only makes another holder yield. */\n rescansLeft: number\n /** Whether an announcement has already bought its window. Every round past\n * the window re-broadcasts a `lease-request`, and its answer would re-arm\n * the window, so an unbounded one turns this into one holder dropped per\n * bus round-trip. One window is the announced-but-not-yet-readable case\n * this exists for; needing a second in the same wait means the releases\n * aren't reaching us, and hammering the network won't change that. */\n rescanWindowUsed: boolean\n}\n\nexport class BatchWriteCoordinator {\n private readonly deps: BatchWriteCoordinatorDeps\n\n /** Active partition-lease; undefined when no lease is held (read-only mode,\n * or single-partition legacy accounts). */\n private partitionLease: PartitionLease | undefined\n private partitionRefreshTimer: ReturnType<typeof setInterval> | undefined\n private readOnly: boolean = false\n /** Set when the next write should (re)acquire a partition: cold start,\n * after a demote, or while read-only. */\n private pendingAcquire: boolean = false\n /** Wall-clock ms of the last lock-SOC validation (acquire / refresh). */\n private lastLeaseValidatedAt: number = 0\n /** Wall-clock ms of the last upload activity; drives the idle-yield. */\n private lastLeaseActivityAt: number = 0\n /** Uploads currently executing under the write lock; the refresh tick only\n * yields an idle lease when this is 0. */\n private activeUploadCount: number = 0\n /** Wall-clock ms the state-pointer heartbeat has been failing since\n * (undefined = healthy). The heartbeat is best-effort per tick, but a\n * PERSISTENT failure streak walks the last durable pointer out of the\n * takeover lookup span while lock renewals keep extending `leasedUntil` —\n * a takeover would then zero-seed over acked slots. Once the streak\n * exceeds one pointer epoch, `refreshTick` demotes. Reset on a successful\n * heartbeat and whenever the lease binding changes. */\n private pointerHeartbeatFailingSince: number | undefined = undefined\n /** The live slot-wait's state; undefined outside slot-wait. Held as ONE\n * object per wait, and every touch goes through this field, so a DETACHED\n * wait — `ensureLease` races the poll against a `withTimeout`, and the\n * loser keeps running — owns a different object and cannot reach into the\n * live wait's wake or flag. */\n private slotWait: SlotWaitState | undefined\n /** Set by `teardown()`. Once disposed the coordinator must never re-acquire a\n * partition or arm a refresh timer: an in-flight `withWrite` (e.g. a deferred\n * publish, or a normal upload) can outlive a disconnect/sign-out, and without\n * this guard its `ensureLease` would re-lease the slot and start a detached\n * interval — a ghost lease the proxy can no longer tear down. */\n private disposed: boolean = false\n /** Bumped whenever held-lease state is reset (`pauseLeaseBackgroundWork`,\n * `finalizeDemote`, `yieldIdleLease`, `teardown`). An `acquire()` that began\n * under an older epoch must not commit (bind / start timers) when it\n * resolves — `Promise.race` cannot cancel a losing acquire, whose\n * continuation would otherwise resurrect a lease the coordinator already\n * cleared. */\n private leaseEpoch: number = 0\n\n constructor(deps: BatchWriteCoordinatorDeps) {\n this.deps = deps\n }\n\n /** Current held partition, or undefined (read-only / legacy / not yet acquired). */\n get currentPartition(): number | undefined {\n return this.partitionLease?.currentPartition\n }\n\n /** True when every partition is held by a live peer and this device cannot write. */\n get isReadOnly(): boolean {\n return this.readOnly\n }\n\n /** The account's partition split — the modulus the proxy ranks holders by\n * when electing which one answers a peer's `lease-request`. */\n get partitionCount(): number {\n return this.deps.partitionCount\n }\n\n /** The bound stamper — exposed for the proxy's `buildConnectionInfo`\n * (appKey / uploadMode). */\n get stamperRef(): UtilizationAwareStamper {\n return this.deps.stamper\n }\n\n /** The account this coordinator (and its batch/lease) was built for —\n * exposed so the proxy can refuse to publish a snapshot assembled for a\n * different account through this coordinator's batch. */\n get accountId(): string {\n return this.deps.accountId\n }\n\n /** The batch this coordinator's lease lives on — lanes are per (batch,\n * partition), so the proxy routes bus lease messages by both (#589). */\n get batchId(): string {\n return this.deps.batchId\n }\n\n // ---- write entry point --------------------------------------------------\n\n /**\n * The single write entry point. Takes the cross-tab Web Lock, ensures a held\n * partition (acquire / slot-wait, or skip-with-throw), runs `op` against a\n * stamper upload target, and flushes stamper state. Subsidised mode is NOT a\n * coordinator concern — the proxy only calls this in stamper mode.\n */\n async withWrite<T>(\n op: (target: UploadTarget) => Promise<T>,\n opts?: WithWriteOptions,\n ): Promise<T> {\n const wait =\n opts?.wait ?? (this.deps.mode === \"persistent\" ? \"block\" : \"skip\")\n return this.lockAndFlush(async () => {\n await this.ensureLease(wait)\n this.ensureHeldForUpload()\n const target = await this.buildStamperTarget(opts)\n // Bracket the in-flight upload so the refresh tick's idle-yield can't\n // release the partition (and unbind the stamper) underneath us.\n this.lastLeaseActivityAt = Date.now()\n this.activeUploadCount++\n try {\n // NOTE: `op` performs the chunk write(s) BEFORE the post-op guard\n // below. The cold-re-entry case (a woken/throttled holder) is fenced by\n // the pre-op `ensureLease` check (`leaseNearExpiry` forces a lock read).\n // A lease that lapses MID-`op` (a long multi-chunk upload while the\n // refresh tick can't renew for a full TTL) is NOT fenced for the data\n // write — stamps only re-check the local `leaseStale` flag — only the\n // ack is. Bounded by skew + TTL (see Postage-Batch-Partitioning.md §12).\n const result = await op(target)\n // Commit-ordered ack: only report the upload durable after the\n // partition-state that reserves its slots is published.\n // 1. Reverse-clobber guard (#rule 2): re-validate the lease before\n // acking. For a fast upload this is throttled (no read); a long\n // upload that outran the refresh tick / TTL re-reads the lock SOC\n // and demotes+throws here, so we never publish slots a peer owns.\n // 2. Ack-after-publish (#rule 1): publish the partition counter, then\n // resolve. A guard/publish throw propagates → upload reported\n // failed, never acked (chunks are content-addressed → safe retry).\n await this.ensureLeaseStillValid()\n const localCounter = this.deps.stamper.getLocalCounter()\n if (localCounter !== undefined) {\n await this.partitionLease?.publishState(localCounter)\n // The publish durably re-wrote the state pointer to the current\n // bucket (same effect as a successful heartbeat), so an idle-blip\n // failure streak is stale — clear it. Otherwise the refresh tick keeps\n // measuring drift from the old timestamp across this fresh publish and\n // could demote a healthy holder prematurely.\n this.pointerHeartbeatFailingSince = undefined\n } else if (this.currentPartition !== undefined) {\n // Held a partition lease but the stamper has no local counter: the\n // commit publish (ack-after-publish) would be silently skipped,\n // acking an upload whose slot reservation never reached Swarm. Fail\n // loudly instead — the chunks are content-addressed, so a retry is\n // safe. (Legacy single-partition mode has no counter to publish and\n // correctly falls through.)\n throw new Error(\n `[BatchWriteCoordinator] Held partition ${this.currentPartition} ` +\n \"but the stamper exposed no local counter — cannot publish the \" +\n \"slot reservation (ack-after-publish); failing the upload.\",\n )\n }\n return result\n } finally {\n this.activeUploadCount--\n this.lastLeaseActivityAt = Date.now()\n }\n })\n }\n\n /** Persistent mode: eagerly acquire a partition + start the refresh timer in\n * the background so the first upload doesn't pay the acquire latency. */\n startLease(): void {\n if (this.disposed) return\n if (this.deps.mode !== \"persistent\") return\n if (this.deps.partitionCount <= 1) return\n this.pendingAcquire = true\n void this.lockAndFlush(() => this.ensureLease(\"block\")).catch((err) =>\n console.warn(\n \"[BatchWriteCoordinator] Background lease warm-up failed:\",\n err,\n ),\n )\n }\n\n /**\n * Tear down all lease background work and release the held partition\n * (best-effort) so peers see this device vacate promptly. Used on sign-out /\n * disconnect. Never throws.\n */\n teardown(): void {\n // Disposed coordinators must never re-acquire: an in-flight `withWrite`\n // (deferred publish or upload) can outlive this call and would otherwise\n // re-lease the slot + arm a detached refresh interval.\n this.disposed = true\n this.leaseEpoch++\n if (this.partitionRefreshTimer !== undefined) {\n clearInterval(this.partitionRefreshTimer)\n this.partitionRefreshTimer = undefined\n }\n // Wake a slot poll so it sees `disposed` and stops, rather than sleeping\n // out its interval first. The wait clears its own state on the way out.\n this.slotWait?.wake?.()\n const lease = this.partitionLease\n const heldPartition = lease?.currentPartition\n const stamper = this.deps.stamper\n if (lease) {\n const localCounter = stamper.getLocalCounter()\n if (localCounter !== undefined) {\n // Serialize the detached release under the batch write lock (#349):\n // a successor coordinator's acquire (`startLease`/`withWrite` →\n // `lockAndFlush`) queues on the same origin-wide Web Lock, so the\n // release's publish + sentinel fully complete BEFORE the successor\n // can read or write the lock SOC — the sentinel's postage stamp is\n // minted strictly before the successor's claim stamp, and Bee's\n // newer-stamp-wins replacement keeps the claim. The generation fence\n // in `releasePartitionLock` covers writers outside this lock. (In\n // non-browser contexts the lock no-ops; only `oneshot` mode runs\n // there and it never releases.)\n //\n // The release runs AFTER the synchronous unbind below — the publish\n // goes through an unbound stamper, which is safe because\n // `writePartitionState` marks its chunks with the explicit partition\n // slot.\n void this.lock(() => lease.release(localCounter)).catch((err) =>\n console.warn(\n \"[BatchWriteCoordinator] Partition lease release failed:\",\n err,\n ),\n )\n }\n // Invalidate BEFORE unbinding (same ordering as the displacement race\n // fix): teardown runs synchronously, off the write lock, so it can land\n // between two stamps of an in-flight upload. An unbound stamper stamps\n // at legacy slots, which here are a peer's; `invalidateLease` only arms\n // on a bound stamper, and the breaker it arms outlives the unbind, so\n // the rest of that upload aborts with `PartitionLeaseLostError` instead.\n stamper.invalidateLease()\n stamper.unbindPartition()\n }\n this.partitionLease = undefined\n this.readOnly = false\n this.lastLeaseValidatedAt = 0\n this.deps.writeLeaseCache?.(undefined)\n this.emitLeaseChange()\n // Announced before the detached release lands, which is harmless: the\n // lock SOCs stay the authority, so a peer woken early spends one extra\n // read round.\n if (heldPartition !== undefined) this.deps.onLeaseReleased?.(heldPartition)\n }\n\n // ---- lease lifecycle ----------------------------------------------------\n\n /**\n * Ensure a partition is held for the current write. Runs under the write\n * lock (called from `withWrite`/`startLease`). Re-arms a pending acquire\n * when no lease is held or we are read-only, then either acquires (with\n * slot-wait in \"block\" mode, or once-then-throw in \"skip\" mode) or runs the\n * throttled freshness check on the held lease.\n */\n private async ensureLease(wait: \"block\" | \"skip\"): Promise<void> {\n try {\n // Single-partition (legacy) accounts never lease — nothing to do (and no\n // ghost-lease risk, so the disposed guard below doesn't apply to them).\n if (this.deps.partitionCount <= 1) return\n // Torn down while this write was in flight (disconnect / sign-out / re-\n // auth). Bail before any (re)acquire so we never resurrect a ghost lease.\n if (this.disposed) {\n throw new Error(\"BatchWriteCoordinator has been torn down.\")\n }\n\n if (!this.pendingAcquire && (!this.partitionLease || this.readOnly)) {\n this.pendingAcquire = true\n }\n\n if (this.pendingAcquire) {\n this.pendingAcquire = false\n if (wait === \"skip\") {\n // One-off: a single acquire attempt. PartitionLease.acquire claims a\n // free/expired slot under the lock SOC's generation fencing. An\n // operational failure inside `acquire` (lock-SOC scan/claim threw)\n // propagates as-is so the caller reports an error; only a COMPLETED\n // acquire that found every partition held by a live peer is\n // contention.\n await this.acquire()\n if (this.readOnly || this.currentPartition === undefined) {\n throw new PartitionContendedError(undefined, this.deps.accountId)\n }\n return\n }\n try {\n await withTimeout(\n this.acquireWithSlotWait(),\n PARTITION_LEASE_ACQUIRE_TIMEOUT_MS,\n `Partition lease timed out after ${PARTITION_LEASE_ACQUIRE_TIMEOUT_MS}ms`,\n )\n } catch (error) {\n console.warn(\n \"[BatchWriteCoordinator] Partition lease acquisition failed, pausing background work:\",\n error,\n )\n this.pauseLeaseBackgroundWork()\n // Surface the real cause (Bee down, timeout, contention) instead of\n // letting `ensureHeldForUpload` mislabel it as \"batch fully leased\n // by other devices\". `startLease`'s warm-up catch absorbs this.\n throw error\n }\n return\n }\n await this.ensureLeaseStillValid()\n } finally {\n // Reflect any partition transition to the consumer. Idempotent — the\n // proxy suppresses unchanged ConnectionInfo.\n this.emitLeaseChange()\n }\n }\n\n /**\n * Acquire a partition via the lock SOC — the single source of truth. The\n * local cache is only a hint (`hydrate`); `acquire` always re-reads and\n * reconciles against the lock SOCs before binding.\n */\n private async acquire(): Promise<void> {\n const { stamper, partitionCount, swarmEncryptionKey } = this.deps\n if (partitionCount <= 1) {\n this.readOnly = false\n return\n }\n // Torn down before this acquire started (e.g. a later slot-wait iteration\n // after a disconnect). Don't bind a partition we can never release.\n if (this.disposed) return\n // Epoch capture: `ensureLease`'s block path races this acquire against a\n // timeout it cannot cancel. If the timeout fires, `pauseLeaseBackgroundWork`\n // resets state and bumps the epoch; a late-completing continuation here must\n // then abort instead of re-binding a partition and arming a refresh timer\n // the coordinator no longer tracks.\n const epoch = this.leaseEpoch\n // Refresh the device registry in the BACKGROUND — do NOT await it here.\n // `refreshKnownDeviceIds` folds the roster from Swarm, which on a slow/flaky\n // gateway can take tens of seconds; awaiting it under the cross-tab write\n // lock made `startLease`'s warm-up hold that lock for the whole scan (up to\n // the 45s acquire cap), stalling the user's first upload behind it. `acquire`\n // reads the rival set from local storage (`knownDeviceIds`), so a refresh\n // that lands after this acquire simply benefits the NEXT one. The only cost\n // of a momentarily-stale set is skipping the per-device intent read for a\n // brand-new peer — still caught by the deviceId-independent occupancy beacon\n // and reconciled on the next acquire.\n void this.deps\n .refreshKnownDeviceIds?.()\n .catch((error) =>\n console.warn(\n \"[BatchWriteCoordinator] Background device-registry refresh failed (using current registry):\",\n error,\n ),\n )\n if (this.leaseEpoch !== epoch || this.disposed) return\n try {\n const deps = this.deps\n const lease = await PartitionLease.fromSwarmEncryptionKey({\n // The lease outlives a change of node, so it reads `deps.bee` anew.\n get bee() {\n return deps.bee\n },\n deviceId: this.deps.deviceId,\n batchId: new BatchId(this.deps.batchId),\n batchDepth: stamper.depth,\n swarmEncryptionKey,\n stamper,\n knownDeviceIds: this.deps.knownDeviceIds,\n intentReadTimeoutMs: this.deps.intentReadTimeoutMs,\n intentGuardWindowMs: this.deps.intentGuardWindowMs,\n intentGuardPollMs: this.deps.intentGuardPollMs,\n })\n if (this.leaseEpoch !== epoch) return\n const cached = this.deps.readLeaseCache?.()\n if (cached) lease.hydrate(cached)\n\n // Re-adopt fast path: a still-valid cached lease (e.g. after a reload\n // within the TTL) is re-established from local state alone — no lock-SOC\n // scan/write, so it survives transient Bee 500s. The refresh tick then\n // reconciles with Swarm and demotes only on a confirmed foreign holder.\n const adopted = lease.adoptIfLive()\n if (adopted !== undefined) {\n // Seed the heartbeat's resume-pointer target from the persisted synced\n // reference. The cold `claimPartition` path seeds this from the state it\n // reads on acquire; the adopt fast path skips `claimPartition`, so\n // without this an adopted-but-idle holder never heartbeats the inherited\n // pointer forward and it ages out of the takeover lookup span\n // (resume-from-zero → re-issues a gone holder's acked slots). Read it\n // BEFORE the synchronous commit below so no await splits the bind, then\n // re-check the epoch the same way the cold path does. Best-effort\n // (undefined on a genuinely fresh partition → heartbeat stays a no-op).\n const seededReferenceHex = await stamper\n .getSyncedReference(adopted)\n .catch(() => undefined)\n if (this.leaseEpoch !== epoch || this.disposed) return\n // Diagnostic: this re-binds a CACHED lease with no Swarm scan and no\n // intent round. If two devices both adopt partition 0 from stale\n // caches, that's a dual-hold the intent round never gets to arbitrate.\n console.debug(\n `[BatchWriteCoordinator] Adopted cached lease p=${adopted} (no acquire/intent round) for device=${this.deps.deviceId}`,\n )\n this.partitionLease = lease\n this.readOnly = false\n stamper.bindPartition({\n partition: adopted,\n partitionCount,\n localCounter: stamper.buildLeaseLocalCounter(),\n })\n // Fence in-flight stamps to the lease's local expiry (bind clears it).\n this.syncStamperLeaseDeadline()\n // Seed before `onLeaseAcquired` (which may publish): a publish's\n // `flushState` then overwrites this with the fresh reference.\n lease.seedReferenceHex(seededReferenceHex)\n this.deps.writeLeaseCache?.(lease.serialize())\n this.startRefreshTimer(lease)\n this.lastLeaseValidatedAt = Date.now()\n this.lastLeaseActivityAt = Date.now()\n this.pointerHeartbeatFailingSince = undefined // fresh lease session\n this.deps.onLeaseAcquired?.(adopted)\n return\n }\n\n const result = await lease.acquire({ partitionCount })\n if (this.leaseEpoch !== epoch) return\n this.partitionLease = lease\n this.readOnly = result.isReadOnly\n\n if (result.partition === undefined) {\n if (result.isReadOnly) {\n console.warn(\n \"[BatchWriteCoordinator] All partitions held; entering read-only mode until a peer releases.\",\n )\n }\n this.deps.writeLeaseCache?.(lease.serialize())\n return\n }\n\n stamper.bindPartition({\n partition: result.partition,\n partitionCount: result.partitionCount,\n localCounter: result.localCounter,\n })\n // Fence in-flight stamps to the lease's local expiry (bind clears it).\n this.syncStamperLeaseDeadline()\n this.deps.writeLeaseCache?.(lease.serialize())\n this.startRefreshTimer(lease)\n this.lastLeaseValidatedAt = Date.now()\n this.lastLeaseActivityAt = Date.now()\n this.pointerHeartbeatFailingSince = undefined // fresh lease session\n this.deps.onLeaseAcquired?.(result.partition)\n } catch (error) {\n console.error(\n \"[BatchWriteCoordinator] Failed to acquire partition lease:\",\n error,\n )\n // Same epoch guard as the success paths above, mirrored to the failure\n // side: a newer epoch owns the coordinator state now (the racing acquire\n // timeout paused background work, and a later acquire may have re-leased).\n // A stale failure must not clobber the live lease's in-memory state or\n // wipe its cache — just propagate.\n if (this.leaseEpoch !== epoch) throw error\n // The throw may have come AFTER bindPartition/startRefreshTimer (e.g. a\n // consumer callback — writeLeaseCache/onLeaseAcquired — threw): undo the\n // partial commit so the stamper and the interval don't outlive the lease\n // record. Invalidate-before-unbind per the displacement-race ordering;\n // all three are no-ops when nothing was bound/armed. When something was,\n // the stamper stays fenced while `readOnly` is false: the next write\n // re-acquires, and that acquire's `bindPartition` clears the fence.\n if (this.partitionRefreshTimer !== undefined) {\n clearInterval(this.partitionRefreshTimer)\n this.partitionRefreshTimer = undefined\n }\n stamper.invalidateLease()\n stamper.unbindPartition()\n this.partitionLease = undefined\n this.readOnly = false\n // Keep cache and in-memory state in lockstep: no lease → no live cache.\n this.deps.writeLeaseCache?.(undefined)\n // Operational failure (the lock-SOC scan/claim threw) — NOT contention.\n // Propagate so the skip path reports an error instead of \"all partitions\n // held\", and the block path pauses background work on the real cause.\n throw error\n }\n }\n\n /**\n * Wait-for-slot wrapper around `acquire`. When the initial acquire returns\n * read-only (every partition held by a live foreign holder), poll every\n * `LEASE_REFRESH_MS` up to `SLOT_WAIT_TIMEOUT_MS`.\n */\n private async acquireWithSlotWait(): Promise<void> {\n const deadline = Date.now() + SLOT_WAIT_TIMEOUT_MS\n // Epoch capture: when the `withTimeout` in `ensureLease` fires, this\n // loop keeps running detached (the race can't cancel it) and would\n // otherwise wake from its poll sleep and claim the lock SOC one more\n // time, only for `acquire()` to discard the claim — a pointless ghost\n // claim peers must wait out via the TTL.\n const epoch = this.leaseEpoch\n // Fresh state per wait: a wake left over from an earlier one says nothing\n // about this one, and this one's must not be clobbered by an earlier one\n // still running detached.\n const state: SlotWaitState = {\n maybeFree: false,\n rescansLeft: 0,\n rescanWindowUsed: false,\n }\n this.slotWait = state\n try {\n await this.pollForSlot(deadline, epoch, state)\n } finally {\n // However this exits — return, throw, or the disposed check — this\n // wait's state describes a sleep that is over. Only clear it if it is\n // still the live one: a detached wait outlives the wait that replaced it.\n if (this.slotWait === state) this.slotWait = undefined\n }\n }\n\n private async pollForSlot(\n deadline: number,\n epoch: number,\n state: SlotWaitState,\n ): Promise<void> {\n while (true) {\n await this.acquire()\n // Disposed mid-wait (disconnect during the slot poll): stop waiting for a\n // slot we'd never be able to use.\n if (this.disposed) return\n if (!this.readOnly) return\n const scanPredatesAnnouncement = state.maybeFree\n state.maybeFree = false\n // Headroom is the step that comes next, not always a full poll: an\n // announcement landing late in the budget still gets its short re-scans.\n const headroomMs = scanPredatesAnnouncement\n ? 0\n : state.rescansLeft > 0\n ? ANNOUNCED_RESCAN_INTERVAL_MS\n : LEASE_REFRESH_MS\n if (Date.now() + headroomMs > deadline) {\n throw new PartitionContendedError(\n \"No partition available — all slots are held by other devices.\",\n this.deps.accountId,\n )\n }\n if (scanPredatesAnnouncement) {\n // A holder announced a release while we were mid-`acquire()`, so the\n // scan above ran against the state BEFORE it let go. Go straight back\n // round rather than sleeping out an interval we already know is stale.\n // Not a busy loop: only a real peer announcement sets the flag, it is\n // armed once per wait, and every iteration pays a full network\n // acquire, bounded by `deadline`.\n } else if (state.rescansLeft > 0) {\n // The scan ran AFTER the announcement and still saw the old holder:\n // the sentinel is on its way, not yet readable here. Look again\n // shortly, and ask nobody — a repeat ask is a fresh request id, and\n // the holder that already yielded cannot stand the others down\n // against it (#724).\n state.rescansLeft--\n await this.sleepUntilWoken(state, ANNOUNCED_RESCAN_INTERVAL_MS)\n } else {\n // Ask live holders to yield (re-broadcast every round — idempotent),\n // then sleep the poll interval, waking early on `notifySlotMaybeFree`.\n this.deps.onSlotWait?.()\n await this.sleepUntilWoken(state, LEASE_REFRESH_MS)\n }\n // Checked between the sleep and the next acquire: a fresh `acquire()`\n // call would capture the bumped epoch as its own baseline and commit.\n if (this.leaseEpoch !== epoch) return\n }\n }\n\n private sleepUntilWoken(state: SlotWaitState, ms: number): Promise<void> {\n return new Promise<void>((resolve) => {\n const wake = () => {\n clearTimeout(timer)\n state.wake = undefined\n // Consumed by this wake — it is the reason we are awake.\n state.maybeFree = false\n resolve()\n }\n const timer = setTimeout(wake, ms)\n state.wake = wake\n })\n }\n\n /**\n * Push the held lease's local expiry to the stamper so an in-flight `stamp()`\n * fences itself the moment the lease lapses locally — the write-side\n * complement of the reverse-clobber ack guard (Postage-Batch-Partitioning.md\n * §12, \"ack-safe, not write-safe\"). Call wherever `leasedUntil` moves while we\n * hold: bind and every renewal. `undefined` (no lease) disables the fence.\n */\n private syncStamperLeaseDeadline(): void {\n this.deps.stamper.setLeaseValidUntil(this.partitionLease?.leasedUntil)\n }\n\n /**\n * Whether the held lease is within {@link LEASE_SKEW_MARGIN_MS} of its local\n * expiry (or already lapsed). When true the freshness check must re-read the\n * lock SOC even inside the throttle window — the reverse-clobber guard.\n */\n private leaseNearExpiry(): boolean {\n const leasedUntil = this.partitionLease?.leasedUntil\n if (leasedUntil === undefined) return false\n return Date.now() >= leasedUntil - LEASE_SKEW_MARGIN_MS\n }\n\n /**\n * Throttled lock-SOC freshness check on the held lease. Runs under the write\n * lock (via `ensureLease`). Demotes (and throws) when a different live device\n * now holds our partition.\n */\n private async ensureLeaseStillValid(): Promise<void> {\n if (!this.partitionLease || this.readOnly) return\n const partition = this.partitionLease.currentPartition\n if (partition === undefined) return\n // Throttle gateway reads to once per refresh window — UNLESS the local\n // lease is within skew of expiry, in which case we must re-read now (the\n // refresh tick fell behind: suspended tab / clock jump / long upload).\n if (\n !this.leaseNearExpiry() &&\n Date.now() - this.lastLeaseValidatedAt < LEASE_REFRESH_MS\n )\n return\n\n let payload\n try {\n payload = await readPartitionLock({\n bee: this.deps.bee,\n backupSigner: this.deps.backupSigner,\n swarmEncryptionKey: this.deps.swarmEncryptionKey,\n batchId: new BatchId(this.deps.batchId),\n partition,\n })\n } catch (err) {\n // Read failed (transient) — inconclusive, keep the lease and retry.\n console.warn(\n \"[BatchWriteCoordinator] Lease freshness check read failed:\",\n err,\n )\n return\n }\n\n if (isDisplaced(payload, Date.now(), this.deps.deviceId)) {\n console.warn(\n `[BatchWriteCoordinator] Lease freshness check: partition ${partition} taken by ${payload!.holderDeviceId}; demoting.`,\n )\n // Already under the write lock here, so finalize inline (no re-acquire —\n // Web Locks are not reentrant). `signalLeaseLost` first so the breaker is\n // armed before unbind, consistent with the refresh-tick path.\n this.signalLeaseLost()\n this.finalizeDemote()\n throw new Error(\"Partition lease was reclaimed by another device.\")\n }\n\n // A release sentinel is visible while we believe we hold — a stale\n // (pre-fencing) sentinel converging late, or a frozen-cache view of one.\n // Re-assert the claim NOW instead of waiting for the next refresh tick,\n // so peers stop reading the partition as free before this upload runs.\n // NOTE: this is defence in depth, not the primary #349 fix — the\n // freshness throttle above usually skips this check in the seconds right\n // after a re-acquire; the teardown-release lock serialization closes\n // that window.\n if (\n payload !== undefined &&\n payload.holderDeviceId === NO_HOLDER_DEVICE_ID\n ) {\n let reasserted: LeaseRefreshOutcome\n try {\n reasserted = await this.partitionLease.refresh()\n } catch (err) {\n // Transient — keep the lease, and do NOT bump lastLeaseValidatedAt so\n // the next upload retries the check.\n console.warn(\n \"[BatchWriteCoordinator] Sentinel re-assert hit a transient error; keeping lease:\",\n err,\n )\n return\n }\n if (reasserted !== \"held\") {\n // blocked / lost-race (`\"lost\"`) or a beacon-confirmed peer\n // (`\"displaced\"`) — a peer claimed after the sentinel. Mirror the\n // displaced branch exactly (we are under the write lock).\n console.warn(\n `[BatchWriteCoordinator] Sentinel re-assert: partition ${partition} taken by a peer; demoting.`,\n )\n this.signalLeaseLost()\n this.finalizeDemote()\n throw new Error(\"Partition lease was reclaimed by another device.\")\n }\n // Re-asserted: refresh() rewrote the claim and bumped the lease.\n this.syncStamperLeaseDeadline()\n this.lastLeaseValidatedAt = Date.now()\n this.deps.writeLeaseCache?.(this.partitionLease.serialize())\n return\n }\n\n // Optimistic resume: the lock read showed no live foreign holder and no\n // release sentinel, so extend the local lease and keep writing — even when\n // our OWN lock claim has lapsed on Swarm (idle past TTL, or renewals failing)\n // but no peer has taken the slot on this gateway. This is the held-lease fast\n // path (an idle-past-TTL upload re-validates its still-readable lock instead\n // of paying a fresh acquire). A same-gateway takeover IS caught above by\n // `isDisplaced`; the only unhandled case is a peer on a disjoint gateway whose\n // lock write we can't see — the documented §12 optimistic-lease residual,\n // bounded by skew and backstopped by the presence/occupancy beacons on\n // `refresh()`. (The refresh tick takes a more conservative stance and\n // self-demotes an un-renewable lease; this on-demand path favors upload\n // latency, relying on the per-upload `isDisplaced` re-read as its net.)\n this.partitionLease.bumpLocalLease(LEASE_TTL_MS)\n this.syncStamperLeaseDeadline()\n this.lastLeaseValidatedAt = Date.now()\n this.deps.writeLeaseCache?.(this.partitionLease.serialize())\n }\n\n /**\n * Phase 1 of demotion (the race fix): mark the bound lease stale so an\n * in-flight `stamp()` throws `PartitionLeaseLostError` and aborts before\n * writing to a slot a peer now owns. The partition stays bound (so the\n * breaker can fire); the unbind happens in `finalizeDemote` under the lock.\n */\n private signalLeaseLost(): void {\n this.deps.stamper.invalidateLease()\n }\n\n /**\n * Phase 2 of demotion: drop the claim on the current partition. MUST run\n * under the write lock when called outside a held lock (see `refreshTick`),\n * so no `stamp()` runs between `invalidateLease` and `unbindPartition`.\n */\n private finalizeDemote(): void {\n this.leaseEpoch++\n this.deps.stamper.unbindPartition()\n if (this.partitionRefreshTimer !== undefined) {\n clearInterval(this.partitionRefreshTimer)\n this.partitionRefreshTimer = undefined\n }\n this.partitionLease = undefined\n this.readOnly = true\n this.lastLeaseValidatedAt = 0\n this.pointerHeartbeatFailingSince = undefined\n this.deps.writeLeaseCache?.(undefined)\n // Re-arm so the next write re-acquires.\n this.pendingAcquire = true\n this.emitLeaseChange()\n }\n\n /**\n * Periodic refresh: re-run the lock protocol on the held partition every\n * `LEASE_REFRESH_MS`. Runs on a bare interval (NOT under the write lock) so\n * it can detect displacement mid-upload — see the race-fix note in the class\n * doc. Persistent mode only.\n */\n private startRefreshTimer(lease: PartitionLease): void {\n // A disposed coordinator must never arm an interval — closes the window\n // where a `withWrite` acquire that began before teardown finishes after it.\n if (this.disposed) return\n if (this.deps.mode !== \"persistent\") return\n if (this.partitionRefreshTimer !== undefined) {\n clearInterval(this.partitionRefreshTimer)\n }\n this.partitionRefreshTimer = setInterval(() => {\n void this.refreshTick(lease)\n }, LEASE_REFRESH_MS)\n }\n\n private async refreshTick(lease: PartitionLease): Promise<void> {\n // Stale-continuation guard: clearing the interval (teardown / demote /\n // pause) cannot stop a tick already in flight, and a slow tick can overlap\n // the next one. A tick for a lease the coordinator no longer tracks must\n // not touch Swarm, the stamper, or the cache. Re-checked after every await\n // below; a teardown landing mid-`lease.refresh()` can still produce one\n // ghost lock-SOC re-claim, which self-heals via LEASE_TTL_MS.\n if (this.disposed || this.partitionLease !== lease) return\n const partition = lease.currentPartition\n if (partition === undefined) return\n\n // Idle yield: if no upload touched this lease for IDLE_YIELD_MS and none is\n // in flight, voluntarily release so a waiting peer takes the slot without\n // waiting out the TTL. We re-acquire on the next upload.\n //\n // Gated on a KNOWN RIVAL: the yield only ever benefits a waiting peer, and\n // for a solo device it converts every >30s pause into a full cold acquire\n // on the next upload. With no rival in the registry the holder keeps its\n // lease (renewed below) and later uploads hit the TTL-optimism fast path.\n // A brand-new peer regains the yield as soon as the roster syncs it into\n // `knownDeviceIds` (every acquire triggers `refreshKnownDeviceIds`); until\n // then it can still take the OTHER partition, or this one via the TTL if\n // this tab closes.\n //\n // The yield itself MUST run under the write lock: this tick is off-lock, so\n // an upload can enter `withWrite` between the idle check here and the\n // unbind inside `yieldIdleLease` — releasing the partition to peers and\n // unbinding the stamper (back to legacy any-slot picking) underneath\n // an in-flight `stamp()` is the same slot-corruption race as a displacement.\n // Holding the lock excludes in-flight uploads; the re-checks inside cover\n // an upload that completed (or a demote/teardown that ran) while this tick\n // waited for the lock.\n const rivalKnown = (this.deps.knownDeviceIds?.() ?? []).some(\n (id) => id !== this.deps.deviceId,\n )\n if (\n rivalKnown &&\n this.activeUploadCount === 0 &&\n Date.now() - this.lastLeaseActivityAt >= IDLE_YIELD_MS\n ) {\n await this.lock(async () => {\n if (this.disposed) return\n if (this.partitionLease !== lease) return\n if (\n this.activeUploadCount !== 0 ||\n Date.now() - this.lastLeaseActivityAt < IDLE_YIELD_MS\n ) {\n return\n }\n await this.yieldIdleLease(lease)\n })\n return\n }\n\n let confirmedDisplaced = false\n // Did this tick confirm we STILL hold the slot? Either write-verified\n // (`refresh()` returns \"held\") or read-verified (a \"lost\" whose lock-SOC\n // read shows us as the live holder). A transient throw or an unconfirmable\n // \"lost\" (lock read undefined / expired) leaves this false — we must NOT\n // extend the local lease on a hold we couldn't confirm, else a holder whose\n // writes keep failing extends itself forever and keeps writing (the\n // reverse-clobber the post-op ack guard can't cover for a long upload).\n let confirmedHeld = false\n try {\n const outcome = await lease.refresh()\n if (outcome === \"held\") confirmedHeld = true\n if (outcome === \"displaced\") {\n // The lease read a foreign presence beacon (earlier generation) at a\n // fresh per-epoch address and confirmed a peer holds this partition.\n // That beacon channel is exactly what the static lock SOC cannot show\n // on a disjoint gateway, so trust the verdict directly. Re-reading the\n // lock SOC here would be the no-op the backstop exists to avoid:\n // refresh() just rewrote our own claim to that address, so the read\n // would return SELF and never confirm the displacement.\n confirmedDisplaced = true\n } else if (outcome === \"lost\") {\n // Couldn't confirm via write+verify — check for ACTUAL displacement\n // with one read. A 500 here returns undefined → not displaced.\n const payload = await readPartitionLock({\n bee: this.deps.bee,\n backupSigner: this.deps.backupSigner,\n swarmEncryptionKey: this.deps.swarmEncryptionKey,\n batchId: new BatchId(this.deps.batchId),\n partition,\n })\n confirmedDisplaced = isDisplaced(\n payload,\n Date.now(),\n this.deps.deviceId,\n )\n // Not displaced AND the lock SOC still names US as the live holder →\n // read-verified hold (the lock protocol's authority), so it's safe to\n // extend the lease even though the write+verify didn't land this tick.\n if (\n !confirmedDisplaced &&\n payload !== undefined &&\n payload.holderDeviceId === this.deps.deviceId &&\n payload.leasedUntil > Date.now()\n ) {\n confirmedHeld = true\n }\n }\n } catch (err) {\n console.warn(\n \"[BatchWriteCoordinator] Lease refresh hit a transient error; keeping lease:\",\n err,\n )\n }\n\n if (confirmedDisplaced) {\n // The lease may have been demoted and a NEW one re-acquired while the\n // refresh/read above was in flight; invalidating the stamper here would\n // arm the breaker on the new binding and demoting would clobber it.\n if (this.disposed || this.partitionLease !== lease) return\n console.warn(\n `[BatchWriteCoordinator] Refresh: partition ${partition} taken by a peer; demoting.`,\n )\n // Race fix: abort any in-flight upload immediately, THEN unbind under the\n // write lock (this tick is not under the lock).\n this.signalLeaseLost()\n await this.lock(async () => {\n // Re-check after winning the lock: a queued withWrite may have demoted\n // this lease and re-acquired a new live one while we waited. Same\n // guards as the idle-yield branch — this is the correctness-critical\n // check; the off-lock one above only narrows the breaker window.\n if (this.disposed || this.partitionLease !== lease) return\n this.finalizeDemote()\n })\n return\n }\n\n // Guard the tail too: after teardown/demote cleared the cache, a stale\n // tick must not re-write a live-looking snapshot that a future acquire\n // would hydrate + adoptIfLive without any Swarm round-trip.\n if (this.disposed || this.partitionLease !== lease) return\n\n if (!confirmedHeld) {\n // Couldn't confirm we still hold (transient failure, not a confirmed\n // peer). Tolerate a short blip — but once the local lease lapses past skew\n // we can no longer assert we hold the slot (rule 2: never write without a\n // valid lease). Stop writing and demote; the next upload re-acquires and\n // re-reads the lock. Until then, keep the lease as-is WITHOUT bumping, so\n // `leasedUntil` tracks the last successful renewal and genuinely counts\n // down. The UI stays Active through the blip (lease not yet lapsed).\n if (this.leaseNearExpiry()) {\n console.warn(\n `[BatchWriteCoordinator] Refresh: partition ${partition} lease lapsed without a successful renewal; demoting.`,\n )\n this.signalLeaseLost()\n await this.lock(async () => {\n if (this.disposed || this.partitionLease !== lease) return\n this.finalizeDemote()\n })\n }\n return\n }\n\n // Renewed on Swarm → bump the local heartbeat and persist it.\n lease.bumpLocalLease(LEASE_TTL_MS)\n this.syncStamperLeaseDeadline()\n this.lastLeaseValidatedAt = Date.now()\n this.deps.writeLeaseCache?.(lease.serialize())\n // Heartbeat the state pointer to the current rotating bucket (best-effort,\n // off the critical path) so an idle-but-alive holder keeps a fresh resume\n // pointer the next reader can find without a feed walk. `withWrite`'s publish\n // also calls `writeStatePointer`, and both route through the stamper's single\n // `intentSoc` slot — overlapping them clobbers that reservation and mis-stamps\n // a pointer SOC into a data slot. So serialize the heartbeat with the publish:\n // run it UNDER the write lock (the publish holds the same lock). The outer\n // `activeUploadCount === 0` gate is a cheap skip when an upload is already in\n // flight; the under-lock re-check closes the TOCTOU where an upload starts\n // AFTER the gate but before the heartbeat wins the lock (the concurrent\n // publish then already refreshes the pointer, so the heartbeat is redundant).\n if (this.activeUploadCount === 0) {\n try {\n await this.lock(async () => {\n if (this.disposed || this.partitionLease !== lease) return\n if (this.activeUploadCount !== 0) return // an upload slipped in while queued\n // Pass the live counter so the heartbeat can RELOCATE a retained state\n // chunk that this epoch's rotating pointer/beacon bucket would otherwise\n // evict (idle-holder-crosses-an-epoch). Undefined in legacy single-\n // partition mode → the heartbeat stays a bare pointer write.\n await lease.heartbeatStatePointer(this.deps.stamper.getLocalCounter())\n })\n this.pointerHeartbeatFailingSince = undefined\n } catch (err) {\n console.warn(\n \"[BatchWriteCoordinator] State-pointer heartbeat failed:\",\n err,\n )\n // One failed heartbeat is a blip (the takeover lookup tolerates a few\n // dropped buckets of slack). A PERSISTENT streak is not: the lock\n // renewal above keeps extending `leasedUntil` independently, so the\n // last durable pointer drifts toward the lookup span's floor — past it,\n // a takeover finds no pointer on a CLEAN scan and zero-seeds,\n // re-issuing every acked slot. Demote before the drift can reach the\n // floor (one pointer epoch of failures, well inside the\n // TTL + slack·epoch tolerance): the next upload re-acquires and its\n // publish re-pins the pointer.\n this.pointerHeartbeatFailingSince ??= Date.now()\n if (\n Date.now() - this.pointerHeartbeatFailingSince >\n STATE_POINTER_EPOCH_MS\n ) {\n console.warn(\n `[BatchWriteCoordinator] Refresh: partition ${partition} state-pointer heartbeat failing persistently; demoting before the resume pointer ages out of the takeover lookup.`,\n )\n this.signalLeaseLost()\n await this.lock(async () => {\n if (this.disposed || this.partitionLease !== lease) return\n this.finalizeDemote()\n })\n }\n }\n }\n }\n\n /**\n * Voluntarily give up the held partition after it has gone idle. Keeps the\n * coordinator able to re-acquire on the next write (re-arm via `ensureLease`).\n * MUST run under the write lock (see the idle-yield note in `refreshTick`).\n */\n private async yieldIdleLease(\n lease: PartitionLease,\n requestId?: string,\n ): Promise<void> {\n this.leaseEpoch++\n const yieldedPartition = lease.currentPartition\n if (this.partitionRefreshTimer !== undefined) {\n clearInterval(this.partitionRefreshTimer)\n this.partitionRefreshTimer = undefined\n }\n const localCounter = this.deps.stamper.getLocalCounter()\n if (localCounter !== undefined) {\n try {\n // Best-effort: on a Swarm-write failure the slot still lapses via the\n // TTL within LEASE_TTL_MS, so peers recover either way.\n await lease.release(localCounter)\n } catch (err) {\n console.warn(\n \"[BatchWriteCoordinator] Idle partition-lease release failed:\",\n err,\n )\n }\n }\n this.deps.stamper.unbindPartition()\n this.partitionLease = undefined\n this.readOnly = false\n this.lastLeaseValidatedAt = 0\n this.deps.writeLeaseCache?.(undefined)\n this.emitLeaseChange()\n // `ui/tests/three-devices.test.ts` reads this line as \"a yield happened\"\n // (a teardown release logs nothing); reword it there too.\n console.info(\n `[BatchWriteCoordinator] Released idle partition ${yieldedPartition ?? \"?\"}; will re-acquire on next write.`,\n )\n // `teardown()` can land during the release write above; it has already\n // announced this partition itself, and a second `lease-released` would\n // only burn a waiter's one re-scan window against a slot it just took.\n if (yieldedPartition !== undefined && !this.disposed) {\n this.deps.onLeaseReleased?.(yieldedPartition, requestId)\n }\n }\n\n /**\n * Wake a slot-waiting acquire immediately (a peer announced a release over\n * the account bus). No-op outside slot-wait; the woken loop still claims\n * through the lock SOCs, so a spurious wake costs one extra read round.\n */\n notifySlotMaybeFree(): void {\n const state = this.slotWait\n if (!state) return\n if (!state.rescanWindowUsed) {\n state.rescanWindowUsed = true\n state.maybeFree = true\n state.rescansLeft = ANNOUNCED_RESCAN_ROUNDS\n }\n // A later announcement still cuts the current sleep short; it just buys\n // no further rounds (see `SlotWaitState.rescanWindowUsed`).\n state.wake?.()\n }\n\n /**\n * Whether a peer's `lease-request` would presently be answered — a partition\n * held, nothing in flight, and idle for `PEER_YIELD_MIN_IDLE_MS`. The proxy\n * checks it before taking a rank in the yield election, so a holder mid-burst\n * doesn't occupy the front of the order only to decline in silence.\n *\n * Advisory: `yieldForPeer` re-checks under the write lock, where an upload\n * that slipped in between can still turn the answer into a no-op.\n */\n get canYieldForPeer(): boolean {\n if (this.disposed) return false\n if (this.partitionLease?.currentPartition === undefined) return false\n if (this.activeUploadCount !== 0) return false\n return Date.now() - this.lastLeaseActivityAt >= PEER_YIELD_MIN_IDLE_MS\n }\n\n /**\n * A waiting peer asked for a slot (bus `lease-request`): release the held\n * partition NOW if it is safe — nothing in flight and no write within\n * `PEER_YIELD_MIN_IDLE_MS`. Runs the exact idle-yield path (under the write\n * lock, with the same re-checks), so the Swarm release protocol and counter\n * flushes are identical to a timer yield. Returns the released partition,\n * or undefined when nothing was (or could safely be) yielded; the release\n * is announced through `onLeaseReleased` with the `requestId` it answers.\n */\n async yieldForPeer(requestId?: string): Promise<number | undefined> {\n if (!this.canYieldForPeer) return undefined\n const lease = this.partitionLease\n const partition = lease?.currentPartition\n if (!lease || partition === undefined) return undefined\n let released: number | undefined\n await this.lock(async () => {\n if (this.disposed || this.partitionLease !== lease) return\n if (this.activeUploadCount !== 0) return\n if (Date.now() - this.lastLeaseActivityAt < PEER_YIELD_MIN_IDLE_MS) {\n return\n }\n await this.yieldIdleLease(lease, requestId)\n released = partition\n })\n return released\n }\n\n /**\n * Stop lease background work and drop the in-memory lease + cache. Used when\n * the acquire path times out. A still-valid cached lease is re-adopted before\n * that path runs, so reaching here means there is no live lease to preserve.\n */\n private pauseLeaseBackgroundWork(): void {\n this.leaseEpoch++\n if (this.partitionRefreshTimer !== undefined) {\n clearInterval(this.partitionRefreshTimer)\n this.partitionRefreshTimer = undefined\n }\n this.partitionLease = undefined\n this.readOnly = false\n this.deps.writeLeaseCache?.(undefined)\n }\n\n // ---- helpers ------------------------------------------------------------\n\n private async buildStamperTarget(\n opts?: WithWriteOptions,\n ): Promise<UploadTarget> {\n const workerPool = opts?.useWorkers\n ? await this.deps.getWorkerPool?.(opts.workerCount)\n : undefined\n return {\n mode: \"stamper\",\n bee: this.deps.bee,\n stamper: this.deps.stamper,\n workerPool,\n }\n }\n\n /** Take the cross-tab Web Lock, run `op`, then flush stamper state. */\n private async lockAndFlush<T>(op: () => Promise<T>): Promise<T> {\n return withBatchWriteLock(this.deps.batchId, async () => {\n try {\n return await op()\n } finally {\n await this.deps.flushStamperState?.()\n }\n })\n }\n\n /** Take the cross-tab Web Lock without the stamper flush (lease mutations). */\n private lock<T>(op: () => Promise<T>): Promise<T> {\n return withBatchWriteLock(this.deps.batchId, op)\n }\n\n /** Post-acquisition guard: a multi-device account must hold a partition. */\n private ensureHeldForUpload(): void {\n if (this.deps.partitionCount <= 1) return\n if (this.currentPartition === undefined) {\n throw new Error(\n \"Uploads are unavailable: the postage batch is fully leased by other devices. \" +\n \"Sign out of another device or wait for its lease to expire.\",\n )\n }\n }\n\n private emitLeaseChange(): void {\n this.deps.onLeaseChange?.({\n currentPartition: this.currentPartition,\n isReadOnly: this.readOnly,\n })\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Epoch-based Feed Indexing\n *\n * Implements time-based indexing using a binary tree structure where each epoch\n * represents a time range with start time and level (0-32).\n *\n * Based on the Swarm Book of Feeds and Bee's epochs implementation.\n */\n\nimport { Binary } from \"cafe-utility\"\n\n/** Maximum epoch level (2^32 seconds ≈ 136 years) */\nexport const MAX_LEVEL = 32\n\n/**\n * Epoch interface - represents a time slot in the epoch tree\n */\nexport interface Epoch {\n start: bigint // Unix timestamp (seconds)\n level: number // 0-32, where length = 2^level\n}\n\n/**\n * EpochIndex class - implements epoch-based feed indexing\n *\n * Each epoch represents a time range:\n * - start: beginning timestamp of the epoch\n * - level: determines the epoch's time span (2^level seconds)\n *\n * Epochs form a binary tree structure for efficient sparse updates.\n */\nexport class EpochIndex implements Epoch {\n constructor(\n public readonly start: bigint,\n public readonly level: number,\n ) {\n if (level < 0 || level > MAX_LEVEL) {\n throw new Error(\n `Epoch level must be between 0 and ${MAX_LEVEL}, got ${level}`,\n )\n }\n }\n\n /**\n * Marshal epoch to binary format for hashing\n * Returns Keccak256 hash of start (8 bytes big-endian) + level (1 byte)\n */\n async marshalBinary(): Promise<Uint8Array> {\n const buffer = new Uint8Array(9)\n const view = new DataView(buffer.buffer)\n\n // Write start as 8-byte big-endian\n view.setBigUint64(0, this.start, false) // false = big-endian\n\n // Write level as 1 byte\n buffer[8] = this.level\n\n return Binary.keccak256(buffer)\n }\n\n /**\n * Calculate the next epoch for a new update\n * @param last - Timestamp of last update\n * @param at - Timestamp of new update\n */\n next(last: bigint, at: bigint): EpochIndex {\n if (this.start + this.length() > at) {\n return this.childAt(at)\n }\n return lca(at, last).childAt(at)\n }\n\n /**\n * Calculate epoch length in seconds (2^level)\n */\n length(): bigint {\n return 1n << BigInt(this.level)\n }\n\n /**\n * Get parent epoch\n * UNSAFE: Do not call on top-level epoch (level 32)\n */\n parent(): EpochIndex {\n const length = this.length() << 1n\n const start = (this.start / length) * length\n return new EpochIndex(start, this.level + 1)\n }\n\n /**\n * Get left sibling epoch\n * UNSAFE: Do not call on left sibling (when start is aligned to 2*length)\n */\n left(): EpochIndex {\n return new EpochIndex(this.start - this.length(), this.level)\n }\n\n /**\n * Get child epoch containing timestamp `at`\n * UNSAFE: Do not call with `at` outside this epoch's range\n */\n childAt(at: bigint): EpochIndex {\n const newLevel = this.level - 1\n const length = 1n << BigInt(newLevel)\n let start = this.start\n\n // If `at` falls in right half, adjust start\n if ((at & length) > 0n) {\n start |= length\n }\n\n return new EpochIndex(start, newLevel)\n }\n\n /**\n * Check if this epoch is a left child of its parent\n */\n isLeft(): boolean {\n return (this.start & this.length()) === 0n\n }\n\n /**\n * String representation: \"start/level\"\n */\n toString(): string {\n return `${this.start}/${this.level}`\n }\n}\n\n/**\n * Calculate Lowest Common Ancestor epoch for two timestamps\n *\n * The LCA is the smallest epoch that contains both `at` and `after`.\n * This is used to find the optimal starting point for feed lookups.\n *\n * @param at - Target timestamp\n * @param after - Reference timestamp (0 if no previous update)\n * @returns LCA epoch\n */\nexport function lca(at: bigint, after: bigint): EpochIndex {\n // If no previous update, start from top-level epoch\n if (after === 0n) {\n return new EpochIndex(0n, MAX_LEVEL)\n }\n\n const diff = at - after\n let length = 1n\n let level = 0\n\n // Find the smallest epoch level where both timestamps fall in different epochs\n // OR at and after are in the same epoch\n while (\n level < MAX_LEVEL &&\n (length < diff || at / length !== after / length)\n ) {\n length <<= 1n\n level++\n }\n\n // Calculate start of the epoch containing both timestamps\n const start = (after / length) * length\n\n return new EpochIndex(start, level)\n}\n\n/**\n * Helper function to get next epoch for update, handling null previous epoch\n * @param prevEpoch - Previous epoch (null if first update)\n * @param last - Timestamp of last update\n * @param at - Timestamp of new update\n */\nexport function next(\n prevEpoch: EpochIndex | undefined,\n last: bigint,\n at: bigint,\n): EpochIndex {\n if (!prevEpoch) {\n return new EpochIndex(0n, MAX_LEVEL)\n }\n return prevEpoch.next(last, at)\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Async Epoch Feed Finder (Concurrent)\n *\n * Concurrent implementation for finding feed updates at specific timestamps\n * using epoch-based indexing with parallel chunk fetching.\n */\n\nimport { Binary } from \"cafe-utility\"\nimport type { BeeRequestOptions } from \"@ethersphere/bee-js\"\nimport { EthAddress, Reference, Topic } from \"@ethersphere/bee-js\"\nimport type { ChunkDownloader } from \"../../download-data\"\nimport { EpochIndex, MAX_LEVEL } from \"./epoch\"\nimport type { EpochFinder, EpochLookupResult } from \"./types\"\nimport { downloadEncryptedSOC } from \"../../download-data\"\nimport { findPreviousLeaf } from \"./utils\"\n\nconst EPOCH_LOOKUP_TIMEOUT_MS = 2000\n\n/**\n * Async concurrent finder for epoch-based feeds\n *\n * Launches parallel chunk fetches along the epoch tree path\n * to find the feed update valid at a specific timestamp.\n *\n * Implements the EpochFinder interface.\n */\nexport class AsyncEpochFinder implements EpochFinder {\n constructor(\n private readonly bee: ChunkDownloader,\n private readonly topic: Topic,\n private readonly owner: EthAddress,\n private readonly encryptionKey?: Uint8Array,\n ) {}\n\n /**\n * Find the feed update valid at time `at`\n * @param at - Target unix timestamp (seconds)\n * @param after - Hint of latest known update timestamp (0 if unknown)\n * @returns 32-byte Swarm reference, or undefined if no update found\n */\n async findAt(\n at: bigint,\n after: bigint = 0n,\n ): Promise<Uint8Array | undefined> {\n // Fast path: exact timestamp updates are written at level-0 and should be\n // retrievable without traversing potentially poisoned ancestors. It runs\n // CONCURRENTLY with the traversal (#400): on a \"latest as of now\" read\n // (every account fold) the exact probe virtually never hits, and awaiting\n // it first serialised a full absent-chunk timeout before the traversal even\n // started. Both are awaited — not raced — because an exact hit must win\n // even when the traversal returns nothing (that IS the poisoned-ancestor\n // recovery this fast path exists for).\n const exactProbe = this.getEpochChunk(at, new EpochIndex(at, 0)).catch(\n () => undefined,\n )\n const [exact, traversed] = await Promise.all([\n exactProbe,\n this.findAtEpoch(at, new EpochIndex(0n, MAX_LEVEL), undefined),\n ])\n if (exact) {\n return exact\n }\n if (traversed) {\n return traversed\n }\n\n // Recovery fallback for poisoned-ancestor histories: only enable bounded\n // leaf back-scan when root epoch exists but is invalid for `at`.\n try {\n const rootProbe = await this.getEpochChunk(\n at,\n new EpochIndex(0n, MAX_LEVEL),\n )\n if (rootProbe === undefined) {\n return this.findPreviousLeaf(at, after)\n }\n } catch {\n // Root missing - no evidence of poisoned ancestors.\n }\n\n return undefined\n }\n\n /**\n * Recursively find update at epoch, with parallel fetching\n *\n * @param at - Target timestamp\n * @param epoch - Current epoch to check\n * @param currentBest - Best result found so far\n * @returns Reference if found, undefined otherwise\n */\n private async findAtEpoch(\n at: bigint,\n epoch: EpochIndex,\n currentBest: Uint8Array | undefined,\n ): Promise<Uint8Array | undefined> {\n // Try to get chunk at this epoch.\n // getEpochChunk throws when chunk is missing, returns undefined when\n // chunk exists but timestamp is invalid for `at`.\n let chunk: Uint8Array | undefined\n try {\n chunk = await this.getEpochChunk(at, epoch)\n } catch {\n // Chunk missing at this epoch.\n if (epoch.isLeft()) {\n return currentBest\n }\n return this.findAtEpoch(epoch.start - 1n, epoch.left(), currentBest)\n }\n\n // If chunk found and valid\n if (chunk) {\n // If at finest resolution, this is our answer\n if (epoch.level === 0) {\n return chunk\n }\n\n // Continue to finer resolution\n return this.findAtEpoch(at, epoch.childAt(at), chunk)\n }\n\n // Chunk exists but timestamp invalid.\n // Keep descending towards the target epoch first, because finer epochs\n // may still contain a valid update.\n if (epoch.level > 0) {\n const down = await this.findAtEpoch(at, epoch.childAt(at), currentBest)\n if (down) {\n return down\n }\n }\n\n if (epoch.isLeft()) {\n // Left child - return best we have so far\n return currentBest\n }\n\n // Right child - need to search left sibling branch\n return this.findAtEpoch(epoch.start - 1n, epoch.left(), currentBest)\n }\n /**\n * Fetch chunk for a specific epoch\n *\n * @param at - Target timestamp for validation\n * @param epoch - Epoch to fetch\n * @returns Chunk payload\n * @throws Error if chunk not found\n */\n private async getEpochChunk(\n at: bigint,\n epoch: EpochIndex,\n ): Promise<Uint8Array | undefined> {\n const result = await this.getEpochChunkWithMetadata(at, epoch)\n return result?.reference\n }\n\n /**\n * Find the feed update valid at time `at` with full metadata\n * Used by updater to calculate next epoch when no hints provided\n *\n * @param at - Target unix timestamp (seconds)\n * @returns EpochLookupResult with reference, epoch, and timestamp, or undefined if no update found\n */\n async findAtWithMetadata(at: bigint): Promise<EpochLookupResult | undefined> {\n // Fast path: exact timestamp updates are written at level-0\n const exactEpoch = new EpochIndex(at, 0)\n try {\n const exact = await this.getEpochChunkWithMetadata(at, exactEpoch)\n if (exact) {\n return {\n reference: exact.reference,\n epoch: exactEpoch,\n timestamp: exact.timestamp,\n }\n }\n } catch {\n // Ignore and fall back to tree traversal\n }\n\n // Start from top epoch and traverse down, tracking found epoch\n return this.findAtEpochWithMetadata(\n at,\n new EpochIndex(0n, MAX_LEVEL),\n undefined,\n )\n }\n\n /**\n * Recursively find update at epoch with full metadata tracking\n *\n * @param at - Target timestamp\n * @param epoch - Current epoch to check\n * @param currentBest - Best result found so far\n * @returns EpochLookupResult if found, undefined otherwise\n */\n private async findAtEpochWithMetadata(\n at: bigint,\n epoch: EpochIndex,\n currentBest: EpochLookupResult | undefined,\n ): Promise<EpochLookupResult | undefined> {\n // Try to get chunk at this epoch\n let chunkData: { reference: Uint8Array; timestamp: bigint } | undefined\n try {\n chunkData = await this.getEpochChunkWithMetadata(at, epoch)\n } catch {\n // Chunk missing at this epoch\n if (epoch.isLeft()) {\n return currentBest\n }\n return this.findAtEpochWithMetadata(\n epoch.start - 1n,\n epoch.left(),\n currentBest,\n )\n }\n\n // If chunk found and valid\n if (chunkData) {\n const result: EpochLookupResult = {\n reference: chunkData.reference,\n epoch,\n timestamp: chunkData.timestamp,\n }\n\n // If at finest resolution, this is our answer\n if (epoch.level === 0) {\n return result\n }\n\n // Continue to finer resolution\n return this.findAtEpochWithMetadata(at, epoch.childAt(at), result)\n }\n\n // Chunk exists but timestamp invalid\n // Keep descending towards the target epoch first\n if (epoch.level > 0) {\n const down = await this.findAtEpochWithMetadata(\n at,\n epoch.childAt(at),\n currentBest,\n )\n if (down) {\n return down\n }\n }\n\n if (epoch.isLeft()) {\n return currentBest\n }\n\n // Right child - need to search left sibling branch\n return this.findAtEpochWithMetadata(\n epoch.start - 1n,\n epoch.left(),\n currentBest,\n )\n }\n\n /**\n * Fetch chunk for a specific epoch and return full metadata\n *\n * @param at - Target timestamp for validation\n * @param epoch - Epoch to fetch\n * @returns Object with reference and timestamp, or undefined if timestamp > at\n * @throws Error if chunk not found\n */\n private async getEpochChunkWithMetadata(\n at: bigint,\n epoch: EpochIndex,\n ): Promise<{ reference: Uint8Array; timestamp: bigint } | undefined> {\n const requestOptions: BeeRequestOptions = {\n timeout: EPOCH_LOOKUP_TIMEOUT_MS,\n }\n // Calculate epoch identifier: Keccak256(topic || Keccak256(start || level))\n const epochHash = await epoch.marshalBinary()\n const identifier = Binary.keccak256(\n Binary.concatBytes(this.topic.toUint8Array(), epochHash),\n )\n\n let payload: Uint8Array\n if (this.encryptionKey) {\n const soc = await downloadEncryptedSOC(\n this.bee,\n this.owner,\n identifier,\n this.encryptionKey,\n requestOptions,\n )\n payload = soc.payload\n } else {\n // Calculate chunk address: Keccak256(identifier || owner)\n const address = new Reference(\n Binary.keccak256(\n Binary.concatBytes(identifier, this.owner.toUint8Array()),\n ),\n )\n\n // Download chunk\n const chunkData = await this.bee.chunk.download(\n address.toHex(),\n undefined,\n requestOptions,\n )\n\n // Extract payload from SOC (Single Owner Chunk)\n const IDENTIFIER_SIZE = 32\n const SIGNATURE_SIZE = 65\n const SPAN_SIZE = 8\n const SOC_HEADER_SIZE = IDENTIFIER_SIZE + SIGNATURE_SIZE\n\n // Read span to get payload length\n const spanStart = SOC_HEADER_SIZE\n const span = chunkData.slice(spanStart, spanStart + SPAN_SIZE)\n const spanView = new DataView(\n span.buffer,\n span.byteOffset,\n span.byteLength,\n )\n const payloadLength = Number(spanView.getBigUint64(0, true)) // little-endian\n\n // Extract full payload (timestamp + reference)\n const payloadStart = spanStart + SPAN_SIZE\n payload = chunkData.slice(payloadStart, payloadStart + payloadLength)\n }\n\n // Detect payload format based on length:\n // - 40 bytes: timestamp(8) + reference(32) - from /soc endpoint\n // - 48 bytes: span(8) + timestamp(8) + reference(32) - from /chunks with v1 format\n // - 72 bytes: timestamp(8) + encrypted_reference(64) - from /soc endpoint\n // - 80 bytes: span(8) + timestamp(8) + encrypted_reference(64) - from /chunks with v1 format\n const VALID_PAYLOAD_LENGTHS = [40, 48, 72, 80]\n if (!VALID_PAYLOAD_LENGTHS.includes(payload.length)) {\n console.warn(\n `Unexpected feed payload length: ${payload.length}. Expected 40, 48, 72, or 80 bytes.`,\n )\n return undefined\n }\n\n const TIMESTAMP_SIZE = 8\n const hasSpanPrefix = payload.length === 48 || payload.length === 80\n\n const timestampOffset = hasSpanPrefix ? 8 : 0\n const timestampBytes = payload.slice(\n timestampOffset,\n timestampOffset + TIMESTAMP_SIZE,\n )\n const timestampView = new DataView(\n timestampBytes.buffer,\n timestampBytes.byteOffset,\n timestampBytes.byteLength,\n )\n const timestamp = timestampView.getBigUint64(0, false) // big-endian\n\n // Validate timestamp - update must be at or before target time\n if (timestamp > at) {\n return undefined\n }\n\n // Return reference and timestamp (skip timestamp and optional span prefix)\n return {\n reference: payload.slice(timestampOffset + TIMESTAMP_SIZE),\n timestamp,\n }\n }\n\n private findPreviousLeaf(\n at: bigint,\n after: bigint,\n ): Promise<Uint8Array | undefined> {\n return findPreviousLeaf(at, after, (targetAt, epoch) =>\n this.getEpochChunk(targetAt, epoch),\n )\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Shared utilities for epoch feed finders\n */\n\nimport { EpochIndex } from \"./epoch\"\n\nconst MAX_LEAF_BACKSCAN = 128n\n\n/**\n * Type for the chunk fetcher callback used by findPreviousLeaf.\n * This allows both sync and async finders to share the same logic.\n */\nexport type EpochChunkFetcher = (\n at: bigint,\n epoch: EpochIndex,\n) => Promise<Uint8Array | undefined>\n\n/**\n * Find a previous leaf chunk by scanning backwards from the target timestamp.\n *\n * This is a recovery fallback for poisoned-ancestor histories where the tree\n * traversal fails but valid leaf nodes exist.\n *\n * @param at - Target unix timestamp (seconds)\n * @param after - Hint of latest known update timestamp (0 if unknown)\n * @param getEpochChunk - Callback function to fetch epoch chunks\n * @returns 32-byte Swarm reference, or undefined if no update found\n */\nexport async function findPreviousLeaf(\n at: bigint,\n after: bigint,\n getEpochChunk: EpochChunkFetcher,\n): Promise<Uint8Array | undefined> {\n if (at === 0n) {\n return undefined\n }\n\n const minAt = after > 0n ? after : 0n\n const lowerBound =\n at > MAX_LEAF_BACKSCAN && at - MAX_LEAF_BACKSCAN > minAt\n ? at - MAX_LEAF_BACKSCAN\n : minAt\n\n let probe = at - 1n\n while (probe >= lowerBound) {\n try {\n const leaf = await getEpochChunk(at, new EpochIndex(probe, 0))\n if (leaf) {\n return leaf\n }\n } catch {\n // Missing leaf at probe timestamp.\n }\n\n if (probe === 0n) {\n break\n }\n probe--\n }\n\n return undefined\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Basic Epoch Feed Updater\n *\n * Handles writing updates to epoch-based feeds by calculating the next\n * epoch and uploading chunks.\n */\n\nimport { Binary } from \"cafe-utility\"\nimport { EthAddress, Topic, PrivateKey, Identifier } from \"@ethersphere/bee-js\"\nimport { EpochIndex, MAX_LEVEL } from \"./epoch\"\nimport { uploadSOC, isStamperTarget, type UploadTarget } from \"../../upload\"\nimport type { EpochUpdater, EpochUpdateHints, EpochUpdateResult } from \"./types\"\nimport { AsyncEpochFinder } from \"./async-finder\"\n\n/**\n * Build the SOC identifier for one epoch of a topic's epoch feed:\n * `keccak256(topic ‖ epoch.marshalBinary())`. Exported so readers can\n * reconstruct the SOC address of a feed entry they found (e.g. the\n * partition-state reader compensates for the slot the entry's own SOC\n * consumed).\n */\nexport async function makeEpochIdentifier(\n topic: Topic,\n epoch: EpochIndex,\n): Promise<Identifier> {\n const epochHash = await epoch.marshalBinary()\n return new Identifier(\n Binary.keccak256(Binary.concatBytes(topic.toUint8Array(), epochHash)),\n )\n}\n\n/**\n * 32-byte SOC chunk address of one epoch-feed entry:\n * `keccak256(identifier ‖ owner)` — same formula as every SOC address.\n */\nexport async function epochSocAddress(\n topic: Topic,\n epoch: EpochIndex,\n owner: EthAddress,\n): Promise<Uint8Array> {\n const identifier = await makeEpochIdentifier(topic, epoch)\n return Binary.keccak256(\n Binary.concatBytes(identifier.toUint8Array(), owner.toUint8Array()),\n )\n}\n\n/**\n * Basic updater for epoch-based feeds\n *\n * Implements Bee-compatible stateless epoch calculation.\n * Each update uses hints from the previous update to calculate the next epoch,\n * creating a proper epoch tree that Bee's finder can traverse.\n *\n * - First update (no hints): uses root epoch (level 32, start 0)\n * - Subsequent updates: calculates next epoch using the standard algorithm:\n * - If new timestamp within previous epoch's range: dive to child\n * - Else: jump to LCA(newTimestamp, prevTimestamp) and dive to child\n *\n * Implements the EpochUpdater interface.\n */\nexport class BasicEpochUpdater implements EpochUpdater {\n constructor(\n private readonly topic: Topic,\n private readonly signer: PrivateKey,\n ) {}\n\n /**\n * Update feed with a reference at given timestamp\n *\n * @param at - Unix timestamp for this update (seconds)\n * @param reference - 32 or 64-byte Swarm reference to store\n * @param target - Upload target (stamper or subsidised gateway)\n * @param encryptionKey - Optional encryption key for the update\n * @param hints - Optional hints from previous update for calculating epoch\n * @returns Update result with SOC address and epoch info for next update\n */\n async update(\n at: bigint,\n reference: Uint8Array,\n target: UploadTarget,\n encryptionKey?: Uint8Array,\n hints?: EpochUpdateHints,\n ): Promise<EpochUpdateResult> {\n if (reference.length !== 32 && reference.length !== 64) {\n throw new Error(\n `Reference must be 32 or 64 bytes, got ${reference.length}`,\n )\n }\n\n // Calculate epoch - auto-lookup if no hints provided\n // For subsidised mode, we can't auto-lookup (no bee instance), so hints are required\n const epoch = await this.calculateEpoch(at, hints, encryptionKey, target)\n\n // Upload the chunk with timestamp + reference\n const socAddress = await this.uploadEpochChunk(\n epoch,\n at,\n reference,\n target,\n encryptionKey,\n )\n\n // Return result with hints for next update\n return {\n socAddress,\n epoch: { start: epoch.start, level: epoch.level },\n timestamp: at,\n }\n }\n\n /**\n * Calculate the epoch for this update based on hints or auto-lookup\n *\n * When hints are provided, uses them for fast epoch calculation.\n * When no hints are provided and using stamper mode, looks up the current\n * feed state to calculate the next epoch, preventing overwrites.\n * In subsidised mode without hints, uses root epoch (first update).\n *\n * @param at - Timestamp for this update\n * @param hints - Optional hints from previous update\n * @param encryptionKey - Optional encryption key for looking up encrypted feeds\n * @param target - Upload target for determining if auto-lookup is possible\n * @returns Epoch to use for this update\n */\n private async calculateEpoch(\n at: bigint,\n hints?: EpochUpdateHints,\n encryptionKey?: Uint8Array,\n target?: UploadTarget,\n ): Promise<EpochIndex> {\n // Fast path: use provided hints\n if (hints?.lastEpoch && hints.lastTimestamp !== undefined) {\n const prevEpoch = new EpochIndex(\n hints.lastEpoch.start,\n hints.lastEpoch.level,\n )\n return prevEpoch.next(hints.lastTimestamp, at)\n }\n\n // Auto-lookup is only possible in stamper mode (we have a bee instance)\n // In subsidised mode without hints, use root epoch (first update)\n if (!target || !isStamperTarget(target)) {\n return new EpochIndex(0n, MAX_LEVEL)\n }\n\n // Slow path: lookup current state using stamper's bee instance\n const owner = this.signer.publicKey().address()\n const finder = new AsyncEpochFinder(\n target.bee,\n this.topic,\n owner,\n encryptionKey,\n )\n\n // Use findAtWithMetadata to get both reference AND epoch info\n const current = await finder.findAtWithMetadata(at)\n\n if (!current) {\n // First update ever - use root epoch\n return new EpochIndex(0n, MAX_LEVEL)\n }\n\n // Calculate next epoch based on found state\n return current.epoch.next(current.timestamp, at)\n }\n\n /**\n * Get the owner address (derived from signer)\n */\n getOwner(): EthAddress {\n return this.signer.publicKey().address()\n }\n\n /**\n * Upload a chunk for a specific epoch\n *\n * @param epoch - Epoch to upload to\n * @param at - Timestamp of this update\n * @param reference - 32 or 64-byte reference to store\n * @param target - Upload target (stamper or subsidised gateway)\n * @returns SOC chunk address for utilization tracking\n */\n private async uploadEpochChunk(\n epoch: EpochIndex,\n at: bigint,\n reference: Uint8Array,\n target: UploadTarget,\n encryptionKey?: Uint8Array,\n ): Promise<Uint8Array> {\n // Calculate epoch identifier: Keccak256(topic || Keccak256(start || level))\n const identifier = await makeEpochIdentifier(this.topic, epoch)\n\n // Timestamp: 8-byte big-endian\n const timestamp = new Uint8Array(8)\n const tsView = new DataView(timestamp.buffer)\n tsView.setBigUint64(0, at, false) // big-endian\n\n // Payload: timestamp + reference = 40 or 72 bytes\n // The upload function will wrap this in a CAC (adding span) to get 48 or 80 bytes\n // which is the v1 format Bee expects for /bzz/ compatibility\n const payload = Binary.concatBytes(timestamp, reference)\n\n const result = await uploadSOC(target, this.signer, identifier, payload, {\n encryptionKey,\n deferred: false, // deferred setting is NOT the cause of /bzz/ timeout\n })\n\n return result.socAddress\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Epoch-Based Feeds\n *\n * Time-based feed indexing using a binary tree structure for efficient\n * sparse updates across long time periods.\n */\n\n// Core exports\nexport { EpochIndex, lca, next, MAX_LEVEL, type Epoch } from \"./epoch\"\n\n// Interface exports\nexport type {\n EpochFinder,\n EpochUpdater,\n EpochFeedOptions,\n EpochFeedWriterOptions,\n EpochLookupResult,\n} from \"./types\"\n\n// Implementation exports\nexport { SyncEpochFinder } from \"./finder\"\nexport { AsyncEpochFinder } from \"./async-finder\"\nexport {\n BasicEpochUpdater,\n epochSocAddress,\n makeEpochIdentifier,\n} from \"./updater\"\n\n// Convenience factory functions\nimport { SyncEpochFinder } from \"./finder\"\nimport { AsyncEpochFinder } from \"./async-finder\"\nimport { BasicEpochUpdater } from \"./updater\"\nimport type {\n EpochFinder,\n EpochUpdater,\n EpochFeedOptions,\n EpochFeedWriterOptions,\n} from \"./types\"\n\n/**\n * Create a synchronous epoch feed finder (non-concurrent)\n *\n * @returns EpochFinder implementation\n */\nexport function createSyncEpochFinder(options: EpochFeedOptions): EpochFinder {\n return new SyncEpochFinder(options.bee, options.topic, options.owner)\n}\n\n/**\n * Create an async epoch feed finder (concurrent)\n *\n * @returns EpochFinder implementation\n */\nexport function createAsyncEpochFinder(options: EpochFeedOptions): EpochFinder {\n return new AsyncEpochFinder(\n options.bee,\n options.topic,\n options.owner,\n options.encryptionKey,\n )\n}\n\n/**\n * Create an epoch feed updater\n *\n * @returns EpochUpdater implementation\n */\nexport function createEpochUpdater(\n options: EpochFeedWriterOptions,\n): EpochUpdater {\n return new BasicEpochUpdater(options.topic, options.signer)\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Synchronous Sequential Feed Finder\n *\n * Linear scan implementation for finding the latest update index.\n * Mirrors the Go sequential finder behavior.\n */\n\nimport { Binary } from \"cafe-utility\"\nimport type { EthAddress, Topic, BeeRequestOptions } from \"@ethersphere/bee-js\"\nimport { Reference } from \"@ethersphere/bee-js\"\nimport type { ChunkDownloader } from \"../../download-data\"\nimport type { SequentialFinder, SequentialLookupResult } from \"./types\"\n\nfunction makeSequentialIdentifier(topic: Topic, index: bigint): Uint8Array {\n const indexBytes = Binary.numberToUint64(index, \"BE\")\n return Binary.keccak256(Binary.concatBytes(topic.toUint8Array(), indexBytes))\n}\n\nfunction makeSequentialAddress(\n identifier: Uint8Array,\n owner: EthAddress,\n): Reference {\n return new Reference(\n Binary.keccak256(Binary.concatBytes(identifier, owner.toUint8Array())),\n )\n}\n\nexport class SyncSequentialFinder implements SequentialFinder {\n constructor(\n private readonly bee: ChunkDownloader,\n private readonly topic: Topic,\n private readonly owner: EthAddress,\n ) {}\n\n async findAt(\n _at: bigint,\n _after: bigint = 0n,\n requestOptions?: BeeRequestOptions,\n ): Promise<SequentialLookupResult> {\n for (let index = 0n; ; index++) {\n const exists = await this.indexExists(index, requestOptions)\n if (!exists) {\n return {\n current: index > 0n ? index - 1n : undefined,\n next: index,\n }\n }\n }\n }\n\n private async indexExists(\n index: bigint,\n requestOptions?: BeeRequestOptions,\n ): Promise<boolean> {\n const identifier = makeSequentialIdentifier(this.topic, index)\n const address = makeSequentialAddress(identifier, this.owner)\n\n // Bee's /chunks endpoint returns 500 (\"read chunk failed\") for a genuinely\n // absent chunk — indistinguishable by status from a transient 500 — so a\n // single failed read can't tell \"free slot\" from \"blip on an existing entry\".\n // Retry once; conclude the slot is free only if BOTH reads fail. This narrows\n // the window where a transient failure lets an append clobber a peer's roster\n // entry. We can't rethrow on failure (absence has no distinct signal here), so\n // the scan must still terminate on a persistent miss.\n // ponytail: single retry — matches readPartitionSoc; add backoff/more attempts\n // only if real gateways prove one insufficient.\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n await this.bee.chunk.download(\n Binary.uint8ArrayToHex(address.toUint8Array()),\n undefined,\n requestOptions,\n )\n return true\n } catch {\n if (attempt === 0) continue\n return false\n }\n }\n return false\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Async Sequential Feed Finder\n *\n * Currently delegates to the sync finder to match Go behavior\n * while keeping an async interface.\n */\n\nimport type { EthAddress, Topic, BeeRequestOptions } from \"@ethersphere/bee-js\"\nimport type { ChunkDownloader } from \"../../download-data\"\nimport type { SequentialFinder, SequentialLookupResult } from \"./types\"\nimport { SyncSequentialFinder } from \"./finder\"\n\nexport class AsyncSequentialFinder implements SequentialFinder {\n private readonly syncFinder: SyncSequentialFinder\n\n constructor(bee: ChunkDownloader, topic: Topic, owner: EthAddress) {\n this.syncFinder = new SyncSequentialFinder(bee, topic, owner)\n }\n\n async findAt(\n at: bigint,\n after: bigint = 0n,\n requestOptions?: BeeRequestOptions,\n ): Promise<SequentialLookupResult> {\n return this.syncFinder.findAt(at, after, requestOptions)\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Basic Sequential Feed Updater\n *\n * Handles writing updates to sequential feeds by incrementing the index.\n */\n\nimport { Binary } from \"cafe-utility\"\nimport type { Bee, Stamper } from \"@ethersphere/bee-js\"\nimport { EthAddress, Topic, PrivateKey, Identifier } from \"@ethersphere/bee-js\"\nimport { uploadSOC, type UploadTarget } from \"../../upload\"\nimport type { SequentialUpdater } from \"./types\"\n\nexport class BasicSequentialUpdater implements SequentialUpdater {\n private nextIndex: bigint = 0n\n\n constructor(\n private readonly bee: Bee,\n private readonly topic: Topic,\n private readonly signer: PrivateKey,\n ) {}\n\n async update(\n payload: Uint8Array,\n stamper: Stamper,\n encryptionKey?: Uint8Array,\n ): Promise<Uint8Array> {\n const identifier = this.makeIdentifier(this.nextIndex)\n\n const target: UploadTarget = {\n mode: \"stamper\",\n bee: this.bee,\n stamper,\n }\n\n const result = await uploadSOC(target, this.signer, identifier, payload, {\n encryptionKey,\n deferred: false,\n })\n\n this.nextIndex += 1n\n\n return result.socAddress\n }\n\n getOwner(): EthAddress {\n return this.signer.publicKey().address()\n }\n\n getState(): { nextIndex: bigint } {\n return { nextIndex: this.nextIndex }\n }\n\n setState(state: { nextIndex: bigint }): void {\n this.nextIndex = state.nextIndex\n }\n\n reset(): void {\n this.nextIndex = 0n\n }\n\n private makeIdentifier(index: bigint): Identifier {\n const indexBytes = Binary.numberToUint64(index, \"BE\")\n const identifierBytes = Binary.keccak256(\n Binary.concatBytes(this.topic.toUint8Array(), indexBytes),\n )\n return new Identifier(identifierBytes)\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\nconst NOT_FOUND = 404\n\n/**\n * True only for a definitive \"chunk absent\" — an HTTP 404, as Bee's `/soc`\n * endpoint returns for a missing single-owner chunk (`BeeResponseError.status`).\n * A timeout / 5xx / network error is INCONCLUSIVE, not a confirmed miss.\n *\n * NOTE: Bee's `/chunks` endpoint returns **500** (\"read chunk failed\") for an\n * absent chunk, so this predicate is usable for the roster `/soc` existence read\n * but NOT for the `/chunks`-based feed finders — there, absence and a transient\n * server error are indistinguishable by status.\n */\nexport function isNotFoundError(error: unknown): boolean {\n if (typeof error === \"object\" && error !== null) {\n const status = (error as { status?: unknown }).status\n if (status === NOT_FOUND) return true\n // A real, non-404 numeric status (e.g. 500) is a server error, not a miss —\n // decide on it and never fall through to the message text.\n if (typeof status === \"number\") return false\n }\n const message = error instanceof Error ? error.message : String(error)\n return /404|not found/i.test(message) // fallback for message-only mocks/wrappers\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Device roster (Phase 3a hardening) — append-only discovery log.\n *\n * Replaces the single shared *mutable* registry doc, which a device could\n * **clobber** on a high-latency/caching gateway: it read-merge-overwrote the\n * whole doc, so a device that couldn't yet see a peer's just-written entry\n * dropped it. (See the gateway finding in the Phase 3a implementation log.)\n *\n * The roster is a **sequential append-only feed** (`swarm-id-roster-v1:<acct>`):\n * each device appends ONLY its own membership record (`Device`) at the next free\n * index. A device never overwrites a peer's slot, so concurrent announces can't\n * clobber an existing entry — the worst case is two devices racing the same\n * fresh index, where one append is lost and re-added on its next sync (existing\n * entries are untouched). Readers scan all entries and fold by `deviceId`\n * (reusing `mergeDevicesList`, so removals/resurrections converge by Phase 1\n * LWW). Account-level immutables (publicKey, createdAt, partitionCount) live on\n * the robust per-device state feed (`device-state.ts`), not here.\n *\n * Each entry is an encrypted blob (privacy parity with the old registry)\n * referenced by the sequential SOC, reusing `uploadData`/`downloadDataWithChunkAPI`.\n */\n\nimport {\n Bee,\n EthAddress,\n Identifier,\n PrivateKey,\n Reference,\n Topic,\n} from \"@ethersphere/bee-js\"\nimport { Binary } from \"cafe-utility\"\nimport { DeviceSchemaV1, type Device } from \"../schemas\"\nimport { hexToUint8Array } from \"../utils/hex\"\nimport {\n BasicSequentialUpdater,\n SyncSequentialFinder,\n} from \"../proxy/feeds/sequence\"\nimport { isStamperTarget, uploadData, type UploadTarget } from \"../proxy/upload\"\nimport { downloadDataWithChunkAPI } from \"../proxy/download-data\"\nimport { withTimeout } from \"../utils/promise\"\nimport { isNotFoundError } from \"../utils/bee-error\"\nimport { SYNC_READ_TIMEOUT_MS } from \"./timing-constants\"\nimport { mergeDevicesList } from \"./merge-snapshot\"\n\nexport const ROSTER_TOPIC_PREFIX = \"swarm-id-roster-v1\"\n\n// Safety cap on a roster scan so a corrupt feed can't loop forever. Far above\n// any realistic device × re-announce count for one account.\nconst MAX_ROSTER_SCAN = 256\n\n// How many roster indices to probe in parallel per round. The roster is small\n// and contiguous, so one window of this size usually resolves the whole scan in\n// a single round-trip instead of K+1 serial ones.\nconst ROSTER_SCAN_WINDOW = 16\n\n// How many indices to probe past a KNOWN roster length to detect new appends\n// (#400). Absent-slot probes are the expensive kind (Bee has no fast\n// authoritative 404), so once a scan has proven the roster reaches N we only\n// need a short lookahead — a transient 404 here can at worst delay discovering\n// a BRAND-NEW device until the next fold (minutes), never drop a known one.\n// Cold scans (no memo) keep the full ROSTER_SCAN_WINDOW stop margin.\nconst ROSTER_TAIL_PROBES = 2\n\n/**\n * Known roster length per `accountId:owner` — a monotonic LOWER bound learned\n * from previous scans/appends (#400). The roster is append-only, so a once-seen\n * length can never overshoot; it must also never shrink (a stale endpoint's\n * truncated read must not poison later scans). Mirrored to localStorage when\n * available so a page reload's first fold is warm too; in Node (tests, CLI) the\n * mirror self-disables and only the in-memory memo applies.\n */\nconst knownRosterLength = new Map<string, number>()\n\nconst ROSTER_LENGTH_STORAGE_PREFIX = \"swarm-id-roster-len\"\n\nfunction rosterMemoKey(accountId: string, owner: EthAddress): string {\n return `${accountId}:${owner.toHex()}`\n}\n\nfunction getKnownRosterLength(key: string): number {\n const cached = knownRosterLength.get(key)\n if (cached !== undefined) return cached\n if (typeof localStorage === \"undefined\") return 0\n let raw: string | null\n try {\n raw = localStorage.getItem(`${ROSTER_LENGTH_STORAGE_PREFIX}:${key}`)\n } catch {\n return 0\n }\n const parsed = raw === null ? 0 : Number.parseInt(raw, 10)\n const length =\n Number.isInteger(parsed) && parsed > 0\n ? Math.min(parsed, MAX_ROSTER_SCAN)\n : 0\n knownRosterLength.set(key, length)\n return length\n}\n\nfunction bumpKnownRosterLength(key: string, length: number): void {\n const capped = Math.min(length, MAX_ROSTER_SCAN)\n if (capped <= getKnownRosterLength(key)) return\n knownRosterLength.set(key, capped)\n if (typeof localStorage === \"undefined\") return\n try {\n localStorage.setItem(`${ROSTER_LENGTH_STORAGE_PREFIX}:${key}`, `${capped}`)\n } catch {\n // Quota/permission failure only costs the cross-reload warm-up.\n }\n}\n\n/** Test-only: forget every memoised roster length (in-memory module state). */\nexport function resetRosterScanCache(): void {\n knownRosterLength.clear()\n}\n\n// Per-read cap so an empty/unreachable slot on a slow gateway fails fast instead\n// of hanging on peer-exhaustion. A present slot retrieves well under this.\n// Matches the epoch finder's bound. An inconclusive read (timeout / 5xx /\n// network) is NOT a confirmed empty slot: `readRosterEntry` surfaces it as\n// `ROSTER_READ_INCONCLUSIVE` so `readRoster` retries it once at the stop boundary\n// rather than mistaking it for end-of-feed and truncating the roster.\nconst ROSTER_READ_TIMEOUT_MS = SYNC_READ_TIMEOUT_MS\n\n/**\n * Sentinel for a roster slot whose read was INCONCLUSIVE — a timeout, a 5xx, or\n * a network error, as opposed to a clean 404 (`undefined`, a genuinely empty\n * slot). An inconclusive read may hide a live device the gateway just couldn't\n * return, so the scan must not treat it as end-of-feed. Only Bee's `/soc` 404\n * confirms a slot is empty.\n */\nconst ROSTER_READ_INCONCLUSIVE = Symbol(\"roster-read-inconclusive\")\ntype RosterReadResult = Device | undefined | typeof ROSTER_READ_INCONCLUSIVE\n\n/** Thrown by {@link readRoster} when it cannot distinguish an empty roster from\n * an outage (index 0 stays inconclusive after the retry, nothing folded).\n * Callers must NOT treat this as \"no devices\" — see {@link ensureInRoster}. */\nexport class RosterScanInconclusiveError extends Error {\n constructor() {\n super(\"roster scan inconclusive: cannot confirm the roster is empty\")\n this.name = \"RosterScanInconclusiveError\"\n }\n}\n\nexport function rosterTopic(accountId: string): Topic {\n return Topic.fromString(`${ROSTER_TOPIC_PREFIX}:${accountId}`)\n}\n\nexport function rosterIdentifier(topic: Topic, index: bigint): Identifier {\n const indexBytes = Binary.numberToUint64(index, \"BE\")\n return new Identifier(\n Binary.keccak256(Binary.concatBytes(topic.toUint8Array(), indexBytes)),\n )\n}\n\nconst HTTP_NOT_FOUND = 404\n\n/**\n * Ask Bee's `GET /soc/{owner}/{id}` (raw request — bee-js has no GET wrapper)\n * whether a roster slot is present. Used only after the `/chunks`-based read\n * failed ambiguously: a small cluster answers an ABSENT chunk on `/chunks` with\n * a persistent 500, never 404 (#457), so `/soc` is the only endpoint whose 404\n * is authoritative there. Returns the SOC payload when present, `undefined` for\n * a validated absent slot, or {@link ROSTER_READ_INCONCLUSIVE}.\n *\n * Only Bee's OWN 404 (a JSON body with `code: 404`) confirms absence: an\n * endpoint that doesn't route `GET /soc` at all (the public gateway) 404s every\n * probe with a plain-text \"Not Found\" — trusting that would read a mid-outage\n * roster as empty and truncate it (device clobber).\n */\nasync function probeSocSlot(opts: {\n bee: Bee\n owner: EthAddress\n identifier: Identifier\n}): Promise<Uint8Array | undefined | typeof ROSTER_READ_INCONCLUSIVE> {\n const url = `${opts.bee.url}/soc/${opts.owner.toHex()}/${opts.identifier.toHex()}`\n try {\n const response = await withTimeout(\n fetch(url),\n ROSTER_READ_TIMEOUT_MS,\n \"roster /soc probe timed out\",\n )\n if (response.ok) return new Uint8Array(await response.arrayBuffer())\n if (response.status !== HTTP_NOT_FOUND) return ROSTER_READ_INCONCLUSIVE\n const body = (await response.json()) as { code?: unknown }\n return body.code === HTTP_NOT_FOUND ? undefined : ROSTER_READ_INCONCLUSIVE\n } catch {\n // Network failure, timeout, or a non-JSON 404 body — all inconclusive.\n return ROSTER_READ_INCONCLUSIVE\n }\n}\n\n/**\n * Read one roster entry (the device record) at `index`. Returns the `Device`\n * when present, `undefined` for a clean 404 / garbage blob (a confirmed-empty\n * slot — the scan's stop signal), or {@link ROSTER_READ_INCONCLUSIVE} when the\n * read was inconclusive (timeout / 5xx / network — the slot may hold a live\n * device the gateway couldn't return, so the scan must not stop on it).\n */\nasync function readRosterEntry(opts: {\n bee: Bee\n topic: Topic\n owner: EthAddress\n index: bigint\n}): Promise<RosterReadResult> {\n const identifier = rosterIdentifier(opts.topic, opts.index)\n let refBytes: Uint8Array\n try {\n const soc = await withTimeout(\n opts.bee.soc.makeReader(opts.owner).download(identifier),\n ROSTER_READ_TIMEOUT_MS,\n \"roster read timed out\",\n )\n refBytes = soc.payload.toUint8Array()\n } catch (error) {\n // A clean 404 from the `/chunks` read is a genuinely-absent slot\n // (end-of-feed). Anything else is ambiguous THERE — on a small cluster an\n // absent chunk 500s persistently (#457) — so ask `GET /soc`, whose 404 is\n // authoritative, before surfacing the read as inconclusive.\n if (isNotFoundError(error)) return undefined\n const probe = await probeSocSlot({\n bee: opts.bee,\n owner: opts.owner,\n identifier,\n })\n if (probe instanceof Uint8Array) {\n refBytes = probe\n } else {\n return probe\n }\n }\n // Split the blob download from the parse: a download failure is a read outcome\n // (404 = empty, else = inconclusive), whereas a parse failure is a genuinely\n // garbage blob (the SOC resolved) — a skippable hole `readRoster` folds around.\n let data: Uint8Array\n try {\n data = await withTimeout(\n downloadDataWithChunkAPI(opts.bee, new Reference(refBytes).toHex()),\n ROSTER_READ_TIMEOUT_MS,\n \"roster read timed out\",\n )\n } catch (error) {\n return isNotFoundError(error) ? undefined : ROSTER_READ_INCONCLUSIVE\n }\n try {\n return DeviceSchemaV1.parse(JSON.parse(new TextDecoder().decode(data)))\n } catch {\n return undefined\n }\n}\n\n/**\n * Scan the roster and fold to the latest record per `deviceId` (Phase 1\n * `mergeDevicesList`: removals/resurrections converge by LWW). Returns `[]` when\n * the feed is empty (nothing published yet).\n */\nexport async function readRoster(opts: {\n bee: Bee\n accountId: string\n owner: EthAddress\n}): Promise<Device[]> {\n const topic = rosterTopic(opts.accountId)\n const memoKey = rosterMemoKey(opts.accountId, opts.owner)\n const known = getKnownRosterLength(memoKey)\n let devices: Device[] = []\n // One past the highest index seen present in THIS scan — feeds the memo.\n let scanEnd = 0\n // Scan in parallel windows. Appends are contiguous (the same-index race\n // re-adds at the new tail, never leaving a permanent hole), so no real entry\n // lives past the true end. A hole *inside* an otherwise-present window is\n // therefore a transient read failure — the exact caching-gateway failure this\n // roster exists to survive — so we skip it and keep folding later entries\n // rather than truncating every device after it. Only a window with no present\n // entry means we may have scanned past the end of the feed.\n //\n // Window sizing (#400): the memoised known range [0..known) is read as ONE\n // parallel window (all expected hits — cheap), and past it only\n // ROSTER_TAIL_PROBES slots are probed to detect new appends. With no memo the\n // full ROSTER_SCAN_WINDOW stop margin applies, so a cold scan (restore,\n // first-ever fold) is exactly as conservative as before.\n for (let base = 0; base < MAX_ROSTER_SCAN; ) {\n const width =\n base < known\n ? known - base\n : Math.min(\n known > 0 ? ROSTER_TAIL_PROBES : ROSTER_SCAN_WINDOW,\n MAX_ROSTER_SCAN - base,\n )\n const indices = Array.from({ length: width }, (_, i) => BigInt(base + i))\n let entries = await Promise.all(\n indices.map((index) =>\n readRosterEntry({ bee: opts.bee, topic, owner: opts.owner, index }),\n ),\n )\n // A window with no present entry is a stop candidate — but an inconclusive\n // read (timeout / 5xx) may have hidden a live device. Retry just the\n // inconclusive slots ONCE before concluding end-of-feed, so a transiently\n // flaky tail window doesn't truncate the roster. Bounded to one extra partial\n // read; a still-inconclusive slot reconciles on the next sync.\n if (\n !entries.some(isDevice) &&\n entries.some((entry) => entry === ROSTER_READ_INCONCLUSIVE)\n ) {\n entries = await Promise.all(\n indices.map((index, i) =>\n entries[i] === ROSTER_READ_INCONCLUSIVE\n ? readRosterEntry({\n bee: opts.bee,\n topic,\n owner: opts.owner,\n index,\n })\n : entries[i],\n ),\n )\n }\n entries.forEach((entry, i) => {\n if (isDevice(entry)) {\n devices = mergeDevicesList(devices, [entry])\n scanEnd = base + i + 1\n }\n })\n // Stop on a window with no present entry. If that window is still\n // inconclusive after the retry AND we have folded nothing, we cannot tell an\n // empty roster from an outage — surface it rather than lie \"[] = no devices\",\n // which a caller could act on and clobber. A truncated but non-empty read is\n // the existing bounded behaviour (reconciles next sync).\n if (!entries.some(isDevice)) {\n if (\n devices.length === 0 &&\n entries.some((entry) => entry === ROSTER_READ_INCONCLUSIVE)\n ) {\n throw new RosterScanInconclusiveError()\n }\n break\n }\n base += width\n }\n if (devices.length > 0) bumpKnownRosterLength(memoKey, scanEnd)\n return devices\n}\n\n/** Narrow a roster read to a present device (not a miss or an inconclusive read). */\nfunction isDevice(entry: RosterReadResult): entry is Device {\n return entry !== undefined && entry !== ROSTER_READ_INCONCLUSIVE\n}\n\n/** Append one device record at the next free index. Each device writes only its own. */\nasync function appendToRoster(opts: {\n bee: Bee\n accountKey: PrivateKey\n owner: EthAddress\n encryptionKey: string\n accountId: string\n device: Device\n target: UploadTarget\n}): Promise<void> {\n const topic = rosterTopic(opts.accountId)\n const { next } = await new SyncSequentialFinder(\n opts.bee,\n topic,\n opts.owner,\n ).findAt(0n)\n\n if (!isStamperTarget(opts.target)) return\n const blob = new TextEncoder().encode(JSON.stringify(opts.device))\n const upload = await uploadData(opts.target, blob, {\n encryptionKey: hexToUint8Array(opts.encryptionKey),\n })\n const refBytes = new Reference(upload.reference).toUint8Array()\n\n const updater = new BasicSequentialUpdater(opts.bee, topic, opts.accountKey)\n updater.setState({ nextIndex: next })\n await updater.update(refBytes, opts.target.stamper)\n // Our own append proves the roster now reaches next+1 — warm the scan memo.\n bumpKnownRosterLength(\n rosterMemoKey(opts.accountId, opts.owner),\n Number(next) + 1,\n )\n}\n\n/**\n * Ensure this device is in the roster with current membership. Appends only when\n * the device is absent or its `removedAt` state differs (a new sign-in, a\n * removal, or a resurrection) — keeping the roster a rare, append-only write.\n */\nexport async function ensureInRoster(opts: {\n bee: Bee\n accountKey: PrivateKey\n owner: EthAddress\n encryptionKey: string\n accountId: string\n device: Device\n target: UploadTarget\n}): Promise<void> {\n // If the roster read is inconclusive we cannot tell \"device absent\" from \"read\n // failed\" — skip rather than append on a guess (an append would compute its\n // index against the same failing feed and could clobber a peer's entry).\n let roster: Device[]\n try {\n roster = await readRoster({\n bee: opts.bee,\n accountId: opts.accountId,\n owner: opts.owner,\n })\n } catch {\n return\n }\n const existing = roster.find((d) => d.deviceId === opts.device.deviceId)\n\n const upToDate =\n existing !== undefined &&\n Boolean(existing.removedAt) === Boolean(opts.device.removedAt)\n if (upToDate) return\n\n await appendToRoster(opts)\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Per-device account-state feed (Phase 3a).\n *\n * Each device publishes its own **full current account view** to its OWN feed\n * (`swarm-id-devstate-v1:<accountId>:<deviceId>`). Because a device writes only\n * its own feed there is no shared SOC address and no cross-device write\n * contention — the `publishAccountState` merge-read-rewrite + `verifyWon` dance\n * is gone. Convergence happens at READ time: `foldAccount` merges every device's\n * latest view with the Phase 1 merge primitives.\n *\n * Device membership (the `deviceId` set) lives in the append-only device roster\n * (`device-roster.ts`), not here; account-level immutables (publicKey, createdAt,\n * partitionCount) ride this robust per-device feed so the fold reconstructs them\n * without a shared doc. Scalar account fields carry per-field LWW clocks so a\n * concurrent change to a different scalar on another device is not dropped\n * (docs/Account-State.md, \"Convergence rules\").\n */\n\nimport {\n Bee,\n BatchId,\n EthAddress,\n PrivateKey,\n Reference,\n Topic,\n} from \"@ethersphere/bee-js\"\nimport { z } from \"zod\"\nimport {\n ConnectedAppSchemaV1,\n PostageStampSchemaV1,\n type AccountStateSnapshot,\n type ConnectedApp,\n type Device,\n type PostageStamp,\n} from \"../schemas\"\nimport {\n serializeConnectedApp,\n serializePostageStamp,\n} from \"../utils/storage-managers\"\nimport { hexToUint8Array } from \"../utils/hex\"\nimport { AsyncEpochFinder, BasicEpochUpdater } from \"../proxy/feeds/epochs\"\nimport { uploadData, type UploadTarget } from \"../proxy/upload\"\nimport { downloadDataWithChunkAPI } from \"../proxy/download-data\"\nimport { mergeConnectedApps, mergePostageStamps } from \"./merge-snapshot\"\nimport { ensureInRoster } from \"./device-roster\"\nimport type { SyncResult } from \"./types\"\n\nexport const DEVICE_STATE_TOPIC_PREFIX = \"swarm-id-devstate-v1\"\n\nconst DEVICE_STATE_VERSION = 1\n\n/** Account settings carried in a device view (mirrors `account.settings`). */\nconst SettingsSchema = z.object({ appSessionDuration: z.number().optional() })\nexport type AccountSettings = z.infer<typeof SettingsSchema>\n\nconst ClockedSchema = <T extends z.ZodTypeAny>(value: T) =>\n z.object({ value, at: z.number() })\n\n/**\n * One device's view of the account. `connectedApps`/`postageStamps` carry their\n * Phase 1 LWW/tombstone clocks; scalars carry their own per-field `at`.\n */\nexport const DeviceStateSnapshotSchemaV1 = z.object({\n version: z.literal(DEVICE_STATE_VERSION),\n accountId: z.string().length(40),\n deviceId: z.string(),\n timestamp: z.number(),\n connectedApps: z.array(ConnectedAppSchemaV1),\n postageStamps: z.array(PostageStampSchemaV1),\n accountName: ClockedSchema(z.string()),\n defaultPostageStampBatchID: ClockedSchema(z.string().length(64).optional()),\n settings: ClockedSchema(SettingsSchema.optional()),\n // Account-level immutables — carried on the robust per-device feed (not the\n // roster), so the fold reconstructs them without a shared doc. Identical\n // across devices.\n accountPublicKey: z.string(),\n accountCreatedAt: z.number(),\n partitionCount: z.number().int().min(1).default(1),\n})\n\nexport type DeviceStateSnapshot = z.infer<typeof DeviceStateSnapshotSchemaV1>\n\n/** The per-device view a device contributes (mutable collections/scalars + account immutables). */\nexport interface DeviceStateView {\n connectedApps: ConnectedApp[]\n postageStamps: PostageStamp[]\n accountName: { value: string; at: number }\n defaultPostageStampBatchID: { value: string | undefined; at: number }\n settings: { value: AccountSettings | undefined; at: number }\n accountPublicKey: string\n accountCreatedAt: number\n partitionCount: number\n}\n\n/** Folded account state — the shape `applyRefreshed` / the proxy consume. */\nexport interface FoldedAccount {\n devices: Device[]\n connectedApps: ConnectedApp[]\n postageStamps: PostageStamp[]\n accountName: string\n defaultPostageStampBatchID: BatchId | undefined\n settings: AccountSettings | undefined\n // Per-field LWW clock of each scalar's winning value, so refresh/restore can\n // apply it by LWW against the local clock.\n accountNameAt: number\n defaultStampAt: number\n settingsAt: number\n createdAt: number\n publicKey: string\n partitionCount: number\n}\n\n/**\n * Build this device's `DeviceStateView` from the local account snapshot — the\n * single derivation shared by both publish paths (`sync-account` and the proxy).\n *\n * Each scalar carries its own per-field LWW clock. A field that was never\n * explicitly edited (no `*At` clock on the snapshot) falls back to the account's\n * STABLE `createdAt` — NEVER a fresh `Date.now()`. Re-stamping an unchanged\n * name / default-stamp / settings with a new timestamp on every publish would\n * let a device that never touched the field clobber a peer's genuine concurrent\n * edit under per-field LWW (docs/Account-State.md, \"Convergence rules\").\n * `createdAt` is identical across devices and\n * predates every edit, so any real change always wins.\n */\nexport function accountStateToDeviceView(\n snapshot: AccountStateSnapshot,\n): DeviceStateView {\n const m = snapshot.metadata\n const stableAt = m.createdAt\n return {\n connectedApps: snapshot.connectedApps,\n postageStamps: snapshot.postageStamps,\n accountName: { value: m.accountName, at: m.accountNameAt ?? stableAt },\n defaultPostageStampBatchID: {\n value: m.defaultPostageStampBatchID,\n at: m.defaultStampAt ?? stableAt,\n },\n settings: { value: m.settings, at: m.settingsAt ?? stableAt },\n accountPublicKey: m.publicKey,\n accountCreatedAt: m.createdAt,\n partitionCount: m.partitionCount ?? 1,\n }\n}\n\nexport function deviceStateTopic(accountId: string, deviceId: string): Topic {\n return Topic.fromString(\n `${DEVICE_STATE_TOPIC_PREFIX}:${accountId}:${deviceId}`,\n )\n}\n\nexport function serializeDeviceState(\n accountId: string,\n deviceId: string,\n view: DeviceStateView,\n): Record<string, unknown> {\n return {\n version: DEVICE_STATE_VERSION,\n accountId,\n deviceId,\n timestamp: Date.now(),\n connectedApps: view.connectedApps.map(serializeConnectedApp),\n postageStamps: view.postageStamps.map(serializePostageStamp),\n accountName: view.accountName,\n defaultPostageStampBatchID: view.defaultPostageStampBatchID,\n settings: view.settings,\n accountPublicKey: view.accountPublicKey,\n accountCreatedAt: view.accountCreatedAt,\n partitionCount: view.partitionCount,\n }\n}\n\nexport function deserializeDeviceState(data: Uint8Array): DeviceStateSnapshot {\n const json = JSON.parse(new TextDecoder().decode(data))\n return DeviceStateSnapshotSchemaV1.parse(json)\n}\n\n/**\n * Write this device's view to its own feed. No merge / no verifyWon — the feed\n * is owned exclusively by this device. Runs inside the caller's\n * `BatchWriteCoordinator.withWrite` (partition lease + Web Lock already held),\n * which supplies `target`.\n */\nexport async function writeDeviceState(opts: {\n accountId: string\n deviceId: string\n accountKey: PrivateKey\n encryptionKey: string\n view: DeviceStateView\n target: UploadTarget\n}): Promise<{ reference: string; chunkAddresses: Uint8Array[] }> {\n const topic = deviceStateTopic(opts.accountId, opts.deviceId)\n const json = serializeDeviceState(opts.accountId, opts.deviceId, opts.view)\n const bytes = new TextEncoder().encode(JSON.stringify(json))\n const uploadResult = await uploadData(opts.target, bytes, {\n encryptionKey: hexToUint8Array(opts.encryptionKey),\n })\n const chunkAddresses = uploadResult.chunkAddresses ?? []\n const refBytes = new Reference(uploadResult.reference).toUint8Array()\n const updater = new BasicEpochUpdater(topic, opts.accountKey)\n const updateResult = await updater.update(\n BigInt(Math.floor(Date.now() / 1000)),\n refBytes,\n opts.target,\n )\n chunkAddresses.push(updateResult.socAddress)\n return { reference: uploadResult.reference, chunkAddresses }\n}\n\n/**\n * Write this device's view AND ensure it's in the append-only device roster. The\n * single write entry point for both `sync-account` (UI-triggered) and the proxy\n * (publish-on-acquire). `ensureInRoster` appends only when this device is absent\n * or its removed-state changed — keeping the roster a rare, clobber-free write.\n */\nexport async function publishDeviceState(opts: {\n bee: Bee\n accountId: string\n device: Device\n accountKey: PrivateKey\n owner: EthAddress\n encryptionKey: string\n view: DeviceStateView\n target: UploadTarget\n /** Per-chunk utilisation hook (mirrors the old publish path). */\n onChunksUploaded?: (chunkAddresses: Uint8Array[]) => Promise<void>\n}): Promise<SyncResult> {\n const written = await writeDeviceState({\n accountId: opts.accountId,\n deviceId: opts.device.deviceId,\n accountKey: opts.accountKey,\n encryptionKey: opts.encryptionKey,\n view: opts.view,\n target: opts.target,\n })\n\n if (opts.onChunksUploaded && written.chunkAddresses.length > 0) {\n // Don't fail the write if utilisation tracking throws.\n await opts.onChunksUploaded(written.chunkAddresses).catch((err) => {\n console.error(\"[publishDeviceState] utilisation hook failed:\", err)\n })\n }\n\n await ensureInRoster({\n bee: opts.bee,\n accountKey: opts.accountKey,\n owner: opts.owner,\n encryptionKey: opts.encryptionKey,\n accountId: opts.accountId,\n device: opts.device,\n target: opts.target,\n })\n\n return {\n status: \"success\",\n reference: written.reference,\n timestamp: BigInt(Math.floor(Date.now() / 1000)),\n chunkAddresses: written.chunkAddresses,\n }\n}\n\n/**\n * Read a device's latest view, or `undefined` when its feed is empty/unreachable.\n */\nexport async function readLatestDeviceState(opts: {\n bee: Bee\n accountId: string\n deviceId: string\n owner: EthAddress\n}): Promise<DeviceStateSnapshot | undefined> {\n const topic = deviceStateTopic(opts.accountId, opts.deviceId)\n const finder = new AsyncEpochFinder(opts.bee, topic, opts.owner)\n const refBytes = await finder.findAt(BigInt(Math.floor(Date.now() / 1000)))\n if (!refBytes) return undefined\n const data = await downloadDataWithChunkAPI(\n opts.bee,\n new Reference(refBytes).toHex(),\n )\n return deserializeDeviceState(data)\n}\n\nfunction pickLatest<T extends { at: number }>(a: T, b: T): T {\n // Ties favour the later-processed entry (b), matching the merge primitives'\n // \"remote then local\" ordering; timestamps differ in practice.\n return b.at >= a.at ? b : a\n}\n\n/**\n * Merge every device's latest view into one account state, reusing the Phase 1\n * primitives for the collections and per-field LWW for the scalars. The device\n * set (`rosterDevices`) comes from the append-only roster (discovery); account\n * immutables come from any device view (identical across devices).\n *\n * Equivalent by construction to today's `mergeSnapshotWithRemote` over the same\n * data (see the differential test) — convergence and deletion semantics are\n * unchanged from Phase 1.\n */\nexport function foldAccount(\n views: DeviceStateSnapshot[],\n rosterDevices: Device[],\n accountId: string,\n): FoldedAccount {\n // Non-empty `views` is a precondition: account immutables (publicKey below)\n // are read from `views[0]`. The sole caller guards this, but assert it here so\n // a future caller gets a clear error rather than a bare `views[0]` TypeError.\n if (views.length === 0) {\n throw new Error(\"foldAccount requires at least one device-state view\")\n }\n let connectedApps: ConnectedApp[] = []\n let postageStamps: PostageStamp[] = []\n let accountName = { value: \"\", at: 0 }\n let defaultBatch: { value?: string; at: number } = { at: 0 }\n let settings: { value?: AccountSettings; at: number } = { at: 0 }\n\n for (const v of views) {\n connectedApps = mergeConnectedApps(\n connectedApps,\n v.connectedApps,\n accountId,\n )\n postageStamps = mergePostageStamps(postageStamps, v.postageStamps)\n accountName = pickLatest(accountName, v.accountName)\n defaultBatch = pickLatest(defaultBatch, v.defaultPostageStampBatchID)\n settings = pickLatest(settings, v.settings)\n }\n\n // Account immutables from any view that defines them — NOT just views[0],\n // whose feed could be older/partial and omit one (partitionCount defaults to 1,\n // which would silently disable partition locking for a multi-partition\n // account). Fall back to the roster (min createdAt) so a fold with reachable\n // roster but no readable device feed still has createdAt. publicKey is carried\n // on every view (callers pass a non-empty `views`), so `views[0]` always has it.\n const meta = views[0]\n const createdAt =\n meta?.accountCreatedAt ??\n rosterDevices.reduce(\n (min, d) => Math.min(min, d.createdAt),\n Number.MAX_SAFE_INTEGER,\n )\n const publicKey = views[0].accountPublicKey\n const partitionCount =\n views.find((v) => v.partitionCount > 1)?.partitionCount ??\n meta?.partitionCount ??\n 1\n\n return {\n devices: rosterDevices,\n connectedApps,\n postageStamps,\n accountName: accountName.value,\n defaultPostageStampBatchID: defaultBatch.value\n ? new BatchId(defaultBatch.value)\n : undefined,\n settings: settings.value,\n accountNameAt: accountName.at,\n defaultStampAt: defaultBatch.at,\n settingsAt: settings.at,\n createdAt: Number.isFinite(createdAt) ? createdAt : Date.now(),\n publicKey,\n // No per-field clock needed: partitionCount is a fixed constant\n // (PARTITION_COUNT, set once at account creation), identical across devices.\n partitionCount,\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Device ID Utilities\n *\n * Manages a per-browser device identifier stored in localStorage and\n * provides helpers for merging device lists across sync/restore.\n */\n\nimport type { Device } from \"../schemas\"\n\nconst DEVICE_ID_KEY = \"swarm-id-device-id\"\n\n/**\n * Get the current device ID, creating one if it doesn't exist.\n */\nexport function getOrCreateDeviceId(): string {\n const existing = localStorage.getItem(DEVICE_ID_KEY)\n if (existing) return existing\n\n const id = crypto.randomUUID()\n localStorage.setItem(DEVICE_ID_KEY, id)\n return id\n}\n\n/**\n * Merge a device list with the current device.\n *\n * Upserts the current device: creates the entry if absent, otherwise leaves it\n * as it is apart from the name. A `deviceName` we are given REPLACES the stored\n * one, so a caller that learns a better label — the partition naming of #570 —\n * corrects the row already in the registry instead of only the next one\n * created. A caller with nothing to say passes nothing and the stored label\n * stands: the name must not be blanked by a context that does not know it.\n *\n * A replacement carries `nameUpdatedAt`, the name's own merge clock, without\n * which the correction never left this device: `mergeDevicesList` ranks records\n * on sign-in recency, a rename moves none of it, and a tie favours the local\n * copy — so every other context kept its stale label forever (#663). Only an\n * ACTUAL change stamps it. Both callers are polls, and a clock that moved every\n * time a caller passed the name it already stored would make every quiet round\n * a byte change to persist, which is the churn #652 removed.\n *\n * Touches neither `removedAt` nor `lastSignedInAt` on an existing entry. Both\n * callers are refresh paths — the proxy's throttled roster poll and the UI's\n * periodic fold — and a poll is not a sign-in: clearing our own tombstone here\n * undid every removal on the next round (#611), and stamping `lastSignedInAt`\n * here (#652) made every fold a byte change to persist, which a proxy holding\n * a partition answered with a bus delta and a stamped feed write. #337's \"a\n * genuine sign-in re-activates a device removed elsewhere\" still holds; it\n * lives at the sign-in seam instead (`reactivateThisDevice`, the only one there\n * is), which is the only place that can tell a user unlocking their account\n * from a timer firing.\n * Liveness is not this field's job either — the bus carries it.\n */\nexport function mergeDevices(\n existing: Device[],\n currentDeviceId: string,\n deviceName?: string,\n): Device[] {\n const found = existing.some((d) => d.deviceId === currentDeviceId)\n\n if (found) {\n return existing.map((d) =>\n d.deviceId === currentDeviceId &&\n deviceName !== undefined &&\n deviceName !== d.name\n ? { ...d, name: deviceName, nameUpdatedAt: Date.now() }\n : d,\n )\n }\n\n const now = Date.now()\n return [\n ...existing,\n {\n deviceId: currentDeviceId,\n createdAt: now,\n lastSignedInAt: now,\n name: deviceName,\n },\n ]\n}\n\n/**\n * Probe `navigator.userAgentData.brands` for a recognisable browser brand.\n * Returns `undefined` when Client Hints aren't exposed (Firefox, Safari),\n * letting the caller fall back to UA sniffing.\n *\n * Order matters: Brave, Edge, and Opera all also publish `Chromium` and\n * `Google Chrome` brands alongside their own, so check the most-specific\n * brand first. Plain Chrome publishes `Google Chrome` + `Chromium` only.\n */\nfunction detectBrowserFromUAData(): string | undefined {\n const data = (\n navigator as Navigator & {\n userAgentData?: { brands?: Array<{ brand: string }> }\n }\n ).userAgentData\n const brands = data?.brands?.map((b) => b.brand)\n if (!brands || brands.length === 0) return undefined\n if (brands.includes(\"Brave\")) return \"Brave\"\n if (brands.includes(\"Microsoft Edge\")) return \"Edge\"\n if (brands.includes(\"Opera\")) return \"Opera\"\n if (brands.includes(\"Google Chrome\")) return \"Chrome\"\n if (brands.includes(\"Chromium\")) return \"Chromium\"\n return undefined\n}\n\n/**\n * Detect a human-readable name for the current device and browser.\n * Returns a string like \"Chrome on Linux Desktop\" or \"Safari on iPhone\".\n */\nexport function detectDeviceName(): string {\n if (typeof navigator === \"undefined\") return \"Unknown Device\"\n const ua = navigator.userAgent\n\n let device: string\n if (/iPhone/.test(ua)) device = \"iPhone\"\n else if (/iPad/.test(ua)) device = \"iPad\"\n else if (/Android/.test(ua))\n device = /Mobile/.test(ua) ? \"Android Phone\" : \"Android Tablet\"\n else if (/Macintosh|Mac OS X/.test(ua)) device = \"Mac\"\n else if (/Windows/.test(ua)) device = \"Windows PC\"\n else if (/Linux/.test(ua)) device = \"Linux Desktop\"\n else device = \"Unknown Device\"\n\n // Prefer UA Client Hints — Brave masks itself in the UA string for\n // privacy, so UA sniffing alone reports it as Chrome. Chromium-derived\n // browsers expose their identity via `navigator.userAgentData.brands`.\n const brandedBrowser = detectBrowserFromUAData()\n if (brandedBrowser) return `${brandedBrowser} on ${device}`\n\n // Fallback: UA sniffing. Order matters: Edge and Chrome both contain\n // \"Chrome\"; OPR is Opera. Brave is indistinguishable from Chrome here.\n let browser: string\n if (/Edg\\//.test(ua)) browser = \"Edge\"\n else if (/OPR\\/|Opera/.test(ua)) browser = \"Opera\"\n else if (/Firefox\\//.test(ua)) browser = \"Firefox\"\n else if (/Chrome\\//.test(ua)) browser = \"Chrome\"\n else if (/Safari\\//.test(ua)) browser = \"Safari\"\n else browser = \"Unknown Browser\"\n\n return `${browser} on ${device}`\n}\n\n/**\n * The device name a STORAGE-PARTITIONED proxy iframe announces itself under.\n *\n * A partitioned iframe keeps its own `swarm-id-device-id` in the partition, and\n * a partition is one (top-level site, iframe origin) pair — so two tabs of one\n * dApp are one device, while two different dApps on the same browser are two.\n * `detectDeviceName()` alone therefore hands a Safari user with three connected\n * dApps three indistinguishable \"Safari on Mac\" rows (#570). The dApp host is\n * what tells them apart, and the port comes with it: locally the dApps differ\n * by port alone.\n *\n * Only for the partitioned case. An UNPARTITIONED proxy reads the trusted\n * domain's first-party store, so it shares `swarm-id-device-id` with the\n * identity UI — it is the same device, and naming that after whichever dApp\n * happened to load it would be wrong.\n *\n * The host is finer-grained than the partition key, which is schemeful site\n * (eTLD+1) and may drop the port. Two same-site dApps — `app1.foo.com` and\n * `app2.foo.com`, or two localhost ports — therefore share ONE partition and\n * one device row, and each announces that row under its own host, so the\n * stored name alternates between them on their polls.\n *\n * The label is per-origin and the thing it labels is per-site, and both are\n * right for their own job. The name is per-origin because the origin is what a\n * user recognises. The row is per-site because it stands for a WRITER, not for\n * a security boundary: nothing reads the device registry to decide whether a\n * write is allowed (`knownDeviceIds` only feeds the lease rival set), and two\n * contexts in one partition cannot write at the same time anyway — they share\n * `withBatchWriteLock`, a Web Lock scoped to the store they share. Giving them\n * a row each would spend two of a finite `partitionCount` on writers that are\n * already serialised. Where per-origin state really is per-origin, the storage\n * key says so: the stored session (#671), and the stamper's.\n *\n * What the alternation costs: nothing on the refresh itself. A partitioned\n * proxy keeps the merged registry in memory, and the refresh that produces it\n * is a pre-acquire hook rather than a publish, so a flip writes nothing and\n * sends nothing. It does not stay in memory, though — the next bus-delta fold\n * persists it, since `refreshStoredPartitionAccount` serialises the whole\n * in-memory account, merged devices included, into the session record. And a\n * flipped row that travels for any other reason (a delta, a feed publish)\n * carries a newer name clock, so it wins the name wherever it lands rather\n * than tying and losing: the alternation reaches the account, not just the two\n * iframes.\n */\nexport function partitionDeviceName(appOrigin: string): string {\n const deviceName = detectDeviceName()\n try {\n // Never worth failing a device announce over: this name is decoration on a\n // roster write, so an origin we cannot parse falls back to the plain name.\n return `${deviceName} · ${new URL(appOrigin).host}`\n } catch {\n return deviceName\n }\n}\n\n/**\n * Whether a merged device registry is worth persisting over the stored one.\n *\n * The old test was \"did the list get LONGER\", which threw away every merge that\n * only refreshed a peer's `lastSignedInAt` or set a `removedAt` tombstone — and\n * those are exactly the fields `activeDeviceIds` prunes the rival set on. A peer\n * the roster shows as live could therefore age out of the stored copy, drop out\n * of the rival set, and never get an intent read: the dual-acquire the registry\n * refresh exists to prevent (#586).\n *\n * Every field counts, our own included. `mergeDevices` no longer stamps our\n * `lastSignedInAt` on a poll (#652), so when it moves it is a genuine sign-in\n * (a reactivation) and worth a save. Until then this excluded it, because\n * counting a stamp that moved on every call meant a save and a storage event in\n * every other tab, every poll round.\n */\nexport function deviceRegistryChanged(\n stored: Device[],\n merged: Device[],\n selfDeviceId: string,\n): boolean {\n if (stored.length !== merged.length) return true\n const before = new Map(stored.map((device) => [device.deviceId, device]))\n return merged.some((device) => {\n const previous = before.get(device.deviceId)\n if (!previous) return true\n const isSelf = device.deviceId === selfDeviceId\n return DEVICE_COMPARED_FIELDS.some(([field, scope]) =>\n isSelf && scope === \"peers-only\"\n ? false\n : previous[field] !== device[field],\n )\n })\n}\n\n/**\n * Every field of `DeviceSchemaV1`, classified for the comparison above.\n *\n * Typed as a full `Record<keyof Device, …>` on purpose: a field added to the\n * schema fails to compile here until it is classified, because the alternative\n * failure is silent — an unlisted field simply never triggers a persist, and\n * what is lost is a registry write nobody is watching for.\n *\n * `deviceId` is in the list though it is the map key: keeping the record total\n * is what makes the compiler the check. The `\"peers-only\"` scope is kept for\n * the next field a poll has to write on ourselves; nothing uses it today.\n */\nconst DEVICE_COMPARED_FIELDS = Object.entries({\n deviceId: \"all\",\n createdAt: \"all\",\n name: \"all\",\n // Cannot move without `name` moving (`mergeDevices` stamps it only on an\n // actual change), so counting it changes nothing today. Listed as \"all\"\n // because a rename IS worth a save, and because the record must stay total.\n nameUpdatedAt: \"all\",\n removedAt: \"all\",\n lastSignedInAt: \"all\",\n} satisfies Record<keyof Device, \"all\" | \"peers-only\">) as [\n keyof Device,\n \"all\" | \"peers-only\",\n][]\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * The registry half of the partition-contention rival set: devices that\n * signed in recently enough to plausibly be acquiring RIGHT NOW.\n *\n * The other half is the account bus. A device beating `presence` in the room\n * (`bus/presence.ts`) is alive by construction, and every caller unions that\n * set with this one; this filter is the bootstrap for a context with no bus,\n * and for a device that signed in moments ago and has not beaten yet.\n *\n * The intent round reads every known device's per-device intent address to\n * deconflict simultaneous fresh claims. If that set is \"all devices the account\n * ever saw\", long-dead devices (ghosts from old sessions — the device list is\n * append-only and never pruned) each add an absent network read to every\n * acquire; on a flaky gateway that cost can push a single acquire past its\n * timeout, so a live device can't even claim a FREE partition. A non-active\n * device must not be able to influence an active one.\n *\n * Filtering by `lastSignedInAt` recency is SAFE (not a correctness mechanism)\n * because the deviceId-INDEPENDENT occupancy beacon, which is NOT gated on this\n * set, is the real dual-acquire backstop for a live peer that this prune drops:\n * - `PartitionLease.refreshHoldersFromOccupancy` (in `acquire`) catches a peer\n * that already HOLDS a partition; and\n * - `PartitionLease.occupancyBeaconBeatsUs` (in `refresh`) resolves a symmetric\n * fresh-claim dual-acquire with such a peer.\n * `lastSignedInAt` advances only on an actual SIGN-IN (a creation or a\n * reactivation), never on a poll (#652) — so a long-running device IS pruned\n * from THIS half after the window; the bus half is what keeps it a rival, and\n * the occupancy channel is what keeps that safe. Pruning only removes the\n * per-acquire absent-read cost of dead ghosts.\n */\n\nimport type { Device } from \"../schemas\"\n\n/**\n * How recently a device must have signed in to count as a contention rival\n * without a bus beat. The stamp is frozen at sign-in (#652), so this bounds the\n * age of the SESSION, not any publish cadence: long enough that a device which\n * just signed in is a rival through its first acquires, far below a prior\n * session, so stale ghosts are dropped. A live device past the window stays a\n * rival through the bus half, not this one.\n */\nexport const KNOWN_DEVICE_MAX_AGE_MS = 30 * 60 * 1000 // 30 minutes\n\n/**\n * Device ids that are (a) not removed and (b) signed in within `maxAgeMs` of\n * `nowMs`. A missing `lastSignedInAt` counts as stale.\n */\nexport function activeDeviceIds(\n devices: Device[],\n nowMs: number,\n maxAgeMs: number,\n): string[] {\n const cutoff = nowMs - maxAgeMs\n return devices\n .filter((d) => !d.removedAt && (d.lastSignedInAt ?? 0) >= cutoff)\n .map((d) => d.deviceId)\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Sync Account\n *\n * Factory function that creates a sync function for syncing account state\n * to Swarm. This integrates store access, key derivation, and utilization\n * tracking.\n */\n\nimport {\n Bee,\n PrivateKey,\n BatchId,\n EthAddress,\n type Chunk,\n} from \"@ethersphere/bee-js\"\nimport {\n deriveSecret,\n deriveSwarmEncryptionKey,\n BACKUP_KEY_LABEL,\n} from \"../utils/key-derivation\"\nimport { hexToUint8Array } from \"../utils/hex\"\nimport {\n updateAfterWrite,\n saveUtilizationState,\n calculateUtilization,\n UtilizationAwareStamper,\n} from \"../utils/batch-utilization\"\nimport type { UtilizationStoreDB } from \"../storage/utilization-store\"\nimport type { DebouncedUtilizationUploader } from \"../storage/debounced-uploader\"\nimport type {\n AccountsStoreInterface,\n PostageStampsStoreInterface,\n} from \"./store-interfaces\"\nimport type { SyncResult } from \"./types\"\nimport { accountToStateSnapshot } from \"../utils/account-state-snapshot\"\nimport type { AccountStateSnapshot } from \"../utils/account-state-snapshot\"\nimport { withTimeout } from \"../utils/promise\"\nimport type { PostageStamp } from \"../schemas\"\nimport {\n BatchWriteCoordinator,\n PartitionContendedError,\n} from \"./batch-write-coordinator\"\nimport { accountStateToDeviceView, publishDeviceState } from \"./device-state\"\nimport { getOrCreateDeviceId, detectDeviceName } from \"../utils/device-id\"\nimport {\n activeDeviceIds,\n KNOWN_DEVICE_MAX_AGE_MS,\n} from \"../utils/active-devices\"\n\n// Timeout for utilization upload in milliseconds\nconst UTILIZATION_UPLOAD_TIMEOUT_MS = 30000\n\n// Timeout for the full sync (upload + feed update) in milliseconds.\n// Bee client requests use bare fetch with no timeout, so a non-responding\n// node would otherwise hang forever.\nconst SYNC_TIMEOUT_MS = 60000\n\n/**\n * Options for creating a sync account function\n */\nexport interface SyncAccountOptions {\n /** Bee client for Swarm operations */\n bee: Bee\n\n /** Store providing account data (the account owns its apps + stamps) */\n accountsStore: AccountsStoreInterface\n\n /** Store providing postage stamp runtime access (stamper/utilization) */\n postageStampsStore: PostageStampsStoreInterface\n\n /** Utilization store for browser-based utilization tracking */\n utilizationStore: UtilizationStoreDB\n\n /** Debounced uploader for batch utilization state */\n utilizationUploader: DebouncedUtilizationUploader\n\n /**\n * Devices heard live on the account bus (`PresenceTracker`), unioned into\n * the partition rival set so this one-shot coordinator and the proxy's agree\n * about who the rivals are. Optional: a caller with no bus has only the\n * registry's recent sign-ins to go on.\n */\n liveDeviceIds?: () => string[]\n}\n\n/**\n * Sync account function type\n */\nexport type SyncAccountFunction = (\n accountId: string,\n) => Promise<SyncResult | undefined>\n\n/**\n * Convert chunk addresses to Chunk objects for utilization tracking\n *\n * Creates minimal chunk objects with just the address property\n * needed for bucket calculation. We don't need actual chunk data\n * since we're only tracking which buckets/slots were used.\n */\nfunction createChunksFromAddresses(addresses: Uint8Array[]): Chunk[] {\n return addresses.map((address) => {\n return {\n address: {\n toUint8Array: () => address,\n toHex: () =>\n Array.from(address)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\"),\n },\n data: new Uint8Array(0), // Not used for utilization tracking\n } as Chunk\n })\n}\n\n/**\n * Create a sync account function with dependency-injected stores\n *\n * @param options - Configuration options including stores and optional utilization tracking\n * @returns Function that syncs an account to Swarm\n */\nexport function createSyncAccount(\n options: SyncAccountOptions,\n): SyncAccountFunction {\n const {\n bee,\n accountsStore,\n postageStampsStore,\n utilizationStore,\n utilizationUploader,\n liveDeviceIds,\n } = options\n\n /**\n * Handle utilization tracking after chunk upload\n */\n async function handleUtilizationUpdate(\n accountId: string,\n chunkAddresses: Uint8Array[],\n ): Promise<void> {\n // Get account\n const account = accountsStore.getAccount(new EthAddress(accountId))\n if (!account) {\n console.warn(\"[SyncCoordinator] Account not found for utilization update\")\n return\n }\n\n // Resolve default stamp\n const defaultStamp = account.defaultPostageStampBatchID\n\n if (!defaultStamp) {\n console.warn(\"[SyncCoordinator] No default stamp, skipping utilization\")\n return\n }\n\n const batchID = new BatchId(defaultStamp)\n const stamp = postageStampsStore.getStamp(batchID)\n\n if (!stamp) {\n console.warn(\"[SyncCoordinator] Stamp not found, skipping utilization\")\n return\n }\n\n // Convert chunk addresses to Chunks\n const chunks = createChunksFromAddresses(chunkAddresses)\n\n // Derive swarm encryption key from derivation key\n const swarmEncryptionKey = await deriveSwarmEncryptionKey(\n account.derivationKey,\n )\n\n // Derive owner address from backup key\n const backupKeyHex = await deriveSecret(\n swarmEncryptionKey,\n BACKUP_KEY_LABEL,\n )\n const backupKey = new PrivateKey(backupKeyHex)\n const owner = backupKey.publicKey().address()\n\n // Update utilization state\n const { state: utilizationState, tracker } = await updateAfterWrite(\n batchID,\n chunks,\n stamp.depth,\n {\n cache: utilizationStore,\n },\n )\n\n // Calculate new utilization fraction\n const newUtilization = calculateUtilization(utilizationState, stamp.depth)\n\n // Update stamp in store (without triggering sync)\n postageStampsStore.updateStampUtilization(batchID, newUtilization)\n\n // Schedule debounced upload of dirty chunks and WAIT for it\n if (tracker.hasDirtyChunks()) {\n // Get stamper for signing chunks (with loaded bucket state)\n const stamper = await postageStampsStore.getStamper(batchID, {\n owner,\n encryptionKey: hexToUint8Array(swarmEncryptionKey),\n })\n if (!stamper) {\n console.warn(\"[SyncCoordinator] Cannot create stamper, skipping upload\")\n return\n }\n\n const uploadPromise = utilizationUploader.scheduleUpload(\n batchID.toHex(),\n tracker,\n async () => {\n await saveUtilizationState(utilizationState, {\n bee,\n stamper,\n encryptionKey: hexToUint8Array(swarmEncryptionKey),\n cache: utilizationStore,\n tracker,\n reservedBuckets:\n stamper instanceof UtilizationAwareStamper\n ? stamper.getProtectedBuckets()\n : undefined,\n })\n\n // Flush stamper bucket state updates to cache (if supported)\n if (stamper.flush) {\n await stamper.flush()\n }\n },\n )\n\n return withTimeout(\n uploadPromise,\n UTILIZATION_UPLOAD_TIMEOUT_MS,\n `Utilization upload timeout (${UTILIZATION_UPLOAD_TIMEOUT_MS}ms)`,\n )\n }\n }\n\n /**\n * Capture a consistent snapshot of account state for syncing.\n * Must be called before any async operations to ensure consistency.\n *\n * @param accountId - Account ID (hex address)\n * @returns Snapshot and sync context, or undefined if account/stamp not found\n */\n async function getAccountStateSnapshot(accountId: string): Promise<\n | {\n snapshot: AccountStateSnapshot\n encryptionKey: string\n defaultStamp: PostageStamp\n }\n | undefined\n > {\n // Get account\n const account = accountsStore.getAccount(new EthAddress(accountId))\n if (!account) {\n console.warn(\"[SyncCoordinator] Account not found\", accountId)\n return undefined\n }\n\n // Resolve default stamp (account-scoped)\n const defaultStampBatchID = account.defaultPostageStampBatchID\n\n if (!defaultStampBatchID) {\n console.warn(\"[SyncCoordinator] No default stamp for account\", accountId)\n return undefined\n }\n\n const defaultStamp = postageStampsStore.getStamp(defaultStampBatchID)\n if (!defaultStamp) {\n console.warn(\"[SyncCoordinator] Default stamp not found\")\n return undefined\n }\n\n // Derive swarm encryption key from stored derivation key\n const encryptionKey = await deriveSwarmEncryptionKey(account.derivationKey)\n\n // Account state is read straight off the nested account document, by the\n // one builder every publisher shares (the per-field clock rules live there).\n const snapshot = accountToStateSnapshot(account, accountId, Date.now())\n\n return {\n snapshot,\n encryptionKey,\n defaultStamp,\n }\n }\n\n return async function syncAccount(\n accountId: string,\n ): Promise<SyncResult | undefined> {\n const startTime = performance.now()\n const timestamp = () => new Date().toISOString()\n\n // Capture state snapshot (derives encryption key from derivation key).\n const snapshotResult = await getAccountStateSnapshot(accountId)\n if (!snapshotResult) {\n return undefined\n }\n\n const { snapshot: state, encryptionKey, defaultStamp } = snapshotResult\n const partitionCount = state.metadata.partitionCount ?? 1\n\n // Derive the account feed key (also the lock-SOC backup signer) + owner.\n const backupKeyHex = await deriveSecret(encryptionKey, BACKUP_KEY_LABEL)\n const accountKey = new PrivateKey(backupKeyHex)\n const owner = accountKey.publicKey().address()\n\n // Build the stamper for the default batch. The coordinator binds the held\n // partition on it and drives the lease circuit-breaker, so it needs the\n // full UtilizationAwareStamper surface — the store interface only promises\n // a FlushableStamper, so narrow at runtime instead of casting past the\n // contract.\n const stamper = await postageStampsStore.getStamper(defaultStamp.batchID, {\n owner,\n encryptionKey: hexToUint8Array(encryptionKey),\n })\n if (!stamper) {\n const error = `Cannot create stamper for batch ${defaultStamp.batchID.toHex()}`\n console.error(`[SyncCoordinator ${timestamp()}] ${error}`)\n return { status: \"error\", error }\n }\n if (!(stamper instanceof UtilizationAwareStamper)) {\n const error = `Stamper for batch ${defaultStamp.batchID.toHex()} does not support coordinated writes (expected a UtilizationAwareStamper)`\n console.error(`[SyncCoordinator ${timestamp()}] ${error}`)\n return { status: \"error\", error }\n }\n\n // Route the publish through the shared coordinator (same Web Lock + same\n // partition acquire the proxy uses). One-off mode: no refresh timer — the\n // lease lapses by TTL. `wait: \"skip\"` claims a free/expired partition once\n // and throws `PartitionContendedError` when every partition is held by a\n // live foreign device, so we skip rather than wait.\n const coordinator = new BatchWriteCoordinator({\n bee,\n batchId: defaultStamp.batchID.toHex(),\n stamper,\n deviceId: getOrCreateDeviceId(),\n accountId,\n // Read fresh so the presence/intent rounds see the current device\n // registry — without this the background-sync acquire has no rivals to\n // check and can collide with a live holder on another device.\n //\n // Bounded the same way the proxy bounds it (`knownDeviceIdsForAccount`).\n // This path listed every device the account had ever seen, tombstoned and\n // long-dead alike, so the two coordinators disagreed about who the rivals\n // were — and each dead entry costs an absent intent read on every acquire.\n knownDeviceIds: () => [\n ...new Set([\n ...activeDeviceIds(\n accountsStore.getAccount(new EthAddress(accountId))?.devices ?? [],\n Date.now(),\n KNOWN_DEVICE_MAX_AGE_MS,\n ),\n ...(liveDeviceIds?.() ?? []),\n ]),\n ],\n backupSigner: accountKey,\n swarmEncryptionKey: hexToUint8Array(encryptionKey),\n partitionCount,\n mode: \"oneshot\",\n })\n\n console.log(\n `[SyncCoordinator ${timestamp()}] Starting sync for ${accountId} bee.url=${bee.url}`,\n )\n\n // Build this device's view + registry seed from the captured snapshot. The\n // per-field scalar clocks (incl. the stable-`createdAt` fallback for a\n // never-edited field) are derived by `accountStateToDeviceView`.\n const deviceId = getOrCreateDeviceId()\n const thisDevice = state.metadata.devices.find(\n (d) => d.deviceId === deviceId,\n ) ?? {\n deviceId,\n name: detectDeviceName(),\n createdAt: Date.now(),\n lastSignedInAt: Date.now(),\n }\n const view = accountStateToDeviceView(state)\n\n try {\n // Time-bound the publish: Bee client requests have no built-in timeout,\n // so a non-responding node would otherwise hang the UI forever. This\n // surfaces the timeout to the caller but does NOT cancel the underlying\n // fetch (that would need AbortSignal propagation through bee-js). The\n // TimeoutError lands in the catch below, which returns it as a\n // `status: \"error\"` SyncResult — same shape and message as before.\n const result = await withTimeout(\n coordinator.withWrite(\n (target) =>\n publishDeviceState({\n bee,\n accountId,\n device: thisDevice,\n accountKey,\n owner,\n encryptionKey,\n view,\n target,\n onChunksUploaded: (addresses) =>\n handleUtilizationUpdate(accountId, addresses),\n }),\n { wait: \"skip\" },\n ),\n SYNC_TIMEOUT_MS,\n `Sync timed out after ${SYNC_TIMEOUT_MS}ms`,\n )\n if (result.status === \"error\") {\n console.error(\n `[SyncCoordinator ${timestamp()}] Sync failed (+${(performance.now() - startTime).toFixed(2)}ms):`,\n result.error,\n )\n }\n return result\n } catch (error) {\n // Genuine contention (every partition held by a live foreign device):\n // skip quietly — a peer (or the proxy) publishes instead.\n if (error instanceof PartitionContendedError) {\n console.warn(\n `[SyncCoordinator] Skipping sync ${accountId}: all partitions are held by other devices.`,\n )\n return undefined\n }\n // Operational failure (stamp/SOC/lock error) — log as an error, distinct\n // from contention.\n console.error(\n `[SyncCoordinator ${timestamp()}] Sync failed (+${(performance.now() - startTime).toFixed(2)}ms):`,\n error,\n )\n return {\n status: \"error\",\n error: error instanceof Error ? error.message : String(error),\n }\n }\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Read entry point for Phase 3a: reconstruct account state from Swarm by\n * scanning the append-only device roster (discovery) and folding every device's\n * latest state feed. Replaces the single shared-snapshot fetch used by restore\n * / refresh / the proxy.\n */\n\nimport { Bee, EthAddress, PrivateKey } from \"@ethersphere/bee-js\"\nimport {\n deriveSwarmEncryptionKey,\n deriveSecret,\n BACKUP_KEY_LABEL,\n} from \"../utils/key-derivation\"\nimport { readRoster } from \"./device-roster\"\nimport {\n readLatestDeviceState,\n foldAccount,\n type DeviceStateSnapshot,\n type FoldedAccount,\n} from \"./device-state\"\nimport type { SyncedAccount, Device } from \"../schemas\"\n\nexport interface FoldAccountResult {\n account: FoldedAccount\n devices: Device[]\n}\n\n/**\n * Project a folded account onto the `SyncedAccount` record — the single place\n * the field list lives so a new `SyncedAccount`/`FoldedAccount` field can't\n * be silently dropped by a hand-written copy on the sign-in path. `id` and\n * `derivationKey` aren't in the fold (they come from the recovering wallet), so\n * the caller supplies them.\n *\n * `foldAccountFromSwarm` shallow-FREEZES its result arrays so coalesced callers\n * can't corrupt each other's shared view; a persisted `SyncedAccount` outlives\n * that window and is mutated in place downstream, so the arrays are COPIED here.\n */\nexport function foldedToSyncedAccount(opts: {\n id: EthAddress\n derivationKey: string\n account: FoldedAccount\n lastModified?: number\n}): SyncedAccount {\n const { id, derivationKey, account } = opts\n return {\n id,\n name: account.accountName,\n publicKey: account.publicKey,\n createdAt: account.createdAt,\n derivationKey,\n defaultPostageStampBatchID: account.defaultPostageStampBatchID,\n settings: account.settings,\n accountNameAt: account.accountNameAt,\n defaultStampAt: account.defaultStampAt,\n settingsAt: account.settingsAt,\n lastModified: opts.lastModified ?? Date.now(),\n devices: [...account.devices],\n connectedApps: [...account.connectedApps],\n postageStamps: [...account.postageStamps],\n partitionCount: account.partitionCount,\n }\n}\n\n/**\n * Folds in flight, keyed on `accountId:derivationKey`. A full fold is many slow\n * Swarm reads (roster + every device-state feed); the UI triggers it from\n * several uncoordinated places (account layout mount, dev page, restore) that\n * commonly fire together. Coalescing concurrent calls to a single promise\n * collapses that burst into one read set — especially on a slow gateway where a\n * fold takes seconds, so overlapping triggers are the norm. The entry is cleared\n * when the fold settles, so a later fold always re-reads (no stale cache).\n *\n * Coalesced callers share ONE `FoldAccountResult` instance — treat it as\n * read-only; a caller that mutates the result corrupts the others. The result's\n * arrays are frozen before return so such a mutation fails loud rather than\n * silently (see `doFoldAccountFromSwarm`).\n */\nconst inFlightFolds = new Map<string, Promise<FoldAccountResult | undefined>>()\n\n/**\n * Derive the feed owner + encryption key from the account derivation key, read\n * the roster, fold all device-state feeds. Returns `undefined` when the roster\n * is empty (no device has published — equivalent to today's \"no-backup\").\n *\n * Concurrent calls for the same account share one in-flight read (see\n * `inFlightFolds`); `bee` is intentionally keyed OUT — every client reads the\n * same feeds. Coalescing across two DIFFERENT `bee` endpoints (e.g. a local\n * cluster and a public gateway mid-transition) is safe, not a bug: a fold is a\n * point-in-time, eventually-consistent LWW/CRDT snapshot (roster + device-state\n * both fold last-writer-wins by their per-field `at` clocks — see\n * `merge-snapshot.ts` / `device-state.ts`). No caller assumes a fold reflects a\n * specific endpoint's live view, so a coalesced caller receiving the other\n * endpoint's equally-valid snapshot is indistinguishable from ordinary gateway\n * staleness — which the LWW fold already tolerates everywhere, and the next\n * (uncoalesced, entry-cleared) fold re-reads and re-converges. Do NOT add `bee`\n * to the key to \"fix\" this; it would only defeat the coalescing.\n */\nexport async function foldAccountFromSwarm(opts: {\n bee: Bee\n derivationKey: string\n accountId: string\n}): Promise<FoldAccountResult | undefined> {\n const key = `${opts.accountId}:${opts.derivationKey}`\n const existing = inFlightFolds.get(key)\n if (existing) return existing\n const fold = doFoldAccountFromSwarm(opts).finally(() => {\n inFlightFolds.delete(key)\n })\n inFlightFolds.set(key, fold)\n return fold\n}\n\nasync function doFoldAccountFromSwarm(opts: {\n bee: Bee\n derivationKey: string\n accountId: string\n}): Promise<FoldAccountResult | undefined> {\n const swarmEncryptionKey = await deriveSwarmEncryptionKey(opts.derivationKey)\n const backupKeyHex = await deriveSecret(swarmEncryptionKey, BACKUP_KEY_LABEL)\n const owner = new PrivateKey(backupKeyHex).publicKey().address()\n\n const devices = await readRoster({\n bee: opts.bee,\n accountId: opts.accountId,\n owner,\n })\n if (devices.length === 0) return undefined\n\n // Fold the latest view of every non-removed device. Reads run in parallel:\n // each device feed is independent and each lookup is a multi-round-trip epoch\n // traversal, so overlapping them collapses N×latency into ≈1×. A device whose\n // feed is unreachable is skipped (best-effort), not fatal — its peers' views\n // still converge the shared entities.\n // ponytail: plain Promise.all — device count is tiny; add a concurrency pool\n // only if rosters ever grow large.\n const views = (\n await Promise.all(\n devices\n .filter((device) => !device.removedAt)\n .map((device) =>\n readLatestDeviceState({\n bee: opts.bee,\n accountId: opts.accountId,\n deviceId: device.deviceId,\n owner,\n }).catch(() => undefined),\n ),\n )\n ).filter((view): view is DeviceStateSnapshot => view !== undefined)\n\n // No readable device feed → nothing to restore (the roster exists but every\n // device's state was unreachable). Mirrors the `devices.length === 0` guard\n // above, and guarantees `foldAccount` has a view carrying `accountPublicKey`.\n if (views.length === 0) return undefined\n\n const account = foldAccount(views, devices, opts.accountId)\n // Coalesced callers share this ONE result (see `inFlightFolds`). Freeze the\n // arrays they iterate so a stray mutation throws loudly instead of silently\n // corrupting a concurrent caller's view.\n // ponytail: shallow freeze (arrays only, not each element) — catches the\n // realistic push/splice/sort corruption since today's callers only read or\n // pass these into the pure `merge*` helpers, which never mutate their inputs.\n // If a caller ever mutates an element in place, deep-freeze the elements too\n // (or clone the result per caller).\n for (const arr of [\n devices,\n account.devices,\n account.connectedApps,\n account.postageStamps,\n ]) {\n if (Array.isArray(arr)) Object.freeze(arr)\n }\n return { account, devices }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Whether a session restored from storage may still write.\n *\n * A saved session outlives the page that made it, and a revoke made while no\n * page of this site was open is written only to Swarm: the account's published\n * state is the one place a Remove or a Disconnect from another device can be\n * read. So the frame asks it before a restored session's first write, and an\n * answer that never arrived is `unconfirmed`, never \"not revoked\".\n */\n\nimport { Bee, PrivateKey } from \"@ethersphere/bee-js\"\nimport {\n BACKUP_KEY_LABEL,\n deriveSecret,\n deriveSwarmEncryptionKey,\n} from \"../utils/key-derivation\"\nimport { readRoster } from \"./device-roster\"\nimport { foldAccountFromSwarm } from \"./fold-account-from-swarm\"\n\n/**\n * - `allowed`: the published account still has this site connected, or the\n * account has never published anything (it has no drive, so it cannot have\n * published a revoke either).\n * - `ended`: the published account removed the site, disconnected it after\n * this session connected, or no longer lists it.\n * - `unconfirmed`: the answer could not be read.\n */\nexport type SessionVerdict = \"allowed\" | \"ended\" | \"unconfirmed\"\n\nexport async function checkRestoredSession(opts: {\n bee: Bee\n derivationKey: string\n accountId: string\n appOrigin: string\n /** When this session's connection was made. Missing reads as 0, so a\n * disconnect stands rather than being explained away by a clock that does\n * not exist. */\n connectedAt: number | undefined\n}): Promise<SessionVerdict> {\n try {\n const folded = await foldAccountFromSwarm({\n bee: opts.bee,\n derivationKey: opts.derivationKey,\n accountId: opts.accountId,\n })\n if (!folded)\n return (await hasNeverPublished(opts)) ? \"allowed\" : \"unconfirmed\"\n // `connectedUntil` is not read: the published form strips it with\n // `appSecret`, and this session's own deadline is enforced on read.\n const app = folded.account.connectedApps.find(\n (entry) => entry.appUrl === opts.appOrigin,\n )\n const ended =\n app === undefined ||\n app.revokedAt !== undefined ||\n (app.disconnectedAt !== undefined &&\n app.disconnectedAt > (opts.connectedAt ?? 0))\n return ended ? \"ended\" : \"allowed\"\n } catch {\n return \"unconfirmed\"\n }\n}\n\n/**\n * `foldAccountFromSwarm` answers `undefined` both for an account with no\n * roster and for a roster whose device states could not be read. Only the\n * first is an answer. `readRoster` itself throws when it cannot tell an empty\n * roster from an outage, so an empty list here is a confirmed absence.\n */\nasync function hasNeverPublished(opts: {\n bee: Bee\n derivationKey: string\n accountId: string\n}): Promise<boolean> {\n const swarmEncryptionKey = await deriveSwarmEncryptionKey(opts.derivationKey)\n const owner = new PrivateKey(\n await deriveSecret(swarmEncryptionKey, BACKUP_KEY_LABEL),\n )\n .publicKey()\n .address()\n const devices = await readRoster({\n bee: opts.bee,\n accountId: opts.accountId,\n owner,\n })\n return devices.length === 0\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Whether the proxy iframe can read the trusted domain's first-party store —\n * asked BEFORE authentication, because it decides which transport the auth\n * popup has to take (#613).\n *\n * The mirror image of the probe the connect popup already runs: there, the\n * iframe writes a challenge and the popup checks whether it can read it back\n * first-party (`ui/src/lib/stores/connect.svelte.ts`). That answer arrives far\n * too late to choose a transport, so the same question is asked the other way\n * round — the first-party app leaves a marker, and the iframe reads it back.\n *\n * Deliberately NOT any of these:\n *\n * - **`document.hasStorageAccess()`** — measured 2026-08-31 in the\n * `chromium-partitioned` rig: it returns `true` inside a genuinely\n * partitioned frame. It is cookie-scoped, and Chrome partitions storage\n * separately from cookies, so it answers a different question.\n * - **The accounts key.** The proxy writes `swarm-id-accounts` from inside the\n * iframe (`clearAuthData`), and the versioned storage manager can materialise\n * state in a fresh partitioned bucket, so its presence conflates \"first-party\n * storage is visible\" with \"this partition has its own copy\".\n * - **The user agent**, which is what this replaces.\n *\n * The marker only ever answers YES with certainty. Absent means \"not proven\n * shared\" — a partitioned iframe, or a browser that has never had the SwarmID\n * site open at the top level — and the caller treats that as partitioned,\n * which is the safe direction: the delegated transport works in both modes.\n */\nexport const STORAGE_SHARED_KEY = \"swarm-id-first-party\"\n\n/**\n * Leave the marker. Called by the trusted domain's own pages.\n *\n * The framed guard is load-bearing: the proxy route runs the same app shell\n * inside the iframe, and a marker written there would land in the partitioned\n * bucket and make the iframe report itself shared — the one wrong answer this\n * must never give.\n */\nexport function markFirstPartyStorage(): void {\n try {\n if (typeof window === \"undefined\" || window.self !== window.top) {\n return\n }\n localStorage.setItem(STORAGE_SHARED_KEY, \"1\")\n } catch {\n // Storage disabled or full. Nothing to do: an unwritten marker reads as\n // \"not proven shared\", which is the safe answer.\n }\n}\n\n/** Whether this context can see the marker a first-party page left. */\nexport function isStorageShared(): boolean {\n try {\n return localStorage.getItem(STORAGE_SHARED_KEY) !== null\n } catch {\n return false\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Reads postage batch state directly from the Swarm PostageStamp contract on\n * Gnosis Chain, by batch ID, over a plain JSON-RPC `eth_call`.\n *\n * This is the ground truth for a stamp's remaining lifetime: it is the exact\n * data a Bee node uses internally to compute `batchTTL`, but it does not\n * require the node to know about the batch. A stamp added manually (or owned\n * by a different node) has no entry in `GET /stamps/{id}`, so the Bee-node TTL\n * path falls back to a constant-price approximation that drifts as the chain\n * price moves (see issue #344). Querying the contract avoids that entirely.\n *\n * The contract exposes the per-chunk normalised balance accounting used by the\n * storage-incentives system:\n * remainingBalancePerChunk = normalisedBalance - currentTotalOutPayment()\n * remainingBlocks = remainingBalancePerChunk / lastPrice()\n * ttlSeconds = remainingBlocks * GNOSIS_BLOCK_TIME\n */\n\nimport { fetchBatchTTL, GNOSIS_BLOCK_TIME } from \"./ttl\"\nimport { CHAIN_READ_TIMEOUT_MS, jsonRpcBatch } from \"./json-rpc\"\n\n/**\n * PostageStamp contract address on Gnosis Chain. Lowercased — RPC nodes accept\n * a non-checksummed `to`, and lowercasing sidesteps any checksum mistake.\n */\nexport const POSTAGE_STAMP_CONTRACT_ADDRESS =\n \"0x45a1502382541cd610cc9068e88727426b696293\"\n\n/**\n * True when an RPC URL targets a local dev chain (bee-compose anvil). A\n * dev-deployed PostageStamp address is only valid against such a node.\n */\nfunction isLocalRpcUrl(rpcUrl: string): boolean {\n try {\n const { hostname } = new URL(rpcUrl)\n return (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"0.0.0.0\"\n )\n } catch {\n return false\n }\n}\n\n/**\n * Picks the PostageStamp contract address that matches the chain `rpcUrl` points\n * at. A dev-override address (bee-compose anvil) is only honoured against a local\n * RPC; against any remote RPC (Gnosis mainnet) it would read a nonexistent\n * contract — `eth_call` returns empty, the batch looks unknown, and callers\n * silently fall back to a bogus price estimate. Tying the address to the RPC\n * network stops the two from drifting apart, which is what produced wildly wrong\n * estimated expiry dates when a dev build read a mainnet batch (#344).\n */\nexport function resolvePostageStampContractAddress(\n rpcUrl: string,\n localContractAddress?: string,\n): string {\n return isLocalRpcUrl(rpcUrl) && localContractAddress\n ? localContractAddress\n : POSTAGE_STAMP_CONTRACT_ADDRESS\n}\n\n/**\n * 4-byte function selectors (first 4 bytes of keccak256 of the signature).\n * Hardcoded so the library needs no keccak/ABI dependency for these fixed,\n * argument-free (or single-bytes32) calls.\n */\nconst SELECTOR_BATCHES = \"c81e25ab\" // batches(bytes32)\nconst SELECTOR_CURRENT_TOTAL_OUT_PAYMENT = \"51b17cd0\" // currentTotalOutPayment()\nconst SELECTOR_LAST_PRICE = \"053f14da\" // lastPrice()\n\nconst HEX_WORD_LENGTH = 64 // 32 bytes as hex\nconst ADDRESS_HEX_LENGTH = 40 // 20 bytes as hex\nconst ZERO_ADDRESS = \"0x0000000000000000000000000000000000000000\"\n\n// A batch ID is exactly one 32-byte word. Compiled once, not per call.\nconst BATCH_ID_REGEX = new RegExp(`^[0-9a-f]{${HEX_WORD_LENGTH}}$`)\n\n/**\n * Decoded `batches(bytes32)` tuple. Field names mirror the contract's `Batch`\n * struct. `normalisedBalance` is a per-chunk value denominated in PLUR; it is\n * the cumulative outpayment level at which this batch runs out.\n */\nexport interface OnChainPostageBatch {\n owner: string\n depth: number\n bucketDepth: number\n immutableFlag: boolean\n normalisedBalance: bigint\n lastUpdatedBlockNumber: bigint\n}\n\n/**\n * A consistent snapshot of one batch plus the global accounting needed to turn\n * its `normalisedBalance` into a remaining TTL.\n */\nexport interface OnChainBatchState {\n batch: OnChainPostageBatch\n /** Per-chunk cumulative outpayment so far, in PLUR (`currentTotalOutPayment()`). */\n currentTotalOutPayment: bigint\n /** Per-chunk per-block storage price, in PLUR (`lastPrice()`). */\n lastPrice: bigint\n}\n\n/**\n * Normalises a batch ID to 64 lowercase hex chars (no `0x`) for ABI encoding.\n * @throws if the input is not a 32-byte hex string.\n */\nfunction normalizeBatchId(batchId: string): string {\n const stripped = batchId.startsWith(\"0x\") ? batchId.slice(2) : batchId\n const lower = stripped.toLowerCase()\n if (!BATCH_ID_REGEX.test(lower)) {\n throw new Error(`Invalid batch ID: ${batchId}`)\n }\n return lower\n}\n\n/** Splits a `0x`-prefixed return payload into 32-byte (64-hex) words. */\nfunction toWords(hex: string): string[] {\n const body = hex.startsWith(\"0x\") ? hex.slice(2) : hex\n const words: string[] = []\n for (let i = 0; i < body.length; i += HEX_WORD_LENGTH) {\n words.push(body.slice(i, i + HEX_WORD_LENGTH))\n }\n return words\n}\n\n/**\n * Decodes the ABI-encoded return of `batches(bytes32)` — a static tuple of\n * `(address, uint8, uint8, bool, uint256, uint256)`, i.e. six 32-byte words.\n * @throws if the payload is too short to hold the tuple.\n */\nexport function decodeBatches(hex: string): OnChainPostageBatch {\n const words = toWords(hex)\n const EXPECTED_WORDS = 6\n if (words.length < EXPECTED_WORDS) {\n throw new Error(`Unexpected batches() return length: ${hex}`)\n }\n return {\n owner: `0x${words[0].slice(HEX_WORD_LENGTH - ADDRESS_HEX_LENGTH)}`,\n depth: Number(BigInt(`0x${words[1]}`)),\n bucketDepth: Number(BigInt(`0x${words[2]}`)),\n immutableFlag: BigInt(`0x${words[3]}`) !== 0n,\n normalisedBalance: BigInt(`0x${words[4]}`),\n lastUpdatedBlockNumber: BigInt(`0x${words[5]}`),\n }\n}\n\n/** Decodes a single-`uint256` return payload to a BigInt. */\nexport function decodeUint(hex: string): bigint {\n const body = hex.startsWith(\"0x\") ? hex.slice(2) : hex\n if (body.length === 0) {\n throw new Error(\"Empty uint return\")\n }\n return BigInt(`0x${body}`)\n}\n\ninterface EthCall {\n to: string\n data: string\n}\n\n/**\n * Sends a batched `eth_call` and returns the raw hex result of each call, in\n * the same order as `calls`. Id matching lives in {@link jsonRpcBatch}; this\n * narrows its `unknown` results to the hex strings `eth_call` returns.\n * @throws if the transport fails or any sub-call returns an error/non-hex result.\n */\nasync function ethCallBatch(\n rpcUrl: string,\n calls: EthCall[],\n): Promise<string[]> {\n const results = await jsonRpcBatch(\n rpcUrl,\n calls.map((call) => ({\n method: \"eth_call\",\n params: [{ to: call.to, data: call.data }, \"latest\"],\n })),\n { timeoutMs: CHAIN_READ_TIMEOUT_MS },\n )\n return results.map((result) => {\n if (typeof result !== \"string\") {\n throw new Error(\"eth_call returned something other than a hex string\")\n }\n return result\n })\n}\n\n/**\n * Fetches a consistent on-chain snapshot of a postage batch from the\n * PostageStamp contract: its `batches(batchId)` tuple plus the global\n * `currentTotalOutPayment()` and `lastPrice()`.\n *\n * @param rpcUrl - Gnosis Chain JSON-RPC URL\n * @param batchId - 32-byte hex batch ID, with or without `0x` prefix\n * @param contractAddress - PostageStamp contract address to read from. Defaults\n * to the Gnosis mainnet deployment; override for a local dev chain\n * (bee-compose anvil deploys it at a different address).\n * @returns The decoded state, or `undefined` if the batch is unknown to the\n * contract (zero owner) or the RPC request fails. Never throws — the\n * UI falls back to other expiry sources on `undefined`.\n */\n/**\n * Outcome of an on-chain batch read, keeping the two failure modes distinct:\n * - `found` — the contract knows the batch (non-zero owner).\n * - `not-found` — the contract returned an all-zero tuple; the batch\n * authoritatively does not exist on this chain.\n * - `error` — the RPC request failed or returned an undecodable payload, so\n * existence is *unknown* (not the same as \"does not exist\").\n */\nexport type OnChainBatchResult =\n | { status: \"found\"; state: OnChainBatchState }\n | { status: \"not-found\" }\n | { status: \"error\" }\n\n/**\n * Reads a batch's on-chain state, distinguishing \"definitively absent\" from\n * \"couldn't check\". {@link fetchOnChainBatchState} is the simpler wrapper that\n * collapses both into `undefined`.\n */\nexport async function fetchOnChainBatchStateResult(\n rpcUrl: string,\n batchId: string,\n contractAddress: string = POSTAGE_STAMP_CONTRACT_ADDRESS,\n): Promise<OnChainBatchResult> {\n try {\n const id = normalizeBatchId(batchId)\n const [batchesResult, outPaymentResult, lastPriceResult] =\n await ethCallBatch(rpcUrl, [\n {\n to: contractAddress,\n data: `0x${SELECTOR_BATCHES}${id}`,\n },\n {\n to: contractAddress,\n data: `0x${SELECTOR_CURRENT_TOTAL_OUT_PAYMENT}`,\n },\n {\n to: contractAddress,\n data: `0x${SELECTOR_LAST_PRICE}`,\n },\n ])\n\n const batch = decodeBatches(batchesResult)\n // A non-existent batch decodes to an all-zero tuple — a definitive \"no such\n // batch on this chain\", distinct from an RPC error below.\n if (batch.owner === ZERO_ADDRESS) {\n return { status: \"not-found\" }\n }\n\n return {\n status: \"found\",\n state: {\n batch,\n currentTotalOutPayment: decodeUint(outPaymentResult),\n lastPrice: decodeUint(lastPriceResult),\n },\n }\n } catch {\n return { status: \"error\" }\n }\n}\n\nexport async function fetchOnChainBatchState(\n rpcUrl: string,\n batchId: string,\n contractAddress: string = POSTAGE_STAMP_CONTRACT_ADDRESS,\n): Promise<OnChainBatchState | undefined> {\n const result = await fetchOnChainBatchStateResult(\n rpcUrl,\n batchId,\n contractAddress,\n )\n return result.status === \"found\" ? result.state : undefined\n}\n\n/**\n * Computes a batch's remaining TTL in seconds from an on-chain snapshot, using\n * the same integer math as the storage-incentives contract and Bee node.\n *\n * @returns Remaining seconds (`0` when expired), or `undefined` when the price\n * is zero — the batch then drains at no cost and has no finite expiry,\n * which we cannot render as a date. On mainnet the PriceOracle floor\n * keeps `lastPrice` well above zero, so this only guards dev chains.\n */\nexport function calculateContractTTLSeconds(\n state: OnChainBatchState,\n): number | undefined {\n const remaining = state.batch.normalisedBalance - state.currentTotalOutPayment\n if (remaining <= 0n) {\n return 0\n }\n if (state.lastPrice <= 0n) {\n return undefined\n }\n const remainingBlocks = remaining / state.lastPrice\n return Number(remainingBlocks * BigInt(GNOSIS_BLOCK_TIME))\n}\n\n/**\n * Convenience wrapper: fetches a batch's on-chain state and returns its\n * remaining TTL in seconds. This is the contract-backed counterpart to\n * {@link fetchBatchTTL} (which reads the Bee node), and unlike it works for any\n * batch the contract knows about, regardless of whether the configured Bee node\n * tracks it.\n *\n * @param contractAddress - PostageStamp contract address (defaults to mainnet;\n * override for a local dev chain).\n * @returns Remaining TTL in seconds (`0` when expired), or `undefined` when the\n * batch is unknown, the RPC request fails, or the price is zero.\n */\nexport async function fetchBatchTTLFromContract(\n rpcUrl: string,\n batchId: string,\n contractAddress: string = POSTAGE_STAMP_CONTRACT_ADDRESS,\n): Promise<number | undefined> {\n const state = await fetchOnChainBatchState(rpcUrl, batchId, contractAddress)\n if (state === undefined) {\n return undefined\n }\n return calculateContractTTLSeconds(state)\n}\n\n/**\n * Resolves a batch's remaining TTL (seconds) from the most authoritative source\n * available, in order:\n * 1. The PostageStamp contract, read directly by batchId — ground truth that\n * works for any batch, even one the configured Bee node has never seen.\n * 2. The Bee node's `batchTTL` — also live chain state, but only for batches\n * the node tracks.\n *\n * This is the single canonical \"best remaining TTL\" lookup; callers layer a\n * price-based approximation ({@link calculateTTLSeconds}) on top when both\n * sources return `undefined`.\n *\n * @param gnosisRpcUrl - Gnosis Chain JSON-RPC URL (for the contract read)\n * @param beeUrl - Bee node URL (fallback)\n * @param batchId - 32-byte hex batch ID, with or without `0x` prefix\n * @param localContractAddress - PostageStamp address for a local dev chain. Only\n * honoured when `gnosisRpcUrl` targets a local node; against any remote\n * RPC the Gnosis mainnet deployment is used regardless (see\n * {@link resolvePostageStampContractAddress}).\n * @returns Remaining TTL in seconds, or `undefined` if neither source can answer.\n */\nexport async function fetchAuthoritativeBatchTTL(\n gnosisRpcUrl: string,\n beeUrl: string,\n batchId: string,\n localContractAddress?: string,\n): Promise<number | undefined> {\n const contractAddress = resolvePostageStampContractAddress(\n gnosisRpcUrl,\n localContractAddress,\n )\n return (\n (await fetchBatchTTLFromContract(gnosisRpcUrl, batchId, contractAddress)) ??\n (await fetchBatchTTL(beeUrl, batchId))\n )\n}\n\n/**\n * Resolves a batch's existence and remaining TTL, treating the on-chain contract\n * as the source of truth. A contract `not-found` is authoritative — a Bee node\n * that happens to track the batch cannot override the chain. The Bee node is\n * consulted only when the contract read itself failed (RPC unreachable), as a\n * best-effort second opinion.\n *\n * Unlike {@link fetchAuthoritativeBatchTTL} (best-effort TTL, falls back to Bee\n * even on contract `not-found`), this is the stricter lookup for existence-aware\n * UI.\n *\n * @param localContractAddress - PostageStamp address for a local dev chain; only\n * honoured against a local RPC (see {@link resolvePostageStampContractAddress}).\n */\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Time and Session Constants\n *\n * Centralized constants for time units and default session durations.\n */\n\n// ============================================================================\n// Time Units (in milliseconds)\n// ============================================================================\n\nexport const SECOND = 1_000\nexport const MINUTE = 60 * SECOND\nexport const HOUR = 60 * MINUTE\nexport const DAY = 24 * HOUR\n\n// ============================================================================\n// Session Defaults\n// ============================================================================\n\n/**\n * A saved session lets a site keep writing until its next load notices a\n * revoke, so it is kept short. An account can set its own.\n */\nexport const DEFAULT_SESSION_DURATION = 7 * DAY\n\n/**\n * How long a connection to a dApp lasts, in ms: the account's own setting, or\n * the default above.\n *\n * One rule with two callers on opposite sides of the handover — the popup\n * writes `connectedUntil` from it when it saves the connection\n * (`ui/src/lib/connect-handshake.ts`), and a partitioned proxy recomputes the\n * same deadline when it hydrates (`hydratePartitionAccount`). Two copies of\n * `?? DEFAULT_SESSION_DURATION` is one copy that goes stale when the rule\n * gains a clamp or a floor (#590).\n */\nexport function appSessionDuration(settings?: {\n appSessionDuration?: number\n}): number {\n return settings?.appSessionDuration ?? DEFAULT_SESSION_DURATION\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { secp256k1 } from \"@noble/curves/secp256k1\"\nimport { Binary } from \"cafe-utility\"\n// An ACT access key is the same 32 random bytes a chunk encryption key is.\n// Re-exported so ACT callers keep reaching for it beside the rest of the ACT crypto.\nimport { generateRandomKey } from \"../../chunk\"\n\nexport { generateRandomKey }\n\n// Key derivation nonces\nconst LOOKUP_KEY_NONCE = 0x00\nconst ACCESS_KEY_DECRYPTION_NONCE = 0x01\n\n// Size constants\nconst PRIVATE_KEY_SIZE = 32\nconst PUBLIC_KEY_COORD_SIZE = 32\nconst COMPRESSED_PUBLIC_KEY_SIZE = 33\nconst UNCOMPRESSED_PREFIX = 0x04\nconst KEY_SIZE = 32\nconst COUNTER_SIZE = 4\n\n/**\n * Derive public key from private key\n */\nexport function publicKeyFromPrivate(privKey: Uint8Array): {\n x: Uint8Array\n y: Uint8Array\n} {\n if (privKey.length !== PRIVATE_KEY_SIZE) {\n throw new Error(`Private key must be ${PRIVATE_KEY_SIZE} bytes`)\n }\n\n const point = secp256k1.getPublicKey(privKey, false)\n\n return {\n x: point.slice(1, 1 + PUBLIC_KEY_COORD_SIZE),\n y: point.slice(1 + PUBLIC_KEY_COORD_SIZE),\n }\n}\n\n/**\n * Compute ECDH shared secret (x-coordinate of shared point)\n *\n * The public key point is validated to be on secp256k1 (rejects off-curve\n * points and the point at infinity) before multiplying — feeding an\n * unvalidated point to scalar multiplication would enable an invalid-curve\n * attack on the private key.\n */\nexport function ecdhSharedSecret(\n privKey: Uint8Array,\n pubX: Uint8Array,\n pubY: Uint8Array,\n): Uint8Array {\n if (privKey.length !== PRIVATE_KEY_SIZE) {\n throw new Error(`Private key must be ${PRIVATE_KEY_SIZE} bytes`)\n }\n if (\n pubX.length !== PUBLIC_KEY_COORD_SIZE ||\n pubY.length !== PUBLIC_KEY_COORD_SIZE\n ) {\n throw new Error(\n `Public key coordinates must be ${PUBLIC_KEY_COORD_SIZE} bytes each`,\n )\n }\n\n const pubKey = new Uint8Array(1 + 2 * PUBLIC_KEY_COORD_SIZE)\n pubKey[0] = UNCOMPRESSED_PREFIX\n pubKey.set(pubX, 1)\n pubKey.set(pubY, 1 + PUBLIC_KEY_COORD_SIZE)\n\n // getSharedSecret validates the point and rejects the point at infinity;\n // compressed output is prefix || x, so slice off the prefix byte\n return secp256k1.getSharedSecret(privKey, pubKey, true).slice(1)\n}\n\n/**\n * Derive lookup key and access key decryption key from ECDH shared secret\n */\nexport function deriveKeys(\n privKey: Uint8Array,\n pubX: Uint8Array,\n pubY: Uint8Array,\n): { lookupKey: Uint8Array; accessKeyDecryptionKey: Uint8Array } {\n const sharedSecret = ecdhSharedSecret(privKey, pubX, pubY)\n\n // lookupKey = keccak256(sharedX || 0x00)\n const lookupKeyInput = new Uint8Array(sharedSecret.length + 1)\n lookupKeyInput.set(sharedSecret)\n lookupKeyInput[sharedSecret.length] = LOOKUP_KEY_NONCE\n const lookupKey = Binary.keccak256(lookupKeyInput)\n\n // accessKeyDecryptionKey = keccak256(sharedX || 0x01)\n const akdKeyInput = new Uint8Array(sharedSecret.length + 1)\n akdKeyInput.set(sharedSecret)\n akdKeyInput[sharedSecret.length] = ACCESS_KEY_DECRYPTION_NONCE\n const accessKeyDecryptionKey = Binary.keccak256(akdKeyInput)\n\n return { lookupKey, accessKeyDecryptionKey }\n}\n\n/**\n * Counter-mode encryption/decryption\n * Matches Bee's Go implementation (bee/pkg/encryption/encryption.go:134-168)\n * For each 32-byte block i: data[i] XOR keccak256(keccak256(key || uint32LE(i)))\n */\nexport function counterModeEncrypt(\n data: Uint8Array,\n key: Uint8Array,\n): Uint8Array {\n if (key.length !== KEY_SIZE) {\n throw new Error(`Key must be ${KEY_SIZE} bytes`)\n }\n\n const result = new Uint8Array(data.length)\n const numBlocks = Math.ceil(data.length / KEY_SIZE)\n\n for (let i = 0; i < numBlocks; i++) {\n // Create counter input: key || uint32LE(i)\n // Must match Bee's Go implementation which uses binary.LittleEndian.PutUint32\n const counterInput = new Uint8Array(key.length + COUNTER_SIZE)\n counterInput.set(key)\n // LITTLE ENDIAN counter (matches Bee's binary.LittleEndian.PutUint32)\n counterInput[key.length] = i & 0xff\n counterInput[key.length + 1] = (i >> 8) & 0xff\n counterInput[key.length + 2] = (i >> 16) & 0xff\n counterInput[key.length + 3] = (i >> 24) & 0xff\n\n // First hash: keccak256(key || counter)\n const ctrHash = Binary.keccak256(counterInput)\n\n // Second hash for \"selective disclosure\" (matches Bee's implementation)\n const keyStream = Binary.keccak256(ctrHash)\n\n // XOR data block with keystream\n const blockStart = i * KEY_SIZE\n const blockEnd = Math.min(blockStart + KEY_SIZE, data.length)\n\n for (let j = blockStart; j < blockEnd; j++) {\n result[j] = data[j] ^ keyStream[j - blockStart]\n }\n }\n\n return result\n}\n\n/**\n * Counter-mode decryption (symmetric with encryption)\n */\nexport const counterModeDecrypt = counterModeEncrypt\n\n/**\n * Parse compressed public key (33 bytes) to uncompressed coordinates\n */\nexport function publicKeyFromCompressed(compressed: Uint8Array): {\n x: Uint8Array\n y: Uint8Array\n} {\n if (compressed.length !== COMPRESSED_PUBLIC_KEY_SIZE) {\n throw new Error(\n `Compressed public key must be ${COMPRESSED_PUBLIC_KEY_SIZE} bytes`,\n )\n }\n\n const prefix = compressed[0]\n if (prefix !== 0x02 && prefix !== 0x03) {\n throw new Error(\"Invalid compressed public key prefix\")\n }\n\n // fromHex validates the point is on the curve (rejects x with no valid y)\n const point = secp256k1.ProjectivePoint.fromHex(compressed).toRawBytes(false)\n\n return {\n x: point.slice(1, 1 + PUBLIC_KEY_COORD_SIZE),\n y: point.slice(1 + PUBLIC_KEY_COORD_SIZE),\n }\n}\n\n/**\n * Compress public key to 33 bytes\n */\nexport function compressPublicKey(x: Uint8Array, y: Uint8Array): Uint8Array {\n if (\n x.length !== PUBLIC_KEY_COORD_SIZE ||\n y.length !== PUBLIC_KEY_COORD_SIZE\n ) {\n throw new Error(\n `Public key coordinates must be ${PUBLIC_KEY_COORD_SIZE} bytes each`,\n )\n }\n\n const prefix = (y[y.length - 1] & 1) === 0 ? 0x02 : 0x03\n\n const result = new Uint8Array(COMPRESSED_PUBLIC_KEY_SIZE)\n result[0] = prefix\n result.set(x, 1)\n\n return result\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * ACT (Access Control Tries) Data Structure\n *\n * Bee-compatible Simple Manifest (JSON) format for storing ACT entries.\n * The ACT stores lookup key -> encrypted access key mappings.\n *\n * Bee's ACT format is a Simple Manifest with structure:\n * {\n * \"entries\": {\n * \"<lookupKeyHex>\": {\n * \"reference\": \"<encryptedAccessKeyHex>\",\n * \"metadata\": {}\n * }\n * }\n * }\n *\n * All other data (publisher public key, encrypted reference, grantee list)\n * is stored separately and passed out-of-band.\n */\n\nimport { hexToUint8Array, uint8ArrayToHex } from \"../../utils/hex\"\n\n/**\n * Single ACT entry: lookup key + encrypted access key\n */\nexport interface ActEntry {\n lookupKey: Uint8Array // 32 bytes\n encryptedAccessKey: Uint8Array // 32 bytes\n}\n\n/**\n * Bee's Simple Manifest entry format\n */\ninterface SimpleManifestEntry {\n reference: string\n metadata?: Record<string, string>\n}\n\n/**\n * Bee's Simple Manifest format for ACT\n */\ninterface SimpleManifest {\n entries: Record<string, SimpleManifestEntry>\n}\n\n/**\n * Serialize ACT entries to JSON bytes (Bee-compatible Simple Manifest format)\n *\n * @param entries - Array of ACT entries\n * @returns JSON-encoded bytes\n */\nexport function serializeAct(entries: ActEntry[]): Uint8Array {\n const manifest: SimpleManifest = {\n entries: {},\n }\n\n for (const entry of entries) {\n // Path is the hex-encoded lookup key (64 chars)\n const path = uint8ArrayToHex(entry.lookupKey)\n // Reference is the hex-encoded encrypted access key (64 chars)\n manifest.entries[path] = {\n reference: uint8ArrayToHex(entry.encryptedAccessKey),\n metadata: {},\n }\n }\n\n const json = JSON.stringify(manifest)\n return new TextEncoder().encode(json)\n}\n\n/**\n * Deserialize ACT entries from JSON bytes (Bee-compatible Simple Manifest format)\n *\n * @param data - JSON-encoded bytes\n * @returns Array of ACT entries\n */\nexport function deserializeAct(data: Uint8Array): ActEntry[] {\n const json = new TextDecoder().decode(data)\n const manifest: SimpleManifest = JSON.parse(json)\n\n const entries: ActEntry[] = []\n\n for (const [path, entry] of Object.entries(manifest.entries)) {\n entries.push({\n lookupKey: hexToUint8Array(path),\n encryptedAccessKey: hexToUint8Array(entry.reference),\n })\n }\n\n return entries\n}\n\n/**\n * Find an ACT entry by lookup key\n *\n * @param entries - Array of ACT entries\n * @param lookupKey - 32-byte lookup key\n * @returns The entry if found, undefined otherwise\n */\nexport function findEntryByLookupKey(\n entries: ActEntry[],\n lookupKey: Uint8Array,\n): ActEntry | undefined {\n return entries.find((entry) => {\n if (entry.lookupKey.length !== lookupKey.length) return false\n for (let i = 0; i < lookupKey.length; i++) {\n if (entry.lookupKey[i] !== lookupKey[i]) return false\n }\n return true\n })\n}\n\n/**\n * Check if two public keys are equal\n */\nexport function publicKeysEqual(\n a: { x: Uint8Array; y: Uint8Array },\n b: { x: Uint8Array; y: Uint8Array },\n): boolean {\n if (a.x.length !== b.x.length || a.y.length !== b.y.length) return false\n for (let i = 0; i < a.x.length; i++) {\n if (a.x[i] !== b.x[i]) return false\n }\n for (let i = 0; i < a.y.length; i++) {\n if (a.y[i] !== b.y[i]) return false\n }\n return true\n}\n\n/**\n * Helper to collect all ACT entries from JSON-encoded ACT data\n *\n * This is a convenience function for working with JSON ACT data directly from storage.\n *\n * @param actData - JSON-encoded ACT data from storage\n * @returns Array of ACT entries\n */\nexport function collectActEntriesFromJson(actData: Uint8Array): ActEntry[] {\n return deserializeAct(actData)\n}\n\n/**\n * Helper to find an ACT entry by lookup key from JSON-encoded ACT data\n *\n * This is a convenience function for working with JSON ACT data directly from storage.\n *\n * @param actData - JSON-encoded ACT data from storage\n * @param lookupKey - 32-byte lookup key to search for\n * @returns The encrypted access key if found, undefined otherwise\n */\nexport function findActEntryByKey(\n actData: Uint8Array,\n lookupKey: Uint8Array,\n): Uint8Array | undefined {\n const entries = deserializeAct(actData)\n const entry = findEntryByLookupKey(entries, lookupKey)\n return entry?.encryptedAccessKey\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { Binary } from \"cafe-utility\"\nimport { counterModeEncrypt, counterModeDecrypt } from \"./crypto\"\nimport { publicKeysEqual } from \"./act\"\n\n// Grantee list format constants\nconst UNCOMPRESSED_PUBLIC_KEY_SIZE = 65 // 0x04 prefix + 32 byte X + 32 byte Y\nconst UNCOMPRESSED_PREFIX = 0x04\nconst PUBLIC_KEY_COORD_SIZE = 32\n\n// Grantee list encryption key derivation suffix\nconst GRANTEE_LIST_KEY_SUFFIX = new TextEncoder().encode(\"act-grantee-list\")\n\n/**\n * Public key in uncompressed format\n */\nexport interface UncompressedPublicKey {\n x: Uint8Array // 32 bytes\n y: Uint8Array // 32 bytes\n}\n\n/**\n * Derive the encryption key for the grantee list\n *\n * This is different from the legacy format key derivation.\n * The grantee list is encrypted with a key derived from the publisher's private key.\n */\nexport function deriveGranteeListEncryptionKey(\n publisherPrivKey: Uint8Array,\n): Uint8Array {\n const input = Binary.concatBytes(publisherPrivKey, GRANTEE_LIST_KEY_SUFFIX)\n return Binary.keccak256(input)\n}\n\n/**\n * Serialize grantee list to Bee-compatible format\n *\n * Bee stores grantees as concatenated 65-byte uncompressed secp256k1 public keys.\n * Format: [0x04 || X (32 bytes) || Y (32 bytes)] for each key\n *\n * @param grantees - Array of public keys with x and y coordinates\n * @returns Serialized grantee list\n */\nexport function serializeGranteeList(\n grantees: UncompressedPublicKey[],\n): Uint8Array {\n const result = new Uint8Array(grantees.length * UNCOMPRESSED_PUBLIC_KEY_SIZE)\n\n for (let i = 0; i < grantees.length; i++) {\n const offset = i * UNCOMPRESSED_PUBLIC_KEY_SIZE\n\n // 0x04 prefix for uncompressed point\n result[offset] = UNCOMPRESSED_PREFIX\n\n // X coordinate (32 bytes)\n result.set(grantees[i].x, offset + 1)\n\n // Y coordinate (32 bytes)\n result.set(grantees[i].y, offset + 1 + PUBLIC_KEY_COORD_SIZE)\n }\n\n return result\n}\n\n/**\n * Deserialize grantee list from Bee-compatible format\n *\n * @param data - Serialized grantee list (concatenated 65-byte uncompressed keys)\n * @returns Array of public keys with x and y coordinates\n */\nexport function deserializeGranteeList(\n data: Uint8Array,\n): UncompressedPublicKey[] {\n if (data.length === 0) {\n return []\n }\n\n if (data.length % UNCOMPRESSED_PUBLIC_KEY_SIZE !== 0) {\n throw new Error(\n `Invalid grantee list length: ${data.length} is not a multiple of ${UNCOMPRESSED_PUBLIC_KEY_SIZE}`,\n )\n }\n\n const granteeCount = data.length / UNCOMPRESSED_PUBLIC_KEY_SIZE\n const grantees: UncompressedPublicKey[] = []\n\n for (let i = 0; i < granteeCount; i++) {\n const offset = i * UNCOMPRESSED_PUBLIC_KEY_SIZE\n\n // Verify 0x04 prefix\n if (data[offset] !== UNCOMPRESSED_PREFIX) {\n throw new Error(\n `Invalid public key prefix at index ${i}: expected 0x04, got 0x${data[offset].toString(16)}`,\n )\n }\n\n // Extract X coordinate\n const x = data.slice(offset + 1, offset + 1 + PUBLIC_KEY_COORD_SIZE)\n\n // Extract Y coordinate\n const y = data.slice(\n offset + 1 + PUBLIC_KEY_COORD_SIZE,\n offset + UNCOMPRESSED_PUBLIC_KEY_SIZE,\n )\n\n grantees.push({ x, y })\n }\n\n return grantees\n}\n\n/**\n * Encrypt a serialized grantee list for storage\n *\n * @param granteeList - Serialized grantee list\n * @param publisherPrivKey - Publisher's private key for key derivation\n * @returns Encrypted grantee list\n */\nexport function encryptGranteeList(\n granteeList: Uint8Array,\n publisherPrivKey: Uint8Array,\n): Uint8Array {\n const encryptionKey = deriveGranteeListEncryptionKey(publisherPrivKey)\n return counterModeEncrypt(granteeList, encryptionKey)\n}\n\n/**\n * Decrypt an encrypted grantee list\n *\n * @param encryptedList - Encrypted grantee list\n * @param publisherPrivKey - Publisher's private key for key derivation\n * @returns Decrypted serialized grantee list\n */\nexport function decryptGranteeList(\n encryptedList: Uint8Array,\n publisherPrivKey: Uint8Array,\n): Uint8Array {\n const encryptionKey = deriveGranteeListEncryptionKey(publisherPrivKey)\n return counterModeDecrypt(encryptedList, encryptionKey)\n}\n\n/**\n * Serialize and encrypt grantee list in one step\n *\n * @param grantees - Array of public keys\n * @param publisherPrivKey - Publisher's private key\n * @returns Encrypted serialized grantee list ready for upload\n */\nexport function serializeAndEncryptGranteeList(\n grantees: UncompressedPublicKey[],\n publisherPrivKey: Uint8Array,\n): Uint8Array {\n const serialized = serializeGranteeList(grantees)\n return encryptGranteeList(serialized, publisherPrivKey)\n}\n\n/**\n * Decrypt and deserialize grantee list in one step\n *\n * @param encryptedList - Encrypted serialized grantee list\n * @param publisherPrivKey - Publisher's private key\n * @returns Array of public keys\n */\nexport function decryptAndDeserializeGranteeList(\n encryptedList: Uint8Array,\n publisherPrivKey: Uint8Array,\n): UncompressedPublicKey[] {\n const decrypted = decryptGranteeList(encryptedList, publisherPrivKey)\n return deserializeGranteeList(decrypted)\n}\n\n/**\n * Add grantees to an existing encrypted grantee list\n *\n * @param encryptedList - Existing encrypted grantee list\n * @param newGrantees - New grantees to add\n * @param publisherPrivKey - Publisher's private key\n * @returns New encrypted grantee list with added grantees\n */\nexport function addToGranteeList(\n encryptedList: Uint8Array,\n newGrantees: UncompressedPublicKey[],\n publisherPrivKey: Uint8Array,\n): Uint8Array {\n // Decrypt existing list\n const existingGrantees =\n encryptedList.length > 0\n ? decryptAndDeserializeGranteeList(encryptedList, publisherPrivKey)\n : []\n\n // Add new grantees\n const updatedGrantees = [...existingGrantees, ...newGrantees]\n\n // Re-encrypt\n return serializeAndEncryptGranteeList(updatedGrantees, publisherPrivKey)\n}\n\n/**\n * Remove grantees from an existing encrypted grantee list\n *\n * @param encryptedList - Existing encrypted grantee list\n * @param revokeGrantees - Grantees to remove\n * @param publisherPrivKey - Publisher's private key\n * @returns New encrypted grantee list with removed grantees\n */\nexport function removeFromGranteeList(\n encryptedList: Uint8Array,\n revokeGrantees: UncompressedPublicKey[],\n publisherPrivKey: Uint8Array,\n): Uint8Array {\n // Decrypt existing list\n const existingGrantees = decryptAndDeserializeGranteeList(\n encryptedList,\n publisherPrivKey,\n )\n\n // Filter out revoked grantees\n const remainingGrantees = existingGrantees.filter((existing) => {\n return !revokeGrantees.some((revoke) => publicKeysEqual(existing, revoke))\n })\n\n // Re-encrypt\n return serializeAndEncryptGranteeList(remainingGrantees, publisherPrivKey)\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * ACT History Management\n *\n * This module provides timestamped versioning of ACT entries, matching Bee's\n * approach to tracking ACT versions over time.\n *\n * Key concepts:\n * - Each ACT update creates a new history entry\n * - Entries are keyed by reversed timestamp (MaxInt64 - timestamp)\n * - History enables looking up ACT state at any point in time\n * - Encrypted grantee list reference is stored in metadata\n *\n * This implementation uses the MantarayNode class from bee-js to produce\n * Bee-compatible binary manifests with the proper version hash header.\n *\n * IMPORTANT: Mantaray manifests are hierarchical - each child node must be\n * uploaded separately to Swarm. The root node references children by their\n * content addresses (selfAddress). To read entries, all child nodes must be\n * loaded recursively.\n */\n\nimport { MantarayNode } from \"@ethersphere/core-sdk\"\nimport { hexToUint8Array, uint8ArrayToHex } from \"../../utils/hex\"\n\n// Constants\nconst MAX_INT64 = BigInt(\"9223372036854775807\")\nconst ENCODER = new TextEncoder()\n\n// Bee uses \"encryptedglref\" as the metadata key for encrypted grantee list reference\nconst ENCRYPTED_GRANTEE_LIST_METADATA_KEY = \"encryptedglref\"\n\n// Ensure monotonic seconds so multiple ACT updates within the same second\n// don't collide on the same history key (mantaray requires unique paths).\nlet lastTimestamp = 0\n\n/**\n * Single history entry metadata\n */\nexport interface HistoryEntryMetadata {\n actReference: string // Reference to the ACT manifest\n encryptedGranteeListRef?: string // Reference to encrypted grantee list (publisher only)\n}\n\n/**\n * History entry with timestamp\n */\nexport interface HistoryEntry {\n timestamp: number // Unix timestamp in seconds\n metadata: HistoryEntryMetadata\n}\n\n/**\n * Result of serializing a history tree\n */\nexport interface SerializedHistoryTree {\n blobs: Map<string, Uint8Array> // Content address -> serialized data\n rootReference: string // Reference to the root node\n}\n\n/**\n * Result of saving a history tree\n */\nexport interface SaveHistoryTreeResult {\n rootReference: string // Reference to the root node (from Bee)\n tagUid?: number // Tag UID from root upload\n}\n\n/**\n * Calculate reversed timestamp key for history lookup\n *\n * Bee uses reversed timestamps so that the latest entry sorts first.\n * Key = MaxInt64 - timestamp\n *\n * @param timestamp - Unix timestamp in seconds\n * @returns Reversed timestamp as string (for use as path)\n */\nexport function calculateReversedTimestamp(timestamp: number): string {\n const reversed = MAX_INT64 - BigInt(timestamp)\n return reversed.toString()\n}\n\n/**\n * Calculate original timestamp from reversed key\n *\n * @param reversedKey - Reversed timestamp string\n * @returns Original Unix timestamp in seconds\n */\nexport function calculateOriginalTimestamp(reversedKey: string): number {\n const reversed = BigInt(reversedKey)\n const original = MAX_INT64 - reversed\n return Number(original)\n}\n\n/**\n * Create a new empty history manifest using MantarayNode\n */\nexport function createHistoryManifest(): MantarayNode {\n return new MantarayNode()\n}\n\n/**\n * Add an entry to the history manifest\n *\n * This mutates the manifest in place by adding a fork.\n *\n * @param manifest - Existing history manifest (MantarayNode)\n * @param timestamp - Unix timestamp for this entry\n * @param actReference - Reference to the ACT manifest (hex string)\n * @param encryptedGranteeListRef - Optional reference to encrypted grantee list (hex string)\n */\nexport function addHistoryEntry(\n manifest: MantarayNode,\n timestamp: number,\n actReference: string,\n encryptedGranteeListRef?: string,\n): void {\n const path = calculateReversedTimestamp(timestamp)\n const reference = hexToUint8Array(actReference)\n const metadata = encryptedGranteeListRef\n ? { [ENCRYPTED_GRANTEE_LIST_METADATA_KEY]: encryptedGranteeListRef }\n : undefined\n manifest.addFork(ENCODER.encode(path), reference, metadata)\n}\n\n/**\n * Get the latest history entry (most recent timestamp)\n *\n * Since keys are reversed timestamps, the smallest key is the latest entry.\n * NOTE: This requires the manifest to have been loaded with loadRecursively()\n * or to have been populated locally with addHistoryEntry().\n *\n * @param manifest - History manifest (MantarayNode)\n * @returns Latest entry with its timestamp, or undefined if empty\n */\nexport function getLatestEntry(\n manifest: MantarayNode,\n): HistoryEntry | undefined {\n const nodes = manifest.collect()\n if (nodes.length === 0) {\n return undefined\n }\n\n // Sort by path ascending (smallest = latest timestamp due to reversal)\n nodes.sort((a, b) => a.fullPathString.localeCompare(b.fullPathString))\n\n const latest = nodes[0]\n return {\n timestamp: calculateOriginalTimestamp(latest.fullPathString),\n metadata: {\n actReference: uint8ArrayToHex(latest.targetAddress),\n encryptedGranteeListRef:\n latest.metadata?.[ENCRYPTED_GRANTEE_LIST_METADATA_KEY],\n },\n }\n}\n\n/**\n * Get entry at or before a specific timestamp\n *\n * This finds the ACT state that was valid at the given timestamp.\n * NOTE: This requires the manifest to have been loaded with loadRecursively().\n *\n * @param manifest - History manifest (MantarayNode)\n * @param timestamp - Target timestamp\n * @returns Entry at or before timestamp, or undefined if none exists\n */\nexport function getEntryAtTimestamp(\n manifest: MantarayNode,\n timestamp: number,\n): HistoryEntry | undefined {\n const targetPath = calculateReversedTimestamp(timestamp)\n const nodes = manifest.collect()\n\n if (nodes.length === 0) {\n return undefined\n }\n\n // Sort by path ascending\n nodes.sort((a, b) => a.fullPathString.localeCompare(b.fullPathString))\n\n // Find the first node with path >= targetPath (which corresponds to timestamp <= target)\n for (const node of nodes) {\n if (node.fullPathString >= targetPath) {\n return {\n timestamp: calculateOriginalTimestamp(node.fullPathString),\n metadata: {\n actReference: uint8ArrayToHex(node.targetAddress),\n encryptedGranteeListRef:\n node.metadata?.[ENCRYPTED_GRANTEE_LIST_METADATA_KEY],\n },\n }\n }\n }\n\n // If no entry at or before timestamp, return the oldest entry\n // (this is the \"first\" ACT state)\n const oldest = nodes[nodes.length - 1]\n return {\n timestamp: calculateOriginalTimestamp(oldest.fullPathString),\n metadata: {\n actReference: uint8ArrayToHex(oldest.targetAddress),\n encryptedGranteeListRef:\n oldest.metadata?.[ENCRYPTED_GRANTEE_LIST_METADATA_KEY],\n },\n }\n}\n\n/**\n * Get all history entries sorted by timestamp (newest first)\n *\n * @param manifest - History manifest (MantarayNode)\n * @returns Array of entries sorted newest first\n */\nexport function getAllEntries(manifest: MantarayNode): HistoryEntry[] {\n const nodes = manifest.collect()\n\n // Sort by path ascending (newest first due to reversed timestamps)\n nodes.sort((a, b) => a.fullPathString.localeCompare(b.fullPathString))\n\n return nodes.map((node) => ({\n timestamp: calculateOriginalTimestamp(node.fullPathString),\n metadata: {\n actReference: uint8ArrayToHex(node.targetAddress),\n encryptedGranteeListRef:\n node.metadata?.[ENCRYPTED_GRANTEE_LIST_METADATA_KEY],\n },\n }))\n}\n\n/**\n * Serialize the entire history manifest tree to individual blobs\n *\n * Mantaray manifests are hierarchical - each node is stored at its content\n * address. This function returns all blobs that need to be uploaded, keyed\n * by their content addresses.\n *\n * @deprecated Use saveHistoryTreeRecursively instead which uploads bottom-up\n * and uses Bee's actual returned references to avoid address mismatches.\n *\n * @param manifest - History manifest (MantarayNode)\n * @returns Map of content address -> serialized data, plus root reference\n */\nexport async function serializeHistoryTree(\n manifest: MantarayNode,\n): Promise<SerializedHistoryTree> {\n const blobs = new Map<string, Uint8Array>()\n\n async function marshalRecursively(node: MantarayNode): Promise<void> {\n // First, marshal all child nodes\n for (const fork of node.forks.values()) {\n await marshalRecursively(fork.node)\n }\n\n // Now marshal this node (which will use children's selfAddresses)\n const data = await node.marshal()\n const selfAddress = await node.calculateSelfAddress()\n node.selfAddress = selfAddress.toUint8Array()\n blobs.set(selfAddress.toHex(), data)\n }\n\n await marshalRecursively(manifest)\n\n const rootRef = uint8ArrayToHex(manifest.selfAddress!)\n return { blobs, rootReference: rootRef }\n}\n\n/**\n * Upload callback type for saveHistoryTreeRecursively\n */\nexport type UploadCallback = (\n data: Uint8Array,\n isRoot: boolean,\n) => Promise<{ reference: string; tagUid?: number }>\n\n/**\n * Save the entire history manifest tree by uploading bottom-up\n *\n * This function uploads nodes in the correct order (children before parents)\n * and uses Bee's actual returned references to update selfAddress before\n * marshaling parents. This avoids address mismatches between local hash\n * computation and Bee's storage.\n *\n * The flow mirrors MantarayNode.saveRecursively() from bee-js:\n * 1. Recursively save all child forks first\n * 2. Marshal this node (which uses children's updated selfAddress)\n * 3. Upload and set selfAddress from Bee's response\n *\n * @param manifest - History manifest (MantarayNode)\n * @param uploadFn - Callback to upload data, returns reference from Bee\n * @returns Root reference from Bee and optional tag UID\n */\nexport async function saveHistoryTreeRecursively(\n manifest: MantarayNode,\n uploadFn: UploadCallback,\n): Promise<SaveHistoryTreeResult> {\n async function saveRecursively(\n node: MantarayNode,\n isRoot: boolean,\n ): Promise<{ reference: string; tagUid?: number }> {\n // First, save all child forks recursively\n for (const fork of node.forks.values()) {\n await saveRecursively(fork.node, false)\n }\n\n // Now marshal this node - children's selfAddress should be set from their uploads\n const data = await node.marshal()\n\n // Upload and get Bee's actual reference\n const result = await uploadFn(data, isRoot)\n\n // Update selfAddress with Bee's reference (critical for parent marshaling)\n node.selfAddress = hexToUint8Array(result.reference)\n\n return result\n }\n\n const result = await saveRecursively(manifest, true)\n\n return {\n rootReference: result.reference,\n tagUid: result.tagUid,\n }\n}\n\n/**\n * Serialize history manifest root to Mantaray binary format\n *\n * @deprecated Use serializeHistoryTree for proper Mantaray serialization\n * @param manifest - History manifest (MantarayNode)\n * @returns Serialized root manifest as Uint8Array\n */\nexport async function serializeHistory(\n manifest: MantarayNode,\n): Promise<Uint8Array> {\n return manifest.marshal()\n}\n\n/**\n * Deserialize history manifest from Mantaray binary format\n *\n * NOTE: After deserialization, call loadRecursively() or manually load\n * child nodes to populate targetAddress for entries.\n *\n * @param data - Serialized manifest\n * @param selfAddress - The reference/address of the manifest (32 bytes as Uint8Array)\n * @returns Parsed history manifest (MantarayNode)\n */\nexport function deserializeHistory(\n data: Uint8Array,\n selfAddress: Uint8Array,\n): MantarayNode {\n return MantarayNode.unmarshalFromData(data, selfAddress)\n}\n\n/**\n * Load child node data into a deserialized manifest\n *\n * After deserializing the root node, this function loads all child node data\n * so that targetAddress is available for each entry.\n *\n * @param manifest - Deserialized root manifest\n * @param loadData - Callback to load data for a given reference\n */\nexport async function loadHistoryEntries(\n manifest: MantarayNode,\n loadData: (reference: string) => Promise<Uint8Array>,\n): Promise<void> {\n async function loadRecursively(node: MantarayNode): Promise<void> {\n for (const fork of node.forks.values()) {\n if (!fork.node.selfAddress) {\n throw new Error(\"Fork node selfAddress is not set\")\n }\n\n // Load the child node data\n const childRef = uint8ArrayToHex(fork.node.selfAddress)\n const childData = await loadData(childRef)\n\n // Deserialize the child node\n const childNode = MantarayNode.unmarshalFromData(\n childData,\n fork.node.selfAddress,\n )\n\n // Copy the loaded data to the fork node\n fork.node.targetAddress = childNode.targetAddress\n fork.node.forks = childNode.forks\n fork.node.obfuscationKey = childNode.obfuscationKey\n\n // Fix parent pointers for nested forks\n for (const nestedFork of fork.node.forks.values()) {\n nestedFork.node.parent = fork.node\n }\n\n // Recursively load any nested forks\n await loadRecursively(fork.node)\n }\n }\n\n await loadRecursively(manifest)\n}\n\n/**\n * Get current Unix timestamp in seconds\n */\nexport function getCurrentTimestamp(): number {\n const now = Math.floor(Date.now() / 1000)\n if (now <= lastTimestamp) {\n lastTimestamp += 1\n return lastTimestamp\n }\n lastTimestamp = now\n return now\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * ACT (Access Control Tries) - Bee-Compatible Implementation\n *\n * This module provides client-side ACT operations with Bee-compatible\n * Simple Manifest (JSON) format:\n * - ACT manifest (JSON with lookup key -> encrypted access key mappings)\n * - Encrypted grantee list (stored separately)\n * - History manifest (tracks ACT versions over time)\n */\n\nimport type { Bee, BeeRequestOptions, UploadOptions } from \"@ethersphere/bee-js\"\nimport type { UploadProgress } from \"../types\"\nimport { uploadData, type UploadTarget } from \"../upload\"\nimport { downloadDataWithChunkAPI } from \"../download-data\"\nimport { hexToUint8Array, uint8ArrayToHex } from \"../../utils/hex\"\nimport {\n deriveKeys,\n counterModeEncrypt,\n counterModeDecrypt,\n publicKeyFromPrivate,\n generateRandomKey,\n publicKeyFromCompressed,\n compressPublicKey,\n} from \"./crypto\"\n\ntype ActUploadOptions = UploadOptions & { beeCompatible?: boolean }\n\n/**\n * The private keys one session may read or publish with. A proxy holds two —\n * the origin-bound app key and the account-wide sharing key (#519) — and does\n * not know which one a share was made out to, so every reader/publisher entry\n * point takes candidates and uses the one the ACT names.\n */\nexport type ActKeyCandidates = Uint8Array | Uint8Array[]\n\nfunction candidateKeys(keys: ActKeyCandidates): Uint8Array[] {\n return Array.isArray(keys) ? keys : [keys]\n}\n\n/**\n * The candidate that published the ACT: the one whose self-entry (ECDH with\n * its own public key) the manifest carries.\n */\nfunction findPublisherKey(\n entries: ActEntry[],\n keys: ActKeyCandidates,\n): {\n privateKey: Uint8Array\n derived: ReturnType<typeof deriveKeys>\n entry: ActEntry\n} {\n for (const privateKey of candidateKeys(keys)) {\n const pub = publicKeyFromPrivate(privateKey)\n const derived = deriveKeys(privateKey, pub.x, pub.y)\n const entry = findEntryByLookupKey(entries, derived.lookupKey)\n if (entry) {\n return { privateKey, derived, entry }\n }\n }\n throw new Error(\"Cannot find publisher entry in ACT\")\n}\n\nimport {\n serializeAct,\n deserializeAct,\n findEntryByLookupKey,\n publicKeysEqual,\n type ActEntry,\n} from \"./act\"\nimport {\n serializeAndEncryptGranteeList,\n decryptAndDeserializeGranteeList,\n type UncompressedPublicKey,\n} from \"./grantee-list\"\nimport {\n createHistoryManifest,\n addHistoryEntry,\n getLatestEntry,\n getEntryAtTimestamp,\n saveHistoryTreeRecursively,\n deserializeHistory,\n loadHistoryEntries,\n getCurrentTimestamp,\n} from \"./history\"\n\n// Reference size constants\nconst REFERENCE_SIZE = 32\n\n/**\n * Result of ACT upload operation\n */\nexport interface ActUploadResult {\n encryptedReference: string // Encrypted content reference (NOT stored in ACT)\n historyReference: string // History manifest reference (root of ACT versions)\n granteeListReference: string // Encrypted grantee list reference\n publisherPubKey: string // Compressed public key for sharing with grantees\n actReference: string // Latest ACT reference (for convenience)\n tagUid?: number\n}\n\n/**\n * Result of ACT grantee modification\n */\nexport interface ActGranteeModifyResult {\n historyReference: string\n granteeListReference: string\n actReference: string\n tagUid?: number\n}\n\n/**\n * Result of ACT revocation. `encryptedReference` is returned unchanged (#496):\n * revocation drops the revoked entries but must not re-encrypt the reference.\n */\nexport interface ActRevocationResult extends ActGranteeModifyResult {\n encryptedReference: string // Unchanged content reference (see #496)\n}\n\n/**\n * Format decrypted reference - trim to 32 bytes if second half is all zeros\n */\nfunction formatDecryptedReference(decryptedRef: Uint8Array): string {\n let isShortRef = true\n for (let i = REFERENCE_SIZE; i < decryptedRef.length; i++) {\n if (decryptedRef[i] !== 0) {\n isShortRef = false\n break\n }\n }\n\n if (isShortRef) {\n return uint8ArrayToHex(decryptedRef.slice(0, REFERENCE_SIZE))\n }\n return uint8ArrayToHex(decryptedRef)\n}\n\n/**\n * Create an ACT-protected upload\n *\n * This creates:\n * 1. ACT manifest (JSON Simple Manifest with lookup key -> encrypted access key mappings)\n * 2. Encrypted grantee list (for publisher management)\n * 3. History manifest (tracks ACT versions over time)\n *\n * @param config - Upload configuration (stamper or subsidised gateway)\n * @param contentReference - The reference to protect (32 or 64 bytes)\n * @param publisherPrivateKey - Publisher's private key (32 bytes)\n * @param granteePublicKeys - Array of grantee public keys\n * @param options - Upload options\n * @param requestOptions - Bee request options\n * @param onProgress - Progress callback\n * @returns Multiple references for ACT\n */\nexport async function createActForContent(\n target: UploadTarget,\n contentReference: Uint8Array,\n publisherPrivateKey: Uint8Array,\n granteePublicKeys: Array<{ x: Uint8Array; y: Uint8Array }>,\n options?: ActUploadOptions,\n requestOptions?: BeeRequestOptions,\n onProgress?: (progress: UploadProgress) => void,\n): Promise<ActUploadResult> {\n // Generate random access key\n const accessKey = generateRandomKey()\n\n // Encrypt the content reference with the access key\n // CTR mode preserves input length - 32-byte ref → 32-byte encrypted\n const encryptedRef = counterModeEncrypt(contentReference, accessKey)\n\n // Get publisher's public key\n const publisherPubKey = publicKeyFromPrivate(publisherPrivateKey)\n\n // Create entries for publisher and all grantees\n const entries: ActEntry[] = []\n\n // Entry for publisher (so they can decrypt their own content)\n const publisherKeys = deriveKeys(\n publisherPrivateKey,\n publisherPubKey.x,\n publisherPubKey.y,\n )\n entries.push({\n lookupKey: publisherKeys.lookupKey,\n encryptedAccessKey: counterModeEncrypt(\n accessKey,\n publisherKeys.accessKeyDecryptionKey,\n ),\n })\n\n // Entry for each grantee\n for (const granteePubKey of granteePublicKeys) {\n const granteeKeys = deriveKeys(\n publisherPrivateKey,\n granteePubKey.x,\n granteePubKey.y,\n )\n entries.push({\n lookupKey: granteeKeys.lookupKey,\n encryptedAccessKey: counterModeEncrypt(\n accessKey,\n granteeKeys.accessKeyDecryptionKey,\n ),\n })\n }\n\n // 1. Serialize and upload ACT manifest (JSON Simple Manifest format)\n const actJson = serializeAct(entries)\n\n const beeCompatible = options?.beeCompatible === true\n\n const actResult = await uploadData(target, actJson, {\n encryptionKey: beeCompatible ? undefined : true,\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n requestOptions,\n })\n\n // 2. Serialize and upload encrypted grantee list\n const encryptedGranteeList = serializeAndEncryptGranteeList(\n granteePublicKeys,\n publisherPrivateKey,\n )\n\n const granteeListResult = await uploadData(target, encryptedGranteeList, {\n encryptionKey: true,\n pin: options?.pin,\n deferred: options?.deferred,\n requestOptions,\n })\n\n // 3. Create and upload history manifest\n const timestamp = getCurrentTimestamp()\n const historyManifest = createHistoryManifest()\n addHistoryEntry(\n historyManifest,\n timestamp,\n actResult.reference,\n granteeListResult.reference,\n )\n\n // Save history tree bottom-up using Bee's actual returned references\n // This ensures parent nodes reference children by their actual storage addresses\n const historyResult = await saveHistoryTreeRecursively(\n historyManifest,\n async (data, isRoot) => {\n return uploadData(target, data, {\n encryptionKey: beeCompatible ? undefined : true,\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n onProgress: isRoot ? onProgress : undefined,\n requestOptions,\n })\n },\n )\n\n const historyReference = historyResult.rootReference\n const historyTagUid = historyResult.tagUid\n\n // Compress publisher public key for API response\n const compressedPubKey = compressPublicKey(\n publisherPubKey.x,\n publisherPubKey.y,\n )\n\n return {\n encryptedReference: uint8ArrayToHex(encryptedRef),\n historyReference,\n granteeListReference: granteeListResult.reference,\n publisherPubKey: uint8ArrayToHex(compressedPubKey),\n actReference: actResult.reference,\n tagUid: historyTagUid,\n }\n}\n\n/**\n * Decrypt an ACT-protected reference\n *\n * @param bee - Bee instance\n * @param encryptedReference - The encrypted reference (hex string)\n * @param historyReference - History manifest reference\n * @param publisherPubKeyHex - Publisher's compressed public key (hex)\n * @param readerPrivateKeys - Reader's private key(s), 32 bytes each; the first\n * one the ACT names (as a grantee, or as the publisher itself) decrypts\n * @param timestamp - Optional timestamp to look up specific ACT version\n * @param requestOptions - Bee request options\n * @returns Decrypted content reference (hex string)\n */\nexport async function decryptActReference(\n bee: Bee,\n encryptedReference: string,\n historyReference: string,\n publisherPubKeyHex: string,\n readerPrivateKeys: ActKeyCandidates,\n timestamp?: number,\n requestOptions?: BeeRequestOptions,\n): Promise<string> {\n // Parse publisher public key\n const compressedPubKey = hexToUint8Array(publisherPubKeyHex)\n const publisherPubKey = publicKeyFromCompressed(compressedPubKey)\n\n // Download history manifest\n const historyData = await downloadDataWithChunkAPI(\n bee,\n historyReference,\n undefined,\n undefined,\n requestOptions,\n )\n const historyManifest = deserializeHistory(\n historyData,\n hexToUint8Array(historyReference),\n )\n\n // Load child nodes to populate entry data\n await loadHistoryEntries(historyManifest, async (ref) => {\n return downloadDataWithChunkAPI(\n bee,\n ref,\n undefined,\n undefined,\n requestOptions,\n )\n })\n\n // Get the appropriate entry\n const entry = timestamp\n ? getEntryAtTimestamp(historyManifest, timestamp)\n : getLatestEntry(historyManifest)\n\n if (!entry) {\n throw new Error(\"No ACT entry found in history\")\n }\n\n const actReference = entry.metadata.actReference\n\n // Download ACT manifest (JSON Simple Manifest)\n const actData = await downloadDataWithChunkAPI(\n bee,\n actReference,\n undefined,\n undefined,\n requestOptions,\n )\n const entries = deserializeAct(actData)\n\n // Each candidate key, first as a grantee (ECDH with the publisher), then as\n // the publisher itself (ECDH with its own key) — all local, the manifest is\n // already here.\n for (const readerPrivateKey of candidateKeys(readerPrivateKeys)) {\n const readerPubKey = publicKeyFromPrivate(readerPrivateKey)\n for (const peer of [publisherPubKey, readerPubKey]) {\n const keys = deriveKeys(readerPrivateKey, peer.x, peer.y)\n const entry = findEntryByLookupKey(entries, keys.lookupKey)\n if (!entry) continue\n const accessKey = counterModeDecrypt(\n entry.encryptedAccessKey,\n keys.accessKeyDecryptionKey,\n )\n const decryptedRef = counterModeDecrypt(\n hexToUint8Array(encryptedReference),\n accessKey,\n )\n return formatDecryptedReference(decryptedRef)\n }\n }\n\n throw new Error(\"Access denied: no ACT entry found for this key\")\n}\n\n/**\n * Add grantees to an existing ACT\n */\nexport async function addGranteesToAct(\n target: UploadTarget,\n bee: Bee,\n historyReference: string,\n publisherCandidates: ActKeyCandidates,\n newGranteePublicKeys: Array<{ x: Uint8Array; y: Uint8Array }>,\n options?: ActUploadOptions,\n requestOptions?: BeeRequestOptions,\n onProgress?: (progress: UploadProgress) => void,\n): Promise<ActGranteeModifyResult> {\n const beeCompatible = historyReference.length === 64\n\n // Download history manifest\n const historyData = await downloadDataWithChunkAPI(\n bee,\n historyReference,\n undefined,\n undefined,\n requestOptions,\n )\n const historyManifest = deserializeHistory(\n historyData,\n hexToUint8Array(historyReference),\n )\n\n // Load child nodes to populate entry data\n await loadHistoryEntries(historyManifest, async (ref) => {\n return downloadDataWithChunkAPI(\n bee,\n ref,\n undefined,\n undefined,\n requestOptions,\n )\n })\n\n // Get latest entry\n const latestEntry = getLatestEntry(historyManifest)\n if (!latestEntry) {\n throw new Error(\"History manifest is empty\")\n }\n\n // Download current ACT (JSON Simple Manifest)\n const actData = await downloadDataWithChunkAPI(\n bee,\n latestEntry.metadata.actReference,\n undefined,\n undefined,\n requestOptions,\n )\n const entries = deserializeAct(actData)\n\n // Recover the access key with whichever candidate published the ACT\n const {\n privateKey: publisherPrivateKey,\n derived: publisherKeys,\n entry: publisherEntry,\n } = findPublisherKey(entries, publisherCandidates)\n const accessKey = counterModeDecrypt(\n publisherEntry.encryptedAccessKey,\n publisherKeys.accessKeyDecryptionKey,\n )\n\n // Create new ACT manifest with existing entries plus new grantees\n const newEntries: ActEntry[] = [...entries]\n for (const granteePubKey of newGranteePublicKeys) {\n const granteeKeys = deriveKeys(\n publisherPrivateKey,\n granteePubKey.x,\n granteePubKey.y,\n )\n newEntries.push({\n lookupKey: granteeKeys.lookupKey,\n encryptedAccessKey: counterModeEncrypt(\n accessKey,\n granteeKeys.accessKeyDecryptionKey,\n ),\n })\n }\n\n // Download and update grantee list\n let existingGrantees: UncompressedPublicKey[] = []\n if (latestEntry.metadata.encryptedGranteeListRef) {\n const encryptedList = await downloadDataWithChunkAPI(\n bee,\n latestEntry.metadata.encryptedGranteeListRef,\n undefined,\n undefined,\n requestOptions,\n )\n existingGrantees = decryptAndDeserializeGranteeList(\n encryptedList,\n publisherPrivateKey,\n )\n }\n const updatedGrantees = [...existingGrantees, ...newGranteePublicKeys]\n\n // Upload new ACT manifest (JSON Simple Manifest format)\n const newActJson = serializeAct(newEntries)\n\n const actResult = await uploadData(target, newActJson, {\n encryptionKey: beeCompatible ? undefined : true,\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n requestOptions,\n })\n\n // Upload updated grantee list\n const encryptedGranteeList = serializeAndEncryptGranteeList(\n updatedGrantees,\n publisherPrivateKey,\n )\n const granteeListResult = await uploadData(target, encryptedGranteeList, {\n encryptionKey: true,\n pin: options?.pin,\n deferred: options?.deferred,\n requestOptions,\n })\n\n // Add new history entry\n const timestamp = getCurrentTimestamp()\n addHistoryEntry(\n historyManifest,\n timestamp,\n actResult.reference,\n granteeListResult.reference,\n )\n\n // Save history tree bottom-up using Bee's actual returned references\n const historyResult = await saveHistoryTreeRecursively(\n historyManifest,\n async (data, isRoot) => {\n return uploadData(target, data, {\n encryptionKey: beeCompatible ? undefined : true,\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n onProgress: isRoot ? onProgress : undefined,\n requestOptions,\n })\n },\n )\n\n const newHistoryReference = historyResult.rootReference\n const historyTagUid = historyResult.tagUid\n\n return {\n historyReference: newHistoryReference,\n granteeListReference: granteeListResult.reference,\n actReference: actResult.reference,\n tagUid: historyTagUid,\n }\n}\n\n/**\n * Revoke grantees from an ACT by dropping their entries. The content reference is\n * returned unchanged — revocation must not re-encrypt it (#496).\n */\nexport async function revokeGranteesFromAct(\n target: UploadTarget,\n bee: Bee,\n historyReference: string,\n encryptedReference: string,\n publisherCandidates: ActKeyCandidates,\n revokePublicKeys: Array<{ x: Uint8Array; y: Uint8Array }>,\n options?: ActUploadOptions,\n requestOptions?: BeeRequestOptions,\n onProgress?: (progress: UploadProgress) => void,\n): Promise<ActRevocationResult> {\n const beeCompatible = historyReference.length === 64\n\n // Download history manifest\n const historyData = await downloadDataWithChunkAPI(\n bee,\n historyReference,\n undefined,\n undefined,\n requestOptions,\n )\n const historyManifest = deserializeHistory(\n historyData,\n hexToUint8Array(historyReference),\n )\n\n // Load child nodes to populate entry data\n await loadHistoryEntries(historyManifest, async (ref) => {\n return downloadDataWithChunkAPI(\n bee,\n ref,\n undefined,\n undefined,\n requestOptions,\n )\n })\n\n // Get latest entry\n const latestEntry = getLatestEntry(historyManifest)\n if (!latestEntry) {\n throw new Error(\"History manifest is empty\")\n }\n\n // Download current ACT (JSON Simple Manifest)\n const actData = await downloadDataWithChunkAPI(\n bee,\n latestEntry.metadata.actReference,\n undefined,\n undefined,\n requestOptions,\n )\n const entries = deserializeAct(actData)\n\n // Recover the access key with whichever candidate published the ACT\n const {\n privateKey: publisherPrivateKey,\n derived: publisherKeys,\n entry: publisherEntry,\n } = findPublisherKey(entries, publisherCandidates)\n\n // Keep the existing access key. Rotating it and re-encrypting the SAME content\n // reference under the new key was a revocation bypass (#496): counterModeEncrypt\n // is nonce-free CTR, so two ciphertexts of the same reference expose the new\n // keystream to any former grantee, who read both from immutable history. Re-keying\n // is also pointless — immutability keeps the old ciphertext readable forever, so\n // it can never un-share. Revocation only drops the revoked entries from the ACT.\n const accessKey = counterModeDecrypt(\n publisherEntry.encryptedAccessKey,\n publisherKeys.accessKeyDecryptionKey,\n )\n\n // Get current grantee list and filter out revoked\n let currentGrantees: UncompressedPublicKey[] = []\n if (latestEntry.metadata.encryptedGranteeListRef) {\n const encryptedList = await downloadDataWithChunkAPI(\n bee,\n latestEntry.metadata.encryptedGranteeListRef,\n undefined,\n undefined,\n requestOptions,\n )\n currentGrantees = decryptAndDeserializeGranteeList(\n encryptedList,\n publisherPrivateKey,\n )\n }\n\n const remainingGrantees = currentGrantees.filter((grantee) => {\n return !revokePublicKeys.some((revoked) =>\n publicKeysEqual(grantee, revoked),\n )\n })\n\n // Rebuild the ACT with the surviving entries under the same access key.\n const newEntries: ActEntry[] = []\n\n // Entry for publisher\n newEntries.push({\n lookupKey: publisherKeys.lookupKey,\n encryptedAccessKey: counterModeEncrypt(\n accessKey,\n publisherKeys.accessKeyDecryptionKey,\n ),\n })\n\n // Entry for each remaining grantee\n for (const granteePubKey of remainingGrantees) {\n const granteeKeys = deriveKeys(\n publisherPrivateKey,\n granteePubKey.x,\n granteePubKey.y,\n )\n newEntries.push({\n lookupKey: granteeKeys.lookupKey,\n encryptedAccessKey: counterModeEncrypt(\n accessKey,\n granteeKeys.accessKeyDecryptionKey,\n ),\n })\n }\n\n // Upload new ACT manifest (JSON Simple Manifest format)\n const newActJson = serializeAct(newEntries)\n\n const actResult = await uploadData(target, newActJson, {\n encryptionKey: beeCompatible ? undefined : true,\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n requestOptions,\n })\n\n // Upload updated grantee list\n const encryptedGranteeList = serializeAndEncryptGranteeList(\n remainingGrantees,\n publisherPrivateKey,\n )\n const granteeListResult = await uploadData(target, encryptedGranteeList, {\n encryptionKey: true,\n pin: options?.pin,\n deferred: options?.deferred,\n requestOptions,\n })\n\n // Add new history entry\n const timestamp = getCurrentTimestamp()\n addHistoryEntry(\n historyManifest,\n timestamp,\n actResult.reference,\n granteeListResult.reference,\n )\n\n // Save history tree bottom-up using Bee's actual returned references\n const historyResult = await saveHistoryTreeRecursively(\n historyManifest,\n async (data, isRoot) => {\n return uploadData(target, data, {\n encryptionKey: beeCompatible ? undefined : true,\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n onProgress: isRoot ? onProgress : undefined,\n requestOptions,\n })\n },\n )\n\n const newHistoryReference = historyResult.rootReference\n const historyTagUid = historyResult.tagUid\n\n return {\n // Unchanged — see #496: revocation must not re-encrypt the reference.\n encryptedReference,\n historyReference: newHistoryReference,\n granteeListReference: granteeListResult.reference,\n actReference: actResult.reference,\n tagUid: historyTagUid,\n }\n}\n\n/**\n * Get grantees from an ACT\n */\nexport async function getGranteesFromAct(\n bee: Bee,\n historyReference: string,\n publisherCandidates: ActKeyCandidates,\n requestOptions?: BeeRequestOptions,\n): Promise<string[]> {\n // Download history manifest\n const historyData = await downloadDataWithChunkAPI(\n bee,\n historyReference,\n undefined,\n undefined,\n requestOptions,\n )\n const historyManifest = deserializeHistory(\n historyData,\n hexToUint8Array(historyReference),\n )\n\n // Load child nodes to populate entry data\n await loadHistoryEntries(historyManifest, async (ref) => {\n return downloadDataWithChunkAPI(\n bee,\n ref,\n undefined,\n undefined,\n requestOptions,\n )\n })\n\n // Get latest entry\n const latestEntry = getLatestEntry(historyManifest)\n if (!latestEntry || !latestEntry.metadata.encryptedGranteeListRef) {\n return []\n }\n\n // The ACT manifest says which candidate published it. Decrypting the list\n // with the wrong key yields garbage that only a 0x04 prefix check would\n // catch, so it is not used to tell the keys apart.\n const actData = await downloadDataWithChunkAPI(\n bee,\n latestEntry.metadata.actReference,\n undefined,\n undefined,\n requestOptions,\n )\n const { privateKey: publisherPrivateKey } = findPublisherKey(\n deserializeAct(actData),\n publisherCandidates,\n )\n\n // Download and decrypt grantee list\n const encryptedList = await downloadDataWithChunkAPI(\n bee,\n latestEntry.metadata.encryptedGranteeListRef,\n undefined,\n undefined,\n requestOptions,\n )\n\n const grantees = decryptAndDeserializeGranteeList(\n encryptedList,\n publisherPrivateKey,\n )\n\n // Return as compressed hex strings\n return grantees.map((grantee) => {\n const compressed = compressPublicKey(grantee.x, grantee.y)\n return uint8ArrayToHex(compressed)\n })\n}\n\n/**\n * Parse a compressed public key from hex string\n */\nexport function parseCompressedPublicKey(hex: string): {\n x: Uint8Array\n y: Uint8Array\n} {\n const compressed = hexToUint8Array(hex)\n return publicKeyFromCompressed(compressed)\n}\n\n// Re-export types and utilities\nexport { type ActEntry } from \"./act\"\nexport { type UncompressedPublicKey } from \"./grantee-list\"\nexport { type HistoryEntry, type HistoryEntryMetadata } from \"./history\"\nexport {\n publicKeyFromPrivate,\n compressPublicKey,\n publicKeyFromCompressed,\n} from \"./crypto\"\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { SwarmIdError } from \"./errors\"\nimport { toWireError } from \"./proxy/errors\"\nimport type {\n ParentToIframeMessage,\n ParentIdentifyMessage,\n IframeToParentMessage,\n ButtonConfig,\n UploadDataMessage,\n DownloadDataMessage,\n DeriveAppSecretMessage,\n GetChatKeyMessage,\n SetBeeApiUrlMessage,\n StampChunkMessage,\n UploadFileMessage,\n DownloadFileMessage,\n UploadFilesMessage,\n ListFilesMessage,\n UploadChunkMessage,\n DownloadChunkMessage,\n IsConnectedMessage,\n GetNodeInfoMessage,\n GsocMineMessage,\n GsocSendMessage,\n SocUploadMessage,\n SocRawUploadMessage,\n SocDownloadMessage,\n SocRawDownloadMessage,\n SocGetOwnerMessage,\n EpochFeedDownloadReferenceMessage,\n EpochFeedUploadReferenceMessage,\n FeedGetOwnerMessage,\n SequentialFeedGetOwnerMessage,\n SequentialFeedDownloadPayloadMessage,\n SequentialFeedDownloadRawPayloadMessage,\n SequentialFeedDownloadReferenceMessage,\n SequentialFeedUploadPayloadMessage,\n SequentialFeedUploadRawPayloadMessage,\n SequentialFeedUploadReferenceMessage,\n ActUploadDataMessage,\n ActDownloadDataMessage,\n ActAddGranteesMessage,\n ActRevokeGranteesMessage,\n ActGetGranteesMessage,\n GetPostageBatchMessage,\n CreateFeedManifestMessage,\n AppMetadata,\n PostageStamp,\n PostageBatch,\n ConnectedApp,\n ConnectionInfo,\n UploadUnavailableReason,\n AuthData,\n PartitionSession,\n StoredPartitionSession,\n} from \"./types\"\nimport {\n AuthDataSchema,\n ParentToIframeMessageSchema,\n ParentIdentifyMessageSchema,\n PopupToIframeMessageSchema,\n STORAGE_CHALLENGE_KEY,\n STORAGE_KEY_NETWORK_SETTINGS,\n partitionSessionStorageKey,\n PartitionSessionSchemaV1,\n leaseCacheStorageKey,\n appStampUsageStorageKey,\n socRestampSlotsStorageKey,\n} from \"./types\"\nimport type { PopupToIframeMessage } from \"./types\"\nimport {\n Bee,\n BatchId,\n EthAddress,\n PrivateKey,\n Identifier,\n Topic,\n NULL_ADDRESS,\n MantarayNode as BeeMantarayNode,\n} from \"@ethersphere/bee-js\"\nimport { MantarayNode } from \"@ethersphere/core-sdk\"\nimport { makeContentAddressedChunk } from \"./chunk\"\nimport { AccountBus, BroadcastChannelTransport } from \"./bus/account-bus\"\nimport { SignalingTransport } from \"./bus/signaling-transport\"\nimport { deriveBusContext } from \"./bus/bus-context\"\nimport type { BusContext } from \"./bus/bus-context\"\nimport { PresenceTracker, PRESENCE_INTERVAL_MS } from \"./bus/presence\"\nimport type { BeeRequestOptions } from \"@ethersphere/bee-js\"\nimport {\n uploadData,\n uploadSOC,\n uploadChunk,\n isStamperTarget,\n type UploadTarget,\n} from \"./proxy/upload\"\nimport {\n accountInternalOwners,\n marshalStamp,\n prepareChunkToStamp,\n type ChunkToStamp,\n} from \"./proxy/stamp-chunk\"\nimport {\n appRestampRefusal,\n appStampRefusal,\n parseAppStampUsage,\n recordAppRestamp,\n recordAppStamp,\n type AppStampUsage,\n} from \"./proxy/app-stamp-limits\"\nimport {\n parseSocRestampSlots,\n recordSocSlot,\n restampTimestamp,\n slotOfStampIndex,\n} from \"./proxy/soc-restamp-slots\"\nimport {\n downloadDataWithChunkAPI,\n downloadSOC,\n downloadEncryptedSOC,\n} from \"./proxy/download-data\"\nimport type { UploadProgress } from \"./proxy/types\"\nimport { StampWorkerPool } from \"./proxy/stamp-worker-pool\"\nimport {\n loadMantarayTreeWithChunkAPI,\n saveMantarayTree,\n} from \"./proxy/mantaray\"\nimport {\n uploadCollection,\n listCollection,\n manifestEncryption,\n} from \"./proxy/collection\"\nimport { createFeedManifestDirect } from \"./proxy/feed-manifest\"\nimport {\n resolveStampForApp,\n stampsReachableByApp,\n} from \"./utils/postage-stamp-association\"\nimport {\n stampRefusal,\n sameStampLifetime,\n type StampLifetimeFields,\n type StampRefusal,\n} from \"./utils/stamp-lifespan\"\nimport {\n accountStateToDeviceView,\n foldAccount,\n foldedToSyncedAccount,\n publishDeviceState,\n readRoster,\n} from \"./sync\"\nimport type { DeviceStateSnapshot } from \"./sync\"\nimport { mergeDevicesList } from \"./sync/merge-snapshot\"\nimport { checkRestoredSession, type SessionVerdict } from \"./sync/session-check\"\nimport {\n accountDeltaFromSnapshot,\n restoreLocalSessionFields,\n} from \"./bus/account-delta\"\nimport { UtilizationAwareStamper, toBucket } from \"./utils/batch-utilization\"\nimport { UtilizationStoreDB } from \"./storage/utilization-store\"\nimport type { PartitionLeaseStateSnapshot } from \"./sync/partition-lease\"\nimport { BatchWriteCoordinator } from \"./sync/batch-write-coordinator\"\nimport {\n getOrCreateDeviceId,\n deviceRegistryChanged,\n mergeDevices,\n detectDeviceName,\n partitionDeviceName,\n} from \"./utils/device-id\"\nimport {\n createNetworkSettingsStorageManager,\n createAccountsStorageManager,\n serializeSyncedAccount,\n} from \"./utils/storage-managers\"\nimport { isStorageShared } from \"./utils/storage-probe\"\nimport {\n isSealedValue,\n isSealedValueUnavailable,\n openedValue,\n primeSealedValues,\n sealValue,\n} from \"./utils/sealed-values\"\nimport {\n deriveChatKey,\n deriveSecret,\n deriveSecretSync,\n deriveSharingKey,\n derivePostageSignerKeySync,\n deriveSwarmEncryptionKey,\n BACKUP_KEY_LABEL,\n} from \"./utils/key-derivation\"\nimport { hexToUint8Array, uint8ArrayToHex } from \"./utils/hex\"\nimport { generatedAvatar } from \"./utils/avatar\"\nimport { connectionInfoEqual } from \"./utils/connection-info\"\nimport {\n acceptedChatBinding,\n onlySiteChatBinding,\n siteChatAddress,\n} from \"./utils/chat-binding\"\nimport type { ChatBinding } from \"./utils/chat-binding\"\nimport {\n activeDeviceIds,\n KNOWN_DEVICE_MAX_AGE_MS,\n} from \"./utils/active-devices\"\nimport {\n createAsyncEpochFinder,\n createEpochUpdater,\n} from \"./proxy/feeds/epochs\"\nimport { createAsyncSequentialFinder } from \"./proxy/feeds/sequence\"\nimport { Binary } from \"cafe-utility\"\nimport {\n calculateTTLSeconds,\n fetchBatchDetails,\n fetchSwarmPrice,\n} from \"./utils/ttl\"\nimport {\n fetchAuthoritativeBatchTTL,\n POSTAGE_STAMP_CONTRACT_ADDRESS,\n} from \"./utils/postage-contract\"\nimport { tryCreateTag } from \"./utils/tag\"\nimport { accountToStateSnapshot } from \"./utils/account-state-snapshot\"\nimport {\n DEFAULT_BEE_NODE_URL,\n DEFAULT_GNOSIS_RPC_URL,\n isSignedOutAccount,\n type AccountStateSnapshot,\n type SignedInAccount,\n type SyncedAccount,\n type NetworkSettings,\n} from \"./schemas\"\nimport { appSessionDuration } from \"./utils/constants\"\nimport { buildAuthUrl } from \"./utils/url\"\nimport type { SiteAllowList } from \"./utils/site-allow-list\"\nimport {\n createActForContent,\n decryptActReference,\n addGranteesToAct,\n revokeGranteesFromAct,\n getGranteesFromAct,\n parseCompressedPublicKey,\n publicKeyFromPrivate,\n compressPublicKey,\n} from \"./proxy/act\"\n\n/**\n * Debounce window for proxy-side account-state publishing. Coalesces a burst of\n * triggers (lease acquisition + storage changes) into a single feed write.\n */\nexport const PUBLISH_DEBOUNCE_MS = 1500\n\n/** Min interval between pre-acquire device-registry pulls (throttles the\n * slot-wait loop's repeated acquires from hammering the feed). */\nconst DEVICE_REGISTRY_REFRESH_THROTTLE_MS = 8_000\n\n/**\n * Gap between consecutive ranks when holders take turns answering a peer's\n * `lease-request` (`yieldRankDelayMs`). It budgets ONE bus hop — the winner's\n * `lease-claim`, which it publishes before it starts releasing (instant over\n * BroadcastChannel, a server round trip over signaling) — and nothing else.\n * Sizing it against the release itself would be hopeless: that is two stamped\n * Swarm writes, so no step short enough to keep the fallthrough useful could\n * ever gate it.\n */\nconst PEER_YIELD_RANK_STEP_MS = 250\n\n/** Hex chars of request id — parsed straight into the rank seed. */\nconst LEASE_REQUEST_ID_LENGTH = 8\n\n/** Request ids remembered as already answered, so a `lease-claim` can still\n * call off a yield whose rank timer has fired. Small: a round's id is spent\n * the moment a holder claims it, and stale entries only cost a `Set` slot. */\nconst ANSWERED_REQUEST_MEMORY = 32\n/** Content, account and gateway clients, with room for a site's next address. */\nconst MAX_BEE_CLIENTS = 4\n\n/** Identifies one slot-wait round, so every holder ranks itself the same way.\n * A UUID's first block is hex, so this is hex without any rewriting. */\nfunction newLeaseRequestId(): string {\n return crypto.randomUUID().slice(0, LEASE_REQUEST_ID_LENGTH)\n}\n\nconst DEFAULT_ACT_FILENAME = \"index.bin\"\nconst DEFAULT_ACT_CONTENT_TYPE = \"application/octet-stream\"\nconst SEQUENTIAL_INDEX_LOOKUP_TIMEOUT_MS = 2000\n\n/**\n * localStorage key holding optional partition-coordination timing overrides\n * (`{ guardWindowMs?, guardPollMs?, readTimeoutMs? }`). Lets the gateway's\n * propagation tuning be adjusted at runtime (set by the /dev \"Partition tuning\"\n * panel) and picked up on the next connect — no rebuild. Absent → code defaults.\n */\nconst PARTITION_TUNING_KEY = \"swarm-id-partition-tuning\"\n\nfunction readPartitionTuningOverride():\n | { guardWindowMs?: number; guardPollMs?: number; readTimeoutMs?: number }\n | undefined {\n try {\n if (typeof localStorage === \"undefined\") return undefined\n const raw = localStorage.getItem(PARTITION_TUNING_KEY)\n if (!raw) return undefined\n const parsed: unknown = JSON.parse(raw)\n if (typeof parsed !== \"object\" || parsed === null) return undefined\n const pick = (v: unknown): number | undefined =>\n typeof v === \"number\" && Number.isFinite(v) && v >= 0 ? v : undefined\n const o = parsed as Record<string, unknown>\n return {\n guardWindowMs: pick(o.guardWindowMs),\n guardPollMs: pick(o.guardPollMs),\n readTimeoutMs: pick(o.readTimeoutMs),\n }\n } catch {\n return undefined\n }\n}\n\n/**\n * A hydrated account as the account-state snapshot the fold consumes.\n *\n * The record's OWN `lastModified` is the snapshot clock, never `Date.now()`:\n * this side of the fold is a record being re-read, not a change being made, and\n * a fresh clock on it would let this session outrank the peer's genuine edit.\n * (`accountToStateSnapshot` keeps the same rule per scalar field.)\n */\nfunction accountAsStateSnapshot(\n account: SignedInAccount,\n): AccountStateSnapshot {\n return accountToStateSnapshot(\n account,\n account.id.toHex(),\n account.lastModified ?? account.createdAt,\n )\n}\n\n/**\n * One side of an `account-delta` fold, in the shape `foldAccount` merges.\n *\n * Both sides go through it so the bus fold IS the Swarm fold — same collection\n * tombstone clocks, same per-field scalar clocks, same `BatchId` revival — with\n * no rule stated twice. `deviceId` and `timestamp` are inert here: `foldAccount`\n * keys collections by their own natural keys, takes the device list from its\n * second argument, and reads neither field — `deltaFoldView` pins that in a\n * test, since it is an assumption about another module's internals.\n */\nfunction deltaFoldView(snapshot: AccountStateSnapshot): DeviceStateSnapshot {\n return {\n version: 1,\n accountId: snapshot.accountId,\n deviceId: \"\",\n timestamp: snapshot.timestamp,\n ...accountStateToDeviceView(snapshot),\n }\n}\n\nfunction subsidisedStampRefusal(): SwarmIdError {\n return new SwarmIdError(\n \"upload-unavailable\",\n \"The account is in subsidised mode: the gateway pays for and stamps every upload, so there is no stamp of the account's to return.\",\n { reason: \"subsidised\" },\n )\n}\n\n/** A stamp made for an app, before it is committed */\ninterface StampedForApp {\n marshaled: string\n /** Remembers a single-owner chunk's slot. Run only once the stamp is committed */\n recordSlot?: () => void\n}\n\n/**\n * Writes to a stored partitioned session, chained per storage key across every\n * proxy in this window, in call order (see `writePartitionSession`).\n */\nconst partitionSessionWrites = new Map<string, Promise<void>>()\n\nfunction sessionEndedError(): SwarmIdError {\n return new SwarmIdError(\n \"not-authenticated\",\n \"This session was ended from the account. Connect again to write.\",\n )\n}\n\n/**\n * Swarm ID Proxy - Runs inside the iframe\n *\n * Responsibilities:\n * - Receive app-specific secrets from auth popup\n * - Keep a partitioned session in this partition's own localStorage (#635)\n * - Proxy Bee API calls from parent dApp\n * - Augment requests with authentication\n * - Return responses to parent dApp\n */\nexport class SwarmIdProxy {\n private parentOrigin: string | undefined\n private parentIdentified: boolean = false\n /**\n * Set by the first `parentIdentify`, before its allow-list check awaits, so a\n * second one cannot start a check of its own. A refused parent leaves it set:\n * the frame answers nothing more until the page loads it again.\n */\n private parentIdentifyReceived: boolean = false\n private siteAllowList: SiteAllowList\n private authenticated: boolean = false\n private authLoading: boolean = true // Start in loading state\n private appSecret: string | undefined\n /**\n * The chat binding the popup handed over with this session, already checked\n * against it (`acceptHandedOverChatBinding`). Only a handover sets it: an\n * unpartitioned session reads its connection out of shared storage and\n * reports none.\n */\n private chatBinding: ChatBinding | undefined\n private postageBatchId: string | undefined\n private signerKey: string | undefined\n private stamper: UtilizationAwareStamper | undefined\n /**\n * Hash of `<owner>-<encryptionKey>` that the current `stamper` was built\n * with. Account-level inputs are baked into `UtilizationAwareStamper` at\n * construction, so when the underlying account changes (e.g. a local\n * account migrates to a synced one) we need to detect that even if the\n * postage stamp's batch/signer didn't change, and rebuild the stamper.\n */\n private stamperAccountFingerprint: string | undefined\n private stampWorkerPool: StampWorkerPool | undefined\n private storagePartitioned: boolean = false\n private pendingChallenge: string | undefined\n /**\n * Hydrated account view for the storage-partitioning fallback: the synced\n * projection handed over by the connect popup (`AuthData.account`), held in\n * memory only — the stored-account schema requires a vault. What survives a\n * reload is the handover itself, under its own key (#635), which this view\n * is then rebuilt from. When set, the proxy is a\n * first-class writer despite the partition (docs/Account-Bus.md, phase 3).\n */\n private partitionAccount: SignedInAccount | undefined\n /** Account-derived topic the attached bus transports share; undefined when\n * none are attached. */\n private busTopic: string | undefined\n private removeBusLocal: (() => void) | undefined\n private removeBusSignaling: (() => void) | undefined\n /** Derivation key of a join that attached everything it wanted. Lets the\n * synchronous `joinAccountBus` return without re-deriving, and — because it\n * stays unset when a transport failed to attach — makes the next join a\n * RETRY rather than a dedup'd no-op. */\n private busJoinedKey: string | undefined\n /** Bumped by every bus join and by `clearAuthData`, so a join whose key\n * derivation is still in flight can tell it has been superseded. */\n private busJoinGeneration = 0\n /** Account whose room the attached transports currently speak into. A\n * transport encrypts with its construction-time key, so this — not whichever\n * connection resolves now — is who a publish actually reaches. */\n private busBoundAccountId: string | undefined\n /** Scheduled answers to peers' `lease-request`s, by request id, so a peer\n * earlier in the rank order can call ours off (`yieldRankDelayMs`). */\n private pendingYields = new Map<string, ReturnType<typeof setTimeout>>()\n /** Request ids somebody has claimed (us or a peer). Insertion-ordered and\n * capped at `ANSWERED_REQUEST_MEMORY`. Separate from `pendingYields`\n * because the cancel window has to outlive the rank timer: the timer only\n * starts the yield, whose first Swarm write is still ahead of it. */\n private answeredRequests = new Set<string>()\n /**\n * Utilization deltas that arrived while the stamper had no lane, held until\n * a bind names one (`applyPendingLaneUpdate`). Keyed by lane so a delta for\n * a lane we do not end up binding cannot displace the one we do — the point\n * of buffering is that nothing is silently lost. Buckets merge per index\n * with `max`, matching `applyUtilizationUpdate`'s own monotonic rule.\n * Bounded by the account's partition count.\n */\n private pendingLaneUpdates = new Map<string, Map<number, number>>()\n /**\n * The newest timestamp this frame gave a stamp again of a single-owner\n * chunk, in milliseconds, by site, stamp and chunk address. Set under the\n * write lock when the stamp is made, while the stored slot record is\n * written only after the lock, so a second stamp of that address inside\n * the lock still gets a newer timestamp than the first.\n */\n private lastRestampMs = new Map<string, number>()\n private utilizationStore: UtilizationStoreDB | undefined\n /** The Bee node the person set in Swarm ID's network settings. */\n private ownBeeApiUrl: string\n /**\n * The Bee node the embedding site named for its own session, from\n * `parentIdentify` or `setBeeApiUrl`. In memory only: a frame\n * serves the one parent origin it was identified by, so this is that\n * site's alone, and nothing writes it to storage, the account or a sync\n * feed. The site sends it again on every load.\n */\n private siteBeeApiUrl: string | undefined\n /**\n * The subsidised gateway the site asked for, kept apart from\n * `subsidisedGatewayUrl`, which the person's own custom node can switch\n * off, so the gateway can come back once the site names its own node.\n */\n private siteSubsidisedGatewayUrl: string | undefined\n private gnosisRpcUrl: string\n private postageStampContractAddress: string\n private signalingUrl: string | undefined\n private authButtonContainer: HTMLElement | undefined\n private buttonConfig: ButtonConfig | undefined\n private popupMode: \"popup\" | \"window\" = \"window\"\n private appMetadata: AppMetadata | undefined\n /**\n * Bee clients by the URL they were asked for. Keyed on that input URL, not\n * on the client's own `url`, which bee-js keeps without the trailing slash.\n */\n private beeClients = new Map<string, Bee>()\n private unsubscribeStorageListeners: Array<() => void> = []\n private storageWorkQueue: Promise<void> = Promise.resolve()\n private lastConnectionInfo: ConnectionInfo | undefined\n private isConnecting: boolean = false\n private parentWindow: WindowProxy | undefined\n private bus: AccountBus\n /** Who else is live in the account's room, from their `presence` beats. */\n private presence = new PresenceTracker()\n private presenceTimer: ReturnType<typeof setInterval> | undefined\n private subsidisedGatewayUrl: string | undefined\n /**\n * The resolved stamp's stored lifetime fields (#745), set wherever the\n * stamp is. Expiry is derived from them on every read, so a tab left open\n * past the drive's lifetime refuses its own writes rather than letting Bee\n * refuse the lease claim.\n */\n private stampLifetime: StampLifetimeFields | undefined\n /** Bumped by every `initializeStamper`; an older init sees it move and stops */\n private stamperInitGeneration = 0\n\n /** Why the session refuses to stamp with the resolved drive, if it does. */\n private get stampRefusal(): StampRefusal | undefined {\n return this.stampLifetime && stampRefusal(this.stampLifetime)\n }\n\n /**\n * The node the site's own content goes through: the site's address when it\n * named one, the person's setting otherwise. Content only, the reads and\n * stamped uploads the site asks for. Nothing that decides whether this\n * session may write, or that coordinates the person's devices, reads it\n * (`accountBee`), because a site could answer those from a node of its own.\n */\n private get beeApiUrl(): string {\n return this.siteBeeApiUrl ?? this.ownBeeApiUrl\n }\n\n /**\n * Switch off the site's subsidised gateway when the node in effect is the\n * person's own custom one, so their uploads go to the node they chose. A\n * node the site named is not the person's choice, and the site asked for\n * both, so it keeps the gateway.\n */\n private dropGatewayForOwnCustomNode(): void {\n if (\n this.siteBeeApiUrl === undefined &&\n this.ownBeeApiUrl !== DEFAULT_BEE_NODE_URL\n ) {\n this.subsidisedGatewayUrl = undefined\n }\n }\n\n /**\n * The client for the node the current mode writes to: the dApp's gateway\n * while the session is subsidised, the configured node otherwise. Reads have\n * to come from where the uploads go, or a dApp reads back a 404 for the\n * chunk it just wrote.\n *\n * Derived on every read rather than assigned, because a mode change is not\n * always an event — a drive's expiry is read off the clock — and a client\n * picked once keeps pointing wherever the session started: at the gateway\n * for a session that connected after the page loaded, or at the node for\n * one whose drive has since run out or been removed.\n */\n private get bee(): Bee {\n return this.beeClientFor(\n this.isSubsidisedModeActive()\n ? this.subsidisedGatewayUrl!\n : this.beeApiUrl,\n )\n }\n\n /**\n * The client for the site's content node, whatever the mode: where the\n * site's stamped uploads go in user-stamp mode, which `bee` also reads from\n * then.\n */\n private get nodeBee(): Bee {\n return this.beeClientFor(this.beeApiUrl)\n }\n\n /**\n * The client for the person's own node, whatever the site named. The\n * coordinator's lock, lease and state chunks, the device roster and the\n * restored session's revocation check go through it: they decide what this\n * session may do with the person's account and stamp, so the embedding\n * site must not be able to answer them.\n */\n private get accountBee(): Bee {\n return this.beeClientFor(this.ownBeeApiUrl)\n }\n\n /**\n * A client for `url`, kept per URL so the content, account and gateway\n * clients do not rebuild each other. Bounded, because a site can name a\n * new address at any time.\n */\n private beeClientFor(url: string): Bee {\n let bee = this.beeClients.get(url)\n if (!bee) {\n if (this.beeClients.size >= MAX_BEE_CLIENTS) this.beeClients.clear()\n bee = new Bee(url)\n this.beeClients.set(url, bee)\n }\n return bee\n }\n\n /**\n * The write path (lock + partition lease + stamp flush) for the current\n * account+batch. Assigned in `initializeStamper` together with the stamper it\n * writes through, and dropped with it whenever the resolved stamp changes\n * (`setResolvedStamp`); undefined before auth / after disconnect. The proxy\n * delegates all writes to it via `withModeAwareWriteLock`.\n */\n private coordinator: BatchWriteCoordinator | undefined\n /**\n * Debounce timer + in-flight guard for proxy-side account-state publishing.\n * The proxy publishes the account snapshot to the shared sync feed when it\n * first acquires a partition (so the device announces itself — see #336\n * motivation #4) and when account state changes while a lease is held.\n */\n private publishTimer: ReturnType<typeof setTimeout> | undefined\n private publishInFlight: boolean = false\n /**\n * Why the next debounced publish was scheduled. `\"acquired\"` (announce on\n * lease acquisition) is gated on this device not yet being in the feed, so a\n * plain reload doesn't republish. `\"change\"` (account-state delta) always\n * publishes. `\"change\"` dominates a coalesced burst — a real delta must never\n * be skipped just because an acquisition was also pending.\n */\n private publishReason: \"acquired\" | \"change\" | undefined\n /**\n * This device's identity, captured ONCE at authentication and reused for\n * every lease operation. Never re-read from `getOrCreateDeviceId()` mid-\n * session, so acquire / refresh / validate always agree on one identity.\n */\n private deviceId: string | undefined\n /**\n * A session this load restored from storage, rather than one the person\n * connected in it, waits on this before its first write, so a session ended\n * while no page of the site was open cannot write.\n * Unset for a fresh connection. Kept through `clearAuthData`, so a write\n * that was waiting when the check ended the session is told why.\n */\n private restoredSessionCheck:\n | {\n verdict: Promise<SessionVerdict>\n settled: SessionVerdict | undefined\n input: Parameters<typeof checkRestoredSession>[0]\n }\n | undefined\n\n constructor(config: ProxyConfig) {\n this.siteAllowList = config.siteAllowList\n // Load Bee API URL from network settings, falling back to default\n const networkSettings = createNetworkSettingsStorageManager().load()\n this.ownBeeApiUrl = networkSettings?.beeNodeUrl || DEFAULT_BEE_NODE_URL\n this.gnosisRpcUrl = networkSettings?.gnosisRpcUrl || DEFAULT_GNOSIS_RPC_URL\n // PostageStamp contract address for on-chain TTL reads. The host app\n // supplies this from a build-time env so local dev can point at the\n // bee-compose anvil deployment; defaults to the Gnosis mainnet contract.\n this.postageStampContractAddress =\n config.postageStampContractAddress || POSTAGE_STAMP_CONTRACT_ADDRESS\n this.signalingUrl = config.signalingUrl\n if (!this.signalingUrl) {\n // Said out loud because the alternative is silence: without a URL the\n // bus never leaves this origin+partition, and a static build that lost\n // its `PUBLIC_BUS_SIGNALING_URL` looks exactly like a healthy one.\n console.info(\n \"[Proxy] No account-bus signaling URL configured — cross-partition and cross-device coordination is off (docs/Account-Bus.md).\",\n )\n }\n this.setupMessageListener()\n this.setupStorageListeners()\n\n // Multi-tab coordination rides the account bus (docs/Account-Bus.md).\n this.bus = new AccountBus([])\n this.setupBusListeners()\n\n // Announce readiness to parent window immediately\n // This signals that our message listener is ready to receive parentIdentify\n this.announceReady()\n }\n\n /**\n * Subscribe to shared localStorage changes that can affect the dApp-visible\n * ConnectionInfo (auth state, identity, postage stamps, account type).\n *\n * - `connectedApps` drives auth transitions (new connection / identity switch /\n * disconnect) and triggers `authSuccess` / `disconnectResponse` to the parent.\n * - `identities`, `accounts`, `postageStamps` may change the derived\n * ConnectionInfo without flipping auth state (e.g. rename, default-stamp\n * change, local→synced migration). On change we re-derive ConnectionInfo\n * and emit `connectionInfoChanged` if it actually differs.\n *\n * Note: We always set up these listeners, even when storage might be partitioned.\n * In some browsers/configurations (like localhost development), storage events\n * work between same-origin windows even in iframes. If storage IS partitioned,\n * the listeners simply won't fire, and we fall back to postMessage from the popup.\n */\n private setupStorageListeners(): void {\n // Avoid duplicate subscriptions\n if (this.unsubscribeStorageListeners.length > 0) {\n return\n }\n\n // The account is the single nested document of record (it owns its\n // connected apps and postage stamps), so one subscription covers every\n // change that can affect auth or derived ConnectionInfo.\n const accountsManager = createAccountsStorageManager()\n this.unsubscribeStorageListeners.push(\n accountsManager.subscribe((_, change) => {\n this.enqueueReconcile(\"handleAccountStorageChange\", () =>\n this.handleAccountStorageChange(\n change?.noRepublish ? \"bus\" : \"storage\",\n ),\n )\n }),\n )\n\n // The user can change the Bee node URL at runtime via the trusted UI's\n // network-settings dialog. That localStorage write fires a `storage` event\n // in this same-origin dApp iframe — repoint the Bee client so the newly\n // configured node is in effect before the next write (#515). The\n // network-settings manager has no `subscribe`, so listen for the raw event.\n if (typeof window !== \"undefined\") {\n const onNetworkSettingsChange = (event: StorageEvent): void => {\n if (event.key !== STORAGE_KEY_NETWORK_SETTINGS) {\n return\n }\n // Never for a partitioned session: its settings came over the handover,\n // and the store this event points at is the PARTITION's, which holds\n // none. Re-reading it loads `undefined` and falls through to the public\n // gateway and the default RPC, moving a user on a local or custom Bee\n // node onto neither (#580). A settings change made in the first-party\n // tab does not reach such a session at all — it is picked up at the\n // next connect (docs/Account-Bus.md, Known gaps).\n if (this.partitionAccount) {\n return\n }\n this.applyNetworkSettings()\n this.emitConnectionInfoIfChanged()\n }\n window.addEventListener(\"storage\", onNetworkSettingsChange)\n this.unsubscribeStorageListeners.push(() =>\n window.removeEventListener(\"storage\", onNetworkSettingsChange),\n )\n }\n }\n\n /**\n * Queue a reconcile behind whatever reconcile is already running.\n *\n * `refreshStampFromStorage` / `initializeStamper` mutate shared\n * `stamper` / `postageBatchId` state across awaits, so two of them in flight\n * at once emit inconsistent ConnectionInfo. ONE queue for every source: a\n * storage event and an `account-delta` fold are the same work arriving two\n * ways, and a queue each would serialize neither against the other.\n *\n * Rejections are caught and logged here — the chain must survive one failure,\n * and nothing else catches them (the bus dispatch's try/catch only covers a\n * synchronous throw).\n */\n private enqueueReconcile(where: string, work: () => Promise<void>): void {\n this.storageWorkQueue = this.storageWorkQueue.then(() =>\n work().catch((error: unknown) => {\n console.error(`[Proxy] ${where} failed:`, error)\n }),\n )\n }\n\n /**\n * Re-read network settings. `beeApiUrl` / `gnosisRpcUrl` are read fresh per\n * call — by the TTL/contract helpers, and by `bee`, which builds the client\n * for a changed URL on its next read — so updating them is all it takes.\n */\n private applyNetworkSettings(): void {\n this.applyNetworkSettingsValues(\n createNetworkSettingsStorageManager().load(),\n )\n }\n\n /**\n * The body of `applyNetworkSettings`, taking the settings as a value: a\n * partitioned session cannot read them from storage and is handed them by\n * the connect popup instead (`hydratePartitionAccount`). One implementation,\n * or the two paths drift — an RPC-only change is invisible to a `beeNodeUrl`\n * guard, and the subsidised-gateway rules below are easy to forget.\n */\n private applyNetworkSettingsValues(\n settings: NetworkSettings | undefined,\n ): void {\n this.gnosisRpcUrl = settings?.gnosisRpcUrl || DEFAULT_GNOSIS_RPC_URL\n\n const beeNodeUrl = settings?.beeNodeUrl || DEFAULT_BEE_NODE_URL\n if (beeNodeUrl === this.ownBeeApiUrl) {\n return\n }\n this.ownBeeApiUrl = beeNodeUrl\n\n // Mirrors the parentIdentify override. ponytail: reverting to the default\n // URL won't re-enable a dropped subsidised gateway. That needs a fresh\n // parentIdentify (dApp reload) or the site naming its own node. Rare\n // enough to not build for.\n this.dropGatewayForOwnCustomNode()\n }\n\n /**\n * Handle changes to the nested account document. Covers auth transitions\n * (connect / secret change / disconnect) AND derived-ConnectionInfo changes\n * (default-stamp change, new stamp purchased, account rename) — all of which\n * now live in one document.\n *\n * `\"storage\"`: a `storage` event from another window, or a same-window write\n * the shell made on its own — this device's change, so it publishes.\n * `\"bus\"`: a same-window write the shell marked `noRepublish` — a peer's\n * delta it folded, a volatile field, a device-local sign-out. The shell\n * sharing this window (the identity UI runs inside the proxy iframe too)\n * makes those beside the proxy, and they reconcile the way a bus delta does,\n * without a publish: republishing a fold stamped a fresh clock on every hop\n * and kept two devices publishing at each other every few seconds (#707).\n */\n private async handleAccountStorageChange(\n source: \"storage\" | \"bus\" = \"storage\",\n ): Promise<void> {\n await this.reevaluateConnection(source)\n }\n\n /**\n * Re-resolve this origin's connection and reconcile the session with it.\n *\n * `source` is where the news came from, and only the disconnect branch cares.\n * A partitioned iframe cannot see connected apps in storage, so a storage\n * event saying \"no valid connection\" means nothing there — but an\n * `account-delta` from a peer is authoritative, and acting on it is the whole\n * point of that message (docs/Account-Bus.md).\n */\n private async reevaluateConnection(source: \"storage\" | \"bus\"): Promise<void> {\n if (!this.parentOrigin) {\n return\n }\n\n // The keys in shared storage are sealed, and the reads below open them\n // from memory (`utils/sealed-values.ts`).\n await createAccountsStorageManager().prime()\n const connection = this.findConnectionForParent()\n\n if (connection) {\n const { app } = connection\n if (!this.authenticated || app.appSecret !== this.appSecret) {\n // New connection, or the connected account / secret changed.\n await this.authenticateFromStorage(app)\n } else {\n // Same connection — related metadata (default stamp, per-app batch\n // override, rename) may have changed. A partitioned session skips this\n // for a storage event, whose account document it cannot read; after a\n // bus fold it must NOT skip it, because the hydrated view it just\n // folded into is exactly what the refresh reads (the same call\n // `hydratePartitionAccount` makes). Otherwise a rotated signer key or a\n // deleted stamp updates the view and leaves the live stamper stale for\n // the page's life.\n if (!this.storagePartitioned || source === \"bus\") {\n await this.refreshStampFromStorage()\n }\n this.emitConnectionInfoIfChanged()\n }\n\n // Propagate the account-state change to peers: over the bus always, and\n // to the shared feed when we hold a partition. Debounced so a burst\n // collapses into one of each. A delta we folded ourselves is not\n // re-broadcast (`source === \"bus\"`) — the publisher already reached\n // everyone in the room, and echoing it back is a loop.\n //\n // No coordinator gate: a session without one (a stamp-less account never\n // gets past `initializeStamper`) has nothing to write to the feed, but a\n // bus message needs no lease — and a stamp-less account still has apps to\n // revoke and partitioned sessions to tell.\n if (source === \"storage\") {\n this.schedulePublish(\"change\")\n }\n } else if (\n this.authenticated &&\n (!this.storagePartitioned || source === \"bus\")\n ) {\n // No valid connection any more, and we're authenticated — disconnect.\n // A storage event is skipped while partitioned: the iframe can't see\n // connected apps, and auth was established via postMessage. A bus delta\n // is not — it IS the channel a revoke reaches a partitioned session by.\n // `clearAuthData` emits the ConnectionInfo update; no need to do so again.\n if (source === \"storage\") {\n // Relay the end of the session to peers that cannot see it. With only\n // this dApp open, this iframe is the ONLY context that reads the\n // account document, so its own partitioned sessions hear about the\n // revoke here or not at all. Storage-sourced only: a bus-sourced\n // disconnect came from a peer that already told the room, and echoing\n // it back is a loop. Ahead of `clearAuthData`, which detaches the\n // transports and rewrites the entry we are reporting.\n this.publishAccountDelta({ ofEndedConnection: true })\n }\n this.clearAuthData()\n this.sendToParent({\n type: \"disconnectResponse\",\n requestId: source === \"bus\" ? \"account-delta\" : \"storage-event\",\n success: true,\n })\n }\n }\n\n /**\n * Re-resolve postage stamp and account inputs for the current connection\n * from shared storage. Reinitializes the stamper if the batch, signer\n * key, or account-level inputs (owner / encryption key) changed — the\n * last case handles e.g. local→synced account migration where the\n * postage stamp is unchanged but the underlying account isn't.\n */\n private async refreshStampFromStorage(): Promise<void> {\n // A rebound account is a different bus topic.\n this.joinAccountBus()\n\n const stamp = this.lookupPostageStampForApp()\n const nextBatchId = stamp?.batchID.toHex()\n const nextSignerKey = stamp?.signerKey.toHex()\n const account = stamp ? await this.lookupAccountForApp() : undefined\n const nextAccountFingerprint = account\n ? `${account.owner.toHex()}-${uint8ArrayToHex(account.encryptionKey)}`\n : undefined\n\n if (\n nextBatchId === this.postageBatchId &&\n nextSignerKey === this.signerKey &&\n nextAccountFingerprint === this.stamperAccountFingerprint &&\n // A renewal keeps the batch and the key and moves only the lifetime.\n sameStampLifetime(stamp, this.stampLifetime)\n ) {\n return\n }\n\n this.setResolvedStamp(stamp)\n if (stamp) {\n await this.initializeStamper(stamp.depth)\n }\n }\n\n /**\n * The only way the resolved stamp changes. The write path — the stamper and\n * the coordinator that writes through it — belongs to the stamp it was built\n * for, so it goes whenever the fields naming that stamp do, before anything\n * is built for the next one. Otherwise the previous coordinator outlives its\n * stamp: after the drive is removed, or the connection moves to another\n * account, an upload still takes that coordinator's lease and spends the old\n * batch while the dApp is told there is no stamp at all.\n */\n private setResolvedStamp(stamp: PostageStamp | undefined): void {\n this.teardownCoordinator()\n this.stamper = undefined\n this.stamperAccountFingerprint = undefined\n this.postageBatchId = stamp?.batchID.toHex()\n this.signerKey = stamp?.signerKey.toHex()\n this.stampLifetime = stamp\n }\n\n /**\n * Authenticate using data from connected apps storage\n */\n private async authenticateFromStorage(\n connectedApp: ConnectedApp,\n ): Promise<void> {\n this.appSecret = connectedApp.appSecret\n this.chatBinding = undefined\n this.authenticated = true\n this.restoredSessionCheck = undefined\n this.storagePartitioned = false\n this.partitionAccount = undefined\n this.authLoading = false\n this.isConnecting = false\n // Capture the device identity once, now, while we can read first-party\n // localStorage. Reused for every lease op so the identity never shifts\n // mid-session.\n this.deviceId = getOrCreateDeviceId()\n this.joinAccountBus()\n\n // Look up postage stamp. When switching identities, the new identity may\n // not have a stamp at all, and the previous identity's write path must not\n // survive into it either way.\n const stamp = this.lookupPostageStampForApp()\n this.setResolvedStamp(stamp)\n if (stamp) {\n await this.initializeStamper(stamp.depth)\n }\n\n this.showAuthButton()\n this.sendToParent({\n type: \"authSuccess\",\n origin: this.parentOrigin!,\n })\n this.emitConnectionInfoIfChanged()\n }\n\n /**\n * Handle messages from the auth popup (same-origin postMessage).\n * Used as storage partitioning fallback when storage events don't fire due to partitioning.\n */\n private async handlePopupMessage(\n message: PopupToIframeMessage,\n ): Promise<void> {\n if (message.type === \"setSecret\") {\n // Challenge FIRST, so the origin check below can tell whose handover it\n // is refusing. Both are guards before anything is read out of the\n // payload, and the message is already same-origin (the listener checks\n // that), so the order is free — what it buys is that a mismatch past\n // this line is provably THIS attempt's popup answering.\n if (\n !this.pendingChallenge ||\n message.challenge !== this.pendingChallenge\n ) {\n // Not this attempt's: a stale popup from a previous round, or one for\n // another dApp. Ending the attempt on it would let a leftover message\n // cancel a connect in flight, so this one only logs.\n console.warn(\"[Proxy] setSecret challenge mismatch — ignoring\")\n return\n }\n\n if (message.appOrigin !== this.parentOrigin) {\n console.warn(\n \"[Proxy] setSecret appOrigin mismatch:\",\n message.appOrigin,\n \"!==\",\n this.parentOrigin,\n )\n // Our challenge, so this IS the handover this attempt was waiting for\n // — refused for naming an origin that is not our parent, which an\n // origin-normalisation drift is the realistic way to reach. The\n // spinner would hang exactly as it did on the schema gate (#587).\n this.endRefusedConnect()\n return\n }\n\n // Clean up challenge\n this.pendingChallenge = undefined\n localStorage.removeItem(STORAGE_CHALLENGE_KEY)\n\n // Authenticate in storage-partitioned mode (no stamps available via postMessage)\n this.appSecret = message.data.secret\n this.chatBinding = this.acceptHandedOverChatBinding(message.data)\n this.authenticated = true\n this.restoredSessionCheck = undefined\n this.storagePartitioned = true\n this.authLoading = false\n this.isConnecting = false\n this.deviceId = getOrCreateDeviceId()\n\n // The projection is required by `AuthDataSchema`, so by here it is\n // present: a payload without one never parses. The identity the dApp is\n // shown comes off the same account, which is why nothing separate is\n // carried for it.\n await this.hydratePartitionAccount(\n message.data.account,\n message.data.secret,\n message.data.networkSettings,\n // A handover is a genuine connect — the same reasoning as the\n // `reactivateThisDevice()` below, one field over.\n Date.now(),\n )\n\n // A handover IS the genuine sign-in for a partition device: the user\n // clicked connect and unlocked the account in the popup. That, and not a\n // background poll, is what lifts a tombstone (#337 via #611).\n // Deliberately not on `restorePartitionSession` — a reload is not a\n // sign-in, and treating it as one is how the removal stopped sticking.\n this.reactivateThisDevice()\n\n // Keep it, so a reload does not log the user out of the dApp (#635).\n // On Safari the partitioned path is the ordinary one, and re-running the\n // popup per page load is not a session — the deadline below is.\n this.savePartitionSession(message.data)\n\n this.showAuthButton()\n this.sendToParent({\n type: \"authSuccess\",\n origin: this.parentOrigin!,\n })\n this.emitConnectionInfoIfChanged()\n }\n }\n\n /**\n * End the connect attempt a refused handover belonged to.\n *\n * The popup cannot be told it was refused — there is no channel back — so it\n * shows \"Connected!\" while the button here spins on, and the only other\n * thing that ever cleared the flag was the user closing that popup\n * (`checkPopupClosed`), which nothing gave them a reason to do.\n *\n * `isConnecting` first: `showAuthButton` early-returns while it is set, so\n * clearing the flag is what lets the button come back. `authLoading` is\n * already false on every path that reaches here — belt-and-braces, not a\n * second fix.\n *\n * The challenge is deliberately kept. The popup is still open and may yet\n * send a payload this bundle can read; refusing one message ends the\n * attempt's spinner, not the ceremony behind it.\n */\n private endRefusedConnect(): void {\n this.isConnecting = false\n this.authLoading = false\n this.showAuthButton()\n }\n\n /**\n * The handed-over chat binding, if it names exactly this parent origin, the\n * address of the chat key of the secret it came with and the account it came\n * with, so the dApp is never shown a statement about another site, key or\n * account. A restored session runs through here again, so a stored binding\n * naming any other address is dropped on reload as well.\n *\n * Local http is allowed in the parse because the comparison with\n * `parentOrigin`, which the browser reported, is the check here. Which sites\n * a binding may name at all is the popup's to decide, and a production popup\n * refuses local http.\n */\n private acceptHandedOverChatBinding(data: AuthData): ChatBinding | undefined {\n if (!this.parentOrigin) return undefined\n return acceptedChatBinding(\n data.chatBinding,\n {\n site: this.parentOrigin,\n chatAddress: siteChatAddress(data.secret),\n signer: data.account.id.toHex(),\n },\n { allowLocalHttp: true },\n )\n }\n\n /**\n * Write the handed-over session to this partition's own store, sealed so\n * none of it is plain text on disk: it holds the app secret, the derivation\n * key and the stamp's signer key.\n *\n * The wire form, not the parsed one: `AuthDataSchema` revives `BatchId` and\n * `PrivateKey` instances, which do not survive `JSON.stringify`, so what goes\n * in is what the popup sent and what comes back out is parsed the same way it\n * was on arrival.\n *\n * The deadline is the one `hydratePartitionAccount` just computed, read back\n * off the connection rather than recomputed, so the session at rest and the\n * session in memory cannot disagree about when it ends.\n */\n private savePartitionSession(data: AuthData): void {\n if (!this.parentOrigin) return\n const parentOrigin = this.parentOrigin\n const connection = this.partitionAccount?.connectedApps.find(\n (app) => app.appUrl === parentOrigin,\n )\n const connectedUntil =\n connection?.connectedUntil ??\n Date.now() + appSessionDuration(this.partitionAccount?.settings)\n // The projection carries `BatchId`/`PrivateKey`/`EthAddress`; this is the\n // same serializer the popup used to build it. This parent's own chat\n // binding is all it keeps, as in `hydratePartitionAccount`.\n const wire = {\n ...data,\n account: serializeSyncedAccount({\n ...data.account,\n connectedApps: onlySiteChatBinding(\n data.account.connectedApps,\n parentOrigin,\n ),\n }),\n }\n this.writePartitionSession(parentOrigin, async () => ({\n version: 1,\n parentOrigin,\n connectedUntil,\n data: await sealValue(JSON.stringify(wire)),\n }))\n }\n\n /**\n * Re-persist the stored session's account view after a live fold moved it.\n *\n * Without this the record is frozen at the handover: a rotated signer key or\n * a moved default stamp arrives over the bus, updates `partitionAccount`, and\n * then a reload resurrects the pre-fold copy and uploads with it until the\n * next live overlap.\n *\n * Only the account. The deadline stays the one the handover recorded, because\n * `hydratePartitionAccount` recomputes `connectedUntil` on every call — write\n * the in-memory one back and a session extends itself for as long as deltas\n * keep arriving. And the record is edited in the shape it was WRITTEN in\n * (raw JSON), not the parsed one: `AuthDataSchema` revives byte classes that\n * do not survive a second `JSON.stringify`.\n *\n * Never creates a record — a session that failed to persist stays unpersisted.\n */\n private refreshStoredPartitionAccount(): void {\n if (!this.partitionAccount || !this.parentOrigin) return\n const parentOrigin = this.parentOrigin\n const account = serializeSyncedAccount(this.partitionAccount)\n this.writePartitionSession(parentOrigin, async () => {\n const stored = await this.openStoredPartitionSession(parentOrigin)\n if (!stored || stored === \"unavailable\") return undefined\n return {\n ...stored.record,\n data: await sealValue(JSON.stringify({ ...stored.wire, account })),\n }\n })\n }\n\n /** Forget the stored session. Every path that ends one calls this. */\n private clearPartitionSession(): void {\n if (!this.parentOrigin) return\n this.writePartitionSession(this.parentOrigin, async () => null)\n }\n\n /**\n * Writes to the stored session wait for sealing, so they run one after\n * another in call order. Otherwise a save that started before a clear could\n * land after it and bring an ended session back. `build` answers the record\n * to write, `null` to remove it, or `undefined` to leave it as it is.\n */\n private writePartitionSession(\n parentOrigin: string,\n build: () => Promise<StoredPartitionSession | null | undefined>,\n ): void {\n const key = partitionSessionStorageKey(parentOrigin)\n const queued = (partitionSessionWrites.get(key) ?? Promise.resolve())\n .then(async () => {\n const record = await build()\n if (record === undefined) return\n if (record === null) localStorage.removeItem(key)\n else localStorage.setItem(key, JSON.stringify(record))\n })\n .catch((error: unknown) => {\n // A session that cannot be stored still works for this page load.\n console.warn(\n \"[Proxy] Could not persist the partitioned session:\",\n error,\n )\n })\n partitionSessionWrites.set(key, queued)\n }\n\n /**\n * The stored record for this parent and its opened wire payload, or\n * `\"unavailable\"` when this page could not read the wrapping key, so the\n * record may still open on a later load.\n */\n private async openStoredPartitionSession(\n parentOrigin: string,\n ): Promise<\n | { record: StoredPartitionSession; wire: Record<string, unknown> }\n | \"unavailable\"\n | undefined\n > {\n let raw: string | null = null\n try {\n raw = localStorage.getItem(partitionSessionStorageKey(parentOrigin))\n } catch {\n return undefined\n }\n if (!raw) return undefined\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return undefined\n }\n const result = PartitionSessionSchemaV1.safeParse(parsed)\n if (!result.success || !isSealedValue(result.data.data)) return undefined\n await primeSealedValues([result.data.data])\n const opened = openedValue(result.data.data)\n if (opened === undefined) {\n return isSealedValueUnavailable(result.data.data)\n ? \"unavailable\"\n : undefined\n }\n try {\n return {\n record: result.data,\n wire: JSON.parse(opened) as Record<string, unknown>,\n }\n } catch {\n return undefined\n }\n }\n\n /**\n * The stored session for THIS parent, if it is still within its deadline.\n *\n * The key is per dApp origin, because the partition is not: it is scoped to\n * the top-level SITE, so same-site dApps share this store (#671). The\n * `parentOrigin` check below is now the belt to that pair of braces rather\n * than the only thing keeping two dApps apart. An expired, malformed or\n * unopenable record is removed on the way past: it will never become valid\n * again, and a record whose seal does not open is no session. So is a record\n * carrying a chat binding that fails `acceptHandedOverChatBinding`, such as\n * one naming the app secret's address from before sites had a chat key:\n * restored without it, the session would hand out a chat key no binding\n * names, so the person unlocks once and the window signs a fresh one. A record whose\n * wrapping key this page could not read is no session for this page only,\n * and stays stored for a later load to open.\n */\n private async readPartitionSession(): Promise<PartitionSession | undefined> {\n if (!this.parentOrigin) return undefined\n const parentOrigin = this.parentOrigin\n // Behind any write still in flight, so a reload sees its own last write.\n await partitionSessionWrites.get(partitionSessionStorageKey(parentOrigin))\n let raw: string | null = null\n try {\n raw = localStorage.getItem(partitionSessionStorageKey(parentOrigin))\n } catch {\n return undefined\n }\n if (!raw) return undefined\n\n const stored = await this.openStoredPartitionSession(parentOrigin)\n if (stored === \"unavailable\") return undefined\n const data = stored && AuthDataSchema.safeParse(stored.wire)\n if (!stored || !data?.success) {\n this.clearPartitionSession()\n return undefined\n }\n if (stored.record.parentOrigin !== parentOrigin) return undefined\n if (stored.record.connectedUntil <= Date.now()) {\n this.clearPartitionSession()\n return undefined\n }\n if (\n data.data.chatBinding !== undefined &&\n !this.acceptHandedOverChatBinding(data.data)\n ) {\n this.clearPartitionSession()\n return undefined\n }\n return {\n parentOrigin: stored.record.parentOrigin,\n connectedUntil: stored.record.connectedUntil,\n data: data.data,\n }\n }\n\n /**\n * Build the in-memory account view for a partitioned session and initialize\n * the write path from it. The vault fields are inert placeholders — the\n * record never touches storage and the seed never leaves first-party\n * context; `SignedInAccount` merely requires their presence.\n */\n private async hydratePartitionAccount(\n account: SyncedAccount,\n appSecret: string,\n networkSettings: NetworkSettings | undefined,\n connectedAt: number,\n ): Promise<void> {\n const now = Date.now()\n const connection: ConnectedApp = {\n ...(account.connectedApps.find(\n (app) => app.appUrl === this.parentOrigin,\n ) ?? {\n appUrl: this.parentOrigin!,\n appName: this.appMetadata?.name ?? this.parentOrigin!,\n lastConnectedAt: connectedAt,\n }),\n appSecret,\n // From the CALLER, never `now`: a handover is a connection and a restore\n // is not, and this clock is what every reader compares a Disconnect\n // against (`restoreLocalSessionFields`, `startRestoredSessionCheck`).\n // Restamping it on a reload made a disconnect made while the tab was\n // closed read as stale, and the session came back undead (#670).\n lastConnectedAt: connectedAt,\n // The deadline IS this context's to issue, which is why it is `now`-based\n // while the line above is not: `savePartitionSession` reads it straight\n // back off this connection, and a restore is bounded by the deadline the\n // stored record already carries (`readPartitionSession`).\n connectedUntil: now + appSessionDuration(account.settings),\n }\n this.partitionAccount = {\n ...account,\n // Every other site's chat binding is dropped here, on the handover and\n // on a restore alike: this frame runs inside its parent's page, and a\n // binding links the account to its chat on that binding's site.\n connectedApps: [\n connection,\n ...onlySiteChatBinding(\n account.connectedApps.filter(\n (app) => app.appUrl !== this.parentOrigin,\n ),\n this.parentOrigin!,\n ),\n ],\n access: { type: \"password\", kdfSalt: \"\", kdfIterations: 0 },\n encryptedSeed: \"\",\n }\n\n this.applyNetworkSettingsValues(networkSettings)\n\n await this.refreshStampFromStorage()\n }\n\n /**\n * Join the account bus for whatever connection is resolvable right now\n * (idempotent per account). Called from every path that establishes or\n * re-resolves a connection — the unpartitioned ones included, or the bus\n * would never reach past this origin+partition outside Safari.\n */\n private joinAccountBus(): void {\n const connection = this.findConnectionForParent()\n if (!connection) return\n const { derivationKey } = connection.account\n const accountId = connection.account.id.toHex()\n // Already joined for this account, with everything it wanted attached.\n // This runs on every accounts storage event, so returning here (rather\n // than after the derivation) saves two HMACs and an `importKey` per event\n // — and avoids bumping `busJoinGeneration`, which retires a join still in\n // flight. A join left incomplete (a transport that threw) does not latch\n // the key, so it retries here instead.\n if (derivationKey === this.busJoinedKey) return\n void this.ensureAccountBusTransports(derivationKey, accountId)\n }\n\n /**\n * Attach the account's bus transports (docs/Account-Bus.md). Idempotent per\n * account; same code path on every browser — Safari is not special.\n *\n * BOTH transports hang off the account-derived topic, so a context only ever\n * shares a channel with other contexts of the same account. The local one is\n * attached whether or not a signaling URL is configured: without it there is\n * still same-device tab↔tab traffic to carry, and a build with no signaling\n * URL must not lose cross-tab utilization.\n *\n * Because the topic is derived, both transports now attach a few ticks after\n * an auth event rather than at construction, so a peer publishing inside\n * that window loses that one message. Bounded by a single HMAC derivation,\n * and no worse than a tab that has not finished loading — which has no\n * listener either; durable counters reconcile on the next read. `busTopic`\n * is set exactly when the local transport goes live, so it is the signal to\n * wait on.\n *\n * The two attaches are INDEPENDENT. `SignalingTransport` connects in its\n * constructor, where `new URL` / `new WebSocket` throw synchronously on a\n * url that is set but unusable (`ws://` from an https page, a CSP that omits\n * the bus host, a typo) — and that must cost the signaling transport only.\n * Letting it take the local one down produces \"no bus at all, for the whole\n * session\", silently and indistinguishably from a healthy build with no\n * signaling url at all.\n */\n private async ensureAccountBusTransports(\n derivationKey: string,\n accountId: string,\n ): Promise<void> {\n const generation = ++this.busJoinGeneration\n let context: BusContext\n try {\n context = await deriveBusContext(derivationKey)\n } catch (error) {\n console.error(\"[Proxy] Failed to derive the account bus context:\", error)\n // Fail safe, not fail wrong: we no longer know this session's room, so\n // the previous account's must not stay attached under it.\n if (generation === this.busJoinGeneration) this.detachBusTransports()\n return\n }\n // Superseded while the key derivation was in flight — by a later join\n // (another account) or by `clearAuthData`, which drops the removers\n // synchronously. Attaching now would put a socket in a room this session\n // no longer belongs to, and overwrite a remover that a live join owns.\n if (generation !== this.busJoinGeneration) return\n\n const joiningRoom = this.busTopic !== context.topic\n if (joiningRoom) {\n // Leave the old room BEFORE anything can throw. A switch that fails\n // otherwise leaves this session publishing the new account's traffic\n // into the previous account's channel — the leak the account-derived\n // topic closes, surviving in the error path.\n this.detachBusTransports()\n try {\n this.removeBusLocal = this.bus.addTransport(\n new BroadcastChannelTransport(context.topic),\n )\n } catch (error) {\n // `new BroadcastChannel` throws in a detaching document. Nothing to\n // unwind: the signaling transport is constructed below, so this order\n // never leaves a live socket with no handle to close it.\n console.error(\n \"[Proxy] Failed to attach the local bus transport:\",\n error,\n )\n return\n }\n this.busTopic = context.topic\n // One beat per room, started here rather than below: the lines below\n // run again on every storage event while the signaling attach keeps\n // failing (`busJoinedKey` stays unset), and a second interval would\n // double the beat for the life of the page.\n this.presenceTimer = setInterval(\n () => this.publishPresence(),\n PRESENCE_INTERVAL_MS,\n )\n }\n // Reached only with the local transport live for `context.topic`, so this\n // is the account a publish now actually reaches — whether the room was just\n // switched or was already the right one.\n this.busBoundAccountId = accountId\n // Announce at once, so a sibling context does not wait a whole interval to\n // count us. Remote peers hear the first interval beat: the signaling\n // socket below has not opened yet, and a publish with no peers is dropped.\n if (joiningRoom) this.publishPresence()\n\n if (this.signalingUrl && !this.removeBusSignaling) {\n try {\n this.removeBusSignaling = this.bus.addTransport(\n new SignalingTransport({\n url: this.signalingUrl,\n topic: context.topic,\n encryptionKey: context.encryptionKey,\n createPeerConnection:\n typeof RTCPeerConnection !== \"undefined\"\n ? () => new RTCPeerConnection()\n : undefined,\n }),\n )\n } catch (error) {\n console.error(\n \"[Proxy] Failed to attach the bus signaling transport:\",\n error,\n )\n // Leaves `busJoinedKey` unset, so the next join retries it — against\n // a local transport that is already live and stays that way.\n return\n }\n }\n this.busJoinedKey = derivationKey\n }\n\n /**\n * \"This device is live\", to the account's room. Reads the room and the\n * device id at fire time — the same guard `publishAccountDelta` carries —\n * so a beat scheduled for one account can never name it into another's\n * room during a switch, and one that fires before auth finished says nothing.\n */\n private publishPresence(): void {\n const accountId = this.busBoundAccountId\n const deviceId = this.deviceId\n if (!accountId || !deviceId) return\n this.bus.publish({ type: \"presence\", accountId, fromDeviceId: deviceId })\n }\n\n /** Stop beating and forget who was live: the next room starts from nothing. */\n private stopPresence(): void {\n if (this.presenceTimer !== undefined) {\n clearInterval(this.presenceTimer)\n this.presenceTimer = undefined\n }\n this.presence.clear()\n }\n\n /** Leave the account's bus room: both transports detached and closed, and\n * every latch cleared so the next join is a fresh one. */\n private detachBusTransports(): void {\n this.stopPresence()\n this.removeBusSignaling?.()\n this.removeBusSignaling = undefined\n this.removeBusLocal?.()\n this.removeBusLocal = undefined\n this.busTopic = undefined\n this.busJoinedKey = undefined\n this.busBoundAccountId = undefined\n }\n\n /**\n * Clean up resources when the proxy is destroyed.\n * Call this method when the proxy iframe is being unloaded.\n */\n destroy(): void {\n for (const unsubscribe of this.unsubscribeStorageListeners) {\n unsubscribe()\n }\n this.unsubscribeStorageListeners = []\n\n // Release the partition lease (best-effort) so peers see this device\n // vacate its partition promptly.\n this.teardownCoordinator()\n if (this.publishTimer !== undefined) {\n clearTimeout(this.publishTimer)\n this.publishTimer = undefined\n }\n\n // Retire any join still deriving its key — its continuation would\n // otherwise attach a freshly-opened socket to the bus we are closing,\n // with no handle left to close it. (`AccountBus.addTransport` also\n // refuses once closed; this just avoids opening the socket at all.)\n this.busJoinGeneration += 1\n this.stopPresence()\n this.removeBusLocal = undefined\n this.removeBusSignaling = undefined\n this.busTopic = undefined\n this.busJoinedKey = undefined\n this.busBoundAccountId = undefined\n this.bus.close()\n }\n\n /**\n * Tear the write coordinator down, announcing the partition it held so\n * waiting peers wake now instead of sleeping out their poll interval\n * (docs/Account-Bus.md, bus-accelerated leases). The announcement races the\n * Swarm release it describes, which is harmless: the lock SOCs stay the\n * authority, so a peer woken too early just spends one extra read round.\n */\n private teardownCoordinator(): void {\n // Scheduled answers describe a lease we are about to drop anyway.\n this.cancelPendingYields()\n // A held partition is announced through `onLeaseReleased` on the way out.\n this.coordinator?.teardown()\n this.coordinator = undefined\n // The pool signs for the stamper this coordinator wrote through, and its\n // workers hold that stamper's signer key.\n this.terminateStampWorkerPool()\n }\n\n private terminateStampWorkerPool(): void {\n this.stampWorkerPool?.terminate()\n this.stampWorkerPool = undefined\n }\n\n private static laneKey(\n batchId: string,\n partition: number,\n partitionCount: number,\n ): string {\n return `${batchId}:${partition}/${partitionCount}`\n }\n\n /**\n * Hold a lane-scoped delta that arrived with no lane bound to compare it\n * against, merged into whatever that lane already has.\n */\n private bufferLaneUpdate(message: {\n batchId: string\n partition: number\n partitionCount: number\n buckets: Array<{ index: number; value: number }>\n }): void {\n const key = SwarmIdProxy.laneKey(\n message.batchId,\n message.partition,\n message.partitionCount,\n )\n const buckets =\n this.pendingLaneUpdates.get(key) ?? new Map<number, number>()\n for (const { index, value } of message.buckets) {\n const current = buckets.get(index)\n if (current === undefined || value > current) buckets.set(index, value)\n }\n this.pendingLaneUpdates.set(key, buckets)\n }\n\n /**\n * Fold the buffered delta for the lane we just bound in, then drop every\n * buffered lane — the ones we did not bind are another device's business.\n * Called on every lease acquire: on the cold path the counters are already\n * seeded from the durable partition-state feed, so this is a harmless\n * monotonic no-op; on the adopt fast path they are not, and this is the\n * whole point.\n */\n private applyPendingLaneUpdate(): void {\n const buffered = this.pendingLaneUpdates\n this.pendingLaneUpdates = new Map()\n const lane = this.stamper?.currentPartition\n if (!this.stamper || !this.postageBatchId || lane === undefined) return\n const buckets = buffered.get(\n SwarmIdProxy.laneKey(\n this.postageBatchId,\n lane,\n this.stamper.partitionCount,\n ),\n )\n if (!buckets) return\n this.stamper.applyUtilizationUpdate(\n [...buckets].map(([index, value]) => ({ index, value })),\n )\n }\n\n /**\n * Run this holder's answer to a peer's `lease-request`, announcing the\n * partition it gave up so the waiter (and holders further down the rank\n * order) hear about it.\n *\n * The `lease-claim` goes out FIRST. Releasing is two stamped Swarm writes\n * (`yieldIdleLease` → `lease.release()`), which is orders of magnitude\n * longer than a rank step, so announcing only on completion would reach the\n * other holders long after every one of them had begun releasing too — #576\n * with extra steps. A claim is cheap to send and is what the rank step\n * actually budgets for.\n */\n private answerLeaseRequest(\n accountId: string,\n batchId: string,\n requestId: string,\n ): void {\n const coordinator = this.coordinator\n if (!coordinator) return\n // Once the rank timer fires its handle is gone from `pendingYields`, so\n // the answered-id memory is what keeps a round from being answered twice\n // from here on. The claim below goes out in this same tick, so nothing\n // can slip between the two — the guard is what keeps that true if a\n // caller ever gets here after an await.\n if (this.answeredRequests.has(requestId)) return\n this.markRequestAnswered(requestId)\n this.bus.publish({\n type: \"lease-claim\",\n accountId,\n batchId,\n fromDeviceId: this.requireDeviceId(),\n requestId,\n })\n // The release itself is announced through `onLeaseReleased`.\n coordinator.yieldForPeer(requestId).catch((error) => {\n console.error(\"[Proxy] Peer lease yield failed:\", error)\n })\n }\n\n /**\n * How long this holder waits before answering `requestId`.\n *\n * A waiter needs exactly ONE slot, but the request names no partition, so\n * every idle holder used to release at once: with `partitionCount = 4` all\n * four dropped their lease, each then paid a cold acquire on its next\n * upload, and whoever lost the re-race got \"Uploads are unavailable\". The\n * waiter re-broadcasts every round, so it repeated.\n *\n * The request id gives every holder the same permutation of the partitions,\n * and each waits its own rank in it. Exactly one draws rank 0 and answers\n * immediately — so the ordinary one-holder handover is not slowed at all —\n * while the rest stand down as soon as they see its `lease-claim`. Only\n * holders that would presently yield take a rank at all (`canYieldForPeer`),\n * so a holder mid-burst does not sit at the front of the order and decline\n * in silence; the next eligible rank answers a step later, which still beats\n * waiting out a poll interval.\n *\n * `scripts/bus-handover-latency.ts` reimplements this election (these\n * handlers are private) to measure the handover against a real cluster.\n * Change the formula here and change it there, or the script starts\n * measuring a race the product does not have.\n */\n private yieldRankDelayMs(requestId: string): number {\n const coordinator = this.coordinator\n const partition = coordinator?.currentPartition\n const partitionCount = coordinator?.partitionCount ?? 1\n if (partition === undefined || partitionCount <= 1) return 0\n // The id is hex (schema-enforced), so the seed needs no hash function.\n const seed = Number.parseInt(\n requestId.slice(0, LEASE_REQUEST_ID_LENGTH),\n 16,\n )\n // Unreachable behind the schema, and deliberately LAST rather than first\n // if it ever becomes reachable: a seed nobody can agree on must not put\n // every holder at the front of the order — that is the bug, not the guard.\n if (!Number.isFinite(seed)) {\n return (partitionCount - 1) * PEER_YIELD_RANK_STEP_MS\n }\n return ((seed + partition) % partitionCount) * PEER_YIELD_RANK_STEP_MS\n }\n\n /** Stop answering `requestId` — somebody else claimed it (or we just did).\n * Remembered as well as cancelled, because the rank timer may already have\n * fired: it hands off to `answerLeaseRequest`, which re-checks this. */\n private standDownFromRequest(requestId: string | undefined): void {\n if (requestId === undefined) return\n this.markRequestAnswered(requestId)\n const scheduled = this.pendingYields.get(requestId)\n if (scheduled === undefined) return\n clearTimeout(scheduled)\n this.pendingYields.delete(requestId)\n }\n\n private markRequestAnswered(requestId: string): void {\n this.answeredRequests.add(requestId)\n if (this.answeredRequests.size <= ANSWERED_REQUEST_MEMORY) return\n // Insertion-ordered: the oldest round is the one safest to forget.\n const oldest = this.answeredRequests.values().next().value\n if (oldest !== undefined) this.answeredRequests.delete(oldest)\n }\n\n /** Drop every scheduled answer — nothing they would release is ours now. */\n private cancelPendingYields(): void {\n for (const timer of this.pendingYields.values()) {\n clearTimeout(timer)\n }\n this.pendingYields.clear()\n }\n\n /**\n * Account-bus subscription: utilization deltas from other contexts, and the\n * lease fast path (docs/Account-Bus.md, bus-accelerated leases). The Swarm\n * lock-SOC protocol stays authoritative — these messages only shortcut its\n * timers.\n */\n private setupBusListeners(): void {\n this.bus.onPeerLeft((peerId) => this.presence.forgetPeer(peerId))\n this.bus.subscribe((message, from) => {\n switch (message.type) {\n case \"utilization-updated\": {\n // Same batch AND same slot lane. The counters are per-partition\n // (`slot = partitionCount + partition + partitionCount·j`), so a\n // delta from a peer holding a DIFFERENT partition of this batch —\n // which is every other device of the account — is not comparable:\n // folding it in would skip this lane past its own unused slots and\n // then publish that as the partition's durable resume counter.\n // An unbound stamper has no lane of its own, so only a legacy\n // single-partition delta can apply. Spelled out rather than leaning\n // on `currentPartition ?? 0` staying unreachable for multi-partition\n // stampers (`unbindPartition` also resets the count to 1) — that\n // coupling lives in another file and is exactly the assumption whose\n // last violation caused the resume-pointer skip described above.\n if (message.batchId !== this.postageBatchId || !this.stamper) return\n const lane = this.stamper.currentPartition\n if (lane === undefined) {\n // Unbound: the lane this delta belongs to may well be the one we\n // are about to bind. Dropping it loses it for good — the adopt\n // fast path re-binds from THIS tab's in-memory counters\n // (`buildLeaseLocalCounter`), not from durable state, so a sibling\n // tab's writes would be invisible and we would re-issue slots it\n // already consumed. Hold it until the bind names our lane.\n // A legacy single-partition delta has no lease to wait for and\n // applies immediately.\n if (message.partition === 0 && message.partitionCount === 1) {\n this.stamper.applyUtilizationUpdate(message.buckets)\n } else {\n this.bufferLaneUpdate(message)\n }\n return\n }\n // Bound: same batch AND same slot lane. The counters are\n // per-partition (`slot = partitionCount + partition + partitionCount·j`),\n // so a delta from a peer holding a DIFFERENT partition of this batch\n // is not comparable: folding it in would skip this lane past its own\n // unused slots and then publish that as the partition's durable\n // resume counter.\n if (\n message.partition === lane &&\n message.partitionCount === this.stamper.partitionCount\n ) {\n // Apply delta update directly - no IndexedDB read needed\n this.stamper.applyUtilizationUpdate(message.buckets)\n }\n return\n }\n case \"lease-request\": {\n const coordinator = this.coordinator\n // Batch as well as account: a lane is one partition of ONE batch\n // (#589), so a waiter on another batch contends with nothing we\n // hold, and yielding to it would drop a lease that helps neither of\n // us. Per-app stamp overrides make that a live pairing, not a\n // multi-batch hypothetical.\n if (\n !coordinator ||\n message.accountId !== coordinator.accountId ||\n message.batchId !== coordinator.batchId ||\n message.fromDeviceId === this.deviceId\n ) {\n return\n }\n const { requestId } = message\n // Idempotent per request: the same message reaches us over every\n // attached transport, and the waiter re-broadcasts each round.\n if (this.pendingYields.has(requestId)) return\n if (this.answeredRequests.has(requestId)) return\n // Only holders that would actually yield take a rank. Otherwise a\n // holder mid-burst — with a 3 s idle threshold, the common case on\n // an active account — sits at the front of the order and declines in\n // silence, and every later rank falls through behind it.\n if (!coordinator.canYieldForPeer) return\n const timer = setTimeout(() => {\n this.pendingYields.delete(requestId)\n this.answerLeaseRequest(\n message.accountId,\n message.batchId,\n requestId,\n )\n }, this.yieldRankDelayMs(requestId))\n this.pendingYields.set(requestId, timer)\n return\n }\n case \"lease-claim\": {\n if (\n message.fromDeviceId === this.deviceId ||\n message.accountId !== this.coordinator?.accountId ||\n message.batchId !== this.coordinator?.batchId\n ) {\n return\n }\n // A peer earlier in the rank order is answering this request — stand\n // down rather than dropping a second lease for one waiter. No slot is\n // free yet, so nothing wakes a waiting poll here.\n this.standDownFromRequest(message.requestId)\n return\n }\n case \"lease-released\": {\n if (\n message.fromDeviceId === this.deviceId ||\n message.accountId !== this.coordinator?.accountId ||\n message.batchId !== this.coordinator?.batchId\n ) {\n return\n }\n // The claim normally arrives first; this also covers a peer whose\n // claim we missed, and a release that answers no request at all.\n this.standDownFromRequest(message.requestId)\n this.coordinator?.notifySlotMaybeFree()\n return\n }\n case \"account-delta\": {\n this.applyAccountDelta(message.snapshot)\n return\n }\n case \"presence\": {\n // A sibling tab of this device shares the id over the local\n // transport; it is not a rival. A beat naming another account did\n // not come from this room's scope (belt and braces, as elsewhere).\n if (\n message.accountId !== this.busBoundAccountId ||\n message.fromDeviceId === this.deviceId\n ) {\n return\n }\n this.presence.observe(message.fromDeviceId, Date.now(), from)\n return\n }\n }\n })\n }\n\n /**\n * Fold a peer's account snapshot into this session's view and reconcile.\n *\n * This is the only push channel a partitioned iframe has: it cannot see\n * shared storage, so without this a revoke reaches it only when the page\n * closes — and since #547 that session is a full writer holding every stamp's\n * signer key (docs/Account-Bus.md, `account-delta`).\n *\n * Durable truth is unchanged. An unpartitioned session still owns its state\n * through storage; the delta only drives the reconcile, and `clearAuthData`\n * invalidates the stored entry if the outcome is a disconnect.\n */\n private applyAccountDelta(snapshot: AccountStateSnapshot): void {\n // An EXPIRED session consumes deltas too, hence `includeEnded`. Nothing\n // expires a session live: `connectedUntil` is set once, at connect, and a\n // partitioned session's is hydrated from the popup — so a page open past\n // its deadline keeps uploading (`ensureCanUpload` reads the secret, not the\n // clock) while `findConnectionForParent` already reports nothing. Requiring\n // a VALID connection here would make the revoke that should end such a\n // session the one message it cannot hear. Folding into it is safe: the\n // reconcile below re-resolves under the normal rule, sees no valid\n // connection, and disconnects — where an expired session belongs anyway.\n const connection = this.findConnectionForParent({ includeEnded: true })\n // A delta for an account this origin is not connected to says nothing\n // about this session. (The topic is account-derived, so this is a\n // belt-and-braces check rather than the boundary.)\n if (!connection || snapshot.accountId !== connection.account.id.toHex()) {\n return\n }\n\n // Only a partitioned session folds. The hydrated view IS its state, and it\n // has no other way to learn this. An unpartitioned session's record is\n // shared storage, which this message did not write, so folding into memory\n // would only diverge the two — it reconciles from storage below instead.\n // (A cross-device revoke still reaches an unpartitioned session — the\n // SwarmID tab folds the same delta into shared storage, and the storage\n // event carries it here; #608, docs/Account-Bus.md.)\n const { account } = connection\n if (this.partitionAccount) {\n // The shared fold, not a second set of rules: `foldAccount` merges the\n // collections per `appUrl` / `batchID` on their tombstone clocks AND\n // every scalar on its own per-field clock, exactly as the Swarm read\n // path does. The metadata is not optional here — a moved default stamp\n // arrives as \"old one tombstoned, new one added, pointer moved\", so\n // folding the collections alone leaves the pointer on the tombstone,\n // `resolveStampForApp` resolves nothing, and the session cannot upload\n // for the page's life with the replacement key already in hand.\n // Local view first, so a tie on a collection entry keeps ours.\n const folded = foldAccount(\n [\n deltaFoldView(accountAsStateSnapshot(account)),\n deltaFoldView(snapshot),\n ],\n mergeDevicesList(account.devices, snapshot.metadata.devices),\n account.id.toHex(),\n )\n const foldedAccount = foldedToSyncedAccount({\n id: account.id,\n derivationKey: account.derivationKey,\n account: folded,\n // The NEWER of the two inputs, never a fresh `Date.now()` (the\n // default): a fold states what the two sides already say, it does not\n // make a change. `accountAsStateSnapshot` reads this back as the\n // snapshot clock on the next fold, so a fresh one here would let this\n // session outrank a peer's genuine edit purely by having consumed a\n // message.\n lastModified: Math.max(\n account.lastModified ?? account.createdAt,\n snapshot.metadata.lastModified,\n ),\n })\n // The bus schema already drops every chat binding. This holds the same\n // line for the fold's output, so no path into this view can bring\n // another site's binding in.\n const connectedApps = onlySiteChatBinding(\n restoreLocalSessionFields(folded.connectedApps, account.connectedApps),\n connection.app.appUrl,\n )\n this.partitionAccount = {\n ...this.partitionAccount,\n ...foldedAccount,\n connectedApps,\n // Narrowed back down after the fold: the publisher is an unpartitioned\n // context and sends the whole collection, so keeping what it sends\n // would undo the narrowed handover (#578) on the first delta. The fold\n // still runs, because the session must hear about ITS stamp — a\n // rotated signer key, a tombstone — and LWW is what tells it.\n //\n // Narrowed by the pointers the fold just decided, never the pre-fold\n // ones: one delta can carry both a re-pointed app (or a moved account\n // default) and the stamp it now names, and filtering by the old\n // pointers drops the stamp while keeping the pointer —\n // `resolveStampForApp` then falls through, and the session spends the\n // wrong batch or nothing at all, with nothing to correct it until some\n // unrelated change publishes again.\n postageStamps: stampsReachableByApp(\n connectedApps.find((app) => app.appUrl === connection.app.appUrl) ??\n connection.app,\n foldedAccount,\n foldedAccount.postageStamps,\n ),\n }\n // The stored session goes with it, or the next reload undoes this fold\n // (#636 review).\n this.refreshStoredPartitionAccount()\n }\n\n // On the reconcile queue, not beside it: this runs the same stamper rebind\n // a storage event does, and its rejections need the queue's catch. Only the\n // RECONCILE is serialized — the fold above is synchronous, so a second delta\n // can still land between a queued reconcile's awaits. That converges (the\n // fold is LWW and the reconcile re-reads the view), it is not ordered.\n this.enqueueReconcile(\"applyAccountDelta\", () =>\n this.reevaluateConnection(\"bus\"),\n )\n }\n\n /**\n * Create an onProgress callback for upload operations.\n * Returns undefined if progress reporting is disabled.\n */\n private createProgressCallback(\n event: MessageEvent,\n requestId: string,\n enableProgress: boolean | undefined,\n ): ((progress: UploadProgress) => void) | undefined {\n if (!enableProgress) return undefined\n return (progress: UploadProgress) => {\n this.postMessage(event, {\n type: \"uploadProgress\",\n requestId,\n total: progress.total,\n processed: progress.processed,\n })\n }\n }\n\n /**\n * Announce that proxy is ready to receive messages\n * Broadcasts to parent with wildcard origin since we don't know parent origin yet\n */\n private announceReady(): void {\n if (window.parent && window.parent !== window) {\n window.parent.postMessage(\n { type: \"proxyInitialized\" },\n \"*\", // Wildcard since we don't know parent origin yet\n )\n }\n }\n\n /**\n * Get the stored postage batch ID\n */\n getPostageBatchId(): string | undefined {\n return this.postageBatchId\n }\n\n /**\n * Get the stored signer key\n */\n getSignerKey(): string | undefined {\n return this.signerKey\n }\n\n /**\n * Initialize the Stamper for client-side signing\n * Uses UtilizationAwareStamper to track bucket usage\n */\n private async initializeStamper(stampDepth: number): Promise<void> {\n const { signerKey, postageBatchId: batchId } = this\n if (!signerKey || !batchId) {\n console.warn(\n \"[Proxy] Cannot initialize stamper: missing signer key or batch ID\",\n )\n return\n }\n // Captured once, above: `loadAuthData` calls this off the reconcile queue,\n // so a storage event's `refreshStampFromStorage` can clear or rebind the\n // stamp under any await below. Reading the fields again afterwards built a\n // stamper and a coordinator for `batchId: undefined`, and every lease op on\n // it threw (#804). A stale init leaves the session to the refresh that\n // superseded it.\n // The generation catches a re-init for the SAME stamp — a storage event\n // during the first init finds no fingerprint yet and inits again — so the\n // older init yields to the newer one instead of racing it for the fields.\n const generation = ++this.stamperInitGeneration\n const superseded = (): boolean =>\n this.postageBatchId !== batchId ||\n this.signerKey !== signerKey ||\n this.stamperInitGeneration !== generation\n\n // Look up account info for utilization tracking\n const accountInfo = await this.lookupAccountForApp()\n if (superseded()) return\n if (!accountInfo) {\n console.warn(\"[Proxy] Cannot initialize stamper: account not found\")\n return\n }\n\n // Initialize utilization cache if not already done\n if (!this.utilizationStore) {\n this.utilizationStore = new UtilizationStoreDB()\n }\n\n // Create utilization-aware stamper with owner and encryption key\n let stamper: UtilizationAwareStamper\n try {\n stamper = await UtilizationAwareStamper.create(\n signerKey,\n new BatchId(batchId),\n stampDepth,\n this.utilizationStore,\n accountInfo.owner,\n accountInfo.encryptionKey,\n )\n } catch (error) {\n // Nothing to clear: the write path was dropped when the stamp was\n // resolved (`setResolvedStamp`), and if this init has been superseded,\n // whatever stamper there is by now belongs to the newer one.\n console.error(\"[Proxy] Failed to create stamper:\", error)\n return\n }\n if (superseded()) return\n\n // Build the write-path coordinator for this account+batch. It owns the\n // cross-tab write lock, the partition-lease lifecycle, and the stamp flush.\n // Lock-SOC routing on the stamper was auto-bound inside\n // `UtilizationAwareStamper.create`. For multi-device accounts the\n // coordinator eagerly pre-acquires a partition in the background\n // (`startLease`) so the first upload doesn't pay the acquire latency; a\n // concurrent first upload queues on the same write lock and then finds the\n // lease already held. Single-device accounts get a lock-only coordinator.\n // No await from here to `startLease`: the stamper and the coordinator that\n // writes through it are assigned together, because a stamper without its\n // coordinator tells an upload it is in user-stamp mode and then has nothing\n // to write through. It also means the stamper is assigned the moment\n // `create` returns: a utilization delta that arrives while there is no\n // stamper is dropped, so one a peer broadcast after `create` read the\n // stored counters would end up in neither.\n this.stamper = stamper\n this.stamperAccountFingerprint = `${accountInfo.owner.toHex()}-${uint8ArrayToHex(accountInfo.encryptionKey)}`\n const tuning = readPartitionTuningOverride()\n // The callbacks below outlive this narrowing, and the batch they name has\n // to be the one the coordinator serves, on both counts: its lease cache is\n // keyed on it, so a snapshot filed under a batch we have since moved off\n // hints at a lane nobody holds (#684), and a lease message naming that\n // batch answers nobody (#589). `batchId` is the capture from the top.\n const accountBee = (): Bee => this.accountBee\n this.coordinator = new BatchWriteCoordinator({\n // The person's node, read on every use so a change to their settings\n // reaches the coordinator already built. The site's content uploads\n // are moved to the site's node in `withModeAwareWriteLock`.\n get bee() {\n return accountBee()\n },\n batchId,\n stamper,\n deviceId: this.requireDeviceId(),\n knownDeviceIds: () =>\n this.knownDeviceIdsForAccount(accountInfo.accountId),\n // Pull the latest device registry before a fresh acquire so a peer that\n // signed in after we created the account is known (else we never read its\n // beacon and dual-acquire). Throttled + best-effort inside.\n refreshKnownDeviceIds: () =>\n this.refreshDeviceRegistryFromSwarm(\n accountInfo.accountId,\n accountInfo.encryptionKey,\n ),\n intentReadTimeoutMs: tuning?.readTimeoutMs,\n intentGuardWindowMs: tuning?.guardWindowMs,\n intentGuardPollMs: tuning?.guardPollMs,\n accountId: accountInfo.accountId,\n backupSigner: new PrivateKey(\n deriveSecretSync(\n uint8ArrayToHex(accountInfo.encryptionKey),\n BACKUP_KEY_LABEL,\n ),\n ),\n swarmEncryptionKey: accountInfo.encryptionKey,\n partitionCount: accountInfo.partitionCount,\n mode: \"persistent\",\n readLeaseCache: () => this.readLeaseCache(accountInfo.accountId, batchId),\n writeLeaseCache: (snap) =>\n this.writeLeaseCache(accountInfo.accountId, batchId, snap ?? null),\n flushStamperState: () => this.saveStamperStateIfNeeded(),\n getWorkerPool: (count) => this.getOrCreateWorkerPool(count),\n onLeaseChange: () => this.emitConnectionInfoIfChanged(),\n // Each slot-wait poll round, ask live holders over the account bus to\n // yield their partition (docs/Account-Bus.md, bus-accelerated leases).\n // Fresh id per round: every holder derives the same rank order from it,\n // so exactly one answers (see `yieldRankDelayMs`), and a new round is a\n // new draw — a holder that was busy last time is not permanently first.\n onSlotWait: () =>\n this.bus.publish({\n type: \"lease-request\",\n accountId: accountInfo.accountId,\n batchId,\n fromDeviceId: this.requireDeviceId(),\n requestId: newLeaseRequestId(),\n }),\n // On first acquiring a partition, announce this device by publishing the\n // account snapshot (which includes ourselves in metadata.devices) to the\n // shared feed. Debounced + deferred so it runs OUTSIDE the acquiring write\n // lock (the publish re-enters the lock via the coordinator).\n //\n // Fold in any delta buffered while we had no lane FIRST, and\n // synchronously: the bind that just happened may have seeded the counters\n // from this tab's own in-memory state (the adopt fast path does), and the\n // publish below reads those counters.\n onLeaseAcquired: () => {\n this.applyPendingLaneUpdate()\n this.schedulePublish(\"acquired\")\n },\n // Every release — idle timer, a peer's request, teardown — wakes the\n // waiters' slot polls; the announcement is not each path's to remember.\n onLeaseReleased: (partition, requestId) => {\n this.bus.publish({\n type: \"lease-released\",\n accountId: accountInfo.accountId,\n batchId,\n partition,\n fromDeviceId: this.requireDeviceId(),\n requestId,\n })\n },\n })\n this.startLeaseOnceAllowed(this.coordinator)\n }\n\n /**\n * The eager lease claim is a write of its own, so a restored session makes\n * it only once its check allows writes. A session whose check could not\n * complete claims lazily instead, on the first write a later check allows.\n */\n private startLeaseOnceAllowed(coordinator: BatchWriteCoordinator): void {\n const check = this.restoredSessionCheck\n if (!check) {\n coordinator.startLease()\n return\n }\n void check.verdict.then((verdict) => {\n if (verdict === \"allowed\" && this.coordinator === coordinator) {\n coordinator.startLease()\n }\n })\n }\n\n /**\n * This device's identity, captured once. Lazily initialised here as a\n * fallback; the auth entry points set it eagerly. Reused for every lease\n * operation so acquire / refresh / validate never disagree about who we\n * are (which previously caused false \"reclaimed by another device\").\n */\n private requireDeviceId(): string {\n if (!this.deviceId) this.deviceId = getOrCreateDeviceId()\n return this.deviceId\n }\n\n private readLeaseCache(\n accountId: string,\n batchId: string,\n ): PartitionLeaseStateSnapshot | undefined {\n try {\n const raw = localStorage.getItem(leaseCacheStorageKey(accountId, batchId))\n return raw ? (JSON.parse(raw) as PartitionLeaseStateSnapshot) : undefined\n } catch {\n return undefined\n }\n }\n\n private writeLeaseCache(\n accountId: string,\n batchId: string,\n snap: PartitionLeaseStateSnapshot | null,\n ): void {\n const key = leaseCacheStorageKey(accountId, batchId)\n if (snap === null) {\n localStorage.removeItem(key)\n } else {\n localStorage.setItem(key, JSON.stringify(snap))\n }\n }\n\n /**\n * Get or create a StampWorkerPool for parallel signing.\n * Lazy-initialized on first use and reused across uploads, for as long as\n * it signs for the current stamper and has the requested size.\n */\n private async getOrCreateWorkerPool(\n requestedCount?: number,\n ): Promise<StampWorkerPool | undefined> {\n const desiredCount =\n requestedCount ??\n (typeof navigator !== \"undefined\" && navigator.hardwareConcurrency\n ? Math.min(navigator.hardwareConcurrency, 8)\n : 4)\n\n if (\n this.stampWorkerPool?.size === desiredCount &&\n this.stampWorkerPool.stamper === this.stamper\n ) {\n return this.stampWorkerPool\n }\n\n this.terminateStampWorkerPool()\n\n if (!this.signerKey || !this.stamper) return undefined\n\n try {\n this.stampWorkerPool = await StampWorkerPool.create(\n this.signerKey,\n this.stamper,\n desiredCount,\n )\n return this.stampWorkerPool\n } catch (error) {\n console.warn(\"[Proxy] Failed to create StampWorkerPool:\", error)\n return undefined\n }\n }\n\n /**\n * Save stamper bucket state to IndexedDB, logging a failure rather than\n * throwing. The upload path relies on this: the coordinator flushes in a\n * `finally`, after the upload has already gone out.\n */\n private async saveStamperState(): Promise<void> {\n try {\n await this.persistStamperState()\n } catch (error) {\n console.error(\"[Proxy] Failed to save stamper state:\", error)\n }\n }\n\n /**\n * Save stamper bucket state to IndexedDB and tell local peers, throwing when\n * the save fails.\n */\n private async persistStamperState(): Promise<void> {\n if (!this.stamper) {\n return\n }\n\n // Capture bucket updates BEFORE flush clears dirtyBuckets — and the lane\n // they belong to in the same breath. Reading the lane after the await\n // instead lets a teardown landing mid-flush (`unbindPartition` resets the\n // partition to undefined and the count to 1) relabel partition-p counters\n // as the legacy `{0, 1}` lane, which every momentarily-unbound same-batch\n // peer then folds — the exact resume-pointer skip the lane guard exists\n // to prevent.\n const buckets = this.stamper.getBucketUpdatesForBroadcast()\n const partition = this.stamper.currentPartition ?? 0\n const partitionCount = this.stamper.partitionCount\n\n await this.stamper.flush()\n\n // Broadcast the utilization update to peers with pre-captured buckets.\n // Local transports only: the counters are per-partition, and every peer\n // a remote transport reaches is a different device holding a different\n // lane, so a remote copy is dropped at the receive guard after paying for\n // encryption and a frame the signaling server's payload cap may refuse\n // outright (one entry per dirty bucket, up to NUM_BUCKETS of them).\n if (this.postageBatchId && buckets.length > 0) {\n this.bus.publish(\n {\n type: \"utilization-updated\",\n batchId: this.postageBatchId,\n partition,\n partitionCount,\n buckets,\n },\n { localOnly: true },\n )\n }\n }\n\n /**\n * Execute an upload operation against the right target for the current mode.\n * In subsidised mode (no usable user stamp) the gateway handles stamping, so\n * there is no local stamp state to protect and we run unlocked. In user-stamp\n * mode the write goes through the {@link BatchWriteCoordinator}, which takes\n * the cross-tab write lock, ensures a held partition, and flushes stamper\n * state — the proxy no longer owns any of that.\n */\n private async withModeAwareWriteLock<T>(\n targetOptions: { useWorkers?: boolean; workerCount?: number } | undefined,\n operation: (target: UploadTarget) => Promise<T>,\n ): Promise<T> {\n if (this.isSubsidisedModeActive()) {\n return operation({\n mode: \"subsidised\",\n gatewayUrl: this.subsidisedGatewayUrl!,\n })\n }\n if (!this.coordinator) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"Stamper not initialized. Please login first.\",\n )\n }\n return this.coordinator.withWrite(\n (target) =>\n operation(\n target.mode === \"stamper\" ? { ...target, bee: this.nodeBee } : target,\n ),\n targetOptions,\n )\n }\n\n /**\n * Save stamper state only in stamper mode.\n * In subsidised mode, there's no local stamp state to persist.\n */\n private async saveStamperStateIfNeeded(): Promise<void> {\n if (!this.isSubsidisedModeActive()) {\n await this.saveStamperState()\n }\n }\n\n /**\n * Setup message listener for parent and popup messages\n */\n private setupMessageListener(): void {\n window.addEventListener(\"message\", async (event: MessageEvent) => {\n const { type } = event.data\n\n // Handle parent identification (must come first). Only the embedding\n // parent window may identify itself — any co-embedded iframe can obtain\n // our WindowProxy and race the real parent otherwise (#410).\n if (type === \"parentIdentify\") {\n if (event.source !== window.parent) {\n console.warn(\"[Proxy] Rejected parentIdentify from non-parent window\")\n return\n }\n const result = ParentIdentifyMessageSchema.safeParse(event.data)\n if (!result.success) {\n console.warn(\"[Proxy] Invalid parentIdentify message:\", result.error)\n return\n }\n try {\n await this.handleParentIdentify(result.data, event)\n } catch (error) {\n // Without this the parent never hears back and its initialize()\n // hangs until the initialization timeout.\n console.error(\"[Proxy] Initialization failed:\", error)\n this.postMessage(event, {\n type: \"initError\",\n error:\n error instanceof Error ? error.message : \"Initialization failed\",\n })\n }\n return\n }\n\n // All other messages require parent to be identified first\n if (!this.parentIdentified) {\n console.warn(\"[Proxy] Ignoring message - parent not identified yet\")\n return\n }\n\n // Handle same-origin popup messages (storage partitioning postMessage fallback)\n if (event.origin === window.location.origin && type === \"setSecret\") {\n const popupResult = PopupToIframeMessageSchema.safeParse(event.data)\n if (!popupResult.success) {\n // Say what actually happened. Falling through from here reaches the\n // parent-origin check below, which rejects this message as coming\n // from an \"unauthorized origin\" — it came from the right one and\n // failed the schema (#587). That was a rare diagnostic while any\n // payload shape produced SOME session; now that the projection is\n // required, a refused handover is the ordinary way a version-skewed\n // identity deployment fails, and it must not read as an origin bug.\n console.warn(\"[Proxy] Invalid setSecret payload:\", popupResult.error)\n this.endRefusedConnect()\n return\n }\n await this.handlePopupMessage(popupResult.data)\n return\n }\n\n // Validate origin - only accept messages from parent\n if (event.origin !== this.parentOrigin) {\n console.warn(\n \"[Proxy] Rejected message from unauthorized origin:\",\n event.origin,\n )\n return\n }\n\n let message: ParentToIframeMessage\n try {\n message = ParentToIframeMessageSchema.parse(event.data)\n } catch (error) {\n console.warn(\"[Proxy] Invalid parent message:\", error)\n return\n }\n\n try {\n await this.handleParentMessage(message, event)\n } catch (error) {\n console.error(\"[Proxy] Error handling parent message:\", error)\n this.sendErrorToParent(event, message.requestId, error, \"Unknown error\")\n }\n })\n }\n\n /**\n * Handle parent identification\n */\n private async handleParentIdentify(\n message: ParentIdentifyMessage,\n event: MessageEvent,\n ): Promise<void> {\n // Prevent parent from changing after first identification\n if (this.parentIdentifyReceived) {\n console.error(\"[Proxy] Parent already identified! Ignoring duplicate.\")\n return\n }\n this.parentIdentifyReceived = true\n\n // The browser-reported origin is checked before the frame reads or says\n // anything for it. The refusal reaches the parent as an `initError`.\n const siteCheck = await this.siteAllowList.check(event.origin)\n if (!siteCheck.allowed) {\n console.warn(\"[Proxy] Refused parent:\", siteCheck.message)\n throw new Error(siteCheck.message)\n }\n\n const parentPopupMode = message.popupMode\n const parentMetadata = message.metadata\n const parentButtonConfig = message.buttonConfig\n\n // Trust event.origin - this is browser-enforced and cannot be spoofed\n this.parentOrigin = event.origin\n this.parentIdentified = true\n // Store reference to parent window for later postMessage calls\n if (event.source) {\n this.parentWindow = event.source as WindowProxy\n }\n\n // Use parent's popup mode if provided\n if (parentPopupMode) {\n this.popupMode = parentPopupMode\n }\n\n // Store metadata from parent\n if (parentMetadata) {\n this.appMetadata = parentMetadata\n }\n\n // Store button config from parent\n if (parentButtonConfig) {\n this.buttonConfig = parentButtonConfig\n }\n\n // Before `loadAuthData`, whose restored-session check is the first read\n // this session makes.\n this.siteBeeApiUrl = message.beeApiUrl\n\n this.siteSubsidisedGatewayUrl = message.subsidisedGatewayUrl\n this.subsidisedGatewayUrl = message.subsidisedGatewayUrl\n this.dropGatewayForOwnCustomNode()\n\n // Load existing secret if available\n await this.loadAuthData()\n\n // Acknowledge receipt\n this.postMessage(event, {\n type: \"proxyReady\",\n authenticated: this.authenticated,\n parentOrigin: this.parentOrigin,\n // Which auth transport can reach this iframe (#613). Read here rather\n // than at construction because the parent is only known now, and the\n // client needs it before its first `connect()` — an await between the\n // user's click and `window.open` is what loses the gesture.\n storageShared: isStorageShared(),\n })\n\n // Send the initial ConnectionInfo snapshot. The client awaits this before\n // resolving `initialize()`, so `client.connectionInfo` is populated by then.\n this.emitConnectionInfoIfChanged()\n }\n\n /**\n * Handle messages from parent window\n */\n private async handleParentMessage(\n message: ParentToIframeMessage,\n event: MessageEvent,\n ): Promise<void> {\n switch (message.type) {\n case \"parentIdentify\":\n // Already handled above\n break\n\n case \"checkAuth\":\n this.handleCheckAuth(message, event)\n break\n\n case \"disconnect\":\n this.handleDisconnect(message, event)\n break\n\n case \"uploadData\":\n await this.handleUploadData(message, event)\n break\n\n case \"downloadData\":\n await this.handleDownloadData(message, event)\n break\n\n case \"deriveAppSecret\":\n await this.handleDeriveAppSecret(message, event)\n break\n\n case \"getChatKey\":\n await this.handleGetChatKey(message, event)\n break\n\n case \"setBeeApiUrl\":\n this.handleSetBeeApiUrl(message, event)\n break\n\n case \"stampChunk\":\n await this.handleStampChunk(message, event)\n break\n\n case \"uploadFile\":\n await this.handleUploadFile(message, event)\n break\n\n case \"downloadFile\":\n await this.handleDownloadFile(message, event)\n break\n\n case \"uploadFiles\":\n await this.handleUploadFiles(message, event)\n break\n\n case \"listFiles\":\n await this.handleListFiles(message, event)\n break\n\n case \"uploadChunk\":\n await this.handleUploadChunk(message, event)\n break\n\n case \"downloadChunk\":\n await this.handleDownloadChunk(message, event)\n break\n\n case \"isConnected\":\n await this.handleIsConnected(message, event)\n break\n\n case \"getNodeInfo\":\n await this.handleGetNodeInfo(message, event)\n break\n\n case \"gsocMine\":\n this.handleGsocMine(message, event)\n break\n\n case \"gsocSend\":\n await this.handleGsocSend(message, event)\n break\n case \"socUpload\":\n await this.handleSocUpload(message, event)\n break\n case \"socRawUpload\":\n await this.handleSocRawUpload(message, event)\n break\n case \"socDownload\":\n await this.handleSocDownload(message, event)\n break\n case \"socRawDownload\":\n await this.handleSocRawDownload(message, event)\n break\n case \"socGetOwner\":\n await this.handleSocGetOwner(message, event)\n break\n case \"epochFeedDownloadReference\":\n await this.handleEpochFeedDownloadReference(message, event)\n break\n case \"epochFeedUploadReference\":\n await this.handleEpochFeedUploadReference(message, event)\n break\n case \"feedGetOwner\":\n await this.handleFeedGetOwner(message, event)\n break\n case \"seqFeedGetOwner\":\n await this.handleSequentialFeedGetOwner(message, event)\n break\n case \"seqFeedDownloadPayload\":\n await this.handleSequentialFeedDownloadPayload(message, event)\n break\n case \"seqFeedDownloadRawPayload\":\n await this.handleSequentialFeedDownloadRawPayload(message, event)\n break\n case \"seqFeedDownloadReference\":\n await this.handleSequentialFeedDownloadReference(message, event)\n break\n case \"seqFeedUploadPayload\":\n await this.handleSequentialFeedUploadPayload(message, event)\n break\n case \"seqFeedUploadRawPayload\":\n await this.handleSequentialFeedUploadRawPayload(message, event)\n break\n case \"seqFeedUploadReference\":\n await this.handleSequentialFeedUploadReference(message, event)\n break\n\n case \"actUploadData\":\n await this.handleActUploadData(message, event)\n break\n\n case \"actDownloadData\":\n await this.handleActDownloadData(message, event)\n break\n\n case \"actAddGrantees\":\n await this.handleActAddGrantees(message, event)\n break\n\n case \"actRevokeGrantees\":\n await this.handleActRevokeGrantees(message, event)\n break\n\n case \"actGetGrantees\":\n await this.handleActGetGrantees(message, event)\n break\n\n case \"getPostageBatch\":\n await this.handleGetPostageBatch(message, event)\n break\n\n case \"createFeedManifest\":\n await this.handleCreateFeedManifest(message, event)\n break\n\n case \"connect\":\n this.handleConnect(message, event)\n break\n\n default:\n // TypeScript should ensure this is never reached\n const exhaustiveCheck: never = message\n console.warn(\"[Proxy] Unhandled message type:\", exhaustiveCheck)\n }\n }\n\n /**\n * Load authentication data from shared storage (ConnectedApp records).\n */\n private async loadAuthData(): Promise<void> {\n if (!this.parentOrigin) {\n this.authLoading = false\n return\n }\n\n await createAccountsStorageManager().prime()\n const sharedData = this.lookupAppSecretFromSharedStorage()\n\n if (sharedData) {\n const connection = this.findConnectionForParent()\n if (connection) {\n this.startRestoredSessionCheck({\n bee: this.accountBee,\n derivationKey: connection.account.derivationKey,\n accountId: connection.account.id.toHex(),\n appOrigin: this.parentOrigin,\n connectedAt: connection.app.lastConnectedAt,\n })\n }\n this.appSecret = sharedData.secret\n this.chatBinding = undefined\n this.authenticated = true\n this.authLoading = false\n this.showAuthButton()\n this.joinAccountBus()\n\n // Look up postage stamp from shared storage based on connected identity\n const stamp = this.lookupPostageStampForApp()\n this.setResolvedStamp(stamp)\n if (stamp) {\n await this.initializeStamper(stamp.depth)\n }\n return\n }\n\n // No shared storage to read: either nobody has connected, or this iframe is\n // partitioned. A session kept from a previous load answers that (#635) —\n // the same deadline the unpartitioned branch above enforces out of shared\n // storage, enforced here out of this partition's own.\n const session = await this.readPartitionSession()\n if (session) {\n await this.restorePartitionSession(session)\n return\n }\n\n this.authLoading = false\n this.showAuthButton()\n }\n\n /**\n * Bring a stored session back up, exactly as the popup handover does.\n *\n * Through the same `hydratePartitionAccount`, so there is one function that\n * builds a partitioned session rather than two that drift.\n *\n * The deadline cannot see a revoke made while the tab was closed, so the\n * session comes up for reads at once and checks the account's published\n * state before its first write (`startRestoredSessionCheck`), as a session\n * restored from shared storage does.\n */\n private async restorePartitionSession(\n session: PartitionSession,\n ): Promise<void> {\n const { data } = session\n // The handover's own record of when this connection began. A reload is not\n // a connection, so the restored view keeps it rather than stamping a fresh\n // one — see `hydratePartitionAccount`.\n const connectedAt = data.account.connectedApps.find(\n (app) => app.appUrl === this.parentOrigin,\n )?.lastConnectedAt\n\n // Before hydrating, which builds the write path and would otherwise claim\n // a lease before the check could hold it.\n this.startRestoredSessionCheck({\n bee: this.accountBee,\n derivationKey: data.account.derivationKey,\n accountId: data.account.id.toHex(),\n appOrigin: this.parentOrigin!,\n // Deliberately the raw value, `undefined` included: a missing instant is\n // not evidence that we connected after a disconnect, and the check reads\n // it as 0 for exactly that reason.\n connectedAt,\n })\n this.appSecret = data.secret\n this.chatBinding = this.acceptHandedOverChatBinding(data)\n this.authenticated = true\n this.storagePartitioned = true\n this.authLoading = false\n this.deviceId = getOrCreateDeviceId()\n\n await this.hydratePartitionAccount(\n data.account,\n data.secret,\n data.networkSettings,\n // A record with no entry for this origin has no connection to date; the\n // session is nonsense either way, and nothing downstream can be poisoned\n // by a clock invented for an entry that does not exist.\n connectedAt ?? Date.now(),\n )\n\n this.showAuthButton()\n this.emitConnectionInfoIfChanged()\n }\n\n /**\n * Ask the account's published state whether this restored session was ended\n * while no page of the site was open. Ended means what it means\n * everywhere else: the entry is gone, `revokedAt` (Remove), or a\n * `disconnectedAt` newer than the connection this session came from\n * (Disconnect, #667), with the rules in `sync/session-check.ts`.\n *\n * Reads start at once. Writes wait on the answer (`ensureCanWrite`), and an\n * answer that could not be read keeps them refused: an unreachable Swarm\n * says nothing about a revoke, so it neither ends the session nor lets it\n * write. The next write asks again.\n */\n private startRestoredSessionCheck(\n input: Parameters<typeof checkRestoredSession>[0],\n ): void {\n const check: NonNullable<typeof this.restoredSessionCheck> = {\n input,\n settled: undefined,\n verdict: checkRestoredSession(input),\n }\n this.restoredSessionCheck = check\n void check.verdict.then((verdict) => {\n check.settled = verdict\n if (this.restoredSessionCheck !== check) return\n if (verdict === \"ended\") {\n console.info(\n \"[Proxy] The restored session was ended while this page was closed\",\n )\n this.clearAuthData()\n } else if (verdict === \"unconfirmed\") {\n console.warn(\n \"[Proxy] Could not confirm the restored session, writes stay refused\",\n )\n }\n this.emitConnectionInfoIfChanged()\n })\n }\n\n /** Waits for the restored session's check, and refuses unless it allowed. */\n private async awaitRestoredSessionAllowed(): Promise<void> {\n let check = this.restoredSessionCheck\n if (!check) return\n if (check.settled === \"unconfirmed\") {\n // At the person's node as it is now, which their settings may have\n // changed since. Never the site's.\n this.startRestoredSessionCheck({ ...check.input, bee: this.accountBee })\n check = this.restoredSessionCheck!\n }\n const verdict = await check.verdict\n if (verdict === \"allowed\") return\n if (verdict === \"ended\") throw sessionEndedError()\n throw new SwarmIdError(\n \"upload-unavailable\",\n \"Could not confirm with Swarm that this session is still allowed to write, so writes are refused until it can be confirmed.\",\n { reason: \"session-unconfirmed\" },\n )\n }\n\n /**\n * The gate every write handler passes. The session check comes first, and\n * the refusal checks run after it because the session can change while it\n * waits.\n */\n private async ensureCanWrite(): Promise<void> {\n // A check that already ended the session also cleared its auth, and says\n // why better than \"not authenticated\" does.\n if (this.restoredSessionCheck?.settled === \"ended\") {\n throw sessionEndedError()\n }\n this.ensureCanUpload()\n await this.awaitRestoredSessionAllowed()\n this.ensureCanUpload()\n }\n\n /**\n * Look up the postage stamp for the currently connected app's identity\n * by reading from shared localStorage stores.\n */\n private lookupPostageStampForApp(): PostageStamp | undefined {\n if (!this.parentOrigin) {\n return undefined\n }\n\n try {\n const connection = this.findConnectionForParent()\n if (!connection) {\n return undefined\n }\n const { account, app } = connection\n\n // Resolve the stamp this app should use: its per-app override if set,\n // otherwise the account default — skipping a pointer whose stamp is\n // missing so a stale pointer falls through instead of failing.\n return resolveStampForApp(app, account, account.postageStamps)\n } catch (error) {\n console.error(\"[Proxy] Error looking up postage stamp:\", error)\n return undefined\n }\n }\n\n /**\n * Look up the account for the currently connected app's identity\n * by reading from shared localStorage stores.\n *\n * @returns Account info with owner address and encryption key, or undefined if not found\n */\n private async lookupAccountForApp(): Promise<\n | {\n owner: EthAddress\n encryptionKey: Uint8Array\n accountId: string\n partitionCount: number\n }\n | undefined\n > {\n if (!this.parentOrigin) {\n return undefined\n }\n\n try {\n const connection = this.findConnectionForParent()\n if (!connection) {\n return undefined\n }\n const { account } = connection\n\n // Derive swarm encryption key from stored derivation key\n const swarmEncryptionKey = await deriveSwarmEncryptionKey(\n account.derivationKey,\n )\n\n return {\n owner: account.id,\n encryptionKey: hexToUint8Array(swarmEncryptionKey),\n accountId: account.id.toHex(),\n partitionCount: account.partitionCount ?? 1,\n }\n } catch (error) {\n console.error(\"[Proxy] Error looking up account:\", error)\n return undefined\n }\n }\n\n /**\n * The partition rival set for `accountId`: every device heard live on the\n * account bus, plus the recently signed-in devices of the stored registry\n * (read fresh, so the intent round reflects a device that announced after\n * the coordinator was built).\n *\n * The bus half is the liveness signal — a peer beating in the room is alive\n * by construction, whether or not its roster append ever landed. The\n * registry half is the bootstrap for contexts with no bus (no signaling\n * server configured) and for a device that just signed in and has not\n * beaten yet; it is bounded to `KNOWN_DEVICE_MAX_AGE_MS` because removed\n * (tombstoned) devices won't write, and long-dead ghosts (the device list is\n * append-only) would each add an absent intent read to every acquire — on a\n * flaky gateway that can push a single acquire past its timeout, so a live\n * device can't claim even a FREE partition. A genuinely-live holder this\n * prune drops is still caught by the deviceId-independent occupancy beacon.\n *\n * On any storage error the bus half alone is returned — the lease then falls\n * back to the guard+TTL acquire path against whoever is live.\n */\n private knownDeviceIdsForAccount(accountId: string): string[] {\n const now = Date.now()\n const live =\n this.busBoundAccountId === accountId\n ? this.presence.liveDeviceIds(now)\n : []\n try {\n const account =\n this.partitionAccount?.id.toHex() === accountId\n ? this.partitionAccount\n : createAccountsStorageManager()\n .load()\n .find((a) => a.id.toHex() === accountId)\n if (!account || isSignedOutAccount(account)) return live\n return [\n ...new Set([\n ...activeDeviceIds(account.devices, now, KNOWN_DEVICE_MAX_AGE_MS),\n ...live,\n ]),\n ]\n } catch {\n return live\n }\n }\n\n private lastDeviceRegistryRefreshAt = 0\n\n /**\n * Pull the latest account snapshot from the shared feed and union its\n * `metadata.devices` into the locally-stored account, so `knownDeviceIds`\n * reflects a peer that signed in AFTER this device created the account (else\n * the claimer never reads the peer's beacon and they dual-acquire). Throttled\n * (the slot-wait loop re-acquires) and best-effort — on any failure the\n * current registry is used as-is.\n */\n private async refreshDeviceRegistryFromSwarm(\n accountId: string,\n encryptionKey: Uint8Array,\n ): Promise<void> {\n const nowMs = Date.now()\n if (\n nowMs - this.lastDeviceRegistryRefreshAt <\n DEVICE_REGISTRY_REFRESH_THROTTLE_MS\n ) {\n return\n }\n this.lastDeviceRegistryRefreshAt = nowMs\n try {\n // A partitioned session cannot see shared storage — the hydrated account\n // view is both the source and the destination there. Without this branch\n // the load below returns nothing and the refresh silently no-ops, leaving\n // a Safari writer's rival set frozen at whatever the connect popup handed\n // over: the intent round never sees a peer that signs in later, and the\n // idle-yield (gated on a known rival) never fires. Mirrors the same\n // branch in `knownDeviceIdsForAccount`.\n const partitioned = this.partitionAccount?.id.toHex() === accountId\n const manager = partitioned ? undefined : createAccountsStorageManager()\n await manager?.prime()\n const accounts = manager?.load() ?? []\n const account = partitioned\n ? this.partitionAccount\n : accounts.find((a) => a.id.toHex() === accountId)\n if (!account || isSignedOutAccount(account)) return\n\n // Feed owner = backup signer (derived from the swarm encryption key).\n // Phase 3a: discovery is the append-only device roster, not a shared doc.\n const backupKey = new PrivateKey(\n await deriveSecret(uint8ArrayToHex(encryptionKey), BACKUP_KEY_LABEL),\n )\n const owner = backupKey.publicKey().address()\n const rosterDevices = await readRoster({\n bee: this.accountBee,\n accountId: account.id.toHex(),\n owner,\n })\n if (rosterDevices.length === 0) return\n\n const mergedDevices = mergeDevices(\n mergeDevicesList(account.devices, rosterDevices),\n this.requireDeviceId(),\n this.announcedDeviceName(),\n )\n // Persist when anything the rival set reads actually moved — a peer\n // appearing, a refreshed sign-in, a tombstone — not merely when the list\n // got longer, which discarded the timestamp-only merges `activeDeviceIds`\n // prunes on (#586). A poll no longer stamps our own entry (#652), so a\n // quiet round compares equal and the cross-tab storage-event churn the\n // length check was really for does not come back.\n if (\n deviceRegistryChanged(\n account.devices,\n mergedDevices,\n this.requireDeviceId(),\n )\n ) {\n if (partitioned) {\n // In-memory only, like the rest of the hydrated view: the stored\n // schema requires a vault. Not worth persisting either — the roster\n // is re-read from Swarm on the next load anyway.\n this.partitionAccount = { ...account, devices: mergedDevices }\n } else {\n // Applied to what is stored when the write's turn comes, not to the\n // copy read before the roster round trip.\n // A failed write is logged by the queue, and a hidden frame has no\n // one to show it to.\n void manager!\n .update((stored) =>\n stored.map((a) =>\n a.id.toHex() === accountId\n ? { ...a, devices: mergedDevices }\n : a,\n ),\n )\n .catch(() => undefined)\n }\n }\n } catch (error) {\n console.warn(\n \"[Proxy] Device-registry refresh failed (using current registry):\",\n error,\n )\n }\n }\n\n /**\n * Assemble the current account-state snapshot for the connected app's\n * account. Needs no feed signing key, so it stays separate from\n * `buildAccountStateSnapshotForPublish` below — reusing that one would cost\n * two key derivations to send a bus message.\n *\n * `requireDefaultStamp` is the FEED path's precondition, not the snapshot's:\n * that write is paid for by the default stamp, while a bus message costs\n * nothing to send and `metadata.defaultPostageStampBatchID` is optional on\n * the wire. An account with no drives is a supported connected state, and\n * gating the delta on a stamp is how such an account's revokes never reach\n * its partitioned sessions at all.\n */\n private buildAccountStateSnapshot(options: {\n requireDefaultStamp: boolean\n includeEndedConnection: boolean\n }): AccountStateSnapshot | undefined {\n if (!this.parentOrigin) return undefined\n const connection = this.findConnectionForParent({\n includeEnded: options.includeEndedConnection,\n })\n if (!connection) return undefined\n const { account } = connection\n\n if (options.requireDefaultStamp && !account.defaultPostageStampBatchID) {\n return undefined\n }\n\n return accountToStateSnapshot(account, account.id.toHex(), Date.now())\n }\n\n /**\n * Announce this session's account state to the account's other live contexts\n * (docs/Account-Bus.md, `account-delta`). Unlike the feed write below it\n * needs no partition and no lock — it is a message, not a write — so a\n * read-only session still relays what it can see to peers that cannot.\n *\n * `ofEndedConnection` reports a session that has just ENDED (the revoke a\n * storage event revealed), where the entry the snapshot is resolved from is\n * by definition no longer a valid connection.\n */\n private publishAccountDelta(options?: { ofEndedConnection: boolean }): void {\n const snapshot = this.buildAccountStateSnapshot({\n requireDefaultStamp: false,\n includeEndedConnection: options?.ofEndedConnection === true,\n })\n if (!snapshot) return\n // Same guard the feed write below carries, for the same reason and one\n // channel earlier: a debounced publish can fire mid account switch, after\n // the new account resolved but before `ensureAccountBusTransports` has\n // detached the old room — and these transports encrypt with the key they\n // were built with. Publishing then hands account B's snapshot, stamp signer\n // keys included, to account A's peers.\n if (\n this.busBoundAccountId !== undefined &&\n snapshot.accountId !== this.busBoundAccountId\n ) {\n console.warn(\n `[Proxy] Account changed since the bus room was joined (snapshot ${snapshot.accountId} vs bus room ${this.busBoundAccountId}); skipping delta.`,\n )\n return\n }\n this.bus.publish({\n type: \"account-delta\",\n snapshot: accountDeltaFromSnapshot(snapshot),\n })\n }\n\n /**\n * Assemble the current account-state snapshot for the connected app's account\n * from shared localStorage (the same shape `sync-account` builds from its DI\n * stores). Returns the snapshot plus the feed signing key + owner. Undefined\n * when storage is partitioned or the account/stamp can't be resolved.\n */\n private async buildAccountStateSnapshotForPublish(): Promise<\n | {\n snapshot: AccountStateSnapshot\n encryptionKey: string\n accountKey: PrivateKey\n owner: EthAddress\n }\n | undefined\n > {\n if (!this.parentOrigin) {\n return undefined\n }\n try {\n const connection = this.findConnectionForParent()\n if (!connection) return undefined\n const snapshot = this.buildAccountStateSnapshot({\n requireDefaultStamp: true,\n includeEndedConnection: false,\n })\n if (!snapshot) return undefined\n\n const encryptionKey = await deriveSwarmEncryptionKey(\n connection.account.derivationKey,\n )\n const accountKey = new PrivateKey(\n await deriveSecret(encryptionKey, BACKUP_KEY_LABEL),\n )\n const owner = accountKey.publicKey().address()\n\n return { snapshot, encryptionKey, accountKey, owner }\n } catch (error) {\n console.error(\"[Proxy] Failed to assemble account-state snapshot:\", error)\n return undefined\n }\n }\n\n /**\n * Schedule a debounced account-state publish. Safe to call from inside the\n * coordinator's lease callbacks: it only arms a timer, so the actual publish\n * runs later, outside any held write lock.\n *\n * A coordinator is NOT a precondition — only the feed half of the publish\n * needs one, and `runAccountStatePublish` checks for it there, after the bus\n * delta has gone out.\n */\n private schedulePublish(reason: \"acquired\" | \"change\"): void {\n // \"change\" dominates: if a real delta is pending in this debounce window,\n // don't let a coalesced \"acquired\" downgrade it to the announce-gated path.\n if (this.publishReason !== \"change\") this.publishReason = reason\n if (this.publishTimer !== undefined) clearTimeout(this.publishTimer)\n this.publishTimer = setTimeout(() => {\n this.publishTimer = undefined\n const effectiveReason = this.publishReason ?? \"acquired\"\n this.publishReason = undefined\n void this.runAccountStatePublish(effectiveReason)\n }, PUBLISH_DEBOUNCE_MS)\n }\n\n /**\n * Publish the account snapshot: to the account's live peers over the bus,\n * and to the shared feed via the coordinator (same write lock + held\n * partition). The feed write needs a partition; the bus message does not, so\n * it goes out first and a read-only session still relays to peers that\n * cannot see storage at all. Single-device accounts' feeds are published by\n * the SwarmID UI. Re-arms if a publish is already in flight so the latest\n * state still lands.\n */\n private async runAccountStatePublish(\n reason: \"acquired\" | \"change\",\n ): Promise<void> {\n // The in-flight guard runs before the delta publish below: a message needs\n // no lease, but a re-entrant call while a publish is in flight re-arms the\n // timer instead of sending the delta a second time for one change.\n if (this.publishInFlight) {\n this.schedulePublish(reason)\n return\n }\n if (reason === \"change\") this.publishAccountDelta()\n const coordinator = this.coordinator\n if (!coordinator || coordinator.currentPartition === undefined) return\n this.publishInFlight = true\n try {\n const assembled = await this.buildAccountStateSnapshotForPublish()\n if (!assembled) return\n // Snapshot assembly awaits (crypto + storage reads); a disconnect /\n // sign-out / re-auth may have torn down or replaced the coordinator in\n // the meantime. Bail rather than write through a stale instance. (The\n // coordinator also self-guards against re-acquire when disposed; this\n // just avoids the needless lock round-trip.)\n if (this.coordinator !== coordinator) return\n const { snapshot, encryptionKey, accountKey, owner } = assembled\n\n // The snapshot is assembled from the most recent connection for this\n // origin; if the connected account changed since the coordinator was\n // built, publishing would pair account B's feed (key derived from the\n // snapshot) with account A's batch and partition lease. Skip — the\n // re-initialised coordinator for the new account publishes its own state.\n if (snapshot.accountId !== coordinator.accountId) {\n console.warn(\n `[Proxy] Account changed during publish scheduling (snapshot ${snapshot.accountId} vs coordinator ${coordinator.accountId}); skipping publish.`,\n )\n return\n }\n\n // Announce-once: a publish triggered purely by acquiring the lease (e.g.\n // every page reload) has nothing new once this device is already in the\n // roster. Skip it. A \"change\" publish carries a real delta and always\n // re-writes this device's own state feed (no shared contention).\n const deviceId = this.requireDeviceId()\n if (reason === \"acquired\") {\n const rosterDevices = await readRoster({\n bee: this.accountBee,\n accountId: snapshot.accountId,\n owner,\n }).catch(() => [])\n const rosterEntry = rosterDevices.find((d) => d.deviceId === deviceId)\n if (rosterEntry?.removedAt) {\n // A tombstoned self is NOT \"not yet announced\". Reading it that way\n // republished us without the tombstone, which resurrected the device\n // on every peer — the roster half of #611, and the half that\n // propagates. A removal or an expiry is news to obey, not a gap to\n // fill: stand down, and let a genuine sign-in put us back\n // (`reactivateThisDevice`).\n //\n // Unless that sign-in has already happened, in which case this is the\n // publish that carries it: the roster is the only way a reactivation\n // reaches peers, so skipping here — on either guard — would silence\n // the very seam this defers to and the removal would never lift\n // anywhere. Our own view says which case it is, on the same clock\n // `mergeDevicesList` ranks by: a handover clears `removedAt` AND\n // stamps a `lastSignedInAt` that outranks the removal. A poll or a\n // reload does neither.\n const localSelf = snapshot.metadata.devices.find(\n (d) => d.deviceId === deviceId,\n )\n const signedInSinceRemoval =\n !!localSelf &&\n !localSelf.removedAt &&\n (localSelf.lastSignedInAt ?? 0) > rosterEntry.removedAt\n if (!signedInSinceRemoval) {\n console.info(\n `[Proxy] This device is tombstoned in the roster for ${snapshot.accountId}; not re-announcing.`,\n )\n return\n }\n console.info(\n `[Proxy] This device signed in since its removal from ${snapshot.accountId}; announcing the reactivation.`,\n )\n } else if (rosterEntry) {\n // Announce-once proper: the roster already says what we would say.\n console.info(\n `[Proxy] Device already in roster for ${snapshot.accountId}; skipping announce publish.`,\n )\n return\n }\n }\n\n const thisDevice = snapshot.metadata.devices.find(\n (d) => d.deviceId === deviceId,\n ) ?? {\n deviceId,\n name: this.announcedDeviceName(),\n createdAt: Date.now(),\n lastSignedInAt: Date.now(),\n }\n // Per-field scalar clocks (incl. the stable-`createdAt` fallback for a\n // never-edited field) are derived by `accountStateToDeviceView`.\n const view = accountStateToDeviceView(snapshot)\n\n await coordinator.withWrite((target) =>\n publishDeviceState({\n bee: this.accountBee,\n accountId: snapshot.accountId,\n device: thisDevice,\n accountKey,\n owner,\n encryptionKey,\n view,\n target,\n }),\n )\n // `ui/tests/three-devices.test.ts` counts this line to tell a busy\n // holder from an idle one (#707); reword it there too.\n console.info(\n `[Proxy] Published device state for ${snapshot.accountId} (partition ${coordinator.currentPartition})`,\n )\n } catch (error) {\n console.warn(\"[Proxy] Account-state publish failed:\", error)\n } finally {\n this.publishInFlight = false\n }\n }\n\n /**\n * Lift this device's own `removedAt` on a genuine sign-in — #337's rule, at\n * the one seam that can tell a user unlocking their account from a timer.\n *\n * `mergeDevices` used to do this on every refresh, which meant no removal and\n * no expiry ever stuck (#611). Here it happens once, on a fresh `setSecret`\n * handover, and the next publish re-announces us with the tombstone gone —\n * `ensureInRoster` appends when the removed-state differs, so the roster\n * carries the reactivation to peers without any extra write of our own.\n *\n * Clearing `removedAt` is only half of it: `mergeDevicesList` ranks devices\n * on `max(removedAt, lastSignedInAt)`, so a lift that left the old sign-in\n * stamp in place would lose to the very tombstone it is undoing and the next\n * fold would put it straight back. A handover IS a sign-in — the user\n * clicked connect and unlocked in the popup — so it stamps one.\n *\n * Partitioned only: an unpartitioned proxy has no business editing the\n * shared account document behind the identity UI's back. The identity UI has\n * no reactivation seam of its own, and needs none — nothing writes a device\n * tombstone today, and a device that stops beating on the bus is ignored at\n * read rather than tombstoned (#655). If manual removal is ever built, its\n * seam has to do what this one does, both halves included.\n */\n private reactivateThisDevice(): void {\n const account = this.partitionAccount\n const deviceId = this.deviceId\n if (!account || !deviceId) return\n if (!account.devices.some((d) => d.deviceId === deviceId && d.removedAt)) {\n return\n }\n const now = Date.now()\n this.partitionAccount = {\n ...account,\n devices: account.devices.map((d) =>\n d.deviceId === deviceId\n ? { ...d, removedAt: undefined, lastSignedInAt: now }\n : d,\n ),\n }\n }\n\n /**\n * The name this context announces itself under in the device registry.\n *\n * A PARTITIONED proxy keeps its own `swarm-id-device-id` in the partition, so\n * it is a per-dApp device and says which dApp (#570). An UNPARTITIONED one\n * reads the trusted domain's first-party store and therefore shares the\n * device id with the identity UI — it is the same device, and naming it after\n * whichever dApp happened to embed it would be wrong.\n */\n private announcedDeviceName(): string {\n return this.storagePartitioned && this.parentOrigin\n ? partitionDeviceName(this.parentOrigin)\n : detectDeviceName()\n }\n\n /**\n * Check if a connection is still valid based on connectedUntil timestamp\n */\n private isConnectionValid(connectedApp: ConnectedApp): boolean {\n if (!connectedApp.connectedUntil) return false\n return connectedApp.connectedUntil > Date.now()\n }\n\n /**\n * Find the account + connected-app pair for the current parent origin, reading\n * the nested account documents. Resolves ambiguity (the same app connected\n * under multiple accounts) by sorting valid entries by `lastConnectedAt`\n * descending and returning the most recent.\n */\n private findConnectionForParent(options?: {\n /**\n * Accept an entry whose session has ENDED — a lapsed `connectedUntil`, a\n * revoke tombstone, a hand disconnect. For CONSUMING authoritative news\n * about a session (`applyAccountDelta`) or REPORTING its end (the final\n * `account-delta`), never for serving work with it: such an entry must\n * still fail the reconcile that follows, or the session would never end.\n */\n includeEnded: boolean\n }): { account: SignedInAccount; app: ConnectedApp } | undefined {\n if (!this.parentOrigin) {\n return undefined\n }\n const usable = (app: ConnectedApp): boolean =>\n this.isConnectionValid(app) || options?.includeEnded === true\n // Partitioned session: shared storage is invisible; the popup-handed\n // account view is the (only) source.\n if (this.partitionAccount) {\n const app = this.partitionAccount.connectedApps.find(\n (candidate) =>\n candidate.appUrl === this.parentOrigin && usable(candidate),\n )\n return app ? { account: this.partitionAccount, app } : undefined\n }\n const accounts = createAccountsStorageManager().load()\n const matches: { account: SignedInAccount; app: ConnectedApp }[] = []\n for (const account of accounts) {\n // A signed-out account keeps no connected apps (and no derivationKey to\n // serve them with) — its record is just the vault remnant.\n if (isSignedOutAccount(account)) continue\n for (const app of account.connectedApps) {\n if (app.appUrl === this.parentOrigin && usable(app)) {\n matches.push({ account, app })\n }\n }\n }\n // Valid first, THEN most-recent: with `includeEnded` every historical entry\n // for this origin is eligible, across every account, and `lastConnectedAt`\n // alone would let a long-dead session outrank a live one from another\n // account purely by having connected later.\n return matches.sort(\n (a, b) =>\n Number(this.isConnectionValid(b.app)) -\n Number(this.isConnectionValid(a.app)) ||\n b.app.lastConnectedAt - a.app.lastConnectedAt,\n )[0]\n }\n\n /**\n * Look up the app secret from shared storage for the current parent origin.\n * Returns the secret if a valid connection is found.\n */\n private lookupAppSecretFromSharedStorage(): { secret: string } | undefined {\n if (!this.parentOrigin) {\n return undefined\n }\n\n try {\n const connection = this.findConnectionForParent()\n if (!connection?.app.appSecret) {\n return undefined\n }\n\n return { secret: connection.app.appSecret }\n } catch (error) {\n console.error(\n \"[Proxy] Error looking up app secret from shared storage:\",\n error,\n )\n return undefined\n }\n }\n\n /**\n * Clear authentication data\n */\n private clearAuthData(): void {\n if (!this.parentOrigin) {\n return\n }\n\n // The session ends here whichever way it ended — disconnect, revoke, or an\n // expiry the reconcile noticed — so the stored copy goes with it, or the\n // next load would bring it back (#635).\n this.clearPartitionSession()\n\n // Clear stamper state from localStorage\n const stamperKey = `swarm-stamper-${this.parentOrigin}-${this.postageBatchId}`\n localStorage.removeItem(stamperKey)\n\n // Invalidate matching connected-app entries in the nested account documents\n // so a refresh does not reconnect: `connectedUntil: undefined` is what\n // `isConnectionValid` gates on, and `disconnectedAt` is how the end travels\n // — the marker membership merges on (#681). This used to write\n // `lastConnectedAt: 0` instead, a sentinel nothing ever read and a claim\n // (\"never connected\") that was not true; under the membership clock a zero\n // is simply the value that loses every merge, so the entry's old timestamp\n // came back on the next fold and a dApp-initiated Disconnect never reached\n // a peer the way the identity UI's does.\n const parentOrigin = this.parentOrigin\n const disconnectWrite = createAccountsStorageManager().update(\n (accounts) => {\n let changed = false\n const updated = accounts.map((account) => {\n if (isSignedOutAccount(account)) return account\n const connectedApps = account.connectedApps.map((app) => {\n if (app.appUrl === parentOrigin) {\n changed = true\n return {\n ...app,\n connectedUntil: undefined,\n appSecret: undefined,\n disconnectedAt: Date.now(),\n }\n }\n return app\n })\n return { ...account, connectedApps }\n })\n return changed ? updated : undefined\n },\n )\n // Logged by the queue. A hidden frame has no one to show it to.\n disconnectWrite.catch(() => undefined)\n\n // Reset auth state\n this.authenticated = false\n this.authLoading = false\n this.appSecret = undefined\n this.chatBinding = undefined\n // Before dropping `deviceId` — the release announcement is sent as us.\n this.setResolvedStamp(undefined)\n this.deviceId = undefined\n if (this.publishTimer !== undefined) {\n clearTimeout(this.publishTimer)\n this.publishTimer = undefined\n }\n this.pendingLaneUpdates.clear()\n this.storagePartitioned = false\n this.partitionAccount = undefined\n this.detachBusTransports()\n // Retire any join still deriving its key, or it would re-attach behind us.\n this.busJoinGeneration += 1\n this.pendingChallenge = undefined\n\n this.emitConnectionInfoIfChanged()\n\n // Show login button\n this.showAuthButton()\n }\n\n /**\n * Send error message to parent\n */\n /**\n * Answer a request with what went wrong, as a dApp can branch on it (#761):\n * the caught value is classified by `toWireError`; `fallback` is the\n * message for a throw that was not an `Error`.\n */\n private sendErrorToParent(\n event: MessageEvent,\n requestId: string | undefined,\n error: unknown,\n fallback: string,\n ): void {\n if (requestId) {\n this.postMessage(event, {\n type: \"error\",\n requestId,\n ...toWireError(error, fallback),\n })\n }\n }\n\n private ensureCanUpload(): void {\n if (!this.authenticated || !this.appSecret) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"Not authenticated. Please login first.\",\n )\n }\n // Allow uploads if subsidised mode is active (gateway handles stamping)\n if (this.isSubsidisedModeActive()) {\n return\n }\n // Refuse here, before the write coordinator: its first stamped write is\n // the lease claim, and a refused claim reads as partition contention (#745).\n if (this.stampRefusal === \"stamp-expired\") {\n throw new SwarmIdError(\n \"upload-unavailable\",\n \"The account's drive has expired. Renew it or add another in Swarm ID before uploading.\",\n { reason: \"stamp-expired\" },\n )\n }\n if (this.stampRefusal === \"stamp-not-usable\") {\n throw new SwarmIdError(\n \"upload-unavailable\",\n \"The account's drive is not usable: its record says the node does not have the batch, or cannot use it. Open the drive in Swarm ID to refresh it, or add another, before uploading.\",\n { reason: \"stamp-not-usable\" },\n )\n }\n // NB: the multi-device \"all partitions held\" case is NOT checked here.\n // It's deferred to the coordinator's `withWrite`, which runs a fresh\n // acquisition attempt (with slot-wait) under the write lock and then throws\n // if no partition is held — so a read-only upload waits for a slot to free\n // (turn-taking) instead of failing instantly.\n }\n\n /**\n * Whether uploads can be stamped with the account's own postage batch. Both\n * what the dApp is told (`buildConnectionInfo`'s `uploadMode`) and where the\n * bytes go (`isSubsidisedModeActive`) are decided by this one rule.\n */\n private hasUsableUserStamp(): boolean {\n // No stamp resolved. Partitioning is not a cause: the connect popup hands\n // the stamps over, so a partitioned session resolves its own like any other.\n if (!this.postageBatchId || !this.signerKey) return false\n // The drive has expired, or its record says the node cannot use it.\n if (this.stampRefusal) return false\n // The stamp resolved but no stamper was built from it, a state the proxy\n // really reaches: `initializeStamper` logs and returns rather than\n // throwing, and the stamp is set before an init's first await. The\n // stamper and its coordinator are assigned together, so no stamper also\n // means no coordinator, and a user-stamp write would throw \"Stamper not\n // initialized\".\n if (!this.stamper) return false\n return true\n }\n\n /**\n * Whether uploads go to the subsidised gateway: one is configured, and there\n * is no usable user stamp to write with instead.\n */\n private isSubsidisedModeActive(): boolean {\n if (!this.subsidisedGatewayUrl) return false\n return !this.hasUsableUserStamp()\n }\n\n /**\n * Send message to parent\n */\n private sendToParent(message: IframeToParentMessage): void {\n if (!this.parentOrigin || !this.parentWindow) {\n console.warn(\n \"[Proxy] Cannot send message to parent - no parent window reference\",\n )\n return\n }\n\n this.parentWindow.postMessage(message, this.parentOrigin)\n }\n\n /**\n * Send message to the event source (parent window that sent the request)\n */\n private postMessage(\n event: MessageEvent,\n message: IframeToParentMessage,\n ): void {\n if (!event.source) {\n console.warn(\"[Proxy] Cannot send message - no event source\")\n return\n }\n ;(event.source as WindowProxy).postMessage(message, {\n targetOrigin: event.origin,\n })\n }\n\n // ============================================================================\n // Message Handlers\n // ============================================================================\n\n private handleCheckAuth(\n message: { type: \"checkAuth\"; requestId: string },\n event: MessageEvent,\n ): void {\n this.postMessage(event, {\n type: \"authStatusResponse\",\n requestId: message.requestId,\n authenticated: this.authenticated,\n origin: this.authenticated ? this.parentOrigin : undefined,\n // The site's own address only. The person's node is theirs, and a site\n // has no need to learn it.\n beeApiUrl: this.siteBeeApiUrl,\n })\n }\n\n /**\n * The two public keys the identity carries, memoised on the derivation key\n * they come from: `buildConnectionInfo` runs from eight sites, and the key\n * changes only when the account does. Public halves only, never a secret.\n */\n private identityKeys?: {\n derivationKey: string\n sharingPublicKey: string\n postageSignerAddress: string\n }\n\n private identityKeysFor(derivationKey: string) {\n if (this.identityKeys?.derivationKey !== derivationKey) {\n this.identityKeys = {\n derivationKey,\n sharingPublicKey: deriveSharingKey(derivationKey).publicKey,\n // The address an app buys a batch for (#815): the signer's, and the\n // signer is one derivation away.\n postageSignerAddress: new PrivateKey(\n derivePostageSignerKeySync(derivationKey),\n )\n .publicKey()\n .address()\n .toHex(),\n }\n }\n return this.identityKeys\n }\n\n /**\n * Build the current ConnectionInfo snapshot from in-memory auth state and\n * shared localStorage. Pure — does not send anything.\n */\n private buildConnectionInfo(): ConnectionInfo {\n let identity: ConnectionInfo[\"identity\"] = undefined\n\n if (this.authenticated && this.parentOrigin) {\n try {\n // One lookup for both modes: a partitioned session resolves this out\n // of the handed-over account view, an unpartitioned one out of shared\n // storage, and `findConnectionForParent` already knows the difference.\n const connection = this.findConnectionForParent()\n if (connection) {\n // The account IS the app-facing identity (single-level model).\n const { account } = connection\n const { sharingPublicKey, postageSignerAddress } =\n this.identityKeysFor(account.derivationKey)\n identity = {\n id: account.id.toHex(),\n name: account.name,\n address: account.id.toHex(),\n publicKey: account.publicKey,\n sharingPublicKey,\n postageSignerAddress,\n avatar: generatedAvatar(account.id.toHex()),\n }\n }\n } catch (error) {\n console.error(\"[Proxy] Error looking up identity:\", error)\n }\n }\n\n let appKey: ConnectionInfo[\"appKey\"] = undefined\n if (this.authenticated && this.appSecret) {\n const privKeyBytes = hexToUint8Array(this.appSecret)\n const { x, y } = publicKeyFromPrivate(privKeyBytes)\n const compressed = compressPublicKey(x, y)\n appKey = {\n address: new PrivateKey(this.appSecret).publicKey().address().toHex(),\n publicKey: uint8ArrayToHex(compressed),\n }\n }\n\n // Upload mode is only meaningful when authenticated — `ensureCanUpload`\n // throws on missing auth, so reporting `canUpload=true` here without an\n // app secret would mislead the dApp into attempting uploads that always\n // fail. Subsidised mode is the fallback when no user stamp is usable.\n let uploadMode: \"user-stamp\" | \"subsidised\" | \"unavailable\" = \"unavailable\"\n if (\n this.authenticated &&\n this.appSecret &&\n this.restoredSessionCheck?.settled !== \"unconfirmed\"\n ) {\n if (this.hasUsableUserStamp()) {\n uploadMode = \"user-stamp\"\n } else if (this.subsidisedGatewayUrl) {\n uploadMode = \"subsidised\"\n }\n }\n\n return {\n canUpload: uploadMode !== \"unavailable\",\n storagePartitioned: this.storagePartitioned || undefined,\n uploadMode,\n uploadUnavailableReason:\n uploadMode === \"unavailable\"\n ? this.uploadUnavailableReason()\n : undefined,\n identity,\n appKey,\n partition: this.coordinator?.currentPartition,\n chatBinding: this.authenticated ? this.chatBinding : undefined,\n beeApiUrl: this.siteBeeApiUrl,\n }\n }\n\n /**\n * Which of the two ways to be upload-less this session hit. Ordered by how\n * early the path gives up, so the answer names the first thing that stopped\n * it rather than the last symptom.\n *\n * Only meaningful once authenticated — an unconnected session has no reason\n * to report beyond not being connected.\n */\n private uploadUnavailableReason(): UploadUnavailableReason | undefined {\n if (!this.authenticated || !this.appSecret) return undefined\n if (this.restoredSessionCheck?.settled === \"unconfirmed\") {\n return \"session-unconfirmed\"\n }\n if (!this.postageBatchId || !this.signerKey) return \"no-stamp\"\n if (this.stampRefusal) return this.stampRefusal\n // The stamp resolved and the write path still did not build:\n // `initializeStamper` logs and returns rather than throwing, so this is the\n // only place that difference survives to the dApp.\n if (!this.stamper) return \"stamper-failed\"\n return undefined\n }\n\n /**\n * Recompute ConnectionInfo and send `connectionInfoChanged` to the parent\n * if it actually differs from the last value we sent. Used both on auth\n * transitions and on storage events that affect derived fields.\n */\n private emitConnectionInfoIfChanged(): void {\n if (!this.parentOrigin) {\n return\n }\n const info = this.buildConnectionInfo()\n if (connectionInfoEqual(this.lastConnectionInfo, info)) {\n return\n }\n this.lastConnectionInfo = info\n this.sendToParent({\n type: \"connectionInfoChanged\",\n canUpload: info.canUpload,\n storagePartitioned: info.storagePartitioned,\n uploadMode: info.uploadMode,\n uploadUnavailableReason: info.uploadUnavailableReason,\n identity: info.identity,\n appKey: info.appKey,\n partition: info.partition,\n chatBinding: info.chatBinding,\n beeApiUrl: info.beeApiUrl,\n })\n }\n\n private async handleIsConnected(\n message: IsConnectedMessage,\n event: MessageEvent,\n ): Promise<void> {\n const connected = await this.bee.connectivity.isConnected()\n\n this.postMessage(event, {\n type: \"isConnectedResponse\",\n requestId: message.requestId,\n connected,\n })\n }\n\n private async handleGetNodeInfo(\n message: GetNodeInfoMessage,\n event: MessageEvent,\n ): Promise<void> {\n try {\n const nodeInfo = await this.bee.status.getNodeInfo()\n\n this.postMessage(event, {\n type: \"getNodeInfoResponse\",\n requestId: message.requestId,\n beeMode: nodeInfo.beeMode,\n chequebookEnabled: nodeInfo.chequebookEnabled,\n swapEnabled: nodeInfo.swapEnabled,\n })\n } catch (error) {\n this.sendErrorToParent(\n event,\n message.requestId,\n error,\n \"Failed to get node info\",\n )\n }\n }\n\n private handleDisconnect(\n message: { type: \"disconnect\"; requestId: string },\n event: MessageEvent,\n ): void {\n // Clear auth data\n this.clearAuthData()\n\n // Send response\n this.postMessage(event, {\n type: \"disconnectResponse\",\n requestId: message.requestId,\n success: true,\n })\n }\n\n /**\n * Show authentication button in the UI\n */\n private showAuthButton(): void {\n if (!this.authButtonContainer || this.isConnecting) {\n return\n }\n\n // Clear existing content\n this.authButtonContainer.innerHTML = \"\"\n\n // Create button based on authentication status\n const button = document.createElement(\"button\")\n const isAuthenticated = this.authenticated\n const isLoading = this.authLoading\n\n // Get text from buttonConfig or use defaults\n const config = this.buttonConfig || {}\n const loadingText = config.loadingText || \"⏳ Loading...\"\n const disconnectText =\n config.disconnectText || \"🔓 Disconnect from Swarm ID\"\n const connectText = config.connectText || \"🔐 Login with Swarm ID\"\n\n if (isLoading) {\n button.textContent = loadingText\n button.disabled = true\n } else if (isAuthenticated) {\n button.textContent = disconnectText\n } else {\n button.textContent = connectText\n }\n\n // Make button fill container\n button.style.width = \"100%\"\n button.style.height = \"100%\"\n button.style.display = \"flex\"\n button.style.alignItems = \"center\"\n button.style.justifyContent = \"center\"\n\n if (isLoading) {\n button.style.backgroundColor = \"#999\"\n button.style.cursor = \"default\"\n } else if (isAuthenticated) {\n // Different color for disconnect button\n button.style.backgroundColor = \"#666\"\n button.style.cursor = \"pointer\"\n } else {\n button.style.backgroundColor = config.backgroundColor || \"#dd7200\"\n button.style.cursor = \"pointer\"\n }\n button.style.color = config.color || \"white\"\n button.style.border = \"none\"\n button.style.borderRadius = config.borderRadius || \"0\"\n button.style.padding = \"0\"\n button.style.fontSize = config.fontSize || \"14px\"\n button.style.fontWeight = config.fontWeight?.toString() ?? \"600\"\n // No default: left unset the button keeps the font the iframe gives it.\n // Forcing `inherit` here would inherit the IFRAME's font, not the parent\n // page's, so it would change the look without getting any closer to the\n // embedding page. A stack the dApp does pass falls back as usual.\n if (config.fontFamily) {\n button.style.fontFamily = config.fontFamily\n }\n\n // Click handler\n button.addEventListener(\"click\", () => {\n if (isAuthenticated) {\n // Handle disconnect\n this.handleDisconnectClick()\n } else {\n // Handle login\n this.handleLoginClick(button)\n }\n })\n\n this.authButtonContainer.appendChild(button)\n }\n\n private handleConnect(\n message: {\n type: \"connect\"\n requestId: string\n popupMode?: \"popup\" | \"window\"\n },\n event: MessageEvent,\n ): void {\n const success = this.openAuthPopup({\n popupMode: message.popupMode,\n })\n this.postMessage(event, {\n type: \"connectResponse\",\n requestId: message.requestId,\n success,\n })\n }\n\n /**\n * Open the authentication popup window.\n * Returns true if popup was opened, false if parent origin is not set.\n */\n private openAuthPopup(options?: { popupMode?: \"popup\" | \"window\" }): boolean {\n if (!this.parentOrigin) {\n console.error(\"[Proxy] Cannot open auth window - parent origin not set\")\n return false\n }\n\n // Build authentication URL using shared utility\n // challenge: used for storage partitioning detection — popup checks if it can read this from localStorage\n const challenge = crypto.randomUUID()\n this.pendingChallenge = challenge\n localStorage.setItem(STORAGE_CHALLENGE_KEY, challenge)\n\n // Get base path from current location (e.g., /id/pr-140/proxy -> /id/pr-140)\n const basePath = window.location.pathname.replace(/\\/proxy$/, \"\")\n const authUrl = buildAuthUrl(\n window.location.origin + basePath,\n this.parentOrigin,\n this.appMetadata,\n { challenge },\n )\n\n // Open as popup or full window based on popupMode (per-call override takes precedence)\n const effectivePopupMode = options?.popupMode ?? this.popupMode\n let popup: Window | null = null\n if (effectivePopupMode === \"popup\") {\n popup = window.open(authUrl, \"_blank\", \"width=500,height=600\")\n } else {\n popup = window.open(authUrl, \"_blank\")\n }\n\n // Check if popup was blocked (common on mobile Safari)\n if (!popup) {\n console.warn(\"[Proxy] Popup was blocked or failed to open\")\n this.isConnecting = false\n this.showAuthButton()\n return false\n }\n\n // Monitor popup closure to handle user closing without completing auth\n // Note: We delay the start of monitoring because on Safari, popup.closed can\n // return true immediately for new tabs before they're fully initialized\n const POPUP_MONITOR_START_DELAY_MS = 2000\n const POPUP_CLOSE_CHECK_INTERVAL_MS = 500\n const POPUP_MONITOR_TIMEOUT_MS = 300000 // 5 minutes\n\n setTimeout(() => {\n const checkPopupClosed = setInterval(() => {\n if (popup?.closed) {\n clearInterval(checkPopupClosed)\n // Only process if we're still in connecting state (auth didn't complete via storage event)\n if (this.isConnecting && !this.authenticated) {\n this.isConnecting = false\n this.showAuthButton()\n }\n }\n }, POPUP_CLOSE_CHECK_INTERVAL_MS)\n\n // Clear interval to prevent memory leak\n setTimeout(() => {\n clearInterval(checkPopupClosed)\n }, POPUP_MONITOR_TIMEOUT_MS)\n }, POPUP_MONITOR_START_DELAY_MS)\n\n return true\n }\n\n /**\n * Handle login button click\n */\n private handleLoginClick(button: HTMLButtonElement): void {\n this.isConnecting = true\n // Disable button and show spinner\n button.disabled = true\n button.innerHTML =\n '<span style=\"display: inline-block; width: 16px; height: 16px; border: 2px solid rgba(255,255,255,.3); border-radius: 50%; border-top-color: white; animation: spin 1s linear infinite;\"></span>'\n\n // Add spinner animation\n if (!document.getElementById(\"swarm-id-spinner-style\")) {\n const style = document.createElement(\"style\")\n style.id = \"swarm-id-spinner-style\"\n style.textContent =\n \"@keyframes spin { to { transform: rotate(360deg); } }\"\n document.head.appendChild(style)\n }\n\n // Open auth popup\n this.openAuthPopup()\n }\n\n /**\n * Handle disconnect button click\n */\n private handleDisconnectClick(): void {\n // Clear auth data\n this.clearAuthData()\n\n // Notify parent about auth status change\n this.sendToParent({\n type: \"authStatusResponse\",\n requestId: \"disconnect\",\n authenticated: false,\n origin: undefined,\n })\n }\n\n /**\n * Set container element for auth button\n */\n setAuthButtonContainer(container: HTMLElement): void {\n this.authButtonContainer = container\n // Show button now that container is available\n // (loadAuthData may have already run and set authenticated status)\n this.showAuthButton()\n }\n\n private async handleUploadData(\n message: UploadDataMessage,\n event: MessageEvent,\n ): Promise<void> {\n const {\n requestId,\n data,\n options,\n requestOptions,\n enableProgress,\n useWebSocket,\n useWorkers,\n workerCount,\n concurrency,\n } = message\n\n try {\n await this.ensureCanWrite()\n\n // Create progress callback if enabled (works in both modes)\n const onProgress = this.createProgressCallback(\n event,\n requestId,\n enableProgress,\n )\n\n // Execute upload with mode-aware locking\n const uploadResult = await this.withModeAwareWriteLock(\n { useWorkers, workerCount },\n async (target) => {\n const result = await uploadData(target, data, {\n encryptionKey: options?.encrypt ? true : undefined,\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n webSocket: useWebSocket ? { concurrency } : undefined,\n httpConcurrency: concurrency,\n onProgress,\n requestOptions,\n })\n return result\n },\n )\n\n // Send response\n this.postMessage(event, {\n type: \"uploadDataResponse\",\n requestId,\n reference: uploadResult.reference,\n tagUid: uploadResult.tagUid,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"Upload failed\")\n }\n }\n\n private async handleDownloadData(\n message: DownloadDataMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, reference, options, requestOptions } = message\n\n try {\n // Download data using chunk API only (supports both regular and encrypted references)\n const data = await downloadDataWithChunkAPI(\n this.bee,\n reference,\n options,\n undefined,\n requestOptions,\n )\n\n this.postMessage(event, {\n type: \"downloadDataResponse\",\n requestId,\n data: data as Uint8Array,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"Download failed\")\n }\n }\n\n private async handleUploadFile(\n message: UploadFileMessage,\n event: MessageEvent,\n ): Promise<void> {\n const {\n requestId,\n data,\n name,\n options,\n requestOptions,\n enableProgress,\n useWebSocket,\n useWorkers,\n workerCount,\n concurrency,\n } = message\n const fileName = name || \"index.bin\"\n\n try {\n await this.ensureCanWrite()\n\n // Create progress callback if enabled\n const onProgress = this.createProgressCallback(\n event,\n requestId,\n enableProgress,\n )\n\n // Create or use provided tag for the entire upload operation\n const uploadTag = options?.tag ?? (await tryCreateTag(this.bee))\n\n // Execute upload with mode-aware locking\n const manifestResult = await this.withModeAwareWriteLock(\n { useWorkers, workerCount },\n async (target) => {\n // Step 1: Upload file content — encrypted by default, like the\n // manifest below (the rule is `manifestEncryption`)\n const encryption = manifestEncryption(options)\n\n const contentUpload = await uploadData(target, data, {\n encryptionKey: encryption.content ? true : undefined,\n pin: options?.pin,\n deferred: options?.deferred,\n tag: uploadTag,\n webSocket: useWebSocket ? { concurrency } : undefined,\n httpConcurrency: concurrency,\n onProgress,\n requestOptions,\n })\n\n // Step 2: Build manifest\n const manifest = new MantarayNode()\n const contentReferenceBytes = hexToUint8Array(contentUpload.reference)\n\n manifest.addFork(fileName, contentReferenceBytes, {\n \"Content-Type\": \"application/octet-stream\",\n Filename: fileName,\n })\n manifest.addFork(\"/\", NULL_ADDRESS, {\n \"website-index-document\": fileName,\n })\n\n // Step 3: Upload manifest tree\n const result = await saveMantarayTree(\n manifest,\n async (chunkData, isRoot) => {\n await uploadChunk(target, chunkData, {\n pin: options?.pin,\n deferred: options?.deferred,\n tag: uploadTag,\n requestOptions,\n })\n return { tagUid: isRoot ? uploadTag : undefined }\n },\n { encrypt: encryption.manifest },\n )\n\n return result\n },\n )\n\n // Send response\n this.postMessage(event, {\n type: \"uploadFileResponse\",\n requestId,\n reference: manifestResult.rootReference,\n tagUid: manifestResult.tagUid,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"Upload failed\")\n }\n }\n\n private async handleDownloadFile(\n message: DownloadFileMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, reference, path, options, requestOptions } = message\n\n try {\n // Always load the manifest first - file uploads create manifests\n const manifest = await loadMantarayTreeWithChunkAPI(\n this.bee,\n reference,\n requestOptions,\n )\n\n let targetPath: string\n if (path) {\n // Use provided path\n targetPath = path\n } else {\n // No path: get index document from manifest metadata\n const { indexDocument } = new BeeMantarayNode(\n manifest,\n ).getDocsMetadata()\n if (!indexDocument) {\n throw new Error(\n \"Manifest does not contain an index document reference\",\n )\n }\n targetPath = indexDocument\n }\n\n // Find the content node at the target path\n const contentNode = manifest.find(targetPath)\n if (!contentNode) {\n throw new Error(`Path not found in manifest: ${targetPath}`)\n }\n if (!contentNode.targetAddress) {\n throw new Error(`Path \"${targetPath}\" does not have a target address`)\n }\n\n // Get filename from metadata or use the path\n const name =\n contentNode.metadata?.[\"Filename\"] ||\n targetPath.split(\"/\").pop() ||\n \"file\"\n\n // Download actual content from the target address\n const targetRef = uint8ArrayToHex(contentNode.targetAddress)\n\n const data = await downloadDataWithChunkAPI(\n this.bee,\n targetRef,\n options,\n undefined,\n requestOptions,\n )\n\n this.postMessage(event, {\n type: \"downloadFileResponse\",\n requestId,\n name,\n data,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"Download failed\")\n }\n }\n\n /** A folder as one manifest (#750); the Swarm-specific part is `uploadCollection`. */\n private async handleUploadFiles(\n message: UploadFilesMessage,\n event: MessageEvent,\n ): Promise<void> {\n const {\n requestId,\n files,\n options,\n requestOptions,\n enableProgress,\n useWorkers,\n workerCount,\n } = message\n\n try {\n await this.ensureCanWrite()\n const onProgress = this.createProgressCallback(\n event,\n requestId,\n enableProgress,\n )\n const tag = options?.tag ?? (await tryCreateTag(this.bee))\n\n const result = await this.withModeAwareWriteLock(\n { useWorkers, workerCount },\n (target) =>\n uploadCollection(target, files, {\n ...options,\n tag,\n onProgress,\n requestOptions,\n }),\n )\n\n this.postMessage(event, {\n type: \"uploadFilesResponse\",\n requestId,\n reference: result.reference,\n tagUid: result.tagUid,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"Upload failed\")\n }\n }\n\n private async handleListFiles(\n message: ListFilesMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, reference, requestOptions } = message\n\n try {\n const entries = await listCollection(this.bee, reference, requestOptions)\n this.postMessage(event, { type: \"listFilesResponse\", requestId, entries })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"Listing failed\")\n }\n }\n\n private async handleUploadChunk(\n message: UploadChunkMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, data, options, requestOptions } = message\n\n try {\n await this.ensureCanWrite()\n\n // Validate chunk size (must be between 1 and 4096 bytes)\n if (data.length < 1 || data.length > 4096) {\n throw new Error(\n `Invalid chunk size: ${data.length} bytes. Chunks must be between 1 and 4096 bytes.`,\n )\n }\n\n // Create content-addressed chunk from raw payload\n const chunk = makeContentAddressedChunk(data)\n\n // Execute upload with mode-aware locking\n await this.withModeAwareWriteLock(undefined, async (target) => {\n await uploadChunk(target, chunk.data, {\n pin: options?.pin,\n deferred: options?.deferred ?? false,\n tag: options?.tag,\n requestOptions,\n })\n })\n\n this.postMessage(event, {\n type: \"uploadChunkResponse\",\n requestId,\n reference: chunk.address.toHex(),\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"Upload failed\")\n }\n }\n\n private async handleDownloadChunk(\n message: DownloadChunkMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, reference, options, requestOptions } = message\n\n try {\n // Download chunk using bee-js (returns Uint8Array directly)\n const data = await this.bee.chunk.download(\n reference,\n options,\n requestOptions,\n )\n\n this.postMessage(event, {\n type: \"downloadChunkResponse\",\n requestId,\n data: data as Uint8Array,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"Download failed\")\n }\n }\n\n private handleGsocMine(message: GsocMineMessage, event: MessageEvent): void {\n const { requestId, targetOverlay, identifier, proximity } = message\n\n try {\n const signer = this.bee.messaging.gsocMine(\n targetOverlay,\n identifier,\n proximity,\n )\n\n this.postMessage(event, {\n type: \"gsocMineResponse\",\n requestId,\n signer: signer.toHex(),\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"GSOC mine failed\")\n }\n }\n\n private async handleGsocSend(\n message: GsocSendMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, signer, identifier, data, options } = message\n\n try {\n await this.ensureCanWrite()\n\n const signerKey = new PrivateKey(signer)\n const id = new Identifier(identifier)\n\n const result = await this.withModeAwareWriteLock(\n undefined,\n async (target) => {\n return await uploadSOC(target, signerKey, id, data, {\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n })\n },\n )\n\n this.postMessage(event, {\n type: \"gsocSendResponse\",\n requestId,\n reference: uint8ArrayToHex(result.socAddress),\n tagUid: result.tagUid,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"GSOC send failed\")\n }\n }\n\n // ============================================================================\n // SOC (Single Owner Chunk) Handlers\n // ============================================================================\n\n private async handleSocUpload(\n message: SocUploadMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, identifier, data, signer, options } = message\n\n try {\n await this.ensureCanWrite()\n\n const signerKey = signer ?? this.appSecret!\n const signerKeyObj = new PrivateKey(signerKey)\n const id = new Identifier(identifier)\n\n const result = await this.withModeAwareWriteLock(\n undefined,\n async (target) => {\n return await uploadSOC(target, signerKeyObj, id, data, {\n encryptionKey: options?.encrypt !== false ? true : undefined,\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n })\n },\n )\n\n this.postMessage(event, {\n type: \"socUploadResponse\",\n requestId,\n reference: uint8ArrayToHex(result.socAddress),\n tagUid: result.tagUid,\n encryptionKey: result.encryptionKey\n ? uint8ArrayToHex(result.encryptionKey)\n : undefined,\n owner: signerKeyObj.publicKey().address().toHex(),\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"SOC upload failed\")\n }\n }\n\n private async handleSocRawUpload(\n message: SocRawUploadMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, identifier, data, signer, options } = message\n\n try {\n await this.ensureCanWrite()\n\n const signerKey = signer ?? this.appSecret!\n const signerKeyObj = new PrivateKey(signerKey)\n const id = new Identifier(identifier)\n\n const result = await this.withModeAwareWriteLock(\n undefined,\n async (target) => {\n return await uploadSOC(target, signerKeyObj, id, data, {\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n })\n },\n )\n\n this.postMessage(event, {\n type: \"socRawUploadResponse\",\n requestId,\n reference: uint8ArrayToHex(result.socAddress),\n tagUid: result.tagUid,\n encryptionKey: undefined,\n owner: signerKeyObj.publicKey().address().toHex(),\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"SOC raw upload failed\")\n }\n }\n\n private async handleSocDownload(\n message: SocDownloadMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, owner, identifier, encryptionKey, requestOptions } =\n message\n\n try {\n let resolvedOwner = owner\n if (!resolvedOwner) {\n if (!this.appSecret) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"Not authenticated. Please login first.\",\n )\n }\n resolvedOwner = new PrivateKey(this.appSecret)\n .publicKey()\n .address()\n .toHex()\n }\n\n const soc = await downloadEncryptedSOC(\n this.bee,\n resolvedOwner,\n identifier,\n encryptionKey,\n requestOptions,\n )\n\n this.postMessage(event, {\n type: \"socDownloadResponse\",\n requestId,\n data: soc.data,\n identifier: soc.identifier,\n signature: soc.signature,\n span: soc.span,\n payload: soc.payload,\n address: soc.address,\n owner: soc.owner,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"SOC download failed\")\n }\n }\n\n private async handleSocRawDownload(\n message: SocRawDownloadMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, owner, identifier, encryptionKey, requestOptions } =\n message\n\n try {\n let resolvedOwner = owner\n if (!resolvedOwner) {\n if (!this.appSecret) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"Not authenticated. Please login first.\",\n )\n }\n resolvedOwner = new PrivateKey(this.appSecret)\n .publicKey()\n .address()\n .toHex()\n }\n\n const soc = encryptionKey\n ? await downloadEncryptedSOC(\n this.bee,\n resolvedOwner,\n identifier,\n encryptionKey,\n requestOptions,\n )\n : await downloadSOC(this.bee, resolvedOwner, identifier, requestOptions)\n\n this.postMessage(event, {\n type: \"socRawDownloadResponse\",\n requestId,\n data: soc.data,\n identifier: soc.identifier,\n signature: soc.signature,\n span: soc.span,\n payload: soc.payload,\n address: soc.address,\n owner: soc.owner,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"SOC raw download failed\")\n }\n }\n\n private async handleSocGetOwner(\n message: SocGetOwnerMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId } = message\n\n try {\n if (!this.appSecret) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"Not authenticated. Please login first.\",\n )\n }\n\n const owner = new PrivateKey(this.appSecret).publicKey().address().toHex()\n\n this.postMessage(event, {\n type: \"socGetOwnerResponse\",\n requestId,\n owner,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"SOC get owner failed\")\n }\n }\n\n /**\n * Derive a stable, app-scoped secret HMAC(appSecret, label) for the connected\n * app. `appSecret` is genuinely secret (unlike the recoverable appKey public\n * key) and never leaves the iframe — only the derived bytes are returned, so\n * the app can seed an unguessable feed topic without exposing metadata (#520).\n */\n private async handleDeriveAppSecret(\n message: DeriveAppSecretMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, label } = message\n\n try {\n if (!this.authenticated || !this.appSecret) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"Not authenticated. Please login first.\",\n )\n }\n\n const secretHex = await deriveSecret(this.appSecret, label)\n\n this.postMessage(event, {\n type: \"deriveAppSecretResponse\",\n requestId,\n secret: hexToUint8Array(secretHex),\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"deriveAppSecret failed\")\n }\n }\n\n /**\n * Hand the authenticated page its site's chat key, the key whose address the\n * site's chat binding names (docs/Chat-Binding.md), so the site's chat can\n * sign with it. Same authentication rule as `handleDeriveAppSecret`, and like\n * it the app secret itself never leaves the frame.\n */\n private async handleGetChatKey(\n message: GetChatKeyMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId } = message\n\n try {\n if (!this.authenticated || !this.appSecret) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"Not authenticated. Please login first.\",\n )\n }\n\n this.postMessage(event, {\n type: \"getChatKeyResponse\",\n requestId,\n chatKey: deriveChatKey(this.appSecret),\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"getChatKey failed\")\n }\n }\n\n /**\n * The site names a new Bee node for its session, as the source its viewer\n * picked changes. Needs no sign-in: it says where this site's reads go,\n * which is the site's to decide whether or not anyone is connected.\n *\n * The site naming its node also brings back a subsidised gateway the\n * person's own custom node had switched off (`dropGatewayForOwnCustomNode`).\n */\n private handleSetBeeApiUrl(\n message: SetBeeApiUrlMessage,\n event: MessageEvent,\n ): void {\n this.siteBeeApiUrl = message.beeApiUrl\n this.subsidisedGatewayUrl = this.siteSubsidisedGatewayUrl\n this.postMessage(event, {\n type: \"setBeeApiUrlResponse\",\n requestId: message.requestId,\n beeApiUrl: message.beeApiUrl,\n })\n this.emitConnectionInfoIfChanged()\n }\n\n /**\n * Stamp a chunk with the account's own stamp and hand the stamp back, for\n * the app to send through its own Swarm access. The stamp takes the same\n * write path as an upload, so it is returned only after the coordinator has\n * published the partition counter, the guarantee an upload's ack carries.\n * It also needs the stamper state saved, and refuses when that save fails.\n * A restored session stamps only once its session check allows writes, as\n * every write does (`ensureCanWrite`).\n * The account's own lock and counter chunks never\n * come through here, they stay inside the frame and go through its node.\n */\n private async handleStampChunk(\n message: StampChunkMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, chunk } = message\n\n try {\n await this.ensureCanWrite()\n if (this.isSubsidisedModeActive()) throw subsidisedStampRefusal()\n const prepared = prepareChunkToStamp(chunk, {\n appSecret: this.appSecret!,\n internalOwners: this.accountInternalOwners(),\n })\n\n const stamped = await this.withModeAwareWriteLock(\n undefined,\n async (target) => {\n // The mode can change while the write waits on the lock.\n if (!isStamperTarget(target)) throw subsidisedStampRefusal()\n if (!(target.stamper instanceof UtilizationAwareStamper)) {\n throw new SwarmIdError(\n \"internal\",\n \"The write path has no utilization-aware stamper to stamp with.\",\n )\n }\n const result = this.stampWithinAppLimits(target.stamper, prepared)\n await this.persistStamperStateForStamp()\n return result\n },\n )\n stamped.recordSlot?.()\n\n this.postMessage(event, {\n type: \"stampChunkResponse\",\n requestId,\n address: uint8ArrayToHex(prepared.address),\n stamp: stamped.marshaled,\n owner: prepared.owner?.toHex(),\n signature: prepared.signature?.toHex(),\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"stampChunk failed\")\n }\n }\n\n /**\n * Save the counter a stamp spent before the stamp is returned. The write\n * path's own flush only logs a failure, which an upload can afford and a\n * stamp cannot: an account on one device keeps its counter nowhere else, so\n * after a reload the slot would be handed out a second time.\n */\n private async persistStamperStateForStamp(): Promise<void> {\n try {\n await this.persistStamperState()\n } catch (error) {\n console.error(\"[Proxy] Failed to save the stamp's counter:\", error)\n throw new SwarmIdError(\n \"internal\",\n \"The stamp counter could not be saved, so no stamp was returned. Try again.\",\n )\n }\n }\n\n /**\n * Stamp for this app if its limits allow, and count the stamp. Synchronous\n * from the read of the usage to its write, so two requests can never both\n * take the last stamp an app is allowed. The usage is written before the\n * stamp is returned, so a stamp that could not be counted is never handed\n * out.\n *\n * A single-owner chunk this app had stamped before in this device's lane is\n * stamped again at the same slot with a newer timestamp, which a mutable\n * batch takes as a replacement. That spends no slot, so only the count and\n * the rate apply. A content chunk always takes a new slot.\n *\n * The slot a single-owner chunk went to is not written here. The caller\n * runs `recordSlot` only once the counter that spent it is saved and\n * published: a record left by a stamp that failed to commit would outlive\n * the counter, the slot would go to another chunk after a reload, and the\n * next stamp of this address would replace that chunk on Bee.\n */\n private stampWithinAppLimits(\n stamper: UtilizationAwareStamper,\n chunk: ChunkToStamp,\n ): StampedForApp {\n if (!this.parentOrigin) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"No app has identified itself to this frame.\",\n )\n }\n const batchId = stamper.batchId.toHex()\n const usageKey = appStampUsageStorageKey(this.parentOrigin, batchId)\n const usage = parseAppStampUsage(localStorage.getItem(usageKey))\n const now = Date.now()\n\n if (!chunk.owner) {\n const stamp = this.stampNewSlot(stamper, chunk.address, usage, now)\n localStorage.setItem(usageKey, JSON.stringify(stamp.usage))\n return { marshaled: stamp.marshaled }\n }\n\n const slotsKey = socRestampSlotsStorageKey(this.parentOrigin, batchId)\n const lane = {\n partition: stamper.currentPartition ?? 0,\n partitionCount: stamper.partitionCount,\n }\n const slots = parseSocRestampSlots(localStorage.getItem(slotsKey), lane)\n const addressHex = uint8ArrayToHex(chunk.address)\n const held = slots.slots[addressHex]\n\n let slot: number\n let timestampMs: number\n let marshaled: string\n let nextUsage: AppStampUsage\n if (held && stamper.isOwnDataSlot(chunk.address, held.slot)) {\n const refusal = appRestampRefusal(usage, now)\n if (refusal) throw refusal\n slot = held.slot\n const restampKey = `${slotsKey}:${addressHex}`\n const previousMs = Math.max(\n held.timestampMs,\n this.lastRestampMs.get(restampKey) ?? 0,\n )\n timestampMs = restampTimestamp(previousMs, now)\n marshaled = marshalStamp(\n stamper.restampDataChunk(chunk.address, slot, timestampMs),\n )\n this.lastRestampMs.set(restampKey, timestampMs)\n nextUsage = recordAppRestamp(usage, now)\n } else {\n const stamp = this.stampNewSlot(stamper, chunk.address, usage, now)\n slot = stamp.slot\n timestampMs = now\n marshaled = stamp.marshaled\n nextUsage = stamp.usage\n }\n localStorage.setItem(usageKey, JSON.stringify(nextUsage))\n // Read again when recording, so a stamp committed meanwhile keeps its entry.\n const recordSlot = () => {\n const latest = parseSocRestampSlots(localStorage.getItem(slotsKey), lane)\n localStorage.setItem(\n slotsKey,\n JSON.stringify(recordSocSlot(latest, addressHex, slot, timestampMs)),\n )\n }\n return { marshaled, recordSlot }\n }\n\n /** Stamp at a new slot within the app's limits, and the usage that counts it */\n private stampNewSlot(\n stamper: UtilizationAwareStamper,\n address: Uint8Array,\n usage: AppStampUsage,\n now: number,\n ): { marshaled: string; slot: number; usage: AppStampUsage } {\n const bucket = toBucket(address)\n const refusal = appStampRefusal(usage, {\n bucket,\n laneCapacity: stamper.laneCapacity(),\n now,\n })\n if (refusal) throw refusal\n const envelope = stamper.stampDataChunk(address, now)\n return {\n marshaled: marshalStamp(envelope),\n slot: slotOfStampIndex(envelope.index),\n usage: recordAppStamp(usage, bucket, now),\n }\n }\n\n /**\n * The owner addresses of the account's own chunks and keys, for refusing a\n * single-owner chunk an app asks to have signed as one of them.\n */\n private accountInternalOwners(): Set<string> {\n const connection = this.findConnectionForParent()\n if (!connection) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"No connected account for this app. Please login first.\",\n )\n }\n return accountInternalOwners({\n accountAddress: connection.account.id.toHex(),\n derivationKey: connection.account.derivationKey,\n postageSignerKey: this.signerKey,\n })\n }\n\n private parseFeedTimestamp(value: string | number): bigint {\n if (typeof value === \"number\") {\n return BigInt(Math.floor(value))\n }\n // Validate string is a valid integer representation\n if (!/^-?\\d+$/.test(value)) {\n throw new Error(\n `Invalid timestamp format: \"${value}\" (expected decimal integer)`,\n )\n }\n return BigInt(value)\n }\n\n private parseFeedIndex(value: string | number): bigint {\n if (typeof value === \"number\") {\n return BigInt(Math.floor(value))\n }\n // Validate string is a valid integer representation\n if (!/^-?\\d+$/.test(value)) {\n throw new Error(\n `Invalid index format: \"${value}\" (expected decimal integer)`,\n )\n }\n return BigInt(value)\n }\n\n private makeSequentialFeedIdentifier(\n topic: Uint8Array,\n index: bigint,\n ): Uint8Array {\n const indexBytes = Binary.numberToUint64(index, \"BE\")\n return Binary.keccak256(Binary.concatBytes(topic, indexBytes))\n }\n\n private async findLatestSequentialIndex(\n topic: Uint8Array,\n owner: EthAddress,\n requestOptions?: BeeRequestOptions,\n lookupTimeoutMs?: number,\n ): Promise<bigint | undefined> {\n const lookupOptions: BeeRequestOptions = {\n ...requestOptions,\n timeout: lookupTimeoutMs ?? SEQUENTIAL_INDEX_LOOKUP_TIMEOUT_MS,\n }\n const finder = createAsyncSequentialFinder({\n bee: this.bee,\n topic: new Topic(topic),\n owner,\n })\n const result = await finder.findAt(0n, 0n, lookupOptions)\n return result.current\n }\n\n private sequentialNextIndex(index: bigint): bigint {\n const max = (1n << 64n) - 1n\n return index === max ? 0n : index + 1n\n }\n\n private async handleFeedGetOwner(\n message: FeedGetOwnerMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId } = message\n\n try {\n if (!this.appSecret) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"Not authenticated. Please login first.\",\n )\n }\n\n const owner = new PrivateKey(this.appSecret).publicKey().address().toHex()\n\n this.postMessage(event, {\n type: \"feedGetOwnerResponse\",\n requestId,\n owner,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"Feed get owner failed\")\n }\n }\n\n private async handleEpochFeedDownloadReference(\n message: EpochFeedDownloadReferenceMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, topic, owner, at, after, encryptionKey } = message\n\n try {\n let resolvedOwner = owner\n if (!resolvedOwner) {\n if (!this.appSecret) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"Not authenticated. Please login first.\",\n )\n }\n resolvedOwner = new PrivateKey(this.appSecret)\n .publicKey()\n .address()\n .toHex()\n }\n\n const topicObj = new Topic(hexToUint8Array(topic))\n const ownerObj = new EthAddress(resolvedOwner)\n const atValue = this.parseFeedTimestamp(at)\n const afterValue =\n after !== undefined ? this.parseFeedTimestamp(after) : 0n\n const epochKeyBytes = encryptionKey\n ? hexToUint8Array(encryptionKey)\n : undefined\n\n let reference: Uint8Array | undefined\n if (epochKeyBytes) {\n const encryptedFinder = createAsyncEpochFinder({\n bee: this.bee,\n topic: topicObj,\n owner: ownerObj,\n encryptionKey: epochKeyBytes,\n })\n reference = await encryptedFinder.findAt(atValue, afterValue)\n } else {\n const plainFinder = createAsyncEpochFinder({\n bee: this.bee,\n topic: topicObj,\n owner: ownerObj,\n })\n reference = await plainFinder.findAt(atValue, afterValue)\n }\n\n this.postMessage(event, {\n type: \"epochFeedDownloadReferenceResponse\",\n requestId,\n reference: reference ? uint8ArrayToHex(reference) : undefined,\n })\n } catch (error) {\n this.sendErrorToParent(\n event,\n requestId,\n error,\n \"Epoch feed download reference failed\",\n )\n }\n }\n\n private async handleEpochFeedUploadReference(\n message: EpochFeedUploadReferenceMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, topic, signer, at, reference, encryptionKey, hints } =\n message\n\n try {\n await this.ensureCanWrite()\n\n const signerKey = signer ?? this.appSecret!\n const signerKeyObj = new PrivateKey(signerKey)\n const topicObj = new Topic(hexToUint8Array(topic))\n const ownerAddress = signerKeyObj.publicKey().address()\n const atValue = this.parseFeedTimestamp(at)\n const epochEncryptionKey = encryptionKey\n ? hexToUint8Array(encryptionKey)\n : undefined\n\n // Convert hints from message format to updater format\n const epochHints = hints?.lastEpoch\n ? {\n lastEpoch: {\n start: BigInt(hints.lastEpoch.start),\n level: hints.lastEpoch.level,\n },\n lastTimestamp: hints.lastTimestamp\n ? BigInt(hints.lastTimestamp)\n : undefined,\n }\n : undefined\n\n // Validate reference length\n const referenceBytes = hexToUint8Array(reference)\n if (referenceBytes.length !== 32 && referenceBytes.length !== 64) {\n throw new Error(\n `Reference must be 32 or 64 bytes, got ${referenceBytes.length}`,\n )\n }\n\n // Create updater - works with both stamper and subsidised modes\n const updater = createEpochUpdater({\n bee: this.bee,\n topic: topicObj,\n owner: ownerAddress,\n signer: signerKeyObj,\n })\n\n // Use mode-aware write lock for the upload operation\n const updateResult = await this.withModeAwareWriteLock(\n undefined,\n async (target) => {\n const result = await updater.update(\n atValue,\n referenceBytes,\n target,\n epochEncryptionKey,\n epochHints,\n )\n\n return result\n },\n )\n\n // Verify upload with read-back\n const readBackFinder = createAsyncEpochFinder({\n bee: this.bee,\n topic: topicObj,\n owner: ownerAddress,\n encryptionKey: epochEncryptionKey,\n })\n\n // Upload read-back should verify the exact timestamp write and avoid\n // broad fallback scans over historical leaves on poisoned networks.\n await readBackFinder.findAt(atValue, atValue)\n\n this.postMessage(event, {\n type: \"epochFeedUploadReferenceResponse\",\n requestId,\n socAddress: uint8ArrayToHex(updateResult.socAddress),\n encryptionKey: encryptionKey ? encryptionKey : undefined,\n epoch: {\n start: updateResult.epoch.start.toString(),\n level: updateResult.epoch.level,\n },\n timestamp: updateResult.timestamp.toString(),\n })\n } catch (error) {\n this.sendErrorToParent(\n event,\n requestId,\n error,\n \"Epoch feed upload reference failed\",\n )\n }\n }\n\n private async handleSequentialFeedGetOwner(\n message: SequentialFeedGetOwnerMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId } = message\n\n try {\n if (!this.appSecret) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"Not authenticated. Please login first.\",\n )\n }\n\n const owner = new PrivateKey(this.appSecret).publicKey().address().toHex()\n\n this.postMessage(event, {\n type: \"seqFeedGetOwnerResponse\",\n requestId,\n owner,\n })\n } catch (error) {\n this.sendErrorToParent(\n event,\n requestId,\n error,\n \"Sequential feed get owner failed\",\n )\n }\n }\n\n private async resolveSequentialOwner(owner?: string): Promise<string> {\n if (owner) {\n return owner\n }\n if (!this.appSecret) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"Not authenticated. Please login first.\",\n )\n }\n return new PrivateKey(this.appSecret).publicKey().address().toHex()\n }\n\n private parseSequentialPayload(\n payload: Uint8Array,\n hasTimestamp: boolean,\n ): { payload: Uint8Array; timestamp?: number } {\n if (!hasTimestamp) {\n return { payload }\n }\n\n if (payload.length < 8) {\n return { payload, timestamp: undefined }\n }\n\n const view = new DataView(\n payload.buffer,\n payload.byteOffset,\n payload.byteLength,\n )\n const timestamp = Number(view.getBigUint64(0, false))\n return { payload: payload.slice(8), timestamp }\n }\n\n private async resolveSequentialIndex(\n topicBytes: Uint8Array,\n ownerAddress: EthAddress,\n index?: string | number,\n at?: string | number,\n hasTimestamp: boolean = true,\n requestOptions?: BeeRequestOptions,\n encryptionKey?: string,\n raw: boolean = false,\n lookupTimeoutMs?: number,\n ): Promise<bigint> {\n if (!raw && !encryptionKey) {\n throw new SwarmIdError(\n \"invalid-request\",\n \"Encryption key is required for encrypted feed lookup\",\n )\n }\n if (index !== undefined) {\n return this.parseFeedIndex(index)\n }\n\n const latest = await this.findLatestSequentialIndex(\n topicBytes,\n ownerAddress,\n requestOptions,\n lookupTimeoutMs,\n )\n if (latest === undefined) {\n throw new Error(\"Sequential feed has no updates\")\n }\n\n if (at === undefined) {\n return latest\n }\n\n if (!hasTimestamp) {\n throw new Error(\"Cannot use 'at' without timestamps\")\n }\n\n const atValue = this.parseFeedTimestamp(at)\n for (let current = latest; current >= 0n; current--) {\n const identifierBytes = this.makeSequentialFeedIdentifier(\n topicBytes,\n current,\n )\n const identifier = new Identifier(identifierBytes)\n const soc = raw\n ? encryptionKey\n ? await downloadEncryptedSOC(\n this.bee,\n ownerAddress,\n identifier,\n encryptionKey,\n requestOptions,\n )\n : await downloadSOC(\n this.bee,\n ownerAddress,\n identifier,\n requestOptions,\n )\n : await downloadEncryptedSOC(\n this.bee,\n ownerAddress,\n identifier,\n encryptionKey ?? \"\",\n requestOptions,\n )\n\n const parsed = this.parseSequentialPayload(soc.payload, true)\n if (\n parsed.timestamp !== undefined &&\n BigInt(parsed.timestamp) <= atValue\n ) {\n return current\n }\n if (current === 0n) {\n break\n }\n }\n\n // If no update matches the timestamp, fall back to latest for sequential feeds.\n return latest\n }\n\n private async handleSequentialFeedDownloadPayload(\n message: SequentialFeedDownloadPayloadMessage,\n event: MessageEvent,\n ): Promise<void> {\n const {\n requestId,\n topic,\n owner,\n index,\n at,\n hasTimestamp,\n encryptionKey,\n lookupTimeoutMs,\n requestOptions,\n } = message\n\n try {\n if (!encryptionKey) {\n throw new SwarmIdError(\n \"invalid-request\",\n \"Encryption key is required for downloadPayload\",\n )\n }\n\n const resolvedOwner = await this.resolveSequentialOwner(owner)\n const ownerAddress = new EthAddress(resolvedOwner)\n const topicBytes = hexToUint8Array(topic)\n const useTimestamp = hasTimestamp !== false\n const resolvedIndex = await this.resolveSequentialIndex(\n topicBytes,\n ownerAddress,\n index,\n at,\n useTimestamp,\n requestOptions,\n encryptionKey,\n false,\n lookupTimeoutMs,\n )\n\n const identifierBytes = this.makeSequentialFeedIdentifier(\n topicBytes,\n resolvedIndex,\n )\n const identifier = new Identifier(identifierBytes)\n const soc = await downloadEncryptedSOC(\n this.bee,\n ownerAddress,\n identifier,\n encryptionKey,\n requestOptions,\n )\n\n const parsed = this.parseSequentialPayload(soc.payload, useTimestamp)\n const nextIndex = this.sequentialNextIndex(resolvedIndex)\n\n this.postMessage(event, {\n type: \"seqFeedDownloadPayloadResponse\",\n requestId,\n payload: parsed.payload,\n timestamp: parsed.timestamp,\n feedIndex: resolvedIndex.toString(),\n feedIndexNext: nextIndex.toString(),\n })\n } catch (error) {\n this.sendErrorToParent(\n event,\n requestId,\n error,\n \"Sequential feed download payload failed\",\n )\n }\n }\n\n private async handleSequentialFeedDownloadRawPayload(\n message: SequentialFeedDownloadRawPayloadMessage,\n event: MessageEvent,\n ): Promise<void> {\n const {\n requestId,\n topic,\n owner,\n index,\n at,\n hasTimestamp,\n encryptionKey,\n lookupTimeoutMs,\n requestOptions,\n } = message\n\n try {\n const resolvedOwner = await this.resolveSequentialOwner(owner)\n const ownerAddress = new EthAddress(resolvedOwner)\n const topicBytes = hexToUint8Array(topic)\n const useTimestamp = hasTimestamp !== false\n const resolvedIndex = await this.resolveSequentialIndex(\n topicBytes,\n ownerAddress,\n index,\n at,\n useTimestamp,\n requestOptions,\n encryptionKey,\n true,\n lookupTimeoutMs,\n )\n\n const identifierBytes = this.makeSequentialFeedIdentifier(\n topicBytes,\n resolvedIndex,\n )\n const identifier = new Identifier(identifierBytes)\n const soc = encryptionKey\n ? await downloadEncryptedSOC(\n this.bee,\n ownerAddress,\n identifier,\n encryptionKey,\n requestOptions,\n )\n : await downloadSOC(this.bee, ownerAddress, identifier, requestOptions)\n\n const parsed = this.parseSequentialPayload(soc.payload, useTimestamp)\n const nextIndex = this.sequentialNextIndex(resolvedIndex)\n\n this.postMessage(event, {\n type: \"seqFeedDownloadRawPayloadResponse\",\n requestId,\n payload: parsed.payload,\n timestamp: parsed.timestamp,\n feedIndex: resolvedIndex.toString(),\n feedIndexNext: nextIndex.toString(),\n })\n } catch (error) {\n this.sendErrorToParent(\n event,\n requestId,\n error,\n \"Sequential feed download raw payload failed\",\n )\n }\n }\n\n private async handleSequentialFeedDownloadReference(\n message: SequentialFeedDownloadReferenceMessage,\n event: MessageEvent,\n ): Promise<void> {\n const {\n requestId,\n topic,\n owner,\n index,\n at,\n hasTimestamp,\n encryptionKey,\n lookupTimeoutMs,\n requestOptions,\n } = message\n\n try {\n if (!encryptionKey) {\n throw new SwarmIdError(\n \"invalid-request\",\n \"Encryption key is required for downloadReference\",\n )\n }\n\n const resolvedOwner = await this.resolveSequentialOwner(owner)\n const ownerAddress = new EthAddress(resolvedOwner)\n const topicBytes = hexToUint8Array(topic)\n const useTimestamp = hasTimestamp !== false\n const resolvedIndex = await this.resolveSequentialIndex(\n topicBytes,\n ownerAddress,\n index,\n at,\n useTimestamp,\n requestOptions,\n encryptionKey,\n false,\n lookupTimeoutMs,\n )\n\n const identifierBytes = this.makeSequentialFeedIdentifier(\n topicBytes,\n resolvedIndex,\n )\n const identifier = new Identifier(identifierBytes)\n const soc = await downloadEncryptedSOC(\n this.bee,\n ownerAddress,\n identifier,\n encryptionKey,\n requestOptions,\n )\n\n const parsed = this.parseSequentialPayload(soc.payload, useTimestamp)\n if (parsed.payload.length !== 32 && parsed.payload.length !== 64) {\n throw new Error(\n \"Sequential feed update does not contain a reference; use downloadPayload\",\n )\n }\n const referenceHex = uint8ArrayToHex(parsed.payload)\n const nextIndex = this.sequentialNextIndex(resolvedIndex)\n\n this.postMessage(event, {\n type: \"seqFeedDownloadReferenceResponse\",\n requestId,\n reference: referenceHex,\n feedIndex: resolvedIndex.toString(),\n feedIndexNext: nextIndex.toString(),\n })\n } catch (error) {\n this.sendErrorToParent(\n event,\n requestId,\n error,\n \"Sequential feed download reference failed\",\n )\n }\n }\n\n private buildSequentialPayload(\n data: Uint8Array,\n hasTimestamp: boolean,\n at: bigint,\n ): Uint8Array {\n if (!hasTimestamp) {\n return data\n }\n const timestamp = new Uint8Array(8)\n const view = new DataView(timestamp.buffer)\n view.setBigUint64(0, at, false)\n return Binary.concatBytes(timestamp, data)\n }\n\n private async handleSequentialFeedUploadPayload(\n message: SequentialFeedUploadPayloadMessage,\n event: MessageEvent,\n ): Promise<void> {\n const {\n requestId,\n topic,\n signer,\n data,\n index,\n at,\n hasTimestamp,\n lookupTimeoutMs,\n options,\n requestOptions,\n } = message\n\n try {\n await this.ensureCanWrite()\n\n const signerKey = signer ?? this.appSecret!\n const signerKeyObj = new PrivateKey(signerKey)\n const ownerAddress = signerKeyObj.publicKey().address()\n const topicBytes = hexToUint8Array(topic)\n\n const useTimestamp = hasTimestamp !== false\n const atValue =\n at !== undefined\n ? this.parseFeedTimestamp(at)\n : BigInt(Math.floor(Date.now() / 1000))\n let resolvedIndex: bigint\n if (index !== undefined) {\n resolvedIndex = this.parseFeedIndex(index)\n } else {\n const latest = await this.findLatestSequentialIndex(\n topicBytes,\n ownerAddress,\n requestOptions,\n lookupTimeoutMs,\n )\n resolvedIndex =\n latest === undefined ? 0n : this.sequentialNextIndex(latest)\n }\n\n const payload = this.buildSequentialPayload(data, useTimestamp, atValue)\n if (payload.length < 1 || payload.length > 4096) {\n throw new Error(\n `Invalid payload length: ${payload.length} (expected 1-4096)`,\n )\n }\n\n const identifierBytes = this.makeSequentialFeedIdentifier(\n topicBytes,\n resolvedIndex,\n )\n const identifier = new Identifier(identifierBytes)\n\n // Upload SOC with optional encryption (enabled by default)\n const result = await this.withModeAwareWriteLock(\n undefined,\n async (target) => {\n return await uploadSOC(target, signerKeyObj, identifier, payload, {\n encryptionKey: options?.encrypt !== false ? true : undefined,\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n })\n },\n )\n\n this.postMessage(event, {\n type: \"seqFeedUploadPayloadResponse\",\n requestId,\n reference: uint8ArrayToHex(result.socAddress),\n feedIndex: resolvedIndex.toString(),\n owner: ownerAddress.toHex(),\n encryptionKey: result.encryptionKey\n ? uint8ArrayToHex(result.encryptionKey)\n : undefined,\n tagUid: result.tagUid,\n })\n } catch (error) {\n this.sendErrorToParent(\n event,\n requestId,\n error,\n \"Sequential feed upload payload failed\",\n )\n }\n }\n\n private async handleSequentialFeedUploadRawPayload(\n message: SequentialFeedUploadRawPayloadMessage,\n event: MessageEvent,\n ): Promise<void> {\n const {\n requestId,\n topic,\n signer,\n data,\n index,\n at,\n hasTimestamp,\n encryptionKey,\n lookupTimeoutMs,\n options,\n requestOptions,\n } = message\n\n try {\n await this.ensureCanWrite()\n\n const signerKey = signer ?? this.appSecret!\n const signerKeyObj = new PrivateKey(signerKey)\n const ownerAddress = signerKeyObj.publicKey().address()\n const topicBytes = hexToUint8Array(topic)\n\n const useTimestamp = hasTimestamp !== false\n const atValue =\n at !== undefined\n ? this.parseFeedTimestamp(at)\n : BigInt(Math.floor(Date.now() / 1000))\n let resolvedIndex: bigint\n if (index !== undefined) {\n resolvedIndex = this.parseFeedIndex(index)\n } else {\n const latest = await this.findLatestSequentialIndex(\n topicBytes,\n ownerAddress,\n requestOptions,\n lookupTimeoutMs,\n )\n resolvedIndex =\n latest === undefined ? 0n : this.sequentialNextIndex(latest)\n }\n\n const payload = this.buildSequentialPayload(data, useTimestamp, atValue)\n if (payload.length < 1 || payload.length > 4096) {\n throw new Error(\n `Invalid payload length: ${payload.length} (expected 1-4096)`,\n )\n }\n\n const identifierBytes = this.makeSequentialFeedIdentifier(\n topicBytes,\n resolvedIndex,\n )\n const identifier = new Identifier(identifierBytes)\n\n // Upload SOC - use encryption if key provided, otherwise plain SOC\n const result = await this.withModeAwareWriteLock(\n undefined,\n async (target) => {\n return await uploadSOC(target, signerKeyObj, identifier, payload, {\n encryptionKey: encryptionKey\n ? hexToUint8Array(encryptionKey)\n : undefined,\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n })\n },\n )\n\n this.postMessage(event, {\n type: \"seqFeedUploadRawPayloadResponse\",\n requestId,\n reference: uint8ArrayToHex(result.socAddress),\n feedIndex: resolvedIndex.toString(),\n owner: ownerAddress.toHex(),\n encryptionKey: encryptionKey ? encryptionKey : undefined,\n tagUid: result.tagUid,\n })\n } catch (error) {\n this.sendErrorToParent(\n event,\n requestId,\n error,\n \"Sequential feed upload raw payload failed\",\n )\n }\n }\n\n private async handleSequentialFeedUploadReference(\n message: SequentialFeedUploadReferenceMessage,\n event: MessageEvent,\n ): Promise<void> {\n const {\n requestId,\n topic,\n signer,\n reference,\n index,\n at,\n hasTimestamp,\n lookupTimeoutMs,\n options,\n requestOptions,\n } = message\n\n try {\n await this.ensureCanWrite()\n\n const signerKey = signer ?? this.appSecret!\n const signerKeyObj = new PrivateKey(signerKey)\n const ownerAddress = signerKeyObj.publicKey().address()\n const topicBytes = hexToUint8Array(topic)\n\n const useTimestamp = hasTimestamp !== false\n const atValue =\n at !== undefined\n ? this.parseFeedTimestamp(at)\n : BigInt(Math.floor(Date.now() / 1000))\n let resolvedIndex: bigint\n if (index !== undefined) {\n resolvedIndex = this.parseFeedIndex(index)\n } else {\n const latest = await this.findLatestSequentialIndex(\n topicBytes,\n ownerAddress,\n requestOptions,\n lookupTimeoutMs,\n )\n resolvedIndex =\n latest === undefined ? 0n : this.sequentialNextIndex(latest)\n }\n\n const referenceBytes = hexToUint8Array(reference)\n const payload = this.buildSequentialPayload(\n referenceBytes,\n useTimestamp,\n atValue,\n )\n if (payload.length < 1 || payload.length > 4096) {\n throw new Error(\n `Invalid payload length: ${payload.length} (expected 1-4096)`,\n )\n }\n\n const identifierBytes = this.makeSequentialFeedIdentifier(\n topicBytes,\n resolvedIndex,\n )\n const identifier = new Identifier(identifierBytes)\n\n // uploadReference always uses encryption with auto-generated key\n const result = await this.withModeAwareWriteLock(\n undefined,\n async (target) => {\n return await uploadSOC(target, signerKeyObj, identifier, payload, {\n encryptionKey: true,\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n })\n },\n )\n\n this.postMessage(event, {\n type: \"seqFeedUploadReferenceResponse\",\n requestId,\n reference: uint8ArrayToHex(result.socAddress),\n feedIndex: resolvedIndex.toString(),\n owner: ownerAddress.toHex(),\n encryptionKey: result.encryptionKey\n ? uint8ArrayToHex(result.encryptionKey)\n : undefined,\n tagUid: result.tagUid,\n })\n } catch (error) {\n this.sendErrorToParent(\n event,\n requestId,\n error,\n \"Sequential feed upload reference failed\",\n )\n }\n }\n\n // ============================================================================\n // ACT (Access Control Tries) Handlers\n // ============================================================================\n\n /**\n * The account-wide ACT key (#519), or undefined when the session carries no\n * account to derive it from.\n */\n private sharingSecret(): Uint8Array | undefined {\n const derivationKey = this.findConnectionForParent()?.account.derivationKey\n return derivationKey ? deriveSharingKey(derivationKey).secret : undefined\n }\n\n /**\n * The keys this session may read or manage ACT data with: the origin-bound\n * app key, then the account-wide sharing key. The ACT module picks the one\n * a share names.\n */\n private actKeys(): Uint8Array[] {\n const appKey = hexToUint8Array(this.appSecret!)\n const sharing = this.sharingSecret()\n return sharing ? [appKey, sharing] : [appKey]\n }\n\n private async handleActUploadData(\n message: ActUploadDataMessage,\n event: MessageEvent,\n ): Promise<void> {\n const {\n requestId,\n data,\n grantees,\n options,\n requestOptions,\n enableProgress,\n } = message\n\n try {\n await this.ensureCanWrite()\n\n // Parse grantee public keys from compressed hex\n const granteePublicKeys = grantees.map((hex) =>\n parseCompressedPublicKey(hex),\n )\n\n // Progress callback (if enabled)\n const onProgress = this.createProgressCallback(\n event,\n requestId,\n enableProgress,\n )\n\n // The app key publishes unless the dApp asked for the identity-level key\n const publisherPrivateKey =\n options?.publisher === \"identity\"\n ? this.sharingSecret()\n : hexToUint8Array(this.appSecret!)\n if (!publisherPrivateKey) {\n throw new Error(\"No sharing key: this session carries no account\")\n }\n const beeCompatible = options?.beeCompatible === true\n\n const { actResult, contentUpload } = await this.withModeAwareWriteLock(\n { useWorkers: true },\n async (target) => {\n // Step 1: Upload raw content data (encrypted by default for 64-byte reference)\n const contentUploadResult = await uploadData(target, data, {\n encryptionKey: options?.encrypt !== false ? true : undefined,\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n onProgress,\n requestOptions,\n })\n\n // Step 2: Create Mantaray manifest wrapping the content\n // Content reference is 64 bytes (encrypted reference: address + encryption key)\n const manifest = new MantarayNode()\n const contentReferenceBytes = hexToUint8Array(\n contentUploadResult.reference,\n )\n manifest.addFork(DEFAULT_ACT_FILENAME, contentReferenceBytes, {\n \"Content-Type\": DEFAULT_ACT_CONTENT_TYPE,\n Filename: DEFAULT_ACT_FILENAME,\n })\n manifest.addFork(\"/\", NULL_ADDRESS, {\n \"website-index-document\": DEFAULT_ACT_FILENAME,\n })\n\n // Step 3: Upload the Mantaray manifest\n const manifestResult = await saveMantarayTree(\n manifest,\n async (chunkData) => {\n await uploadChunk(target, chunkData, {\n pin: options?.pin,\n deferred: options?.deferred,\n tag: options?.tag,\n })\n return {}\n },\n { encrypt: !beeCompatible },\n )\n\n // Step 4: Use manifest reference for ACT encryption\n const manifestReferenceBytes = hexToUint8Array(\n manifestResult.rootReference,\n )\n\n // Create ACT for the manifest (which points to the content)\n const actResultValue = await createActForContent(\n target,\n manifestReferenceBytes,\n publisherPrivateKey,\n granteePublicKeys,\n options,\n requestOptions,\n )\n\n return {\n actResult: actResultValue,\n contentUpload: contentUploadResult,\n }\n },\n )\n\n // Send final response\n this.postMessage(event, {\n type: \"actUploadDataResponse\",\n requestId,\n encryptedReference: actResult.encryptedReference,\n historyReference: actResult.historyReference,\n granteeListReference: actResult.granteeListReference,\n publisherPubKey: actResult.publisherPubKey,\n actReference: actResult.actReference,\n tagUid: contentUpload.tagUid,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"ACT upload failed\")\n }\n }\n\n private async handleActDownloadData(\n message: ActDownloadDataMessage,\n event: MessageEvent,\n ): Promise<void> {\n const {\n requestId,\n encryptedReference,\n historyReference,\n publisherPubKey,\n timestamp,\n requestOptions,\n } = message\n\n try {\n if (!this.authenticated || !this.appSecret) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"Not authenticated. Please login first.\",\n )\n }\n\n // Decrypt the ACT reference to get the content reference\n const contentReference = await decryptActReference(\n this.bee,\n encryptedReference,\n historyReference,\n publisherPubKey,\n this.actKeys(),\n timestamp,\n requestOptions,\n )\n\n // Step 1: Download and unmarshal the Mantaray manifest (chunk API only)\n const manifest = await loadMantarayTreeWithChunkAPI(\n this.bee,\n contentReference,\n requestOptions,\n )\n\n // Step 2: Get the index document path from manifest metadata\n const { indexDocument } = new BeeMantarayNode(manifest).getDocsMetadata()\n if (!indexDocument) {\n throw new Error(\"Manifest does not contain an index document reference\")\n }\n\n // Step 3: Find the node at the index document path\n const contentNode = manifest.find(indexDocument)\n if (!contentNode) {\n throw new Error(`Content node \"${indexDocument}\" not found in manifest`)\n }\n\n if (!contentNode.targetAddress) {\n throw new Error(\n `Content node \"${indexDocument}\" does not have a target address`,\n )\n }\n\n const actualContentRef = uint8ArrayToHex(contentNode.targetAddress)\n\n // Step 4: Download the actual content\n const data = await downloadDataWithChunkAPI(\n this.bee,\n actualContentRef,\n undefined,\n undefined,\n requestOptions,\n )\n\n this.postMessage(event, {\n type: \"actDownloadDataResponse\",\n requestId,\n data: data as Uint8Array,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"ACT download failed\")\n }\n }\n\n private async handleActAddGrantees(\n message: ActAddGranteesMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, historyReference, grantees, requestOptions } = message\n\n try {\n await this.ensureCanWrite()\n\n const publisherPrivateKey = this.actKeys()\n\n // Parse grantee public keys from compressed hex\n const newGranteePublicKeys = grantees.map((hex) =>\n parseCompressedPublicKey(hex),\n )\n\n // Add grantees to ACT\n const result = await this.withModeAwareWriteLock(\n { useWorkers: true },\n async (target) => {\n return await addGranteesToAct(\n target,\n this.bee,\n historyReference,\n publisherPrivateKey,\n newGranteePublicKeys,\n undefined,\n requestOptions,\n )\n },\n )\n\n this.postMessage(event, {\n type: \"actAddGranteesResponse\",\n requestId,\n historyReference: result.historyReference,\n granteeListReference: result.granteeListReference,\n actReference: result.actReference,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"ACT add grantees failed\")\n }\n }\n\n private async handleActRevokeGrantees(\n message: ActRevokeGranteesMessage,\n event: MessageEvent,\n ): Promise<void> {\n const {\n requestId,\n historyReference,\n encryptedReference,\n revokeGrantees,\n requestOptions,\n } = message\n\n try {\n await this.ensureCanWrite()\n\n const publisherPrivateKey = this.actKeys()\n\n // Parse grantee public keys from compressed hex\n const revokePublicKeys = revokeGrantees.map((hex) =>\n parseCompressedPublicKey(hex),\n )\n\n // Revoke grantees from ACT (drops their entries; reference unchanged, #496)\n const result = await this.withModeAwareWriteLock(\n { useWorkers: true },\n async (target) => {\n return await revokeGranteesFromAct(\n target,\n this.bee,\n historyReference,\n encryptedReference,\n publisherPrivateKey,\n revokePublicKeys,\n undefined,\n requestOptions,\n )\n },\n )\n\n this.postMessage(event, {\n type: \"actRevokeGranteesResponse\",\n requestId,\n encryptedReference: result.encryptedReference,\n historyReference: result.historyReference,\n granteeListReference: result.granteeListReference,\n actReference: result.actReference,\n })\n } catch (error) {\n this.sendErrorToParent(\n event,\n requestId,\n error,\n \"ACT revoke grantees failed\",\n )\n }\n }\n\n private async handleActGetGrantees(\n message: ActGetGranteesMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { requestId, historyReference, requestOptions } = message\n\n try {\n if (!this.authenticated || !this.appSecret) {\n throw new SwarmIdError(\n \"not-authenticated\",\n \"Not authenticated. Please login first.\",\n )\n }\n\n const publisherPrivateKey = this.actKeys()\n\n // Get grantees from ACT\n const grantees = await getGranteesFromAct(\n this.bee,\n historyReference,\n publisherPrivateKey,\n requestOptions,\n )\n\n this.postMessage(event, {\n type: \"actGetGranteesResponse\",\n requestId,\n grantees,\n })\n } catch (error) {\n this.sendErrorToParent(event, requestId, error, \"ACT get grantees failed\")\n }\n }\n\n private async handleGetPostageBatch(\n message: GetPostageBatchMessage,\n event: MessageEvent,\n ): Promise<void> {\n const stamp = this.lookupPostageStampForApp()\n\n if (!stamp) {\n this.postMessage(event, {\n type: \"getPostageBatchResponse\",\n requestId: message.requestId,\n postageBatch: undefined,\n })\n return\n }\n\n // The stored stamp's `usable`/`exists` are a snapshot from assignment time\n // and can be stale (e.g. a batch assigned during its ~30s warm-up stays\n // `usable: false` in storage forever), so read live batch details from the\n // Bee node for those fields.\n const details = await fetchBatchDetails(\n this.beeApiUrl,\n stamp.batchID.toHex(),\n )\n\n // Resolve TTL from live chain state: the PostageStamp contract first\n // (ground truth for any batch, even one this Bee node has never seen), then\n // the Bee node's batchTTL. Fall back to the Swarmscan-price approximation\n // only when neither authoritative source can answer (e.g. RPC unreachable\n // and a public gateway that does not track the batch).\n let batchTTL: number | undefined = await fetchAuthoritativeBatchTTL(\n this.gnosisRpcUrl,\n this.beeApiUrl,\n stamp.batchID.toHex(),\n this.postageStampContractAddress,\n )\n if (batchTTL === undefined) {\n try {\n const pricePerGBPerMonth = await fetchSwarmPrice()\n batchTTL = calculateTTLSeconds(stamp.amount, pricePerGBPerMonth)\n } catch (error) {\n console.warn(\"[Proxy] Failed to calculate TTL:\", error)\n }\n }\n\n // Map PostageStamp to public PostageBatch (exclude signerKey)\n const postageBatch: PostageBatch = {\n batchID: stamp.batchID.toHex(),\n utilization: stamp.utilization,\n usable: details?.usable ?? stamp.usable,\n label: \"\", // PostageStamp doesn't store label\n depth: stamp.depth,\n amount: stamp.amount.toString(),\n bucketDepth: stamp.bucketDepth,\n blockNumber: stamp.blockNumber,\n immutableFlag: stamp.immutableFlag,\n exists: details?.exists ?? stamp.exists,\n batchTTL,\n }\n\n this.postMessage(event, {\n type: \"getPostageBatchResponse\",\n requestId: message.requestId,\n postageBatch,\n })\n }\n\n /**\n * Handle createFeedManifest request\n * Creates a feed manifest for accessing feed content via URL\n */\n private async handleCreateFeedManifest(\n message: CreateFeedManifestMessage,\n event: MessageEvent,\n ): Promise<void> {\n const { topic, owner, feedType, uploadOptions } = message\n\n // Resolve owner - use provided or fall back to app signer\n let resolvedOwner = owner\n if (!resolvedOwner && this.appSecret) {\n const signerKeyObj = new PrivateKey(this.appSecret)\n resolvedOwner = signerKeyObj.publicKey().address().toHex()\n }\n\n if (!resolvedOwner) {\n this.sendErrorToParent(\n event,\n message.requestId,\n new SwarmIdError(\n \"invalid-request\",\n \"No owner provided and no app signer available\",\n ),\n \"No owner provided and no app signer available\",\n )\n return\n }\n\n try {\n await this.ensureCanWrite()\n\n const result = await this.withModeAwareWriteLock(\n undefined,\n async (target) => {\n return await createFeedManifestDirect(\n target,\n topic,\n resolvedOwner,\n {\n encrypt: uploadOptions?.encrypt !== false,\n feedType,\n },\n uploadOptions,\n )\n },\n )\n\n this.postMessage(event, {\n type: \"createFeedManifestResponse\",\n requestId: message.requestId,\n reference: result.reference,\n })\n } catch (error) {\n this.sendErrorToParent(\n event,\n message.requestId,\n error,\n \"Create feed manifest failed\",\n )\n }\n }\n}\n\n/**\n * Optional configuration for the proxy, supplied by the host trusted-domain app.\n */\nexport interface ProxyConfig {\n /**\n * The sites this frame serves (docs/Site-Allow-List.md). A parent whose\n * origin it refuses gets an `initError` and nothing else.\n */\n siteAllowList: SiteAllowList\n /**\n * PostageStamp contract address for on-chain stamp-TTL reads. Defaults to the\n * Gnosis mainnet deployment; set this (from a build-time env) to the local\n * bee-compose anvil address when developing against a local chain.\n */\n postageStampContractAddress?: string\n /**\n * Account-bus signaling server URL (e.g. `wss://bus.example.com/bus`,\n * `ws://localhost:5520` in dev). Unset disables the cross-partition/\n * cross-device bus transport (docs/Account-Bus.md).\n */\n signalingUrl?: string\n}\n\n/**\n * Initialize the proxy (called from HTML page)\n */\nexport function initProxy(config: ProxyConfig): SwarmIdProxy {\n return new SwarmIdProxy(config)\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { ConnectionInfo } from \"../types\"\n\n/**\n * Structural equality for {@link ConnectionInfo} snapshots, scoped to the\n * fields the proxy emits to dApps. Used by the proxy to suppress redundant\n * `connectionInfoChanged` events.\n *\n * `storagePartitioned` is compared via boolean coercion so a writer that\n * sends `false` and one that omits the field (effectively `undefined`)\n * are treated as equal — they mean the same thing to the dApp.\n */\nexport function connectionInfoEqual(\n a: ConnectionInfo | undefined,\n b: ConnectionInfo,\n): boolean {\n if (!a) return false\n return (\n a.canUpload === b.canUpload &&\n !!a.storagePartitioned === !!b.storagePartitioned &&\n a.uploadMode === b.uploadMode &&\n // Compared, or a session whose reason changes while `uploadMode` stays\n // \"unavailable\" — a drive bought, a stamper that failed on retry — would\n // compute a new reason and never send it.\n a.uploadUnavailableReason === b.uploadUnavailableReason &&\n a.identity?.id === b.identity?.id &&\n a.identity?.name === b.identity?.name &&\n a.identity?.address === b.identity?.address &&\n a.identity?.publicKey === b.identity?.publicKey &&\n // Compared explicitly rather than assumed to follow the id: an avatar not\n // derived from the id could change while the id stays put.\n a.identity?.avatar.source === b.identity?.avatar.source &&\n a.identity?.avatar.url === b.identity?.avatar.url &&\n a.appKey?.address === b.appKey?.address &&\n a.appKey?.publicKey === b.appKey?.publicKey &&\n a.partition === b.partition &&\n // A newer binding replaces the old one, and readers trust the newest, so\n // the dApp has to hear of it.\n a.chatBinding?.message === b.chatBinding?.message &&\n a.chatBinding?.signature === b.chatBinding?.signature &&\n a.beeApiUrl === b.beeApiUrl\n )\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Sequential Feeds\n *\n * Sequential feed indexing for versioned updates.\n */\n\nexport type {\n SequentialFeedOptions,\n SequentialFeedWriterOptions,\n SequentialLookupResult,\n SequentialFinder,\n SequentialUpdater,\n} from \"./types\"\n\nexport { SyncSequentialFinder } from \"./finder\"\nexport { AsyncSequentialFinder } from \"./async-finder\"\nexport { BasicSequentialUpdater } from \"./updater\"\n\nimport { SyncSequentialFinder } from \"./finder\"\nimport { AsyncSequentialFinder } from \"./async-finder\"\nimport { BasicSequentialUpdater } from \"./updater\"\nimport type {\n SequentialFeedOptions,\n SequentialFeedWriterOptions,\n SequentialFinder,\n SequentialUpdater,\n} from \"./types\"\n\n/**\n * Create a synchronous sequential feed finder\n */\nexport function createSyncSequentialFinder(\n options: SequentialFeedOptions,\n): SequentialFinder {\n return new SyncSequentialFinder(options.bee, options.topic, options.owner)\n}\n\n/**\n * Create an async sequential feed finder\n */\nexport function createAsyncSequentialFinder(\n options: SequentialFeedOptions,\n): SequentialFinder {\n return new AsyncSequentialFinder(options.bee, options.topic, options.owner)\n}\n\n/**\n * Create a sequential feed updater\n */\nexport function createSequentialUpdater(\n options: SequentialFeedWriterOptions,\n): SequentialUpdater {\n return new BasicSequentialUpdater(options.bee, options.topic, options.signer)\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { NULL_ADDRESS } from \"@ethersphere/bee-js\"\nimport { MantarayNode } from \"@ethersphere/core-sdk\"\nimport type { UploadOptions } from \"@ethersphere/bee-js\"\nimport {\n makeEncryptedContentAddressedChunk,\n makeContentAddressedChunk,\n} from \"../chunk\"\nimport { uint8ArrayToHex } from \"../utils/hex\"\nimport { uploadChunk, type UploadTarget } from \"./upload\"\nimport { tryCreateTag } from \"../utils/tag\"\n\n/**\n * Options for creating a feed manifest\n */\nexport interface CreateFeedManifestOptions {\n /**\n * Whether to encrypt the manifest.\n * Default: true (encrypted)\n */\n encrypt?: boolean\n /**\n * Feed type: \"Sequence\" for sequential feeds, \"Epoch\" for epoch feeds.\n * Default: \"Sequence\"\n */\n feedType?: \"Sequence\" | \"Epoch\"\n}\n\n/**\n * Result of creating a feed manifest\n */\nexport interface CreateFeedManifestResult {\n /**\n * Reference to the feed manifest.\n * - Encrypted: 128 hex chars (64 bytes = address + encryption key)\n * - Unencrypted: 64 hex chars (32 bytes = address)\n */\n reference: string\n /**\n * Tag UID if a tag was created during upload\n */\n tagUid?: number\n}\n\n/**\n * Create a feed manifest directly using chunk upload\n *\n * Instead of using bee.createFeedManifest() which calls the /feeds endpoint,\n * this function builds the manifest locally as a MantarayNode and uploads it\n * via the encrypted chunk endpoint (or plain chunk endpoint if encrypt=false).\n *\n * Feed manifests have a single \"/\" path with metadata:\n * - swarm-feed-owner: Owner's ethereum address (40 hex chars, no 0x)\n * - swarm-feed-topic: Topic hash (64 hex chars)\n * - swarm-feed-type: \"Sequence\" for sequential feeds\n *\n * IMPORTANT: This function implements client-side \"saveRecursively\" logic:\n * 1. Upload the \"/\" child node first and get its address\n * 2. Set the child's selfAddress to the uploaded address\n * 3. Then upload the root node (which references the child's address)\n *\n * Without this, Bee's /bzz/ endpoint returns 404 because the \"/\" child chunk\n * doesn't exist (only its calculated hash was stored in the root manifest).\n *\n * @param target - Upload target (stamper or subsidised mode)\n * @param topic - Topic hex string (64 chars)\n * @param owner - Owner hex string (40 chars, no 0x prefix)\n * @param options - Options for creating the manifest (encrypt, etc.)\n * @param uploadOptions - Upload options (tag, deferred, etc.)\n * @returns Reference to the feed manifest\n */\nexport async function createFeedManifestDirect(\n target: UploadTarget,\n topic: string,\n owner: string,\n options?: CreateFeedManifestOptions,\n uploadOptions?: UploadOptions,\n): Promise<CreateFeedManifestResult> {\n // Normalize owner (remove 0x prefix if present)\n const normalizedOwner = owner.startsWith(\"0x\") ? owner.slice(2) : owner\n\n // Create tag for upload if in stamper mode (subsidised mode doesn't support tags)\n const tag =\n target.mode === \"stamper\"\n ? (uploadOptions?.tag ?? (await tryCreateTag(target.bee)))\n : undefined\n const chunkOptions = {\n pin: uploadOptions?.pin,\n deferred: uploadOptions?.deferred,\n tag,\n }\n\n // 2. Create root MantarayNode with \"/\" fork containing feed metadata\n const rootNode = new MantarayNode()\n rootNode.addFork(\"/\", NULL_ADDRESS, {\n \"swarm-feed-owner\": normalizedOwner,\n \"swarm-feed-topic\": topic,\n \"swarm-feed-type\": options?.feedType ?? \"Sequence\",\n })\n\n // 3. Get the \"/\" child node (addFork created it, we need to access it)\n // 47 is ASCII code for '/'\n const slashFork = rootNode.forks.get(47)\n if (!slashFork) {\n throw new Error(\"[FeedManifest] Failed to create '/' fork\")\n }\n const slashNode = slashFork.node\n\n // 4. Marshal and upload the \"/\" child node FIRST (saveRecursively pattern)\n // This is critical: Bee's /bzz/ needs this chunk to exist\n const slashNodeData = await slashNode.marshal()\n const slashChunk = makeContentAddressedChunk(slashNodeData)\n\n await uploadChunk(target, slashChunk.data, chunkOptions)\n\n // 5. Set the child's selfAddress to the uploaded chunk address\n // This is used when marshaling the root node\n slashNode.selfAddress = slashChunk.address.toUint8Array()\n\n // 6. Now marshal and upload the root node\n const rootNodeData = await rootNode.marshal()\n\n // 7. Encrypt and upload OR upload directly\n const shouldEncrypt = options?.encrypt !== false\n\n if (shouldEncrypt) {\n // Encrypted upload for root\n const encryptedChunk = makeEncryptedContentAddressedChunk(rootNodeData)\n\n await uploadChunk(target, encryptedChunk.data, chunkOptions)\n\n // Return 64-byte reference (address + key)\n const ref = new Uint8Array(64)\n ref.set(encryptedChunk.address.toUint8Array(), 0)\n ref.set(encryptedChunk.encryptionKey, 32)\n const reference = uint8ArrayToHex(ref)\n\n return { reference, tagUid: tag }\n } else {\n // Unencrypted upload for root\n const rootChunk = makeContentAddressedChunk(rootNodeData)\n\n await uploadChunk(target, rootChunk.data, chunkOptions)\n\n // Return 32-byte reference\n const reference = rootChunk.address.toHex()\n\n return { reference, tagUid: tag }\n }\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Run `task` at most once per `cooldownMs` across all same-origin browser tabs.\n *\n * An exclusive Web Lock (`navigator.locks`, keyed on `lockName`) serialises\n * concurrent callers so two tabs can't run `task` at the same instant; a shared\n * `localStorage` timestamp (`cooldownKey`) then lets the second caller skip when\n * another tab already ran within the window. `force` bypasses the cooldown (e.g.\n * a page load / manual refresh) but still honours the optional short `graceMs`\n * window, so a burst of forced runs (N tabs on load, or two back-to-back forced\n * triggers) still collapses to one.\n *\n * Effect: with several tabs each scheduling the same task, only ONE actually runs\n * per window — the rest observe the fresh timestamp and return. Whatever the task\n * persists (e.g. the account document) then propagates to the other tabs via their\n * own `storage` listeners, so they never need to fetch themselves.\n *\n * Falls back to running `task` directly when the Web Locks API is unavailable\n * (Node / test), still honouring the cooldown when `localStorage` is present.\n */\nexport async function runCoalescedAcrossTabs(opts: {\n lockName: string\n cooldownKey: string\n cooldownMs: number\n force?: boolean\n /** Short window honoured even under `force` — collapses back-to-back forced runs. */\n graceMs?: number\n task: () => Promise<void>\n}): Promise<void> {\n const run = async () => {\n // The grace window applies even under force; the long cooldown only when not forced.\n if (\n opts.graceMs !== undefined &&\n ranWithin(opts.cooldownKey, opts.graceMs)\n ) {\n return\n }\n if (!opts.force && ranWithin(opts.cooldownKey, opts.cooldownMs)) {\n return\n }\n try {\n await opts.task()\n } finally {\n stampNow(opts.cooldownKey)\n }\n }\n\n if (typeof navigator !== \"undefined\" && navigator.locks) {\n await navigator.locks.request(opts.lockName, { mode: \"exclusive\" }, run)\n } else {\n await run()\n }\n}\n\n/** True when `key`'s stored epoch-ms timestamp is within `windowMs` of now. */\nfunction ranWithin(key: string, windowMs: number): boolean {\n if (typeof window === \"undefined\" || !window.localStorage) {\n return false\n }\n const raw = window.localStorage.getItem(key)\n if (!raw) {\n return false\n }\n const last = Number(raw)\n return Number.isFinite(last) && Date.now() - last < windowMs\n}\n\n/** Record \"ran just now\" so other tabs in the window skip. */\nfunction stampNow(key: string): void {\n if (typeof window === \"undefined\" || !window.localStorage) {\n return\n }\n window.localStorage.setItem(key, String(Date.now()))\n}\n","// Copyright 2026 The Swarm Authors. All rights reserved.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * The sites this identity manager serves (docs/Site-Allow-List.md).\n *\n * The list is a text record on an ENS name, read over an Ethereum mainnet\n * JSON-RPC endpoint the build names, so adding a site is a change to the\n * record rather than a new upload. It is never read through a Swarm gateway:\n * the record decides who may ask for an identity, and a gateway that serves\n * this code is exactly the party it must not have to trust.\n *\n * Everything that goes wrong refuses. No endpoint, an endpoint that does not\n * answer, a name with no resolver, an empty or unreadable record, and no fresh\n * cached copy each leave the list unavailable, and an unavailable list allows\n * nothing.\n */\n\nimport { keccak_256 } from \"@noble/hashes/sha3\"\nimport { z } from \"zod\"\nimport { STORAGE_KEY_SITE_ALLOW_LIST } from \"../types\"\nimport { isCleanSiteOrigin } from \"./chat-binding\"\nimport { hexToUint8Array, uint8ArrayToHex } from \"./hex\"\nimport { CHAIN_READ_TIMEOUT_MS, jsonRpcCall } from \"./json-rpc\"\n\n/** The name whose text record holds the list. */\nexport const DEFAULT_SITE_ALLOW_LIST_ENS_NAME = \"solarpunkid.eth\"\n/** The text record key, in the reverse-domain form ENSIP-5 asks of a vendor key. */\nexport const DEFAULT_SITE_ALLOW_LIST_RECORD_KEY = \"buzz.solarpunk.sites\"\n/**\n * How long a list read from the chain is trusted before it is read again. A\n * site removed from the record keeps working at most this long on a device\n * that read it before.\n */\nexport const SITE_ALLOW_LIST_CACHE_MS = 10 * 60 * 1000\n\n/** The ENS registry, at the same address on every chain ENS is deployed to. */\nexport const ENS_REGISTRY_ADDRESS = \"0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e\"\n\n/**\n * The longest chain answer read, in bytes. A record of a thousand sites is a\n * few tens of kilobytes, and an answer of millions of bytes overflows the\n * stack of the hex test, so anything longer is refused before it is read.\n */\nconst MAX_CHAIN_ANSWER_BYTES = 128 * 1024\n\nconst LOG_PREFIX = \"[SiteAllowList]\"\nconst ENTRY_SEPARATOR = /[\\s,]+/\nconst ENS_LABEL = /^[a-z0-9-]+$/\nconst MIN_ENS_LABELS = 2\nconst WORD_BYTES = 32\nconst ADDRESS_BYTES = 20\nconst SELECTOR_BYTES = 4\nconst HEX_RADIX = 16\nconst HEX_PREFIX_LENGTH = 2\n\nconst RESOLVER_SELECTOR = selector(\"resolver(bytes32)\")\nconst TEXT_SELECTOR = selector(\"text(bytes32,string)\")\n\n/** Why a site was refused, so a caller can word its own screen. */\nexport type SiteRefusal = \"not-clean\" | \"not-listed\" | \"list-unavailable\"\n\nexport type SiteCheck =\n | { allowed: true }\n | { allowed: false; reason: SiteRefusal; message: string }\n\nexport interface SiteAllowList {\n /** Whether `origin`, exactly as the browser reports it, may be served. */\n check(origin: string): Promise<SiteCheck>\n}\n\nexport interface SiteAllowListSettings {\n /** An Ethereum mainnet JSON-RPC endpoint. Unset, every site is refused. */\n rpcUrl: string | undefined\n ensName: string\n recordKey: string\n /**\n * Origins allowed without the chain, for local work. Local http is accepted\n * here. Only a development build passes any.\n */\n devOrigins?: readonly string[]\n /** Where the read list is kept between loads. Unset, it is kept in memory. */\n storage?: Pick<Storage, \"getItem\" | \"setItem\">\n now?: () => number\n}\n\nconst CachedSiteAllowListSchema = z.object({\n ensName: z.string(),\n recordKey: z.string(),\n origins: z.array(z.string()),\n fetchedAt: z.number(),\n})\n\ntype CachedSiteAllowList = z.infer<typeof CachedSiteAllowListSchema>\n\nfunction selector(signature: string): string {\n return uint8ArrayToHex(\n keccak_256(new TextEncoder().encode(signature)).slice(0, SELECTOR_BYTES),\n )\n}\n\n/** The EIP-137 namehash of a name already in its normalized form. */\nexport function ensNamehash(name: string): string {\n let node: Uint8Array = new Uint8Array(WORD_BYTES)\n const labels = name === \"\" ? [] : name.split(\".\")\n for (const label of labels.reverse()) {\n const joined = new Uint8Array(WORD_BYTES * 2)\n joined.set(node)\n joined.set(keccak_256(new TextEncoder().encode(label)), WORD_BYTES)\n node = keccak_256(joined)\n }\n return `0x${uint8ArrayToHex(node)}`\n}\n\n/**\n * Whether `name` is already in the form ENS normalization would give it, for\n * the plain names this setting is meant to hold. Anything else is refused\n * rather than normalized here, since a wrong normalization reads the record\n * of a different name.\n */\nfunction isPlainEnsName(name: string): boolean {\n const labels = name.split(\".\")\n return (\n labels.length >= MIN_ENS_LABELS &&\n labels.every((label) => ENS_LABEL.test(label))\n )\n}\n\n/**\n * The origins a record names. Entries are separated by white space or commas.\n * An entry that is not a clean https origin is ignored and logged, so one bad\n * entry cannot take the whole list down, and no entry is ever read as a\n * pattern.\n */\nexport function parseSiteAllowList(record: string): string[] {\n const origins: string[] = []\n for (const entry of record.split(ENTRY_SEPARATOR)) {\n if (entry === \"\") {\n continue\n }\n if (isCleanSiteOrigin(entry)) {\n origins.push(entry)\n } else {\n console.warn(\n `${LOG_PREFIX} Ignored an entry that is not a clean https origin:`,\n entry,\n )\n }\n }\n return origins\n}\n\nfunction word(value: number): string {\n return value.toString(HEX_RADIX).padStart(WORD_BYTES * 2, \"0\")\n}\n\nfunction encodeTextCall(node: string, key: string): string {\n const keyBytes = new TextEncoder().encode(key)\n const keyHex = uint8ArrayToHex(keyBytes)\n const paddedLength = Math.ceil(keyBytes.length / WORD_BYTES) * WORD_BYTES * 2\n const head = word(WORD_BYTES * 2)\n return `0x${TEXT_SELECTOR}${node.slice(2)}${head}${word(keyBytes.length)}${keyHex.padEnd(paddedLength, \"0\")}`\n}\n\nfunction resultBytes(result: unknown, subject: string): Uint8Array {\n if (\n typeof result === \"string\" &&\n result.length > HEX_PREFIX_LENGTH + MAX_CHAIN_ANSWER_BYTES * 2\n ) {\n throw new Error(\n `${subject} answered ${result.length} characters, longer than ${MAX_CHAIN_ANSWER_BYTES} bytes of hex data.`,\n )\n }\n if (typeof result !== \"string\" || !/^0x([0-9a-fA-F]{2})*$/.test(result)) {\n throw new Error(`${subject} answered something that is not hex data.`)\n }\n return hexToUint8Array(result.slice(2))\n}\n\nfunction readWord(bytes: Uint8Array, offset: number): bigint {\n if (offset + WORD_BYTES > bytes.length) {\n throw new Error(\"The answer ends inside a word.\")\n }\n return BigInt(\n `0x${uint8ArrayToHex(bytes.slice(offset, offset + WORD_BYTES))}`,\n )\n}\n\nfunction decodeAddress(result: unknown): string | undefined {\n const bytes = resultBytes(result, \"The registry\")\n if (bytes.length !== WORD_BYTES) {\n throw new Error(\"The registry answered a resolver that is not one word.\")\n }\n const padding = bytes.slice(0, WORD_BYTES - ADDRESS_BYTES)\n if (padding.some((byte) => byte !== 0)) {\n throw new Error(\"The registry answered a resolver that is not an address.\")\n }\n const address = bytes.slice(WORD_BYTES - ADDRESS_BYTES)\n return address.every((byte) => byte === 0)\n ? undefined\n : `0x${uint8ArrayToHex(address)}`\n}\n\nfunction decodeString(result: unknown): string {\n const bytes = resultBytes(result, \"The resolver\")\n const offset = readWord(bytes, 0)\n if (offset > BigInt(bytes.length)) {\n throw new Error(\"The resolver answered a string offset past its end.\")\n }\n const start = Number(offset)\n const length = readWord(bytes, start)\n const end = BigInt(start + WORD_BYTES) + length\n if (end > BigInt(bytes.length)) {\n throw new Error(\"The resolver answered a string longer than its answer.\")\n }\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(\n bytes.slice(start + WORD_BYTES, Number(end)),\n )\n}\n\nasync function ethCall(\n rpcUrl: string,\n to: string,\n data: string,\n): Promise<unknown> {\n return jsonRpcCall(rpcUrl, \"eth_call\", [{ to, data }, \"latest\"], {\n timeoutMs: CHAIN_READ_TIMEOUT_MS,\n })\n}\n\n/**\n * The text record `key` of `name`, through the resolver the registry names.\n * @throws when the name has no resolver or the answers do not decode.\n */\nasync function readEnsText(\n rpcUrl: string,\n name: string,\n key: string,\n): Promise<string> {\n const node = ensNamehash(name)\n const resolver = decodeAddress(\n await ethCall(\n rpcUrl,\n ENS_REGISTRY_ADDRESS,\n `0x${RESOLVER_SELECTOR}${node.slice(2)}`,\n ),\n )\n if (!resolver) {\n throw new Error(`${name} has no resolver.`)\n }\n return decodeString(\n await ethCall(rpcUrl, resolver, encodeTextCall(node, key)),\n )\n}\n\nfunction refusal(reason: SiteRefusal, origin: string): SiteCheck {\n const messages: Record<SiteRefusal, string> = {\n \"not-clean\": `${origin} is not a plain https site address, and Swarm ID serves only the sites on its allow list.`,\n \"not-listed\": `${origin} is not on Swarm ID's allow list of sites.`,\n \"list-unavailable\": `Swarm ID could not read its allow list of sites, so it refused ${origin}. It serves no site until the list can be read.`,\n }\n return { allowed: false, reason, message: messages[reason] }\n}\n\nfunction cleanDevOrigins(origins: readonly string[]): Set<string> {\n const clean = new Set<string>()\n for (const origin of origins) {\n if (isCleanSiteOrigin(origin, { allowLocalHttp: true })) {\n clean.add(origin)\n } else {\n console.warn(\n `${LOG_PREFIX} Ignored a dev origin that is not a clean origin:`,\n origin,\n )\n }\n }\n return clean\n}\n\n/**\n * The allow list for one setting of name, record key and endpoint. One\n * instance per page: it shares a read in flight between the checks that wait\n * on it.\n */\nexport function createSiteAllowList(\n settings: SiteAllowListSettings,\n): SiteAllowList {\n const now = settings.now ?? Date.now\n const devOrigins = cleanDevOrigins(settings.devOrigins ?? [])\n let memory: CachedSiteAllowList | undefined\n let inFlight: Promise<readonly string[] | undefined> | undefined\n\n const isFresh = (entry: CachedSiteAllowList | undefined): boolean => {\n if (\n !entry ||\n entry.ensName !== settings.ensName ||\n entry.recordKey !== settings.recordKey\n ) {\n return false\n }\n const age = now() - entry.fetchedAt\n return age >= 0 && age < SITE_ALLOW_LIST_CACHE_MS\n }\n\n const loadStored = (): CachedSiteAllowList | undefined => {\n try {\n const raw = settings.storage?.getItem(STORAGE_KEY_SITE_ALLOW_LIST)\n if (!raw) {\n return undefined\n }\n const parsed = CachedSiteAllowListSchema.safeParse(JSON.parse(raw))\n return parsed.success ? parsed.data : undefined\n } catch {\n return undefined\n }\n }\n\n const store = (entry: CachedSiteAllowList): void => {\n memory = entry\n try {\n settings.storage?.setItem(\n STORAGE_KEY_SITE_ALLOW_LIST,\n JSON.stringify(entry),\n )\n } catch (error) {\n console.warn(`${LOG_PREFIX} Could not keep the list in storage:`, error)\n }\n }\n\n const readFromChain = async (): Promise<readonly string[] | undefined> => {\n if (!settings.rpcUrl) {\n console.warn(\n `${LOG_PREFIX} No Ethereum endpoint is set, so no site is allowed.`,\n )\n return undefined\n }\n if (!isPlainEnsName(settings.ensName)) {\n console.warn(\n `${LOG_PREFIX} The ENS name is not a plain lower-case name:`,\n settings.ensName,\n )\n return undefined\n }\n try {\n const record = await readEnsText(\n settings.rpcUrl,\n settings.ensName,\n settings.recordKey,\n )\n const origins = parseSiteAllowList(record)\n if (origins.length === 0) {\n console.warn(\n `${LOG_PREFIX} The record names no site, so no site is allowed.`,\n )\n return undefined\n }\n store({\n ensName: settings.ensName,\n recordKey: settings.recordKey,\n origins,\n fetchedAt: now(),\n })\n return origins\n } catch (error) {\n console.warn(\n `${LOG_PREFIX} Could not read the list, so no site is allowed:`,\n error,\n )\n return undefined\n }\n }\n\n const currentList = async (): Promise<readonly string[] | undefined> => {\n if (isFresh(memory)) {\n return memory!.origins\n }\n const stored = loadStored()\n if (isFresh(stored)) {\n memory = stored\n return stored!.origins\n }\n inFlight ??= readFromChain().finally(() => {\n inFlight = undefined\n })\n return inFlight\n }\n\n return {\n async check(origin) {\n if (devOrigins.has(origin)) {\n return { allowed: true }\n }\n if (!isCleanSiteOrigin(origin)) {\n return refusal(\"not-clean\", origin)\n }\n const origins = await currentList()\n if (!origins) {\n return refusal(\"list-unavailable\", origin)\n }\n return origins.includes(origin)\n ? { allowed: true }\n : refusal(\"not-listed\", origin)\n },\n }\n}\n"],"names":["Size","constructor","bytes","this","Math","ceil","Error","fromBytes","fromKilobytes","kilobytes","fromMegabytes","megabytes","fromGigabytes","gigabytes","parseFromString","size","Numbers","makeStorage","toBytes","toGigabytes","toFormattedString","convertBytes","represent","getStampCost","depth","amount","BZZ","fromPLUR","BigInt","getAmountForDuration","duration","pricePerBlock","blockTime","toSeconds","getDepthForSize","encryption","erasureCodeLevel","coreGetDepthForSize","mapPostageBatch","raw","batchTTL","normalizeBatchTTL","Duration","fromSeconds","effectiveBytes","getStampEffectiveBytes","usage","utilizationRatio","getStampUsage","utilization","bucketDepth","batchID","BatchId","usable","label","asNumberString","blockNumber","immutableFlag","usageText","round","remainingSize","theoreticalSize","getStampTheoreticalBytes","calculateSize","redundancyLevel","calculateRemainingSize","readFileHeaders","headers","name","header","BeeError","dispositionMatch","match","length","readContentDispositionFilename","tagUid","parseInt","readTagUid","contentType","undefined","prepareRequestHeaders","stamp","nullableOptions","envelope","value","z.object","safeParse","success","issuer","index","signature","timestamp","batchId","isEnvelopeWithBatchId","toHex","coreConvertEnvelopeToMarshaledStamp","options","String","act","z.boolean","data","pin","encrypt","tag","deferred","redundancyStrategy","fallback","timeoutMs","indexDocument","errorDocument","actPublisher","PublicKey","toCompressedHex","actHistoryAddress","Reference","actTimestamp","gasPrice","gasLimit","endpoint","async","download","requestOptions","reference","response","http","responseType","url","Uint8Array","unmarshalSingleOwnerChunk","address","coreUnmarshalSingleOwnerChunk","e","message","uploadSingleOwnerChunk","chunk","Bytes","concat","span","toUint8Array","payload","owner","identifier","method","params","sig","UploadResultBody","parse","makeTagUid","historyAddress","Optional","of","empty","socAPI.upload","uploadSingleOwnerChunkData","signer","PrivateKey","Identifier","makeContentAddressedChunk","toSingleOwnerChunk","downloadSingleOwnerChunk","ownerAddress","EthAddress","makeSOCAddress","chunkAPI.download","balancesEndpoint","consumedEndpoint","Balance","context","getAll","GetAllBalancesResponse","balanceApi.getBalances","getRequestOptionsForCall","getPeer","peer","PeerAddress","GetPeerBalanceResponse","balanceApi.getPeerBalance","getAllPastDueConsumption","consumedApi.getPastDueConsumptionBalances","getAllPastDueConsumptionForPeer","consumedApi.getPastDueConsumptionPeerBalance","chequebookEndpoint","Cheque","getAllLatest","GetLastChequesResponse","api.getLastCheques","getAllLatestForPeer","GetLastChequesForPeerResponse","api.getLastChequesForPeer","getLastCashoutAction","GetLastCashoutActionResponse","api.getLastCashoutAction","cashoutLast","TransactionOptionsSchema","TransactionHashResponse","transactionHash","api.cashoutLastCheque","Chequebook","getAddress","GetChequebookAddressResponse","api.getChequebookAddress","getBalance","GetChequebookBalanceResponse","api.getChequebookBalance","deposit","amountString","toPLURString","min","gasPriceString","api.depositTokens","withdraw","api.withdrawTokens","Chunk","upload","isSOC","UploadOptionsSchema","Span","LENGTH","BeeArgumentError","CHUNK_SIZE","Signature","api.upload","ref","DownloadOptionsSchema","api.download","saveRecursively","node","bee","postageBatchId","fork","forks","values","uploadResult","ifPresent","metadata","result","marshal","selfAddress","loadRecursively","downloadOptions","loaded","CoreMantarayNode","unmarshalFromData","targetAddress","path","prefix","parent","MantarayNode","core","obfuscationKey","type","fullPath","fullPathString","getRootMetadata","find","getDocsMetadata","resolveFeed","topic","feed","fetchLatestUpdate","unmarshal","addFork","removeFork","calculateSelfAddress","findClosest","current","collect","nodes","collectAndMap","determineType","totalChunks","fileSize","chunksAtLevel","chunkCount","assertCollection","Array","isArray","every","entry","isCollection","makeFilePath","file","webkitRelativePath","replace","TypeError","mimes","aac","abw","ai","arc","avi","azw","bin","bz","bz2","csh","css","csv","doc","dll","dmg","eot","epub","gif","htm","html","ico","ics","jar","jpeg","jpg","js","json","mid","midi","mp2","mp3","mp4","mpa","mpe","mpeg","mpkg","odp","ods","odt","oga","ogv","ogx","otf","png","pdf","ppt","rar","rtf","sh","svg","swf","tar","tif","tiff","ts","ttf","txt","vsd","wav","weba","webm","webp","woff","woff2","xhtml","xls","xlsx","xml","xul","zip","maybeEnrichMime","mime","includes","TarStream","pieces","currentFileSize","output","reduce","acc","piece","newAcc","set","beginFile","longLink","i","substring","lastSlash","lastIndexOf","truncatedName","slice","splitPath","pathData","TextEncoder","encode","push","encoder","writeToBuffer","str","offset","fill","toString","padStart","checksum","createLongLinkHeader","padding","padEnd","floor","Date","getTime","createHeader","appendFile","endFile","end","uploadTar","collection","tarStream","item","fileArrayBuffer","writeTar","bzzEndpoint","uploadFile","uploadCollection","Collection","CollectionUploadOptionsSchema","api.uploadCollection","uploadFromFileList","fileList","from","map","makeCollectionFromFileList","uploadFromDirectory","dir","makeCollectionFromFS","stream","files","onUploadProgress","signal","aborted","queue","AsyncQueue","total","processed","uploadAndTrack","build","err","onBatch","batch","enqueue","mantaray","rootChunk","Promise","resolve","reject","tree","ChunkSplitter","reader","FileReader","onerror","error","readNextChunk","finalize","readAsArrayBuffer","onload","event","target","append","drain","filename","extension","Strings","parseFilename","hash","toLowerCase","Filename","NULL_ADDRESS","streamFilesImpl","streamFromDirectory","streamDirectoryImpl","hashDirectory","hashDirectoryImpl","peersEndpoint","Connectivity","checkConnection","api.checkConnection","isConnected","isGateway","IsGatewayResponse","gateway","api.isGateway","getNodeAddresses","GetNodeAddressesResponse","api.getNodeAddresses","getBlocklist","GetBlocklistResponse","peers","api.getBlocklist","getPeers","GetPeersResponse","api.getPeers","removePeer","api.removePeer","getTopology","GetTopologyResponse","api.getTopology","ping","api.ping","resource","stewardshipEndpoint","ResourceLocator","asString","z.string","Data","assertData","RedundantUploadOptionsSchema","bytes.upload","bytes.download","downloadReadable","bytes.downloadReadable","probe","contentLength","bytes.head","isRetrievable","IsRetrievableResponse","stewardship.isRetrievable","feedEndpoint","readFeedUpdateHeaders","feedIndex","feedIndexNext","FeedIndex","fetchLatestFeedUpdate","makeFeedIdentifier","fromBigInt","keccak256","findNextIndex","feedUpdate","BeeResponseError","updateFeedWithPayload","publicKey","wrappedChunk","uploadSingleOwnerChunkWithWrappedChunk","unmarshalContentAddressedChunk","dataString","fromUtf8","getFeedUpdateChunkReference","downloadFeedUpdate","hasTimestamp","soc","timestampBytes","fromSlice","Number","uint64ToNumber","downloadFeedUpdateAsCAC","makeFeedReader","update","next","downloadPayload","cac","toBigInt","downloadReference","probeFeed","makeFeedWriter","at","now","numberToUint64","updateFeedWithReference","uploadReference","uploadPayload","areAllSequentialFeedsUpdateRetrievable","chunkRetrievablePromises","count","updateReferences","getAllSequenceUpdateReferences","status","Objects","getDeep","isChunkRetrievable","all","Feed","makeReader","Topic","makeWriter","key","createManifest","feedTopic","feedOwner","createFeedManifest","File","assertFileData","FileUploadOptionsSchema","isFile","fileData","fileName","fileOptions","api.uploadFile","api.downloadFile","api.downloadFileReadable","granteeEndpoint","Grantee","create","grantees","publicKeys","x","body","GranteesResultBodyResponse","statusText","historyref","api.createGrantees","get","GetGranteesBodyResponse","api.getGrantees","patch","history","historyRef","add","revoke","api.patchGrantees","prepareWebsocketData","ArrayBuffer","Blob","Response","arrayBuffer","Messaging","cancelled","WeakSet","pssSend","recipient","recipientKey","pssApi.send","pssSubscribe","handler","PssMessageHandlerSchema","ws","openWebSocket","subscription","cancel","makeCancel","onmessage","onMessage","isCancelled","onError","onclose","onClose","pssReceive","timeoutMsec","timeout","clearTimeout","setTimeout","gsocMine","targetOverlay","proximity","overlay","id","numberToUint256","socAddress","Binary","gsocSend","gsocSubscribe","ethAddress","GsocMessageHandlerSchema","wsUrl","System","whereAmI","WebSocket","has","terminate","close","PINNING_ENDPOINT","Pin","pinApi.pin","remove","pinApi.unpin","references","GetAllPinsResponse","pinApi.getAllPins","pinApi.getPin","reuploadData","stewardshipApi.reupload","settlementsEndpoint","Settlement","GetSettlementsResponse","api.getSettlements","GetAllSettlementsResponse","api.getAllSettlements","Soc","bind","STAKE_ENDPOINT","Stake","GetStakeResponse","stakedAmount","stakeApi.getStake","getWithdrawable","GetWithdrawableStakeResponse","withdrawableAmount","stakeApi.getWithdrawableStake","TxHashResponse","txHash","stakeApi.depositStake","withdrawSurplus","stakeApi.withdrawSurplusStake","migrate","stakeApi.migrateStake","getRedistributionState","GetRedistributionStateResponse","redistributionApi.getRedistributionState","STAMPS_ENDPOINT","batchesEndpoint","Stamp","PostageBatchOptionsSchema","STAMPS_DEPTH_MIN","STAMPS_DEPTH_MAX","chainState","getChainState","minimumAmount","currentPrice","immutable","BatchIdResponse","stampApi.createPostageBatch","waitForUsable","waitForUsableTimeout","updateLabel","stampApi.updateLabel","calculateTopUpForBZZ","bzz","network","toPLURBigInt","amountBigInt","getStampDuration","topUp","stampApi.topUpBatch","dilute","depthNumber","z.number","int","max","stampApi.diluteBatch","GetPostageBatchResponse","stampApi.getPostageBatch","getGlobal","GetGlobalPostageBatchResponse","batchesApi.getGlobalPostageBatch","getBuckets","GetPostageBatchBucketsResponse","stampApi.getPostageBatchBuckets","GetAllPostageBatchesResponse","stamps","stampApi.getAllPostageBatches","getAllGlobal","GetGlobalPostageBatchesResponse","batches","batchesApi.getAllGlobalPostageBatches","time","sleepMillis","Status","GetDebugStatusResponse","api.getDebugStatus","getHealth","GetHealthResponse","api.getHealth","getReadiness","GetReadinessResponse","api.getReadiness","getNodeInfo","GetNodeInfoResponse","api.getNodeInfo","isSupportedExactVersion","version","SUPPORTED_BEE_VERSION_EXACT","isSupportedApiVersion","apiVersion","getMajorSemver","SUPPORTED_API_VERSION","getVersions","supportedBeeVersion","supportedBeeApiVersion","beeVersion","beeApiVersion","getReserveState","GetReserveStateResponse","api.getReserveState","GetChainStateResponse","api.getChainState","Storage","buy","getCost","extend","depthDelta","multiplier","additionalAmount","currentAmount","amountDelta","isZero","transactionId","extendSize","delta","extendDuration","getExtensionCost","currentCost","minus","getSizeExtensionCost","getDurationExtensionCost","rename","newLabel","Tag","TagSchema","api.createTag","AllTagsOptionsSchema","limit","GetAllTagsResponse","tags","api.getAllTags","uid","api.getTag","api.deleteTag","api.updateTag","transactionsEndpoint","Transaction","GetAllTransactionsResponse","pendingTransactions","api.getAllTransactions","TransactionId","GetTransactionResponse","api.getTransaction","rebroadcast","api.rebroadcastTransaction","api.cancelTransaction","WALLET_ENDPOINT","Wallet","GetWalletBalanceResponse","api.getWalletBalance","withdrawBZZ","WithdrawResponse","api.withdrawBZZ","withdrawDAI","dai","DAI","fromWei","toWeiString","api.withdrawDAI","BAD_PORTS","assertBeeUrl","urlObject","URL","protocol","isValidBeeUrl","port","portNumber","isNaN","isValidBeePort","Bee","endsWith","stripLastSlash","baseURL","onRequest","httpAgent","httpsAgent","balance","settlement","transaction","stake","connectivity","wallet","storage","chequebook","cheque","grantee","messaging","ChunkNamespace","FileNamespace","rawPayload","makeSingleOwnerChunk","coreMakeSingleOwnerChunk","calculateSingleOwnerChunkAddress","createEnvelope","PostEnvelopeBodyResponse","postEnvelope","rchash","anchor1","anchor2","RchashResponse","durationSeconds","BeeRequestOptionsSchema","deepMerge2","splitDataIntoChunks","chunks","BusConnectedAppSchema","ConnectedAppSchemaV1","omit","appSecret","connectedUntil","chatBinding","AccountDeltaMessageSchema","z.literal","snapshot","AccountStateSnapshotSchemaV1","connectedApps","z.array","DeviceIdSchema","LeaseRequestIdSchema","regex","BusMessageSchema","z.discriminatedUnion","UtilizationUpdateMessageSchema","accountId","fromDeviceId","requestId","partition","optional","BroadcastChannelTransport","local","handlers","Set","channelName","busChannelName","channel","BroadcastChannel","publish","postMessage","subscribe","delete","clear","AccountBus","transports","peerLeftHandlers","closed","transportUnsubscribers","flatMap","transport","listenTo","removers","dispatch","onPeerLeft","peerId","console","localOnly","addTransport","filter","t","u","unsubscribe","VersionedStorageSchema","nonnegative","z.unknown","LocalStorageAdapter","getItem","window","localStorage","setItem","removeItem","SAME_WINDOW_WRITE_EVENT","writeQueues","Map","VersionedStorageManager","listeners","listener","boundStorageHandler","setupStorageEventListener","cleanupStorageEventListener","reloadAndNotify","addEventListener","boundSameWindowHandler","detail","source","noRepublish","removeEventListener","notifySameWindow","dispatchEvent","CustomEvent","change","sealing","prime","then","notifyListeners","load","loggerName","stored","parsed","JSON","versioned","open","parser","parsers","storedData","save","serialized","serialize","write","seal","mutate","serializer","records","wrapped","currentVersion","stringify","task","run","navigator","locks","started","request","mode","withStorageWriteLock","settled","catch","SEALED_VALUE_PREFIX","KEY_STORE","WRAPPING_KEY_ID","openKeyDatabase","fail","timer","indexedDB","onupgradeneeded","objectStoreNames","contains","createObjectStore","onsuccess","onblocked","indexedDbWrappingKeyStore","db","store","objectStore","getOrAdd","candidate","tx","kept","read","oncomplete","onabort","memoryWrappingKeyStore","keyStore","wrappingKey","currentKeyStore","standsIn","chooseKeyStore","getWrappingKey","existing","crypto","subtle","generateKey","openedBySealed","sealedByPlain","pendingOpens","isSealedValue","startsWith","sealValue","plaintext","known","iv","getRandomValues","ciphertext","sealed","uint8ArrayToHex","unavailableThisPage","openOne","hexToUint8Array","TextDecoder","decode","decrypt","isSealedValueUnavailable","primeSealedValues","opens","pending","finally","openedValue","LIB_ERROR_CODES","AppSliceFullError","PartitionContendedError","PartitionLeaseLostError","ReservedSlotError","TimeoutError","LIB_ERROR_REASONS","refusedPath","pathname","toWireError","SwarmIdError","toWire","code","wire","beeMessage","beeMessageOf","responseBody","reason","ZodError","test","ChunkUploadStream","beeUrl","pendingAcks","concurrency","getConcurrency","buildWsUrl","binaryType","onopen","msg","errorMessage","rejectAllPending","shift","uploadChunk","stampedData","readyState","OPEN","waitForAck","send","pop","base","oldest","originalResolve","originalReject","tagsUnsupported","WeakMap","tryCreateTag","unsupported","isStatus","warn","SocUploadError","super","isStamperTarget","isSubsidisedTarget","makeStampFn","stamper","workerPool","stampChunkData","uploadStampedChunkViaHttp","chunkData","beeHeaders","instance","uploadChunkViaSubsidisedGatewayInternal","gatewayUrl","normalizeUrl","fetch","ok","errorText","text","uploadChunksViaHttp","onUploaded","DEFAULT_UPLOAD_CONCURRENCY","nextIndex","worker","uploadStampedChunkViaWebSocket","stamped","concatBytes","convertEnvelopeToMarshaledStamp","uploadChunksViaWebSocket","workerCount","buildPlainMerkleTree","chunkRefs","uploadIntermediate","intermediateRefs","refs","forEach","idx","totalSpan","sum","buildEncryptedMerkleTree","intermediateChunks","spanBytes","encrypter","newChunkEncrypter","encryptedSpan","encryptedData","encryptChunk","encryptedChunkData","calculateChunkAddress","rootRef","uploadData","encryptionKey","payloads","encryptedChunks","encryptedChunkRefs","makeEncryptedContentAddressedChunk","encryptChunkPayloads","chunksToUpload","processedChunks","uploadedChunkAddresses","onChunkUploaded","onProgress","uploadOptions","chunkAddresses","wsStream","webSocket","httpConcurrency","uploadDataEncrypted","chunkPayloads","reportProgress","rootReference","uploadDataPlain","uploadSOC","chunkAddress","socBody","encryptionKeyResult","encryptedChunk","toSign","sign","socAddressBytes","stampHex","makePartitionLockIdentifier","lockSocAddress","lockSocBucket","addr","SYNC_READ_TIMEOUT_MS","LEASE_TTL_MS","INTENT_EPOCH_MS","NUM_BUCKETS","BUCKET_DEPTH","PARTITION_COUNT","LEASE_REFRESH_MS","getChunkLayout","batchDepth","counterByteSize","bucketsPerChunk","numUtilizationChunks","toBucket","getChunkIndexForBucket","bucketIndex","extractChunk","dataCounters","chunkIndex","startBucket","endBucket","chunkCounters","serializeUint16Array","serializeUint32Array","mergeChunk","arr","Uint32Array","view","DataView","buffer","byteOffset","byteLength","getUint16","deserializeUint16Array","getUint32","deserializeUint32Array","DirtyChunkTracker","dirtyChunks","markDirty","markClean","getDirtyChunks","sort","a","b","hasDirtyChunks","deriveUtilizationChunkKey","swarmEncryptionKey","nonce","domain","batchIdBytes","indexBytes","setUint32","nonceBytes","cryptoKey","importKey","chooseUtilizationChunkKey","args","claimedBuckets","priorNonce","bucket","claimedBucketsForCleanChunks","state","claimed","meta","dirty","contentHash","uploadUtilizationChunk","cacReference","v","setUint16","calculateMaxSlotsPerBucket","pow","hasBucketCapacity","dataCounter","partitionCount","partitionCapacity","dataSlot","j","slotsPerBucket","MIN_USABLE_BATCH_DEPTH","updateAfterWrite","dataChunks","cache","cachedChunks","getAllChunks","cached","lastUpload","lastAccess","lastSync","defaultCounters","defaultChunkData","c","loadUtilizationState","tracker","slot","UtilizationAwareStamper","buckets","utilizationState","partitionCountValue","currentPartition","getLocalCounter","getLockSocBuckets","lockSocs","getProtectedBuckets","stateBuckets","protectedStateBuckets","getCleanChunkBuckets","setProtectedStateBuckets","getMetadata","putMetadata","syncedReferences","stateChunkBuckets","bindPartition","opts","localCounter","leaseStale","leaseValidUntil","bindLockSocs","socs","markReservedUtilizationChunk","reservedUtilizationChunks","clearReservedUtilizationChunks","reserveIntentSocSlot","intentSoc","addressHex","clearIntentSocSlot","withIntentSocSlot","fn","previous","intentSocLock","release","unbindPartition","invalidateLease","setLeaseValidUntil","ms","leaseLocallyLapsed","dirtyBuckets","maxSlot","privateKey","initializeBatchUtilization","p","Object","entries","swarmEncryptionKeyHex","backupKeyHex","deriveSecret","BACKUP_KEY_LABEL","backupOwner","_","reservedSlotFor","lockSoc","equals","utilizationSlot","requireLiveLease","assignSlot","reservedSlot","timestampMs","stampAtSlot","stampDataChunk","appSliceCapacity","isOwnDataSlot","lane","restampDataChunk","laneCapacity","getState","flush","dirtyChunkIndexes","sortedDirtyChunkIndexes","resolved","putChunk","getUtilizationState","applyUtilizationUpdate","buildLeaseLocalCounter","getSyncedReference","setSyncedReference","referenceHex","getBucketUpdatesForBroadcast","parseAccountsV1","format","accounts","record","LocalAccountSchemaV1","account","isSignedOutAccount","devices","serializeSyncedAccount","createdAt","derivationKey","defaultPostageStampBatchID","app","portableConnectedApp","serializeConnectedApp","postageStamps","serializePostageStamp","settings","accountNameAt","defaultStampAt","settingsAt","lastModified","serializeAccount","access","encryptedSeed","encryptedState","storageWarning","soonestDriveExpiry","signedOutAt","appUrl","appName","lastConnectedAt","appIcon","appDescription","postageStampBatchID","postageStampBatchIDAt","revokedAt","disconnectedAt","_appSecret","_connectedUntil","rest","signerKey","nameUpdatedAt","exists","updatedAt","deletedAt","isRecord","recordsOf","holdsKeys","storedKeys","openStoredKeys","opened","some","signedOutForUnreadableKeys","recordToStore","storedById","keepsUnavailableKeys","holdsUnavailableKeys","sealsWithPageKey","transform","mapStoredKeys","accountKeySealing","createAccountsStorageManager","STORAGE_KEY_ACCOUNTS","createNetworkSettingsStorageManager","STORAGE_KEY_NETWORK_SETTINGS","NetworkSettingsSchemaV1","parseNetworkSettingsV1","beeNodeUrl","gnosisRpcUrl","serializeNetworkSettings","accountToStateSnapshot","accountName","accountDeltaSnapshot","accountDeltaFromSnapshot","input","_chatBinding","restoreLocalSessionFields","merged","mine","ours","deriveBusContext","deriveAesGcmKey","PRESENCE_INTERVAL_MS","PresenceTracker","seen","observe","deviceId","nowMs","forgetPeer","liveDeviceIds","cutoff","to","ServerMessageSchema","encryptEnvelope","encoded","ciphertextBuffer","combined","binary","byte","fromCharCode","btoa","uint8ArrayToBase64","decryptEnvelope","ciphertextBase64","base64","atob","charCodeAt","base64ToUint8Array","plaintextBuffer","SignalPayloadSchema","kind","sdp","newPeerState","remoteDescriptionSet","pendingCandidates","SignalingTransport","reconnectDelayMs","pendingDeliveries","connect","peerCount","delivery","deliver","reconnectTimer","allSettled","shutdown","connection","socket","sendToServer","handleServerMessage","scheduleReconnect","jittered","random","dataChannel","initiatePeerConnection","receiveEnvelope","handleSignal","createPeerConnection","adoptConnection","adoptDataChannel","createDataChannel","forwardIceCandidates","offer","createOffer","setLocalDescription","sendSignal","ondatachannel","setRemoteDescription","flushPendingCandidates","answer","createAnswer","addIceCandidate","onicecandidate","toJSON","DebouncedUtilizationUploader","delay","pendingUploads","defaultDelay","scheduleUpload","uploadFn","actualDelay","timerId","promise","mergedTracker","flushAll","batchIds","keys","cancelAll","getPendingCount","hasPending","AES_GCM","PBKDF2_ITERATIONS","SEED_ENCRYPTION_INFO","randomSalt","salt","encryptSeed","decryptSeed","payloadHex","deriveKeyFromSecret","ikm","info","deriveKey","deriveKeyFromPrf","prfOutput","deriveKeyFromSignature","deriveKeyFromPassword","password","iterations","PASSWORD_KDF_ITERATIONS","CHUNKS_STORE","METADATA_STORE","UtilizationStoreDB","onversionchange","deleteObjectStore","keyPath","createIndex","unique","getChunk","touchChunk","entryWithAccess","put","clearBatch","chunksStore","metadataStore","chunksRequest","openCursor","cursor","continue","ownerOf","prepareChunkToStamp","SPAN_SIZE","MIN_PAYLOAD_SIZE","MAX_PAYLOAD_SIZE","requireChunkData","contentAddress","internalOwners","marshalStamp","APP_BUCKET_SHARE","AppStampUsageSchema","windowStart","windowCount","z.record","windowIsCurrent","limitRefusal","appRestampRefusal","appStampRefusal","refusal","cap","recordAppRestamp","recordAppStamp","SocSlotSchema","SocRestampSlotsSchema","positive","slots","emptySocRestampSlots","parseSocRestampSlots","POOL_TERMINATED_MESSAGE","StampWorkerPool","workers","nextWorker","nextId","height","nextBucketSlot","signerKeyHex","blob","workerUrl","createObjectURL","handles","initPromises","Worker","handle","signatureHex","initPromise","origHandler","origErrorHandler","issuerHex","revokeObjectURL","indexView","setBigUint64","dispatchSign","manifestEncryption","content","manifest","encryptManifest","stampsReachableByApp","reachable","stampRefusal","remaining","remainingLifespanSeconds","isStampExpired","measuredAt","mergeDevicesList","remote","recency","d","removedAt","lastSignedInAt","nameRecency","winner","loser","mergeConnectedApps","keyOf","stampRecency","withStamp","newerChatBinding","mergePostageStamps","s","withBatchWriteLock","batchIdHex","op","lockName","NO_HOLDER_DEVICE_ID","makeDeviceTiebreaker","deviceHomePartition","compareGenerations","tiebreaker","readPartitionLock","backupSigner","downloadEncryptedSOC","PartitionLockPayloadSchemaV1","debug","writePartitionLock","acquirePartitionLock","wait","sleep","readBounded","withTimeout","readTimeoutMs","skipInitialRead","holderDeviceId","leasedUntil","outcome","shouldAbort","ourGeneration","generation","ourPayload","acquiredAt","ttlMs","guardMs","verified","INTENT_READ_TIMEOUT_MS","INTENT_LIVENESS_GRACE_MS","intentEpochBucket","makePartitionIntentIdentifier","epochBucket","intentSocAddress","makePartitionOccupancyIdentifier","partitionOccupancyAddress","writePartitionIntent","writeReservedPartitionSoc","isServerError","readPartitionSoc","attempt","PartitionIntentPayloadSchemaV1","logLabel","retryOn5xx","timedOut","readPartitionIntent","readPartitionOccupancy","ZERO_CHUNK_REF","PartitionStateSchemaV1","counters","z.instanceof","refine","makePartitionStateTopic","partitionBytes","STATE_POINTER_EPOCH_MS","POINTER_READ_TIMED_OUT","Symbol","statePointerEpochBucket","StatePointerPayloadSchemaV1","makeStatePointerIdentifier","bucketBytes","writeStatePointer","makeStatePointerAddress","statePointerAddress","readStatePointer","cur","holderLeasedUntilMs","hi","ordered","found","pointer","inconclusive","newer","downloadReferenceChunkRefs","refBytes","referenceChunk","downloadDataWithChunkAPI","expectedLength","bytesAllZero","pickReservedChunkKey","encrypted","isZeroRef","PartitionLease","holders","releasedPartitions","lastSeenLeasedUntil","unreadableLocks","pendingBeaconPublish","fromSwarmEncryptionKey","refreshFromSwarm","_unused","lock","self","summary","h","join","refreshHoldersFromPresence","applyPresence","readPresence","rivals","knownDeviceIds","currentBucket","intent","intentReadTimeoutMs","results","refreshHoldersFromOccupancy","applyOccupancy","readOccupancy","occupancy","isActive","holder","heldPartition","getHolders","pickFreeOrExpired","start","acquire","publishedReferences","publishedCounter","lastReferenceHex","lastFullPublishEpoch","isReadOnly","home","presenceReads","occupancyReads","speculativePointer","speculativeStatePointer","held","chosenPartition","soloClean","claimPartition","freshClaim","requireWriteContext","knownReference","stateResult","knownReferenceHex","lockUnreadable","prefetched","prefetchedPointer","pointerInconclusive","unchanged","readFailed","chunkReads","readPartitionState","hasRival","previousBucket","sweep","observed","rivalId","r","releasedGeneration","guardWindowMs","guardPollMs","beatenBy","polls","resolveIntentRound","intentGuardWindowMs","intentGuardPollMs","lockResult","publishPresenceBeacon","lockPayload","beaconPublishSettled","refresh","foreignBeaconBeatsUs","occupancyBeaconBeatsUs","beaconOutranksRelease","beaconGeneration","releasedGen","writePartitionOccupancy","flushState","skip","releasePayload","releasePartitionLock","publishState","currentEpoch","forceFullRepin","forceFull","published","previousReferences","previousCounter","reserve","incremental","allIndices","uploadIndices","bytesEqual","uploadSet","ptrMs","epoch","beaconBucket","prepared","picked","refPicked","writtenBuckets","publishStart","writePartitionState","seedReferenceHex","heartbeatStatePointer","retainedChunksCollideThisEpoch","fixed","refChunkAddr","bumpLocalLease","adoptIfLive","hydrate","isDisplaced","selfDeviceId","BatchWriteCoordinator","deps","readOnly","pendingAcquire","lastLeaseValidatedAt","lastLeaseActivityAt","activeUploadCount","pointerHeartbeatFailingSince","disposed","leaseEpoch","partitionLease","stamperRef","withWrite","lockAndFlush","ensureLease","ensureHeldForUpload","buildStamperTarget","ensureLeaseStillValid","startLease","teardown","partitionRefreshTimer","clearInterval","slotWait","wake","lease","writeLeaseCache","emitLeaseChange","onLeaseReleased","acquireWithSlotWait","pauseLeaseBackgroundWork","refreshKnownDeviceIds","readLeaseCache","adopted","seededReferenceHex","syncStamperLeaseDeadline","startRefreshTimer","onLeaseAcquired","deadline","maybeFree","rescansLeft","rescanWindowUsed","pollForSlot","scanPredatesAnnouncement","headroomMs","sleepUntilWoken","onSlotWait","leaseNearExpiry","signalLeaseLost","finalizeDemote","reasserted","setInterval","refreshTick","yieldIdleLease","confirmedDisplaced","confirmedHeld","yieldedPartition","notifySlotMaybeFree","canYieldForPeer","yieldForPeer","released","useWorkers","getWorkerPool","flushStamperState","onLeaseChange","MAX_LEVEL","EpochIndex","level","marshalBinary","last","childAt","after","diff","lca","left","newLevel","isLeft","AsyncEpochFinder","findAt","exactProbe","getEpochChunk","exact","traversed","findAtEpoch","findPreviousLeaf","currentBest","down","getEpochChunkWithMetadata","findAtWithMetadata","exactEpoch","findAtEpochWithMetadata","epochHash","spanStart","spanView","payloadLength","getBigUint64","payloadStart","timestampOffset","minAt","lowerBound","leaf","targetAt","BasicEpochUpdater","hints","calculateEpoch","uploadEpochChunk","lastEpoch","lastTimestamp","finder","getOwner","makeEpochIdentifier","createAsyncEpochFinder","SyncSequentialFinder","_at","_after","indexExists","makeSequentialIdentifier","makeSequentialAddress","AsyncSequentialFinder","syncFinder","BasicSequentialUpdater","makeIdentifier","setState","reset","identifierBytes","isNotFoundError","MAX_ROSTER_SCAN","knownRosterLength","ROSTER_LENGTH_STORAGE_PREFIX","rosterMemoKey","getKnownRosterLength","isInteger","bumpKnownRosterLength","capped","ROSTER_READ_TIMEOUT_MS","ROSTER_READ_INCONCLUSIVE","RosterScanInconclusiveError","rosterTopic","fromString","readRosterEntry","rosterIdentifier","probeSocSlot","DeviceSchemaV1","readRoster","memoKey","scanEnd","width","indices","isDevice","ensureInRoster","roster","device","Boolean","updater","accountKey","appendToRoster","SettingsSchema","appSessionDuration","ClockedSchema","DeviceStateSnapshotSchemaV1","PostageStampSchemaV1","accountPublicKey","accountCreatedAt","default","accountStateToDeviceView","m","stableAt","deviceStateTopic","writeDeviceState","updateResult","publishDeviceState","written","onChunksUploaded","readLatestDeviceState","deserializeDeviceState","pickLatest","foldAccount","views","rosterDevices","defaultBatch","MAX_SAFE_INTEGER","isFinite","DEVICE_ID_KEY","getOrCreateDeviceId","randomUUID","mergeDevices","currentDeviceId","deviceName","detectDeviceName","ua","userAgent","brandedBrowser","userAgentData","brands","brand","detectBrowserFromUAData","browser","DEVICE_COMPARED_FIELDS","KNOWN_DEVICE_MAX_AGE_MS","activeDeviceIds","maxAgeMs","createSyncAccount","accountsStore","postageStampsStore","utilizationStore","utilizationUploader","handleUtilizationUpdate","getAccount","defaultStamp","getStamp","deriveSwarmEncryptionKey","newUtilization","capacity","maxBucketUsage","calculateUtilization","updateStampUtilization","getStamper","uploadPromise","reservedBuckets","dirtyChunkIndices","isUtilStamper","chunkMetadata","cacReferenceHex","saveUtilizationState","startTime","performance","toISOString","snapshotResult","defaultStampBatchID","getAccountStateSnapshot","coordinator","log","thisDevice","addresses","toFixed","foldedToSyncedAccount","inFlightFolds","foldAccountFromSwarm","fold","freeze","doFoldAccountFromSwarm","checkRestoredSession","folded","hasNeverPublished","appOrigin","connectedAt","STORAGE_SHARED_KEY","markFirstPartyStorage","top","isStorageShared","POSTAGE_STAMP_CONTRACT_ADDRESS","resolvePostageStampContractAddress","rpcUrl","localContractAddress","hostname","isLocalRpcUrl","BATCH_ID_REGEX","RegExp","decodeUint","hex","fetchOnChainBatchStateResult","contractAddress","lower","normalizeBatchId","batchesResult","outPaymentResult","lastPriceResult","calls","jsonRpcBatch","call","CHAIN_READ_TIMEOUT_MS","ethCallBatch","words","toWords","HEX_WORD_LENGTH","normalisedBalance","lastUpdatedBlockNumber","decodeBatches","currentTotalOutPayment","lastPrice","calculateContractTTLSeconds","remainingBlocks","GNOSIS_BLOCK_TIME","fetchBatchTTLFromContract","fetchOnChainBatchState","DAY","DEFAULT_SESSION_DURATION","PUBLIC_KEY_COORD_SIZE","publicKeyFromPrivate","privKey","point","secp256k1","getPublicKey","y","deriveKeys","pubX","pubY","sharedSecret","pubKey","getSharedSecret","ecdhSharedSecret","lookupKeyInput","lookupKey","akdKeyInput","accessKeyDecryptionKey","counterModeEncrypt","numBlocks","counterInput","ctrHash","keyStream","blockStart","blockEnd","counterModeDecrypt","publicKeyFromCompressed","compressed","ProjectivePoint","fromHex","toRawBytes","compressPublicKey","serializeAct","encryptedAccessKey","deserializeAct","findEntryByLookupKey","UNCOMPRESSED_PUBLIC_KEY_SIZE","GRANTEE_LIST_KEY_SUFFIX","deriveGranteeListEncryptionKey","publisherPrivKey","serializeAndEncryptGranteeList","serializeGranteeList","granteeList","encryptGranteeList","decryptAndDeserializeGranteeList","encryptedList","decrypted","decryptGranteeList","granteeCount","deserializeGranteeList","MAX_INT64","ENCODER","ENCRYPTED_GRANTEE_LIST_METADATA_KEY","calculateReversedTimestamp","calculateOriginalTimestamp","reversedKey","reversed","addHistoryEntry","actReference","encryptedGranteeListRef","getLatestEntry","localeCompare","latest","saveHistoryTreeRecursively","isRoot","deserializeHistory","loadHistoryEntries","loadData","childRef","childData","childNode","nestedFork","getCurrentTimestamp","candidateKeys","findPublisherKey","pub","derived","formatDecryptedReference","decryptedRef","isShortRef","createActForContent","contentReference","publisherPrivateKey","granteePublicKeys","accessKey","generateRandomKey","encryptedRef","publisherPubKey","publisherKeys","granteePubKey","granteeKeys","actJson","beeCompatible","actResult","encryptedGranteeList","granteeListResult","historyManifest","historyResult","historyReference","historyTagUid","compressedPubKey","encryptedReference","granteeListReference","decryptActReference","publisherPubKeyHex","readerPrivateKeys","targetPath","getEntryAtTimestamp","readerPrivateKey","readerPubKey","parseCompressedPublicKey","DEFAULT_ACT_FILENAME","accountAsStateSnapshot","deltaFoldView","subsidisedStampRefusal","partitionSessionWrites","sessionEndedError","SwarmIdProxy","stampLifetime","beeApiUrl","siteBeeApiUrl","ownBeeApiUrl","dropGatewayForOwnCustomNode","DEFAULT_BEE_NODE_URL","subsidisedGatewayUrl","beeClientFor","isSubsidisedModeActive","nodeBee","accountBee","beeClients","config","parentIdentified","parentIdentifyReceived","authenticated","authLoading","storagePartitioned","busJoinGeneration","pendingYields","answeredRequests","pendingLaneUpdates","lastRestampMs","popupMode","unsubscribeStorageListeners","storageWorkQueue","isConnecting","presence","stamperInitGeneration","publishInFlight","lastDeviceRegistryRefreshAt","siteAllowList","networkSettings","DEFAULT_GNOSIS_RPC_URL","postageStampContractAddress","signalingUrl","setupMessageListener","setupStorageListeners","bus","setupBusListeners","announceReady","accountsManager","enqueueReconcile","handleAccountStorageChange","onNetworkSettingsChange","partitionAccount","applyNetworkSettings","emitConnectionInfoIfChanged","where","work","applyNetworkSettingsValues","reevaluateConnection","parentOrigin","findConnectionForParent","refreshStampFromStorage","authenticateFromStorage","schedulePublish","publishAccountDelta","ofEndedConnection","clearAuthData","sendToParent","joinAccountBus","lookupPostageStampForApp","nextBatchId","nextSignerKey","lookupAccountForApp","nextAccountFingerprint","stamperAccountFingerprint","setResolvedStamp","initializeStamper","teardownCoordinator","connectedApp","restoredSessionCheck","showAuthButton","origin","handlePopupMessage","pendingChallenge","challenge","endRefusedConnect","STORAGE_CHALLENGE_KEY","secret","acceptHandedOverChatBinding","hydratePartitionAccount","reactivateThisDevice","savePartitionSession","acceptedChatBinding","site","chatAddress","siteChatAddress","allowLocalHttp","onlySiteChatBinding","writePartitionSession","refreshStoredPartitionAccount","openStoredPartitionSession","clearPartitionSession","partitionSessionStorageKey","queued","PartitionSessionSchemaV1","readPartitionSession","AuthDataSchema","appMetadata","kdfSalt","kdfIterations","busJoinedKey","ensureAccountBusTransports","detachBusTransports","joiningRoom","busTopic","removeBusLocal","presenceTimer","publishPresence","busBoundAccountId","removeBusSignaling","RTCPeerConnection","stopPresence","destroy","publishTimer","cancelPendingYields","terminateStampWorkerPool","stampWorkerPool","laneKey","bufferLaneUpdate","applyPendingLaneUpdate","buffered","answerLeaseRequest","markRequestAnswered","requireDeviceId","yieldRankDelayMs","seed","standDownFromRequest","scheduled","applyAccountDelta","includeEnded","foldedAccount","createProgressCallback","enableProgress","progress","getPostageBatchId","getSignerKey","stampDepth","superseded","accountInfo","tuning","pick","o","readPartitionTuningOverride","knownDeviceIdsForAccount","refreshDeviceRegistryFromSwarm","deriveSecretSync","snap","saveStamperStateIfNeeded","getOrCreateWorkerPool","startLeaseOnceAllowed","check","verdict","leaseCacheStorageKey","requestedCount","desiredCount","hardwareConcurrency","saveStamperState","persistStamperState","withModeAwareWriteLock","targetOptions","operation","ParentIdentifyMessageSchema","handleParentIdentify","location","popupResult","PopupToIframeMessageSchema","ParentToIframeMessageSchema","handleParentMessage","sendErrorToParent","siteCheck","allowed","parentPopupMode","parentMetadata","parentButtonConfig","buttonConfig","parentWindow","siteSubsidisedGatewayUrl","loadAuthData","storageShared","handleCheckAuth","handleDisconnect","handleUploadData","handleDownloadData","handleDeriveAppSecret","handleGetChatKey","handleSetBeeApiUrl","handleStampChunk","handleUploadFile","handleDownloadFile","handleUploadFiles","handleListFiles","handleUploadChunk","handleDownloadChunk","handleIsConnected","handleGetNodeInfo","handleGsocMine","handleGsocSend","handleSocUpload","handleSocRawUpload","handleSocDownload","handleSocRawDownload","handleSocGetOwner","handleEpochFeedDownloadReference","handleEpochFeedUploadReference","handleFeedGetOwner","handleSequentialFeedGetOwner","handleSequentialFeedDownloadPayload","handleSequentialFeedDownloadRawPayload","handleSequentialFeedDownloadReference","handleSequentialFeedUploadPayload","handleSequentialFeedUploadRawPayload","handleSequentialFeedUploadReference","handleActUploadData","handleActDownloadData","handleActAddGrantees","handleActRevokeGrantees","handleActGetGrantees","handleGetPostageBatch","handleCreateFeedManifest","handleConnect","exhaustiveCheck","sharedData","lookupAppSecretFromSharedStorage","startRestoredSessionCheck","session","restorePartitionSession","awaitRestoredSessionAllowed","ensureCanWrite","ensureCanUpload","candidates","resolveStampForApp","live","partitioned","manager","mergedDevices","announcedDeviceName","before","isSelf","field","scope","deviceRegistryChanged","buildAccountStateSnapshot","includeEndedConnection","requireDefaultStamp","buildAccountStateSnapshotForPublish","publishReason","effectiveReason","runAccountStatePublish","assembled","rosterEntry","localSelf","host","partitionDeviceName","isConnectionValid","matches","stamperKey","changed","updated","hasUsableUserStamp","targetOrigin","identityKeysFor","identityKeys","sharingPublicKey","deriveSharingKey","postageSignerAddress","derivePostageSignerKeySync","buildConnectionInfo","identity","appKey","avatar","generatedAvatar","privKeyBytes","uploadMode","canUpload","uploadUnavailableReason","lastConnectionInfo","connected","nodeInfo","beeMode","chequebookEnabled","swapEnabled","authButtonContainer","innerHTML","button","document","createElement","isAuthenticated","isLoading","loadingText","disconnectText","connectText","textContent","disabled","style","display","alignItems","justifyContent","backgroundColor","color","border","borderRadius","fontSize","fontWeight","fontFamily","handleDisconnectClick","handleLoginClick","appendChild","openAuthPopup","basePath","authUrl","buildAuthUrl","effectivePopupMode","popup","checkPopupClosed","getElementById","head","setAuthButtonContainer","container","useWebSocket","uploadTag","manifestResult","contentUpload","contentReferenceBytes","saveMantarayTree","loadMantarayTreeWithChunkAPI","BeeMantarayNode","contentNode","split","targetRef","encryptContent","chunkOptions","paths","docs","listCollection","signerKeyObj","resolvedOwner","downloadSOC","secretHex","chatKey","deriveChatKey","accountInternalOwners","stampWithinAppLimits","persistStamperStateForStamp","recordSlot","marshaled","usageKey","appStampUsageStorageKey","parseAppStampUsage","stampNewSlot","slotsKey","socRestampSlotsStorageKey","nextUsage","restampKey","previousMs","restampTimestamp","recordSocSlot","SWARM_ENCRYPTION_LABEL","internalKeys","POSTAGE_SIGNER_LABEL","ACT_SHARING_LABEL","postageSignerKey","owners","accountAddress","parseFeedTimestamp","parseFeedIndex","makeSequentialFeedIdentifier","findLatestSequentialIndex","lookupTimeoutMs","lookupOptions","sequentialNextIndex","topicObj","ownerObj","atValue","afterValue","epochKeyBytes","encryptedFinder","plainFinder","epochEncryptionKey","epochHints","referenceBytes","readBackFinder","resolveSequentialOwner","parseSequentialPayload","resolveSequentialIndex","topicBytes","useTimestamp","resolvedIndex","buildSequentialPayload","sharingSecret","actKeys","sharing","publisher","contentUploadResult","manifestReferenceBytes","actualContentRef","newGranteePublicKeys","publisherCandidates","latestEntry","publisherEntry","newEntries","existingGrantees","updatedGrantees","newActJson","newHistoryReference","addGranteesToAct","revokeGrantees","revokePublicKeys","currentGrantees","remainingGrantees","revoked","publicKeysEqual","revokeGranteesFromAct","actData","getGranteesFromAct","postageBatch","details","fetchBatchDetails","fetchBatchTTL","fetchAuthoritativeBatchTTL","pricePerGBPerMonth","fetchSwarmPrice","calculateTTLSeconds","feedType","normalizedOwner","rootNode","slashFork","slashNode","slashNodeData","slashChunk","rootNodeData","createFeedManifestDirect","initProxy","runCoalescedAcrossTabs","graceMs","ranWithin","cooldownKey","force","cooldownMs","stampNow","windowMs","DEFAULT_SITE_ALLOW_LIST_ENS_NAME","DEFAULT_SITE_ALLOW_LIST_RECORD_KEY","LOG_PREFIX","ENTRY_SEPARATOR","ENS_LABEL","WORD_BYTES","RESOLVER_SELECTOR","selector","TEXT_SELECTOR","CachedSiteAllowListSchema","ensName","recordKey","origins","fetchedAt","keccak_256","word","resultBytes","subject","HEX_PREFIX_LENGTH","readWord","ethCall","jsonRpcCall","readEnsText","labels","reverse","joined","ensNamehash","resolver","decodeAddress","fatal","decodeString","keyBytes","keyHex","paddedLength","encodeTextCall","createSiteAllowList","devOrigins","clean","isCleanSiteOrigin","cleanDevOrigins","memory","inFlight","isFresh","age","readFromChain","isPlainEnsName","parseSiteAllowList","STORAGE_KEY_SITE_ALLOW_LIST","currentList","loadStored"],"mappings":"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","x_google_ignoreList":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65]}