@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,28 @@
|
|
|
1
|
+
// Public API
|
|
2
|
+
export {
|
|
3
|
+
// Account-level key derivation
|
|
4
|
+
deriveAccountBackupKey,
|
|
5
|
+
deriveAccountSwarmEncryptionKey,
|
|
6
|
+
backupKeyToPrivateKey,
|
|
7
|
+
} from "../utils/key-derivation"
|
|
8
|
+
export { serializeAccountState, deserializeAccountState } from "./serialization"
|
|
9
|
+
|
|
10
|
+
// Sync account
|
|
11
|
+
export { createSyncAccount, ACCOUNT_SYNC_TOPIC_PREFIX } from "./sync-account"
|
|
12
|
+
export type { SyncAccountOptions, SyncAccountFunction } from "./sync-account"
|
|
13
|
+
|
|
14
|
+
// Restore account from Swarm
|
|
15
|
+
export { restoreAccountFromSwarm } from "./restore-account"
|
|
16
|
+
export type { RestoreAccountResult } from "./restore-account"
|
|
17
|
+
|
|
18
|
+
// Store interfaces
|
|
19
|
+
export type {
|
|
20
|
+
AccountsStoreInterface,
|
|
21
|
+
IdentitiesStoreInterface,
|
|
22
|
+
ConnectedAppsStoreInterface,
|
|
23
|
+
PostageStampsStoreInterface,
|
|
24
|
+
StamperOptions,
|
|
25
|
+
FlushableStamper,
|
|
26
|
+
} from "./store-interfaces"
|
|
27
|
+
|
|
28
|
+
export type { SyncResult } from "./types"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Restore Account from Swarm
|
|
3
|
+
*
|
|
4
|
+
* When a passkey auth succeeds but no local account exists (e.g. new device),
|
|
5
|
+
* this utility derives the keys needed to find and decrypt the account
|
|
6
|
+
* snapshot stored in Swarm and returns the restored state.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
Bee,
|
|
11
|
+
PrivateKey,
|
|
12
|
+
EthAddress,
|
|
13
|
+
Topic,
|
|
14
|
+
Reference,
|
|
15
|
+
type Bytes,
|
|
16
|
+
} from "@ethersphere/bee-js"
|
|
17
|
+
import {
|
|
18
|
+
deriveAccountSwarmEncryptionKey,
|
|
19
|
+
deriveSecret,
|
|
20
|
+
} from "../utils/key-derivation"
|
|
21
|
+
import { ACCOUNT_SYNC_TOPIC_PREFIX } from "./sync-account"
|
|
22
|
+
import { AsyncEpochFinder } from "../proxy/feeds/epochs/async-finder"
|
|
23
|
+
import { downloadDataWithChunkAPI } from "../proxy/download-data"
|
|
24
|
+
import { deserializeAccountState } from "./serialization"
|
|
25
|
+
import type { AccountStateSnapshot } from "../utils/account-state-snapshot"
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Result of a successful account restore from Swarm
|
|
29
|
+
*/
|
|
30
|
+
export interface RestoreAccountResult {
|
|
31
|
+
snapshot: AccountStateSnapshot
|
|
32
|
+
swarmEncryptionKey: string
|
|
33
|
+
credentialId: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Restore account state from Swarm using passkey authentication result
|
|
38
|
+
*
|
|
39
|
+
* @param bee - Bee client instance
|
|
40
|
+
* @param masterKey - Master key from passkey authentication
|
|
41
|
+
* @param ethereumAddress - Account ID (EthAddress) from passkey authentication
|
|
42
|
+
* @param credentialId - Credential ID from passkey authentication
|
|
43
|
+
* @returns Restored account state, or undefined if no backup found in Swarm
|
|
44
|
+
*/
|
|
45
|
+
export async function restoreAccountFromSwarm(
|
|
46
|
+
bee: Bee,
|
|
47
|
+
masterKey: Bytes,
|
|
48
|
+
ethereumAddress: EthAddress,
|
|
49
|
+
credentialId: string,
|
|
50
|
+
): Promise<RestoreAccountResult | undefined> {
|
|
51
|
+
const accountId = ethereumAddress.toHex()
|
|
52
|
+
|
|
53
|
+
// 1. Derive the swarm encryption key from the master key
|
|
54
|
+
const swarmEncryptionKey = await deriveAccountSwarmEncryptionKey(
|
|
55
|
+
masterKey.toHex(),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
// 2. Derive the backup key (used as feed owner)
|
|
59
|
+
const backupKeyHex = await deriveSecret(swarmEncryptionKey, "backup-key")
|
|
60
|
+
const backupKey = new PrivateKey(backupKeyHex)
|
|
61
|
+
const owner = backupKey.publicKey().address()
|
|
62
|
+
|
|
63
|
+
// 3. Build the feed topic
|
|
64
|
+
const topic = Topic.fromString(`${ACCOUNT_SYNC_TOPIC_PREFIX}:${accountId}`)
|
|
65
|
+
|
|
66
|
+
// 4. Look up the latest epoch feed entry
|
|
67
|
+
// Note: feed SOCs are uploaded unencrypted (sync-account.ts doesn't pass
|
|
68
|
+
// encryptionKey to updater.update()), so the finder must NOT use one either.
|
|
69
|
+
const finder = new AsyncEpochFinder(bee, topic, owner)
|
|
70
|
+
const now = BigInt(Math.floor(Date.now() / 1000))
|
|
71
|
+
|
|
72
|
+
const refBytes = await finder.findAt(now)
|
|
73
|
+
|
|
74
|
+
if (!refBytes) {
|
|
75
|
+
return undefined
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 5. Download and decrypt the account snapshot
|
|
79
|
+
const reference = new Reference(refBytes)
|
|
80
|
+
const data = await downloadDataWithChunkAPI(bee, reference.toHex())
|
|
81
|
+
|
|
82
|
+
// 6. Deserialize the snapshot
|
|
83
|
+
const snapshot = deserializeAccountState(data)
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
snapshot,
|
|
87
|
+
swarmEncryptionKey,
|
|
88
|
+
credentialId,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
serializeAccountStateSnapshot,
|
|
3
|
+
deserializeAccountStateSnapshot,
|
|
4
|
+
} from "../utils/account-state-snapshot"
|
|
5
|
+
import type { AccountStateSnapshot } from "../utils/account-state-snapshot"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Serialize account state to JSON bytes
|
|
9
|
+
*
|
|
10
|
+
* @param state - Account payload to serialize
|
|
11
|
+
* @returns JSON encoded as Uint8Array
|
|
12
|
+
*/
|
|
13
|
+
export function serializeAccountState(state: AccountStateSnapshot): Uint8Array {
|
|
14
|
+
const json = JSON.stringify(serializeAccountStateSnapshot(state))
|
|
15
|
+
|
|
16
|
+
return new TextEncoder().encode(json)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Deserialize JSON bytes to account payload
|
|
21
|
+
*
|
|
22
|
+
* @param data - JSON bytes to deserialize
|
|
23
|
+
* @returns Account payload
|
|
24
|
+
*/
|
|
25
|
+
export function deserializeAccountState(
|
|
26
|
+
data: Uint8Array,
|
|
27
|
+
): AccountStateSnapshot {
|
|
28
|
+
const json = new TextDecoder().decode(data)
|
|
29
|
+
const parsed = JSON.parse(json)
|
|
30
|
+
const result = deserializeAccountStateSnapshot(parsed)
|
|
31
|
+
|
|
32
|
+
if (!result.success) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Failed to deserialize account state: ${result.error.message}`,
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return result.data
|
|
39
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store Interfaces for Sync Coordinator
|
|
3
|
+
*
|
|
4
|
+
* These interfaces define the minimal contract that stores must implement
|
|
5
|
+
* to be used with the sync coordinator. This allows the coordinator to be
|
|
6
|
+
* used with different store implementations (Svelte stores, plain objects, etc.)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { EthAddress, BatchId, Stamper } from "@ethersphere/bee-js"
|
|
10
|
+
import type { Account, Identity, ConnectedApp, PostageStamp } from "../schemas"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Options for creating a stamper with utilization tracking
|
|
14
|
+
*/
|
|
15
|
+
export interface StamperOptions {
|
|
16
|
+
owner: EthAddress
|
|
17
|
+
encryptionKey: Uint8Array
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extended stamper interface with optional flush capability
|
|
22
|
+
*
|
|
23
|
+
* Some stampers (like UtilizationAwareStamper) support flushing
|
|
24
|
+
* dirty bucket state to cache.
|
|
25
|
+
*/
|
|
26
|
+
export interface FlushableStamper extends Stamper {
|
|
27
|
+
flush?(): Promise<void>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Interface for accessing account data
|
|
32
|
+
*/
|
|
33
|
+
export interface AccountsStoreInterface {
|
|
34
|
+
getAccount(id: EthAddress): Account | undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Interface for accessing identity data
|
|
39
|
+
*/
|
|
40
|
+
export interface IdentitiesStoreInterface {
|
|
41
|
+
getIdentitiesByAccount(accountId: EthAddress): Identity[]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Interface for accessing connected app data
|
|
46
|
+
*/
|
|
47
|
+
export interface ConnectedAppsStoreInterface {
|
|
48
|
+
getAppsByIdentityId(identityId: string): ConnectedApp[]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Interface for accessing and managing postage stamp data
|
|
53
|
+
*/
|
|
54
|
+
export interface PostageStampsStoreInterface {
|
|
55
|
+
getStamp(batchID: BatchId): PostageStamp | undefined
|
|
56
|
+
getStampsByAccount(accountId: string): PostageStamp[]
|
|
57
|
+
getStamper(
|
|
58
|
+
batchID: BatchId,
|
|
59
|
+
options?: StamperOptions,
|
|
60
|
+
): Promise<FlushableStamper | undefined>
|
|
61
|
+
updateStampUtilization(batchID: BatchId, utilization: number): void
|
|
62
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest"
|
|
2
|
+
import { EthAddress, BatchId } from "@ethersphere/bee-js"
|
|
3
|
+
import { createSyncAccount } from "./sync-account"
|
|
4
|
+
import { deserializeAccountState } from "./serialization"
|
|
5
|
+
import type {
|
|
6
|
+
AccountsStoreInterface,
|
|
7
|
+
IdentitiesStoreInterface,
|
|
8
|
+
ConnectedAppsStoreInterface,
|
|
9
|
+
PostageStampsStoreInterface,
|
|
10
|
+
} from "./store-interfaces"
|
|
11
|
+
import type { UtilizationStoreDB } from "../storage/utilization-store"
|
|
12
|
+
import type { DebouncedUtilizationUploader } from "../storage/debounced-uploader"
|
|
13
|
+
import {
|
|
14
|
+
TEST_ETH_ADDRESS_HEX,
|
|
15
|
+
TEST_IDENTITY_ADDRESS_HEX,
|
|
16
|
+
TEST_BATCH_ID_HEX,
|
|
17
|
+
createPasskeyAccount,
|
|
18
|
+
createIdentity,
|
|
19
|
+
createConnectedApp,
|
|
20
|
+
createPostageStamp,
|
|
21
|
+
} from "../test-fixtures"
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Mock Factories
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
function createMockStores() {
|
|
28
|
+
const account = createPasskeyAccount({
|
|
29
|
+
credentialId: "test-credential",
|
|
30
|
+
name: "Test Account",
|
|
31
|
+
defaultPostageStampBatchID: new BatchId(TEST_BATCH_ID_HEX),
|
|
32
|
+
})
|
|
33
|
+
const identity = createIdentity()
|
|
34
|
+
const connectedApp = createConnectedApp({ appSecret: undefined })
|
|
35
|
+
const stamp = createPostageStamp()
|
|
36
|
+
|
|
37
|
+
const accountsStore: AccountsStoreInterface = {
|
|
38
|
+
getAccount: vi.fn((id: EthAddress) =>
|
|
39
|
+
id.toHex() === TEST_ETH_ADDRESS_HEX ? account : undefined,
|
|
40
|
+
),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const identitiesStore: IdentitiesStoreInterface = {
|
|
44
|
+
getIdentitiesByAccount: vi.fn((accountId: EthAddress) =>
|
|
45
|
+
accountId.toHex() === TEST_ETH_ADDRESS_HEX ? [identity] : [],
|
|
46
|
+
),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const connectedAppsStore: ConnectedAppsStoreInterface = {
|
|
50
|
+
getAppsByIdentityId: vi.fn((identityId: string) =>
|
|
51
|
+
identityId === TEST_IDENTITY_ADDRESS_HEX ? [connectedApp] : [],
|
|
52
|
+
),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const mockStamper = {
|
|
56
|
+
stamp: vi.fn().mockResolvedValue({
|
|
57
|
+
batchId: new Uint8Array(32),
|
|
58
|
+
index: new Uint8Array(8),
|
|
59
|
+
timestamp: new Uint8Array(8),
|
|
60
|
+
signature: new Uint8Array(65),
|
|
61
|
+
}),
|
|
62
|
+
flush: vi.fn().mockResolvedValue(undefined),
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const postageStampsStore: PostageStampsStoreInterface = {
|
|
66
|
+
getStamp: vi.fn((batchID: BatchId) =>
|
|
67
|
+
batchID.toHex() === TEST_BATCH_ID_HEX ? stamp : undefined,
|
|
68
|
+
),
|
|
69
|
+
getStampsByAccount: vi.fn((accountId: string) =>
|
|
70
|
+
accountId === TEST_ETH_ADDRESS_HEX ? [stamp] : [],
|
|
71
|
+
),
|
|
72
|
+
getStamper: vi.fn().mockResolvedValue(mockStamper),
|
|
73
|
+
updateStampUtilization: vi.fn(),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
accountsStore,
|
|
78
|
+
identitiesStore,
|
|
79
|
+
connectedAppsStore,
|
|
80
|
+
postageStampsStore,
|
|
81
|
+
mockStamper,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// Upload & Epoch Mock Setup
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
89
|
+
// Track what was uploaded
|
|
90
|
+
let capturedUploadData: Uint8Array | undefined
|
|
91
|
+
let capturedEncryptionKey: Uint8Array | undefined
|
|
92
|
+
let uploadCallCount: number
|
|
93
|
+
let epochUpdateCallCount: number
|
|
94
|
+
let capturedEpochReference: Uint8Array | undefined
|
|
95
|
+
|
|
96
|
+
const FAKE_UPLOAD_REFERENCE = "ab".repeat(32)
|
|
97
|
+
const FAKE_SOC_ADDRESS = new Uint8Array(32).fill(0xee)
|
|
98
|
+
|
|
99
|
+
vi.mock("../proxy/upload-encrypted-data", () => ({
|
|
100
|
+
uploadEncryptedDataWithSigning: vi.fn(
|
|
101
|
+
async (
|
|
102
|
+
_context: unknown,
|
|
103
|
+
data: Uint8Array,
|
|
104
|
+
encryptionKey: Uint8Array | undefined,
|
|
105
|
+
) => {
|
|
106
|
+
capturedUploadData = data
|
|
107
|
+
capturedEncryptionKey = encryptionKey
|
|
108
|
+
uploadCallCount++
|
|
109
|
+
return {
|
|
110
|
+
reference: FAKE_UPLOAD_REFERENCE,
|
|
111
|
+
chunkAddresses: [new Uint8Array(32).fill(0xaa)],
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
),
|
|
115
|
+
}))
|
|
116
|
+
|
|
117
|
+
// Use a real class for the mock so `new BasicEpochUpdater(...)` works
|
|
118
|
+
const mockUpdate = vi.fn()
|
|
119
|
+
|
|
120
|
+
vi.mock("../proxy/feeds/epochs", () => {
|
|
121
|
+
return {
|
|
122
|
+
BasicEpochUpdater: class MockBasicEpochUpdater {
|
|
123
|
+
update = mockUpdate
|
|
124
|
+
getOwner = vi.fn(() => new EthAddress("a".repeat(40)))
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Mock utilization to avoid complexity in these tests
|
|
130
|
+
vi.mock("../utils/batch-utilization", () => ({
|
|
131
|
+
updateAfterWrite: vi.fn().mockResolvedValue({
|
|
132
|
+
state: { chunks: new Map() },
|
|
133
|
+
tracker: { hasDirtyChunks: () => false, getDirtyChunks: () => [] },
|
|
134
|
+
}),
|
|
135
|
+
saveUtilizationState: vi.fn().mockResolvedValue(undefined),
|
|
136
|
+
calculateUtilization: vi.fn().mockReturnValue(0.01),
|
|
137
|
+
}))
|
|
138
|
+
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// Tests
|
|
141
|
+
// ============================================================================
|
|
142
|
+
|
|
143
|
+
describe("createSyncAccount", () => {
|
|
144
|
+
beforeEach(() => {
|
|
145
|
+
capturedUploadData = undefined
|
|
146
|
+
capturedEncryptionKey = undefined
|
|
147
|
+
capturedEpochReference = undefined
|
|
148
|
+
uploadCallCount = 0
|
|
149
|
+
epochUpdateCallCount = 0
|
|
150
|
+
|
|
151
|
+
mockUpdate.mockReset()
|
|
152
|
+
mockUpdate.mockImplementation(
|
|
153
|
+
async (_timestamp: bigint, reference: Uint8Array) => {
|
|
154
|
+
capturedEpochReference = reference
|
|
155
|
+
epochUpdateCallCount++
|
|
156
|
+
return {
|
|
157
|
+
socAddress: FAKE_SOC_ADDRESS,
|
|
158
|
+
epoch: { start: 0n, level: 0 },
|
|
159
|
+
timestamp: BigInt(Math.floor(Date.now() / 1000)),
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it("should upload encrypted data and update epoch feed", async () => {
|
|
166
|
+
const stores = createMockStores()
|
|
167
|
+
|
|
168
|
+
const syncAccount = createSyncAccount({
|
|
169
|
+
bee: {} as never,
|
|
170
|
+
...stores,
|
|
171
|
+
utilizationStore: {} as UtilizationStoreDB,
|
|
172
|
+
utilizationUploader: {
|
|
173
|
+
scheduleUpload: vi.fn().mockResolvedValue(undefined),
|
|
174
|
+
} as unknown as DebouncedUtilizationUploader,
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const result = await syncAccount(TEST_ETH_ADDRESS_HEX)
|
|
178
|
+
|
|
179
|
+
expect(result).toBeDefined()
|
|
180
|
+
expect(result!.status).toBe("success")
|
|
181
|
+
if (result!.status !== "success") return
|
|
182
|
+
|
|
183
|
+
// Verify upload happened
|
|
184
|
+
expect(uploadCallCount).toBe(1)
|
|
185
|
+
expect(capturedUploadData).toBeDefined()
|
|
186
|
+
expect(capturedEncryptionKey).toBeDefined()
|
|
187
|
+
|
|
188
|
+
// Verify epoch feed was updated
|
|
189
|
+
expect(epochUpdateCallCount).toBe(1)
|
|
190
|
+
expect(capturedEpochReference).toBeDefined()
|
|
191
|
+
|
|
192
|
+
// Verify result contains reference and chunk addresses
|
|
193
|
+
expect(result.reference).toBe(FAKE_UPLOAD_REFERENCE)
|
|
194
|
+
expect(result.chunkAddresses.length).toBeGreaterThanOrEqual(2) // data chunks + SOC
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it("should serialize account state with all fields including accountName", async () => {
|
|
198
|
+
const stores = createMockStores()
|
|
199
|
+
|
|
200
|
+
const syncAccount = createSyncAccount({
|
|
201
|
+
bee: {} as never,
|
|
202
|
+
...stores,
|
|
203
|
+
utilizationStore: {} as UtilizationStoreDB,
|
|
204
|
+
utilizationUploader: {
|
|
205
|
+
scheduleUpload: vi.fn().mockResolvedValue(undefined),
|
|
206
|
+
} as unknown as DebouncedUtilizationUploader,
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
await syncAccount(TEST_ETH_ADDRESS_HEX)
|
|
210
|
+
|
|
211
|
+
// Deserialize the captured upload data to verify contents
|
|
212
|
+
expect(capturedUploadData).toBeDefined()
|
|
213
|
+
const deserialized = deserializeAccountState(capturedUploadData!)
|
|
214
|
+
|
|
215
|
+
expect(deserialized.version).toBe(1)
|
|
216
|
+
expect(deserialized.accountId).toBe(TEST_ETH_ADDRESS_HEX)
|
|
217
|
+
expect(deserialized.metadata.accountName).toBe("Test Account")
|
|
218
|
+
expect(deserialized.metadata.defaultPostageStampBatchID).toBe(
|
|
219
|
+
TEST_BATCH_ID_HEX,
|
|
220
|
+
)
|
|
221
|
+
expect(deserialized.metadata.createdAt).toBe(1700000000000)
|
|
222
|
+
expect(deserialized.identities).toHaveLength(1)
|
|
223
|
+
expect(deserialized.identities[0].name).toBe("Default Identity")
|
|
224
|
+
expect(deserialized.connectedApps).toHaveLength(1)
|
|
225
|
+
expect(deserialized.connectedApps[0].appName).toBe("Test App")
|
|
226
|
+
expect(deserialized.postageStamps).toHaveLength(1)
|
|
227
|
+
expect(deserialized.postageStamps[0].depth).toBe(20)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it("should return undefined when account not found", async () => {
|
|
231
|
+
const stores = createMockStores()
|
|
232
|
+
;(
|
|
233
|
+
stores.accountsStore.getAccount as ReturnType<typeof vi.fn>
|
|
234
|
+
).mockReturnValue(undefined)
|
|
235
|
+
|
|
236
|
+
const syncAccount = createSyncAccount({
|
|
237
|
+
bee: {} as never,
|
|
238
|
+
...stores,
|
|
239
|
+
utilizationStore: {} as UtilizationStoreDB,
|
|
240
|
+
utilizationUploader: {
|
|
241
|
+
scheduleUpload: vi.fn().mockResolvedValue(undefined),
|
|
242
|
+
} as unknown as DebouncedUtilizationUploader,
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
const result = await syncAccount(TEST_ETH_ADDRESS_HEX)
|
|
246
|
+
expect(result).toBeUndefined()
|
|
247
|
+
expect(uploadCallCount).toBe(0)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it("should return undefined when no default stamp available", async () => {
|
|
251
|
+
const stores = createMockStores()
|
|
252
|
+
const account = createPasskeyAccount({
|
|
253
|
+
defaultPostageStampBatchID: new BatchId(TEST_BATCH_ID_HEX),
|
|
254
|
+
})
|
|
255
|
+
;(
|
|
256
|
+
stores.accountsStore.getAccount as ReturnType<typeof vi.fn>
|
|
257
|
+
).mockReturnValue({
|
|
258
|
+
...account,
|
|
259
|
+
defaultPostageStampBatchID: undefined,
|
|
260
|
+
})
|
|
261
|
+
;(
|
|
262
|
+
stores.identitiesStore.getIdentitiesByAccount as ReturnType<typeof vi.fn>
|
|
263
|
+
).mockReturnValue([
|
|
264
|
+
{ ...createIdentity(), defaultPostageStampBatchID: undefined },
|
|
265
|
+
])
|
|
266
|
+
|
|
267
|
+
const syncAccount = createSyncAccount({
|
|
268
|
+
bee: {} as never,
|
|
269
|
+
...stores,
|
|
270
|
+
utilizationStore: {} as UtilizationStoreDB,
|
|
271
|
+
utilizationUploader: {
|
|
272
|
+
scheduleUpload: vi.fn().mockResolvedValue(undefined),
|
|
273
|
+
} as unknown as DebouncedUtilizationUploader,
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
const result = await syncAccount(TEST_ETH_ADDRESS_HEX)
|
|
277
|
+
expect(result).toBeUndefined()
|
|
278
|
+
expect(uploadCallCount).toBe(0)
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
it("should include SOC address in returned chunk addresses", async () => {
|
|
282
|
+
const stores = createMockStores()
|
|
283
|
+
|
|
284
|
+
const syncAccount = createSyncAccount({
|
|
285
|
+
bee: {} as never,
|
|
286
|
+
...stores,
|
|
287
|
+
utilizationStore: {} as UtilizationStoreDB,
|
|
288
|
+
utilizationUploader: {
|
|
289
|
+
scheduleUpload: vi.fn().mockResolvedValue(undefined),
|
|
290
|
+
} as unknown as DebouncedUtilizationUploader,
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
const result = await syncAccount(TEST_ETH_ADDRESS_HEX)
|
|
294
|
+
expect(result).toBeDefined()
|
|
295
|
+
expect(result!.status).toBe("success")
|
|
296
|
+
if (result!.status !== "success") return
|
|
297
|
+
|
|
298
|
+
// Last address should be the SOC address from epoch feed update
|
|
299
|
+
const lastAddress = result.chunkAddresses[result.chunkAddresses.length - 1]
|
|
300
|
+
expect(lastAddress).toEqual(FAKE_SOC_ADDRESS)
|
|
301
|
+
})
|
|
302
|
+
})
|