holosphere 2.0.0-alpha13 → 2.0.0-alpha15

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 (50) hide show
  1. package/dist/2019-ATLjawsU.cjs +8 -0
  2. package/dist/{2019-Cp3uYhyY.cjs.map → 2019-ATLjawsU.cjs.map} +1 -1
  3. package/dist/{2019-CLMqIAfQ.js → 2019-BfjzDRje.js} +1667 -1721
  4. package/dist/{2019-CLMqIAfQ.js.map → 2019-BfjzDRje.js.map} +1 -1
  5. package/dist/{browser-nUQt1cnB.js → browser-CKTczilW.js} +2 -2
  6. package/dist/{browser-nUQt1cnB.js.map → browser-CKTczilW.js.map} +1 -1
  7. package/dist/{browser-D6cNVl0v.cjs → browser-GOg6KKOV.cjs} +2 -2
  8. package/dist/{browser-D6cNVl0v.cjs.map → browser-GOg6KKOV.cjs.map} +1 -1
  9. package/dist/cjs/holosphere.cjs +1 -1
  10. package/dist/esm/holosphere.js +25 -21
  11. package/dist/{index-CoAjtqsD.js → index-BdnrGafX.js} +2 -2
  12. package/dist/{index-CoAjtqsD.js.map → index-BdnrGafX.js.map} +1 -1
  13. package/dist/{index-BN_uoxQK.js → index-C3Cag0SV.js} +2558 -495
  14. package/dist/index-C3Cag0SV.js.map +1 -0
  15. package/dist/index-ChpSfdYS.cjs +29 -0
  16. package/dist/index-ChpSfdYS.cjs.map +1 -0
  17. package/dist/{index-DJjGSwXG.cjs → index-D_QecZNu.cjs} +2 -2
  18. package/dist/{index-DJjGSwXG.cjs.map → index-D_QecZNu.cjs.map} +1 -1
  19. package/dist/{index-Z5TstN1e.js → index-nMC3dWZ5.js} +2 -2
  20. package/dist/{index-Z5TstN1e.js.map → index-nMC3dWZ5.js.map} +1 -1
  21. package/dist/{index-Cp3tI53z.cjs → index-z5HWfWMu.cjs} +2 -2
  22. package/dist/{index-Cp3tI53z.cjs.map → index-z5HWfWMu.cjs.map} +1 -1
  23. package/dist/{indexeddb-storage-CZK5A7XH.cjs → indexeddb-storage-DWSeL-YF.cjs} +2 -2
  24. package/dist/{indexeddb-storage-CZK5A7XH.cjs.map → indexeddb-storage-DWSeL-YF.cjs.map} +1 -1
  25. package/dist/{indexeddb-storage-bpA01pAU.js → indexeddb-storage-dx01N0ET.js} +2 -2
  26. package/dist/{indexeddb-storage-bpA01pAU.js.map → indexeddb-storage-dx01N0ET.js.map} +1 -1
  27. package/dist/{memory-storage-BqhmytP_.js → memory-storage-BPIfkpcf.js} +2 -2
  28. package/dist/{memory-storage-BqhmytP_.js.map → memory-storage-BPIfkpcf.js.map} +1 -1
  29. package/dist/{memory-storage-B1k8Jszd.cjs → memory-storage-CKUGDq2d.cjs} +2 -2
  30. package/dist/{memory-storage-B1k8Jszd.cjs.map → memory-storage-CKUGDq2d.cjs.map} +1 -1
  31. package/package.json +3 -1
  32. package/scripts/test-ndk-direct.js +104 -0
  33. package/src/crypto/key-store.js +356 -0
  34. package/src/crypto/lens-keys.js +205 -0
  35. package/src/crypto/secp256k1.js +181 -18
  36. package/src/federation/handshake.js +317 -23
  37. package/src/federation/hologram.js +25 -17
  38. package/src/federation/registry.js +779 -59
  39. package/src/index.js +416 -27
  40. package/src/lib/federation-methods.js +308 -4
  41. package/src/storage/nostr-async.js +144 -86
  42. package/src/storage/nostr-client.js +77 -18
  43. package/src/storage/nostr-wrapper.js +4 -1
  44. package/src/storage/unified-storage.js +5 -4
  45. package/src/subscriptions/manager.js +1 -1
  46. package/vitest.config.js +6 -1
  47. package/dist/2019-Cp3uYhyY.cjs +0 -8
  48. package/dist/index-BN_uoxQK.js.map +0 -1
  49. package/dist/index-V8EHMYEY.cjs +0 -29
  50. package/dist/index-V8EHMYEY.cjs.map +0 -1
@@ -1347,7 +1347,7 @@ function createHasher$1(hashCons) {
1347
1347
  hashC.create = () => hashCons();
1348
1348
  return hashC;
1349
1349
  }
1350
- function randomBytes$2(bytesLength = 32) {
1350
+ function randomBytes$3(bytesLength = 32) {
1351
1351
  if (crypto$2 && typeof crypto$2.getRandomValues === "function") {
1352
1352
  return crypto$2.getRandomValues(new Uint8Array(bytesLength));
1353
1353
  }
@@ -2930,15 +2930,15 @@ function weierstrassN(params, extraOpts = {}) {
2930
2930
  if (!Fn.isValidNot0(scalar))
2931
2931
  throw new Error("invalid scalar: out of range");
2932
2932
  let point, fake;
2933
- const mul = (n2) => wnaf.cached(this, n2, (p) => normalizeZ(Point, p));
2933
+ const mul3 = (n2) => wnaf.cached(this, n2, (p) => normalizeZ(Point, p));
2934
2934
  if (endo2) {
2935
2935
  const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(scalar);
2936
- const { p: k1p, f: k1f } = mul(k1);
2937
- const { p: k2p, f: k2f } = mul(k2);
2936
+ const { p: k1p, f: k1f } = mul3(k1);
2937
+ const { p: k2p, f: k2f } = mul3(k2);
2938
2938
  fake = k1f.add(k2f);
2939
2939
  point = finishEndo(endo2.beta, k1p, k2p, k1neg, k2neg);
2940
2940
  } else {
2941
- const { p, f } = mul(scalar);
2941
+ const { p, f } = mul3(scalar);
2942
2942
  point = p;
2943
2943
  fake = f;
2944
2944
  }
@@ -3062,7 +3062,7 @@ function getWLengths(Fp, Fn) {
3062
3062
  }
3063
3063
  function ecdh(Point, ecdhOpts = {}) {
3064
3064
  const { Fn } = Point;
3065
- const randomBytes_ = ecdhOpts.randomBytes || randomBytes$2;
3065
+ const randomBytes_ = ecdhOpts.randomBytes || randomBytes$3;
3066
3066
  const lengths = Object.assign(getWLengths(Point.Fp, Fn), { seed: getMinHashLength(Fn.ORDER) });
3067
3067
  function isValidSecretKey(secretKey) {
3068
3068
  try {
@@ -3137,7 +3137,7 @@ function ecdsa(Point, hash2, ecdsaOpts = {}) {
3137
3137
  bits2int: "function",
3138
3138
  bits2int_modN: "function"
3139
3139
  });
3140
- const randomBytes2 = ecdsaOpts.randomBytes || randomBytes$2;
3140
+ const randomBytes2 = ecdsaOpts.randomBytes || randomBytes$3;
3141
3141
  const hmac2 = ecdsaOpts.hmac || ((key, ...msgs) => hmac$1(hash2, key, concatBytes$3(...msgs)));
3142
3142
  const { Fp, Fn } = Point;
3143
3143
  const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn;
@@ -3541,7 +3541,7 @@ function challenge(...args) {
3541
3541
  function schnorrGetPublicKey(secretKey) {
3542
3542
  return schnorrGetExtPubKey(secretKey).bytes;
3543
3543
  }
3544
- function schnorrSign(message, secretKey, auxRand = randomBytes$2(32)) {
3544
+ function schnorrSign(message, secretKey, auxRand = randomBytes$3(32)) {
3545
3545
  const { Fn } = Pointk1;
3546
3546
  const m = ensureBytes("message", message);
3547
3547
  const { bytes: px, scalar: d2 } = schnorrGetExtPubKey(secretKey);
@@ -3583,7 +3583,7 @@ function schnorrVerify(signature, message, publicKey) {
3583
3583
  const schnorr = /* @__PURE__ */ (() => {
3584
3584
  const size = 32;
3585
3585
  const seedLength = 48;
3586
- const randomSecretKey = (seed = randomBytes$2(seedLength)) => {
3586
+ const randomSecretKey = (seed = randomBytes$3(seedLength)) => {
3587
3587
  return mapHashToField(seed, secp256k1_CURVE.n);
3588
3588
  };
3589
3589
  secp256k1$1.utils.randomSecretKey;
@@ -4808,7 +4808,7 @@ function wrapConstructor$2(hashCons) {
4808
4808
  hashC.create = () => hashCons();
4809
4809
  return hashC;
4810
4810
  }
4811
- function randomBytes$1(bytesLength = 32) {
4811
+ function randomBytes$2(bytesLength = 32) {
4812
4812
  if (crypto$1 && typeof crypto$1.getRandomValues === "function") {
4813
4813
  return crypto$1.getRandomValues(new Uint8Array(bytesLength));
4814
4814
  }
@@ -5365,6 +5365,7 @@ function output$2(out, instance) {
5365
5365
  }
5366
5366
  }
5367
5367
  /*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
5368
+ const u8 = (arr) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
5368
5369
  const u32$1 = (arr) => new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
5369
5370
  const createView$2 = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
5370
5371
  const isLE$2 = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68;
@@ -5409,8 +5410,8 @@ function setBigUint64$1(view, byteOffset, value, isLE2) {
5409
5410
  const _u32_max = BigInt(4294967295);
5410
5411
  const wh = Number(value >> _32n2 & _u32_max);
5411
5412
  const wl = Number(value & _u32_max);
5412
- const h = 4;
5413
- const l = 0;
5413
+ const h = isLE2 ? 4 : 0;
5414
+ const l = isLE2 ? 0 : 4;
5414
5415
  view.setUint32(byteOffset + h, wh, isLE2);
5415
5416
  view.setUint32(byteOffset + l, wl, isLE2);
5416
5417
  }
@@ -5659,7 +5660,7 @@ class Poly1305 {
5659
5660
  return res;
5660
5661
  }
5661
5662
  }
5662
- function wrapConstructorWithKey(hashCons) {
5663
+ function wrapConstructorWithKey$1(hashCons) {
5663
5664
  const hashC = (msg, key) => hashCons(key).update(toBytes$2(msg)).digest();
5664
5665
  const tmp = hashCons(new Uint8Array(32));
5665
5666
  hashC.outputLen = tmp.outputLen;
@@ -5667,7 +5668,7 @@ function wrapConstructorWithKey(hashCons) {
5667
5668
  hashC.create = (key) => hashCons(key);
5668
5669
  return hashC;
5669
5670
  }
5670
- const poly1305 = wrapConstructorWithKey((key) => new Poly1305(key));
5671
+ const poly1305 = wrapConstructorWithKey$1((key) => new Poly1305(key));
5671
5672
  const _utf8ToBytes = (str2) => Uint8Array.from(str2.split("").map((c) => c.charCodeAt(0)));
5672
5673
  const sigma16 = _utf8ToBytes("expand 16-byte k");
5673
5674
  const sigma32 = _utf8ToBytes("expand 32-byte k");
@@ -5948,16 +5949,16 @@ const xchacha20 = /* @__PURE__ */ createCipher(chachaCore, {
5948
5949
  extendNonceFn: hchacha,
5949
5950
  allowShortKeys: false
5950
5951
  });
5951
- const ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
5952
+ const ZEROS16$1 = /* @__PURE__ */ new Uint8Array(16);
5952
5953
  const updatePadded = (h, msg) => {
5953
5954
  h.update(msg);
5954
5955
  const left = msg.length % 16;
5955
5956
  if (left)
5956
- h.update(ZEROS16.subarray(left));
5957
+ h.update(ZEROS16$1.subarray(left));
5957
5958
  };
5958
- const ZEROS32 = /* @__PURE__ */ new Uint8Array(32);
5959
- function computeTag(fn, key, nonce, data, AAD) {
5960
- const authKey = fn(key, nonce, ZEROS32);
5959
+ const ZEROS32$1 = /* @__PURE__ */ new Uint8Array(32);
5960
+ function computeTag$1(fn, key, nonce, data, AAD) {
5961
+ const authKey = fn(key, nonce, ZEROS32$1);
5961
5962
  const h = poly1305.create(authKey);
5962
5963
  if (AAD)
5963
5964
  updatePadded(h, AAD);
@@ -5985,7 +5986,7 @@ const _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
5985
5986
  output2 = new Uint8Array(clength);
5986
5987
  }
5987
5988
  xorStream(key, nonce, plaintext, output2, 1);
5988
- const tag = computeTag(xorStream, key, nonce, output2.subarray(0, -tagLength), AAD);
5989
+ const tag = computeTag$1(xorStream, key, nonce, output2.subarray(0, -tagLength), AAD);
5989
5990
  output2.set(tag, plength);
5990
5991
  return output2;
5991
5992
  },
@@ -6001,7 +6002,7 @@ const _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
6001
6002
  }
6002
6003
  const data = ciphertext.subarray(0, -tagLength);
6003
6004
  const passedTag = ciphertext.subarray(-tagLength);
6004
- const tag = computeTag(xorStream, key, nonce, data, AAD);
6005
+ const tag = computeTag$1(xorStream, key, nonce, data, AAD);
6005
6006
  if (!equalBytes(passedTag, tag))
6006
6007
  throw new Error("invalid tag");
6007
6008
  xorStream(key, nonce, data, output2, 1);
@@ -6424,11 +6425,11 @@ function encodeBech32$1(prefix, data) {
6424
6425
  function encodeBytes$1(prefix, bytes2) {
6425
6426
  return encodeBech32$1(prefix, bytes2);
6426
6427
  }
6427
- function encrypt$1(sec, password, logn = 16, ksb = 2) {
6428
- let salt = randomBytes$1(16);
6428
+ function encrypt$2(sec, password, logn = 16, ksb = 2) {
6429
+ let salt = randomBytes$2(16);
6429
6430
  let n2 = 2 ** logn;
6430
6431
  let key = scrypt(password.normalize("NFKC"), salt, { N: n2, r: 8, p: 1, dkLen: 32 });
6431
- let nonce = randomBytes$1(24);
6432
+ let nonce = randomBytes$2(24);
6432
6433
  let aad = Uint8Array.from([ksb]);
6433
6434
  let xc2p1 = xchacha20poly1305(key, nonce, aad);
6434
6435
  let ciphertext = xc2p1.encrypt(sec);
@@ -6460,7 +6461,7 @@ function decrypt$1(ncryptsec, password) {
6460
6461
  const nip49_star = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
6461
6462
  __proto__: null,
6462
6463
  decrypt: decrypt$1,
6463
- encrypt: encrypt$1
6464
+ encrypt: encrypt$2
6464
6465
  }, Symbol.toStringTag, { value: "Module" }));
6465
6466
  var utf8Decoder = new TextDecoder("utf-8");
6466
6467
  var utf8Encoder = new TextEncoder();
@@ -7408,9 +7409,9 @@ var NDKRelayConnectivity = class {
7408
7409
  }
7409
7410
  case "COUNT": {
7410
7411
  const payload = data[2];
7411
- const cr = this.openCountRequests.get(id2);
7412
- if (cr) {
7413
- cr.resolve(payload.count);
7412
+ const cr2 = this.openCountRequests.get(id2);
7413
+ if (cr2) {
7414
+ cr2.resolve(payload.count);
7414
7415
  this.openCountRequests.delete(id2);
7415
7416
  }
7416
7417
  return;
@@ -9050,7 +9051,7 @@ async function generateContentTags(content, tags = [], opts, ctx) {
9050
9051
  async function maybeGetEventRelayUrl(_nip19Id) {
9051
9052
  return "";
9052
9053
  }
9053
- async function encrypt(recipient, signer, scheme = "nip44") {
9054
+ async function encrypt$1(recipient, signer, scheme = "nip44") {
9054
9055
  let encrypted;
9055
9056
  if (!this.ndk) throw new Error("No NDK instance found!");
9056
9057
  let currentSigner = signer;
@@ -9718,7 +9719,7 @@ var NDKEvent = class _NDKEvent extends lib$1.EventEmitter {
9718
9719
  * @returns {string} - Encoded naddr, note or nevent.
9719
9720
  */
9720
9721
  encode = encode$1.bind(this);
9721
- encrypt = encrypt.bind(this);
9722
+ encrypt = encrypt$1.bind(this);
9722
9723
  decrypt = decrypt.bind(this);
9723
9724
  /**
9724
9725
  * Get all tags with the given name
@@ -15528,7 +15529,7 @@ var NDKPrivateKeySigner = class _NDKPrivateKeySigner {
15528
15529
  */
15529
15530
  encryptToNcryptsec(password, logn = 16, ksb = 2) {
15530
15531
  if (!this._privateKey) throw new Error("Private key not available");
15531
- return encrypt$1(this._privateKey, password, logn, ksb);
15532
+ return encrypt$2(this._privateKey, password, logn, ksb);
15532
15533
  }
15533
15534
  /**
15534
15535
  * Generate a new private key.
@@ -18010,12 +18011,12 @@ async function createPersistentStorage(namespace, options = {}) {
18010
18011
  const isBrowser = typeof window !== "undefined" && typeof window.indexedDB !== "undefined";
18011
18012
  typeof process !== "undefined" && process.versions && void 0;
18012
18013
  if (isBrowser) {
18013
- const { IndexedDBStorage } = await import("./indexeddb-storage-bpA01pAU.js");
18014
+ const { IndexedDBStorage } = await import("./indexeddb-storage-dx01N0ET.js");
18014
18015
  const storage = new IndexedDBStorage();
18015
18016
  await storage.init(namespace);
18016
18017
  return storage;
18017
18018
  } else {
18018
- const { MemoryStorage } = await import("./memory-storage-BqhmytP_.js");
18019
+ const { MemoryStorage } = await import("./memory-storage-BPIfkpcf.js");
18019
18020
  const storage = new MemoryStorage();
18020
18021
  await storage.init(namespace);
18021
18022
  return storage;
@@ -18028,7 +18029,7 @@ async function loadNDKCacheAdapter() {
18028
18029
  ndkCacheAdapterLoaded = true;
18029
18030
  if (typeof window !== "undefined" && typeof indexedDB !== "undefined") {
18030
18031
  try {
18031
- const module2 = await import("./index-Z5TstN1e.js");
18032
+ const module2 = await import("./index-nMC3dWZ5.js");
18032
18033
  NDKCacheAdapterDexie = module2.default || module2.NDKCacheAdapterDexie;
18033
18034
  } catch (e) {
18034
18035
  console.debug("[nostr] NDK cache adapter not available, using LRU cache");
@@ -18220,7 +18221,13 @@ class NostrClient {
18220
18221
  cacheAdapter
18221
18222
  });
18222
18223
  this.relays.forEach((r) => globalNDKRelays.add(r));
18223
- await this.ndk.connect();
18224
+ console.log("[NostrClient] Connecting to relays:", this.relays);
18225
+ try {
18226
+ await this.ndk.connect();
18227
+ console.log("[NostrClient] NDK connect() completed. Pool stats:", this.ndk.pool?.stats?.());
18228
+ } catch (err) {
18229
+ console.error("[NostrClient] NDK connect() FAILED:", err.message);
18230
+ }
18224
18231
  } else {
18225
18232
  this.ndk = null;
18226
18233
  }
@@ -18403,7 +18410,15 @@ class NostrClient {
18403
18410
  * @returns {Promise<Object|null>} The event or null if not found
18404
18411
  */
18405
18412
  async persistentGet(path) {
18406
- await this._initReady;
18413
+ try {
18414
+ await Promise.race([
18415
+ this._initReady,
18416
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Init timeout")), 1e4))
18417
+ ]);
18418
+ } catch (err) {
18419
+ console.warn("[NostrClient] persistentGet: Init timeout, returning null");
18420
+ return null;
18421
+ }
18407
18422
  if (!this.persistentStorage) return null;
18408
18423
  try {
18409
18424
  const event = await this.persistentStorage.get(path);
@@ -18490,7 +18505,14 @@ class NostrClient {
18490
18505
  * });
18491
18506
  */
18492
18507
  async publish(event, options = {}) {
18493
- await this._initReady;
18508
+ try {
18509
+ await Promise.race([
18510
+ this._initReady,
18511
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Init timeout")), 15e3))
18512
+ ]);
18513
+ } catch (err) {
18514
+ console.warn("[NostrClient] publish: Init timeout, continuing with available state");
18515
+ }
18494
18516
  const waitForRelays = options.waitForRelays || false;
18495
18517
  const isReplaceable2 = event.kind >= 3e4 && event.kind < 4e4;
18496
18518
  const shouldDebounce = isReplaceable2 && options.debounce !== false && !waitForRelays;
@@ -18642,9 +18664,14 @@ class NostrClient {
18642
18664
  const failed = [];
18643
18665
  const formattedResults = [];
18644
18666
  try {
18645
- const publishedRelays = await ndkEvent.publish();
18667
+ const publishPromise = ndkEvent.publish();
18668
+ const timeoutPromise = new Promise(
18669
+ (_, reject) => setTimeout(() => reject(new Error("Publish timeout")), 3e4)
18670
+ );
18671
+ const publishedRelays = await Promise.race([publishPromise, timeoutPromise]);
18646
18672
  for (const relay of relays) {
18647
- const wasPublished = publishedRelays.has(this.ndk.pool.getRelay(relay));
18673
+ const ndkRelay = this.ndk.pool.getRelay(relay);
18674
+ const wasPublished = publishedRelays.has(ndkRelay);
18648
18675
  if (wasPublished) {
18649
18676
  successful.push(relay);
18650
18677
  formattedResults.push({
@@ -18701,8 +18728,16 @@ class NostrClient {
18701
18728
  * });
18702
18729
  */
18703
18730
  async query(filter, options = {}) {
18704
- await this._initReady;
18705
- const timeout = options.timeout !== void 0 ? options.timeout : 3e4;
18731
+ const queryTimeout = options.timeout !== void 0 ? options.timeout : 3e4;
18732
+ try {
18733
+ await Promise.race([
18734
+ this._initReady,
18735
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Init timeout")), Math.min(queryTimeout, 1e4)))
18736
+ ]);
18737
+ } catch (err) {
18738
+ console.warn("[NostrClient] query: Init timeout, continuing with available state");
18739
+ }
18740
+ const timeout = queryTimeout;
18706
18741
  const localFirst = options.localFirst !== false;
18707
18742
  const forceRelay = options.forceRelay === true;
18708
18743
  if (this.relays.length === 0 || !this.ndk) {
@@ -18715,7 +18750,7 @@ class NostrClient {
18715
18750
  const matchingEvents = this._getMatchingCachedEvents(filter);
18716
18751
  return matchingEvents;
18717
18752
  }
18718
- if (isOwnDataQuery && subInfo && !subInfo.initialized) {
18753
+ if (!forceRelay && isOwnDataQuery && subInfo && !subInfo.initialized) {
18719
18754
  await Promise.race([
18720
18755
  subInfo.initPromise,
18721
18756
  new Promise((resolve) => setTimeout(resolve, Math.min(timeout, AUTHOR_SUB_INIT_TIMEOUT)))
@@ -18757,10 +18792,23 @@ class NostrClient {
18757
18792
  try {
18758
18793
  let events = [];
18759
18794
  if (this.ndk) {
18760
- const fetchedEvents = await this.ndk.fetchEvents(filter, {
18795
+ const fetchPromise = this.ndk.fetchEvents(filter, {
18761
18796
  closeOnEose: true
18762
18797
  });
18763
- events = Array.from(fetchedEvents).map((e) => e.rawEvent());
18798
+ const timeoutPromise = new Promise(
18799
+ (_, reject) => setTimeout(() => reject(new Error("Relay query timeout")), timeout || 3e4)
18800
+ );
18801
+ try {
18802
+ const fetchedEvents = await Promise.race([fetchPromise, timeoutPromise]);
18803
+ events = Array.from(fetchedEvents).map((e) => e.rawEvent());
18804
+ } catch (err) {
18805
+ if (err.message === "Relay query timeout") {
18806
+ console.warn("[NostrClient] Relay query timed out, returning empty results");
18807
+ events = [];
18808
+ } else {
18809
+ throw err;
18810
+ }
18811
+ }
18764
18812
  }
18765
18813
  if (filter.authors && filter.authors.length > 0) {
18766
18814
  events = events.filter((event) => filter.authors.includes(event.pubkey));
@@ -18935,16 +18983,20 @@ class NostrClient {
18935
18983
  */
18936
18984
  async queryHybrid(filter, options = {}) {
18937
18985
  await this._initReady;
18938
- options.timeout !== void 0 ? options.timeout : 3e4;
18986
+ const timeout = options.timeout !== void 0 ? options.timeout : 3e4;
18939
18987
  const localEvents = this._getMatchingCachedEvents(filter);
18940
18988
  if (this.relays.length === 0 || !this.ndk) {
18941
18989
  return localEvents;
18942
18990
  }
18943
18991
  let relayEvents = [];
18944
18992
  try {
18945
- const fetchedEvents = await this.ndk.fetchEvents(filter, {
18993
+ const fetchPromise = this.ndk.fetchEvents(filter, {
18946
18994
  closeOnEose: true
18947
18995
  });
18996
+ const timeoutPromise = new Promise(
18997
+ (_, reject) => setTimeout(() => reject(new Error("Relay query timeout")), timeout)
18998
+ );
18999
+ const fetchedEvents = await Promise.race([fetchPromise, timeoutPromise]);
18948
19000
  relayEvents = Array.from(fetchedEvents).map((e) => e.rawEvent());
18949
19001
  if (filter.authors && filter.authors.length > 0) {
18950
19002
  relayEvents = relayEvents.filter((event) => filter.authors.includes(event.pubkey));
@@ -20963,7 +21015,7 @@ class GunSchemaValidator {
20963
21015
  return true;
20964
21016
  }
20965
21017
  try {
20966
- const AjvModule = await import("./2019-CLMqIAfQ.js").then((n2) => n2._);
21018
+ const AjvModule = await import("./2019-BfjzDRje.js").then((n2) => n2._);
20967
21019
  this.Ajv = AjvModule.default || AjvModule;
20968
21020
  this.validator = new this.Ajv({
20969
21021
  allErrors: true,
@@ -21184,7 +21236,7 @@ class GunDBBackend extends StorageBackend {
21184
21236
  let Gun;
21185
21237
  try {
21186
21238
  console.log("[gundb-backend] Importing Gun...");
21187
- const gunModule = await import("./browser-nUQt1cnB.js").then((n2) => n2.b);
21239
+ const gunModule = await import("./browser-CKTczilW.js").then((n2) => n2.b);
21188
21240
  Gun = gunModule.default || gunModule;
21189
21241
  console.log("[gundb-backend] Gun imported:", typeof Gun);
21190
21242
  } catch (error) {
@@ -21815,7 +21867,7 @@ class GunDBBackend extends StorageBackend {
21815
21867
  }
21816
21868
  }
21817
21869
  const name = "holosphere";
21818
- const version$1 = "2.0.0-alpha12";
21870
+ const version$1 = "2.0.0-alpha14";
21819
21871
  const description = "Holonic geospatial communication infrastructure combining H3 hexagonal indexing with distributed P2P storage";
21820
21872
  const type = "module";
21821
21873
  const bin = {
@@ -21840,6 +21892,8 @@ const scripts = {
21840
21892
  test: "vitest run",
21841
21893
  "test:watch": "vitest",
21842
21894
  "test:coverage": "vitest run --coverage",
21895
+ "test:real-relays": "USE_REAL_RELAYS=true vitest run",
21896
+ "test:integration:real": "USE_REAL_RELAYS=true vitest run tests/unit/integration",
21843
21897
  lint: "eslint src tests",
21844
21898
  format: 'prettier --write "src/**/*.js" "tests/**/*.js"'
21845
21899
  };
@@ -22580,44 +22634,784 @@ const h3Operations = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.define
22580
22634
  isValidH3,
22581
22635
  toHolon
22582
22636
  }, Symbol.toStringTag, { value: "Module" }));
22637
+ const BLOCK_SIZE$1 = 16;
22638
+ const ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
22639
+ const ZEROS32 = u32$1(ZEROS16);
22640
+ const POLY$1 = 225;
22641
+ const mul2$1 = (s0, s1, s2, s3) => {
22642
+ const hiBit = s3 & 1;
22643
+ return {
22644
+ s3: s2 << 31 | s3 >>> 1,
22645
+ s2: s1 << 31 | s2 >>> 1,
22646
+ s1: s0 << 31 | s1 >>> 1,
22647
+ s0: s0 >>> 1 ^ POLY$1 << 24 & -(hiBit & 1)
22648
+ // reduce % poly
22649
+ };
22650
+ };
22651
+ const swapLE = (n2) => (n2 >>> 0 & 255) << 24 | (n2 >>> 8 & 255) << 16 | (n2 >>> 16 & 255) << 8 | n2 >>> 24 & 255 | 0;
22652
+ function _toGHASHKey(k) {
22653
+ k.reverse();
22654
+ const hiBit = k[15] & 1;
22655
+ let carry = 0;
22656
+ for (let i = 0; i < k.length; i++) {
22657
+ const t = k[i];
22658
+ k[i] = t >>> 1 | carry;
22659
+ carry = (t & 1) << 7;
22660
+ }
22661
+ k[0] ^= -hiBit & 225;
22662
+ return k;
22663
+ }
22664
+ const estimateWindow = (bytes2) => {
22665
+ if (bytes2 > 64 * 1024)
22666
+ return 8;
22667
+ if (bytes2 > 1024)
22668
+ return 4;
22669
+ return 2;
22670
+ };
22671
+ class GHASH {
22672
+ // We select bits per window adaptively based on expectedLength
22673
+ constructor(key, expectedLength) {
22674
+ this.blockLen = BLOCK_SIZE$1;
22675
+ this.outputLen = BLOCK_SIZE$1;
22676
+ this.s0 = 0;
22677
+ this.s1 = 0;
22678
+ this.s2 = 0;
22679
+ this.s3 = 0;
22680
+ this.finished = false;
22681
+ key = toBytes$2(key);
22682
+ bytes$2(key, 16);
22683
+ const kView = createView$2(key);
22684
+ let k0 = kView.getUint32(0, false);
22685
+ let k1 = kView.getUint32(4, false);
22686
+ let k2 = kView.getUint32(8, false);
22687
+ let k3 = kView.getUint32(12, false);
22688
+ const doubles = [];
22689
+ for (let i = 0; i < 128; i++) {
22690
+ doubles.push({ s0: swapLE(k0), s1: swapLE(k1), s2: swapLE(k2), s3: swapLE(k3) });
22691
+ ({ s0: k0, s1: k1, s2: k2, s3: k3 } = mul2$1(k0, k1, k2, k3));
22692
+ }
22693
+ const W = estimateWindow(expectedLength || 1024);
22694
+ if (![1, 2, 4, 8].includes(W))
22695
+ throw new Error(`ghash: wrong window size=${W}, should be 2, 4 or 8`);
22696
+ this.W = W;
22697
+ const bits = 128;
22698
+ const windows = bits / W;
22699
+ const windowSize = this.windowSize = 2 ** W;
22700
+ const items = [];
22701
+ for (let w = 0; w < windows; w++) {
22702
+ for (let byte = 0; byte < windowSize; byte++) {
22703
+ let s0 = 0, s1 = 0, s2 = 0, s3 = 0;
22704
+ for (let j = 0; j < W; j++) {
22705
+ const bit = byte >>> W - j - 1 & 1;
22706
+ if (!bit)
22707
+ continue;
22708
+ const { s0: d0, s1: d1, s2: d2, s3: d3 } = doubles[W * w + j];
22709
+ s0 ^= d0, s1 ^= d1, s2 ^= d2, s3 ^= d3;
22710
+ }
22711
+ items.push({ s0, s1, s2, s3 });
22712
+ }
22713
+ }
22714
+ this.t = items;
22715
+ }
22716
+ _updateBlock(s0, s1, s2, s3) {
22717
+ s0 ^= this.s0, s1 ^= this.s1, s2 ^= this.s2, s3 ^= this.s3;
22718
+ const { W, t, windowSize } = this;
22719
+ let o0 = 0, o1 = 0, o2 = 0, o3 = 0;
22720
+ const mask2 = (1 << W) - 1;
22721
+ let w = 0;
22722
+ for (const num2 of [s0, s1, s2, s3]) {
22723
+ for (let bytePos = 0; bytePos < 4; bytePos++) {
22724
+ const byte = num2 >>> 8 * bytePos & 255;
22725
+ for (let bitPos = 8 / W - 1; bitPos >= 0; bitPos--) {
22726
+ const bit = byte >>> W * bitPos & mask2;
22727
+ const { s0: e0, s1: e1, s2: e2, s3: e3 } = t[w * windowSize + bit];
22728
+ o0 ^= e0, o1 ^= e1, o2 ^= e2, o3 ^= e3;
22729
+ w += 1;
22730
+ }
22731
+ }
22732
+ }
22733
+ this.s0 = o0;
22734
+ this.s1 = o1;
22735
+ this.s2 = o2;
22736
+ this.s3 = o3;
22737
+ }
22738
+ update(data) {
22739
+ data = toBytes$2(data);
22740
+ exists$2(this);
22741
+ const b32 = u32$1(data);
22742
+ const blocks = Math.floor(data.length / BLOCK_SIZE$1);
22743
+ const left = data.length % BLOCK_SIZE$1;
22744
+ for (let i = 0; i < blocks; i++) {
22745
+ this._updateBlock(b32[i * 4 + 0], b32[i * 4 + 1], b32[i * 4 + 2], b32[i * 4 + 3]);
22746
+ }
22747
+ if (left) {
22748
+ ZEROS16.set(data.subarray(blocks * BLOCK_SIZE$1));
22749
+ this._updateBlock(ZEROS32[0], ZEROS32[1], ZEROS32[2], ZEROS32[3]);
22750
+ ZEROS32.fill(0);
22751
+ }
22752
+ return this;
22753
+ }
22754
+ destroy() {
22755
+ const { t } = this;
22756
+ for (const elm of t) {
22757
+ elm.s0 = 0, elm.s1 = 0, elm.s2 = 0, elm.s3 = 0;
22758
+ }
22759
+ }
22760
+ digestInto(out) {
22761
+ exists$2(this);
22762
+ output$2(out, this);
22763
+ this.finished = true;
22764
+ const { s0, s1, s2, s3 } = this;
22765
+ const o32 = u32$1(out);
22766
+ o32[0] = s0;
22767
+ o32[1] = s1;
22768
+ o32[2] = s2;
22769
+ o32[3] = s3;
22770
+ return out;
22771
+ }
22772
+ digest() {
22773
+ const res = new Uint8Array(BLOCK_SIZE$1);
22774
+ this.digestInto(res);
22775
+ this.destroy();
22776
+ return res;
22777
+ }
22778
+ }
22779
+ class Polyval extends GHASH {
22780
+ constructor(key, expectedLength) {
22781
+ key = toBytes$2(key);
22782
+ const ghKey = _toGHASHKey(key.slice());
22783
+ super(ghKey, expectedLength);
22784
+ ghKey.fill(0);
22785
+ }
22786
+ update(data) {
22787
+ data = toBytes$2(data);
22788
+ exists$2(this);
22789
+ const b32 = u32$1(data);
22790
+ const left = data.length % BLOCK_SIZE$1;
22791
+ const blocks = Math.floor(data.length / BLOCK_SIZE$1);
22792
+ for (let i = 0; i < blocks; i++) {
22793
+ this._updateBlock(swapLE(b32[i * 4 + 3]), swapLE(b32[i * 4 + 2]), swapLE(b32[i * 4 + 1]), swapLE(b32[i * 4 + 0]));
22794
+ }
22795
+ if (left) {
22796
+ ZEROS16.set(data.subarray(blocks * BLOCK_SIZE$1));
22797
+ this._updateBlock(swapLE(ZEROS32[3]), swapLE(ZEROS32[2]), swapLE(ZEROS32[1]), swapLE(ZEROS32[0]));
22798
+ ZEROS32.fill(0);
22799
+ }
22800
+ return this;
22801
+ }
22802
+ digestInto(out) {
22803
+ exists$2(this);
22804
+ output$2(out, this);
22805
+ this.finished = true;
22806
+ const { s0, s1, s2, s3 } = this;
22807
+ const o32 = u32$1(out);
22808
+ o32[0] = s0;
22809
+ o32[1] = s1;
22810
+ o32[2] = s2;
22811
+ o32[3] = s3;
22812
+ return out.reverse();
22813
+ }
22814
+ }
22815
+ function wrapConstructorWithKey(hashCons) {
22816
+ const hashC = (msg, key) => hashCons(key, msg.length).update(toBytes$2(msg)).digest();
22817
+ const tmp = hashCons(new Uint8Array(16), 0);
22818
+ hashC.outputLen = tmp.outputLen;
22819
+ hashC.blockLen = tmp.blockLen;
22820
+ hashC.create = (key, expectedLength) => hashCons(key, expectedLength);
22821
+ return hashC;
22822
+ }
22823
+ const ghash = wrapConstructorWithKey((key, expectedLength) => new GHASH(key, expectedLength));
22824
+ wrapConstructorWithKey((key, expectedLength) => new Polyval(key, expectedLength));
22825
+ const BLOCK_SIZE = 16;
22826
+ const BLOCK_SIZE32 = 4;
22827
+ const EMPTY_BLOCK = new Uint8Array(BLOCK_SIZE);
22828
+ const POLY = 283;
22829
+ function mul2(n2) {
22830
+ return n2 << 1 ^ POLY & -(n2 >> 7);
22831
+ }
22832
+ function mul(a, b2) {
22833
+ let res = 0;
22834
+ for (; b2 > 0; b2 >>= 1) {
22835
+ res ^= a & -(b2 & 1);
22836
+ a = mul2(a);
22837
+ }
22838
+ return res;
22839
+ }
22840
+ const sbox = /* @__PURE__ */ (() => {
22841
+ let t = new Uint8Array(256);
22842
+ for (let i = 0, x = 1; i < 256; i++, x ^= mul2(x))
22843
+ t[i] = x;
22844
+ const box = new Uint8Array(256);
22845
+ box[0] = 99;
22846
+ for (let i = 0; i < 255; i++) {
22847
+ let x = t[255 - i];
22848
+ x |= x << 8;
22849
+ box[t[i]] = (x ^ x >> 4 ^ x >> 5 ^ x >> 6 ^ x >> 7 ^ 99) & 255;
22850
+ }
22851
+ return box;
22852
+ })();
22853
+ const rotr32_8 = (n2) => n2 << 24 | n2 >>> 8;
22854
+ const rotl32_8 = (n2) => n2 << 8 | n2 >>> 24;
22855
+ function genTtable(sbox2, fn) {
22856
+ if (sbox2.length !== 256)
22857
+ throw new Error("Wrong sbox length");
22858
+ const T0 = new Uint32Array(256).map((_, j) => fn(sbox2[j]));
22859
+ const T1 = T0.map(rotl32_8);
22860
+ const T2 = T1.map(rotl32_8);
22861
+ const T3 = T2.map(rotl32_8);
22862
+ const T01 = new Uint32Array(256 * 256);
22863
+ const T23 = new Uint32Array(256 * 256);
22864
+ const sbox22 = new Uint16Array(256 * 256);
22865
+ for (let i = 0; i < 256; i++) {
22866
+ for (let j = 0; j < 256; j++) {
22867
+ const idx = i * 256 + j;
22868
+ T01[idx] = T0[i] ^ T1[j];
22869
+ T23[idx] = T2[i] ^ T3[j];
22870
+ sbox22[idx] = sbox2[i] << 8 | sbox2[j];
22871
+ }
22872
+ }
22873
+ return { sbox: sbox2, sbox2: sbox22, T0, T1, T2, T3, T01, T23 };
22874
+ }
22875
+ const tableEncoding = /* @__PURE__ */ genTtable(sbox, (s) => mul(s, 3) << 24 | s << 16 | s << 8 | mul(s, 2));
22876
+ const xPowers = /* @__PURE__ */ (() => {
22877
+ const p = new Uint8Array(16);
22878
+ for (let i = 0, x = 1; i < 16; i++, x = mul2(x))
22879
+ p[i] = x;
22880
+ return p;
22881
+ })();
22882
+ function expandKeyLE(key) {
22883
+ bytes$2(key);
22884
+ const len = key.length;
22885
+ if (![16, 24, 32].includes(len))
22886
+ throw new Error(`aes: wrong key size: should be 16, 24 or 32, got: ${len}`);
22887
+ const { sbox2 } = tableEncoding;
22888
+ const k32 = u32$1(key);
22889
+ const Nk = k32.length;
22890
+ const subByte = (n2) => applySbox(sbox2, n2, n2, n2, n2);
22891
+ const xk = new Uint32Array(len + 28);
22892
+ xk.set(k32);
22893
+ for (let i = Nk; i < xk.length; i++) {
22894
+ let t = xk[i - 1];
22895
+ if (i % Nk === 0)
22896
+ t = subByte(rotr32_8(t)) ^ xPowers[i / Nk - 1];
22897
+ else if (Nk > 6 && i % Nk === 4)
22898
+ t = subByte(t);
22899
+ xk[i] = xk[i - Nk] ^ t;
22900
+ }
22901
+ return xk;
22902
+ }
22903
+ function apply0123(T01, T23, s0, s1, s2, s3) {
22904
+ return T01[s0 << 8 & 65280 | s1 >>> 8 & 255] ^ T23[s2 >>> 8 & 65280 | s3 >>> 24 & 255];
22905
+ }
22906
+ function applySbox(sbox2, s0, s1, s2, s3) {
22907
+ return sbox2[s0 & 255 | s1 & 65280] | sbox2[s2 >>> 16 & 255 | s3 >>> 16 & 65280] << 16;
22908
+ }
22909
+ function encrypt(xk, s0, s1, s2, s3) {
22910
+ const { sbox2, T01, T23 } = tableEncoding;
22911
+ let k = 0;
22912
+ s0 ^= xk[k++], s1 ^= xk[k++], s2 ^= xk[k++], s3 ^= xk[k++];
22913
+ const rounds = xk.length / 4 - 2;
22914
+ for (let i = 0; i < rounds; i++) {
22915
+ const t02 = xk[k++] ^ apply0123(T01, T23, s0, s1, s2, s3);
22916
+ const t12 = xk[k++] ^ apply0123(T01, T23, s1, s2, s3, s0);
22917
+ const t22 = xk[k++] ^ apply0123(T01, T23, s2, s3, s0, s1);
22918
+ const t32 = xk[k++] ^ apply0123(T01, T23, s3, s0, s1, s2);
22919
+ s0 = t02, s1 = t12, s2 = t22, s3 = t32;
22920
+ }
22921
+ const t0 = xk[k++] ^ applySbox(sbox2, s0, s1, s2, s3);
22922
+ const t1 = xk[k++] ^ applySbox(sbox2, s1, s2, s3, s0);
22923
+ const t2 = xk[k++] ^ applySbox(sbox2, s2, s3, s0, s1);
22924
+ const t3 = xk[k++] ^ applySbox(sbox2, s3, s0, s1, s2);
22925
+ return { s0: t0, s1: t1, s2: t2, s3: t3 };
22926
+ }
22927
+ function getDst(len, dst) {
22928
+ if (!dst)
22929
+ return new Uint8Array(len);
22930
+ bytes$2(dst);
22931
+ if (dst.length < len)
22932
+ throw new Error(`aes: wrong destination length, expected at least ${len}, got: ${dst.length}`);
22933
+ return dst;
22934
+ }
22935
+ function ctr32(xk, isLE2, nonce, src, dst) {
22936
+ bytes$2(nonce, BLOCK_SIZE);
22937
+ bytes$2(src);
22938
+ dst = getDst(src.length, dst);
22939
+ const ctr = nonce;
22940
+ const c32 = u32$1(ctr);
22941
+ const view = createView$2(ctr);
22942
+ const src32 = u32$1(src);
22943
+ const dst32 = u32$1(dst);
22944
+ const ctrPos = isLE2 ? 0 : 12;
22945
+ const srcLen = src.length;
22946
+ let ctrNum = view.getUint32(ctrPos, isLE2);
22947
+ let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
22948
+ for (let i = 0; i + 4 <= src32.length; i += 4) {
22949
+ dst32[i + 0] = src32[i + 0] ^ s0;
22950
+ dst32[i + 1] = src32[i + 1] ^ s1;
22951
+ dst32[i + 2] = src32[i + 2] ^ s2;
22952
+ dst32[i + 3] = src32[i + 3] ^ s3;
22953
+ ctrNum = ctrNum + 1 >>> 0;
22954
+ view.setUint32(ctrPos, ctrNum, isLE2);
22955
+ ({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
22956
+ }
22957
+ const start = BLOCK_SIZE * Math.floor(src32.length / BLOCK_SIZE32);
22958
+ if (start < srcLen) {
22959
+ const b32 = new Uint32Array([s0, s1, s2, s3]);
22960
+ const buf = u8(b32);
22961
+ for (let i = start, pos = 0; i < srcLen; i++, pos++)
22962
+ dst[i] = src[i] ^ buf[pos];
22963
+ }
22964
+ return dst;
22965
+ }
22966
+ function computeTag(fn, isLE2, key, data, AAD) {
22967
+ const h = fn.create(key, data.length + (AAD?.length || 0));
22968
+ if (AAD)
22969
+ h.update(AAD);
22970
+ h.update(data);
22971
+ const num2 = new Uint8Array(16);
22972
+ const view = createView$2(num2);
22973
+ if (AAD)
22974
+ setBigUint64$1(view, 0, BigInt(AAD.length * 8), isLE2);
22975
+ setBigUint64$1(view, 8, BigInt(data.length * 8), isLE2);
22976
+ h.update(num2);
22977
+ return h.digest();
22978
+ }
22979
+ const gcm = /* @__PURE__ */ wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16 }, function gcm2(key, nonce, AAD) {
22980
+ bytes$2(nonce);
22981
+ if (nonce.length === 0)
22982
+ throw new Error("aes/gcm: empty nonce");
22983
+ const tagLength = 16;
22984
+ function _computeTag(authKey, tagMask, data) {
22985
+ const tag = computeTag(ghash, false, authKey, data, AAD);
22986
+ for (let i = 0; i < tagMask.length; i++)
22987
+ tag[i] ^= tagMask[i];
22988
+ return tag;
22989
+ }
22990
+ function deriveKeys() {
22991
+ const xk = expandKeyLE(key);
22992
+ const authKey = EMPTY_BLOCK.slice();
22993
+ const counter = EMPTY_BLOCK.slice();
22994
+ ctr32(xk, false, counter, counter, authKey);
22995
+ if (nonce.length === 12) {
22996
+ counter.set(nonce);
22997
+ } else {
22998
+ const nonceLen = EMPTY_BLOCK.slice();
22999
+ const view = createView$2(nonceLen);
23000
+ setBigUint64$1(view, 8, BigInt(nonce.length * 8), false);
23001
+ ghash.create(authKey).update(nonce).update(nonceLen).digestInto(counter);
23002
+ }
23003
+ const tagMask = ctr32(xk, false, counter, EMPTY_BLOCK);
23004
+ return { xk, authKey, counter, tagMask };
23005
+ }
23006
+ return {
23007
+ encrypt: (plaintext) => {
23008
+ bytes$2(plaintext);
23009
+ const { xk, authKey, counter, tagMask } = deriveKeys();
23010
+ const out = new Uint8Array(plaintext.length + tagLength);
23011
+ ctr32(xk, false, counter, plaintext, out);
23012
+ const tag = _computeTag(authKey, tagMask, out.subarray(0, out.length - tagLength));
23013
+ out.set(tag, plaintext.length);
23014
+ xk.fill(0);
23015
+ return out;
23016
+ },
23017
+ decrypt: (ciphertext) => {
23018
+ bytes$2(ciphertext);
23019
+ if (ciphertext.length < tagLength)
23020
+ throw new Error(`aes/gcm: ciphertext less than tagLen (${tagLength})`);
23021
+ const { xk, authKey, counter, tagMask } = deriveKeys();
23022
+ const data = ciphertext.subarray(0, -tagLength);
23023
+ const passedTag = ciphertext.subarray(-tagLength);
23024
+ const tag = _computeTag(authKey, tagMask, data);
23025
+ if (!equalBytes(tag, passedTag))
23026
+ throw new Error("aes/gcm: invalid ghash tag");
23027
+ const out = ctr32(xk, false, counter, data);
23028
+ authKey.fill(0);
23029
+ tagMask.fill(0);
23030
+ xk.fill(0);
23031
+ return out;
23032
+ }
23033
+ };
23034
+ });
23035
+ const cr = typeof globalThis === "object" && "crypto" in globalThis ? globalThis.crypto : void 0;
23036
+ function randomBytes$1(bytesLength = 32) {
23037
+ if (cr && typeof cr.getRandomValues === "function")
23038
+ return cr.getRandomValues(new Uint8Array(bytesLength));
23039
+ throw new Error("crypto.getRandomValues must be defined");
23040
+ }
23041
+ /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
23042
+ const u8a$1 = (a) => a instanceof Uint8Array;
23043
+ const createView$1 = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
23044
+ const rotr$1 = (word, shift) => word << 32 - shift | word >>> shift;
23045
+ const isLE$1 = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68;
23046
+ if (!isLE$1)
23047
+ throw new Error("Non little-endian hardware is not supported");
23048
+ const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, "0"));
23049
+ function bytesToHex$1(bytes2) {
23050
+ if (!u8a$1(bytes2))
23051
+ throw new Error("Uint8Array expected");
23052
+ let hex2 = "";
23053
+ for (let i = 0; i < bytes2.length; i++) {
23054
+ hex2 += hexes[bytes2[i]];
23055
+ }
23056
+ return hex2;
23057
+ }
23058
+ function hexToBytes$1(hex2) {
23059
+ if (typeof hex2 !== "string")
23060
+ throw new Error("hex string expected, got " + typeof hex2);
23061
+ const len = hex2.length;
23062
+ if (len % 2)
23063
+ throw new Error("padded hex string expected, got unpadded hex of length " + len);
23064
+ const array = new Uint8Array(len / 2);
23065
+ for (let i = 0; i < array.length; i++) {
23066
+ const j = i * 2;
23067
+ const hexByte = hex2.slice(j, j + 2);
23068
+ const byte = Number.parseInt(hexByte, 16);
23069
+ if (Number.isNaN(byte) || byte < 0)
23070
+ throw new Error("Invalid byte sequence");
23071
+ array[i] = byte;
23072
+ }
23073
+ return array;
23074
+ }
23075
+ function utf8ToBytes$1(str2) {
23076
+ if (typeof str2 !== "string")
23077
+ throw new Error(`utf8ToBytes expected string, got ${typeof str2}`);
23078
+ return new Uint8Array(new TextEncoder().encode(str2));
23079
+ }
23080
+ function toBytes$1(data) {
23081
+ if (typeof data === "string")
23082
+ data = utf8ToBytes$1(data);
23083
+ if (!u8a$1(data))
23084
+ throw new Error(`expected Uint8Array, got ${typeof data}`);
23085
+ return data;
23086
+ }
23087
+ function concatBytes$1(...arrays) {
23088
+ const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
23089
+ let pad = 0;
23090
+ arrays.forEach((a) => {
23091
+ if (!u8a$1(a))
23092
+ throw new Error("Uint8Array expected");
23093
+ r.set(a, pad);
23094
+ pad += a.length;
23095
+ });
23096
+ return r;
23097
+ }
23098
+ let Hash$1 = class Hash5 {
23099
+ // Safe version that clones internal state
23100
+ clone() {
23101
+ return this._cloneInto();
23102
+ }
23103
+ };
23104
+ function wrapConstructor$1(hashCons) {
23105
+ const hashC = (msg) => hashCons().update(toBytes$1(msg)).digest();
23106
+ const tmp = hashCons();
23107
+ hashC.outputLen = tmp.outputLen;
23108
+ hashC.blockLen = tmp.blockLen;
23109
+ hashC.create = () => hashCons();
23110
+ return hashC;
23111
+ }
23112
+ function hexToBytes(hex2) {
23113
+ const bytes2 = new Uint8Array(hex2.length / 2);
23114
+ for (let i = 0; i < hex2.length; i += 2) {
23115
+ bytes2[i / 2] = parseInt(hex2.substr(i, 2), 16);
23116
+ }
23117
+ return bytes2;
23118
+ }
23119
+ function bytesToHex(bytes2) {
23120
+ return Array.from(bytes2).map((b2) => b2.toString(16).padStart(2, "0")).join("");
23121
+ }
23122
+ function parseNpubOrHex(input) {
23123
+ const trimmed = input?.trim();
23124
+ if (!trimmed) {
23125
+ return { valid: false, error: "Public key is required" };
23126
+ }
23127
+ if (/^[0-9a-fA-F]{64}$/.test(trimmed)) {
23128
+ return { valid: true, hexPubKey: trimmed.toLowerCase() };
23129
+ }
23130
+ let npubString = trimmed;
23131
+ if (npubString.startsWith("nostr:")) {
23132
+ npubString = npubString.slice(6);
23133
+ }
23134
+ if (npubString.startsWith("npub1")) {
23135
+ try {
23136
+ const decoded = nip19.decode(npubString);
23137
+ if (decoded.type === "npub") {
23138
+ return { valid: true, hexPubKey: decoded.data };
23139
+ }
23140
+ return { valid: false, error: "Invalid npub format" };
23141
+ } catch (e) {
23142
+ return { valid: false, error: "Invalid npub: unable to decode" };
23143
+ }
23144
+ }
23145
+ return { valid: false, error: "Enter a valid npub (npub1...) or 64-character hex public key" };
23146
+ }
23147
+ function hexToNpub(hexPubKey) {
23148
+ try {
23149
+ return nip19.npubEncode(hexPubKey);
23150
+ } catch (e) {
23151
+ console.error("Failed to encode hex to npub:", e);
23152
+ return hexPubKey;
23153
+ }
23154
+ }
23155
+ function npubToHex(npub2) {
23156
+ try {
23157
+ const decoded = nip19.decode(npub2);
23158
+ if (decoded.type === "npub") {
23159
+ return decoded.data;
23160
+ }
23161
+ return null;
23162
+ } catch (e) {
23163
+ return null;
23164
+ }
23165
+ }
23166
+ function shortenPubKey(pubKey) {
23167
+ if (!pubKey || pubKey.length <= 20) return pubKey || "";
23168
+ return `${pubKey.slice(0, 8)}...${pubKey.slice(-8)}`;
23169
+ }
23170
+ function shortenNpub(npub2) {
23171
+ if (!npub2 || npub2.length <= 20) return npub2 || "";
23172
+ return `${npub2.slice(0, 12)}...${npub2.slice(-8)}`;
23173
+ }
23174
+ function getPublicKey$1(privateKey) {
23175
+ return getPublicKey$2(hexToBytes(privateKey));
23176
+ }
23177
+ async function encryptNIP04(privateKey, recipientPubKey, content) {
23178
+ return await nip04.encrypt(privateKey, recipientPubKey, content);
23179
+ }
23180
+ async function decryptNIP04(privateKey, senderPubKey, encryptedContent) {
23181
+ return await nip04.decrypt(privateKey, senderPubKey, encryptedContent);
23182
+ }
23183
+ function encryptNIP44(privateKey, recipientPubKey, content) {
23184
+ const privKeyBytes = hexToBytes(privateKey);
23185
+ const conversationKey = nip44.v2.utils.getConversationKey(privKeyBytes, recipientPubKey);
23186
+ return nip44.v2.encrypt(content, conversationKey);
23187
+ }
23188
+ function decryptNIP44(privateKey, senderPubKey, encryptedContent) {
23189
+ const privKeyBytes = hexToBytes(privateKey);
23190
+ const conversationKey = nip44.v2.utils.getConversationKey(privKeyBytes, senderPubKey);
23191
+ return nip44.v2.decrypt(encryptedContent, conversationKey);
23192
+ }
23193
+ function createSignedEvent(kind2, content, tags, privateKey) {
23194
+ const event = {
23195
+ kind: kind2,
23196
+ content,
23197
+ tags,
23198
+ created_at: Math.floor(Date.now() / 1e3)
23199
+ };
23200
+ return finalizeEvent(event, hexToBytes(privateKey));
23201
+ }
23202
+ function createDMEvent(recipientPubKey, encryptedContent, privateKey) {
23203
+ return createSignedEvent(
23204
+ 4,
23205
+ // NIP-04 encrypted DM
23206
+ encryptedContent,
23207
+ [["p", recipientPubKey]],
23208
+ privateKey
23209
+ );
23210
+ }
23211
+ function isValidHexPubKey(str2) {
23212
+ return typeof str2 === "string" && /^[0-9a-fA-F]{64}$/.test(str2);
23213
+ }
23214
+ function isValidNpub(str2) {
23215
+ if (typeof str2 !== "string" || !str2.startsWith("npub1")) {
23216
+ return false;
23217
+ }
23218
+ try {
23219
+ const decoded = nip19.decode(str2);
23220
+ return decoded.type === "npub";
23221
+ } catch {
23222
+ return false;
23223
+ }
23224
+ }
23225
+ function generateNonce$1() {
23226
+ return Date.now().toString(36) + Math.random().toString(36).substring(2, 15);
23227
+ }
23228
+ function signEvent(event, privateKey) {
23229
+ const eventToSign = {
23230
+ kind: event.kind,
23231
+ content: event.content,
23232
+ tags: event.tags || [],
23233
+ created_at: event.created_at || Math.floor(Date.now() / 1e3)
23234
+ };
23235
+ return finalizeEvent(eventToSign, hexToBytes(privateKey));
23236
+ }
23237
+ function verifyEvent(event) {
23238
+ return verifyEvent$1(event);
23239
+ }
23240
+ const nostrUtils = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
23241
+ __proto__: null,
23242
+ NDK,
23243
+ NDKEvent,
23244
+ NDKPrivateKeySigner,
23245
+ NDKSubscriptionCacheUsage,
23246
+ NDKUser,
23247
+ bytesToHex,
23248
+ createDMEvent,
23249
+ createSignedEvent,
23250
+ decryptNIP04,
23251
+ decryptNIP44,
23252
+ encryptNIP04,
23253
+ encryptNIP44,
23254
+ generateNonce: generateNonce$1,
23255
+ getPublicKey: getPublicKey$1,
23256
+ hexToBytes,
23257
+ hexToNpub,
23258
+ isValidHexPubKey,
23259
+ isValidNpub,
23260
+ npubToHex,
23261
+ parseNpubOrHex,
23262
+ shortenNpub,
23263
+ shortenPubKey,
23264
+ signEvent,
23265
+ verifyEvent
23266
+ }, Symbol.toStringTag, { value: "Module" }));
23267
+ function generateLensKey() {
23268
+ return randomBytes$1(32);
23269
+ }
23270
+ function uint8ToBase64(bytes2) {
23271
+ if (typeof btoa === "function") {
23272
+ return btoa(String.fromCharCode(...bytes2));
23273
+ }
23274
+ return Buffer.from(bytes2).toString("base64");
23275
+ }
23276
+ function base64ToUint8(base64) {
23277
+ if (typeof atob === "function") {
23278
+ const binary = atob(base64);
23279
+ const bytes2 = new Uint8Array(binary.length);
23280
+ for (let i = 0; i < binary.length; i++) {
23281
+ bytes2[i] = binary.charCodeAt(i);
23282
+ }
23283
+ return bytes2;
23284
+ }
23285
+ return new Uint8Array(Buffer.from(base64, "base64"));
23286
+ }
23287
+ function encryptWithKey(key, plaintext) {
23288
+ const iv = randomBytes$1(12);
23289
+ const aes = gcm(key, iv);
23290
+ const plaintextBytes = new TextEncoder().encode(plaintext);
23291
+ const ciphertext = aes.encrypt(plaintextBytes);
23292
+ const combined = new Uint8Array(iv.length + ciphertext.length);
23293
+ combined.set(iv, 0);
23294
+ combined.set(ciphertext, iv.length);
23295
+ return uint8ToBase64(combined);
23296
+ }
23297
+ function decryptWithKey(key, ciphertext) {
23298
+ const data = base64ToUint8(ciphertext);
23299
+ const iv = data.slice(0, 12);
23300
+ const encrypted = data.slice(12);
23301
+ const aes = gcm(key, iv);
23302
+ const decrypted = aes.decrypt(encrypted);
23303
+ return new TextDecoder().decode(decrypted);
23304
+ }
23305
+ function wrapKeyForRecipient(lensKey, senderPrivKey, recipientPubKey) {
23306
+ const keyBase64 = uint8ToBase64(lensKey);
23307
+ return encryptNIP44(senderPrivKey, recipientPubKey, keyBase64);
23308
+ }
23309
+ function unwrapKey(wrappedKey, recipientPrivKey, senderPubKey) {
23310
+ const keyBase64 = decryptNIP44(recipientPrivKey, senderPubKey, wrappedKey);
23311
+ return base64ToUint8(keyBase64);
23312
+ }
23313
+ function serializeLensKey(lensKey, ownerPrivKey, ownerPubKey) {
23314
+ return {
23315
+ wrappedKey: wrapKeyForRecipient(lensKey, ownerPrivKey, ownerPubKey),
23316
+ algorithm: "aes-256-gcm",
23317
+ keyWrap: "nip44",
23318
+ version: "1.0"
23319
+ };
23320
+ }
23321
+ function deserializeLensKey(keyData, ownerPrivKey, ownerPubKey) {
23322
+ if (keyData.version !== "1.0") {
23323
+ throw new Error(`Unsupported lens key version: ${keyData.version}`);
23324
+ }
23325
+ return unwrapKey(keyData.wrappedKey, ownerPrivKey, ownerPubKey);
23326
+ }
23327
+ function isEncrypted(content) {
23328
+ if (!content || typeof content !== "string") return false;
23329
+ const trimmed = content.trim();
23330
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
23331
+ return false;
23332
+ }
23333
+ if (content.length < 38) return false;
23334
+ try {
23335
+ const decoded = base64ToUint8(content);
23336
+ return decoded.length >= 29;
23337
+ } catch {
23338
+ return false;
23339
+ }
23340
+ }
23341
+ const lensKeys = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
23342
+ __proto__: null,
23343
+ decryptWithKey,
23344
+ deserializeLensKey,
23345
+ encryptWithKey,
23346
+ generateLensKey,
23347
+ isEncrypted,
23348
+ serializeLensKey,
23349
+ unwrapKey,
23350
+ wrapKeyForRecipient
23351
+ }, Symbol.toStringTag, { value: "Module" }));
22583
23352
  const globalSubscriptions = /* @__PURE__ */ new Map();
22584
23353
  const singlePathSubscriptions = /* @__PURE__ */ new Map();
22585
23354
  const pendingQueries = /* @__PURE__ */ new Map();
22586
23355
  const QUERY_DEDUP_WINDOW = 2e3;
23356
+ function _parseEventContent(event, lensKey = null) {
23357
+ if (!event || !event.content) return null;
23358
+ try {
23359
+ const encryptedTag = event.tags?.find((t) => t[0] === "encrypted");
23360
+ const isSymmetricEncrypted = encryptedTag && encryptedTag[1] === "symmetric";
23361
+ if (isSymmetricEncrypted) {
23362
+ if (!lensKey) {
23363
+ return null;
23364
+ }
23365
+ const decrypted = decryptWithKey(lensKey, event.content);
23366
+ return JSON.parse(decrypted);
23367
+ } else {
23368
+ return JSON.parse(event.content);
23369
+ }
23370
+ } catch (error) {
23371
+ return null;
23372
+ }
23373
+ }
22587
23374
  async function nostrPut(client, path, data, kindOrOptions = 3e4) {
22588
23375
  const options = typeof kindOrOptions === "number" ? { kind: kindOrOptions } : kindOrOptions;
22589
23376
  const kind2 = options.kind || 3e4;
23377
+ const { lensKey } = options;
23378
+ let content;
23379
+ const tags = [["d", path]];
23380
+ if (lensKey) {
23381
+ content = encryptWithKey(lensKey, JSON.stringify(data));
23382
+ tags.push(["encrypted", "symmetric"]);
23383
+ } else {
23384
+ content = JSON.stringify(data);
23385
+ }
22590
23386
  const dataEvent = {
22591
23387
  kind: kind2,
22592
23388
  created_at: Math.floor(Date.now() / 1e3),
22593
- tags: [
22594
- ["d", path]
22595
- // d-tag for parameterized replaceable events
22596
- ],
22597
- content: JSON.stringify(data)
23389
+ tags,
23390
+ content
23391
+ };
23392
+ const publishOptions = {
23393
+ ...options.signingKey && { signingKey: options.signingKey },
23394
+ ...options.waitForRelays && { waitForRelays: options.waitForRelays }
22598
23395
  };
22599
- const publishOptions = options.signingKey ? { signingKey: options.signingKey } : {};
22600
23396
  const result = await client.publish(dataEvent, publishOptions);
22601
23397
  return result;
22602
23398
  }
22603
23399
  async function nostrGet(client, path, kind2 = 3e4, options = {}) {
22604
23400
  const timeout = options.timeout !== void 0 ? options.timeout : 3e4;
22605
23401
  const authors = options.authors || [client.publicKey];
23402
+ const { lensKey } = options;
22606
23403
  if (!options.skipCache && client.getCachedByPath) {
22607
23404
  const cachedEvent = client.getCachedByPath(path, kind2);
22608
23405
  if (cachedEvent && cachedEvent.content) {
22609
23406
  if (authors.includes(cachedEvent.pubkey)) {
22610
- try {
22611
- const data = JSON.parse(cachedEvent.content);
22612
- if (!data || data._deleted) {
22613
- return null;
22614
- }
22615
- if (options.includeAuthor) {
22616
- data._author = cachedEvent.pubkey;
22617
- }
22618
- return data;
22619
- } catch (error) {
23407
+ const data = _parseEventContent(cachedEvent, lensKey);
23408
+ if (!data || data._deleted) {
23409
+ return null;
23410
+ }
23411
+ if (options.includeAuthor) {
23412
+ data._author = cachedEvent.pubkey;
22620
23413
  }
23414
+ return data;
22621
23415
  }
22622
23416
  }
22623
23417
  }
@@ -22626,21 +23420,20 @@ async function nostrGet(client, path, kind2 = 3e4, options = {}) {
22626
23420
  if (persistedEvent && persistedEvent.content) {
22627
23421
  if (!authors.includes(persistedEvent.pubkey)) ;
22628
23422
  else {
22629
- try {
22630
- const data = JSON.parse(persistedEvent.content);
22631
- if (!data || data._deleted) {
23423
+ const data = _parseEventContent(persistedEvent, lensKey);
23424
+ if (!data || data._deleted) {
23425
+ if (persistedEvent.tags?.find((t) => t[0] === "encrypted") && !lensKey) {
22632
23426
  return null;
22633
23427
  }
22634
- if (options.includeAuthor) {
22635
- data._author = persistedEvent.pubkey;
22636
- }
22637
- if (client.refreshPathInBackground) {
22638
- client.refreshPathInBackground(path, kind2, { authors, timeout });
22639
- }
22640
- return data;
22641
- } catch (error) {
22642
- console.warn("[nostrGet] Failed to parse persisted event:", error);
23428
+ return null;
23429
+ }
23430
+ if (options.includeAuthor) {
23431
+ data._author = persistedEvent.pubkey;
22643
23432
  }
23433
+ if (client.refreshPathInBackground) {
23434
+ client.refreshPathInBackground(path, kind2, { authors, timeout });
23435
+ }
23436
+ return data;
22644
23437
  }
22645
23438
  }
22646
23439
  }
@@ -22665,6 +23458,7 @@ async function nostrGet(client, path, kind2 = 3e4, options = {}) {
22665
23458
  return queryPromise;
22666
23459
  }
22667
23460
  async function _executeNostrGet(client, path, kind2, authors, timeout, options) {
23461
+ const { lensKey } = options;
22668
23462
  const filter = {
22669
23463
  kinds: [kind2],
22670
23464
  authors,
@@ -22673,29 +23467,26 @@ async function _executeNostrGet(client, path, kind2, authors, timeout, options)
22673
23467
  limit: authors.length
22674
23468
  // Increase limit to get events from all authors
22675
23469
  };
22676
- const events = await client.query(filter, { timeout });
23470
+ const events = await client.query(filter, { timeout, forceRelay: options.forceRelay });
22677
23471
  const authoredEvents = events.filter((event2) => authors.includes(event2.pubkey));
22678
23472
  if (authoredEvents.length === 0) {
22679
23473
  return null;
22680
23474
  }
22681
23475
  const event = authoredEvents.sort((a, b2) => b2.created_at - a.created_at)[0];
22682
- try {
22683
- const data = JSON.parse(event.content);
22684
- if (!data || data._deleted) {
22685
- return null;
22686
- }
22687
- if (options.includeAuthor) {
22688
- data._author = event.pubkey;
22689
- }
22690
- return data;
22691
- } catch (error) {
23476
+ const data = _parseEventContent(event, lensKey);
23477
+ if (!data || data._deleted) {
22692
23478
  return null;
22693
23479
  }
23480
+ if (options.includeAuthor) {
23481
+ data._author = event.pubkey;
23482
+ }
23483
+ return data;
22694
23484
  }
22695
23485
  async function nostrGetAll(client, pathPrefix, kind2 = 3e4, options = {}) {
22696
23486
  const timeout = options.timeout !== void 0 ? options.timeout : 3e4;
22697
23487
  const limit2 = options.limit || 1e3;
22698
23488
  const authors = options.authors || [client.publicKey];
23489
+ const { lensKey } = options;
22699
23490
  if (!options.skipPersistent && client.persistentGetAll) {
22700
23491
  const persistedEvents = await client.persistentGetAll(pathPrefix);
22701
23492
  if (persistedEvents.length > 0) {
@@ -22708,18 +23499,15 @@ async function nostrGetAll(client, pathPrefix, kind2 = 3e4, options = {}) {
22708
23499
  const path = dTag[1];
22709
23500
  const existing = byPath.get(path);
22710
23501
  if (!existing || event.created_at > existing.created_at) {
22711
- try {
22712
- const data = JSON.parse(event.content);
22713
- if (!data || data._deleted) {
22714
- byPath.delete(path);
22715
- continue;
22716
- }
22717
- if (options.includeAuthor) {
22718
- data._author = event.pubkey;
22719
- }
22720
- byPath.set(path, { data, created_at: event.created_at });
22721
- } catch (error) {
23502
+ const data = _parseEventContent(event, lensKey);
23503
+ if (!data || data._deleted) {
23504
+ byPath.delete(path);
23505
+ continue;
22722
23506
  }
23507
+ if (options.includeAuthor) {
23508
+ data._author = event.pubkey;
23509
+ }
23510
+ byPath.set(path, { data, created_at: event.created_at });
22723
23511
  }
22724
23512
  }
22725
23513
  if (client.refreshPrefixInBackground) {
@@ -22749,6 +23537,7 @@ async function nostrGetAll(client, pathPrefix, kind2 = 3e4, options = {}) {
22749
23537
  return queryPromise;
22750
23538
  }
22751
23539
  async function _executeNostrGetAll(client, pathPrefix, kind2, authors, timeout, limit2, options) {
23540
+ const { lensKey } = options;
22752
23541
  const filter = {
22753
23542
  kinds: [kind2],
22754
23543
  authors,
@@ -22766,18 +23555,15 @@ async function _executeNostrGetAll(client, pathPrefix, kind2, authors, timeout,
22766
23555
  const dTag = event.tags.find((t) => t[0] === "d")[1];
22767
23556
  const existing = byPath.get(dTag);
22768
23557
  if (!existing || event.created_at > existing.created_at) {
22769
- try {
22770
- const data = JSON.parse(event.content);
22771
- if (!data || data._deleted) {
22772
- byPath.delete(dTag);
22773
- continue;
22774
- }
22775
- if (options.includeAuthor) {
22776
- data._author = event.pubkey;
22777
- }
22778
- byPath.set(dTag, { data, created_at: event.created_at });
22779
- } catch (error) {
23558
+ const data = _parseEventContent(event, lensKey);
23559
+ if (!data || data._deleted) {
23560
+ byPath.delete(dTag);
23561
+ continue;
22780
23562
  }
23563
+ if (options.includeAuthor) {
23564
+ data._author = event.pubkey;
23565
+ }
23566
+ byPath.set(dTag, { data, created_at: event.created_at });
22781
23567
  }
22782
23568
  }
22783
23569
  return Array.from(byPath.values()).map((item) => item.data);
@@ -23014,7 +23800,9 @@ async function nostrSubscribe(client, path, callback, options = {}) {
23014
23800
  }
23015
23801
  async function nostrSubscribeMany(client, pathPrefix, callback, options = {}) {
23016
23802
  const kind2 = options.kind || 3e4;
23017
- const subscriptionKey = `${client.publicKey}:${kind2}:${pathPrefix}`;
23803
+ const pathParts = pathPrefix.split("/");
23804
+ const targetAuthor = pathParts[1] || client.publicKey;
23805
+ const subscriptionKey = `${targetAuthor}:${kind2}:${pathPrefix}`;
23018
23806
  const existingSub = globalSubscriptions.get(subscriptionKey);
23019
23807
  if (existingSub) {
23020
23808
  existingSub.callbacks.push(callback);
@@ -23042,14 +23830,14 @@ async function nostrSubscribeMany(client, pathPrefix, callback, options = {}) {
23042
23830
  return;
23043
23831
  }
23044
23832
  seenEventIds.add(event.id);
23045
- if (event.pubkey !== client.publicKey) {
23833
+ if (event.pubkey !== targetAuthor) {
23046
23834
  rejectedCount++;
23047
23835
  const now = Date.now();
23048
23836
  if (now - lastLogTime > LOG_INTERVAL) {
23049
23837
  console.warn("[nostrSubscribeMany] ⚠️ Relay not respecting authors filter!", {
23050
23838
  rejectedCount,
23051
23839
  acceptedCount,
23052
- expected: client.publicKey,
23840
+ expected: targetAuthor,
23053
23841
  message: "Consider using a different relay or implementing private relay"
23054
23842
  });
23055
23843
  lastLogTime = now;
@@ -23076,10 +23864,12 @@ async function nostrSubscribeMany(client, pathPrefix, callback, options = {}) {
23076
23864
  const subscriptionStartTime = Math.floor(Date.now() / 1e3);
23077
23865
  const dataFilter = {
23078
23866
  kinds: [kind2],
23079
- authors: [client.publicKey],
23867
+ authors: [targetAuthor],
23868
+ // Use target author (holon ID), not client pubkey
23080
23869
  since: subscriptionStartTime
23081
23870
  // Only get new events after subscription
23082
23871
  };
23872
+ console.log("[nostrSubscribeMany] Subscribing to:", { pathPrefix, targetAuthor: targetAuthor.slice(0, 12), clientPubKey: client.publicKey.slice(0, 12) });
23083
23873
  const dataSubscription = await client.subscribe(dataFilter, handleDataEvent);
23084
23874
  let isDocumentHidden = typeof document !== "undefined" && document.hidden;
23085
23875
  let lastVisibilityCheck = subscriptionStartTime;
@@ -23140,7 +23930,10 @@ function encodePathComponent(component) {
23140
23930
  }
23141
23931
  async function write$1(client, path, data, options = {}) {
23142
23932
  try {
23143
- const putOptions = options.signingKey ? { signingKey: options.signingKey } : {};
23933
+ const putOptions = {
23934
+ ...options.signingKey && { signingKey: options.signingKey },
23935
+ ...options.waitForRelays && { waitForRelays: options.waitForRelays }
23936
+ };
23144
23937
  const result = await nostrPut(client, path, data, putOptions);
23145
23938
  const success = result.results.length === 0 ? true : result.results.some((r) => r.status === "fulfilled");
23146
23939
  return success;
@@ -23222,14 +24015,14 @@ async function subscribe$1(client, path, callback, options = {}) {
23222
24015
  function buildPath(appName, holonId, lensName, key = null) {
23223
24016
  return buildPath$1(appName, holonId, lensName, key);
23224
24017
  }
23225
- async function write(client, path, data) {
24018
+ async function write(client, path, data, options = {}) {
23226
24019
  if (client.write && client.gun) {
23227
- return client.write(path, data);
24020
+ return client.write(path, data, options);
23228
24021
  }
23229
24022
  if (client.gun) {
23230
24023
  return write$2(client.gun, path, data);
23231
24024
  }
23232
- return write$1(client, path, data);
24025
+ return write$1(client, path, data, options);
23233
24026
  }
23234
24027
  async function read(client, path, options = {}) {
23235
24028
  if (client.read && client.gun) {
@@ -23432,77 +24225,6 @@ const assert$1 = {
23432
24225
  exists: exists$1,
23433
24226
  output: output$1
23434
24227
  };
23435
- /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
23436
- const u8a$1 = (a) => a instanceof Uint8Array;
23437
- const createView$1 = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
23438
- const rotr$1 = (word, shift) => word << 32 - shift | word >>> shift;
23439
- const isLE$1 = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68;
23440
- if (!isLE$1)
23441
- throw new Error("Non little-endian hardware is not supported");
23442
- const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, "0"));
23443
- function bytesToHex$1(bytes2) {
23444
- if (!u8a$1(bytes2))
23445
- throw new Error("Uint8Array expected");
23446
- let hex2 = "";
23447
- for (let i = 0; i < bytes2.length; i++) {
23448
- hex2 += hexes[bytes2[i]];
23449
- }
23450
- return hex2;
23451
- }
23452
- function hexToBytes$1(hex2) {
23453
- if (typeof hex2 !== "string")
23454
- throw new Error("hex string expected, got " + typeof hex2);
23455
- const len = hex2.length;
23456
- if (len % 2)
23457
- throw new Error("padded hex string expected, got unpadded hex of length " + len);
23458
- const array = new Uint8Array(len / 2);
23459
- for (let i = 0; i < array.length; i++) {
23460
- const j = i * 2;
23461
- const hexByte = hex2.slice(j, j + 2);
23462
- const byte = Number.parseInt(hexByte, 16);
23463
- if (Number.isNaN(byte) || byte < 0)
23464
- throw new Error("Invalid byte sequence");
23465
- array[i] = byte;
23466
- }
23467
- return array;
23468
- }
23469
- function utf8ToBytes$1(str2) {
23470
- if (typeof str2 !== "string")
23471
- throw new Error(`utf8ToBytes expected string, got ${typeof str2}`);
23472
- return new Uint8Array(new TextEncoder().encode(str2));
23473
- }
23474
- function toBytes$1(data) {
23475
- if (typeof data === "string")
23476
- data = utf8ToBytes$1(data);
23477
- if (!u8a$1(data))
23478
- throw new Error(`expected Uint8Array, got ${typeof data}`);
23479
- return data;
23480
- }
23481
- function concatBytes$1(...arrays) {
23482
- const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
23483
- let pad = 0;
23484
- arrays.forEach((a) => {
23485
- if (!u8a$1(a))
23486
- throw new Error("Uint8Array expected");
23487
- r.set(a, pad);
23488
- pad += a.length;
23489
- });
23490
- return r;
23491
- }
23492
- let Hash$1 = class Hash5 {
23493
- // Safe version that clones internal state
23494
- clone() {
23495
- return this._cloneInto();
23496
- }
23497
- };
23498
- function wrapConstructor$1(hashCons) {
23499
- const hashC = (msg) => hashCons().update(toBytes$1(msg)).digest();
23500
- const tmp = hashCons();
23501
- hashC.outputLen = tmp.outputLen;
23502
- hashC.blockLen = tmp.blockLen;
23503
- hashC.create = () => hashCons();
23504
- return hashC;
23505
- }
23506
24228
  function setBigUint64(view, byteOffset, value, isLE2) {
23507
24229
  if (typeof view.setBigUint64 === "function")
23508
24230
  return view.setBigUint64(byteOffset, value, isLE2);
@@ -23766,15 +24488,15 @@ class SHA224 extends SHA256 {
23766
24488
  }
23767
24489
  const sha256 = wrapConstructor$1(() => new SHA256());
23768
24490
  wrapConstructor$1(() => new SHA224());
23769
- function getPublicKey$1(privateKey) {
23770
- const pubKey = secp256k1$1.getPublicKey(privateKey);
24491
+ function getPublicKey(privateKey) {
24492
+ const pubKey = schnorr.getPublicKey(privateKey);
23771
24493
  return bytesToHex$1(pubKey);
23772
24494
  }
23773
24495
  async function sign(content, privateKey) {
23774
24496
  try {
23775
24497
  const msgHash = hashContent(content);
23776
- const signature = secp256k1$1.sign(msgHash, privateKey);
23777
- return signature.toCompactHex();
24498
+ const signature = schnorr.sign(msgHash, privateKey);
24499
+ return bytesToHex$1(signature);
23778
24500
  } catch (error) {
23779
24501
  throw new Error(`Signature generation failed: ${error.message}`);
23780
24502
  }
@@ -23785,7 +24507,7 @@ async function verify(content, signature, publicKey) {
23785
24507
  throw new Error("Invalid public key format");
23786
24508
  }
23787
24509
  const msgHash = hashContent(content);
23788
- const isValid = secp256k1$1.verify(signature, msgHash, publicKey);
24510
+ const isValid = schnorr.verify(signature, msgHash, publicKey);
23789
24511
  return isValid;
23790
24512
  } catch (error) {
23791
24513
  if (error.message === "Invalid public key format") {
@@ -23840,12 +24562,12 @@ async function issueCapability$1(permissions, scope, recipient, options = {}) {
23840
24562
  issuer,
23841
24563
  isSelfCapability: selfCap,
23842
24564
  // Mark self-capabilities for clarity
23843
- nonce: generateNonce$1(),
24565
+ nonce: generateNonce(),
23844
24566
  issued: Date.now(),
23845
24567
  expires: Date.now() + expiresIn
23846
24568
  };
23847
24569
  const payload = JSON.stringify(token);
23848
- const encoded = Buffer.from ? Buffer.from(payload).toString("base64") : btoa(payload);
24570
+ const encoded = typeof btoa === "function" ? btoa(payload) : Buffer.from(payload).toString("base64");
23849
24571
  if (issuerKey) {
23850
24572
  const signature = await sign(payload, issuerKey);
23851
24573
  return `${encoded}.${signature}`;
@@ -23867,11 +24589,28 @@ async function verifyCapability$1(token, requiredPermission, scope) {
23867
24589
  if (token && typeof token === "object" && token.token) {
23868
24590
  token = token.token;
23869
24591
  }
24592
+ if (token && typeof token === "object" && token.type === "Buffer" && Array.isArray(token.data)) {
24593
+ try {
24594
+ token = String.fromCharCode.apply(null, token.data);
24595
+ } catch (e) {
24596
+ console.log("[verifyCapability] ❌ Failed to decode Buffer object:", e.message);
24597
+ return false;
24598
+ }
24599
+ }
24600
+ if (typeof token === "string" && /^\d+(,\d+)+$/.test(token.substring(0, 50))) {
24601
+ try {
24602
+ const bytes2 = token.split(",").map(Number);
24603
+ token = String.fromCharCode.apply(null, bytes2);
24604
+ } catch (e) {
24605
+ console.log("[verifyCapability] ❌ Failed to decode byte string:", e.message);
24606
+ return false;
24607
+ }
24608
+ }
23870
24609
  if (typeof token === "string") {
23871
24610
  if (token.startsWith("ey") || !token.includes(".") && !token.startsWith("{")) {
23872
24611
  try {
23873
24612
  const payload = token.includes(".") ? token.split(".")[0] : token;
23874
- const decoded = Buffer.from ? Buffer.from(payload, "base64").toString("utf8") : atob(payload);
24613
+ const decoded = typeof atob === "function" ? atob(payload) : Buffer.from(payload, "base64").toString("utf8");
23875
24614
  tokenObj = JSON.parse(decoded);
23876
24615
  } catch (e) {
23877
24616
  console.log("[verifyCapability] ❌ Token is not valid base64 JSON:", {
@@ -23933,7 +24672,86 @@ async function verifyCapability$1(token, requiredPermission, scope) {
23933
24672
  return false;
23934
24673
  }
23935
24674
  }
23936
- function generateNonce$1() {
24675
+ async function verifyCapabilityIssuer(token, expectedIssuer) {
24676
+ try {
24677
+ if (token && typeof token === "object" && token.token) {
24678
+ token = token.token;
24679
+ }
24680
+ if (token && typeof token === "object" && token.type === "Buffer" && Array.isArray(token.data)) {
24681
+ token = String.fromCharCode.apply(null, token.data);
24682
+ }
24683
+ if (typeof token !== "string") {
24684
+ console.log("[verifyCapabilityIssuer] ❌ Token is not a string");
24685
+ return false;
24686
+ }
24687
+ let payload;
24688
+ let signature;
24689
+ let tokenObj;
24690
+ if (token.includes(".")) {
24691
+ const parts = token.split(".");
24692
+ const encodedPayload = parts[0];
24693
+ signature = parts[1];
24694
+ const decoded = typeof atob === "function" ? atob(encodedPayload) : Buffer.from(encodedPayload, "base64").toString("utf8");
24695
+ payload = decoded;
24696
+ tokenObj = JSON.parse(decoded);
24697
+ } else if (token.startsWith("ey")) {
24698
+ const decoded = typeof atob === "function" ? atob(token) : Buffer.from(token, "base64").toString("utf8");
24699
+ payload = decoded;
24700
+ tokenObj = JSON.parse(decoded);
24701
+ } else if (token.startsWith("{")) {
24702
+ payload = token;
24703
+ tokenObj = JSON.parse(token);
24704
+ } else {
24705
+ console.log("[verifyCapabilityIssuer] ❌ Unknown token format");
24706
+ return false;
24707
+ }
24708
+ if (tokenObj.issuer !== expectedIssuer) {
24709
+ console.log("[verifyCapabilityIssuer] ❌ Issuer mismatch:", {
24710
+ tokenIssuer: tokenObj.issuer?.slice(0, 12) + "...",
24711
+ expectedIssuer: expectedIssuer?.slice(0, 12) + "..."
24712
+ });
24713
+ return false;
24714
+ }
24715
+ if (signature) {
24716
+ const signatureValid = await verify(payload, signature, expectedIssuer);
24717
+ if (!signatureValid) {
24718
+ console.log("[verifyCapabilityIssuer] ❌ Signature verification failed - capability not signed by claimed issuer");
24719
+ return false;
24720
+ }
24721
+ } else {
24722
+ console.log("[verifyCapabilityIssuer] ⚠️ Token has no signature - cannot verify cryptographically");
24723
+ }
24724
+ console.log("[verifyCapabilityIssuer] ✅ Issuer verified");
24725
+ return true;
24726
+ } catch (error) {
24727
+ console.log("[verifyCapabilityIssuer] ❌ Error:", error.message);
24728
+ return false;
24729
+ }
24730
+ }
24731
+ function decodeCapability(token) {
24732
+ try {
24733
+ if (token && typeof token === "object" && token.token) {
24734
+ token = token.token;
24735
+ }
24736
+ if (token && typeof token === "object" && token.type === "Buffer" && Array.isArray(token.data)) {
24737
+ token = String.fromCharCode.apply(null, token.data);
24738
+ }
24739
+ if (typeof token !== "string") {
24740
+ return null;
24741
+ }
24742
+ if (token.startsWith("ey") || !token.includes(".") && !token.startsWith("{")) {
24743
+ const payload = token.includes(".") ? token.split(".")[0] : token;
24744
+ const decoded = typeof atob === "function" ? atob(payload) : Buffer.from(payload, "base64").toString("utf8");
24745
+ return JSON.parse(decoded);
24746
+ } else if (token.startsWith("{")) {
24747
+ return JSON.parse(token);
24748
+ }
24749
+ return null;
24750
+ } catch (error) {
24751
+ return null;
24752
+ }
24753
+ }
24754
+ function generateNonce() {
23937
24755
  return Date.now().toString(36) + Math.random().toString(36).substring(2, 15);
23938
24756
  }
23939
24757
  function hashContent(content) {
@@ -23945,18 +24763,45 @@ function hashContent(content) {
23945
24763
  }
23946
24764
  const secp256k1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
23947
24765
  __proto__: null,
23948
- getPublicKey: getPublicKey$1,
24766
+ decodeCapability,
24767
+ getPublicKey,
23949
24768
  issueCapability: issueCapability$1,
23950
24769
  issueSelfCapability,
23951
24770
  matchScope,
23952
24771
  sign,
23953
24772
  verify,
23954
- verifyCapability: verifyCapability$1
24773
+ verifyCapability: verifyCapability$1,
24774
+ verifyCapabilityIssuer
23955
24775
  }, Symbol.toStringTag, { value: "Module" }));
23956
24776
  const FEDERATION_TABLE = "federations";
23957
- async function getFederationRegistry(client, appname) {
23958
- const registry2 = await readGlobal(client, appname, FEDERATION_TABLE, client.publicKey);
23959
- return registry2 || {
24777
+ const registryCache = /* @__PURE__ */ new Map();
24778
+ const CACHE_TTL_MS = 1e4;
24779
+ function buildPartnerIndex(federatedWith) {
24780
+ const index = /* @__PURE__ */ new Map();
24781
+ for (const partner of federatedWith) {
24782
+ if (partner.pubKey) {
24783
+ index.set(partner.pubKey, partner);
24784
+ }
24785
+ }
24786
+ return index;
24787
+ }
24788
+ function getCacheKey(client, appname) {
24789
+ return `${client.publicKey}:${appname}`;
24790
+ }
24791
+ function invalidateRegistryCache(client, appname) {
24792
+ const key = getCacheKey(client, appname);
24793
+ registryCache.delete(key);
24794
+ }
24795
+ async function getFederationRegistry(client, appname, options = {}) {
24796
+ const cacheKey = getCacheKey(client, appname);
24797
+ const now = Date.now();
24798
+ if (!options.skipCache) {
24799
+ const cached = registryCache.get(cacheKey);
24800
+ if (cached && now - cached.timestamp < CACHE_TTL_MS) {
24801
+ return cached.registry;
24802
+ }
24803
+ }
24804
+ const registry2 = await readGlobal(client, appname, FEDERATION_TABLE, client.publicKey) || {
23960
24805
  id: client.publicKey,
23961
24806
  federatedWith: [],
23962
24807
  discoveryEnabled: false,
@@ -23964,8 +24809,25 @@ async function getFederationRegistry(client, appname) {
23964
24809
  defaultScope: { holonId: "*", lensName: "*" },
23965
24810
  defaultPermissions: ["read"]
23966
24811
  };
24812
+ const partnerIndex = buildPartnerIndex(registry2.federatedWith || []);
24813
+ registryCache.set(cacheKey, {
24814
+ registry: registry2,
24815
+ partnerIndex,
24816
+ timestamp: now
24817
+ });
24818
+ return registry2;
24819
+ }
24820
+ async function getPartnerFromIndex(client, appname, partnerPubKey) {
24821
+ const cacheKey = getCacheKey(client, appname);
24822
+ await getFederationRegistry(client, appname);
24823
+ const cached = registryCache.get(cacheKey);
24824
+ if (cached && cached.partnerIndex) {
24825
+ return cached.partnerIndex.get(partnerPubKey) || null;
24826
+ }
24827
+ return null;
23967
24828
  }
23968
24829
  async function saveFederationRegistry(client, appname, registry2) {
24830
+ invalidateRegistryCache(client, appname);
23969
24831
  return writeGlobal(client, appname, FEDERATION_TABLE, registry2);
23970
24832
  }
23971
24833
  async function addFederatedPartner(client, appname, partnerPubKey, options = {}) {
@@ -23977,10 +24839,16 @@ async function addFederatedPartner(client, appname, partnerPubKey, options = {})
23977
24839
  existing.alias = options.alias;
23978
24840
  }
23979
24841
  if (options.inboundCapabilities) {
23980
- existing.inboundCapabilities = [
24842
+ const allCaps = [
23981
24843
  ...existing.inboundCapabilities || [],
23982
24844
  ...options.inboundCapabilities
23983
24845
  ];
24846
+ const seen = /* @__PURE__ */ new Map();
24847
+ for (const cap of allCaps) {
24848
+ const scopeKey = `${cap.scope?.holonId}:${cap.scope?.lensName}:${cap.scope?.dataId}`;
24849
+ seen.set(scopeKey, cap);
24850
+ }
24851
+ existing.inboundCapabilities = Array.from(seen.values());
23984
24852
  }
23985
24853
  existing.updatedAt = Date.now();
23986
24854
  registry2.federatedWith[existingIndex] = existing;
@@ -24010,65 +24878,31 @@ async function getFederatedAuthors(client, appname) {
24010
24878
  return registry2.federatedWith.map((p) => p.pubKey);
24011
24879
  }
24012
24880
  async function getFederatedPartner(client, appname, partnerPubKey) {
24013
- const registry2 = await getFederationRegistry(client, appname);
24014
- return registry2.federatedWith.find((p) => p.pubKey === partnerPubKey) || null;
24881
+ return getPartnerFromIndex(client, appname, partnerPubKey);
24015
24882
  }
24016
24883
  async function getCapabilityForAuthor(client, appname, authorPubKey, scope) {
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
- });
24024
- const partner = registry2.federatedWith.find((p) => p.pubKey === authorPubKey);
24884
+ const partner = await getPartnerFromIndex(client, appname, authorPubKey);
24025
24885
  if (!partner) {
24026
- console.log("[Registry] ❌ Partner not found in federatedWith");
24027
24886
  return null;
24028
24887
  }
24029
24888
  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
- });
24035
24889
  return null;
24036
24890
  }
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) => {
24043
- if (cap.expires && cap.expires < Date.now()) {
24044
- console.log("[Registry] Capability expired:", { expires: cap.expires, now: Date.now() });
24045
- return false;
24891
+ const now = Date.now();
24892
+ for (const cap of partner.inboundCapabilities) {
24893
+ if (cap.expires && cap.expires < now) {
24894
+ continue;
24895
+ }
24896
+ if (matchScope(cap.scope, scope)) {
24897
+ return cap;
24046
24898
  }
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;
24054
- }) || null;
24055
- if (result) {
24056
- console.log("[Registry] ✅ Matching capability found");
24057
- } else {
24058
- console.log("[Registry] ❌ No matching capability found");
24059
24899
  }
24060
- return result;
24900
+ return null;
24061
24901
  }
24062
24902
  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
- });
24068
24903
  const registry2 = await getFederationRegistry(client, appname);
24069
24904
  const partner = registry2.federatedWith.find((p) => p.pubKey === partnerPubKey);
24070
24905
  if (!partner) {
24071
- console.log("[Registry] Partner not in registry, auto-adding with capability");
24072
24906
  return addFederatedPartner(client, appname, partnerPubKey, {
24073
24907
  addedVia: "capability_received",
24074
24908
  inboundCapabilities: [capabilityInfo]
@@ -24081,21 +24915,17 @@ async function storeInboundCapability(client, appname, partnerPubKey, capability
24081
24915
  (cap) => JSON.stringify(cap.scope) === JSON.stringify(capabilityInfo.scope)
24082
24916
  );
24083
24917
  if (existingIndex >= 0) {
24084
- console.log("[Registry] Updating existing capability at index:", existingIndex);
24085
24918
  partner.inboundCapabilities[existingIndex] = {
24086
24919
  ...capabilityInfo,
24087
24920
  updatedAt: Date.now()
24088
24921
  };
24089
24922
  } else {
24090
- console.log("[Registry] Adding new capability, total will be:", partner.inboundCapabilities.length + 1);
24091
24923
  partner.inboundCapabilities.push({
24092
24924
  ...capabilityInfo,
24093
24925
  receivedAt: Date.now()
24094
24926
  });
24095
24927
  }
24096
- const result = await saveFederationRegistry(client, appname, registry2);
24097
- console.log("[Registry] ✅ Capability stored, registry saved:", result);
24098
- return result;
24928
+ return saveFederationRegistry(client, appname, registry2);
24099
24929
  }
24100
24930
  async function storeOutboundCapability(client, appname, partnerPubKey, capabilityInfo) {
24101
24931
  const registry2 = await getFederationRegistry(client, appname);
@@ -24253,19 +25083,412 @@ async function getSelfCapability(client, appname, scope) {
24253
25083
  function isSelfCapability(capabilityInfo) {
24254
25084
  return capabilityInfo && capabilityInfo.isSelfCapability === true;
24255
25085
  }
25086
+ async function grantWriteAccessToHolon(client, appname, holonId, lensName, options = {}) {
25087
+ console.warn("[Deprecated] grantWriteAccessToHolon() is deprecated. Use grantAccess() instead.");
25088
+ const registry2 = await getFederationRegistry(client, appname);
25089
+ let partner = registry2.federatedWith.find((p) => p.pubKey === holonId);
25090
+ if (!partner) {
25091
+ partner = {
25092
+ pubKey: holonId,
25093
+ alias: null,
25094
+ addedAt: Date.now(),
25095
+ addedVia: "write_grant",
25096
+ inboundCapabilities: [],
25097
+ outboundCapabilities: [],
25098
+ writeGrants: []
25099
+ };
25100
+ registry2.federatedWith.push(partner);
25101
+ }
25102
+ if (!partner.writeGrants) {
25103
+ partner.writeGrants = [];
25104
+ }
25105
+ const existingIndex = partner.writeGrants.findIndex((g) => g.lensName === lensName);
25106
+ const grant = {
25107
+ lensName,
25108
+ grantedAt: Date.now(),
25109
+ expiresAt: options.expiresAt || null
25110
+ };
25111
+ if (existingIndex >= 0) {
25112
+ partner.writeGrants[existingIndex] = grant;
25113
+ } else {
25114
+ partner.writeGrants.push(grant);
25115
+ }
25116
+ return saveFederationRegistry(client, appname, registry2);
25117
+ }
25118
+ async function revokeWriteAccess(client, appname, holonId, lensName) {
25119
+ console.warn("[Deprecated] revokeWriteAccess() is deprecated. Use revokeAccess() instead.");
25120
+ const registry2 = await getFederationRegistry(client, appname);
25121
+ const partner = registry2.federatedWith.find((p) => p.pubKey === holonId);
25122
+ if (!partner || !partner.writeGrants) {
25123
+ return false;
25124
+ }
25125
+ const initialLength = partner.writeGrants.length;
25126
+ partner.writeGrants = partner.writeGrants.filter((g) => g.lensName !== lensName);
25127
+ if (partner.writeGrants.length === initialLength) {
25128
+ return false;
25129
+ }
25130
+ return saveFederationRegistry(client, appname, registry2);
25131
+ }
25132
+ async function getWriteGrantsForHolon(client, appname, holonId) {
25133
+ console.warn("[Deprecated] getWriteGrantsForHolon() is deprecated. Use getAccessGrants() instead.");
25134
+ const registry2 = await getFederationRegistry(client, appname);
25135
+ const partner = registry2.federatedWith.find((p) => p.pubKey === holonId);
25136
+ if (!partner || !partner.writeGrants) {
25137
+ return [];
25138
+ }
25139
+ const now = Date.now();
25140
+ return partner.writeGrants.filter((g) => !g.expiresAt || g.expiresAt > now);
25141
+ }
25142
+ async function hasWriteAccess(client, appname, holonId, lensName) {
25143
+ console.warn("[Deprecated] hasWriteAccess() is deprecated. Use findAccessGrant() instead.");
25144
+ const grants = await getWriteGrantsForHolon(client, appname, holonId);
25145
+ return grants.some((g) => g.lensName === lensName || g.lensName === "*");
25146
+ }
25147
+ async function getAllWriteGrants(client, appname) {
25148
+ console.warn("[Deprecated] getAllWriteGrants() is deprecated. Use getAccessGrants() instead.");
25149
+ const registry2 = await getFederationRegistry(client, appname);
25150
+ const now = Date.now();
25151
+ const results = [];
25152
+ for (const partner of registry2.federatedWith) {
25153
+ if (partner.writeGrants && partner.writeGrants.length > 0) {
25154
+ const validGrants = partner.writeGrants.filter((g) => !g.expiresAt || g.expiresAt > now);
25155
+ if (validGrants.length > 0) {
25156
+ results.push({
25157
+ holonId: partner.pubKey,
25158
+ alias: partner.alias,
25159
+ writeGrants: validGrants
25160
+ });
25161
+ }
25162
+ }
25163
+ }
25164
+ return results;
25165
+ }
25166
+ async function grantAccess(client, appname, partnerPubKey, scope, permissions, options = {}) {
25167
+ const registry2 = await getFederationRegistry(client, appname);
25168
+ const { expiresAt = null, capabilityToken = null, direction = "inbound" } = options;
25169
+ let partner = registry2.federatedWith.find((p) => p.pubKey === partnerPubKey);
25170
+ if (!partner) {
25171
+ partner = {
25172
+ pubKey: partnerPubKey,
25173
+ alias: null,
25174
+ addedAt: Date.now(),
25175
+ addedVia: "access_grant",
25176
+ inboundCapabilities: [],
25177
+ outboundCapabilities: [],
25178
+ writeGrants: [],
25179
+ accessGrants: []
25180
+ // New unified array
25181
+ };
25182
+ registry2.federatedWith.push(partner);
25183
+ }
25184
+ if (!partner.accessGrants) {
25185
+ partner.accessGrants = [];
25186
+ }
25187
+ const normalizedScope = {
25188
+ holonId: scope.holonId || "*",
25189
+ lensName: scope.lensName || "*"
25190
+ };
25191
+ const existingIndex = partner.accessGrants.findIndex(
25192
+ (g) => g.scope.holonId === normalizedScope.holonId && g.scope.lensName === normalizedScope.lensName && g.direction === direction
25193
+ );
25194
+ const grant = {
25195
+ scope: normalizedScope,
25196
+ permissions: [...permissions],
25197
+ grantedAt: Date.now(),
25198
+ expiresAt,
25199
+ capabilityToken,
25200
+ direction
25201
+ };
25202
+ if (existingIndex >= 0) {
25203
+ partner.accessGrants[existingIndex] = grant;
25204
+ } else {
25205
+ partner.accessGrants.push(grant);
25206
+ }
25207
+ return saveFederationRegistry(client, appname, registry2);
25208
+ }
25209
+ async function revokeAccess(client, appname, partnerPubKey, scope, permissions = null, options = {}) {
25210
+ const registry2 = await getFederationRegistry(client, appname);
25211
+ const { direction = "inbound" } = options;
25212
+ const partner = registry2.federatedWith.find((p) => p.pubKey === partnerPubKey);
25213
+ if (!partner || !partner.accessGrants) {
25214
+ return false;
25215
+ }
25216
+ const normalizedScope = {
25217
+ holonId: scope.holonId || "*",
25218
+ lensName: scope.lensName || "*"
25219
+ };
25220
+ const grantIndex = partner.accessGrants.findIndex(
25221
+ (g) => g.scope.holonId === normalizedScope.holonId && g.scope.lensName === normalizedScope.lensName && g.direction === direction
25222
+ );
25223
+ if (grantIndex < 0) {
25224
+ return false;
25225
+ }
25226
+ if (permissions === null) {
25227
+ partner.accessGrants.splice(grantIndex, 1);
25228
+ } else {
25229
+ const grant = partner.accessGrants[grantIndex];
25230
+ grant.permissions = grant.permissions.filter((p) => !permissions.includes(p));
25231
+ if (grant.permissions.length === 0) {
25232
+ partner.accessGrants.splice(grantIndex, 1);
25233
+ }
25234
+ }
25235
+ return saveFederationRegistry(client, appname, registry2);
25236
+ }
25237
+ async function findAccessGrant(client, appname, partnerPubKey, scope, permission, options = {}) {
25238
+ const registry2 = await getFederationRegistry(client, appname);
25239
+ const { direction = "inbound" } = options;
25240
+ const now = Date.now();
25241
+ const partner = registry2.federatedWith.find((p) => p.pubKey === partnerPubKey);
25242
+ if (!partner) {
25243
+ return null;
25244
+ }
25245
+ if (partner.accessGrants && partner.accessGrants.length > 0) {
25246
+ for (const grant of partner.accessGrants) {
25247
+ if (grant.expiresAt && grant.expiresAt < now) {
25248
+ continue;
25249
+ }
25250
+ if (grant.direction !== direction) {
25251
+ continue;
25252
+ }
25253
+ if (!grant.permissions.includes(permission)) {
25254
+ continue;
25255
+ }
25256
+ if (matchScope(grant.scope, scope)) {
25257
+ return grant;
25258
+ }
25259
+ }
25260
+ }
25261
+ if (permission === "read" && direction === "inbound") {
25262
+ if (partner.inboundCapabilities) {
25263
+ for (const cap of partner.inboundCapabilities) {
25264
+ if (cap.expires && cap.expires < now) continue;
25265
+ if (cap.permissions && !cap.permissions.includes("read")) continue;
25266
+ if (matchScope(cap.scope, scope)) {
25267
+ return {
25268
+ scope: cap.scope,
25269
+ permissions: cap.permissions || ["read"],
25270
+ grantedAt: cap.receivedAt || Date.now(),
25271
+ expiresAt: cap.expires || null,
25272
+ capabilityToken: cap.token,
25273
+ direction: "inbound",
25274
+ _legacy: true
25275
+ };
25276
+ }
25277
+ }
25278
+ }
25279
+ }
25280
+ if (permission === "write" && direction === "inbound") {
25281
+ if (partner.writeGrants) {
25282
+ for (const wg of partner.writeGrants) {
25283
+ if (wg.expiresAt && wg.expiresAt < now) continue;
25284
+ if (wg.lensName === scope.lensName || wg.lensName === "*") {
25285
+ return {
25286
+ scope: { holonId: "*", lensName: wg.lensName },
25287
+ permissions: ["write"],
25288
+ grantedAt: wg.grantedAt || Date.now(),
25289
+ expiresAt: wg.expiresAt || null,
25290
+ direction: "inbound",
25291
+ _legacy: true
25292
+ };
25293
+ }
25294
+ }
25295
+ }
25296
+ }
25297
+ return null;
25298
+ }
25299
+ async function getAccessGrants(client, appname, partnerPubKey, options = {}) {
25300
+ const registry2 = await getFederationRegistry(client, appname);
25301
+ const { direction = "all", includeLegacy = true } = options;
25302
+ const now = Date.now();
25303
+ const partner = registry2.federatedWith.find((p) => p.pubKey === partnerPubKey);
25304
+ if (!partner) {
25305
+ return [];
25306
+ }
25307
+ const results = [];
25308
+ if (partner.accessGrants) {
25309
+ for (const grant of partner.accessGrants) {
25310
+ if (grant.expiresAt && grant.expiresAt < now) continue;
25311
+ if (direction !== "all" && grant.direction !== direction) continue;
25312
+ results.push(grant);
25313
+ }
25314
+ }
25315
+ if (includeLegacy) {
25316
+ if (partner.inboundCapabilities && (direction === "all" || direction === "inbound")) {
25317
+ for (const cap of partner.inboundCapabilities) {
25318
+ if (cap.expires && cap.expires < now) continue;
25319
+ results.push({
25320
+ scope: cap.scope,
25321
+ permissions: cap.permissions || ["read"],
25322
+ grantedAt: cap.receivedAt || Date.now(),
25323
+ expiresAt: cap.expires || null,
25324
+ capabilityToken: cap.token,
25325
+ direction: "inbound",
25326
+ _legacy: true
25327
+ });
25328
+ }
25329
+ }
25330
+ if (partner.writeGrants && (direction === "all" || direction === "inbound")) {
25331
+ for (const wg of partner.writeGrants) {
25332
+ if (wg.expiresAt && wg.expiresAt < now) continue;
25333
+ results.push({
25334
+ scope: { holonId: "*", lensName: wg.lensName },
25335
+ permissions: ["write"],
25336
+ grantedAt: wg.grantedAt || Date.now(),
25337
+ expiresAt: wg.expiresAt || null,
25338
+ direction: "inbound",
25339
+ _legacy: true
25340
+ });
25341
+ }
25342
+ }
25343
+ }
25344
+ return results;
25345
+ }
25346
+ async function migrateToUnifiedGrants(client, appname) {
25347
+ const registry2 = await getFederationRegistry(client, appname);
25348
+ let migratedPartners = 0;
25349
+ let migratedGrants = 0;
25350
+ for (const partner of registry2.federatedWith) {
25351
+ let partnerModified = false;
25352
+ if (!partner.accessGrants) {
25353
+ partner.accessGrants = [];
25354
+ }
25355
+ if (partner.inboundCapabilities && partner.inboundCapabilities.length > 0) {
25356
+ for (const cap of partner.inboundCapabilities) {
25357
+ const alreadyMigrated = partner.accessGrants.some(
25358
+ (g) => g.scope.holonId === cap.scope?.holonId && g.scope.lensName === cap.scope?.lensName && g.direction === "inbound" && g._migratedFrom === "inboundCapability"
25359
+ );
25360
+ if (!alreadyMigrated && cap.scope) {
25361
+ partner.accessGrants.push({
25362
+ scope: { holonId: cap.scope.holonId || "*", lensName: cap.scope.lensName || "*" },
25363
+ permissions: cap.permissions || ["read"],
25364
+ grantedAt: cap.receivedAt || Date.now(),
25365
+ expiresAt: cap.expires || null,
25366
+ capabilityToken: cap.token,
25367
+ direction: "inbound",
25368
+ _migratedFrom: "inboundCapability"
25369
+ });
25370
+ migratedGrants++;
25371
+ partnerModified = true;
25372
+ }
25373
+ }
25374
+ }
25375
+ if (partner.writeGrants && partner.writeGrants.length > 0) {
25376
+ for (const wg of partner.writeGrants) {
25377
+ const alreadyMigrated = partner.accessGrants.some(
25378
+ (g) => g.scope.lensName === wg.lensName && g.permissions.includes("write") && g.direction === "inbound" && g._migratedFrom === "writeGrant"
25379
+ );
25380
+ if (!alreadyMigrated) {
25381
+ partner.accessGrants.push({
25382
+ scope: { holonId: "*", lensName: wg.lensName },
25383
+ permissions: ["write"],
25384
+ grantedAt: wg.grantedAt || Date.now(),
25385
+ expiresAt: wg.expiresAt || null,
25386
+ direction: "inbound",
25387
+ _migratedFrom: "writeGrant"
25388
+ });
25389
+ migratedGrants++;
25390
+ partnerModified = true;
25391
+ }
25392
+ }
25393
+ }
25394
+ if (partnerModified) {
25395
+ migratedPartners++;
25396
+ }
25397
+ }
25398
+ if (migratedGrants > 0) {
25399
+ await saveFederationRegistry(client, appname, registry2);
25400
+ }
25401
+ return { migratedPartners, migratedGrants };
25402
+ }
25403
+ function convertLegacyLensConfig(legacyConfig) {
25404
+ if (!legacyConfig) return { version: "2.0", permissions: {} };
25405
+ const permissions = {};
25406
+ for (const lens of legacyConfig.inbound || []) {
25407
+ if (!permissions[lens]) {
25408
+ permissions[lens] = { receive: [], share: [] };
25409
+ }
25410
+ if (!permissions[lens].receive.includes("read")) {
25411
+ permissions[lens].receive.push("read");
25412
+ }
25413
+ }
25414
+ for (const lens of legacyConfig.outbound || []) {
25415
+ if (!permissions[lens]) {
25416
+ permissions[lens] = { receive: [], share: [] };
25417
+ }
25418
+ if (!permissions[lens].share.includes("read")) {
25419
+ permissions[lens].share.push("read");
25420
+ }
25421
+ }
25422
+ for (const lens of legacyConfig.writeInbound || []) {
25423
+ if (!permissions[lens]) {
25424
+ permissions[lens] = { receive: [], share: [] };
25425
+ }
25426
+ if (!permissions[lens].receive.includes("write")) {
25427
+ permissions[lens].receive.push("write");
25428
+ }
25429
+ }
25430
+ for (const lens of legacyConfig.writeOutbound || []) {
25431
+ if (!permissions[lens]) {
25432
+ permissions[lens] = { receive: [], share: [] };
25433
+ }
25434
+ if (!permissions[lens].share.includes("write")) {
25435
+ permissions[lens].share.push("write");
25436
+ }
25437
+ }
25438
+ return { version: "2.0", permissions };
25439
+ }
25440
+ function convertToLegacyLensConfig(unifiedConfig) {
25441
+ if (!unifiedConfig || !unifiedConfig.permissions) {
25442
+ return { inbound: [], outbound: [], writeInbound: [], writeOutbound: [] };
25443
+ }
25444
+ const legacy = {
25445
+ inbound: [],
25446
+ outbound: [],
25447
+ writeInbound: [],
25448
+ writeOutbound: []
25449
+ };
25450
+ for (const [lensName, perms] of Object.entries(unifiedConfig.permissions)) {
25451
+ if (perms.receive?.includes("read")) {
25452
+ legacy.inbound.push(lensName);
25453
+ }
25454
+ if (perms.share?.includes("read")) {
25455
+ legacy.outbound.push(lensName);
25456
+ }
25457
+ if (perms.receive?.includes("write")) {
25458
+ legacy.writeInbound.push(lensName);
25459
+ }
25460
+ if (perms.share?.includes("write")) {
25461
+ legacy.writeOutbound.push(lensName);
25462
+ }
25463
+ }
25464
+ return legacy;
25465
+ }
24256
25466
  const registry = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
24257
25467
  __proto__: null,
24258
25468
  addFederatedPartner,
24259
25469
  cleanupExpiredCapabilities,
25470
+ convertLegacyLensConfig,
25471
+ convertToLegacyLensConfig,
25472
+ findAccessGrant,
25473
+ getAccessGrants,
25474
+ getAllWriteGrants,
24260
25475
  getCapabilityForAuthor,
24261
25476
  getFederatedAuthors,
24262
25477
  getFederatedAuthorsForScope,
24263
25478
  getFederatedPartner,
24264
25479
  getFederationRegistry,
24265
25480
  getSelfCapability,
25481
+ getWriteGrantsForHolon,
25482
+ grantAccess,
25483
+ grantWriteAccessToHolon,
25484
+ hasWriteAccess,
25485
+ invalidateRegistryCache,
24266
25486
  isSelfCapability,
25487
+ migrateToUnifiedGrants,
24267
25488
  removeFederatedPartner,
25489
+ revokeAccess,
24268
25490
  revokeOutboundCapability,
25491
+ revokeWriteAccess,
24269
25492
  saveFederationRegistry,
24270
25493
  storeInboundCapability,
24271
25494
  storeOutboundCapability,
@@ -24334,6 +25557,7 @@ function createHologram(sourceHolon, targetHolon, lensName, dataId, appname, opt
24334
25557
  // Always the token string (normalized above)
24335
25558
  _meta: {
24336
25559
  created: Date.now(),
25560
+ lastUpdated: Date.now(),
24337
25561
  sourceHolon,
24338
25562
  source: sourceHolon,
24339
25563
  // Alias for compatibility
@@ -24346,6 +25570,8 @@ function createHologram(sourceHolon, targetHolon, lensName, dataId, appname, opt
24346
25570
  async function createHologramWithCapability(client, sourceHolon, targetHolon, lensName, dataId, appname, options = {}) {
24347
25571
  const {
24348
25572
  sourceAuthorPubKey = client.publicKey,
25573
+ sourceAuthorPrivKey = client.privateKey,
25574
+ // Allow passing author's private key for signing
24349
25575
  targetAuthorPubKey = client.publicKey,
24350
25576
  capability: providedCapability = null,
24351
25577
  permissions = ["read"],
@@ -24362,7 +25588,8 @@ async function createHologramWithCapability(client, sourceHolon, targetHolon, le
24362
25588
  expiresIn: expiresIn !== null ? expiresIn : isSelfCapability2 ? 365 * 24 * 60 * 60 * 1e3 : 36e5,
24363
25589
  // 1 year for self, 1 hour for cross
24364
25590
  issuer: sourceAuthorPubKey,
24365
- issuerKey: client.privateKey
25591
+ issuerKey: sourceAuthorPrivKey
25592
+ // Use passed private key (holon's key when federated via holonsbot)
24366
25593
  }
24367
25594
  );
24368
25595
  if (!isSelfCapability2 && client.privateKey) {
@@ -24442,6 +25669,18 @@ async function resolveHologram(client, hologram2, visited = /* @__PURE__ */ new
24442
25669
  console.warn(`❌ Hologram missing capability: ${soul}`);
24443
25670
  return null;
24444
25671
  }
25672
+ if (authorPubKey) {
25673
+ const issuerMatch = await verifyCapabilityIssuer(capability, authorPubKey);
25674
+ if (!issuerMatch) {
25675
+ console.warn(`❌ Capability issuer does not match authorPubKey for hologram: ${soul}`);
25676
+ console.warn(` Expected author: ${authorPubKey?.slice(0, 12)}...`);
25677
+ const decoded = decodeCapability(capability);
25678
+ if (decoded) {
25679
+ console.warn(` Actual issuer: ${decoded.issuer?.slice(0, 12)}...`);
25680
+ }
25681
+ return null;
25682
+ }
25683
+ }
24445
25684
  const isValid = await verifyCapability$1(
24446
25685
  capability,
24447
25686
  "read",
@@ -24636,13 +25875,6 @@ async function propagateData(client, appname, data, sourceHolon, targetHolon, le
24636
25875
  const sourceAuthorPubKey = options.sourceAuthorPubKey || client.publicKey;
24637
25876
  const isPubkey2 = typeof targetHolon === "string" && /^[0-9a-f]{64}$/i.test(targetHolon);
24638
25877
  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
- });
24646
25878
  const hologram2 = await createHologramWithCapability(
24647
25879
  client,
24648
25880
  sourceHolon,
@@ -24652,6 +25884,8 @@ async function propagateData(client, appname, data, sourceHolon, targetHolon, le
24652
25884
  appname,
24653
25885
  {
24654
25886
  sourceAuthorPubKey,
25887
+ sourceAuthorPrivKey: options.sourceAuthorPrivKey,
25888
+ // Pass through for holon-signed capabilities
24655
25889
  targetAuthorPubKey,
24656
25890
  capability: options.capability,
24657
25891
  permissions: ["read"]
@@ -24716,7 +25950,6 @@ async function addActiveHologram(client, appname, sourceHolon, lensName, dataId,
24716
25950
  let sourcePath = buildPath(currentAppname, currentHolon, currentLens, currentDataId);
24717
25951
  let sourceData = await read(client, sourcePath);
24718
25952
  if (!sourceData) {
24719
- console.warn(`Source data not found at ${sourcePath}`);
24720
25953
  return false;
24721
25954
  }
24722
25955
  let depth = 0;
@@ -24730,13 +25963,9 @@ async function addActiveHologram(client, appname, sourceHolon, lensName, dataId,
24730
25963
  sourcePath = buildPath(currentAppname, currentHolon, currentLens, currentDataId);
24731
25964
  sourceData = await read(client, sourcePath);
24732
25965
  if (!sourceData) {
24733
- console.warn(`Real source data not found at ${sourcePath}`);
24734
25966
  return false;
24735
25967
  }
24736
25968
  }
24737
- if (depth > 0) {
24738
- console.info(`📍 Followed hologram chain (depth: ${depth}) to real source: ${currentHolon}/${currentLens}/${currentDataId}`);
24739
- }
24740
25969
  if (!sourceData._meta) {
24741
25970
  sourceData._meta = {};
24742
25971
  }
@@ -24804,7 +26033,6 @@ async function updateActiveHologramPlatform(client, appname, sourceHolon, lensNa
24804
26033
  let sourcePath = buildPath(currentAppname, currentHolon, currentLens, currentDataId);
24805
26034
  let sourceData = await read(client, sourcePath);
24806
26035
  if (!sourceData) {
24807
- console.warn(`Source data not found at ${sourcePath}`);
24808
26036
  return false;
24809
26037
  }
24810
26038
  let depth = 0;
@@ -24818,7 +26046,6 @@ async function updateActiveHologramPlatform(client, appname, sourceHolon, lensNa
24818
26046
  sourcePath = buildPath(currentAppname, currentHolon, currentLens, currentDataId);
24819
26047
  sourceData = await read(client, sourcePath);
24820
26048
  if (!sourceData) {
24821
- console.warn(`Real source data not found at ${sourcePath}`);
24822
26049
  return false;
24823
26050
  }
24824
26051
  }
@@ -25086,161 +26313,6 @@ const hologram = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
25086
26313
  updateHologramOverrides,
25087
26314
  wouldCreateCircularHologram
25088
26315
  }, Symbol.toStringTag, { value: "Module" }));
25089
- function hexToBytes(hex2) {
25090
- const bytes2 = new Uint8Array(hex2.length / 2);
25091
- for (let i = 0; i < hex2.length; i += 2) {
25092
- bytes2[i / 2] = parseInt(hex2.substr(i, 2), 16);
25093
- }
25094
- return bytes2;
25095
- }
25096
- function bytesToHex(bytes2) {
25097
- return Array.from(bytes2).map((b2) => b2.toString(16).padStart(2, "0")).join("");
25098
- }
25099
- function parseNpubOrHex(input) {
25100
- const trimmed = input?.trim();
25101
- if (!trimmed) {
25102
- return { valid: false, error: "Public key is required" };
25103
- }
25104
- if (/^[0-9a-fA-F]{64}$/.test(trimmed)) {
25105
- return { valid: true, hexPubKey: trimmed.toLowerCase() };
25106
- }
25107
- let npubString = trimmed;
25108
- if (npubString.startsWith("nostr:")) {
25109
- npubString = npubString.slice(6);
25110
- }
25111
- if (npubString.startsWith("npub1")) {
25112
- try {
25113
- const decoded = nip19.decode(npubString);
25114
- if (decoded.type === "npub") {
25115
- return { valid: true, hexPubKey: decoded.data };
25116
- }
25117
- return { valid: false, error: "Invalid npub format" };
25118
- } catch (e) {
25119
- return { valid: false, error: "Invalid npub: unable to decode" };
25120
- }
25121
- }
25122
- return { valid: false, error: "Enter a valid npub (npub1...) or 64-character hex public key" };
25123
- }
25124
- function hexToNpub(hexPubKey) {
25125
- try {
25126
- return nip19.npubEncode(hexPubKey);
25127
- } catch (e) {
25128
- console.error("Failed to encode hex to npub:", e);
25129
- return hexPubKey;
25130
- }
25131
- }
25132
- function npubToHex(npub2) {
25133
- try {
25134
- const decoded = nip19.decode(npub2);
25135
- if (decoded.type === "npub") {
25136
- return decoded.data;
25137
- }
25138
- return null;
25139
- } catch (e) {
25140
- return null;
25141
- }
25142
- }
25143
- function shortenPubKey(pubKey) {
25144
- if (!pubKey || pubKey.length <= 20) return pubKey || "";
25145
- return `${pubKey.slice(0, 8)}...${pubKey.slice(-8)}`;
25146
- }
25147
- function shortenNpub(npub2) {
25148
- if (!npub2 || npub2.length <= 20) return npub2 || "";
25149
- return `${npub2.slice(0, 12)}...${npub2.slice(-8)}`;
25150
- }
25151
- function getPublicKey(privateKey) {
25152
- return getPublicKey$2(hexToBytes(privateKey));
25153
- }
25154
- async function encryptNIP04(privateKey, recipientPubKey, content) {
25155
- return await nip04.encrypt(privateKey, recipientPubKey, content);
25156
- }
25157
- async function decryptNIP04(privateKey, senderPubKey, encryptedContent) {
25158
- return await nip04.decrypt(privateKey, senderPubKey, encryptedContent);
25159
- }
25160
- function encryptNIP44(privateKey, recipientPubKey, content) {
25161
- const privKeyBytes = hexToBytes(privateKey);
25162
- const conversationKey = nip44.v2.utils.getConversationKey(privKeyBytes, recipientPubKey);
25163
- return nip44.v2.encrypt(content, conversationKey);
25164
- }
25165
- function decryptNIP44(privateKey, senderPubKey, encryptedContent) {
25166
- const privKeyBytes = hexToBytes(privateKey);
25167
- const conversationKey = nip44.v2.utils.getConversationKey(privKeyBytes, senderPubKey);
25168
- return nip44.v2.decrypt(encryptedContent, conversationKey);
25169
- }
25170
- function createSignedEvent(kind2, content, tags, privateKey) {
25171
- const event = {
25172
- kind: kind2,
25173
- content,
25174
- tags,
25175
- created_at: Math.floor(Date.now() / 1e3)
25176
- };
25177
- return finalizeEvent(event, hexToBytes(privateKey));
25178
- }
25179
- function createDMEvent(recipientPubKey, encryptedContent, privateKey) {
25180
- return createSignedEvent(
25181
- 4,
25182
- // NIP-04 encrypted DM
25183
- encryptedContent,
25184
- [["p", recipientPubKey]],
25185
- privateKey
25186
- );
25187
- }
25188
- function isValidHexPubKey(str2) {
25189
- return typeof str2 === "string" && /^[0-9a-fA-F]{64}$/.test(str2);
25190
- }
25191
- function isValidNpub(str2) {
25192
- if (typeof str2 !== "string" || !str2.startsWith("npub1")) {
25193
- return false;
25194
- }
25195
- try {
25196
- const decoded = nip19.decode(str2);
25197
- return decoded.type === "npub";
25198
- } catch {
25199
- return false;
25200
- }
25201
- }
25202
- function generateNonce() {
25203
- return Date.now().toString(36) + Math.random().toString(36).substring(2, 15);
25204
- }
25205
- function signEvent(event, privateKey) {
25206
- const eventToSign = {
25207
- kind: event.kind,
25208
- content: event.content,
25209
- tags: event.tags || [],
25210
- created_at: event.created_at || Math.floor(Date.now() / 1e3)
25211
- };
25212
- return finalizeEvent(eventToSign, hexToBytes(privateKey));
25213
- }
25214
- function verifyEvent(event) {
25215
- return verifyEvent$1(event);
25216
- }
25217
- const nostrUtils = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
25218
- __proto__: null,
25219
- NDK,
25220
- NDKEvent,
25221
- NDKPrivateKeySigner,
25222
- NDKSubscriptionCacheUsage,
25223
- NDKUser,
25224
- bytesToHex,
25225
- createDMEvent,
25226
- createSignedEvent,
25227
- decryptNIP04,
25228
- decryptNIP44,
25229
- encryptNIP04,
25230
- encryptNIP44,
25231
- generateNonce,
25232
- getPublicKey,
25233
- hexToBytes,
25234
- hexToNpub,
25235
- isValidHexPubKey,
25236
- isValidNpub,
25237
- npubToHex,
25238
- parseNpubOrHex,
25239
- shortenNpub,
25240
- shortenPubKey,
25241
- signEvent,
25242
- verifyEvent
25243
- }, Symbol.toStringTag, { value: "Module" }));
25244
26316
  const HOLON_REGISTRY_TABLE = "_holon-registry";
25245
26317
  function isPubkey(str2) {
25246
26318
  return typeof str2 === "string" && /^[0-9a-f]{64}$/i.test(str2);
@@ -25848,23 +26920,309 @@ const cardStorage$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defin
25848
26920
  setLastDMFetchTime,
25849
26921
  updateCardStatus
25850
26922
  }, Symbol.toStringTag, { value: "Module" }));
26923
+ class LensKeyStore {
26924
+ /**
26925
+ * Creates a new LensKeyStore instance.
26926
+ */
26927
+ constructor() {
26928
+ this.ownKeys = /* @__PURE__ */ new Map();
26929
+ this.receivedKeys = /* @__PURE__ */ new Map();
26930
+ this.partnerWrappedKeys = /* @__PURE__ */ new Map();
26931
+ }
26932
+ /**
26933
+ * Store a lens key we own (we generated it).
26934
+ *
26935
+ * @param {string} lensPath - Path identifying the lens (appName/holonId/lensName)
26936
+ * @param {Uint8Array} key - 32-byte symmetric key
26937
+ * @param {string} wrappedForSelf - NIP-44 wrapped key for self-recovery
26938
+ */
26939
+ storeOwnKey(lensPath, key, wrappedForSelf) {
26940
+ this.ownKeys.set(lensPath, {
26941
+ key,
26942
+ wrappedForSelf,
26943
+ createdAt: Date.now()
26944
+ });
26945
+ }
26946
+ /**
26947
+ * Store a lens key we received from a federation partner.
26948
+ *
26949
+ * @param {string} lensPath - Path identifying the lens
26950
+ * @param {Uint8Array} key - 32-byte symmetric key
26951
+ * @param {string} fromPubKey - Public key of the partner who shared the key
26952
+ */
26953
+ storeReceivedKey(lensPath, key, fromPubKey) {
26954
+ this.receivedKeys.set(lensPath, {
26955
+ key,
26956
+ fromPubKey,
26957
+ receivedAt: Date.now()
26958
+ });
26959
+ }
26960
+ /**
26961
+ * Get the key for a lens (own or received).
26962
+ * Own keys take precedence over received keys.
26963
+ *
26964
+ * @param {string} lensPath - Path identifying the lens
26965
+ * @returns {Uint8Array|null} 32-byte symmetric key or null if not found
26966
+ */
26967
+ getKey(lensPath) {
26968
+ const own = this.ownKeys.get(lensPath);
26969
+ if (own) return own.key;
26970
+ const received = this.receivedKeys.get(lensPath);
26971
+ if (received) return received.key;
26972
+ return null;
26973
+ }
26974
+ /**
26975
+ * Check if we have a key for a lens.
26976
+ *
26977
+ * @param {string} lensPath - Path identifying the lens
26978
+ * @returns {boolean} True if key exists
26979
+ */
26980
+ hasKey(lensPath) {
26981
+ return this.ownKeys.has(lensPath) || this.receivedKeys.has(lensPath);
26982
+ }
26983
+ /**
26984
+ * Check if we own the key for a lens (vs received it).
26985
+ *
26986
+ * @param {string} lensPath - Path identifying the lens
26987
+ * @returns {boolean} True if we own the key
26988
+ */
26989
+ isOwnKey(lensPath) {
26990
+ return this.ownKeys.has(lensPath);
26991
+ }
26992
+ /**
26993
+ * Get or create a key for a lens.
26994
+ * If key doesn't exist, generates a new one.
26995
+ *
26996
+ * @param {string} lensPath - Path identifying the lens
26997
+ * @param {string} ownerPrivKey - Owner's private key for wrapping
26998
+ * @param {string} ownerPubKey - Owner's public key
26999
+ * @returns {Object} { key: Uint8Array, isNew: boolean, wrappedForSelf: string }
27000
+ */
27001
+ getOrCreateKey(lensPath, ownerPrivKey, ownerPubKey) {
27002
+ const existing = this.ownKeys.get(lensPath);
27003
+ if (existing) {
27004
+ return {
27005
+ key: existing.key,
27006
+ isNew: false,
27007
+ wrappedForSelf: existing.wrappedForSelf
27008
+ };
27009
+ }
27010
+ if (this.receivedKeys.has(lensPath)) {
27011
+ const received = this.receivedKeys.get(lensPath);
27012
+ return {
27013
+ key: received.key,
27014
+ isNew: false,
27015
+ wrappedForSelf: null
27016
+ // We don't have wrapped version for received keys
27017
+ };
27018
+ }
27019
+ const key = generateLensKey();
27020
+ const wrappedForSelf = wrapKeyForRecipient(key, ownerPrivKey, ownerPubKey);
27021
+ this.storeOwnKey(lensPath, key, wrappedForSelf);
27022
+ return { key, isNew: true, wrappedForSelf };
27023
+ }
27024
+ /**
27025
+ * Wrap a lens key for a partner (for federation sharing).
27026
+ *
27027
+ * @param {string} lensPath - Path identifying the lens
27028
+ * @param {string} ownerPrivKey - Owner's private key
27029
+ * @param {string} partnerPubKey - Partner's public key
27030
+ * @returns {string|null} Wrapped key for partner or null if no key exists
27031
+ */
27032
+ wrapKeyForPartner(lensPath, ownerPrivKey, partnerPubKey) {
27033
+ const key = this.getKey(lensPath);
27034
+ if (!key) return null;
27035
+ const wrappedKey = wrapKeyForRecipient(key, ownerPrivKey, partnerPubKey);
27036
+ if (!this.partnerWrappedKeys.has(lensPath)) {
27037
+ this.partnerWrappedKeys.set(lensPath, /* @__PURE__ */ new Map());
27038
+ }
27039
+ this.partnerWrappedKeys.get(lensPath).set(partnerPubKey, wrappedKey);
27040
+ return wrappedKey;
27041
+ }
27042
+ /**
27043
+ * Get all partners who have been given wrapped keys for a lens.
27044
+ *
27045
+ * @param {string} lensPath - Path identifying the lens
27046
+ * @returns {string[]} Array of partner public keys
27047
+ */
27048
+ getPartnersWithAccess(lensPath) {
27049
+ const partners = this.partnerWrappedKeys.get(lensPath);
27050
+ return partners ? Array.from(partners.keys()) : [];
27051
+ }
27052
+ /**
27053
+ * Revoke a partner's access by removing their wrapped key.
27054
+ * Note: This doesn't prevent them from using their cached key.
27055
+ * For true revocation, you need to re-key the lens.
27056
+ *
27057
+ * @param {string} lensPath - Path identifying the lens
27058
+ * @param {string} partnerPubKey - Partner's public key
27059
+ * @returns {boolean} True if partner was found and removed
27060
+ */
27061
+ revokePartnerAccess(lensPath, partnerPubKey) {
27062
+ const partners = this.partnerWrappedKeys.get(lensPath);
27063
+ if (partners) {
27064
+ return partners.delete(partnerPubKey);
27065
+ }
27066
+ return false;
27067
+ }
27068
+ /**
27069
+ * Re-key a lens (generate new key and re-wrap for remaining partners).
27070
+ * Used for key rotation or access revocation.
27071
+ *
27072
+ * @param {string} lensPath - Path identifying the lens
27073
+ * @param {string} ownerPrivKey - Owner's private key
27074
+ * @param {string} ownerPubKey - Owner's public key
27075
+ * @param {string[]} [excludePartners=[]] - Partners to exclude from re-wrapping
27076
+ * @returns {Object} { newKey, wrappedForSelf, partnerKeys: Map<pubKey, wrappedKey> }
27077
+ */
27078
+ rekeyLens(lensPath, ownerPrivKey, ownerPubKey, excludePartners = []) {
27079
+ const currentPartners = this.getPartnersWithAccess(lensPath);
27080
+ const remainingPartners = currentPartners.filter((p) => !excludePartners.includes(p));
27081
+ const newKey = generateLensKey();
27082
+ const wrappedForSelf = wrapKeyForRecipient(newKey, ownerPrivKey, ownerPubKey);
27083
+ this.storeOwnKey(lensPath, newKey, wrappedForSelf);
27084
+ const partnerKeys = /* @__PURE__ */ new Map();
27085
+ this.partnerWrappedKeys.set(lensPath, /* @__PURE__ */ new Map());
27086
+ for (const partnerPubKey of remainingPartners) {
27087
+ const wrappedKey = wrapKeyForRecipient(newKey, ownerPrivKey, partnerPubKey);
27088
+ this.partnerWrappedKeys.get(lensPath).set(partnerPubKey, wrappedKey);
27089
+ partnerKeys.set(partnerPubKey, wrappedKey);
27090
+ }
27091
+ return { newKey, wrappedForSelf, partnerKeys };
27092
+ }
27093
+ /**
27094
+ * Receive and unwrap a key from a partner.
27095
+ *
27096
+ * @param {string} lensPath - Path identifying the lens
27097
+ * @param {string} wrappedKey - NIP-44 wrapped key from partner
27098
+ * @param {string} recipientPrivKey - Our private key
27099
+ * @param {string} senderPubKey - Partner's public key
27100
+ * @returns {Uint8Array} Unwrapped 32-byte symmetric key
27101
+ */
27102
+ receiveKey(lensPath, wrappedKey, recipientPrivKey, senderPubKey) {
27103
+ const key = unwrapKey(wrappedKey, recipientPrivKey, senderPubKey);
27104
+ this.storeReceivedKey(lensPath, key, senderPubKey);
27105
+ return key;
27106
+ }
27107
+ /**
27108
+ * Export own keys for persistent storage.
27109
+ * Returns serializable format that can be stored in Nostr.
27110
+ *
27111
+ * @returns {Object[]} Array of { lensPath, wrappedForSelf, createdAt }
27112
+ */
27113
+ exportOwnKeys() {
27114
+ const exported = [];
27115
+ for (const [lensPath, data] of this.ownKeys.entries()) {
27116
+ exported.push({
27117
+ lensPath,
27118
+ wrappedForSelf: data.wrappedForSelf,
27119
+ createdAt: data.createdAt
27120
+ });
27121
+ }
27122
+ return exported;
27123
+ }
27124
+ /**
27125
+ * Import own keys from persistent storage.
27126
+ *
27127
+ * @param {Object[]} keyData - Array of exported key data
27128
+ * @param {string} ownerPrivKey - Owner's private key for unwrapping
27129
+ * @param {string} ownerPubKey - Owner's public key
27130
+ */
27131
+ importOwnKeys(keyData, ownerPrivKey, ownerPubKey) {
27132
+ for (const data of keyData) {
27133
+ const key = unwrapKey(data.wrappedForSelf, ownerPrivKey, ownerPubKey);
27134
+ this.ownKeys.set(data.lensPath, {
27135
+ key,
27136
+ wrappedForSelf: data.wrappedForSelf,
27137
+ createdAt: data.createdAt
27138
+ });
27139
+ }
27140
+ }
27141
+ /**
27142
+ * Clear all keys from the store.
27143
+ * Use with caution - this will remove all access to encrypted data.
27144
+ */
27145
+ clear() {
27146
+ this.ownKeys.clear();
27147
+ this.receivedKeys.clear();
27148
+ this.partnerWrappedKeys.clear();
27149
+ }
27150
+ /**
27151
+ * Get statistics about the key store.
27152
+ *
27153
+ * @returns {Object} { ownKeyCount, receivedKeyCount, totalPartnerGrants }
27154
+ */
27155
+ getStats() {
27156
+ let totalPartnerGrants = 0;
27157
+ for (const partners of this.partnerWrappedKeys.values()) {
27158
+ totalPartnerGrants += partners.size;
27159
+ }
27160
+ return {
27161
+ ownKeyCount: this.ownKeys.size,
27162
+ receivedKeyCount: this.receivedKeys.size,
27163
+ totalPartnerGrants
27164
+ };
27165
+ }
27166
+ }
27167
+ function buildLensPath(appName, holonId, lensName) {
27168
+ return `${appName}/${holonId}/${lensName}`;
27169
+ }
27170
+ function parseLensPath(lensPath) {
27171
+ const parts = lensPath.split("/");
27172
+ if (parts.length < 3) {
27173
+ throw new Error(`Invalid lens path: ${lensPath}`);
27174
+ }
27175
+ return {
27176
+ appName: parts[0],
27177
+ holonId: parts[1],
27178
+ lensName: parts[2]
27179
+ };
27180
+ }
27181
+ function isV2LensConfig(lensConfig) {
27182
+ return lensConfig && lensConfig.version === "2.0" && lensConfig.permissions;
27183
+ }
27184
+ function normalizeLensConfig(lensConfig) {
27185
+ if (!lensConfig) {
27186
+ return { inbound: [], outbound: [], writeInbound: [], writeOutbound: [] };
27187
+ }
27188
+ if (isV2LensConfig(lensConfig)) {
27189
+ return convertToLegacyLensConfig(lensConfig);
27190
+ }
27191
+ return {
27192
+ inbound: lensConfig.inbound || [],
27193
+ outbound: lensConfig.outbound || [],
27194
+ writeInbound: lensConfig.writeInbound || [],
27195
+ writeOutbound: lensConfig.writeOutbound || []
27196
+ };
27197
+ }
27198
+ function toUnifiedPermissions(lensConfig) {
27199
+ if (!lensConfig) {
27200
+ return { version: "2.0", permissions: {} };
27201
+ }
27202
+ if (isV2LensConfig(lensConfig)) {
27203
+ return lensConfig;
27204
+ }
27205
+ return convertLegacyLensConfig(lensConfig);
27206
+ }
25851
27207
  function createFederationRequest({
25852
27208
  senderHolonId,
25853
27209
  senderHolonName,
25854
27210
  senderPubKey,
25855
- lensConfig = { inbound: [], outbound: [] },
27211
+ lensConfig = { inbound: [], outbound: [], writeInbound: [], writeOutbound: [] },
25856
27212
  capabilities: capabilities2 = [],
25857
- message
27213
+ message,
27214
+ useV2 = false
25858
27215
  }) {
27216
+ const normalizedLensConfig = useV2 ? toUnifiedPermissions(lensConfig) : normalizeLensConfig(lensConfig);
25859
27217
  return {
25860
27218
  type: "federation_request",
25861
- version: "1.0",
25862
- requestId: generateNonce(),
27219
+ version: useV2 ? "2.0" : "1.0",
27220
+ requestId: generateNonce$1(),
25863
27221
  timestamp: Date.now(),
25864
27222
  senderHolonId,
25865
27223
  senderHolonName,
25866
27224
  senderNpub: hexToNpub(senderPubKey),
25867
- lensConfig,
27225
+ lensConfig: normalizedLensConfig,
25868
27226
  capabilities: capabilities2,
25869
27227
  message
25870
27228
  };
@@ -25877,18 +27235,23 @@ function createFederationResponse({
25877
27235
  responderPubKey,
25878
27236
  lensConfig,
25879
27237
  capabilities: capabilities2,
25880
- message
27238
+ message,
27239
+ useV2 = false
25881
27240
  }) {
27241
+ let normalizedLensConfig;
27242
+ if (lensConfig) {
27243
+ normalizedLensConfig = useV2 ? toUnifiedPermissions(lensConfig) : normalizeLensConfig(lensConfig);
27244
+ }
25882
27245
  return {
25883
27246
  type: "federation_response",
25884
- version: "1.0",
27247
+ version: useV2 ? "2.0" : "1.0",
25885
27248
  requestId,
25886
27249
  timestamp: Date.now(),
25887
27250
  status,
25888
27251
  responderHolonId,
25889
27252
  responderHolonName,
25890
27253
  responderNpub: responderPubKey ? hexToNpub(responderPubKey) : void 0,
25891
- lensConfig,
27254
+ lensConfig: normalizedLensConfig,
25892
27255
  capabilities: capabilities2,
25893
27256
  message
25894
27257
  };
@@ -25929,8 +27292,33 @@ async function sendFederationResponse(client, privateKey, recipientPubKey, respo
25929
27292
  return false;
25930
27293
  }
25931
27294
  }
27295
+ async function sendLensKeyShare(client, privateKey, recipientPubKey, keyShare) {
27296
+ try {
27297
+ const payload = {
27298
+ type: "lens_key_share",
27299
+ version: "1.0",
27300
+ lensPath: keyShare.lensPath,
27301
+ wrappedKey: keyShare.wrappedKey,
27302
+ timestamp: Date.now()
27303
+ };
27304
+ const content = JSON.stringify(payload);
27305
+ const encrypted = encryptNIP44(privateKey, recipientPubKey, content);
27306
+ const event = createDMEvent(recipientPubKey, encrypted, privateKey);
27307
+ if (client?.publish) {
27308
+ await client.publish(event);
27309
+ console.log("[Handshake] Lens key share sent to:", recipientPubKey.substring(0, 8) + "...", "for lens:", keyShare.lensPath);
27310
+ return true;
27311
+ } else {
27312
+ console.error("[Handshake] No publish method on client");
27313
+ return false;
27314
+ }
27315
+ } catch (error) {
27316
+ console.error("[Handshake] Failed to send lens key share:", error);
27317
+ return false;
27318
+ }
27319
+ }
25932
27320
  function subscribeToFederationDMs(client, privateKey, publicKey, handlers, options = {}) {
25933
- const { onRequest, onResponse, onUpdate, onUpdateResponse } = handlers;
27321
+ const { onRequest, onResponse, onUpdate, onUpdateResponse, onKeyShare } = handlers;
25934
27322
  const { appname } = options;
25935
27323
  let isActive = true;
25936
27324
  const processedEventIds = /* @__PURE__ */ new Set();
@@ -26027,6 +27415,19 @@ function subscribeToFederationDMs(client, privateKey, publicKey, handlers, optio
26027
27415
  }
26028
27416
  console.log("[Handshake] Received federation update response from:", event.pubkey.substring(0, 8) + "...");
26029
27417
  onUpdateResponse?.(payload, event.pubkey);
27418
+ } else if (payload.type === "lens_key_share" && payload.version === "1.0") {
27419
+ const keyShareKey = `key_${payload.lensPath}_${event.pubkey}`;
27420
+ if (processedResponseIds.has(keyShareKey)) {
27421
+ console.log("[Handshake] Skipping already processed key share:", keyShareKey);
27422
+ return;
27423
+ }
27424
+ if (appname && client?.client) {
27425
+ await markDMProcessed(client.client, appname, event.id, "key_share");
27426
+ processedResponseIds.add(keyShareKey);
27427
+ processedDMIds.add(event.id);
27428
+ }
27429
+ console.log("[Handshake] Received lens key share from:", event.pubkey.substring(0, 8) + "...", "for lens:", payload.lensPath);
27430
+ onKeyShare?.(payload, event.pubkey);
26030
27431
  }
26031
27432
  } catch (error) {
26032
27433
  }
@@ -26072,13 +27473,19 @@ async function initiateFederationHandshake(holosphere, privateKey, params) {
26072
27473
  partnerPubKey,
26073
27474
  holonId,
26074
27475
  holonName,
26075
- lensConfig = { inbound: [], outbound: [] },
27476
+ lensConfig = { inbound: [], outbound: [], writeInbound: [], writeOutbound: [] },
26076
27477
  message
26077
27478
  } = params;
26078
27479
  try {
26079
- const senderPubKey = getPublicKey(privateKey);
27480
+ const senderPubKey = getPublicKey$1(privateKey);
27481
+ const normalizedLensConfig = {
27482
+ inbound: lensConfig.inbound || [],
27483
+ outbound: lensConfig.outbound || [],
27484
+ writeInbound: lensConfig.writeInbound || [],
27485
+ writeOutbound: lensConfig.writeOutbound || []
27486
+ };
26080
27487
  const capabilities2 = [];
26081
- for (const lensName of lensConfig.outbound || []) {
27488
+ for (const lensName of normalizedLensConfig.outbound) {
26082
27489
  try {
26083
27490
  const token = await issueCapability$1(
26084
27491
  ["read"],
@@ -26100,11 +27507,42 @@ async function initiateFederationHandshake(holosphere, privateKey, params) {
26100
27507
  console.warn(`[Handshake] Failed to issue capability for ${lensName}:`, err.message);
26101
27508
  }
26102
27509
  }
27510
+ if (holosphere.client) {
27511
+ for (const lensName of normalizedLensConfig.writeOutbound) {
27512
+ try {
27513
+ await grantAccess(
27514
+ holosphere.client,
27515
+ holosphere.config.appName,
27516
+ partnerPubKey,
27517
+ { holonId: "*", lensName },
27518
+ ["write"],
27519
+ { direction: "inbound" }
27520
+ );
27521
+ console.log(`[Handshake] Pre-granted write access to partner for lens "${lensName}"`);
27522
+ } catch (err) {
27523
+ console.warn(`[Handshake] Failed to grant write access for ${lensName}:`, err.message);
27524
+ }
27525
+ }
27526
+ for (const lensName of normalizedLensConfig.outbound) {
27527
+ try {
27528
+ await grantAccess(
27529
+ holosphere.client,
27530
+ holosphere.config.appName,
27531
+ partnerPubKey,
27532
+ { holonId: "*", lensName },
27533
+ ["read"],
27534
+ { direction: "outbound" }
27535
+ );
27536
+ } catch (err) {
27537
+ console.warn(`[Handshake] Failed to grant read access for ${lensName}:`, err.message);
27538
+ }
27539
+ }
27540
+ }
26103
27541
  const request = createFederationRequest({
26104
27542
  senderHolonId: holonId,
26105
27543
  senderHolonName: holonName,
26106
27544
  senderPubKey,
26107
- lensConfig,
27545
+ lensConfig: normalizedLensConfig,
26108
27546
  capabilities: capabilities2,
26109
27547
  message
26110
27548
  });
@@ -26130,11 +27568,17 @@ async function acceptFederationRequest$1(holosphere, privateKey, params) {
26130
27568
  senderPubKey,
26131
27569
  holonId,
26132
27570
  holonName,
26133
- lensConfig = { inbound: [], outbound: [] },
27571
+ lensConfig = { inbound: [], outbound: [], writeInbound: [], writeOutbound: [] },
26134
27572
  message
26135
27573
  } = params;
26136
27574
  try {
26137
- const responderPubKey = getPublicKey(privateKey);
27575
+ const responderPubKey = getPublicKey$1(privateKey);
27576
+ const normalizedLensConfig = {
27577
+ inbound: lensConfig.inbound || [],
27578
+ outbound: lensConfig.outbound || [],
27579
+ writeInbound: lensConfig.writeInbound || [],
27580
+ writeOutbound: lensConfig.writeOutbound || []
27581
+ };
26138
27582
  if (holosphere.client) {
26139
27583
  await addFederatedPartner(holosphere.client, holosphere.config.appName, senderPubKey, {
26140
27584
  alias: request.senderHolonName,
@@ -26145,6 +27589,39 @@ async function acceptFederationRequest$1(holosphere, privateKey, params) {
26145
27589
  await storeInboundCapability(holosphere.client, holosphere.config.appName, senderPubKey, cap);
26146
27590
  }
26147
27591
  }
27592
+ const senderWriteOutbound = request.lensConfig?.writeOutbound || [];
27593
+ if (senderWriteOutbound.length > 0) {
27594
+ console.log("[Handshake] Sender is granting write access for lenses:", senderWriteOutbound);
27595
+ }
27596
+ for (const lensName of normalizedLensConfig.writeOutbound) {
27597
+ try {
27598
+ await grantAccess(
27599
+ holosphere.client,
27600
+ holosphere.config.appName,
27601
+ senderPubKey,
27602
+ { holonId: "*", lensName },
27603
+ ["write"],
27604
+ { direction: "inbound" }
27605
+ );
27606
+ console.log(`[Handshake] Granted write access to sender for lens "${lensName}"`);
27607
+ } catch (err) {
27608
+ console.warn(`[Handshake] Failed to grant write access for ${lensName}:`, err.message);
27609
+ }
27610
+ }
27611
+ for (const lensName of normalizedLensConfig.outbound) {
27612
+ try {
27613
+ await grantAccess(
27614
+ holosphere.client,
27615
+ holosphere.config.appName,
27616
+ senderPubKey,
27617
+ { holonId: "*", lensName },
27618
+ ["read"],
27619
+ { direction: "outbound" }
27620
+ );
27621
+ } catch (err) {
27622
+ console.warn(`[Handshake] Failed to grant read access for ${lensName}:`, err.message);
27623
+ }
27624
+ }
26148
27625
  if (request.senderHolonId) {
26149
27626
  await registerHolon(holosphere.client, holosphere.config.appName, request.senderHolonId, senderPubKey, {
26150
27627
  alias: request.senderHolonName
@@ -26156,11 +27633,11 @@ async function acceptFederationRequest$1(holosphere, privateKey, params) {
26156
27633
  }
26157
27634
  }
26158
27635
  await holosphere.federateHolon(holonId, senderPubKey, {
26159
- lensConfig,
27636
+ lensConfig: normalizedLensConfig,
26160
27637
  partnerName: request.senderHolonName
26161
27638
  });
26162
27639
  const capabilities2 = [];
26163
- for (const lensName of lensConfig.outbound || []) {
27640
+ for (const lensName of normalizedLensConfig.outbound) {
26164
27641
  try {
26165
27642
  const token = await issueCapability$1(
26166
27643
  ["read"],
@@ -26182,13 +27659,37 @@ async function acceptFederationRequest$1(holosphere, privateKey, params) {
26182
27659
  console.warn(`[Handshake] Failed to issue capability for ${lensName}:`, err.message);
26183
27660
  }
26184
27661
  }
27662
+ if (holosphere.keyStore && holosphere.client?.privateKey) {
27663
+ for (const lensName of normalizedLensConfig.outbound) {
27664
+ const lensPath = buildLensPath(holosphere.config.appName, responderPubKey, lensName);
27665
+ const lensKey = holosphere.keyStore.getKey(lensPath);
27666
+ if (lensKey) {
27667
+ try {
27668
+ const wrappedKey = holosphere.keyStore.wrapKeyForPartner(
27669
+ lensPath,
27670
+ holosphere.client.privateKey,
27671
+ senderPubKey
27672
+ );
27673
+ if (wrappedKey) {
27674
+ await sendLensKeyShare(holosphere.client, privateKey, senderPubKey, {
27675
+ lensPath,
27676
+ wrappedKey
27677
+ });
27678
+ console.log(`[Handshake] Shared lens key for "${lensName}" with partner`);
27679
+ }
27680
+ } catch (err) {
27681
+ console.warn(`[Handshake] Failed to share lens key for ${lensName}:`, err.message);
27682
+ }
27683
+ }
27684
+ }
27685
+ }
26185
27686
  const response = createFederationResponse({
26186
27687
  requestId: request.requestId,
26187
27688
  status: "accepted",
26188
27689
  responderHolonId: holonId,
26189
27690
  responderHolonName: holonName,
26190
27691
  responderPubKey,
26191
- lensConfig,
27692
+ lensConfig: normalizedLensConfig,
26192
27693
  capabilities: capabilities2,
26193
27694
  message
26194
27695
  });
@@ -26199,7 +27700,7 @@ async function acceptFederationRequest$1(holosphere, privateKey, params) {
26199
27700
  console.log("[Handshake] Request dismissed after acceptance:", request.requestId);
26200
27701
  }
26201
27702
  const receivedHolograms = {};
26202
- const inboundLenses = lensConfig.inbound || [];
27703
+ const inboundLenses = normalizedLensConfig.inbound;
26203
27704
  if (inboundLenses.length > 0 && holosphere.receiveFederatedLens) {
26204
27705
  console.log("[Handshake] Receiving federated lens data as holograms...");
26205
27706
  for (const lensName of inboundLenses) {
@@ -26312,7 +27813,7 @@ function createFederationUpdate({
26312
27813
  return {
26313
27814
  type: "federation_update",
26314
27815
  version: "1.0",
26315
- updateId: generateNonce(),
27816
+ updateId: generateNonce$1(),
26316
27817
  timestamp: Date.now(),
26317
27818
  senderHolonId,
26318
27819
  senderHolonName,
@@ -26370,7 +27871,7 @@ async function sendFederationUpdateResponse(client, privateKey, recipientPubKey,
26370
27871
  async function requestFederationUpdate(holosphere, privateKey, params) {
26371
27872
  const { partnerPubKey, holonId, holonName, newLensConfig, message } = params;
26372
27873
  try {
26373
- const senderPubKey = getPublicKey(privateKey);
27874
+ const senderPubKey = getPublicKey$1(privateKey);
26374
27875
  const update2 = createFederationUpdate({
26375
27876
  senderHolonId: holonId,
26376
27877
  senderHolonName: holonName,
@@ -26439,6 +27940,8 @@ const handshake = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
26439
27940
  isFederationResponse,
26440
27941
  isFederationUpdate,
26441
27942
  isFederationUpdateResponse,
27943
+ isV2LensConfig,
27944
+ normalizeLensConfig,
26442
27945
  processFederationResponse,
26443
27946
  rejectFederationRequest,
26444
27947
  rejectFederationUpdate,
@@ -26447,7 +27950,9 @@ const handshake = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
26447
27950
  sendFederationResponse,
26448
27951
  sendFederationUpdate,
26449
27952
  sendFederationUpdateResponse,
26450
- subscribeToFederationDMs
27953
+ sendLensKeyShare,
27954
+ subscribeToFederationDMs,
27955
+ toUnifiedPermissions
26451
27956
  }, Symbol.toStringTag, { value: "Module" }));
26452
27957
  function validateNostrEvent(event, partial = true, throwOnError = false) {
26453
27958
  if (!event || typeof event !== "object") {
@@ -26578,7 +28083,7 @@ async function createSubscription(client, path, callback, options = {}) {
26578
28083
  if (!isActive) {
26579
28084
  return;
26580
28085
  }
26581
- const uniqueKey = `${key}-${data?.id || ""}-${data?._meta?.timestamp || Date.now()}`;
28086
+ const uniqueKey = `${key}-${data?.id || ""}-${data?._meta?.lastUpdated || data?._meta?.timestamp || Date.now()}`;
26582
28087
  if (seenKeys.has(uniqueKey)) {
26583
28088
  return;
26584
28089
  }
@@ -36839,7 +38344,7 @@ class ChainManager {
36839
38344
  */
36840
38345
  async _loadEthers() {
36841
38346
  if (!this.ethers) {
36842
- const ethersModule = await import("./index-CoAjtqsD.js");
38347
+ const ethersModule = await import("./index-BdnrGafX.js");
36843
38348
  this.ethers = ethersModule;
36844
38349
  }
36845
38350
  return this.ethers;
@@ -45630,7 +47135,7 @@ class ContractDeployer {
45630
47135
  */
45631
47136
  async _loadEthers() {
45632
47137
  if (!this.ethers) {
45633
- this.ethers = await import("./index-CoAjtqsD.js");
47138
+ this.ethers = await import("./index-BdnrGafX.js");
45634
47139
  }
45635
47140
  return this.ethers;
45636
47141
  }
@@ -46016,7 +47521,7 @@ class ContractOperations {
46016
47521
  */
46017
47522
  async _loadEthers() {
46018
47523
  if (!this.ethers) {
46019
- this.ethers = await import("./index-CoAjtqsD.js");
47524
+ this.ethers = await import("./index-BdnrGafX.js");
46020
47525
  }
46021
47526
  return this.ethers;
46022
47527
  }
@@ -57066,8 +58571,9 @@ function withFederationMethods(Base) {
57066
58571
  permissions = ["read"],
57067
58572
  filter = null
57068
58573
  } = options;
57069
- const normalizedSource = typeof source === "string" ? { holonId: source, authorPubKey: this.client.publicKey } : { holonId: source.holonId, authorPubKey: source.authorPubKey || this.client.publicKey };
57070
- const normalizedTarget = typeof target === "string" ? { holonId: target, authorPubKey: this.client.publicKey } : { holonId: target.holonId, authorPubKey: target.authorPubKey || this.client.publicKey };
58574
+ const isPubkey2 = (str2) => typeof str2 === "string" && /^[0-9a-f]{64}$/i.test(str2);
58575
+ const normalizedSource = typeof source === "string" ? { holonId: source, authorPubKey: isPubkey2(source) ? source : this.client.publicKey } : { holonId: source.holonId, authorPubKey: source.authorPubKey || (isPubkey2(source.holonId) ? source.holonId : this.client.publicKey) };
58576
+ const normalizedTarget = typeof target === "string" ? { holonId: target, authorPubKey: isPubkey2(target) ? target : this.client.publicKey } : { holonId: target.holonId, authorPubKey: target.authorPubKey || (isPubkey2(target.holonId) ? target.holonId : this.client.publicKey) };
57071
58577
  if (normalizedSource.holonId === normalizedTarget.holonId && normalizedSource.authorPubKey === normalizedTarget.authorPubKey) {
57072
58578
  throw new ValidationError("Cannot federate a holon with itself");
57073
58579
  }
@@ -57715,6 +59221,259 @@ function withFederationMethods(Base) {
57715
59221
  }
57716
59222
  return results;
57717
59223
  }
59224
+ // === UNIFIED ACCESS CONTROL ===
59225
+ /**
59226
+ * Unified access verification method.
59227
+ * Single entry point for checking all access permissions (read, write, etc.).
59228
+ * Replaces and unifies the previous separate access checking methods.
59229
+ *
59230
+ * @param {string} targetHolonId - The holon being accessed
59231
+ * @param {string} lensName - The lens being accessed
59232
+ * @param {string} actorPubKey - The actor's public key
59233
+ * @param {string} permission - Permission to check ('read' or 'write')
59234
+ * @param {Object} [options={}] - Options
59235
+ * @param {string} [options.actingAsHolon] - The holon the actor is acting on behalf of
59236
+ * @param {string} [options.capabilityToken] - Explicit capability token to verify
59237
+ * @returns {Promise<Object>} { allowed: boolean, via: string, reason?: string, grant?: Object }
59238
+ *
59239
+ * @example
59240
+ * // Check if current user can read from a holon
59241
+ * const result = await hs.canAccess(targetHolonId, 'quests', myPubKey, 'read');
59242
+ * if (result.allowed) {
59243
+ * console.log('Access granted via:', result.via);
59244
+ * }
59245
+ *
59246
+ * @example
59247
+ * // Check write access with explicit capability
59248
+ * const result = await hs.canAccess(targetHolonId, 'quests', myPubKey, 'write', {
59249
+ * capabilityToken: myCapToken
59250
+ * });
59251
+ */
59252
+ async canAccess(targetHolonId, lensName, actorPubKey, permission, options = {}) {
59253
+ const { actingAsHolon = null, capabilityToken = null } = options;
59254
+ if (actorPubKey === targetHolonId) {
59255
+ return { allowed: true, via: "owner" };
59256
+ }
59257
+ if (capabilityToken) {
59258
+ try {
59259
+ const valid = await this.verifyCapability(capabilityToken, permission, {
59260
+ holonId: targetHolonId,
59261
+ lensName
59262
+ });
59263
+ if (valid) {
59264
+ return { allowed: true, via: "capability" };
59265
+ }
59266
+ } catch (err) {
59267
+ console.log("[canAccess] Capability verification failed:", err.message);
59268
+ }
59269
+ }
59270
+ try {
59271
+ const members = await this.get(targetHolonId, "users");
59272
+ if (members && Array.isArray(members)) {
59273
+ const isMember = members.some(
59274
+ (m) => m.pubKey === actorPubKey || m.id === actorPubKey || m.nostrPubKey === actorPubKey
59275
+ );
59276
+ if (isMember) {
59277
+ return { allowed: true, via: "membership" };
59278
+ }
59279
+ }
59280
+ } catch (err) {
59281
+ console.log("[canAccess] Could not read users lens:", err.message);
59282
+ }
59283
+ const scope = { holonId: targetHolonId, lensName };
59284
+ if (actingAsHolon) {
59285
+ const actorHolons = await this.getHolonsForPubKey(actorPubKey);
59286
+ const isMemberOfActingHolon = actorHolons.some((h) => h.id === actingAsHolon);
59287
+ if (!isMemberOfActingHolon) {
59288
+ return { allowed: false, via: "none", reason: "Not a member of the acting holon" };
59289
+ }
59290
+ const grant = await findAccessGrant(
59291
+ this.client,
59292
+ this.config.appName,
59293
+ actingAsHolon,
59294
+ scope,
59295
+ permission,
59296
+ { direction: "inbound" }
59297
+ );
59298
+ if (grant) {
59299
+ return {
59300
+ allowed: true,
59301
+ via: "federation",
59302
+ grant,
59303
+ reason: `Acting as ${actingAsHolon} with ${permission} grant`
59304
+ };
59305
+ }
59306
+ } else {
59307
+ const actorHolons = await this.getHolonsForPubKey(actorPubKey);
59308
+ for (const holon of actorHolons) {
59309
+ const grant = await findAccessGrant(
59310
+ this.client,
59311
+ this.config.appName,
59312
+ holon.id,
59313
+ scope,
59314
+ permission,
59315
+ { direction: "inbound" }
59316
+ );
59317
+ if (grant) {
59318
+ return {
59319
+ allowed: true,
59320
+ via: "federation",
59321
+ grant,
59322
+ reason: `Member of ${holon.name || holon.id} which has ${permission} grant`,
59323
+ viaHolon: holon.id
59324
+ };
59325
+ }
59326
+ }
59327
+ }
59328
+ return { allowed: false, via: "none", reason: "No access" };
59329
+ }
59330
+ // === CROSS-HOLON WRITE ACCESS ===
59331
+ /**
59332
+ * Check if a writer can write to a target holon's lens.
59333
+ * This is a backward-compatible wrapper that delegates to canAccess().
59334
+ *
59335
+ * @param {string} targetHolonId - The holon being written to
59336
+ * @param {string} lensName - The lens being written to
59337
+ * @param {string} writerPubKey - The writer's public key
59338
+ * @param {Object} [options={}] - Options
59339
+ * @param {string} [options.actingAsHolon] - The holon the writer is acting on behalf of
59340
+ * @param {string} [options.capabilityToken] - Explicit capability token to verify
59341
+ * @returns {Promise<Object>} { canWrite: boolean, reason: string, accessType: string }
59342
+ *
59343
+ * @example
59344
+ * // Check if current user can write to another holon
59345
+ * const result = await hs.canWrite(targetHolonId, 'quests', myPubKey);
59346
+ * if (result.canWrite) {
59347
+ * console.log('Access granted:', result.accessType);
59348
+ * }
59349
+ */
59350
+ async canWrite(targetHolonId, lensName, writerPubKey, options = {}) {
59351
+ const result = await this.canAccess(targetHolonId, lensName, writerPubKey, "write", options);
59352
+ return {
59353
+ canWrite: result.allowed,
59354
+ reason: result.reason || result.via,
59355
+ accessType: result.via,
59356
+ viaHolon: result.viaHolon,
59357
+ grant: result.grant
59358
+ };
59359
+ }
59360
+ /**
59361
+ * Check if an actor can read from a target holon's lens.
59362
+ * Convenience wrapper around canAccess() for read operations.
59363
+ *
59364
+ * @param {string} targetHolonId - The holon being read from
59365
+ * @param {string} lensName - The lens being read from
59366
+ * @param {string} readerPubKey - The reader's public key
59367
+ * @param {Object} [options={}] - Options
59368
+ * @param {string} [options.actingAsHolon] - The holon the reader is acting on behalf of
59369
+ * @param {string} [options.capabilityToken] - Explicit capability token to verify
59370
+ * @returns {Promise<Object>} { canRead: boolean, reason: string, accessType: string }
59371
+ */
59372
+ async canRead(targetHolonId, lensName, readerPubKey, options = {}) {
59373
+ const result = await this.canAccess(targetHolonId, lensName, readerPubKey, "read", options);
59374
+ return {
59375
+ canRead: result.allowed,
59376
+ reason: result.reason || result.via,
59377
+ accessType: result.via,
59378
+ viaHolon: result.viaHolon,
59379
+ grant: result.grant
59380
+ };
59381
+ }
59382
+ /**
59383
+ * Get all holons that a public key is a member of.
59384
+ * Searches federation registry and users lenses.
59385
+ *
59386
+ * @param {string} pubKey - The public key to look up
59387
+ * @returns {Promise<Array>} Array of { id, name } for each holon
59388
+ */
59389
+ async getHolonsForPubKey(pubKey) {
59390
+ const results = [];
59391
+ results.push({ id: pubKey, name: "Personal Holon" });
59392
+ try {
59393
+ const reg = await getFederationRegistry(this.client, this.config.appName);
59394
+ if (reg && reg.federatedWith) {
59395
+ for (const partner of reg.federatedWith) {
59396
+ try {
59397
+ const members = await this.get(partner.pubKey, "users");
59398
+ if (members && Array.isArray(members)) {
59399
+ const isMember = members.some(
59400
+ (m) => m.pubKey === pubKey || m.id === pubKey || m.nostrPubKey === pubKey
59401
+ );
59402
+ if (isMember) {
59403
+ results.push({ id: partner.pubKey, name: partner.alias || partner.pubKey });
59404
+ }
59405
+ }
59406
+ } catch (err) {
59407
+ }
59408
+ }
59409
+ }
59410
+ } catch (err) {
59411
+ console.warn("[getHolonsForPubKey] Error reading federation registry:", err.message);
59412
+ }
59413
+ return results;
59414
+ }
59415
+ /**
59416
+ * Grant write access to a federated holon for a specific lens.
59417
+ * Members of the granted holon will be able to write to this lens.
59418
+ *
59419
+ * @param {string} holonId - The holon to grant write access to
59420
+ * @param {string} lensName - The lens to grant write access for (or '*' for all)
59421
+ * @param {Object} [options={}] - Grant options
59422
+ * @param {number} [options.expiresAt] - Optional expiration timestamp
59423
+ * @returns {Promise<boolean>} Success indicator
59424
+ *
59425
+ * @example
59426
+ * // Grant holon B write access to quests lens
59427
+ * await hs.grantWriteAccess('holon-b-pubkey', 'quests');
59428
+ */
59429
+ async grantWriteAccess(holonId, lensName, options = {}) {
59430
+ return grantWriteAccessToHolon(
59431
+ this.client,
59432
+ this.config.appName,
59433
+ holonId,
59434
+ lensName,
59435
+ options
59436
+ );
59437
+ }
59438
+ /**
59439
+ * Revoke write access from a federated holon for a specific lens.
59440
+ *
59441
+ * @param {string} holonId - The holon to revoke write access from
59442
+ * @param {string} lensName - The lens to revoke write access for
59443
+ * @returns {Promise<boolean>} Success indicator
59444
+ */
59445
+ async revokeWriteAccess(holonId, lensName) {
59446
+ return revokeWriteAccess(
59447
+ this.client,
59448
+ this.config.appName,
59449
+ holonId,
59450
+ lensName
59451
+ );
59452
+ }
59453
+ /**
59454
+ * Get write grants for a specific holon.
59455
+ *
59456
+ * @param {string} holonId - The holon to get write grants for
59457
+ * @returns {Promise<Array>} Array of write grants { lensName, grantedAt, expiresAt }
59458
+ */
59459
+ async getWriteGrantsForHolon(holonId) {
59460
+ return getWriteGrantsForHolon(
59461
+ this.client,
59462
+ this.config.appName,
59463
+ holonId
59464
+ );
59465
+ }
59466
+ /**
59467
+ * Get all write grants issued to federated holons.
59468
+ *
59469
+ * @returns {Promise<Array>} Array of { holonId, alias, writeGrants }
59470
+ */
59471
+ async getAllWriteGrants() {
59472
+ return getAllWriteGrants(
59473
+ this.client,
59474
+ this.config.appName
59475
+ );
59476
+ }
57718
59477
  };
57719
59478
  }
57720
59479
  function createAIServices(apiKey, holosphere = null, options = {}, openaiClient = null) {
@@ -57795,6 +59554,11 @@ class HoloSphereBase extends HoloSphere$1 {
57795
59554
  this._initializeAI(openaiKey, aiOptions);
57796
59555
  }
57797
59556
  this._contracts = null;
59557
+ this.keyStore = new LensKeyStore();
59558
+ this._encryptionConfig = {
59559
+ encryptByDefault: config.encryptByDefault ?? false,
59560
+ publicLenses: config.publicLenses || ["profile", "settings", "public"]
59561
+ };
57798
59562
  }
57799
59563
  /**
57800
59564
  * Gets an environment variable value (Node.js only).
@@ -57969,6 +59733,7 @@ class HoloSphereBase extends HoloSphere$1 {
57969
59733
  * @param {Object} [options.propagationOptions] - Options for propagation
57970
59734
  * @param {boolean} [options.blocking=false] - If true, wait for relay confirmation before returning
57971
59735
  * @param {string} [options.signingKey] - Private key to sign with (hex format). If not provided, uses holosphere's default key.
59736
+ * @param {boolean} [options.encrypt] - Whether to encrypt this data. Defaults to encryptByDefault config.
57972
59737
  * @returns {Promise<boolean>} True if write succeeded (or queued for optimistic writes)
57973
59738
  * @throws {ValidationError} If holonId, lensName, or data is invalid
57974
59739
  * @throws {AuthorizationError} If capability token is invalid
@@ -57985,8 +59750,61 @@ class HoloSphereBase extends HoloSphere$1 {
57985
59750
  if (!data || typeof data !== "object") {
57986
59751
  throw new ValidationError$1("ValidationError: data must be an object");
57987
59752
  }
59753
+ const isPubkeyHolon = typeof holonId === "string" && /^[0-9a-f]{64}$/i.test(holonId);
59754
+ let effectiveWriterPubKey = this.client?.publicKey;
59755
+ if (options.signingKey) {
59756
+ try {
59757
+ effectiveWriterPubKey = getPublicKey(options.signingKey);
59758
+ } catch (err) {
59759
+ this._log("WARN", "⚠️ Invalid signingKey provided, using client key", { error: err.message });
59760
+ }
59761
+ }
59762
+ const isOwnHolon = effectiveWriterPubKey && (holonId === effectiveWriterPubKey || !isPubkeyHolon);
57988
59763
  const capToken = options.capabilityToken || options.capability;
57989
- if (capToken) {
59764
+ const actingAsHolon = options.actingAs || options.actingAsHolon;
59765
+ if (!isOwnHolon) {
59766
+ if (capToken) {
59767
+ const authorized = await this.verifyCapability(capToken, "write", { holonId, lensName });
59768
+ if (!authorized) {
59769
+ this._log("WARN", "🚫 Write denied: Invalid capability token", { holonId, lensName });
59770
+ throw new AuthorizationError(
59771
+ "Invalid capability token for write operation",
59772
+ "write"
59773
+ );
59774
+ }
59775
+ this._log("DEBUG", "✅ Write authorized via capability token", { holonId, lensName });
59776
+ } else {
59777
+ const writerPubKey = effectiveWriterPubKey;
59778
+ if (!writerPubKey) {
59779
+ this._log("WARN", "🚫 Write denied: No authenticated user", { holonId, lensName });
59780
+ throw new AuthorizationError(
59781
+ "Authentication required for writing to federated holons",
59782
+ "write"
59783
+ );
59784
+ }
59785
+ const accessCheck = await this.canAccess(holonId, lensName, writerPubKey, "write", { actingAsHolon });
59786
+ if (!accessCheck.allowed) {
59787
+ this._log("WARN", "🚫 Write denied: No write access", {
59788
+ holonId,
59789
+ lensName,
59790
+ reason: accessCheck.reason,
59791
+ via: accessCheck.via,
59792
+ writerPubKey: writerPubKey?.slice(0, 12) + "..."
59793
+ });
59794
+ throw new AuthorizationError(
59795
+ `Write access denied: ${accessCheck.reason}`,
59796
+ "write"
59797
+ );
59798
+ }
59799
+ this._log("DEBUG", "✅ Write authorized via unified access control", {
59800
+ holonId,
59801
+ lensName,
59802
+ via: accessCheck.via,
59803
+ reason: accessCheck.reason,
59804
+ grant: accessCheck.grant ? "yes" : "no"
59805
+ });
59806
+ }
59807
+ } else if (capToken) {
57990
59808
  const authorized = await this.verifyCapability(capToken, "write", { holonId, lensName });
57991
59809
  if (!authorized) {
57992
59810
  throw new AuthorizationError("AuthorizationError: Invalid capability token for write operation", "write");
@@ -58050,7 +59868,29 @@ class HoloSphereBase extends HoloSphere$1 {
58050
59868
  this._log("DEBUG", "🔗 Syncing hologram write", { path, target: existingData.target });
58051
59869
  return this._syncHologramWrite(existingData, data, path, lensName, options);
58052
59870
  }
58053
- const writeOptions = options.signingKey ? { signingKey: options.signingKey } : {};
59871
+ const shouldEncrypt = this._shouldEncryptLens(lensName, options);
59872
+ let lensKey = null;
59873
+ if (shouldEncrypt && this.client?.privateKey && this.client?.publicKey) {
59874
+ const lensPath = buildLensPath(this.config.appName, holonId, lensName);
59875
+ const keyResult = this.keyStore.getOrCreateKey(
59876
+ lensPath,
59877
+ this.client.privateKey,
59878
+ this.client.publicKey
59879
+ );
59880
+ lensKey = keyResult.key;
59881
+ if (keyResult.isNew) {
59882
+ this._log("DEBUG", "🔑 Generated new lens key", { lensPath });
59883
+ this._storeWrappedKey(lensPath, keyResult.wrappedForSelf).catch((err) => {
59884
+ this._log("WARN", "⚠️ Failed to persist wrapped key", { lensPath, error: err.message });
59885
+ });
59886
+ }
59887
+ }
59888
+ const writeOptions = {
59889
+ ...options.signingKey ? { signingKey: options.signingKey } : {},
59890
+ ...lensKey ? { lensKey } : {},
59891
+ waitForRelays: true
59892
+ // Always wait for relay confirmation when sync is called
59893
+ };
58054
59894
  await write(this.client, path, data, writeOptions);
58055
59895
  const endTime = Date.now();
58056
59896
  const duration = endTime - startTime;
@@ -58118,6 +59958,22 @@ class HoloSphereBase extends HoloSphere$1 {
58118
59958
  const startTime = Date.now();
58119
59959
  await write(this.client, path, localData);
58120
59960
  if (Object.keys(sourceUpdates).length > 0) {
59961
+ const capability = existingData.capability;
59962
+ if (!capability) {
59963
+ throw new Error("Hologram missing capability token for source update");
59964
+ }
59965
+ const hasWrite = await verifyCapability$1(
59966
+ capability,
59967
+ "write",
59968
+ {
59969
+ holonId: existingData.target.holonId,
59970
+ lensName: existingData.target.lensName,
59971
+ dataId: existingData.target.dataId
59972
+ }
59973
+ );
59974
+ if (!hasWrite) {
59975
+ throw new Error("Write capability required to update source data through hologram");
59976
+ }
58121
59977
  const sourcePath = buildPath(
58122
59978
  existingData.target.appname || this.config.appName,
58123
59979
  existingData.target.holonId,
@@ -58162,6 +60018,156 @@ class HoloSphereBase extends HoloSphere$1 {
58162
60018
  async _getCapabilityForAuthor(authorPubKey, scope) {
58163
60019
  return getCapabilityForAuthor(this.client, this.config.appName, authorPubKey, scope);
58164
60020
  }
60021
+ /**
60022
+ * Determine if a lens should be encrypted.
60023
+ *
60024
+ * @private
60025
+ * @param {string} lensName - Name of the lens
60026
+ * @param {Object} options - Write options
60027
+ * @returns {boolean} True if lens should be encrypted
60028
+ */
60029
+ _shouldEncryptLens(lensName, options = {}) {
60030
+ if (options.encrypt !== void 0) {
60031
+ return options.encrypt;
60032
+ }
60033
+ if (this._encryptionConfig.publicLenses.includes(lensName)) {
60034
+ return false;
60035
+ }
60036
+ return this._encryptionConfig.encryptByDefault;
60037
+ }
60038
+ /**
60039
+ * Store a wrapped lens key in Nostr for persistence.
60040
+ * This allows recovering keys after restart.
60041
+ *
60042
+ * @private
60043
+ * @param {string} lensPath - Path identifying the lens
60044
+ * @param {string} wrappedKey - NIP-44 wrapped key for self
60045
+ * @returns {Promise<void>}
60046
+ */
60047
+ async _storeWrappedKey(lensPath, wrappedKey) {
60048
+ const keyStorePath = buildPath(this.config.appName, "_keys", "lens-keys", lensPath.replace(/\//g, "_"));
60049
+ await write(this.client, keyStorePath, {
60050
+ id: lensPath.replace(/\//g, "_"),
60051
+ lensPath,
60052
+ wrappedKey,
60053
+ algorithm: "aes-256-gcm",
60054
+ keyWrap: "nip44",
60055
+ version: "1.0",
60056
+ createdAt: Date.now()
60057
+ });
60058
+ }
60059
+ /**
60060
+ * Load stored wrapped keys from Nostr and restore them to the keyStore.
60061
+ * Called during initialization to recover keys after restart.
60062
+ *
60063
+ * @private
60064
+ * @returns {Promise<number>} Number of keys restored
60065
+ */
60066
+ async _loadStoredKeys() {
60067
+ if (!this.client?.privateKey || !this.client?.publicKey) {
60068
+ return 0;
60069
+ }
60070
+ const keyStorePath = buildPath(this.config.appName, "_keys", "lens-keys") + "/";
60071
+ const storedKeys = await readAll(this.client, keyStorePath);
60072
+ let restored = 0;
60073
+ for (const keyData of storedKeys) {
60074
+ if (keyData && keyData.lensPath && keyData.wrappedKey) {
60075
+ try {
60076
+ const key = unwrapKey(
60077
+ keyData.wrappedKey,
60078
+ this.client.privateKey,
60079
+ this.client.publicKey
60080
+ );
60081
+ this.keyStore.storeOwnKey(keyData.lensPath, key, keyData.wrappedKey);
60082
+ restored++;
60083
+ } catch (err) {
60084
+ this._log("WARN", "⚠️ Failed to restore lens key", { lensPath: keyData.lensPath, error: err.message });
60085
+ }
60086
+ }
60087
+ }
60088
+ if (restored > 0) {
60089
+ this._log("DEBUG", "🔑 Restored lens keys", { count: restored });
60090
+ }
60091
+ return restored;
60092
+ }
60093
+ /**
60094
+ * Get the lens key for reading encrypted data.
60095
+ * Returns the key from keyStore if available, otherwise null.
60096
+ *
60097
+ * @private
60098
+ * @param {string} holonId - Holon identifier
60099
+ * @param {string} lensName - Lens name
60100
+ * @returns {Buffer|null} Lens key or null if not available
60101
+ */
60102
+ _getLensKey(holonId, lensName) {
60103
+ const lensPath = buildLensPath(this.config.appName, holonId, lensName);
60104
+ return this.keyStore.getKey(lensPath);
60105
+ }
60106
+ /**
60107
+ * Handle a received lens key share from a federation partner.
60108
+ * Call this from your onKeyShare handler in subscribeToFederationDMs.
60109
+ *
60110
+ * @param {Object} keyShare - Key share payload
60111
+ * @param {string} keyShare.lensPath - Path identifying the lens
60112
+ * @param {string} keyShare.wrappedKey - NIP-44 wrapped symmetric key
60113
+ * @param {string} senderPubKey - Sender's public key
60114
+ * @returns {boolean} True if key was successfully stored
60115
+ */
60116
+ handleReceivedKeyShare(keyShare, senderPubKey) {
60117
+ if (!this.client?.privateKey) {
60118
+ this._log("WARN", "⚠️ Cannot receive key share: no private key");
60119
+ return false;
60120
+ }
60121
+ try {
60122
+ const key = this.keyStore.receiveKey(
60123
+ keyShare.lensPath,
60124
+ keyShare.wrappedKey,
60125
+ this.client.privateKey,
60126
+ senderPubKey
60127
+ );
60128
+ this._log("DEBUG", "🔑 Received and stored lens key", {
60129
+ lensPath: keyShare.lensPath,
60130
+ from: senderPubKey.slice(0, 12) + "..."
60131
+ });
60132
+ return true;
60133
+ } catch (error) {
60134
+ this._log("ERROR", "❌ Failed to receive key share", {
60135
+ lensPath: keyShare.lensPath,
60136
+ error: error.message
60137
+ });
60138
+ return false;
60139
+ }
60140
+ }
60141
+ /**
60142
+ * Get encryption statistics for the key store.
60143
+ *
60144
+ * @returns {Object} { ownKeyCount, receivedKeyCount, totalPartnerGrants }
60145
+ */
60146
+ getEncryptionStats() {
60147
+ return this.keyStore.getStats();
60148
+ }
60149
+ /**
60150
+ * Check if a lens has an encryption key available.
60151
+ *
60152
+ * @param {string} holonId - Holon identifier
60153
+ * @param {string} lensName - Lens name
60154
+ * @returns {boolean} True if key is available for this lens
60155
+ */
60156
+ hasLensKey(holonId, lensName) {
60157
+ const lensPath = buildLensPath(this.config.appName, holonId, lensName);
60158
+ return this.keyStore.hasKey(lensPath);
60159
+ }
60160
+ /**
60161
+ * Check if we own the key for a lens (vs received it from a partner).
60162
+ *
60163
+ * @param {string} holonId - Holon identifier
60164
+ * @param {string} lensName - Lens name
60165
+ * @returns {boolean} True if we own the key
60166
+ */
60167
+ isOwnLensKey(holonId, lensName) {
60168
+ const lensPath = buildLensPath(this.config.appName, holonId, lensName);
60169
+ return this.keyStore.isOwnKey(lensPath);
60170
+ }
58165
60171
  /**
58166
60172
  * Recursively resolves holograms (references) to their source data.
58167
60173
  * Handles circular reference detection and local overrides.
@@ -58292,9 +60298,17 @@ class HoloSphereBase extends HoloSphere$1 {
58292
60298
  return cached.data;
58293
60299
  }
58294
60300
  }
58295
- const targetPubkey = await this._resolveHolonToPubkey(holonId);
60301
+ const targetPubkey = options.skipResolve ? null : await this._resolveHolonToPubkey(holonId);
58296
60302
  const isOtherAuthor = targetPubkey && targetPubkey !== this.client.publicKey;
60303
+ this._log("DEBUG", "🔍 READ: author check", {
60304
+ targetPubkey: targetPubkey?.slice(0, 12),
60305
+ clientPubkey: this.client?.publicKey?.slice(0, 12),
60306
+ isOtherAuthor,
60307
+ holonId: holonId?.slice(0, 12)
60308
+ });
58297
60309
  let readOptions = {};
60310
+ if (options.forceRelay) readOptions.forceRelay = true;
60311
+ if (options.timeout) readOptions.timeout = options.timeout;
58298
60312
  const capToken = options.capabilityToken || options.capability;
58299
60313
  if (capToken) {
58300
60314
  const authorized = await this.verifyCapability(capToken, "read", { holonId, lensName, dataId });
@@ -58328,18 +60342,23 @@ class HoloSphereBase extends HoloSphere$1 {
58328
60342
  });
58329
60343
  readOptions.authors = [targetPubkey];
58330
60344
  }
60345
+ this._log("DEBUG", "🔍 Checking for federated authors", { isOtherAuthor, holonId: holonId?.slice(0, 12) });
58331
60346
  if (!isOtherAuthor) {
58332
60347
  try {
58333
- const federatedAuthors = await getFederatedAuthorsForScope(
60348
+ this._log("DEBUG", "🔍 Getting federated authors...");
60349
+ const fedAuthorsPromise = getFederatedAuthors(
58334
60350
  this.client,
58335
- this.config.appName,
58336
- { holonId, lensName, dataId },
58337
- "read"
60351
+ this.config.appName
58338
60352
  );
60353
+ const fedAuthorsTimeout = new Promise(
60354
+ (resolve) => setTimeout(() => resolve([]), 5e3)
60355
+ // 5 second timeout, resolve with empty array
60356
+ );
60357
+ const federatedAuthors = await Promise.race([fedAuthorsPromise, fedAuthorsTimeout]);
60358
+ this._log("DEBUG", "✅ Got federated authors", { count: federatedAuthors?.length || 0 });
58339
60359
  if (federatedAuthors.length > 0) {
58340
- const partnerPubkeys = federatedAuthors.map((f) => f.pubKey);
58341
- readOptions.authors = [this.client.publicKey, ...partnerPubkeys];
58342
- this._log("DEBUG", "Including federated authors", { count: partnerPubkeys.length });
60360
+ readOptions.authors = [this.client.publicKey, ...federatedAuthors];
60361
+ this._log("DEBUG", "Including federated authors", { count: federatedAuthors.length });
58343
60362
  }
58344
60363
  } catch (err) {
58345
60364
  this._log("WARN", "Failed to get federated authors", { error: err.message });
@@ -58347,6 +60366,10 @@ class HoloSphereBase extends HoloSphere$1 {
58347
60366
  }
58348
60367
  const startTime = Date.now();
58349
60368
  let result;
60369
+ const lensKey = this._getLensKey(holonId, lensName);
60370
+ if (lensKey) {
60371
+ readOptions.lensKey = lensKey;
60372
+ }
58350
60373
  if (dataId) {
58351
60374
  const path = buildPath(this.config.appName, holonId, lensName, dataId);
58352
60375
  if (this._deleteCache.has(path)) {
@@ -58368,7 +60391,7 @@ class HoloSphereBase extends HoloSphere$1 {
58368
60391
  });
58369
60392
  result = cached.data;
58370
60393
  } else {
58371
- this._log("DEBUG", "📖 CACHE MISS: Reading from storage", { path, authors: readOptions.authors });
60394
+ this._log("DEBUG", "📖 CACHE MISS: Reading from storage", { path, authors: readOptions.authors, hasKey: !!lensKey });
58372
60395
  result = await read(this.client, path, readOptions);
58373
60396
  this._log("DEBUG", "💾 STORAGE READ", {
58374
60397
  path,
@@ -58380,7 +60403,7 @@ class HoloSphereBase extends HoloSphere$1 {
58380
60403
  }
58381
60404
  } else {
58382
60405
  const path = buildPath(this.config.appName, holonId, lensName);
58383
- this._log("DEBUG", "readAll", { holonId, lensName, path, authors: readOptions.authors });
60406
+ this._log("DEBUG", "readAll", { holonId, lensName, path, authors: readOptions.authors, hasKey: !!lensKey });
58384
60407
  const storageResult = await readAll(this.client, path, readOptions);
58385
60408
  result = isOtherAuthor ? storageResult : this._mergeWithWriteCache(storageResult, path);
58386
60409
  this._log("DEBUG", "readAll result", { count: Array.isArray(result) ? result.length : 0 });
@@ -58456,8 +60479,26 @@ class HoloSphereBase extends HoloSphere$1 {
58456
60479
  if (!updates || typeof updates !== "object") {
58457
60480
  throw new ValidationError$1("ValidationError: updates must be an object");
58458
60481
  }
60482
+ const isPubkeyHolon = typeof holonId === "string" && /^[0-9a-f]{64}$/i.test(holonId);
60483
+ const isOwnHolon = this.client && (holonId === this.client.publicKey || !isPubkeyHolon);
58459
60484
  const capToken = options.capabilityToken || options.capability;
58460
- if (capToken) {
60485
+ if (!isOwnHolon) {
60486
+ if (!capToken) {
60487
+ this._log("WARN", "🚫 Update denied: No capability token for federated holon", { holonId, lensName, dataId });
60488
+ throw new AuthorizationError(
60489
+ "Capability token required for writing to federated holons",
60490
+ "write"
60491
+ );
60492
+ }
60493
+ const authorized = await this.verifyCapability(capToken, "write", { holonId, lensName, dataId });
60494
+ if (!authorized) {
60495
+ this._log("WARN", "🚫 Update denied: Invalid capability token", { holonId, lensName, dataId });
60496
+ throw new AuthorizationError(
60497
+ "Invalid capability token for update operation",
60498
+ "write"
60499
+ );
60500
+ }
60501
+ } else if (capToken) {
58461
60502
  const authorized = await this.verifyCapability(capToken, "write", { holonId, lensName, dataId });
58462
60503
  if (!authorized) {
58463
60504
  throw new AuthorizationError("AuthorizationError: Invalid capability token for update operation", "write");
@@ -58528,7 +60569,7 @@ class HoloSphereBase extends HoloSphere$1 {
58528
60569
  });
58529
60570
  if (existingData._meta && existingData._meta.activeHolograms) {
58530
60571
  this._log("DEBUG", "🔗 Refreshing active holograms", { path });
58531
- refreshActiveHolograms(this.client, this.config.appName, holonId, lensName, dataId).catch((err) => this._log("WARN", "⚠️ Hologram refresh failed", { path, error: err.message }));
60572
+ await refreshActiveHolograms(this.client, this.config.appName, holonId, lensName, dataId).catch((err) => this._log("WARN", "⚠️ Hologram refresh failed", { path, error: err.message }));
58532
60573
  }
58533
60574
  return true;
58534
60575
  } catch (error) {
@@ -58571,6 +60612,16 @@ class HoloSphereBase extends HoloSphere$1 {
58571
60612
  if (!dataId) {
58572
60613
  throw new ValidationError$1("ValidationError: dataId must be a non-empty string");
58573
60614
  }
60615
+ const isPubkeyHolon = typeof holonId === "string" && /^[0-9a-f]{64}$/i.test(holonId);
60616
+ const isOwnHolon = this.client && (holonId === this.client.publicKey || !isPubkeyHolon);
60617
+ const capToken = options.capabilityToken || options.capability;
60618
+ if (!isOwnHolon && !capToken) {
60619
+ this._log("WARN", "🚫 Delete denied: No capability token for federated holon", { holonId, lensName, dataId });
60620
+ throw new AuthorizationError(
60621
+ "Capability token required for writing to federated holons",
60622
+ "delete"
60623
+ );
60624
+ }
58574
60625
  const path = buildPath(this.config.appName, holonId, lensName, dataId);
58575
60626
  const cached = this._writeCache.get(path);
58576
60627
  let existingData = cached ? cached.data : null;
@@ -58585,9 +60636,8 @@ class HoloSphereBase extends HoloSphere$1 {
58585
60636
  }
58586
60637
  this._log("DEBUG", "🗑️ DELETE: Found data", { path, source: dataSource });
58587
60638
  const dataOwner = existingData.owner || existingData._creator;
58588
- const isOwner = !dataOwner || dataOwner === this.client.publicKey;
58589
- const capToken = options.capabilityToken || options.capability;
58590
- if (!isOwner) {
60639
+ const isDataOwner = !dataOwner || dataOwner === this.client.publicKey;
60640
+ if (!isDataOwner) {
58591
60641
  if (!capToken) {
58592
60642
  throw new AuthorizationError("AuthorizationError: Capability token required for delete operation", "delete");
58593
60643
  }
@@ -58870,15 +60920,22 @@ class HoloSphereBase extends HoloSphere$1 {
58870
60920
  * @param {string} lensName - Name of the lens
58871
60921
  * @param {Object} data - Data object containing the id to reference
58872
60922
  * @param {string} [targetHolon=null] - Target holon for the hologram, defaults to sourceHolon
58873
- * @returns {Object} Hologram object structure
60923
+ * @returns {Promise<Object>} Hologram object structure
58874
60924
  */
58875
- createHologram(sourceHolon, lensName, data, targetHolon = null) {
58876
- return createHologram(
60925
+ async createHologram(sourceHolon, lensName, data, targetHolon = null) {
60926
+ const target = targetHolon || sourceHolon;
60927
+ return createHologramWithCapability(
60928
+ this.client,
58877
60929
  sourceHolon,
58878
- targetHolon || sourceHolon,
60930
+ target,
58879
60931
  lensName,
58880
60932
  data.id,
58881
- this.config.appName
60933
+ this.config.appName,
60934
+ {
60935
+ sourceAuthorPubKey: this.client.publicKey,
60936
+ targetAuthorPubKey: this.client.publicKey,
60937
+ permissions: ["read"]
60938
+ }
58882
60939
  );
58883
60940
  }
58884
60941
  /**
@@ -59187,6 +61244,8 @@ class HoloSphereBase extends HoloSphere$1 {
59187
61244
  federationData.lensConfig[targetHolon] = {
59188
61245
  inbound: lensConfig.inbound || [],
59189
61246
  outbound: lensConfig.outbound || [],
61247
+ writeInbound: lensConfig.writeInbound || [],
61248
+ writeOutbound: lensConfig.writeOutbound || [],
59190
61249
  timestamp: Date.now()
59191
61250
  };
59192
61251
  const success = await this.writeGlobal("federation", federationData);
@@ -59323,7 +61382,7 @@ class HoloSphereBase extends HoloSphere$1 {
59323
61382
  * @returns {Promise<string>} Hex-encoded public key
59324
61383
  */
59325
61384
  async getPublicKey(privateKey) {
59326
- return getPublicKey$1(privateKey);
61385
+ return getPublicKey(privateKey);
59327
61386
  }
59328
61387
  /**
59329
61388
  * Signs content with a private key.
@@ -59789,26 +61848,30 @@ export {
59789
61848
  networks as bA,
59790
61849
  matchScope as bB,
59791
61850
  createHologram as bC,
59792
- createFederationCard as bD,
59793
- getVisibleLenses as bE,
59794
- getLensConfigForHandshake as bF,
59795
- toggleLens as bG,
59796
- toggleCardExpansion as bH,
59797
- dismissCard$1 as bI,
59798
- saveCard as bJ,
59799
- getCard as bK,
59800
- getDisplayableCards as bL,
59801
- dismissRequest as bM,
59802
- markResponseProcessed as bN,
59803
- isResponseProcessed as bO,
59804
- createAIServices as bP,
59805
- NETWORKS as bQ,
59806
- getNetwork as bR,
59807
- getNetworksByType as bS,
59808
- listNetworks as bT,
59809
- isNetworkSupported as bU,
59810
- getTxUrl as bV,
59811
- getAddressUrl as bW,
61851
+ LensKeyStore as bD,
61852
+ buildLensPath as bE,
61853
+ parseLensPath as bF,
61854
+ lensKeys as bG,
61855
+ createFederationCard as bH,
61856
+ getVisibleLenses as bI,
61857
+ getLensConfigForHandshake as bJ,
61858
+ toggleLens as bK,
61859
+ toggleCardExpansion as bL,
61860
+ dismissCard$1 as bM,
61861
+ saveCard as bN,
61862
+ getCard as bO,
61863
+ getDisplayableCards as bP,
61864
+ dismissRequest as bQ,
61865
+ markResponseProcessed as bR,
61866
+ isResponseProcessed as bS,
61867
+ createAIServices as bT,
61868
+ NETWORKS as bU,
61869
+ getNetwork as bV,
61870
+ getNetworksByType as bW,
61871
+ listNetworks as bX,
61872
+ isNetworkSupported as bY,
61873
+ getTxUrl as bZ,
61874
+ getAddressUrl as b_,
59812
61875
  RelationshipDiscovery as ba,
59813
61876
  TaskBreakdown as bb,
59814
61877
  H3AI as bc,
@@ -59860,4 +61923,4 @@ export {
59860
61923
  concatBytes as y,
59861
61924
  dataLength as z
59862
61925
  };
59863
- //# sourceMappingURL=index-BN_uoxQK.js.map
61926
+ //# sourceMappingURL=index-C3Cag0SV.js.map