@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,595 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import { EthAddress, BatchId, PrivateKey } from "@ethersphere/bee-js"
|
|
3
|
+
import {
|
|
4
|
+
serializeAccountStateSnapshot,
|
|
5
|
+
deserializeAccountStateSnapshot,
|
|
6
|
+
AccountStateSnapshotSchemaV1,
|
|
7
|
+
} from "./account-state-snapshot"
|
|
8
|
+
import type { Account, ConnectedApp, PostageStamp } from "../schemas"
|
|
9
|
+
import {
|
|
10
|
+
TEST_ETH_ADDRESS_HEX,
|
|
11
|
+
TEST_IDENTITY_ADDRESS_HEX,
|
|
12
|
+
TEST_IDENTITY_ADDRESS_2_HEX,
|
|
13
|
+
TEST_BATCH_ID_HEX,
|
|
14
|
+
TEST_BATCH_ID_2_HEX,
|
|
15
|
+
TEST_PRIVATE_KEY_HEX,
|
|
16
|
+
createPasskeyAccount,
|
|
17
|
+
createEthereumAccount,
|
|
18
|
+
createAgentAccount,
|
|
19
|
+
createIdentity,
|
|
20
|
+
createConnectedApp,
|
|
21
|
+
createPostageStamp,
|
|
22
|
+
} from "../test-fixtures"
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Helper: extract metadata from an Account and serialize via the snapshot function.
|
|
26
|
+
*/
|
|
27
|
+
function serializeFromAccount(
|
|
28
|
+
account: Account,
|
|
29
|
+
identities: Parameters<typeof serializeAccountStateSnapshot>[0]["identities"],
|
|
30
|
+
connectedApps: Parameters<
|
|
31
|
+
typeof serializeAccountStateSnapshot
|
|
32
|
+
>[0]["connectedApps"],
|
|
33
|
+
postageStamps: Parameters<
|
|
34
|
+
typeof serializeAccountStateSnapshot
|
|
35
|
+
>[0]["postageStamps"],
|
|
36
|
+
) {
|
|
37
|
+
return serializeAccountStateSnapshot({
|
|
38
|
+
accountId: account.id.toHex(),
|
|
39
|
+
metadata: {
|
|
40
|
+
accountName: account.name,
|
|
41
|
+
defaultPostageStampBatchID: account.defaultPostageStampBatchID?.toHex(),
|
|
42
|
+
createdAt: account.createdAt,
|
|
43
|
+
lastModified: Date.now(),
|
|
44
|
+
},
|
|
45
|
+
identities,
|
|
46
|
+
connectedApps,
|
|
47
|
+
postageStamps,
|
|
48
|
+
timestamp: Date.now(),
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Round-trip Tests
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
describe("round-trip: serialize → JSON → deserialize", () => {
|
|
57
|
+
it("should round-trip a passkey account with identities, apps, and stamps", () => {
|
|
58
|
+
const account = createPasskeyAccount()
|
|
59
|
+
const identities = [createIdentity()]
|
|
60
|
+
const connectedApps = [createConnectedApp()]
|
|
61
|
+
const postageStamps = [createPostageStamp()]
|
|
62
|
+
|
|
63
|
+
const serialized = serializeFromAccount(
|
|
64
|
+
account,
|
|
65
|
+
identities,
|
|
66
|
+
connectedApps,
|
|
67
|
+
postageStamps,
|
|
68
|
+
)
|
|
69
|
+
const json = JSON.stringify(serialized)
|
|
70
|
+
const parsed = JSON.parse(json)
|
|
71
|
+
const result = deserializeAccountStateSnapshot(parsed)
|
|
72
|
+
|
|
73
|
+
expect(result.success).toBe(true)
|
|
74
|
+
if (!result.success) return
|
|
75
|
+
|
|
76
|
+
expect(result.data.accountId).toBe(TEST_ETH_ADDRESS_HEX)
|
|
77
|
+
expect(result.data.metadata.accountName).toBe("Test Passkey Account")
|
|
78
|
+
expect(result.data.identities).toHaveLength(1)
|
|
79
|
+
expect(result.data.identities[0].accountId).toBeInstanceOf(EthAddress)
|
|
80
|
+
expect(result.data.connectedApps).toHaveLength(1)
|
|
81
|
+
expect(result.data.connectedApps[0].appName).toBe("Test App")
|
|
82
|
+
expect(result.data.postageStamps).toHaveLength(1)
|
|
83
|
+
expect(result.data.postageStamps[0].batchID).toBeInstanceOf(BatchId)
|
|
84
|
+
expect(result.data.postageStamps[0].signerKey).toBeInstanceOf(PrivateKey)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it("should round-trip an ethereum account with metadata", () => {
|
|
88
|
+
const account = createEthereumAccount()
|
|
89
|
+
const identities = [createIdentity()]
|
|
90
|
+
const connectedApps: ConnectedApp[] = []
|
|
91
|
+
const postageStamps: PostageStamp[] = []
|
|
92
|
+
|
|
93
|
+
const serialized = serializeFromAccount(
|
|
94
|
+
account,
|
|
95
|
+
identities,
|
|
96
|
+
connectedApps,
|
|
97
|
+
postageStamps,
|
|
98
|
+
)
|
|
99
|
+
const json = JSON.stringify(serialized)
|
|
100
|
+
const parsed = JSON.parse(json)
|
|
101
|
+
const result = deserializeAccountStateSnapshot(parsed)
|
|
102
|
+
|
|
103
|
+
expect(result.success).toBe(true)
|
|
104
|
+
if (!result.success) return
|
|
105
|
+
|
|
106
|
+
expect(result.data.accountId).toBe(TEST_ETH_ADDRESS_HEX)
|
|
107
|
+
expect(result.data.metadata.accountName).toBe("Test Ethereum Account")
|
|
108
|
+
expect(result.data.metadata.createdAt).toBe(1700000000000)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it("should round-trip an agent account", () => {
|
|
112
|
+
const account = createAgentAccount()
|
|
113
|
+
const identities = [createIdentity()]
|
|
114
|
+
const connectedApps: ConnectedApp[] = []
|
|
115
|
+
const postageStamps: PostageStamp[] = []
|
|
116
|
+
|
|
117
|
+
const serialized = serializeFromAccount(
|
|
118
|
+
account,
|
|
119
|
+
identities,
|
|
120
|
+
connectedApps,
|
|
121
|
+
postageStamps,
|
|
122
|
+
)
|
|
123
|
+
const json = JSON.stringify(serialized)
|
|
124
|
+
const parsed = JSON.parse(json)
|
|
125
|
+
const result = deserializeAccountStateSnapshot(parsed)
|
|
126
|
+
|
|
127
|
+
expect(result.success).toBe(true)
|
|
128
|
+
if (!result.success) return
|
|
129
|
+
|
|
130
|
+
expect(result.data.metadata.accountName).toBe("Test Agent Account")
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it("should produce valid JSON for actual file I/O simulation", () => {
|
|
134
|
+
const account = createPasskeyAccount({
|
|
135
|
+
defaultPostageStampBatchID: new BatchId(TEST_BATCH_ID_HEX),
|
|
136
|
+
})
|
|
137
|
+
const identities = [
|
|
138
|
+
createIdentity({ settings: { appSessionDuration: 3600 } }),
|
|
139
|
+
]
|
|
140
|
+
const connectedApps = [
|
|
141
|
+
createConnectedApp({
|
|
142
|
+
appIcon: "https://example.com/icon.png",
|
|
143
|
+
appDescription: "A test app",
|
|
144
|
+
connectedUntil: 1700100000000,
|
|
145
|
+
}),
|
|
146
|
+
]
|
|
147
|
+
const postageStamps = [createPostageStamp({ batchTTL: 86400 })]
|
|
148
|
+
|
|
149
|
+
const serialized = serializeFromAccount(
|
|
150
|
+
account,
|
|
151
|
+
identities,
|
|
152
|
+
connectedApps,
|
|
153
|
+
postageStamps,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
// Simulate file write + read
|
|
157
|
+
const fileContent = JSON.stringify(serialized, undefined, 2)
|
|
158
|
+
const fileData = JSON.parse(fileContent)
|
|
159
|
+
const result = deserializeAccountStateSnapshot(fileData)
|
|
160
|
+
|
|
161
|
+
expect(result.success).toBe(true)
|
|
162
|
+
if (!result.success) return
|
|
163
|
+
|
|
164
|
+
expect(result.data.metadata.defaultPostageStampBatchID).toBe(
|
|
165
|
+
TEST_BATCH_ID_HEX,
|
|
166
|
+
)
|
|
167
|
+
expect(result.data.identities[0].settings?.appSessionDuration).toBe(3600)
|
|
168
|
+
expect(result.data.connectedApps[0].appIcon).toBe(
|
|
169
|
+
"https://example.com/icon.png",
|
|
170
|
+
)
|
|
171
|
+
expect(result.data.postageStamps[0].batchTTL).toBe(86400)
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// appSecret Persistence Tests
|
|
177
|
+
// ============================================================================
|
|
178
|
+
|
|
179
|
+
describe("appSecret in snapshots", () => {
|
|
180
|
+
it("should include appSecret in serialized export when present on input", () => {
|
|
181
|
+
const account = createPasskeyAccount()
|
|
182
|
+
const connectedApps = [createConnectedApp({ appSecret: "my-secret-value" })]
|
|
183
|
+
|
|
184
|
+
const serialized = serializeFromAccount(account, [], connectedApps, [])
|
|
185
|
+
|
|
186
|
+
const apps = serialized.connectedApps as Record<string, unknown>[]
|
|
187
|
+
expect(apps[0]).toHaveProperty("appSecret", "my-secret-value")
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it("should preserve appSecret through round-trip", () => {
|
|
191
|
+
const account = createPasskeyAccount()
|
|
192
|
+
const serialized = serializeFromAccount(account, [], [], [])
|
|
193
|
+
|
|
194
|
+
const raw = JSON.parse(JSON.stringify(serialized))
|
|
195
|
+
raw.connectedApps = [
|
|
196
|
+
{
|
|
197
|
+
appUrl: "https://example.com",
|
|
198
|
+
appName: "Test App",
|
|
199
|
+
lastConnectedAt: 1700000000000,
|
|
200
|
+
identityId: TEST_IDENTITY_ADDRESS_HEX,
|
|
201
|
+
appSecret: "preserved-secret",
|
|
202
|
+
},
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
const result = deserializeAccountStateSnapshot(raw)
|
|
206
|
+
|
|
207
|
+
expect(result.success).toBe(true)
|
|
208
|
+
if (!result.success) return
|
|
209
|
+
|
|
210
|
+
expect(result.data.connectedApps[0].appSecret).toBe("preserved-secret")
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
// ============================================================================
|
|
215
|
+
// Edge Cases
|
|
216
|
+
// ============================================================================
|
|
217
|
+
|
|
218
|
+
describe("edge cases", () => {
|
|
219
|
+
it("should handle empty arrays for identities, connectedApps, and postageStamps", () => {
|
|
220
|
+
const account = createPasskeyAccount()
|
|
221
|
+
const serialized = serializeFromAccount(account, [], [], [])
|
|
222
|
+
const result = deserializeAccountStateSnapshot(
|
|
223
|
+
JSON.parse(JSON.stringify(serialized)),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
expect(result.success).toBe(true)
|
|
227
|
+
if (!result.success) return
|
|
228
|
+
|
|
229
|
+
expect(result.data.identities).toEqual([])
|
|
230
|
+
expect(result.data.connectedApps).toEqual([])
|
|
231
|
+
expect(result.data.postageStamps).toEqual([])
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it("should handle optional fields absent on identity", () => {
|
|
235
|
+
const identity = createIdentity({
|
|
236
|
+
settings: undefined,
|
|
237
|
+
defaultPostageStampBatchID: undefined,
|
|
238
|
+
})
|
|
239
|
+
const serialized = serializeFromAccount(
|
|
240
|
+
createPasskeyAccount(),
|
|
241
|
+
[identity],
|
|
242
|
+
[],
|
|
243
|
+
[],
|
|
244
|
+
)
|
|
245
|
+
const result = deserializeAccountStateSnapshot(
|
|
246
|
+
JSON.parse(JSON.stringify(serialized)),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
expect(result.success).toBe(true)
|
|
250
|
+
if (!result.success) return
|
|
251
|
+
|
|
252
|
+
expect(result.data.identities[0].settings).toBeUndefined()
|
|
253
|
+
expect(result.data.identities[0].defaultPostageStampBatchID).toBeUndefined()
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it("should handle optional fields absent on connected app", () => {
|
|
257
|
+
const app = createConnectedApp({
|
|
258
|
+
appIcon: undefined,
|
|
259
|
+
appDescription: undefined,
|
|
260
|
+
connectedUntil: undefined,
|
|
261
|
+
appSecret: undefined,
|
|
262
|
+
})
|
|
263
|
+
const serialized = serializeFromAccount(
|
|
264
|
+
createPasskeyAccount(),
|
|
265
|
+
[],
|
|
266
|
+
[app],
|
|
267
|
+
[],
|
|
268
|
+
)
|
|
269
|
+
const result = deserializeAccountStateSnapshot(
|
|
270
|
+
JSON.parse(JSON.stringify(serialized)),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
expect(result.success).toBe(true)
|
|
274
|
+
if (!result.success) return
|
|
275
|
+
|
|
276
|
+
expect(result.data.connectedApps[0].appIcon).toBeUndefined()
|
|
277
|
+
expect(result.data.connectedApps[0].appDescription).toBeUndefined()
|
|
278
|
+
expect(result.data.connectedApps[0].connectedUntil).toBeUndefined()
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
it("should handle optional fields absent on postage stamp", () => {
|
|
282
|
+
const stamp = createPostageStamp({ batchTTL: undefined })
|
|
283
|
+
const serialized = serializeFromAccount(
|
|
284
|
+
createPasskeyAccount(),
|
|
285
|
+
[],
|
|
286
|
+
[],
|
|
287
|
+
[stamp],
|
|
288
|
+
)
|
|
289
|
+
const result = deserializeAccountStateSnapshot(
|
|
290
|
+
JSON.parse(JSON.stringify(serialized)),
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
expect(result.success).toBe(true)
|
|
294
|
+
if (!result.success) return
|
|
295
|
+
|
|
296
|
+
expect(result.data.postageStamps[0].batchTTL).toBeUndefined()
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it("should handle optional defaultPostageStampBatchID absent on account metadata", () => {
|
|
300
|
+
const account = createPasskeyAccount({
|
|
301
|
+
defaultPostageStampBatchID: undefined,
|
|
302
|
+
})
|
|
303
|
+
const serialized = serializeFromAccount(account, [], [], [])
|
|
304
|
+
const result = deserializeAccountStateSnapshot(
|
|
305
|
+
JSON.parse(JSON.stringify(serialized)),
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
expect(result.success).toBe(true)
|
|
309
|
+
if (!result.success) return
|
|
310
|
+
|
|
311
|
+
expect(result.data.metadata.defaultPostageStampBatchID).toBeUndefined()
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it("should handle multiple entities of each type", () => {
|
|
315
|
+
const account = createPasskeyAccount()
|
|
316
|
+
const identities = [
|
|
317
|
+
createIdentity({ id: TEST_IDENTITY_ADDRESS_HEX, name: "Identity One" }),
|
|
318
|
+
createIdentity({ id: TEST_IDENTITY_ADDRESS_2_HEX, name: "Identity Two" }),
|
|
319
|
+
createIdentity({ id: "3".repeat(40), name: "Identity Three" }),
|
|
320
|
+
]
|
|
321
|
+
const connectedApps = [
|
|
322
|
+
createConnectedApp({
|
|
323
|
+
appUrl: "https://app1.example.com",
|
|
324
|
+
identityId: TEST_IDENTITY_ADDRESS_HEX,
|
|
325
|
+
}),
|
|
326
|
+
createConnectedApp({
|
|
327
|
+
appUrl: "https://app2.example.com",
|
|
328
|
+
identityId: TEST_IDENTITY_ADDRESS_2_HEX,
|
|
329
|
+
}),
|
|
330
|
+
]
|
|
331
|
+
const postageStamps = [
|
|
332
|
+
createPostageStamp({ batchID: new BatchId(TEST_BATCH_ID_HEX) }),
|
|
333
|
+
createPostageStamp({ batchID: new BatchId(TEST_BATCH_ID_2_HEX) }),
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
const serialized = serializeFromAccount(
|
|
337
|
+
account,
|
|
338
|
+
identities,
|
|
339
|
+
connectedApps,
|
|
340
|
+
postageStamps,
|
|
341
|
+
)
|
|
342
|
+
const result = deserializeAccountStateSnapshot(
|
|
343
|
+
JSON.parse(JSON.stringify(serialized)),
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
expect(result.success).toBe(true)
|
|
347
|
+
if (!result.success) return
|
|
348
|
+
|
|
349
|
+
expect(result.data.identities).toHaveLength(3)
|
|
350
|
+
expect(result.data.connectedApps).toHaveLength(2)
|
|
351
|
+
expect(result.data.postageStamps).toHaveLength(2)
|
|
352
|
+
})
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
// ============================================================================
|
|
356
|
+
// Invalid Data Rejection
|
|
357
|
+
// ============================================================================
|
|
358
|
+
|
|
359
|
+
describe("invalid data rejection", () => {
|
|
360
|
+
it("should reject wrong version number", () => {
|
|
361
|
+
const serialized = serializeFromAccount(createPasskeyAccount(), [], [], [])
|
|
362
|
+
const raw = JSON.parse(JSON.stringify(serialized))
|
|
363
|
+
raw.version = 2
|
|
364
|
+
|
|
365
|
+
const result = deserializeAccountStateSnapshot(raw)
|
|
366
|
+
expect(result.success).toBe(false)
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
it("should reject missing version", () => {
|
|
370
|
+
const serialized = serializeFromAccount(createPasskeyAccount(), [], [], [])
|
|
371
|
+
const raw = JSON.parse(JSON.stringify(serialized))
|
|
372
|
+
delete raw.version
|
|
373
|
+
|
|
374
|
+
const result = deserializeAccountStateSnapshot(raw)
|
|
375
|
+
expect(result.success).toBe(false)
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
it("should reject missing accountId", () => {
|
|
379
|
+
const serialized = serializeFromAccount(createPasskeyAccount(), [], [], [])
|
|
380
|
+
const raw = JSON.parse(JSON.stringify(serialized))
|
|
381
|
+
delete raw.accountId
|
|
382
|
+
|
|
383
|
+
const result = deserializeAccountStateSnapshot(raw)
|
|
384
|
+
expect(result.success).toBe(false)
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
it("should reject missing metadata", () => {
|
|
388
|
+
const serialized = serializeFromAccount(createPasskeyAccount(), [], [], [])
|
|
389
|
+
const raw = JSON.parse(JSON.stringify(serialized))
|
|
390
|
+
delete raw.metadata
|
|
391
|
+
|
|
392
|
+
const result = deserializeAccountStateSnapshot(raw)
|
|
393
|
+
expect(result.success).toBe(false)
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
it("should reject invalid accountId hex length", () => {
|
|
397
|
+
const serialized = serializeFromAccount(createPasskeyAccount(), [], [], [])
|
|
398
|
+
const raw = JSON.parse(JSON.stringify(serialized))
|
|
399
|
+
raw.accountId = "abc" // too short
|
|
400
|
+
|
|
401
|
+
const result = deserializeAccountStateSnapshot(raw)
|
|
402
|
+
expect(result.success).toBe(false)
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
it("should reject invalid BatchId hex length", () => {
|
|
406
|
+
const raw = {
|
|
407
|
+
version: 1,
|
|
408
|
+
timestamp: Date.now(),
|
|
409
|
+
accountId: TEST_ETH_ADDRESS_HEX,
|
|
410
|
+
metadata: {
|
|
411
|
+
accountName: "Test",
|
|
412
|
+
createdAt: 1700000000000,
|
|
413
|
+
lastModified: Date.now(),
|
|
414
|
+
},
|
|
415
|
+
identities: [],
|
|
416
|
+
connectedApps: [],
|
|
417
|
+
postageStamps: [
|
|
418
|
+
{
|
|
419
|
+
accountId: TEST_ETH_ADDRESS_HEX,
|
|
420
|
+
batchID: "short",
|
|
421
|
+
signerKey: TEST_PRIVATE_KEY_HEX,
|
|
422
|
+
utilization: 0,
|
|
423
|
+
usable: true,
|
|
424
|
+
depth: 20,
|
|
425
|
+
amount: "100000000",
|
|
426
|
+
bucketDepth: 16,
|
|
427
|
+
blockNumber: 12345678,
|
|
428
|
+
immutableFlag: false,
|
|
429
|
+
exists: true,
|
|
430
|
+
createdAt: 1700000000000,
|
|
431
|
+
},
|
|
432
|
+
],
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const result = deserializeAccountStateSnapshot(raw)
|
|
436
|
+
expect(result.success).toBe(false)
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
it("should reject invalid PrivateKey hex length", () => {
|
|
440
|
+
const raw = {
|
|
441
|
+
version: 1,
|
|
442
|
+
timestamp: Date.now(),
|
|
443
|
+
accountId: TEST_ETH_ADDRESS_HEX,
|
|
444
|
+
metadata: {
|
|
445
|
+
accountName: "Test",
|
|
446
|
+
createdAt: 1700000000000,
|
|
447
|
+
lastModified: Date.now(),
|
|
448
|
+
},
|
|
449
|
+
identities: [],
|
|
450
|
+
connectedApps: [],
|
|
451
|
+
postageStamps: [
|
|
452
|
+
{
|
|
453
|
+
accountId: TEST_ETH_ADDRESS_HEX,
|
|
454
|
+
batchID: TEST_BATCH_ID_HEX,
|
|
455
|
+
signerKey: "short",
|
|
456
|
+
utilization: 0,
|
|
457
|
+
usable: true,
|
|
458
|
+
depth: 20,
|
|
459
|
+
amount: "100000000",
|
|
460
|
+
bucketDepth: 16,
|
|
461
|
+
blockNumber: 12345678,
|
|
462
|
+
immutableFlag: false,
|
|
463
|
+
exists: true,
|
|
464
|
+
createdAt: 1700000000000,
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const result = deserializeAccountStateSnapshot(raw)
|
|
470
|
+
expect(result.success).toBe(false)
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
it("should reject number where string is expected", () => {
|
|
474
|
+
const serialized = serializeFromAccount(createPasskeyAccount(), [], [], [])
|
|
475
|
+
const raw = JSON.parse(JSON.stringify(serialized))
|
|
476
|
+
raw.metadata.accountName = 12345
|
|
477
|
+
|
|
478
|
+
const result = deserializeAccountStateSnapshot(raw)
|
|
479
|
+
expect(result.success).toBe(false)
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
it("should reject string where array is expected", () => {
|
|
483
|
+
const serialized = serializeFromAccount(createPasskeyAccount(), [], [], [])
|
|
484
|
+
const raw = JSON.parse(JSON.stringify(serialized))
|
|
485
|
+
raw.identities = "not-an-array"
|
|
486
|
+
|
|
487
|
+
const result = deserializeAccountStateSnapshot(raw)
|
|
488
|
+
expect(result.success).toBe(false)
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
it("should reject non-object input (string)", () => {
|
|
492
|
+
const result = deserializeAccountStateSnapshot("not-an-object")
|
|
493
|
+
expect(result.success).toBe(false)
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
it("should reject non-object input (number)", () => {
|
|
497
|
+
const result = deserializeAccountStateSnapshot(42)
|
|
498
|
+
expect(result.success).toBe(false)
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
it("should reject non-object input (undefined)", () => {
|
|
502
|
+
const result = deserializeAccountStateSnapshot(undefined)
|
|
503
|
+
expect(result.success).toBe(false)
|
|
504
|
+
})
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
// ============================================================================
|
|
508
|
+
// bee-js Type Conversions
|
|
509
|
+
// ============================================================================
|
|
510
|
+
|
|
511
|
+
describe("bee-js type conversions", () => {
|
|
512
|
+
it("should convert hex strings to EthAddress instances in identities", () => {
|
|
513
|
+
const raw = {
|
|
514
|
+
version: 1,
|
|
515
|
+
timestamp: Date.now(),
|
|
516
|
+
accountId: TEST_ETH_ADDRESS_HEX,
|
|
517
|
+
metadata: {
|
|
518
|
+
accountName: "Test",
|
|
519
|
+
createdAt: 1700000000000,
|
|
520
|
+
lastModified: Date.now(),
|
|
521
|
+
},
|
|
522
|
+
identities: [
|
|
523
|
+
{
|
|
524
|
+
id: TEST_IDENTITY_ADDRESS_HEX,
|
|
525
|
+
accountId: TEST_ETH_ADDRESS_HEX,
|
|
526
|
+
name: "Identity",
|
|
527
|
+
createdAt: 1700000000000,
|
|
528
|
+
},
|
|
529
|
+
],
|
|
530
|
+
connectedApps: [],
|
|
531
|
+
postageStamps: [],
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const result = deserializeAccountStateSnapshot(raw)
|
|
535
|
+
|
|
536
|
+
expect(result.success).toBe(true)
|
|
537
|
+
if (!result.success) return
|
|
538
|
+
|
|
539
|
+
expect(result.data.identities[0].accountId).toBeInstanceOf(EthAddress)
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
it("should convert hex strings to BatchId and PrivateKey instances", () => {
|
|
543
|
+
const raw = {
|
|
544
|
+
version: 1,
|
|
545
|
+
timestamp: Date.now(),
|
|
546
|
+
accountId: TEST_ETH_ADDRESS_HEX,
|
|
547
|
+
metadata: {
|
|
548
|
+
accountName: "Test",
|
|
549
|
+
createdAt: 1700000000000,
|
|
550
|
+
lastModified: Date.now(),
|
|
551
|
+
},
|
|
552
|
+
identities: [],
|
|
553
|
+
connectedApps: [],
|
|
554
|
+
postageStamps: [
|
|
555
|
+
{
|
|
556
|
+
accountId: TEST_ETH_ADDRESS_HEX,
|
|
557
|
+
batchID: TEST_BATCH_ID_HEX,
|
|
558
|
+
signerKey: TEST_PRIVATE_KEY_HEX,
|
|
559
|
+
utilization: 0,
|
|
560
|
+
usable: true,
|
|
561
|
+
depth: 20,
|
|
562
|
+
amount: "100000000",
|
|
563
|
+
bucketDepth: 16,
|
|
564
|
+
blockNumber: 12345678,
|
|
565
|
+
immutableFlag: false,
|
|
566
|
+
exists: true,
|
|
567
|
+
createdAt: 1700000000000,
|
|
568
|
+
},
|
|
569
|
+
],
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const result = deserializeAccountStateSnapshot(raw)
|
|
573
|
+
|
|
574
|
+
expect(result.success).toBe(true)
|
|
575
|
+
if (!result.success) return
|
|
576
|
+
|
|
577
|
+
expect(result.data.postageStamps[0].batchID).toBeInstanceOf(BatchId)
|
|
578
|
+
expect(result.data.postageStamps[0].batchID.toHex()).toBe(TEST_BATCH_ID_HEX)
|
|
579
|
+
expect(result.data.postageStamps[0].signerKey).toBeInstanceOf(PrivateKey)
|
|
580
|
+
expect(result.data.postageStamps[0].signerKey.toHex()).toBe(
|
|
581
|
+
TEST_PRIVATE_KEY_HEX,
|
|
582
|
+
)
|
|
583
|
+
})
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
// ============================================================================
|
|
587
|
+
// Schema Export
|
|
588
|
+
// ============================================================================
|
|
589
|
+
|
|
590
|
+
describe("AccountStateSnapshotSchemaV1", () => {
|
|
591
|
+
it("should be exported and usable for direct validation", () => {
|
|
592
|
+
expect(AccountStateSnapshotSchemaV1).toBeDefined()
|
|
593
|
+
expect(typeof AccountStateSnapshotSchemaV1.safeParse).toBe("function")
|
|
594
|
+
})
|
|
595
|
+
})
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account State Snapshot Module
|
|
3
|
+
*
|
|
4
|
+
* Shared serialization for account state snapshots used by both
|
|
5
|
+
* file export (.swarmid) and Swarm sync flows.
|
|
6
|
+
*
|
|
7
|
+
* appSecret is included in snapshots so that backups preserve app connections.
|
|
8
|
+
* Since the backup is encrypted with the master key (and appSecret is
|
|
9
|
+
* deterministically derivable from it), this doesn't change the threat model.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { z } from "zod"
|
|
13
|
+
import { AccountStateSnapshotSchemaV1 } from "../schemas"
|
|
14
|
+
import type {
|
|
15
|
+
Identity,
|
|
16
|
+
ConnectedApp,
|
|
17
|
+
PostageStamp,
|
|
18
|
+
AccountMetadata,
|
|
19
|
+
AccountStateSnapshot,
|
|
20
|
+
} from "../schemas"
|
|
21
|
+
import {
|
|
22
|
+
serializeIdentity,
|
|
23
|
+
serializeConnectedApp,
|
|
24
|
+
serializePostageStamp,
|
|
25
|
+
} from "./storage-managers"
|
|
26
|
+
|
|
27
|
+
// Re-export schema and types for consumers
|
|
28
|
+
export { AccountStateSnapshotSchemaV1 } from "../schemas"
|
|
29
|
+
export type { AccountStateSnapshot } from "../schemas"
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Constants
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
const ACCOUNT_STATE_SNAPSHOT_VERSION = 1
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Types
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
export type AccountStateSnapshotResult =
|
|
42
|
+
| { success: true; data: AccountStateSnapshot }
|
|
43
|
+
| { success: false; error: z.ZodError }
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Serialize
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Serialize account data into a plain object suitable for JSON encoding.
|
|
51
|
+
*/
|
|
52
|
+
export function serializeAccountStateSnapshot(input: {
|
|
53
|
+
accountId: string
|
|
54
|
+
metadata: AccountMetadata
|
|
55
|
+
identities: Identity[]
|
|
56
|
+
connectedApps: ConnectedApp[]
|
|
57
|
+
postageStamps: PostageStamp[]
|
|
58
|
+
timestamp: number
|
|
59
|
+
}): Record<string, unknown> {
|
|
60
|
+
return {
|
|
61
|
+
version: ACCOUNT_STATE_SNAPSHOT_VERSION,
|
|
62
|
+
timestamp: input.timestamp,
|
|
63
|
+
accountId: input.accountId,
|
|
64
|
+
metadata: {
|
|
65
|
+
accountName: input.metadata.accountName,
|
|
66
|
+
defaultPostageStampBatchID: input.metadata.defaultPostageStampBatchID,
|
|
67
|
+
createdAt: input.metadata.createdAt,
|
|
68
|
+
lastModified: input.metadata.lastModified,
|
|
69
|
+
},
|
|
70
|
+
identities: input.identities.map(serializeIdentity),
|
|
71
|
+
connectedApps: input.connectedApps.map(serializeConnectedApp),
|
|
72
|
+
postageStamps: input.postageStamps.map(serializePostageStamp),
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// Deserialize
|
|
78
|
+
// ============================================================================
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Deserialize and validate an account state snapshot.
|
|
82
|
+
* Returns a discriminated union: success with parsed data, or failure with Zod error.
|
|
83
|
+
*/
|
|
84
|
+
export function deserializeAccountStateSnapshot(
|
|
85
|
+
data: unknown,
|
|
86
|
+
): AccountStateSnapshotResult {
|
|
87
|
+
const result = AccountStateSnapshotSchemaV1.safeParse(data)
|
|
88
|
+
|
|
89
|
+
if (!result.success) {
|
|
90
|
+
return { success: false, error: result.error }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { success: true, data: result.data }
|
|
94
|
+
}
|