@snaha/swarm-id 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +431 -0
- package/dist/chunk/bmt.d.ts +17 -0
- package/dist/chunk/bmt.d.ts.map +1 -0
- package/dist/chunk/cac.d.ts +18 -0
- package/dist/chunk/cac.d.ts.map +1 -0
- package/dist/chunk/constants.d.ts +10 -0
- package/dist/chunk/constants.d.ts.map +1 -0
- package/dist/chunk/encrypted-cac.d.ts +48 -0
- package/dist/chunk/encrypted-cac.d.ts.map +1 -0
- package/dist/chunk/encryption.d.ts +86 -0
- package/dist/chunk/encryption.d.ts.map +1 -0
- package/dist/chunk/index.d.ts +6 -0
- package/dist/chunk/index.d.ts.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/proxy/act/act.d.ts +78 -0
- package/dist/proxy/act/act.d.ts.map +1 -0
- package/dist/proxy/act/crypto.d.ts +44 -0
- package/dist/proxy/act/crypto.d.ts.map +1 -0
- package/dist/proxy/act/grantee-list.d.ts +82 -0
- package/dist/proxy/act/grantee-list.d.ts.map +1 -0
- package/dist/proxy/act/history.d.ts +183 -0
- package/dist/proxy/act/history.d.ts.map +1 -0
- package/dist/proxy/act/index.d.ts +104 -0
- package/dist/proxy/act/index.d.ts.map +1 -0
- package/dist/proxy/chunking-encrypted.d.ts +14 -0
- package/dist/proxy/chunking-encrypted.d.ts.map +1 -0
- package/dist/proxy/chunking.d.ts +15 -0
- package/dist/proxy/chunking.d.ts.map +1 -0
- package/dist/proxy/download-data.d.ts +16 -0
- package/dist/proxy/download-data.d.ts.map +1 -0
- package/dist/proxy/feed-manifest.d.ts +62 -0
- package/dist/proxy/feed-manifest.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts +77 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts +88 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/finder.d.ts +67 -0
- package/dist/proxy/feeds/epochs/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/index.d.ts +35 -0
- package/dist/proxy/feeds/epochs/index.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts +93 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/types.d.ts +109 -0
- package/dist/proxy/feeds/epochs/types.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/updater.d.ts +68 -0
- package/dist/proxy/feeds/epochs/updater.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/utils.d.ts +22 -0
- package/dist/proxy/feeds/epochs/utils.d.ts.map +1 -0
- package/dist/proxy/feeds/index.d.ts +5 -0
- package/dist/proxy/feeds/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/finder.d.ts +17 -0
- package/dist/proxy/feeds/sequence/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/index.d.ts +23 -0
- package/dist/proxy/feeds/sequence/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/types.d.ts +80 -0
- package/dist/proxy/feeds/sequence/types.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/updater.d.ts +26 -0
- package/dist/proxy/feeds/sequence/updater.d.ts.map +1 -0
- package/dist/proxy/index.d.ts +6 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/manifest-builder.d.ts +183 -0
- package/dist/proxy/manifest-builder.d.ts.map +1 -0
- package/dist/proxy/mantaray-encrypted.d.ts +27 -0
- package/dist/proxy/mantaray-encrypted.d.ts.map +1 -0
- package/dist/proxy/mantaray.d.ts +26 -0
- package/dist/proxy/mantaray.d.ts.map +1 -0
- package/dist/proxy/types.d.ts +29 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/upload-data.d.ts +17 -0
- package/dist/proxy/upload-data.d.ts.map +1 -0
- package/dist/proxy/upload-encrypted-data.d.ts +103 -0
- package/dist/proxy/upload-encrypted-data.d.ts.map +1 -0
- package/dist/schemas.d.ts +240 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/storage/debounced-uploader.d.ts +62 -0
- package/dist/storage/debounced-uploader.d.ts.map +1 -0
- package/dist/storage/utilization-store.d.ts +108 -0
- package/dist/storage/utilization-store.d.ts.map +1 -0
- package/dist/swarm-id-auth.d.ts +74 -0
- package/dist/swarm-id-auth.d.ts.map +1 -0
- package/dist/swarm-id-auth.js +2 -0
- package/dist/swarm-id-auth.js.map +1 -0
- package/dist/swarm-id-client.d.ts +878 -0
- package/dist/swarm-id-client.d.ts.map +1 -0
- package/dist/swarm-id-client.js +2 -0
- package/dist/swarm-id-client.js.map +1 -0
- package/dist/swarm-id-proxy.d.ts +236 -0
- package/dist/swarm-id-proxy.d.ts.map +1 -0
- package/dist/swarm-id-proxy.js +2 -0
- package/dist/swarm-id-proxy.js.map +1 -0
- package/dist/swarm-id.esm.js +2 -0
- package/dist/swarm-id.esm.js.map +1 -0
- package/dist/swarm-id.umd.js +2 -0
- package/dist/swarm-id.umd.js.map +1 -0
- package/dist/sync/index.d.ts +9 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/key-derivation.d.ts +25 -0
- package/dist/sync/key-derivation.d.ts.map +1 -0
- package/dist/sync/restore-account.d.ts +28 -0
- package/dist/sync/restore-account.d.ts.map +1 -0
- package/dist/sync/serialization.d.ts +16 -0
- package/dist/sync/serialization.d.ts.map +1 -0
- package/dist/sync/store-interfaces.d.ts +53 -0
- package/dist/sync/store-interfaces.d.ts.map +1 -0
- package/dist/sync/sync-account.d.ts +44 -0
- package/dist/sync/sync-account.d.ts.map +1 -0
- package/dist/sync/types.d.ts +13 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/test-fixtures.d.ts +17 -0
- package/dist/test-fixtures.d.ts.map +1 -0
- package/dist/types-BD_VkNn0.js +2 -0
- package/dist/types-BD_VkNn0.js.map +1 -0
- package/dist/types-lJCaT-50.js +2 -0
- package/dist/types-lJCaT-50.js.map +1 -0
- package/dist/types.d.ts +2157 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/account-payload.d.ts +94 -0
- package/dist/utils/account-payload.d.ts.map +1 -0
- package/dist/utils/account-state-snapshot.d.ts +38 -0
- package/dist/utils/account-state-snapshot.d.ts.map +1 -0
- package/dist/utils/backup-encryption.d.ts +127 -0
- package/dist/utils/backup-encryption.d.ts.map +1 -0
- package/dist/utils/batch-utilization.d.ts +432 -0
- package/dist/utils/batch-utilization.d.ts.map +1 -0
- package/dist/utils/constants.d.ts +11 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/hex.d.ts +17 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/key-derivation.d.ts +92 -0
- package/dist/utils/key-derivation.d.ts.map +1 -0
- package/dist/utils/storage-managers.d.ts +65 -0
- package/dist/utils/storage-managers.d.ts.map +1 -0
- package/dist/utils/swarm-id-export.d.ts +24 -0
- package/dist/utils/swarm-id-export.d.ts.map +1 -0
- package/dist/utils/ttl.d.ts +49 -0
- package/dist/utils/ttl.d.ts.map +1 -0
- package/dist/utils/url.d.ts +41 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/versioned-storage.d.ts +131 -0
- package/dist/utils/versioned-storage.d.ts.map +1 -0
- package/package.json +78 -0
- package/src/chunk/bmt.test.ts +217 -0
- package/src/chunk/bmt.ts +57 -0
- package/src/chunk/cac.test.ts +214 -0
- package/src/chunk/cac.ts +65 -0
- package/src/chunk/constants.ts +18 -0
- package/src/chunk/encrypted-cac.test.ts +385 -0
- package/src/chunk/encrypted-cac.ts +131 -0
- package/src/chunk/encryption.test.ts +352 -0
- package/src/chunk/encryption.ts +300 -0
- package/src/chunk/index.ts +47 -0
- package/src/index.ts +430 -0
- package/src/proxy/act/act.test.ts +278 -0
- package/src/proxy/act/act.ts +158 -0
- package/src/proxy/act/bee-compat.test.ts +948 -0
- package/src/proxy/act/crypto.test.ts +436 -0
- package/src/proxy/act/crypto.ts +376 -0
- package/src/proxy/act/grantee-list.test.ts +393 -0
- package/src/proxy/act/grantee-list.ts +239 -0
- package/src/proxy/act/history.test.ts +360 -0
- package/src/proxy/act/history.ts +413 -0
- package/src/proxy/act/index.test.ts +748 -0
- package/src/proxy/act/index.ts +853 -0
- package/src/proxy/chunking-encrypted.ts +95 -0
- package/src/proxy/chunking.ts +65 -0
- package/src/proxy/download-data.ts +448 -0
- package/src/proxy/feed-manifest.ts +174 -0
- package/src/proxy/feeds/epochs/async-finder.ts +372 -0
- package/src/proxy/feeds/epochs/epoch.test.ts +249 -0
- package/src/proxy/feeds/epochs/epoch.ts +181 -0
- package/src/proxy/feeds/epochs/finder.ts +282 -0
- package/src/proxy/feeds/epochs/index.ts +73 -0
- package/src/proxy/feeds/epochs/integration.test.ts +1336 -0
- package/src/proxy/feeds/epochs/test-utils.ts +274 -0
- package/src/proxy/feeds/epochs/types.ts +128 -0
- package/src/proxy/feeds/epochs/updater.ts +192 -0
- package/src/proxy/feeds/epochs/utils.ts +62 -0
- package/src/proxy/feeds/index.ts +5 -0
- package/src/proxy/feeds/sequence/async-finder.ts +31 -0
- package/src/proxy/feeds/sequence/finder.ts +73 -0
- package/src/proxy/feeds/sequence/index.ts +54 -0
- package/src/proxy/feeds/sequence/integration.test.ts +966 -0
- package/src/proxy/feeds/sequence/types.ts +103 -0
- package/src/proxy/feeds/sequence/updater.ts +71 -0
- package/src/proxy/index.ts +5 -0
- package/src/proxy/manifest-builder.test.ts +427 -0
- package/src/proxy/manifest-builder.ts +679 -0
- package/src/proxy/mantaray-encrypted.ts +78 -0
- package/src/proxy/mantaray.ts +104 -0
- package/src/proxy/types.ts +32 -0
- package/src/proxy/upload-data.ts +189 -0
- package/src/proxy/upload-encrypted-data.ts +658 -0
- package/src/schemas.ts +299 -0
- package/src/storage/debounced-uploader.ts +192 -0
- package/src/storage/utilization-store.ts +397 -0
- package/src/swarm-id-client.test.ts +99 -0
- package/src/swarm-id-client.ts +3095 -0
- package/src/swarm-id-proxy.ts +3891 -0
- package/src/sync/index.ts +28 -0
- package/src/sync/restore-account.ts +90 -0
- package/src/sync/serialization.ts +39 -0
- package/src/sync/store-interfaces.ts +62 -0
- package/src/sync/sync-account.test.ts +302 -0
- package/src/sync/sync-account.ts +396 -0
- package/src/sync/types.ts +11 -0
- package/src/test-fixtures.ts +109 -0
- package/src/types.ts +1651 -0
- package/src/utils/account-state-snapshot.test.ts +595 -0
- package/src/utils/account-state-snapshot.ts +94 -0
- package/src/utils/backup-encryption.test.ts +442 -0
- package/src/utils/backup-encryption.ts +352 -0
- package/src/utils/batch-utilization.ts +1309 -0
- package/src/utils/constants.ts +20 -0
- package/src/utils/hex.ts +27 -0
- package/src/utils/key-derivation.ts +197 -0
- package/src/utils/storage-managers.ts +365 -0
- package/src/utils/ttl.ts +129 -0
- package/src/utils/url.test.ts +136 -0
- package/src/utils/url.ts +71 -0
- package/src/utils/versioned-storage.ts +323 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TTL (Time To Live) calculation and formatting utilities for postage stamps
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Gnosis Chain block time in seconds
|
|
6
|
+
*/
|
|
7
|
+
export declare const GNOSIS_BLOCK_TIME = 5;
|
|
8
|
+
/**
|
|
9
|
+
* Swarmscan API URL for price data
|
|
10
|
+
*/
|
|
11
|
+
export declare const SWARMSCAN_STATS_URL = "https://api.swarmscan.io/v1/postage-stamps/stats";
|
|
12
|
+
/**
|
|
13
|
+
* Fetches current price from Swarmscan.
|
|
14
|
+
* @returns pricePerGBPerMonth in BZZ
|
|
15
|
+
*/
|
|
16
|
+
export declare function fetchSwarmPrice(): Promise<number>;
|
|
17
|
+
/**
|
|
18
|
+
* Calculates TTL in seconds from stamp amount and Swarmscan price.
|
|
19
|
+
*
|
|
20
|
+
* @param amount - Stamp amount in PLUR (smallest BZZ unit)
|
|
21
|
+
* @param pricePerGBPerMonth - Price from Swarmscan (in BZZ)
|
|
22
|
+
* @returns TTL in seconds
|
|
23
|
+
*/
|
|
24
|
+
export declare function calculateTTLSeconds(amount: bigint | number | string, pricePerGBPerMonth: number): number;
|
|
25
|
+
/**
|
|
26
|
+
* Formats a TTL value (in seconds) to a human-readable string.
|
|
27
|
+
* Returns "Xd Yh" format (e.g., "30d 14h").
|
|
28
|
+
*
|
|
29
|
+
* @param ttlSeconds - TTL in seconds
|
|
30
|
+
* @returns Human-readable TTL string, or "N/A" if undefined/invalid
|
|
31
|
+
*/
|
|
32
|
+
export declare function formatTTL(ttlSeconds: number | undefined): string;
|
|
33
|
+
/**
|
|
34
|
+
* Fetches block timestamp from Gnosis RPC.
|
|
35
|
+
*
|
|
36
|
+
* @param rpcUrl - Gnosis RPC URL
|
|
37
|
+
* @param blockNumber - Block number to get timestamp for
|
|
38
|
+
* @returns Block timestamp in seconds (Unix timestamp)
|
|
39
|
+
*/
|
|
40
|
+
export declare function getBlockTimestamp(rpcUrl: string, blockNumber: number): Promise<number>;
|
|
41
|
+
/**
|
|
42
|
+
* Calculates expiry timestamp for a postage stamp.
|
|
43
|
+
*
|
|
44
|
+
* @param blockTimestamp - Timestamp when stamp was created (from blockNumber)
|
|
45
|
+
* @param ttlSeconds - TTL in seconds
|
|
46
|
+
* @returns Expiry timestamp in seconds (Unix timestamp)
|
|
47
|
+
*/
|
|
48
|
+
export declare function calculateExpiryTimestamp(blockTimestamp: number, ttlSeconds: number): number;
|
|
49
|
+
//# sourceMappingURL=ttl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ttl.d.ts","sourceRoot":"","sources":["../../src/utils/ttl.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAA;AAkBlC;;GAEG;AACH,eAAO,MAAM,mBAAmB,qDACoB,CAAA;AAEpD;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAOvD;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAChC,kBAAkB,EAAE,MAAM,GACzB,MAAM,CASR;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAShE;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CAqBjB;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GACjB,MAAM,CAER"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AppMetadata } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration options for building the authentication URL.
|
|
4
|
+
*/
|
|
5
|
+
export interface BuildAuthUrlOptions {
|
|
6
|
+
/**
|
|
7
|
+
* When true, shows the agent sign-up option on the connect page.
|
|
8
|
+
* Agents are automated services that can perform operations on behalf of users.
|
|
9
|
+
*/
|
|
10
|
+
agent?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Challenge string for storage partitioning detection. When present, the popup checks
|
|
13
|
+
* if it can read this challenge from localStorage to determine whether
|
|
14
|
+
* storage is partitioned.
|
|
15
|
+
*/
|
|
16
|
+
challenge?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build the authentication URL for connecting to Swarm ID
|
|
20
|
+
*
|
|
21
|
+
* This function creates the same URL format as used by SwarmIdProxy.openAuthPopup()
|
|
22
|
+
* to ensure consistency across the library.
|
|
23
|
+
*
|
|
24
|
+
* @param baseUrl - The base URL where the authentication page is hosted
|
|
25
|
+
* @param origin - The origin of the parent application requesting authentication
|
|
26
|
+
* @param metadata - Optional application metadata to display during authentication
|
|
27
|
+
* @param options - Optional configuration for the auth URL
|
|
28
|
+
* @returns The complete authentication URL with hash parameters
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const url = buildAuthUrl(
|
|
33
|
+
* "https://swarm-id.example.com",
|
|
34
|
+
* "https://myapp.example.com",
|
|
35
|
+
* { name: "My App", description: "A decentralized application" }
|
|
36
|
+
* )
|
|
37
|
+
* // Returns: "https://swarm-id.example.com/connect#origin=https%3A%2F%2Fmyapp.example.com&appName=My+App&appDescription=A+decentralized+application"
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildAuthUrl(baseUrl: string, origin: string, metadata?: AppMetadata, options?: BuildAuthUrlOptions): string;
|
|
41
|
+
//# sourceMappingURL=url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,WAAW,EACtB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,MAAM,CAwBR"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Versioned Storage Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides a framework-agnostic way to store and retrieve versioned data
|
|
5
|
+
* with automatic migration support.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
/**
|
|
9
|
+
* Versioned storage wrapper schema
|
|
10
|
+
* Used to check if data is in versioned format
|
|
11
|
+
*/
|
|
12
|
+
export declare const VersionedStorageSchema: z.ZodObject<{
|
|
13
|
+
version: z.ZodNumber;
|
|
14
|
+
data: z.ZodUnknown;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
export type VersionedStorage = z.infer<typeof VersionedStorageSchema>;
|
|
17
|
+
/**
|
|
18
|
+
* Storage adapter interface - allows different storage backends
|
|
19
|
+
*/
|
|
20
|
+
export interface StorageAdapter {
|
|
21
|
+
getItem(key: string): string | undefined;
|
|
22
|
+
setItem(key: string, value: string): void;
|
|
23
|
+
removeItem(key: string): void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Version parser function - handles migration from one version to another
|
|
27
|
+
*/
|
|
28
|
+
export type VersionParser<T> = (data: unknown, version: number) => T[];
|
|
29
|
+
/**
|
|
30
|
+
* Serializer function - converts data to JSON-serializable format
|
|
31
|
+
*/
|
|
32
|
+
export type Serializer<T> = (data: T) => Record<string, unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* Listener function for storage change events
|
|
35
|
+
*/
|
|
36
|
+
export type StorageChangeListener<T> = (data: T[]) => void;
|
|
37
|
+
/**
|
|
38
|
+
* Options for versioned storage
|
|
39
|
+
*/
|
|
40
|
+
export interface VersionedStorageOptions<T> {
|
|
41
|
+
/** Storage key */
|
|
42
|
+
key: string;
|
|
43
|
+
/** Current version number */
|
|
44
|
+
currentVersion: number;
|
|
45
|
+
/** Storage adapter (e.g., localStorage) */
|
|
46
|
+
storage: StorageAdapter;
|
|
47
|
+
/** Version parsers - map of version to parser function */
|
|
48
|
+
parsers: Record<number, VersionParser<T>>;
|
|
49
|
+
/** Optional serializer for complex types */
|
|
50
|
+
serializer?: Serializer<T>;
|
|
51
|
+
/** Logger name for error messages */
|
|
52
|
+
loggerName?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* LocalStorage adapter for browser environments
|
|
56
|
+
*/
|
|
57
|
+
export declare class LocalStorageAdapter implements StorageAdapter {
|
|
58
|
+
getItem(key: string): string | undefined;
|
|
59
|
+
setItem(key: string, value: string): void;
|
|
60
|
+
removeItem(key: string): void;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* In-memory storage adapter (for testing or non-browser environments)
|
|
64
|
+
*/
|
|
65
|
+
export declare class MemoryStorageAdapter implements StorageAdapter {
|
|
66
|
+
private storage;
|
|
67
|
+
getItem(key: string): string | undefined;
|
|
68
|
+
setItem(key: string, value: string): void;
|
|
69
|
+
removeItem(key: string): void;
|
|
70
|
+
clear(): void;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Generic versioned storage manager
|
|
74
|
+
*/
|
|
75
|
+
export declare class VersionedStorageManager<T> {
|
|
76
|
+
private options;
|
|
77
|
+
private listeners;
|
|
78
|
+
private boundStorageHandler;
|
|
79
|
+
constructor(options: VersionedStorageOptions<T>);
|
|
80
|
+
/**
|
|
81
|
+
* Subscribe to storage change events from other windows/tabs
|
|
82
|
+
* The browser's storage event fires when localStorage changes in OTHER windows,
|
|
83
|
+
* making this useful for cross-window synchronization.
|
|
84
|
+
*
|
|
85
|
+
* @param listener - Callback function that receives the updated data
|
|
86
|
+
* @returns Unsubscribe function to remove the listener
|
|
87
|
+
*/
|
|
88
|
+
subscribe(listener: StorageChangeListener<T>): () => void;
|
|
89
|
+
/**
|
|
90
|
+
* Set up the browser storage event listener
|
|
91
|
+
* Only listens for changes to this manager's specific key
|
|
92
|
+
*/
|
|
93
|
+
private setupStorageEventListener;
|
|
94
|
+
/**
|
|
95
|
+
* Clean up the storage event listener when no more subscribers
|
|
96
|
+
*/
|
|
97
|
+
private cleanupStorageEventListener;
|
|
98
|
+
/**
|
|
99
|
+
* Notify all listeners of data changes
|
|
100
|
+
*/
|
|
101
|
+
private notifyListeners;
|
|
102
|
+
/**
|
|
103
|
+
* Load data from storage
|
|
104
|
+
*/
|
|
105
|
+
load(): T[];
|
|
106
|
+
/**
|
|
107
|
+
* Parse versioned data and migrate if needed
|
|
108
|
+
*/
|
|
109
|
+
private parse;
|
|
110
|
+
/**
|
|
111
|
+
* Save data to storage
|
|
112
|
+
*/
|
|
113
|
+
save(data: T[]): void;
|
|
114
|
+
/**
|
|
115
|
+
* Clear data from storage
|
|
116
|
+
*/
|
|
117
|
+
clear(): void;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Create a versioned storage manager with localStorage
|
|
121
|
+
*/
|
|
122
|
+
export declare function createLocalStorageManager<T>(options: Omit<VersionedStorageOptions<T>, "storage">): VersionedStorageManager<T>;
|
|
123
|
+
/**
|
|
124
|
+
* Create a versioned storage manager with memory storage
|
|
125
|
+
*/
|
|
126
|
+
export declare function createMemoryStorageManager<T>(options: Omit<VersionedStorageOptions<T>, "storage">): VersionedStorageManager<T>;
|
|
127
|
+
/**
|
|
128
|
+
* Create a simple Zod-based parser for a single version
|
|
129
|
+
*/
|
|
130
|
+
export declare function createZodParser<T>(schema: z.ZodType<T[]>): VersionParser<T>;
|
|
131
|
+
//# sourceMappingURL=versioned-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"versioned-storage.d.ts","sourceRoot":"","sources":["../../src/utils/versioned-storage.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@snaha/swarm-id",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Swarm ID library for authentication and Bee API operations",
|
|
6
|
+
"main": "dist/swarm-id.esm.js",
|
|
7
|
+
"module": "dist/swarm-id.esm.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/swarm-id.esm.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "rollup -c",
|
|
21
|
+
"build:watch": "rollup -c -w",
|
|
22
|
+
"clean": "rm -rf dist",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"test:ui": "vitest --ui",
|
|
26
|
+
"test:coverage": "vitest run --coverage",
|
|
27
|
+
"lint": "eslint src/**/*.ts",
|
|
28
|
+
"lint:fix": "eslint src/**/*.ts --fix",
|
|
29
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
30
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"check:all": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm test"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"swarm",
|
|
36
|
+
"identity",
|
|
37
|
+
"authentication",
|
|
38
|
+
"bee",
|
|
39
|
+
"web3"
|
|
40
|
+
],
|
|
41
|
+
"author": "snaha",
|
|
42
|
+
"license": "Apache-2.0",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/snaha/swarm-id"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public",
|
|
49
|
+
"provenance": true
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@ethersphere/bee-js": "^11.1.1",
|
|
53
|
+
"cafe-utility": "33.3.2",
|
|
54
|
+
"zod": "4.1.13"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@rollup/plugin-commonjs": "29.0.0",
|
|
58
|
+
"@rollup/plugin-json": "6.1.0",
|
|
59
|
+
"@rollup/plugin-node-resolve": "16.0.3",
|
|
60
|
+
"@rollup/plugin-replace": "6.0.3",
|
|
61
|
+
"@rollup/plugin-terser": "0.4.4",
|
|
62
|
+
"@rollup/plugin-typescript": "12.3.0",
|
|
63
|
+
"@types/node": "24.10.1",
|
|
64
|
+
"@typescript-eslint/eslint-plugin": "8.48.1",
|
|
65
|
+
"@typescript-eslint/parser": "8.48.1",
|
|
66
|
+
"@vitest/ui": "^4.0.16",
|
|
67
|
+
"eslint": "9.39.1",
|
|
68
|
+
"prettier": "3.7.3",
|
|
69
|
+
"rollup": "4.53.3",
|
|
70
|
+
"tslib": "2.8.1",
|
|
71
|
+
"typescript": "5.8.3",
|
|
72
|
+
"vitest": "4.0.14"
|
|
73
|
+
},
|
|
74
|
+
"engines": {
|
|
75
|
+
"node": ">=22",
|
|
76
|
+
"pnpm": "10.x"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for BMT (Binary Merkle Tree) hashing
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from "vitest"
|
|
6
|
+
import { Span } from "@ethersphere/bee-js"
|
|
7
|
+
import { calculateChunkAddress } from "./bmt"
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// calculateChunkAddress Tests
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
describe("calculateChunkAddress", () => {
|
|
14
|
+
describe("basic functionality", () => {
|
|
15
|
+
it("should return 32-byte Reference", () => {
|
|
16
|
+
const span = Span.fromBigInt(BigInt(4))
|
|
17
|
+
const payload = new Uint8Array([1, 2, 3, 4])
|
|
18
|
+
const chunkContent = new Uint8Array(8 + payload.length)
|
|
19
|
+
chunkContent.set(span.toUint8Array())
|
|
20
|
+
chunkContent.set(payload, 8)
|
|
21
|
+
|
|
22
|
+
const address = calculateChunkAddress(chunkContent)
|
|
23
|
+
|
|
24
|
+
expect(address.toUint8Array().length).toBe(32)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it("should produce deterministic result (same content = same address)", () => {
|
|
28
|
+
const span = Span.fromBigInt(BigInt(5))
|
|
29
|
+
const payload = new Uint8Array([10, 20, 30, 40, 50])
|
|
30
|
+
const chunkContent = new Uint8Array(8 + payload.length)
|
|
31
|
+
chunkContent.set(span.toUint8Array())
|
|
32
|
+
chunkContent.set(payload, 8)
|
|
33
|
+
|
|
34
|
+
const address1 = calculateChunkAddress(chunkContent)
|
|
35
|
+
const address2 = calculateChunkAddress(chunkContent)
|
|
36
|
+
|
|
37
|
+
expect(address1.toUint8Array()).toEqual(address2.toUint8Array())
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it("should produce different address for different content", () => {
|
|
41
|
+
const span = Span.fromBigInt(BigInt(4))
|
|
42
|
+
|
|
43
|
+
const payload1 = new Uint8Array([1, 2, 3, 4])
|
|
44
|
+
const chunkContent1 = new Uint8Array(8 + payload1.length)
|
|
45
|
+
chunkContent1.set(span.toUint8Array())
|
|
46
|
+
chunkContent1.set(payload1, 8)
|
|
47
|
+
|
|
48
|
+
const payload2 = new Uint8Array([5, 6, 7, 8])
|
|
49
|
+
const chunkContent2 = new Uint8Array(8 + payload2.length)
|
|
50
|
+
chunkContent2.set(span.toUint8Array())
|
|
51
|
+
chunkContent2.set(payload2, 8)
|
|
52
|
+
|
|
53
|
+
const address1 = calculateChunkAddress(chunkContent1)
|
|
54
|
+
const address2 = calculateChunkAddress(chunkContent2)
|
|
55
|
+
|
|
56
|
+
expect(address1.toUint8Array()).not.toEqual(address2.toUint8Array())
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe("payload size handling", () => {
|
|
61
|
+
it("should handle minimum payload (1 byte)", () => {
|
|
62
|
+
const span = Span.fromBigInt(BigInt(1))
|
|
63
|
+
const payload = new Uint8Array([42])
|
|
64
|
+
const chunkContent = new Uint8Array(8 + payload.length)
|
|
65
|
+
chunkContent.set(span.toUint8Array())
|
|
66
|
+
chunkContent.set(payload, 8)
|
|
67
|
+
|
|
68
|
+
const address = calculateChunkAddress(chunkContent)
|
|
69
|
+
|
|
70
|
+
expect(address.toUint8Array().length).toBe(32)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it("should handle maximum payload (4096 bytes)", () => {
|
|
74
|
+
const span = Span.fromBigInt(BigInt(4096))
|
|
75
|
+
const payload = new Uint8Array(4096)
|
|
76
|
+
for (let i = 0; i < 4096; i++) {
|
|
77
|
+
payload[i] = i % 256
|
|
78
|
+
}
|
|
79
|
+
const chunkContent = new Uint8Array(8 + payload.length)
|
|
80
|
+
chunkContent.set(span.toUint8Array())
|
|
81
|
+
chunkContent.set(payload, 8)
|
|
82
|
+
|
|
83
|
+
const address = calculateChunkAddress(chunkContent)
|
|
84
|
+
|
|
85
|
+
expect(address.toUint8Array().length).toBe(32)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it("should throw error on payload larger than 4096 bytes", () => {
|
|
89
|
+
const span = Span.fromBigInt(BigInt(4097))
|
|
90
|
+
const payload = new Uint8Array(4097)
|
|
91
|
+
const chunkContent = new Uint8Array(8 + payload.length)
|
|
92
|
+
chunkContent.set(span.toUint8Array())
|
|
93
|
+
chunkContent.set(payload, 8)
|
|
94
|
+
|
|
95
|
+
expect(() => calculateChunkAddress(chunkContent)).toThrow(
|
|
96
|
+
/payload size .* exceeds maximum chunk payload size/,
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
describe("zero-padding behavior", () => {
|
|
102
|
+
it("should produce consistent hash with short payloads (zero-padded internally)", () => {
|
|
103
|
+
// Two chunks with same payload should have same address
|
|
104
|
+
const span = Span.fromBigInt(BigInt(10))
|
|
105
|
+
const payload = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
|
106
|
+
|
|
107
|
+
const chunkContent = new Uint8Array(8 + payload.length)
|
|
108
|
+
chunkContent.set(span.toUint8Array())
|
|
109
|
+
chunkContent.set(payload, 8)
|
|
110
|
+
|
|
111
|
+
const address1 = calculateChunkAddress(chunkContent)
|
|
112
|
+
const address2 = calculateChunkAddress(chunkContent)
|
|
113
|
+
|
|
114
|
+
expect(address1.toUint8Array()).toEqual(address2.toUint8Array())
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it("should produce different hash for different payload lengths with same prefix", () => {
|
|
118
|
+
// Payload of length 4 vs length 5 should have different addresses
|
|
119
|
+
const payload4 = new Uint8Array([1, 2, 3, 4])
|
|
120
|
+
const payload5 = new Uint8Array([1, 2, 3, 4, 0])
|
|
121
|
+
|
|
122
|
+
const span4 = Span.fromBigInt(BigInt(4))
|
|
123
|
+
const chunkContent4 = new Uint8Array(8 + payload4.length)
|
|
124
|
+
chunkContent4.set(span4.toUint8Array())
|
|
125
|
+
chunkContent4.set(payload4, 8)
|
|
126
|
+
|
|
127
|
+
const span5 = Span.fromBigInt(BigInt(5))
|
|
128
|
+
const chunkContent5 = new Uint8Array(8 + payload5.length)
|
|
129
|
+
chunkContent5.set(span5.toUint8Array())
|
|
130
|
+
chunkContent5.set(payload5, 8)
|
|
131
|
+
|
|
132
|
+
const address4 = calculateChunkAddress(chunkContent4)
|
|
133
|
+
const address5 = calculateChunkAddress(chunkContent5)
|
|
134
|
+
|
|
135
|
+
// Even though payload5 ends with 0 (like zero-padding), different spans mean different addresses
|
|
136
|
+
expect(address4.toUint8Array()).not.toEqual(address5.toUint8Array())
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe("span variations", () => {
|
|
141
|
+
it("should produce different addresses for same payload but different span", () => {
|
|
142
|
+
const payload = new Uint8Array([1, 2, 3, 4])
|
|
143
|
+
|
|
144
|
+
const span1 = Span.fromBigInt(BigInt(4))
|
|
145
|
+
const chunkContent1 = new Uint8Array(8 + payload.length)
|
|
146
|
+
chunkContent1.set(span1.toUint8Array())
|
|
147
|
+
chunkContent1.set(payload, 8)
|
|
148
|
+
|
|
149
|
+
// Same payload but span indicates different length
|
|
150
|
+
const span2 = Span.fromBigInt(BigInt(3))
|
|
151
|
+
const chunkContent2 = new Uint8Array(8 + payload.length)
|
|
152
|
+
chunkContent2.set(span2.toUint8Array())
|
|
153
|
+
chunkContent2.set(payload, 8)
|
|
154
|
+
|
|
155
|
+
const address1 = calculateChunkAddress(chunkContent1)
|
|
156
|
+
const address2 = calculateChunkAddress(chunkContent2)
|
|
157
|
+
|
|
158
|
+
expect(address1.toUint8Array()).not.toEqual(address2.toUint8Array())
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
describe("edge cases", () => {
|
|
163
|
+
it("should handle chunk with zero-filled payload", () => {
|
|
164
|
+
const span = Span.fromBigInt(BigInt(32))
|
|
165
|
+
const payload = new Uint8Array(32) // all zeros
|
|
166
|
+
const chunkContent = new Uint8Array(8 + payload.length)
|
|
167
|
+
chunkContent.set(span.toUint8Array())
|
|
168
|
+
chunkContent.set(payload, 8)
|
|
169
|
+
|
|
170
|
+
const address = calculateChunkAddress(chunkContent)
|
|
171
|
+
|
|
172
|
+
expect(address.toUint8Array().length).toBe(32)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it("should handle chunk with all 0xFF payload", () => {
|
|
176
|
+
const span = Span.fromBigInt(BigInt(32))
|
|
177
|
+
const payload = new Uint8Array(32).fill(0xff)
|
|
178
|
+
const chunkContent = new Uint8Array(8 + payload.length)
|
|
179
|
+
chunkContent.set(span.toUint8Array())
|
|
180
|
+
chunkContent.set(payload, 8)
|
|
181
|
+
|
|
182
|
+
const address = calculateChunkAddress(chunkContent)
|
|
183
|
+
|
|
184
|
+
expect(address.toUint8Array().length).toBe(32)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it("should handle exactly 32 bytes payload (one BMT segment)", () => {
|
|
188
|
+
const span = Span.fromBigInt(BigInt(32))
|
|
189
|
+
const payload = new Uint8Array(32)
|
|
190
|
+
for (let i = 0; i < 32; i++) {
|
|
191
|
+
payload[i] = i
|
|
192
|
+
}
|
|
193
|
+
const chunkContent = new Uint8Array(8 + payload.length)
|
|
194
|
+
chunkContent.set(span.toUint8Array())
|
|
195
|
+
chunkContent.set(payload, 8)
|
|
196
|
+
|
|
197
|
+
const address = calculateChunkAddress(chunkContent)
|
|
198
|
+
|
|
199
|
+
expect(address.toUint8Array().length).toBe(32)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it("should handle exactly 64 bytes payload (two BMT segments)", () => {
|
|
203
|
+
const span = Span.fromBigInt(BigInt(64))
|
|
204
|
+
const payload = new Uint8Array(64)
|
|
205
|
+
for (let i = 0; i < 64; i++) {
|
|
206
|
+
payload[i] = i
|
|
207
|
+
}
|
|
208
|
+
const chunkContent = new Uint8Array(8 + payload.length)
|
|
209
|
+
chunkContent.set(span.toUint8Array())
|
|
210
|
+
chunkContent.set(payload, 8)
|
|
211
|
+
|
|
212
|
+
const address = calculateChunkAddress(chunkContent)
|
|
213
|
+
|
|
214
|
+
expect(address.toUint8Array().length).toBe(32)
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
})
|
package/src/chunk/bmt.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Binary } from "cafe-utility"
|
|
2
|
+
import { Reference, Span } from "@ethersphere/bee-js"
|
|
3
|
+
|
|
4
|
+
const MAX_CHUNK_PAYLOAD_SIZE = 4096
|
|
5
|
+
const SEGMENT_SIZE = 32
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Calculate a Binary Merkle Tree hash for a chunk
|
|
9
|
+
*
|
|
10
|
+
* The BMT chunk address is the hash of the 8 byte span and the root
|
|
11
|
+
* hash of a binary Merkle tree (BMT) built on the 32-byte segments
|
|
12
|
+
* of the underlying data.
|
|
13
|
+
*
|
|
14
|
+
* If the chunk content is less than 4k, the hash is calculated as
|
|
15
|
+
* if the chunk was padded with all zeros up to 4096 bytes.
|
|
16
|
+
*
|
|
17
|
+
* @param chunkContent Chunk data including span and payload as well
|
|
18
|
+
*
|
|
19
|
+
* @returns the keccak256 hash in a byte array
|
|
20
|
+
*/
|
|
21
|
+
export function calculateChunkAddress(chunkContent: Uint8Array): Reference {
|
|
22
|
+
const span = chunkContent.slice(0, Span.LENGTH)
|
|
23
|
+
const payload = chunkContent.slice(Span.LENGTH)
|
|
24
|
+
const rootHash = calculateBmtRootHash(payload)
|
|
25
|
+
const chunkHash = Binary.keccak256(Binary.concatBytes(span, rootHash))
|
|
26
|
+
|
|
27
|
+
return new Reference(chunkHash)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function calculateBmtRootHash(payload: Uint8Array): Uint8Array {
|
|
31
|
+
if (payload.length > MAX_CHUNK_PAYLOAD_SIZE) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`payload size ${payload.length} exceeds maximum chunk payload size ${MAX_CHUNK_PAYLOAD_SIZE}`,
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
const input = new Uint8Array(MAX_CHUNK_PAYLOAD_SIZE)
|
|
37
|
+
input.set(payload)
|
|
38
|
+
|
|
39
|
+
// Build BMT by hashing pairs of segments level by level
|
|
40
|
+
let currentLevel = Binary.partition(input, SEGMENT_SIZE)
|
|
41
|
+
|
|
42
|
+
while (currentLevel.length > 1) {
|
|
43
|
+
const nextLevel: Uint8Array[] = []
|
|
44
|
+
|
|
45
|
+
for (let i = 0; i < currentLevel.length; i += 2) {
|
|
46
|
+
const left = currentLevel[i]
|
|
47
|
+
const right = currentLevel[i + 1]
|
|
48
|
+
const combined = Binary.concatBytes(left, right)
|
|
49
|
+
const hash = Binary.keccak256(combined)
|
|
50
|
+
nextLevel.push(hash)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
currentLevel = nextLevel
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return currentLevel[0]
|
|
57
|
+
}
|