@orbitmem/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/README.md +104 -0
  2. package/dist/agent/agent-adapter.d.ts +3 -0
  3. package/dist/agent/agent-adapter.d.ts.map +1 -0
  4. package/dist/agent/agent-adapter.js +3 -0
  5. package/dist/agent/agent-adapter.js.map +1 -0
  6. package/dist/agent/client.d.ts +5 -0
  7. package/dist/agent/client.d.ts.map +1 -0
  8. package/dist/agent/client.js +146 -0
  9. package/dist/agent/client.js.map +1 -0
  10. package/dist/agent/index.d.ts +2 -0
  11. package/dist/agent/index.d.ts.map +1 -0
  12. package/dist/agent/index.js +2 -0
  13. package/dist/agent/index.js.map +1 -0
  14. package/dist/client.d.ts +3 -0
  15. package/dist/client.d.ts.map +1 -0
  16. package/dist/client.js +118 -0
  17. package/dist/client.js.map +1 -0
  18. package/dist/contracts.d.ts +19 -0
  19. package/dist/contracts.d.ts.map +1 -0
  20. package/dist/contracts.js +28 -0
  21. package/dist/contracts.js.map +1 -0
  22. package/dist/data/index.d.ts +5 -0
  23. package/dist/data/index.d.ts.map +1 -0
  24. package/dist/data/index.js +5 -0
  25. package/dist/data/index.js.map +1 -0
  26. package/dist/data/orbitdb.d.ts +10 -0
  27. package/dist/data/orbitdb.d.ts.map +1 -0
  28. package/dist/data/orbitdb.js +39 -0
  29. package/dist/data/orbitdb.js.map +1 -0
  30. package/dist/data/pricing.d.ts +7 -0
  31. package/dist/data/pricing.d.ts.map +1 -0
  32. package/dist/data/pricing.js +55 -0
  33. package/dist/data/pricing.js.map +1 -0
  34. package/dist/data/serialization.d.ts +28 -0
  35. package/dist/data/serialization.d.ts.map +1 -0
  36. package/dist/data/serialization.js +76 -0
  37. package/dist/data/serialization.js.map +1 -0
  38. package/dist/data/vault.d.ts +21 -0
  39. package/dist/data/vault.d.ts.map +1 -0
  40. package/dist/data/vault.js +284 -0
  41. package/dist/data/vault.js.map +1 -0
  42. package/dist/discovery/discovery-layer.d.ts +3 -0
  43. package/dist/discovery/discovery-layer.d.ts.map +1 -0
  44. package/dist/discovery/discovery-layer.js +205 -0
  45. package/dist/discovery/discovery-layer.js.map +1 -0
  46. package/dist/discovery/index.d.ts +4 -0
  47. package/dist/discovery/index.d.ts.map +1 -0
  48. package/dist/discovery/index.js +4 -0
  49. package/dist/discovery/index.js.map +1 -0
  50. package/dist/discovery/mock-registry.d.ts +30 -0
  51. package/dist/discovery/mock-registry.d.ts.map +1 -0
  52. package/dist/discovery/mock-registry.js +71 -0
  53. package/dist/discovery/mock-registry.js.map +1 -0
  54. package/dist/discovery/on-chain-registry.d.ts +35 -0
  55. package/dist/discovery/on-chain-registry.d.ts.map +1 -0
  56. package/dist/discovery/on-chain-registry.js +199 -0
  57. package/dist/discovery/on-chain-registry.js.map +1 -0
  58. package/dist/encryption/aes.d.ts +15 -0
  59. package/dist/encryption/aes.d.ts.map +1 -0
  60. package/dist/encryption/aes.js +63 -0
  61. package/dist/encryption/aes.js.map +1 -0
  62. package/dist/encryption/encryption-layer.d.ts +8 -0
  63. package/dist/encryption/encryption-layer.d.ts.map +1 -0
  64. package/dist/encryption/encryption-layer.js +82 -0
  65. package/dist/encryption/encryption-layer.js.map +1 -0
  66. package/dist/encryption/index.d.ts +6 -0
  67. package/dist/encryption/index.d.ts.map +1 -0
  68. package/dist/encryption/index.js +4 -0
  69. package/dist/encryption/index.js.map +1 -0
  70. package/dist/encryption/lit.d.ts +23 -0
  71. package/dist/encryption/lit.d.ts.map +1 -0
  72. package/dist/encryption/lit.js +113 -0
  73. package/dist/encryption/lit.js.map +1 -0
  74. package/dist/encryption/vault-key.d.ts +37 -0
  75. package/dist/encryption/vault-key.d.ts.map +1 -0
  76. package/dist/encryption/vault-key.js +43 -0
  77. package/dist/encryption/vault-key.js.map +1 -0
  78. package/dist/identity/identity-layer.d.ts +3 -0
  79. package/dist/identity/identity-layer.d.ts.map +1 -0
  80. package/dist/identity/identity-layer.js +99 -0
  81. package/dist/identity/identity-layer.js.map +1 -0
  82. package/dist/identity/index.d.ts +4 -0
  83. package/dist/identity/index.d.ts.map +1 -0
  84. package/dist/identity/index.js +4 -0
  85. package/dist/identity/index.js.map +1 -0
  86. package/dist/identity/ows-adapter.d.ts +15 -0
  87. package/dist/identity/ows-adapter.d.ts.map +1 -0
  88. package/dist/identity/ows-adapter.js +67 -0
  89. package/dist/identity/ows-adapter.js.map +1 -0
  90. package/dist/identity/session.d.ts +10 -0
  91. package/dist/identity/session.d.ts.map +1 -0
  92. package/dist/identity/session.js +36 -0
  93. package/dist/identity/session.js.map +1 -0
  94. package/dist/index.d.ts +12 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +12 -0
  97. package/dist/index.js.map +1 -0
  98. package/dist/persistence/create-agent.d.ts +11 -0
  99. package/dist/persistence/create-agent.d.ts.map +1 -0
  100. package/dist/persistence/create-agent.js +47 -0
  101. package/dist/persistence/create-agent.js.map +1 -0
  102. package/dist/persistence/index.d.ts +3 -0
  103. package/dist/persistence/index.d.ts.map +1 -0
  104. package/dist/persistence/index.js +3 -0
  105. package/dist/persistence/index.js.map +1 -0
  106. package/dist/persistence/persistence-layer.d.ts +12 -0
  107. package/dist/persistence/persistence-layer.d.ts.map +1 -0
  108. package/dist/persistence/persistence-layer.js +194 -0
  109. package/dist/persistence/persistence-layer.js.map +1 -0
  110. package/dist/transport/index.d.ts +3 -0
  111. package/dist/transport/index.d.ts.map +1 -0
  112. package/dist/transport/index.js +3 -0
  113. package/dist/transport/index.js.map +1 -0
  114. package/dist/transport/relay-session.d.ts +41 -0
  115. package/dist/transport/relay-session.d.ts.map +1 -0
  116. package/dist/transport/relay-session.js +86 -0
  117. package/dist/transport/relay-session.js.map +1 -0
  118. package/dist/transport/transport-layer.d.ts +32 -0
  119. package/dist/transport/transport-layer.d.ts.map +1 -0
  120. package/dist/transport/transport-layer.js +110 -0
  121. package/dist/transport/transport-layer.js.map +1 -0
  122. package/dist/types.d.ts +1319 -0
  123. package/dist/types.d.ts.map +1 -0
  124. package/dist/types.js +7 -0
  125. package/dist/types.js.map +1 -0
  126. package/package.json +91 -0
  127. package/src/__tests__/client.test.ts +30 -0
  128. package/src/__tests__/orbitdb-availability.ts +8 -0
  129. package/src/agent/__tests__/agent-adapter.test.ts +50 -0
  130. package/src/agent/__tests__/client.test.ts +50 -0
  131. package/src/agent/agent-adapter.ts +2 -0
  132. package/src/agent/client.ts +158 -0
  133. package/src/agent/index.ts +1 -0
  134. package/src/client.ts +134 -0
  135. package/src/contracts.ts +44 -0
  136. package/src/data/__tests__/pricing.test.ts +73 -0
  137. package/src/data/__tests__/vault-encryption.test.ts +346 -0
  138. package/src/data/__tests__/vault.test.ts +75 -0
  139. package/src/data/index.ts +8 -0
  140. package/src/data/orbitdb.ts +47 -0
  141. package/src/data/pricing.ts +63 -0
  142. package/src/data/serialization.ts +108 -0
  143. package/src/data/vault.ts +382 -0
  144. package/src/discovery/__tests__/discovery.test.ts +49 -0
  145. package/src/discovery/__tests__/on-chain-registry.test.ts +176 -0
  146. package/src/discovery/discovery-layer.ts +244 -0
  147. package/src/discovery/index.ts +3 -0
  148. package/src/discovery/mock-registry.ts +96 -0
  149. package/src/discovery/on-chain-registry.ts +237 -0
  150. package/src/encryption/__tests__/aes.test.ts +64 -0
  151. package/src/encryption/__tests__/encryption-layer.test.ts +80 -0
  152. package/src/encryption/__tests__/lit.test.ts +97 -0
  153. package/src/encryption/aes.ts +109 -0
  154. package/src/encryption/encryption-layer.ts +100 -0
  155. package/src/encryption/index.ts +5 -0
  156. package/src/encryption/lit.ts +161 -0
  157. package/src/encryption/vault-key.ts +63 -0
  158. package/src/identity/__tests__/identity.test.ts +31 -0
  159. package/src/identity/__tests__/ows-adapter.test.ts +47 -0
  160. package/src/identity/identity-layer.ts +123 -0
  161. package/src/identity/index.ts +3 -0
  162. package/src/identity/ows-adapter.ts +80 -0
  163. package/src/identity/session.ts +57 -0
  164. package/src/index.ts +12 -0
  165. package/src/persistence/__tests__/create-agent.test.ts +9 -0
  166. package/src/persistence/__tests__/persistence.test.ts +242 -0
  167. package/src/persistence/create-agent.ts +55 -0
  168. package/src/persistence/index.ts +2 -0
  169. package/src/persistence/persistence-layer.ts +236 -0
  170. package/src/transport/__tests__/solana-transport.test.ts +112 -0
  171. package/src/transport/__tests__/transport.test.ts +84 -0
  172. package/src/transport/index.ts +2 -0
  173. package/src/transport/relay-session.ts +118 -0
  174. package/src/transport/transport-layer.ts +171 -0
  175. package/src/types/orbitdb.d.ts +9 -0
  176. package/src/types.ts +1496 -0
@@ -0,0 +1,108 @@
1
+ import type { AESEncryptedData, EncryptedData, LitEncryptedData } from "../types.js";
2
+
3
+ /** JSON-safe AES encrypted data (Uint8Array fields as base64 strings) */
4
+ export interface SerializedAESEncryptedData {
5
+ engine: "aes";
6
+ ciphertext: string;
7
+ iv: string;
8
+ authTag: string;
9
+ keyDerivation: {
10
+ source: "wallet-signature" | "password" | "raw";
11
+ salt: string;
12
+ kdf: "hkdf-sha256" | "pbkdf2-sha256";
13
+ iterations?: number;
14
+ };
15
+ }
16
+
17
+ /** JSON-safe Lit encrypted data */
18
+ export interface SerializedLitEncryptedData {
19
+ engine: "lit";
20
+ ciphertext: string;
21
+ dataToEncryptHash: string;
22
+ accessControlConditions: unknown[];
23
+ chain: string;
24
+ }
25
+
26
+ export type SerializedEncryptedData = SerializedAESEncryptedData | SerializedLitEncryptedData;
27
+
28
+ function uint8ToBase64(bytes: Uint8Array): string {
29
+ let binary = "";
30
+ for (const byte of bytes) {
31
+ binary += String.fromCharCode(byte);
32
+ }
33
+ return btoa(binary);
34
+ }
35
+
36
+ function base64ToUint8(b64: string): Uint8Array {
37
+ const binary = atob(b64);
38
+ const bytes = new Uint8Array(binary.length);
39
+ for (let i = 0; i < binary.length; i++) {
40
+ bytes[i] = binary.charCodeAt(i);
41
+ }
42
+ return bytes;
43
+ }
44
+
45
+ export function serializeEncrypted(data: EncryptedData): SerializedEncryptedData {
46
+ if (data.engine === "aes") {
47
+ const aes = data as AESEncryptedData;
48
+ return {
49
+ engine: "aes",
50
+ ciphertext: uint8ToBase64(aes.ciphertext),
51
+ iv: uint8ToBase64(aes.iv),
52
+ authTag: uint8ToBase64(aes.authTag),
53
+ keyDerivation: {
54
+ source: aes.keyDerivation.source,
55
+ salt: uint8ToBase64(aes.keyDerivation.salt),
56
+ kdf: aes.keyDerivation.kdf,
57
+ ...(aes.keyDerivation.iterations != null
58
+ ? { iterations: aes.keyDerivation.iterations }
59
+ : {}),
60
+ },
61
+ };
62
+ }
63
+ const lit = data as LitEncryptedData;
64
+ return {
65
+ engine: "lit",
66
+ ciphertext: uint8ToBase64(lit.ciphertext),
67
+ dataToEncryptHash: lit.dataToEncryptHash,
68
+ accessControlConditions: lit.accessControlConditions,
69
+ chain: lit.chain as string,
70
+ };
71
+ }
72
+
73
+ export function deserializeEncrypted(data: SerializedEncryptedData): EncryptedData {
74
+ if (data.engine === "aes") {
75
+ return {
76
+ engine: "aes",
77
+ ciphertext: base64ToUint8(data.ciphertext),
78
+ iv: base64ToUint8(data.iv),
79
+ authTag: base64ToUint8(data.authTag),
80
+ keyDerivation: {
81
+ source: data.keyDerivation.source,
82
+ salt: base64ToUint8(data.keyDerivation.salt),
83
+ kdf: data.keyDerivation.kdf,
84
+ ...(data.keyDerivation.iterations != null
85
+ ? { iterations: data.keyDerivation.iterations }
86
+ : {}),
87
+ },
88
+ };
89
+ }
90
+ return {
91
+ engine: "lit",
92
+ ciphertext: base64ToUint8(data.ciphertext),
93
+ dataToEncryptHash: data.dataToEncryptHash,
94
+ accessControlConditions: data.accessControlConditions as any,
95
+ chain: data.chain as any,
96
+ };
97
+ }
98
+
99
+ /** Check whether a stored value is a serialized encrypted blob */
100
+ export function isSerializedEncrypted(value: unknown): value is SerializedEncryptedData {
101
+ return (
102
+ value !== null &&
103
+ typeof value === "object" &&
104
+ "engine" in (value as Record<string, unknown>) &&
105
+ ((value as Record<string, unknown>).engine === "aes" ||
106
+ (value as Record<string, unknown>).engine === "lit")
107
+ );
108
+ }
@@ -0,0 +1,382 @@
1
+ // oxlint-disable-next-line typescript/triple-slash-reference
2
+ /// <reference path="../types/orbitdb.d.ts" />
3
+
4
+ import { useDatabaseType } from "@orbitdb/core";
5
+ import { Nested } from "@orbitdb/nested-db";
6
+
7
+ import type { AESEngine } from "../encryption/aes.js";
8
+ import type {
9
+ AESEncryptedData,
10
+ ChainFamily,
11
+ EncryptionEngine,
12
+ IDataLayer,
13
+ IEncryptionLayer,
14
+ LitAccessCondition,
15
+ LitAuthSig,
16
+ LitEncryptedData,
17
+ VaultEntry,
18
+ VaultPath,
19
+ Visibility,
20
+ WalletAddress,
21
+ } from "../types.js";
22
+ import {
23
+ deserializeEncrypted,
24
+ isSerializedEncrypted,
25
+ serializeEncrypted,
26
+ } from "./serialization.js";
27
+
28
+ // Register the Nested database type at the point of use so callers do not depend on setup order.
29
+ useDatabaseType(Nested);
30
+
31
+ function normalizePath(path: VaultPath): string {
32
+ return Array.isArray(path) ? path.join("/") : path;
33
+ }
34
+
35
+ export async function createVault(
36
+ orbitdb: any,
37
+ config: {
38
+ dbName?: string;
39
+ author?: WalletAddress;
40
+ authorChain?: ChainFamily;
41
+ /** AES engine for private/shared encryption */
42
+ aesEngine?: AESEngine;
43
+ /** Encryption layer for Lit Protocol operations */
44
+ encryptionLayer?: IEncryptionLayer;
45
+ },
46
+ ): Promise<
47
+ IDataLayer & {
48
+ close: () => Promise<void>;
49
+ db: any;
50
+ metaDb: any;
51
+ setDefaultKey: (key: CryptoKey) => void;
52
+ setAuthSig: (authSig: LitAuthSig) => void;
53
+ updateAccess: (
54
+ path: VaultPath,
55
+ newConditions: LitAccessCondition[],
56
+ opts?: { chain?: string },
57
+ ) => Promise<VaultEntry>;
58
+ }
59
+ > {
60
+ const db = await orbitdb.open(config.dbName ?? "orbitmem-vault", { type: "nested" });
61
+
62
+ // Metadata store: path -> { visibility, encrypted, encryptionEngine, author, authorChain, timestamp }
63
+ const metaDb = await orbitdb.open(`${config.dbName ?? "orbitmem-vault"}-meta`, {
64
+ type: "nested",
65
+ });
66
+
67
+ // Default AES key for private data — set via setDefaultKey() after wallet connect
68
+ let defaultKey: CryptoKey | undefined;
69
+ let litAuthSig: LitAuthSig | undefined;
70
+
71
+ function makeEntry<T>(
72
+ _path: string,
73
+ value: T,
74
+ visibility: Visibility,
75
+ encrypted: boolean,
76
+ engine?: EncryptionEngine,
77
+ ): VaultEntry<T> {
78
+ return {
79
+ value,
80
+ visibility,
81
+ author: config.author ?? ("0x0" as WalletAddress),
82
+ authorChain: config.authorChain ?? "evm",
83
+ timestamp: Date.now(),
84
+ encrypted,
85
+ encryptionEngine: engine,
86
+ hash: "",
87
+ };
88
+ }
89
+
90
+ /** Encrypt a value for storage. Returns the serialized blob or the raw value (public). */
91
+ async function encryptValue(
92
+ value: unknown,
93
+ visibility: Visibility,
94
+ engine: EncryptionEngine | null,
95
+ opts?: any,
96
+ ): Promise<unknown> {
97
+ if (visibility === "public" || !engine) return value;
98
+
99
+ const plaintext = new TextEncoder().encode(JSON.stringify(value));
100
+
101
+ if (engine === "aes") {
102
+ if (!config.aesEngine)
103
+ throw new Error("AES engine not configured — pass aesEngine to createVault");
104
+ let cryptoKey: CryptoKey | undefined;
105
+ if (visibility === "private") {
106
+ cryptoKey = defaultKey;
107
+ if (!cryptoKey) throw new Error("No default key — call setDefaultKey() or connect() first");
108
+ } else if (visibility === "shared" && opts?.sharedKeySource) {
109
+ cryptoKey = await config.aesEngine.deriveKey(opts.sharedKeySource);
110
+ }
111
+ if (!cryptoKey) throw new Error("No encryption key available");
112
+ const encrypted = await config.aesEngine.encrypt(plaintext, cryptoKey);
113
+ return serializeEncrypted(encrypted);
114
+ }
115
+
116
+ if (engine === "lit") {
117
+ if (!config.encryptionLayer) throw new Error("Encryption layer not configured for Lit");
118
+ if (!opts?.accessConditions) throw new Error("accessConditions required for Lit encryption");
119
+ const encrypted = await config.encryptionLayer.encrypt(plaintext, {
120
+ engine: "lit",
121
+ accessConditions: opts.accessConditions,
122
+ });
123
+ return serializeEncrypted(encrypted);
124
+ }
125
+
126
+ return value;
127
+ }
128
+
129
+ /** Attempt auto-decryption of a stored value. Returns the decrypted value or the raw value. */
130
+ async function tryDecrypt(rawValue: unknown, meta: any): Promise<unknown> {
131
+ if (!meta?.encrypted || !isSerializedEncrypted(rawValue)) return rawValue;
132
+
133
+ const encrypted = deserializeEncrypted(rawValue);
134
+
135
+ if (
136
+ encrypted.engine === "aes" &&
137
+ config.aesEngine &&
138
+ meta.visibility === "private" &&
139
+ defaultKey
140
+ ) {
141
+ try {
142
+ const decrypted = await config.aesEngine.decrypt(encrypted as AESEncryptedData, defaultKey);
143
+ return JSON.parse(new TextDecoder().decode(decrypted));
144
+ } catch {
145
+ // Decryption failed — return raw blob
146
+ return rawValue;
147
+ }
148
+ }
149
+
150
+ if (encrypted.engine === "lit" && config.encryptionLayer && litAuthSig) {
151
+ try {
152
+ const decrypted = await config.encryptionLayer.decrypt(encrypted, { authSig: litAuthSig });
153
+ return JSON.parse(new TextDecoder().decode(decrypted));
154
+ } catch {
155
+ return rawValue;
156
+ }
157
+ }
158
+
159
+ // shared+aes (no internal key) or lit (no authSig) — return encrypted blob
160
+ return rawValue;
161
+ }
162
+
163
+ const vaultImpl: IDataLayer & {
164
+ close: () => Promise<void>;
165
+ db: any;
166
+ metaDb: any;
167
+ setDefaultKey: (key: CryptoKey) => void;
168
+ setAuthSig: (authSig: LitAuthSig) => void;
169
+ updateAccess: (
170
+ path: VaultPath,
171
+ newConditions: LitAccessCondition[],
172
+ opts?: { chain?: string },
173
+ ) => Promise<VaultEntry>;
174
+ } = {
175
+ db,
176
+ metaDb,
177
+
178
+ setDefaultKey(key: CryptoKey) {
179
+ defaultKey = key;
180
+ },
181
+
182
+ setAuthSig(authSig: LitAuthSig) {
183
+ litAuthSig = authSig;
184
+ },
185
+
186
+ async put(path, value, opts) {
187
+ const key = normalizePath(path);
188
+ const visibility = opts?.visibility ?? "private";
189
+ const encrypted = visibility !== "public";
190
+ const engine = encrypted ? (opts?.engine ?? "aes") : null;
191
+
192
+ const storedValue = await encryptValue(value, visibility, engine, opts);
193
+ const hash = await db.put(key, storedValue);
194
+ const meta: Record<string, any> = { visibility, encrypted, timestamp: Date.now() };
195
+ if (engine) meta.encryptionEngine = engine;
196
+ await metaDb.put(key, meta);
197
+
198
+ return { ...makeEntry(key, value, visibility, encrypted, engine ?? undefined), hash };
199
+ },
200
+
201
+ async insert(obj, opts) {
202
+ const visibility = opts?.visibility ?? "private";
203
+ const prefix = opts?.prefix;
204
+
205
+ // Flatten the object and put each leaf
206
+ const flatten = (o: any, parentKey: string = ""): [string, any][] => {
207
+ const entries: [string, any][] = [];
208
+ for (const [k, v] of Object.entries(o)) {
209
+ const newKey = parentKey ? `${parentKey}/${k}` : k;
210
+ if (v && typeof v === "object" && !Array.isArray(v)) {
211
+ entries.push(...flatten(v, newKey));
212
+ } else {
213
+ entries.push([newKey, v]);
214
+ }
215
+ }
216
+ return entries;
217
+ };
218
+
219
+ const leaves = flatten(obj, prefix ?? "");
220
+ for (const [leafKey, leafValue] of leaves) {
221
+ await vaultImpl.put(leafKey, leafValue, { visibility, engine: opts?.engine as any });
222
+ }
223
+ },
224
+
225
+ async get<T = unknown>(path: VaultPath): Promise<VaultEntry<T> | null> {
226
+ const key = normalizePath(path);
227
+ const rawValue = await db.get(key);
228
+ if (rawValue === undefined) return null;
229
+
230
+ const meta = await metaDb.get(key);
231
+ const visibility = meta?.visibility ?? "private";
232
+ const encEngine = meta?.encryptionEngine;
233
+ const value = await tryDecrypt(rawValue, meta);
234
+ return makeEntry(
235
+ key,
236
+ value,
237
+ visibility,
238
+ meta?.encrypted ?? visibility !== "public",
239
+ encEngine,
240
+ ) as VaultEntry<T>;
241
+ },
242
+
243
+ async del(path) {
244
+ const key = normalizePath(path);
245
+ await db.del(key);
246
+ await metaDb.del(key);
247
+ },
248
+
249
+ async keys(prefix?) {
250
+ const all = await db.all();
251
+ const flatten = (o: any, parentKey: string = ""): string[] => {
252
+ const keys: string[] = [];
253
+ if (o && typeof o === "object" && !Array.isArray(o) && !isSerializedEncrypted(o)) {
254
+ for (const [k, v] of Object.entries(o)) {
255
+ const newKey = parentKey ? `${parentKey}/${k}` : k;
256
+ keys.push(...flatten(v, newKey));
257
+ }
258
+ } else {
259
+ keys.push(parentKey);
260
+ }
261
+ return keys;
262
+ };
263
+ const allKeys = flatten(all);
264
+ if (prefix) return allKeys.filter((k) => k.startsWith(prefix));
265
+ return allKeys;
266
+ },
267
+
268
+ async all() {
269
+ return db.all() as any;
270
+ },
271
+
272
+ async query(filter) {
273
+ const allData = await db.all();
274
+ const results: VaultEntry[] = [];
275
+ const flatten = (o: any, parentKey: string = ""): [string, any][] => {
276
+ const entries: [string, any][] = [];
277
+ if (o && typeof o === "object" && !Array.isArray(o) && !isSerializedEncrypted(o)) {
278
+ for (const [k, v] of Object.entries(o)) {
279
+ const newKey = parentKey ? `${parentKey}/${k}` : k;
280
+ entries.push(...flatten(v, newKey));
281
+ }
282
+ } else {
283
+ entries.push([parentKey, o]);
284
+ }
285
+ return entries;
286
+ };
287
+ const leaves = flatten(allData);
288
+ for (const [key, value] of leaves) {
289
+ if (filter.prefix && !key.startsWith(filter.prefix)) continue;
290
+ const meta = await metaDb.get(key);
291
+ if (filter.visibility && meta?.visibility !== filter.visibility) continue;
292
+ if (filter.since && (meta?.timestamp ?? 0) < filter.since) continue;
293
+ const resolved = await tryDecrypt(value, meta);
294
+ results.push(
295
+ makeEntry(key, resolved, meta?.visibility ?? "private", meta?.encrypted ?? true),
296
+ );
297
+ if (filter.limit && results.length >= filter.limit) break;
298
+ }
299
+ return results as any;
300
+ },
301
+
302
+ async sync() {
303
+ return {
304
+ syncing: false,
305
+ pendingPush: 0,
306
+ pendingPull: 0,
307
+ lastSynced: Date.now(),
308
+ connectedPeers: 0,
309
+ };
310
+ },
311
+
312
+ getSyncStatus() {
313
+ return {
314
+ syncing: false,
315
+ pendingPush: 0,
316
+ pendingPull: 0,
317
+ lastSynced: null,
318
+ connectedPeers: 0,
319
+ };
320
+ },
321
+
322
+ onChange(callback) {
323
+ const handler = (entry: any) => {
324
+ callback({ type: "put", path: entry?.payload?.key ?? "", entry: undefined });
325
+ };
326
+ db.events.on("update", handler);
327
+ return () => db.events.off("update", handler);
328
+ },
329
+
330
+ async exportSnapshot() {
331
+ const allData = await db.all();
332
+ const data = new TextEncoder().encode(JSON.stringify(allData));
333
+ return { data, entryCount: Object.keys(allData).length, timestamp: Date.now() };
334
+ },
335
+
336
+ async importSnapshot(data) {
337
+ const obj = JSON.parse(new TextDecoder().decode(data));
338
+ await db.insert(obj);
339
+ return { merged: Object.keys(obj).length, conflicts: 0 };
340
+ },
341
+
342
+ async updateAccess(path, newConditions, opts) {
343
+ if (!config.encryptionLayer) throw new Error("Encryption layer not configured for Lit");
344
+ if (!litAuthSig) throw new Error("No authSig — call setAuthSig() first");
345
+
346
+ const key = normalizePath(path);
347
+ const rawValue = await db.get(key);
348
+ if (rawValue === undefined) throw new Error(`Not found: ${key}`);
349
+
350
+ const meta = await metaDb.get(key);
351
+ if (meta?.encryptionEngine !== "lit")
352
+ throw new Error(`Entry "${key}" is not Lit-encrypted (engine: ${meta?.encryptionEngine})`);
353
+ if (!isSerializedEncrypted(rawValue)) throw new Error(`Entry "${key}" is not encrypted`);
354
+
355
+ // Decrypt with current conditions
356
+ const encrypted = deserializeEncrypted(rawValue) as LitEncryptedData;
357
+ const plaintext = await config.encryptionLayer.decrypt(encrypted, { authSig: litAuthSig });
358
+
359
+ // Re-encrypt with new conditions
360
+ const reEncrypted = await config.encryptionLayer.encrypt(plaintext, {
361
+ engine: "lit",
362
+ accessConditions: newConditions,
363
+ chain: opts?.chain as any,
364
+ });
365
+ const serialized = serializeEncrypted(reEncrypted);
366
+
367
+ // Write back
368
+ await db.put(key, serialized);
369
+ await metaDb.put(key, { ...meta, timestamp: Date.now() });
370
+
371
+ const value = JSON.parse(new TextDecoder().decode(plaintext));
372
+ return makeEntry(key, value, meta.visibility, true, "lit");
373
+ },
374
+
375
+ async close() {
376
+ await db.close();
377
+ await metaDb.close();
378
+ },
379
+ };
380
+
381
+ return vaultImpl;
382
+ }
@@ -0,0 +1,49 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { createDiscoveryLayer } from "../discovery-layer.js";
4
+
5
+ describe("DiscoveryLayer (mock)", () => {
6
+ const discovery = createDiscoveryLayer({
7
+ dataRegistry: "0xDATA_REG" as any,
8
+ reputationRegistry: "0xREP_REG" as any,
9
+ registryChain: "base",
10
+ });
11
+
12
+ test("registerData and findData", async () => {
13
+ const reg = await discovery.registerData({
14
+ key: "travel/dietary",
15
+ name: "Dietary Preferences",
16
+ description: "Dietary restrictions",
17
+ schema: "orbitmem:dietary:v1",
18
+ tags: ["verified", "human-curated"],
19
+ });
20
+ expect(reg.dataId).toBeGreaterThan(0);
21
+ expect(reg.name).toBe("Dietary Preferences");
22
+
23
+ const results = await discovery.findData({ schema: "orbitmem:dietary:v1" });
24
+ expect(results).toHaveLength(1);
25
+ expect(results[0].vaultKey).toBe("travel/dietary");
26
+ });
27
+
28
+ test("rateData and getDataScoreById", async () => {
29
+ // Register
30
+ const reg = await discovery.registerData({
31
+ key: "travel/budget",
32
+ name: "Budget",
33
+ description: "Budget range",
34
+ tags: ["verified"],
35
+ });
36
+
37
+ // Rate
38
+ await discovery.rateData({
39
+ dataId: reg.dataId,
40
+ value: 90,
41
+ qualityDimension: "accuracy",
42
+ tag1: "accurate",
43
+ });
44
+
45
+ const score = await discovery.getDataScoreById(reg.dataId);
46
+ expect(score.quality).toBeGreaterThan(0);
47
+ expect(score.totalFeedback).toBe(1);
48
+ });
49
+ });
@@ -0,0 +1,176 @@
1
+ import { beforeAll, describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ DataRegistryAbi,
5
+ DataRegistryBytecode,
6
+ FeedbackRegistryAbi,
7
+ FeedbackRegistryBytecode,
8
+ } from "@orbitmem/contracts";
9
+ import {
10
+ type Address,
11
+ createPublicClient,
12
+ createWalletClient,
13
+ http,
14
+ type PublicClient,
15
+ type WalletClient,
16
+ } from "viem";
17
+ import { privateKeyToAccount } from "viem/accounts";
18
+ import { foundry } from "viem/chains";
19
+
20
+ import { OnChainRegistry } from "../on-chain-registry.js";
21
+
22
+ // Skip all tests if Anvil is not running
23
+ async function isAnvilRunning(): Promise<boolean> {
24
+ try {
25
+ const res = await fetch("http://127.0.0.1:8545", {
26
+ method: "POST",
27
+ headers: { "Content-Type": "application/json" },
28
+ body: JSON.stringify({ jsonrpc: "2.0", method: "eth_chainId", params: [], id: 1 }),
29
+ });
30
+ return res.ok;
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ const ANVIL_AVAILABLE = await isAnvilRunning();
37
+
38
+ // Anvil default accounts
39
+ const ALICE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" as const;
40
+ const BOB_KEY = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" as const;
41
+
42
+ const aliceAccount = privateKeyToAccount(ALICE_KEY);
43
+ const bobAccount = privateKeyToAccount(BOB_KEY);
44
+
45
+ let publicClient: PublicClient;
46
+ let aliceWallet: WalletClient;
47
+ let bobWallet: WalletClient;
48
+ let dataRegAddr: Address;
49
+ let feedbackRegAddr: Address;
50
+
51
+ async function deployContract(
52
+ wallet: WalletClient,
53
+ pub: PublicClient,
54
+ abi: readonly any[],
55
+ bytecode: `0x${string}`,
56
+ ): Promise<Address> {
57
+ const hash = await wallet.deployContract({
58
+ abi,
59
+ bytecode,
60
+ account: wallet.account!,
61
+ chain: foundry,
62
+ });
63
+ const receipt = await pub.waitForTransactionReceipt({ hash });
64
+ return receipt.contractAddress!;
65
+ }
66
+
67
+ describe.skipIf(!ANVIL_AVAILABLE)("OnChainRegistry (Anvil)", () => {
68
+ beforeAll(async () => {
69
+ const transport = http("http://127.0.0.1:8545");
70
+
71
+ publicClient = createPublicClient({ chain: foundry, transport });
72
+ aliceWallet = createWalletClient({
73
+ chain: foundry,
74
+ transport,
75
+ account: aliceAccount,
76
+ });
77
+ bobWallet = createWalletClient({
78
+ chain: foundry,
79
+ transport,
80
+ account: bobAccount,
81
+ });
82
+
83
+ // Deploy contracts
84
+ dataRegAddr = await deployContract(
85
+ aliceWallet,
86
+ publicClient,
87
+ DataRegistryAbi,
88
+ DataRegistryBytecode,
89
+ );
90
+ feedbackRegAddr = await deployContract(
91
+ aliceWallet,
92
+ publicClient,
93
+ FeedbackRegistryAbi,
94
+ FeedbackRegistryBytecode,
95
+ );
96
+ });
97
+
98
+ test("register data and get score", async () => {
99
+ const aliceRegistry = new OnChainRegistry({
100
+ publicClient,
101
+ walletClient: aliceWallet,
102
+ dataRegistry: dataRegAddr,
103
+ feedbackRegistry: feedbackRegAddr,
104
+ });
105
+
106
+ const dataId = await aliceRegistry.registerData("ipfs://data-1");
107
+ expect(dataId).toBeGreaterThan(0);
108
+
109
+ // Bob rates Alice's data
110
+ const bobRegistry = new OnChainRegistry({
111
+ publicClient,
112
+ walletClient: bobWallet,
113
+ dataRegistry: dataRegAddr,
114
+ feedbackRegistry: feedbackRegAddr,
115
+ });
116
+
117
+ await bobRegistry.rateData(
118
+ dataId,
119
+ 90,
120
+ 0,
121
+ "accurate",
122
+ "",
123
+ "",
124
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
125
+ );
126
+
127
+ const score = await aliceRegistry.getDataScore(dataId);
128
+ expect(score.quality).toBe(90);
129
+ expect(score.totalFeedback).toBe(1);
130
+ });
131
+
132
+ test("tag scores tracked separately", async () => {
133
+ const aliceRegistry = new OnChainRegistry({
134
+ publicClient,
135
+ walletClient: aliceWallet,
136
+ dataRegistry: dataRegAddr,
137
+ feedbackRegistry: feedbackRegAddr,
138
+ });
139
+
140
+ const dataId = await aliceRegistry.registerData("ipfs://data-tags");
141
+
142
+ const bobRegistry = new OnChainRegistry({
143
+ publicClient,
144
+ walletClient: bobWallet,
145
+ dataRegistry: dataRegAddr,
146
+ feedbackRegistry: feedbackRegAddr,
147
+ });
148
+
149
+ await bobRegistry.rateData(
150
+ dataId,
151
+ 80,
152
+ 0,
153
+ "accurate",
154
+ "",
155
+ "",
156
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
157
+ );
158
+ await bobRegistry.rateData(
159
+ dataId,
160
+ 60,
161
+ 0,
162
+ "fresh",
163
+ "",
164
+ "",
165
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
166
+ );
167
+
168
+ const accurateScore = await aliceRegistry.getTagScore(dataRegAddr, dataId, "accurate");
169
+ expect(accurateScore.totalValue).toBe(80);
170
+ expect(accurateScore.count).toBe(1);
171
+
172
+ const freshScore = await aliceRegistry.getTagScore(dataRegAddr, dataId, "fresh");
173
+ expect(freshScore.totalValue).toBe(60);
174
+ expect(freshScore.count).toBe(1);
175
+ });
176
+ });