holosphere 2.0.0-alpha12 → 2.0.0-alpha13

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 (49) hide show
  1. package/dist/{2019-DQdDE6DG.js → 2019-CLMqIAfQ.js} +1722 -1668
  2. package/dist/{2019-DQdDE6DG.js.map → 2019-CLMqIAfQ.js.map} +1 -1
  3. package/dist/2019-Cp3uYhyY.cjs +8 -0
  4. package/dist/{2019-Ew-DTDlI.cjs.map → 2019-Cp3uYhyY.cjs.map} +1 -1
  5. package/dist/{browser-D2qtVhH5.cjs → browser-D6cNVl0v.cjs} +2 -2
  6. package/dist/{browser-D2qtVhH5.cjs.map → browser-D6cNVl0v.cjs.map} +1 -1
  7. package/dist/{browser-CckFyRI9.js → browser-nUQt1cnB.js} +2 -2
  8. package/dist/{browser-CckFyRI9.js.map → browser-nUQt1cnB.js.map} +1 -1
  9. package/dist/cjs/holosphere.cjs +1 -1
  10. package/dist/esm/holosphere.js +1 -1
  11. package/dist/{index-TDDyakLc.js → index-BN_uoxQK.js} +610 -128
  12. package/dist/index-BN_uoxQK.js.map +1 -0
  13. package/dist/{index-DrYM1LOY.js → index-CoAjtqsD.js} +2 -2
  14. package/dist/{index-DrYM1LOY.js.map → index-CoAjtqsD.js.map} +1 -1
  15. package/dist/{index-kyf1sjaC.cjs → index-Cp3tI53z.cjs} +2 -2
  16. package/dist/{index-kyf1sjaC.cjs.map → index-Cp3tI53z.cjs.map} +1 -1
  17. package/dist/{index-C0ITDyFo.cjs → index-DJjGSwXG.cjs} +2 -2
  18. package/dist/{index-C0ITDyFo.cjs.map → index-DJjGSwXG.cjs.map} +1 -1
  19. package/dist/index-V8EHMYEY.cjs +29 -0
  20. package/dist/index-V8EHMYEY.cjs.map +1 -0
  21. package/dist/{index-lbSQUoRz.js → index-Z5TstN1e.js} +2 -2
  22. package/dist/{index-lbSQUoRz.js.map → index-Z5TstN1e.js.map} +1 -1
  23. package/dist/indexeddb-storage-CZK5A7XH.cjs +2 -0
  24. package/dist/indexeddb-storage-CZK5A7XH.cjs.map +1 -0
  25. package/dist/{indexeddb-storage-CXhjqwhA.js → indexeddb-storage-bpA01pAU.js} +39 -2
  26. package/dist/indexeddb-storage-bpA01pAU.js.map +1 -0
  27. package/dist/{memory-storage-D1tc1bjk.cjs → memory-storage-B1k8Jszd.cjs} +2 -2
  28. package/dist/{memory-storage-D1tc1bjk.cjs.map → memory-storage-B1k8Jszd.cjs.map} +1 -1
  29. package/dist/{memory-storage-DkewsdcM.js → memory-storage-BqhmytP_.js} +2 -2
  30. package/dist/{memory-storage-DkewsdcM.js.map → memory-storage-BqhmytP_.js.map} +1 -1
  31. package/package.json +1 -1
  32. package/src/crypto/secp256k1.js +58 -9
  33. package/src/federation/card-storage.js +72 -0
  34. package/src/federation/handshake.js +363 -8
  35. package/src/federation/hologram.js +31 -3
  36. package/src/federation/holon-registry.js +22 -1
  37. package/src/federation/registry.js +54 -4
  38. package/src/index.js +52 -4
  39. package/src/storage/indexeddb-storage.js +41 -0
  40. package/src/storage/nostr-async.js +12 -3
  41. package/src/storage/nostr-client.js +112 -11
  42. package/src/storage/nostr-wrapper.js +5 -2
  43. package/dist/2019-Ew-DTDlI.cjs +0 -8
  44. package/dist/index-9sqetkAn.cjs +0 -29
  45. package/dist/index-9sqetkAn.cjs.map +0 -1
  46. package/dist/index-TDDyakLc.js.map +0 -1
  47. package/dist/indexeddb-storage-CXhjqwhA.js.map +0 -1
  48. package/dist/indexeddb-storage-DFESDYIj.cjs +0 -2
  49. package/dist/indexeddb-storage-DFESDYIj.cjs.map +0 -1
@@ -18010,12 +18010,12 @@ async function createPersistentStorage(namespace, options = {}) {
18010
18010
  const isBrowser = typeof window !== "undefined" && typeof window.indexedDB !== "undefined";
18011
18011
  typeof process !== "undefined" && process.versions && void 0;
18012
18012
  if (isBrowser) {
18013
- const { IndexedDBStorage } = await import("./indexeddb-storage-CXhjqwhA.js");
18013
+ const { IndexedDBStorage } = await import("./indexeddb-storage-bpA01pAU.js");
18014
18014
  const storage = new IndexedDBStorage();
18015
18015
  await storage.init(namespace);
18016
18016
  return storage;
18017
18017
  } else {
18018
- const { MemoryStorage } = await import("./memory-storage-DkewsdcM.js");
18018
+ const { MemoryStorage } = await import("./memory-storage-BqhmytP_.js");
18019
18019
  const storage = new MemoryStorage();
18020
18020
  await storage.init(namespace);
18021
18021
  return storage;
@@ -18028,7 +18028,7 @@ async function loadNDKCacheAdapter() {
18028
18028
  ndkCacheAdapterLoaded = true;
18029
18029
  if (typeof window !== "undefined" && typeof indexedDB !== "undefined") {
18030
18030
  try {
18031
- const module2 = await import("./index-lbSQUoRz.js");
18031
+ const module2 = await import("./index-Z5TstN1e.js");
18032
18032
  NDKCacheAdapterDexie = module2.default || module2.NDKCacheAdapterDexie;
18033
18033
  } catch (e) {
18034
18034
  console.debug("[nostr] NDK cache adapter not available, using LRU cache");
@@ -18197,13 +18197,20 @@ class NostrClient {
18197
18197
  const CacheAdapter = await loadNDKCacheAdapter();
18198
18198
  let cacheAdapter = null;
18199
18199
  if (CacheAdapter && this.config.appName) {
18200
+ const dbName = `holosphere_ndk_${this.config.appName}`;
18201
+ await this._clearIncompatibleNDKCache(dbName);
18200
18202
  try {
18201
- cacheAdapter = new CacheAdapter({
18202
- dbName: `holosphere_${this.config.appName}`
18203
- });
18203
+ cacheAdapter = new CacheAdapter({ dbName });
18204
18204
  this._useNDKCache = true;
18205
18205
  } catch (e) {
18206
18206
  console.warn("[nostr] Failed to initialize NDK cache adapter:", e.message);
18207
+ await this._deleteDexieDatabase(dbName);
18208
+ try {
18209
+ cacheAdapter = new CacheAdapter({ dbName });
18210
+ this._useNDKCache = true;
18211
+ } catch (retryError) {
18212
+ console.warn("[nostr] Failed to initialize NDK cache adapter after reset:", retryError.message);
18213
+ }
18207
18214
  }
18208
18215
  }
18209
18216
  if (this.relays.length > 0) {
@@ -18312,6 +18319,67 @@ class NostrClient {
18312
18319
  }
18313
18320
  }
18314
18321
  }
18322
+ /**
18323
+ * Check if NDK cache database exists with potentially incompatible schema.
18324
+ * NDK cache adapter uses Dexie which can't handle primary key changes.
18325
+ * We detect this by checking if the database exists and clearing it if needed.
18326
+ * @param {string} dbName - The database name to check
18327
+ * @returns {Promise<void>}
18328
+ * @private
18329
+ */
18330
+ async _clearIncompatibleNDKCache(dbName) {
18331
+ if (typeof indexedDB === "undefined") return;
18332
+ const databases = await indexedDB.databases?.() || [];
18333
+ const existingDb = databases.find((db) => db.name === dbName);
18334
+ if (existingDb) {
18335
+ try {
18336
+ await new Promise((resolve, reject) => {
18337
+ const request = indexedDB.open(dbName);
18338
+ request.onsuccess = () => {
18339
+ const db = request.result;
18340
+ const storeNames = Array.from(db.objectStoreNames);
18341
+ db.close();
18342
+ const requiredStores = ["events", "eventTags"];
18343
+ const hasRequiredStores = requiredStores.every((name2) => storeNames.includes(name2));
18344
+ if (!hasRequiredStores && storeNames.length > 0) {
18345
+ console.warn(`[nostr] NDK cache database ${dbName} has incompatible schema, clearing...`);
18346
+ this._deleteDexieDatabase(dbName).then(resolve).catch(resolve);
18347
+ } else {
18348
+ resolve();
18349
+ }
18350
+ };
18351
+ request.onerror = () => resolve();
18352
+ request.onblocked = () => resolve();
18353
+ });
18354
+ } catch {
18355
+ }
18356
+ }
18357
+ }
18358
+ /**
18359
+ * Delete a Dexie database by name.
18360
+ * Used to recover from schema upgrade errors.
18361
+ * @param {string} dbName - The database name to delete
18362
+ * @returns {Promise<void>}
18363
+ * @private
18364
+ */
18365
+ async _deleteDexieDatabase(dbName) {
18366
+ if (typeof indexedDB === "undefined") return;
18367
+ return new Promise((resolve) => {
18368
+ const request = indexedDB.deleteDatabase(dbName);
18369
+ request.onsuccess = () => {
18370
+ console.log(`[nostr] Deleted incompatible database: ${dbName}`);
18371
+ resolve();
18372
+ };
18373
+ request.onerror = () => {
18374
+ console.warn(`[nostr] Failed to delete database: ${dbName}`);
18375
+ resolve();
18376
+ };
18377
+ request.onblocked = () => {
18378
+ console.warn(`[nostr] Database deletion blocked: ${dbName}`);
18379
+ resolve();
18380
+ };
18381
+ });
18382
+ }
18315
18383
  /**
18316
18384
  * Load events from persistent storage into cache
18317
18385
  * @private
@@ -18412,6 +18480,7 @@ class NostrClient {
18412
18480
  * @param {Object} [options={}] - Publish options
18413
18481
  * @param {boolean} [options.waitForRelays=false] - Wait for relay confirmation
18414
18482
  * @param {boolean} [options.debounce=true] - Debounce rapid writes to same d-tag
18483
+ * @param {string} [options.signingKey] - Private key to sign with (hex format). If not provided, uses client's default key.
18415
18484
  * @returns {Promise<Object>} Signed event with relay publish results
18416
18485
  * @example
18417
18486
  * const result = await client.publish({
@@ -18459,7 +18528,7 @@ class NostrClient {
18459
18528
  }
18460
18529
  }, WRITE_DEBOUNCE_MS);
18461
18530
  pendingWrites.set(dTagPath, { event, timer, resolve, reject });
18462
- const signedEvent = this._createSignedEvent(event);
18531
+ const signedEvent = this._createSignedEvent(event, options.signingKey);
18463
18532
  this._cacheEvent(signedEvent);
18464
18533
  });
18465
18534
  }
@@ -18467,9 +18536,12 @@ class NostrClient {
18467
18536
  * Create a signed event using NDK or nostr-tools fallback.
18468
18537
  * @private
18469
18538
  * @param {Object} event - Unsigned event
18539
+ * @param {string} [signingKey] - Optional private key to sign with (hex format)
18470
18540
  * @returns {Object} Signed event
18471
18541
  */
18472
- _createSignedEvent(event) {
18542
+ _createSignedEvent(event, signingKey = null) {
18543
+ const keyToUse = signingKey || this.privateKey;
18544
+ const pubkeyToUse = signingKey ? getPublicKey$2(hexToBytes$2(signingKey)) : this.publicKey;
18473
18545
  if (this.ndk) {
18474
18546
  const ndkEvent = new NDKEvent(this.ndk);
18475
18547
  ndkEvent.kind = event.kind;
@@ -18481,7 +18553,7 @@ class NostrClient {
18481
18553
  content: ndkEvent.content,
18482
18554
  tags: ndkEvent.tags,
18483
18555
  created_at: ndkEvent.created_at,
18484
- pubkey: this.publicKey,
18556
+ pubkey: pubkeyToUse,
18485
18557
  id: "",
18486
18558
  // Will be set during actual publish
18487
18559
  sig: ""
@@ -18489,11 +18561,11 @@ class NostrClient {
18489
18561
  };
18490
18562
  }
18491
18563
  try {
18492
- return finalizeEvent(event, hexToBytes$2(this.privateKey));
18564
+ return finalizeEvent(event, hexToBytes$2(keyToUse));
18493
18565
  } catch (e) {
18494
18566
  return {
18495
18567
  ...event,
18496
- pubkey: this.publicKey,
18568
+ pubkey: pubkeyToUse,
18497
18569
  id: Math.random().toString(16).slice(2).padStart(64, "0"),
18498
18570
  sig: "mock-signature",
18499
18571
  created_at: event.created_at || Math.floor(Date.now() / 1e3)
@@ -18506,6 +18578,7 @@ class NostrClient {
18506
18578
  */
18507
18579
  async _doPublish(event, options = {}) {
18508
18580
  const waitForRelays = options.waitForRelays || false;
18581
+ const signingKey = options.signingKey || null;
18509
18582
  let signedEvent;
18510
18583
  if (event.id && event.sig) {
18511
18584
  signedEvent = event;
@@ -18515,10 +18588,15 @@ class NostrClient {
18515
18588
  ndkEvent.content = event.content;
18516
18589
  ndkEvent.tags = event.tags || [];
18517
18590
  ndkEvent.created_at = event.created_at || Math.floor(Date.now() / 1e3);
18518
- await ndkEvent.sign(this.signer);
18591
+ if (signingKey) {
18592
+ const tempSigner = new NDKPrivateKeySigner(signingKey);
18593
+ await ndkEvent.sign(tempSigner);
18594
+ } else {
18595
+ await ndkEvent.sign(this.signer);
18596
+ }
18519
18597
  signedEvent = ndkEvent.rawEvent();
18520
18598
  } else {
18521
- signedEvent = this._createSignedEvent(event);
18599
+ signedEvent = this._createSignedEvent(event, signingKey);
18522
18600
  }
18523
18601
  await this._cacheEvent(signedEvent);
18524
18602
  if (this.outboxQueue) {
@@ -20885,7 +20963,7 @@ class GunSchemaValidator {
20885
20963
  return true;
20886
20964
  }
20887
20965
  try {
20888
- const AjvModule = await import("./2019-DQdDE6DG.js").then((n2) => n2._);
20966
+ const AjvModule = await import("./2019-CLMqIAfQ.js").then((n2) => n2._);
20889
20967
  this.Ajv = AjvModule.default || AjvModule;
20890
20968
  this.validator = new this.Ajv({
20891
20969
  allErrors: true,
@@ -21106,7 +21184,7 @@ class GunDBBackend extends StorageBackend {
21106
21184
  let Gun;
21107
21185
  try {
21108
21186
  console.log("[gundb-backend] Importing Gun...");
21109
- const gunModule = await import("./browser-CckFyRI9.js").then((n2) => n2.b);
21187
+ const gunModule = await import("./browser-nUQt1cnB.js").then((n2) => n2.b);
21110
21188
  Gun = gunModule.default || gunModule;
21111
21189
  console.log("[gundb-backend] Gun imported:", typeof Gun);
21112
21190
  } catch (error) {
@@ -22506,7 +22584,9 @@ const globalSubscriptions = /* @__PURE__ */ new Map();
22506
22584
  const singlePathSubscriptions = /* @__PURE__ */ new Map();
22507
22585
  const pendingQueries = /* @__PURE__ */ new Map();
22508
22586
  const QUERY_DEDUP_WINDOW = 2e3;
22509
- async function nostrPut(client, path, data, kind2 = 3e4) {
22587
+ async function nostrPut(client, path, data, kindOrOptions = 3e4) {
22588
+ const options = typeof kindOrOptions === "number" ? { kind: kindOrOptions } : kindOrOptions;
22589
+ const kind2 = options.kind || 3e4;
22510
22590
  const dataEvent = {
22511
22591
  kind: kind2,
22512
22592
  created_at: Math.floor(Date.now() / 1e3),
@@ -22516,7 +22596,8 @@ async function nostrPut(client, path, data, kind2 = 3e4) {
22516
22596
  ],
22517
22597
  content: JSON.stringify(data)
22518
22598
  };
22519
- const result = await client.publish(dataEvent);
22599
+ const publishOptions = options.signingKey ? { signingKey: options.signingKey } : {};
22600
+ const result = await client.publish(dataEvent, publishOptions);
22520
22601
  return result;
22521
22602
  }
22522
22603
  async function nostrGet(client, path, kind2 = 3e4, options = {}) {
@@ -23057,9 +23138,10 @@ function buildPath$1(appname, holon, lens, key = null) {
23057
23138
  function encodePathComponent(component) {
23058
23139
  return encodeURIComponent(component).replace(/%2F/g, "/");
23059
23140
  }
23060
- async function write$1(client, path, data) {
23141
+ async function write$1(client, path, data, options = {}) {
23061
23142
  try {
23062
- const result = await nostrPut(client, path, data);
23143
+ const putOptions = options.signingKey ? { signingKey: options.signingKey } : {};
23144
+ const result = await nostrPut(client, path, data, putOptions);
23063
23145
  const success = result.results.length === 0 ? true : result.results.some((r) => r.status === "fulfilled");
23064
23146
  return success;
23065
23147
  } catch (error) {
@@ -23782,28 +23864,72 @@ async function issueSelfCapability(permissions, scope, authorPubKey, options = {
23782
23864
  async function verifyCapability$1(token, requiredPermission, scope) {
23783
23865
  try {
23784
23866
  let tokenObj;
23867
+ if (token && typeof token === "object" && token.token) {
23868
+ token = token.token;
23869
+ }
23785
23870
  if (typeof token === "string") {
23786
- const parts = token.split(".");
23787
- const payload = parts[0];
23788
- const decoded = Buffer.from ? Buffer.from(payload, "base64").toString("utf8") : atob(payload);
23789
- tokenObj = JSON.parse(decoded);
23790
- } else {
23871
+ if (token.startsWith("ey") || !token.includes(".") && !token.startsWith("{")) {
23872
+ try {
23873
+ const payload = token.includes(".") ? token.split(".")[0] : token;
23874
+ const decoded = Buffer.from ? Buffer.from(payload, "base64").toString("utf8") : atob(payload);
23875
+ tokenObj = JSON.parse(decoded);
23876
+ } catch (e) {
23877
+ console.log("[verifyCapability] ❌ Token is not valid base64 JSON:", {
23878
+ preview: token.substring(0, 50),
23879
+ error: e.message
23880
+ });
23881
+ return false;
23882
+ }
23883
+ } else if (token.startsWith("{")) {
23884
+ try {
23885
+ tokenObj = JSON.parse(token);
23886
+ } catch (e) {
23887
+ console.log("[verifyCapability] ❌ Token is not valid JSON:", {
23888
+ preview: token.substring(0, 50),
23889
+ error: e.message
23890
+ });
23891
+ return false;
23892
+ }
23893
+ } else {
23894
+ console.log("[verifyCapability] ❌ Unknown token format:", token.substring(0, 50));
23895
+ return false;
23896
+ }
23897
+ } else if (token && typeof token === "object") {
23791
23898
  tokenObj = token;
23899
+ } else {
23900
+ console.log("[verifyCapability] ❌ Invalid token:", typeof token);
23901
+ return false;
23792
23902
  }
23793
23903
  if (!tokenObj || tokenObj.type !== "capability") {
23904
+ console.log("[verifyCapability] ❌ Invalid token type:", { type: tokenObj?.type, tokenObj });
23794
23905
  return false;
23795
23906
  }
23796
23907
  if (Date.now() > tokenObj.expires) {
23908
+ console.log("[verifyCapability] ❌ Token expired:", {
23909
+ expires: tokenObj.expires,
23910
+ now: Date.now(),
23911
+ expiredAgo: `${(Date.now() - tokenObj.expires) / 1e3}s ago`
23912
+ });
23797
23913
  return false;
23798
23914
  }
23799
23915
  if (!matchScope(tokenObj.scope, scope)) {
23916
+ console.log("[verifyCapability] ❌ Scope mismatch:", {
23917
+ tokenScope: tokenObj.scope,
23918
+ requestedScope: scope
23919
+ });
23800
23920
  return false;
23801
23921
  }
23802
23922
  if (!tokenObj.permissions.includes(requiredPermission)) {
23923
+ console.log("[verifyCapability] ❌ Permission denied:", {
23924
+ required: requiredPermission,
23925
+ has: tokenObj.permissions
23926
+ });
23803
23927
  return false;
23804
23928
  }
23929
+ console.log("[verifyCapability] ✅ Capability valid");
23805
23930
  return true;
23806
23931
  } catch (error) {
23932
+ console.log("[verifyCapability] ❌ Error:", error.message);
23807
23933
  return false;
23808
23934
  }
23809
23935
  }
@@ -23889,21 +24015,60 @@ async function getFederatedPartner(client, appname, partnerPubKey) {
23889
24015
  }
23890
24016
  async function getCapabilityForAuthor(client, appname, authorPubKey, scope) {
23891
24017
  const registry2 = await getFederationRegistry(client, appname);
24018
+ console.log("[Registry] 🔍 getCapabilityForAuthor called:", {
24019
+ authorPubKey: authorPubKey?.slice(0, 12) + "...",
24020
+ scope,
24021
+ federatedWithCount: registry2.federatedWith?.length || 0,
24022
+ federatedPartners: registry2.federatedWith?.map((p) => p.pubKey?.slice(0, 12) + "...") || []
24023
+ });
23892
24024
  const partner = registry2.federatedWith.find((p) => p.pubKey === authorPubKey);
23893
- if (!partner || !partner.inboundCapabilities) {
24025
+ if (!partner) {
24026
+ console.log("[Registry] ❌ Partner not found in federatedWith");
24027
+ return null;
24028
+ }
24029
+ if (!partner.inboundCapabilities || partner.inboundCapabilities.length === 0) {
24030
+ console.log("[Registry] ❌ Partner has no inboundCapabilities:", {
24031
+ partnerPubKey: authorPubKey?.slice(0, 12) + "...",
24032
+ hasInboundCaps: !!partner.inboundCapabilities,
24033
+ inboundCapsLength: partner.inboundCapabilities?.length || 0
24034
+ });
23894
24035
  return null;
23895
24036
  }
23896
- return partner.inboundCapabilities.find((cap) => {
24037
+ console.log("[Registry] Partner found with inboundCapabilities:", {
24038
+ partnerPubKey: authorPubKey?.slice(0, 12) + "...",
24039
+ capsCount: partner.inboundCapabilities.length,
24040
+ capScopes: partner.inboundCapabilities.map((c) => c.scope)
24041
+ });
24042
+ const result = partner.inboundCapabilities.find((cap) => {
23897
24043
  if (cap.expires && cap.expires < Date.now()) {
24044
+ console.log("[Registry] Capability expired:", { expires: cap.expires, now: Date.now() });
23898
24045
  return false;
23899
24046
  }
23900
- return matchScope(cap.scope, scope);
24047
+ const matches = matchScope(cap.scope, scope);
24048
+ console.log("[Registry] Scope match check:", {
24049
+ capScope: cap.scope,
24050
+ requestedScope: scope,
24051
+ matches
24052
+ });
24053
+ return matches;
23901
24054
  }) || null;
24055
+ if (result) {
24056
+ console.log("[Registry] ✅ Matching capability found");
24057
+ } else {
24058
+ console.log("[Registry] ❌ No matching capability found");
24059
+ }
24060
+ return result;
23902
24061
  }
23903
24062
  async function storeInboundCapability(client, appname, partnerPubKey, capabilityInfo) {
24063
+ console.log("[Registry] 📥 storeInboundCapability called:", {
24064
+ partnerPubKey: partnerPubKey?.slice(0, 12) + "...",
24065
+ scope: capabilityInfo?.scope,
24066
+ hasToken: !!capabilityInfo?.token
24067
+ });
23904
24068
  const registry2 = await getFederationRegistry(client, appname);
23905
24069
  const partner = registry2.federatedWith.find((p) => p.pubKey === partnerPubKey);
23906
24070
  if (!partner) {
24071
+ console.log("[Registry] Partner not in registry, auto-adding with capability");
23907
24072
  return addFederatedPartner(client, appname, partnerPubKey, {
23908
24073
  addedVia: "capability_received",
23909
24074
  inboundCapabilities: [capabilityInfo]
@@ -23916,17 +24081,21 @@ async function storeInboundCapability(client, appname, partnerPubKey, capability
23916
24081
  (cap) => JSON.stringify(cap.scope) === JSON.stringify(capabilityInfo.scope)
23917
24082
  );
23918
24083
  if (existingIndex >= 0) {
24084
+ console.log("[Registry] Updating existing capability at index:", existingIndex);
23919
24085
  partner.inboundCapabilities[existingIndex] = {
23920
24086
  ...capabilityInfo,
23921
24087
  updatedAt: Date.now()
23922
24088
  };
23923
24089
  } else {
24090
+ console.log("[Registry] Adding new capability, total will be:", partner.inboundCapabilities.length + 1);
23924
24091
  partner.inboundCapabilities.push({
23925
24092
  ...capabilityInfo,
23926
24093
  receivedAt: Date.now()
23927
24094
  });
23928
24095
  }
23929
- return saveFederationRegistry(client, appname, registry2);
24096
+ const result = await saveFederationRegistry(client, appname, registry2);
24097
+ console.log("[Registry] ✅ Capability stored, registry saved:", result);
24098
+ return result;
23930
24099
  }
23931
24100
  async function storeOutboundCapability(client, appname, partnerPubKey, capabilityInfo) {
23932
24101
  const registry2 = await getFederationRegistry(client, appname);
@@ -24137,13 +24306,17 @@ async function wouldCreateCircularHologram(client, appname, sourceHolon, targetH
24137
24306
  return { isCircular: true, chain, reason: "max_depth_exceeded" };
24138
24307
  }
24139
24308
  function createHologram(sourceHolon, targetHolon, lensName, dataId, appname, options = {}) {
24140
- const { authorPubKey, capability } = options;
24309
+ const { authorPubKey } = options;
24310
+ let { capability } = options;
24141
24311
  if (!authorPubKey) {
24142
24312
  throw new Error("authorPubKey is required for hologram creation (unified model)");
24143
24313
  }
24144
24314
  if (!capability) {
24145
24315
  throw new Error("capability is required for hologram creation (unified model)");
24146
24316
  }
24317
+ if (typeof capability === "object" && capability.token) {
24318
+ capability = capability.token;
24319
+ }
24147
24320
  const soul = buildPath(appname, sourceHolon, lensName, dataId);
24148
24321
  const hologram2 = {
24149
24322
  id: dataId,
@@ -24158,7 +24331,7 @@ function createHologram(sourceHolon, targetHolon, lensName, dataId, appname, opt
24158
24331
  // Always present in unified model
24159
24332
  },
24160
24333
  capability,
24161
- // Always present in unified model
24334
+ // Always the token string (normalized above)
24162
24335
  _meta: {
24163
24336
  created: Date.now(),
24164
24337
  sourceHolon,
@@ -24246,6 +24419,9 @@ async function resolveHologram(client, hologram2, visited = /* @__PURE__ */ new
24246
24419
  let sourceData;
24247
24420
  let capability = hologram2.capability;
24248
24421
  const authorPubKey = target.authorPubKey;
24422
+ if (capability && typeof capability === "object" && capability.token) {
24423
+ capability = capability.token;
24424
+ }
24249
24425
  if (!capability && options.appname && authorPubKey) {
24250
24426
  const capEntry = await getCapabilityForAuthor(
24251
24427
  client,
@@ -24258,7 +24434,7 @@ async function resolveHologram(client, hologram2, visited = /* @__PURE__ */ new
24258
24434
  }
24259
24435
  );
24260
24436
  if (capEntry) {
24261
- capability = capEntry.token;
24437
+ capability = capEntry.token || capEntry;
24262
24438
  }
24263
24439
  }
24264
24440
  if (!skipCapabilityVerification) {
@@ -24458,6 +24634,15 @@ async function propagateData(client, appname, data, sourceHolon, targetHolon, le
24458
24634
  return false;
24459
24635
  }
24460
24636
  const sourceAuthorPubKey = options.sourceAuthorPubKey || client.publicKey;
24637
+ const isPubkey2 = typeof targetHolon === "string" && /^[0-9a-f]{64}$/i.test(targetHolon);
24638
+ const targetAuthorPubKey = options.targetAuthorPubKey || (isPubkey2 ? targetHolon : client.publicKey);
24639
+ console.log("[propagateData] Creating hologram with capability:", {
24640
+ sourceHolon: sourceHolon?.slice(0, 12) + "...",
24641
+ targetHolon: targetHolon?.slice(0, 12) + "...",
24642
+ sourceAuthorPubKey: sourceAuthorPubKey?.slice(0, 12) + "...",
24643
+ targetAuthorPubKey: targetAuthorPubKey?.slice(0, 12) + "...",
24644
+ hasProvidedCapability: !!options.capability
24645
+ });
24461
24646
  const hologram2 = await createHologramWithCapability(
24462
24647
  client,
24463
24648
  sourceHolon,
@@ -24467,6 +24652,7 @@ async function propagateData(client, appname, data, sourceHolon, targetHolon, le
24467
24652
  appname,
24468
24653
  {
24469
24654
  sourceAuthorPubKey,
24655
+ targetAuthorPubKey,
24470
24656
  capability: options.capability,
24471
24657
  permissions: ["read"]
24472
24658
  }
@@ -25055,6 +25241,108 @@ const nostrUtils = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePr
25055
25241
  signEvent,
25056
25242
  verifyEvent
25057
25243
  }, Symbol.toStringTag, { value: "Module" }));
25244
+ const HOLON_REGISTRY_TABLE = "_holon-registry";
25245
+ function isPubkey(str2) {
25246
+ return typeof str2 === "string" && /^[0-9a-f]{64}$/i.test(str2);
25247
+ }
25248
+ async function registerHolon(client, appname, holonId, publicKey, options = {}) {
25249
+ if (!isPubkey(publicKey)) {
25250
+ throw new Error(`Invalid public key format: ${publicKey}`);
25251
+ }
25252
+ if (isPubkey(holonId)) {
25253
+ console.log("[HolonRegistry] registerHolon: holonId is already a pubkey, skipping registration");
25254
+ return true;
25255
+ }
25256
+ const entry = {
25257
+ id: holonId,
25258
+ holonId,
25259
+ publicKey,
25260
+ alias: options.alias || null,
25261
+ createdAt: Date.now(),
25262
+ updatedAt: Date.now(),
25263
+ createdBy: client.publicKey
25264
+ };
25265
+ console.log("[HolonRegistry] 📝 registerHolon:", {
25266
+ holonId,
25267
+ publicKey: publicKey?.slice(0, 12) + "...",
25268
+ alias: options.alias
25269
+ });
25270
+ const result = await writeGlobal(client, appname, HOLON_REGISTRY_TABLE, entry);
25271
+ console.log("[HolonRegistry] registerHolon result:", result);
25272
+ return result;
25273
+ }
25274
+ async function lookupHolon(client, appname, holonId) {
25275
+ if (isPubkey(holonId)) {
25276
+ return {
25277
+ holonId,
25278
+ publicKey: holonId,
25279
+ isDirect: true
25280
+ };
25281
+ }
25282
+ return readGlobal(client, appname, HOLON_REGISTRY_TABLE, holonId);
25283
+ }
25284
+ async function resolveHolonToPubkey(client, appname, holonId) {
25285
+ if (isPubkey(holonId)) {
25286
+ console.log("[HolonRegistry] resolveHolonToPubkey: holonId is already a pubkey:", holonId?.slice(0, 12) + "...");
25287
+ return holonId;
25288
+ }
25289
+ const entry = await lookupHolon(client, appname, holonId);
25290
+ if (entry?.publicKey) {
25291
+ console.log("[HolonRegistry] ✅ resolveHolonToPubkey: Found mapping", {
25292
+ holonId,
25293
+ publicKey: entry.publicKey?.slice(0, 12) + "...",
25294
+ alias: entry.alias
25295
+ });
25296
+ } else {
25297
+ console.log("[HolonRegistry] ❌ resolveHolonToPubkey: No mapping found for holonId:", holonId);
25298
+ }
25299
+ return entry?.publicKey || null;
25300
+ }
25301
+ async function unregisterHolon(client, appname, holonId) {
25302
+ if (isPubkey(holonId)) {
25303
+ return false;
25304
+ }
25305
+ const entry = {
25306
+ id: holonId,
25307
+ holonId,
25308
+ publicKey: null,
25309
+ _deleted: true,
25310
+ deletedAt: Date.now(),
25311
+ deletedBy: client.publicKey
25312
+ };
25313
+ return writeGlobal(client, appname, HOLON_REGISTRY_TABLE, entry);
25314
+ }
25315
+ async function updateHolon(client, appname, holonId, updates = {}) {
25316
+ const existing = await lookupHolon(client, appname, holonId);
25317
+ if (!existing || existing.isDirect) {
25318
+ return false;
25319
+ }
25320
+ const entry = {
25321
+ ...existing,
25322
+ ...updates,
25323
+ updatedAt: Date.now()
25324
+ };
25325
+ return writeGlobal(client, appname, HOLON_REGISTRY_TABLE, entry);
25326
+ }
25327
+ async function listRegisteredHolons(client, appname) {
25328
+ const entries = await getAllGlobal(client, appname, HOLON_REGISTRY_TABLE);
25329
+ return (entries || []).filter((e) => !e._deleted);
25330
+ }
25331
+ async function findHolonsByPubkey(client, appname, publicKey) {
25332
+ const all = await listRegisteredHolons(client, appname);
25333
+ return all.filter((e) => e.publicKey === publicKey);
25334
+ }
25335
+ const holonRegistry = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
25336
+ __proto__: null,
25337
+ findHolonsByPubkey,
25338
+ isPubkey,
25339
+ listRegisteredHolons,
25340
+ lookupHolon,
25341
+ registerHolon,
25342
+ resolveHolonToPubkey,
25343
+ unregisterHolon,
25344
+ updateHolon
25345
+ }, Symbol.toStringTag, { value: "Module" }));
25058
25346
  async function issueCapability(client, targetPubKey, scope, permissions, options = {}) {
25059
25347
  const {
25060
25348
  expiresIn = 36e5,
@@ -25362,6 +25650,8 @@ const requestCard$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defin
25362
25650
  const FEDERATION_CARDS_TABLE = "_federation-cards";
25363
25651
  const DISMISSED_REQUESTS_TABLE = "_dismissed-federation-requests";
25364
25652
  const PROCESSED_RESPONSES_TABLE = "_processed-federation-responses";
25653
+ const DM_STATE_TABLE = "_federation-dm-state";
25654
+ const PROCESSED_DMS_TABLE = "_processed-federation-dms";
25365
25655
  async function saveCard(client, appname, card) {
25366
25656
  const serialized = serializeCard(card);
25367
25657
  serialized.id = card.id;
@@ -25484,6 +25774,32 @@ async function cleanupOldEntries(client, appname, maxAge = 30 * 24 * 60 * 60 * 1
25484
25774
  }
25485
25775
  return result;
25486
25776
  }
25777
+ async function getLastDMFetchTime(client, appname) {
25778
+ const entry = await readGlobal(client, appname, DM_STATE_TABLE, "lastFetch");
25779
+ return entry?.timestamp || 0;
25780
+ }
25781
+ async function setLastDMFetchTime(client, appname, timestamp) {
25782
+ return writeGlobal(client, appname, DM_STATE_TABLE, {
25783
+ id: "lastFetch",
25784
+ timestamp,
25785
+ updatedAt: Date.now()
25786
+ });
25787
+ }
25788
+ async function markDMProcessed(client, appname, eventId, type2 = "unknown") {
25789
+ return writeGlobal(client, appname, PROCESSED_DMS_TABLE, {
25790
+ id: eventId,
25791
+ type: type2,
25792
+ processedAt: Date.now()
25793
+ });
25794
+ }
25795
+ async function isDMProcessed(client, appname, eventId) {
25796
+ const entry = await readGlobal(client, appname, PROCESSED_DMS_TABLE, eventId);
25797
+ return !!entry;
25798
+ }
25799
+ async function getProcessedDMIds(client, appname) {
25800
+ const entries = await getAllGlobal(client, appname, PROCESSED_DMS_TABLE);
25801
+ return new Set((entries || []).map((e) => e.id));
25802
+ }
25487
25803
  const cardStorage = {
25488
25804
  saveCard,
25489
25805
  getCard,
@@ -25500,7 +25816,12 @@ const cardStorage = {
25500
25816
  getProcessedResponseIds,
25501
25817
  updateCardStatus,
25502
25818
  findPendingCardsForPartner,
25503
- cleanupOldEntries
25819
+ cleanupOldEntries,
25820
+ getLastDMFetchTime,
25821
+ setLastDMFetchTime,
25822
+ markDMProcessed,
25823
+ isDMProcessed,
25824
+ getProcessedDMIds
25504
25825
  };
25505
25826
  const cardStorage$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
25506
25827
  __proto__: null,
@@ -25515,11 +25836,16 @@ const cardStorage$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defin
25515
25836
  getCardByPartner,
25516
25837
  getDismissedRequestIds,
25517
25838
  getDisplayableCards,
25839
+ getLastDMFetchTime,
25840
+ getProcessedDMIds,
25518
25841
  getProcessedResponseIds,
25842
+ isDMProcessed,
25519
25843
  isRequestDismissed,
25520
25844
  isResponseProcessed,
25845
+ markDMProcessed,
25521
25846
  markResponseProcessed,
25522
25847
  saveCard,
25848
+ setLastDMFetchTime,
25523
25849
  updateCardStatus
25524
25850
  }, Symbol.toStringTag, { value: "Module" }));
25525
25851
  function createFederationRequest({
@@ -25604,17 +25930,22 @@ async function sendFederationResponse(client, privateKey, recipientPubKey, respo
25604
25930
  }
25605
25931
  }
25606
25932
  function subscribeToFederationDMs(client, privateKey, publicKey, handlers, options = {}) {
25607
- const { onRequest, onResponse } = handlers;
25933
+ const { onRequest, onResponse, onUpdate, onUpdateResponse } = handlers;
25608
25934
  const { appname } = options;
25609
25935
  let isActive = true;
25610
25936
  const processedEventIds = /* @__PURE__ */ new Set();
25937
+ let processedDMIds = /* @__PURE__ */ new Set();
25611
25938
  let processedResponseIds = /* @__PURE__ */ new Set();
25612
- const loadProcessedResponses = async () => {
25939
+ let lastFetchTime = 0;
25940
+ const loadPersistedState = async () => {
25613
25941
  if (appname && client?.client) {
25614
25942
  try {
25943
+ processedDMIds = await getProcessedDMIds(client.client, appname);
25615
25944
  processedResponseIds = await getProcessedResponseIds(client.client, appname);
25945
+ lastFetchTime = await getLastDMFetchTime(client.client, appname);
25946
+ console.log("[Handshake] Loaded persisted state - last fetch:", lastFetchTime, "processed DMs:", processedDMIds.size);
25616
25947
  } catch (err) {
25617
- console.warn("[Handshake] Could not load processed responses:", err.message);
25948
+ console.warn("[Handshake] Could not load persisted state:", err.message);
25618
25949
  }
25619
25950
  }
25620
25951
  };
@@ -25622,6 +25953,9 @@ function subscribeToFederationDMs(client, privateKey, publicKey, handlers, optio
25622
25953
  if (!isActive) return;
25623
25954
  if (processedEventIds.has(event.id)) return;
25624
25955
  processedEventIds.add(event.id);
25956
+ if (processedDMIds.has(event.id)) {
25957
+ return;
25958
+ }
25625
25959
  if (event.kind !== 4) return;
25626
25960
  const pTag = event.tags?.find((t) => t[0] === "p");
25627
25961
  if (!pTag || pTag[1] !== publicKey) return;
@@ -25638,9 +25972,15 @@ function subscribeToFederationDMs(client, privateKey, publicKey, handlers, optio
25638
25972
  const isDismissed = await isRequestDismissed(client.client, appname, payload.requestId);
25639
25973
  if (isDismissed) {
25640
25974
  console.log("[Handshake] Skipping dismissed request:", payload.requestId);
25975
+ await markDMProcessed(client.client, appname, event.id, "request");
25976
+ processedDMIds.add(event.id);
25641
25977
  return;
25642
25978
  }
25643
25979
  }
25980
+ if (appname && client?.client) {
25981
+ await markDMProcessed(client.client, appname, event.id, "request");
25982
+ processedDMIds.add(event.id);
25983
+ }
25644
25984
  console.log("[Handshake] Received federation request from:", event.pubkey.substring(0, 8) + "...");
25645
25985
  onRequest?.(payload, event.pubkey);
25646
25986
  } else if (payload.type === "federation_response" && payload.version === "1.0") {
@@ -25651,31 +25991,68 @@ function subscribeToFederationDMs(client, privateKey, publicKey, handlers, optio
25651
25991
  }
25652
25992
  if (appname && client?.client) {
25653
25993
  await markResponseProcessed(client.client, appname, payload.requestId, event.pubkey);
25994
+ await markDMProcessed(client.client, appname, event.id, "response");
25654
25995
  processedResponseIds.add(responseKey);
25996
+ processedDMIds.add(event.id);
25655
25997
  }
25656
25998
  console.log("[Handshake] Received federation response from:", event.pubkey.substring(0, 8) + "...");
25657
25999
  onResponse?.(payload, event.pubkey);
26000
+ } else if (payload.type === "federation_update" && payload.version === "1.0") {
26001
+ if (appname && client?.client) {
26002
+ const isDismissed = await isRequestDismissed(client.client, appname, payload.updateId);
26003
+ if (isDismissed) {
26004
+ console.log("[Handshake] Skipping dismissed update:", payload.updateId);
26005
+ await markDMProcessed(client.client, appname, event.id, "update");
26006
+ processedDMIds.add(event.id);
26007
+ return;
26008
+ }
26009
+ }
26010
+ if (appname && client?.client) {
26011
+ await markDMProcessed(client.client, appname, event.id, "update");
26012
+ processedDMIds.add(event.id);
26013
+ }
26014
+ console.log("[Handshake] Received federation update from:", event.pubkey.substring(0, 8) + "...");
26015
+ onUpdate?.(payload, event.pubkey);
26016
+ } else if (payload.type === "federation_update_response" && payload.version === "1.0") {
26017
+ const responseKey = `update_${payload.updateId}_${event.pubkey}`;
26018
+ if (processedResponseIds.has(responseKey)) {
26019
+ console.log("[Handshake] Skipping already processed update response:", responseKey);
26020
+ return;
26021
+ }
26022
+ if (appname && client?.client) {
26023
+ await markResponseProcessed(client.client, appname, payload.updateId, event.pubkey);
26024
+ await markDMProcessed(client.client, appname, event.id, "update_response");
26025
+ processedResponseIds.add(responseKey);
26026
+ processedDMIds.add(event.id);
26027
+ }
26028
+ console.log("[Handshake] Received federation update response from:", event.pubkey.substring(0, 8) + "...");
26029
+ onUpdateResponse?.(payload, event.pubkey);
25658
26030
  }
25659
26031
  } catch (error) {
25660
26032
  }
25661
26033
  };
25662
26034
  let subscription = null;
25663
26035
  const startSubscription = async () => {
25664
- await loadProcessedResponses();
26036
+ await loadPersistedState();
25665
26037
  const nostrClient = client?.client;
25666
26038
  if (!nostrClient?.subscribe) {
25667
26039
  console.warn("[Handshake] No NostrClient subscribe method available");
25668
26040
  return;
25669
26041
  }
26042
+ const sinceTime = lastFetchTime || 0;
26043
+ console.log("[Handshake] Fetching DMs since:", sinceTime ? new Date(sinceTime * 1e3).toISOString() : "beginning of time");
25670
26044
  const filter = {
25671
26045
  kinds: [4],
25672
26046
  "#p": [publicKey],
25673
- since: Math.floor(Date.now() / 1e3) - 86400
25674
- // Last 24 hours
26047
+ since: sinceTime
25675
26048
  };
25676
26049
  try {
25677
26050
  subscription = await nostrClient.subscribe(filter, handleEvent, {});
25678
26051
  console.log("[Handshake] Federation DM subscription started");
26052
+ if (appname && client?.client) {
26053
+ const now = Math.floor(Date.now() / 1e3);
26054
+ await setLastDMFetchTime(client.client, appname, now);
26055
+ }
25679
26056
  } catch (error) {
25680
26057
  console.error("[Handshake] Failed to subscribe:", error);
25681
26058
  }
@@ -25768,6 +26145,15 @@ async function acceptFederationRequest$1(holosphere, privateKey, params) {
25768
26145
  await storeInboundCapability(holosphere.client, holosphere.config.appName, senderPubKey, cap);
25769
26146
  }
25770
26147
  }
26148
+ if (request.senderHolonId) {
26149
+ await registerHolon(holosphere.client, holosphere.config.appName, request.senderHolonId, senderPubKey, {
26150
+ alias: request.senderHolonName
26151
+ });
26152
+ console.log("[Handshake] Registered partner holon:", {
26153
+ holonId: request.senderHolonId,
26154
+ pubKey: senderPubKey?.slice(0, 12) + "..."
26155
+ });
26156
+ }
25771
26157
  }
25772
26158
  await holosphere.federateHolon(holonId, senderPubKey, {
25773
26159
  lensConfig,
@@ -25877,6 +26263,15 @@ async function processFederationResponse(holosphere, response, responderPubKey,
25877
26263
  alias: response.responderHolonName,
25878
26264
  addedVia: "handshake_accepted"
25879
26265
  });
26266
+ if (response.responderHolonId) {
26267
+ await registerHolon(holosphere.client, holosphere.config.appName, response.responderHolonId, responderPubKey, {
26268
+ alias: response.responderHolonName
26269
+ });
26270
+ console.log("[Handshake] Registered partner holon from response:", {
26271
+ holonId: response.responderHolonId,
26272
+ pubKey: responderPubKey?.slice(0, 12) + "..."
26273
+ });
26274
+ }
25880
26275
  }
25881
26276
  const receivedHolograms = {};
25882
26277
  const responderHolonId = response.responderHolonId;
@@ -25907,103 +26302,152 @@ function isFederationRequest(payload) {
25907
26302
  function isFederationResponse(payload) {
25908
26303
  return payload?.type === "federation_response" && payload?.version === "1.0";
25909
26304
  }
25910
- const handshake = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
25911
- __proto__: null,
25912
- acceptFederationRequest: acceptFederationRequest$1,
25913
- createFederationRequest,
25914
- createFederationResponse,
25915
- initiateFederationHandshake,
25916
- isFederationRequest,
25917
- isFederationResponse,
25918
- processFederationResponse,
25919
- rejectFederationRequest,
25920
- sendFederationRequest: sendFederationRequest$1,
25921
- sendFederationResponse,
25922
- subscribeToFederationDMs
25923
- }, Symbol.toStringTag, { value: "Module" }));
25924
- const HOLON_REGISTRY_TABLE = "_holon-registry";
25925
- function isPubkey(str2) {
25926
- return typeof str2 === "string" && /^[0-9a-f]{64}$/i.test(str2);
26305
+ function createFederationUpdate({
26306
+ senderHolonId,
26307
+ senderHolonName,
26308
+ senderPubKey,
26309
+ newLensConfig,
26310
+ message
26311
+ }) {
26312
+ return {
26313
+ type: "federation_update",
26314
+ version: "1.0",
26315
+ updateId: generateNonce(),
26316
+ timestamp: Date.now(),
26317
+ senderHolonId,
26318
+ senderHolonName,
26319
+ senderNpub: hexToNpub(senderPubKey),
26320
+ newLensConfig,
26321
+ message
26322
+ };
25927
26323
  }
25928
- async function registerHolon(client, appname, holonId, publicKey, options = {}) {
25929
- if (!isPubkey(publicKey)) {
25930
- throw new Error(`Invalid public key format: ${publicKey}`);
25931
- }
25932
- if (isPubkey(holonId)) {
25933
- return true;
25934
- }
25935
- const entry = {
25936
- id: holonId,
25937
- holonId,
25938
- publicKey,
25939
- alias: options.alias || null,
25940
- createdAt: Date.now(),
25941
- updatedAt: Date.now(),
25942
- createdBy: client.publicKey
26324
+ function createFederationUpdateResponse({
26325
+ updateId,
26326
+ status,
26327
+ message
26328
+ }) {
26329
+ return {
26330
+ type: "federation_update_response",
26331
+ version: "1.0",
26332
+ updateId,
26333
+ timestamp: Date.now(),
26334
+ status,
26335
+ message
25943
26336
  };
25944
- return writeGlobal(client, appname, HOLON_REGISTRY_TABLE, entry);
25945
26337
  }
25946
- async function lookupHolon(client, appname, holonId) {
25947
- if (isPubkey(holonId)) {
25948
- return {
25949
- holonId,
25950
- publicKey: holonId,
25951
- isDirect: true
25952
- };
26338
+ async function sendFederationUpdate(client, privateKey, recipientPubKey, update2) {
26339
+ try {
26340
+ const content = JSON.stringify(update2);
26341
+ const encrypted = encryptNIP44(privateKey, recipientPubKey, content);
26342
+ const event = createDMEvent(recipientPubKey, encrypted, privateKey);
26343
+ if (client?.publish) {
26344
+ await client.publish(event);
26345
+ console.log("[Handshake] Federation update sent to:", recipientPubKey.substring(0, 8) + "...");
26346
+ return true;
26347
+ }
26348
+ return false;
26349
+ } catch (error) {
26350
+ console.error("[Handshake] Failed to send federation update:", error);
26351
+ return false;
25953
26352
  }
25954
- return readGlobal(client, appname, HOLON_REGISTRY_TABLE, holonId);
25955
26353
  }
25956
- async function resolveHolonToPubkey(client, appname, holonId) {
25957
- if (isPubkey(holonId)) {
25958
- return holonId;
26354
+ async function sendFederationUpdateResponse(client, privateKey, recipientPubKey, response) {
26355
+ try {
26356
+ const content = JSON.stringify(response);
26357
+ const encrypted = encryptNIP44(privateKey, recipientPubKey, content);
26358
+ const event = createDMEvent(recipientPubKey, encrypted, privateKey);
26359
+ if (client?.publish) {
26360
+ await client.publish(event);
26361
+ console.log("[Handshake] Federation update response sent to:", recipientPubKey.substring(0, 8) + "...");
26362
+ return true;
26363
+ }
26364
+ return false;
26365
+ } catch (error) {
26366
+ console.error("[Handshake] Failed to send federation update response:", error);
26367
+ return false;
25959
26368
  }
25960
- const entry = await lookupHolon(client, appname, holonId);
25961
- return entry?.publicKey || null;
25962
26369
  }
25963
- async function unregisterHolon(client, appname, holonId) {
25964
- if (isPubkey(holonId)) {
25965
- return false;
26370
+ async function requestFederationUpdate(holosphere, privateKey, params) {
26371
+ const { partnerPubKey, holonId, holonName, newLensConfig, message } = params;
26372
+ try {
26373
+ const senderPubKey = getPublicKey(privateKey);
26374
+ const update2 = createFederationUpdate({
26375
+ senderHolonId: holonId,
26376
+ senderHolonName: holonName,
26377
+ senderPubKey,
26378
+ newLensConfig,
26379
+ message
26380
+ });
26381
+ const sent = await sendFederationUpdate(holosphere.client, privateKey, partnerPubKey, update2);
26382
+ if (sent) {
26383
+ return { success: true, updateId: update2.updateId };
26384
+ }
26385
+ return { success: false, error: "Failed to send update DM" };
26386
+ } catch (error) {
26387
+ return { success: false, error: error.message };
25966
26388
  }
25967
- const entry = {
25968
- id: holonId,
25969
- holonId,
25970
- publicKey: null,
25971
- _deleted: true,
25972
- deletedAt: Date.now(),
25973
- deletedBy: client.publicKey
25974
- };
25975
- return writeGlobal(client, appname, HOLON_REGISTRY_TABLE, entry);
25976
26389
  }
25977
- async function updateHolon(client, appname, holonId, updates = {}) {
25978
- const existing = await lookupHolon(client, appname, holonId);
25979
- if (!existing || existing.isDirect) {
25980
- return false;
26390
+ async function acceptFederationUpdate(holosphere, privateKey, params) {
26391
+ const { updateId, senderPubKey, holonId, newLensConfig, message } = params;
26392
+ try {
26393
+ if (holosphere.federateHolon) {
26394
+ await holosphere.federateHolon(holonId, senderPubKey, {
26395
+ lensConfig: newLensConfig
26396
+ });
26397
+ }
26398
+ const response = createFederationUpdateResponse({
26399
+ updateId,
26400
+ status: "accepted",
26401
+ message
26402
+ });
26403
+ const sent = await sendFederationUpdateResponse(holosphere.client, privateKey, senderPubKey, response);
26404
+ return { success: sent, error: sent ? void 0 : "Failed to send response" };
26405
+ } catch (error) {
26406
+ return { success: false, error: error.message };
25981
26407
  }
25982
- const entry = {
25983
- ...existing,
25984
- ...updates,
25985
- updatedAt: Date.now()
25986
- };
25987
- return writeGlobal(client, appname, HOLON_REGISTRY_TABLE, entry);
25988
26408
  }
25989
- async function listRegisteredHolons(client, appname) {
25990
- const entries = await getAllGlobal(client, appname, HOLON_REGISTRY_TABLE);
25991
- return (entries || []).filter((e) => !e._deleted);
26409
+ async function rejectFederationUpdate(holosphere, privateKey, params) {
26410
+ const { updateId, senderPubKey, message } = params;
26411
+ try {
26412
+ const response = createFederationUpdateResponse({
26413
+ updateId,
26414
+ status: "rejected",
26415
+ message
26416
+ });
26417
+ const sent = await sendFederationUpdateResponse(holosphere.client, privateKey, senderPubKey, response);
26418
+ return { success: sent, error: sent ? void 0 : "Failed to send response" };
26419
+ } catch (error) {
26420
+ return { success: false, error: error.message };
26421
+ }
25992
26422
  }
25993
- async function findHolonsByPubkey(client, appname, publicKey) {
25994
- const all = await listRegisteredHolons(client, appname);
25995
- return all.filter((e) => e.publicKey === publicKey);
26423
+ function isFederationUpdate(payload) {
26424
+ return payload?.type === "federation_update" && payload?.version === "1.0";
25996
26425
  }
25997
- const holonRegistry = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
26426
+ function isFederationUpdateResponse(payload) {
26427
+ return payload?.type === "federation_update_response" && payload?.version === "1.0";
26428
+ }
26429
+ const handshake = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
25998
26430
  __proto__: null,
25999
- findHolonsByPubkey,
26000
- isPubkey,
26001
- listRegisteredHolons,
26002
- lookupHolon,
26003
- registerHolon,
26004
- resolveHolonToPubkey,
26005
- unregisterHolon,
26006
- updateHolon
26431
+ acceptFederationRequest: acceptFederationRequest$1,
26432
+ acceptFederationUpdate,
26433
+ createFederationRequest,
26434
+ createFederationResponse,
26435
+ createFederationUpdate,
26436
+ createFederationUpdateResponse,
26437
+ initiateFederationHandshake,
26438
+ isFederationRequest,
26439
+ isFederationResponse,
26440
+ isFederationUpdate,
26441
+ isFederationUpdateResponse,
26442
+ processFederationResponse,
26443
+ rejectFederationRequest,
26444
+ rejectFederationUpdate,
26445
+ requestFederationUpdate,
26446
+ sendFederationRequest: sendFederationRequest$1,
26447
+ sendFederationResponse,
26448
+ sendFederationUpdate,
26449
+ sendFederationUpdateResponse,
26450
+ subscribeToFederationDMs
26007
26451
  }, Symbol.toStringTag, { value: "Module" }));
26008
26452
  function validateNostrEvent(event, partial = true, throwOnError = false) {
26009
26453
  if (!event || typeof event !== "object") {
@@ -36395,7 +36839,7 @@ class ChainManager {
36395
36839
  */
36396
36840
  async _loadEthers() {
36397
36841
  if (!this.ethers) {
36398
- const ethersModule = await import("./index-DrYM1LOY.js");
36842
+ const ethersModule = await import("./index-CoAjtqsD.js");
36399
36843
  this.ethers = ethersModule;
36400
36844
  }
36401
36845
  return this.ethers;
@@ -45186,7 +45630,7 @@ class ContractDeployer {
45186
45630
  */
45187
45631
  async _loadEthers() {
45188
45632
  if (!this.ethers) {
45189
- this.ethers = await import("./index-DrYM1LOY.js");
45633
+ this.ethers = await import("./index-CoAjtqsD.js");
45190
45634
  }
45191
45635
  return this.ethers;
45192
45636
  }
@@ -45572,7 +46016,7 @@ class ContractOperations {
45572
46016
  */
45573
46017
  async _loadEthers() {
45574
46018
  if (!this.ethers) {
45575
- this.ethers = await import("./index-DrYM1LOY.js");
46019
+ this.ethers = await import("./index-CoAjtqsD.js");
45576
46020
  }
45577
46021
  return this.ethers;
45578
46022
  }
@@ -57524,6 +57968,7 @@ class HoloSphereBase extends HoloSphere$1 {
57524
57968
  * @param {boolean} [options.autoPropagate=true] - Whether to propagate to federated holons
57525
57969
  * @param {Object} [options.propagationOptions] - Options for propagation
57526
57970
  * @param {boolean} [options.blocking=false] - If true, wait for relay confirmation before returning
57971
+ * @param {string} [options.signingKey] - Private key to sign with (hex format). If not provided, uses holosphere's default key.
57527
57972
  * @returns {Promise<boolean>} True if write succeeded (or queued for optimistic writes)
57528
57973
  * @throws {ValidationError} If holonId, lensName, or data is invalid
57529
57974
  * @throws {AuthorizationError} If capability token is invalid
@@ -57605,7 +58050,8 @@ class HoloSphereBase extends HoloSphere$1 {
57605
58050
  this._log("DEBUG", "🔗 Syncing hologram write", { path, target: existingData.target });
57606
58051
  return this._syncHologramWrite(existingData, data, path, lensName, options);
57607
58052
  }
57608
- await write(this.client, path, data);
58053
+ const writeOptions = options.signingKey ? { signingKey: options.signingKey } : {};
58054
+ await write(this.client, path, data, writeOptions);
57609
58055
  const endTime = Date.now();
57610
58056
  const duration = endTime - startTime;
57611
58057
  this._metrics.writes++;
@@ -57826,6 +58272,26 @@ class HoloSphereBase extends HoloSphere$1 {
57826
58272
  if (!lensName) {
57827
58273
  throw new ValidationError$1("ValidationError: lensName must be a non-empty string");
57828
58274
  }
58275
+ if (dataId) {
58276
+ const earlyPath = buildPath(this.config.appName, holonId, lensName, dataId);
58277
+ if (this._deleteCache.has(earlyPath)) {
58278
+ this._log("DEBUG", "🗑️ EARLY CACHE: Deleted item", { path: earlyPath });
58279
+ return null;
58280
+ }
58281
+ const cached = this._writeCache.get(earlyPath);
58282
+ if (cached) {
58283
+ const cacheAge = Date.now() - cached.timestamp;
58284
+ this._log("DEBUG", "⚡ EARLY CACHE HIT: Write cache", {
58285
+ path: earlyPath,
58286
+ cacheAge: `${cacheAge}ms`
58287
+ });
58288
+ const { resolveHolograms: resolveHolograms2 = true } = options;
58289
+ if (resolveHolograms2 && cached.data) {
58290
+ return this._resolveHolograms(cached.data);
58291
+ }
58292
+ return cached.data;
58293
+ }
58294
+ }
57829
58295
  const targetPubkey = await this._resolveHolonToPubkey(holonId);
57830
58296
  const isOtherAuthor = targetPubkey && targetPubkey !== this.client.publicKey;
57831
58297
  let readOptions = {};
@@ -57839,11 +58305,27 @@ class HoloSphereBase extends HoloSphere$1 {
57839
58305
  readOptions.authors = [targetPubkey];
57840
58306
  }
57841
58307
  } else if (isOtherAuthor) {
58308
+ this._log("DEBUG", "🔍 Looking up capability for federated author", {
58309
+ holonId,
58310
+ lensName,
58311
+ dataId: dataId || "*",
58312
+ targetPubkey: targetPubkey?.slice(0, 12) + "...",
58313
+ myPubkey: this.client?.publicKey?.slice(0, 12) + "..."
58314
+ });
57842
58315
  const capability = await this._getCapabilityForAuthor(targetPubkey, { holonId, lensName, dataId });
57843
58316
  if (!capability) {
57844
- this._log("DEBUG", "No capability for author", { holonId, targetPubkey });
58317
+ this._log("WARN", "No capability found for federated author - returning empty", {
58318
+ holonId,
58319
+ lensName,
58320
+ targetPubkey: targetPubkey?.slice(0, 12) + "..."
58321
+ });
57845
58322
  return dataId ? null : [];
57846
58323
  }
58324
+ this._log("DEBUG", "✅ Capability found for federated author", {
58325
+ holonId,
58326
+ lensName,
58327
+ targetPubkey: targetPubkey?.slice(0, 12) + "..."
58328
+ });
57847
58329
  readOptions.authors = [targetPubkey];
57848
58330
  }
57849
58331
  if (!isOtherAuthor) {
@@ -57875,7 +58357,7 @@ class HoloSphereBase extends HoloSphere$1 {
57875
58357
  });
57876
58358
  result = null;
57877
58359
  } else {
57878
- const cached = !isOtherAuthor ? this._writeCache.get(path) : null;
58360
+ const cached = this._writeCache.get(path);
57879
58361
  if (cached) {
57880
58362
  const cacheAge = Date.now() - cached.timestamp;
57881
58363
  this._log("DEBUG", "⚡ CACHE HIT: Write cache", {
@@ -59378,4 +59860,4 @@ export {
59378
59860
  concatBytes as y,
59379
59861
  dataLength as z
59380
59862
  };
59381
- //# sourceMappingURL=index-TDDyakLc.js.map
59863
+ //# sourceMappingURL=index-BN_uoxQK.js.map