@unicitylabs/sphere-sdk 0.1.3 → 0.1.5

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.
@@ -1,6 +1,131 @@
1
1
  // impl/nodejs/storage/FileStorageProvider.ts
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
+
5
+ // constants.ts
6
+ var STORAGE_KEYS_GLOBAL = {
7
+ /** Encrypted BIP39 mnemonic */
8
+ MNEMONIC: "mnemonic",
9
+ /** Encrypted master private key */
10
+ MASTER_KEY: "master_key",
11
+ /** BIP32 chain code */
12
+ CHAIN_CODE: "chain_code",
13
+ /** HD derivation path (full path like m/44'/0'/0'/0/0) */
14
+ DERIVATION_PATH: "derivation_path",
15
+ /** Base derivation path (like m/44'/0'/0' without chain/index) */
16
+ BASE_PATH: "base_path",
17
+ /** Derivation mode: bip32, wif_hmac, legacy_hmac */
18
+ DERIVATION_MODE: "derivation_mode",
19
+ /** Wallet source: mnemonic, file, unknown */
20
+ WALLET_SOURCE: "wallet_source",
21
+ /** Wallet existence flag */
22
+ WALLET_EXISTS: "wallet_exists",
23
+ /** Current active address index */
24
+ CURRENT_ADDRESS_INDEX: "current_address_index",
25
+ /** Index of address nametags (JSON: { "0": "alice", "1": "bob" }) - for discovery */
26
+ ADDRESS_NAMETAGS: "address_nametags"
27
+ };
28
+ var STORAGE_KEYS_ADDRESS = {
29
+ /** Pending transfers for this address */
30
+ PENDING_TRANSFERS: "pending_transfers",
31
+ /** Transfer outbox for this address */
32
+ OUTBOX: "outbox",
33
+ /** Conversations for this address */
34
+ CONVERSATIONS: "conversations",
35
+ /** Messages for this address */
36
+ MESSAGES: "messages",
37
+ /** Transaction history for this address */
38
+ TRANSACTION_HISTORY: "transaction_history"
39
+ };
40
+ var STORAGE_KEYS = {
41
+ ...STORAGE_KEYS_GLOBAL,
42
+ ...STORAGE_KEYS_ADDRESS
43
+ };
44
+ function getAddressId(directAddress) {
45
+ let hash = directAddress;
46
+ if (hash.startsWith("DIRECT://")) {
47
+ hash = hash.slice(9);
48
+ } else if (hash.startsWith("DIRECT:")) {
49
+ hash = hash.slice(7);
50
+ }
51
+ const first = hash.slice(0, 6).toLowerCase();
52
+ const last = hash.slice(-6).toLowerCase();
53
+ return `DIRECT_${first}_${last}`;
54
+ }
55
+ var DEFAULT_NOSTR_RELAYS = [
56
+ "wss://relay.unicity.network",
57
+ "wss://relay.damus.io",
58
+ "wss://nos.lol",
59
+ "wss://relay.nostr.band"
60
+ ];
61
+ var NOSTR_EVENT_KINDS = {
62
+ /** NIP-04 encrypted direct message */
63
+ DIRECT_MESSAGE: 4,
64
+ /** Token transfer (Unicity custom - 31113) */
65
+ TOKEN_TRANSFER: 31113,
66
+ /** Payment request (Unicity custom - 31115) */
67
+ PAYMENT_REQUEST: 31115,
68
+ /** Payment request response (Unicity custom - 31116) */
69
+ PAYMENT_REQUEST_RESPONSE: 31116,
70
+ /** Nametag binding (NIP-78 app-specific data) */
71
+ NAMETAG_BINDING: 30078,
72
+ /** Public broadcast */
73
+ BROADCAST: 1
74
+ };
75
+ var DEFAULT_AGGREGATOR_URL = "https://aggregator.unicity.network/rpc";
76
+ var DEV_AGGREGATOR_URL = "https://dev-aggregator.dyndns.org/rpc";
77
+ var TEST_AGGREGATOR_URL = "https://goggregator-test.unicity.network";
78
+ var DEFAULT_AGGREGATOR_TIMEOUT = 3e4;
79
+ var DEFAULT_AGGREGATOR_API_KEY = "sk_06365a9c44654841a366068bcfc68986";
80
+ var DEFAULT_IPFS_GATEWAYS = [
81
+ "https://ipfs.unicity.network",
82
+ "https://dweb.link",
83
+ "https://ipfs.io"
84
+ ];
85
+ var DEFAULT_BASE_PATH = "m/44'/0'/0'";
86
+ var DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0`;
87
+ var DEFAULT_ELECTRUM_URL = "wss://fulcrum.alpha.unicity.network:50004";
88
+ var TEST_ELECTRUM_URL = "wss://fulcrum.alpha.testnet.unicity.network:50004";
89
+ var TEST_NOSTR_RELAYS = [
90
+ "wss://nostr-relay.testnet.unicity.network"
91
+ ];
92
+ var NETWORKS = {
93
+ mainnet: {
94
+ name: "Mainnet",
95
+ aggregatorUrl: DEFAULT_AGGREGATOR_URL,
96
+ nostrRelays: DEFAULT_NOSTR_RELAYS,
97
+ ipfsGateways: DEFAULT_IPFS_GATEWAYS,
98
+ electrumUrl: DEFAULT_ELECTRUM_URL
99
+ },
100
+ testnet: {
101
+ name: "Testnet",
102
+ aggregatorUrl: TEST_AGGREGATOR_URL,
103
+ nostrRelays: TEST_NOSTR_RELAYS,
104
+ ipfsGateways: DEFAULT_IPFS_GATEWAYS,
105
+ electrumUrl: TEST_ELECTRUM_URL
106
+ },
107
+ dev: {
108
+ name: "Development",
109
+ aggregatorUrl: DEV_AGGREGATOR_URL,
110
+ nostrRelays: TEST_NOSTR_RELAYS,
111
+ ipfsGateways: DEFAULT_IPFS_GATEWAYS,
112
+ electrumUrl: TEST_ELECTRUM_URL
113
+ }
114
+ };
115
+ var TIMEOUTS = {
116
+ /** WebSocket connection timeout */
117
+ WEBSOCKET_CONNECT: 1e4,
118
+ /** Nostr relay reconnect delay */
119
+ NOSTR_RECONNECT_DELAY: 3e3,
120
+ /** Max reconnect attempts */
121
+ MAX_RECONNECT_ATTEMPTS: 5,
122
+ /** Proof polling interval */
123
+ PROOF_POLL_INTERVAL: 1e3,
124
+ /** Sync interval */
125
+ SYNC_INTERVAL: 6e4
126
+ };
127
+
128
+ // impl/nodejs/storage/FileStorageProvider.ts
4
129
  var FileStorageProvider = class {
5
130
  id = "file-storage";
6
131
  name = "File Storage";
@@ -53,18 +178,22 @@ var FileStorageProvider = class {
53
178
  return this.status;
54
179
  }
55
180
  async get(key) {
56
- return this.data[key] ?? null;
181
+ const fullKey = this.getFullKey(key);
182
+ return this.data[fullKey] ?? null;
57
183
  }
58
184
  async set(key, value) {
59
- this.data[key] = value;
185
+ const fullKey = this.getFullKey(key);
186
+ this.data[fullKey] = value;
60
187
  await this.save();
61
188
  }
62
189
  async remove(key) {
63
- delete this.data[key];
190
+ const fullKey = this.getFullKey(key);
191
+ delete this.data[fullKey];
64
192
  await this.save();
65
193
  }
66
194
  async has(key) {
67
- return key in this.data;
195
+ const fullKey = this.getFullKey(key);
196
+ return fullKey in this.data;
68
197
  }
69
198
  async keys(prefix) {
70
199
  const allKeys = Object.keys(this.data);
@@ -84,6 +213,19 @@ var FileStorageProvider = class {
84
213
  }
85
214
  await this.save();
86
215
  }
216
+ /**
217
+ * Get full storage key with address prefix for per-address keys
218
+ */
219
+ getFullKey(key) {
220
+ const isPerAddressKey = Object.values(STORAGE_KEYS_ADDRESS).includes(
221
+ key
222
+ );
223
+ if (isPerAddressKey && this._identity?.directAddress) {
224
+ const addressId = getAddressId(this._identity.directAddress);
225
+ return `${addressId}_${key}`;
226
+ }
227
+ return key;
228
+ }
87
229
  async save() {
88
230
  if (!fs.existsSync(this.dataDir)) {
89
231
  fs.mkdirSync(this.dataDir, { recursive: true });
@@ -102,15 +244,26 @@ var FileTokenStorageProvider = class {
102
244
  id = "file-token-storage";
103
245
  name = "File Token Storage";
104
246
  type = "local";
105
- tokensDir;
247
+ baseTokensDir;
106
248
  status = "disconnected";
107
249
  identity = null;
108
250
  constructor(config) {
109
- this.tokensDir = typeof config === "string" ? config : config.tokensDir;
251
+ this.baseTokensDir = typeof config === "string" ? config : config.tokensDir;
110
252
  }
111
253
  setIdentity(identity) {
112
254
  this.identity = identity;
113
255
  }
256
+ /**
257
+ * Get tokens directory for current address
258
+ * Format: {baseTokensDir}/{addressId}/
259
+ */
260
+ get tokensDir() {
261
+ if (this.identity?.directAddress) {
262
+ const addressId = getAddressId(this.identity.directAddress);
263
+ return path2.join(this.baseTokensDir, addressId);
264
+ }
265
+ return this.baseTokensDir;
266
+ }
114
267
  async initialize() {
115
268
  if (!fs2.existsSync(this.tokensDir)) {
116
269
  fs2.mkdirSync(this.tokensDir, { recursive: true });
@@ -137,7 +290,7 @@ var FileTokenStorageProvider = class {
137
290
  const data = {
138
291
  _meta: {
139
292
  version: 1,
140
- address: this.identity?.address ?? "",
293
+ address: this.identity?.l1Address ?? "",
141
294
  formatVersion: "2.0",
142
295
  updatedAt: Date.now()
143
296
  }
@@ -251,21 +404,566 @@ function createFileTokenStorageProvider(config) {
251
404
  import WebSocket from "ws";
252
405
 
253
406
  // transport/NostrTransportProvider.ts
254
- import { Buffer } from "buffer";
407
+ import { Buffer as Buffer2 } from "buffer";
408
+
409
+ // node_modules/@noble/hashes/utils.js
410
+ function isBytes(a) {
411
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
412
+ }
413
+ function anumber(n, title = "") {
414
+ if (!Number.isSafeInteger(n) || n < 0) {
415
+ const prefix = title && `"${title}" `;
416
+ throw new Error(`${prefix}expected integer >= 0, got ${n}`);
417
+ }
418
+ }
419
+ function abytes(value, length, title = "") {
420
+ const bytes = isBytes(value);
421
+ const len = value?.length;
422
+ const needsLen = length !== void 0;
423
+ if (!bytes || needsLen && len !== length) {
424
+ const prefix = title && `"${title}" `;
425
+ const ofLen = needsLen ? ` of length ${length}` : "";
426
+ const got = bytes ? `length=${len}` : `type=${typeof value}`;
427
+ throw new Error(prefix + "expected Uint8Array" + ofLen + ", got " + got);
428
+ }
429
+ return value;
430
+ }
431
+ function ahash(h) {
432
+ if (typeof h !== "function" || typeof h.create !== "function")
433
+ throw new Error("Hash must wrapped by utils.createHasher");
434
+ anumber(h.outputLen);
435
+ anumber(h.blockLen);
436
+ }
437
+ function aexists(instance, checkFinished = true) {
438
+ if (instance.destroyed)
439
+ throw new Error("Hash instance has been destroyed");
440
+ if (checkFinished && instance.finished)
441
+ throw new Error("Hash#digest() has already been called");
442
+ }
443
+ function aoutput(out, instance) {
444
+ abytes(out, void 0, "digestInto() output");
445
+ const min = instance.outputLen;
446
+ if (out.length < min) {
447
+ throw new Error('"digestInto() output" expected to be of length >=' + min);
448
+ }
449
+ }
450
+ function clean(...arrays) {
451
+ for (let i = 0; i < arrays.length; i++) {
452
+ arrays[i].fill(0);
453
+ }
454
+ }
455
+ function createView(arr) {
456
+ return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
457
+ }
458
+ function rotr(word, shift) {
459
+ return word << 32 - shift | word >>> shift;
460
+ }
461
+ function createHasher(hashCons, info = {}) {
462
+ const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
463
+ const tmp = hashCons(void 0);
464
+ hashC.outputLen = tmp.outputLen;
465
+ hashC.blockLen = tmp.blockLen;
466
+ hashC.create = (opts) => hashCons(opts);
467
+ Object.assign(hashC, info);
468
+ return Object.freeze(hashC);
469
+ }
470
+ var oidNist = (suffix) => ({
471
+ oid: Uint8Array.from([6, 9, 96, 134, 72, 1, 101, 3, 4, 2, suffix])
472
+ });
473
+
474
+ // node_modules/@noble/hashes/hmac.js
475
+ var _HMAC = class {
476
+ oHash;
477
+ iHash;
478
+ blockLen;
479
+ outputLen;
480
+ finished = false;
481
+ destroyed = false;
482
+ constructor(hash, key) {
483
+ ahash(hash);
484
+ abytes(key, void 0, "key");
485
+ this.iHash = hash.create();
486
+ if (typeof this.iHash.update !== "function")
487
+ throw new Error("Expected instance of class which extends utils.Hash");
488
+ this.blockLen = this.iHash.blockLen;
489
+ this.outputLen = this.iHash.outputLen;
490
+ const blockLen = this.blockLen;
491
+ const pad = new Uint8Array(blockLen);
492
+ pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
493
+ for (let i = 0; i < pad.length; i++)
494
+ pad[i] ^= 54;
495
+ this.iHash.update(pad);
496
+ this.oHash = hash.create();
497
+ for (let i = 0; i < pad.length; i++)
498
+ pad[i] ^= 54 ^ 92;
499
+ this.oHash.update(pad);
500
+ clean(pad);
501
+ }
502
+ update(buf) {
503
+ aexists(this);
504
+ this.iHash.update(buf);
505
+ return this;
506
+ }
507
+ digestInto(out) {
508
+ aexists(this);
509
+ abytes(out, this.outputLen, "output");
510
+ this.finished = true;
511
+ this.iHash.digestInto(out);
512
+ this.oHash.update(out);
513
+ this.oHash.digestInto(out);
514
+ this.destroy();
515
+ }
516
+ digest() {
517
+ const out = new Uint8Array(this.oHash.outputLen);
518
+ this.digestInto(out);
519
+ return out;
520
+ }
521
+ _cloneInto(to) {
522
+ to ||= Object.create(Object.getPrototypeOf(this), {});
523
+ const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
524
+ to = to;
525
+ to.finished = finished;
526
+ to.destroyed = destroyed;
527
+ to.blockLen = blockLen;
528
+ to.outputLen = outputLen;
529
+ to.oHash = oHash._cloneInto(to.oHash);
530
+ to.iHash = iHash._cloneInto(to.iHash);
531
+ return to;
532
+ }
533
+ clone() {
534
+ return this._cloneInto();
535
+ }
536
+ destroy() {
537
+ this.destroyed = true;
538
+ this.oHash.destroy();
539
+ this.iHash.destroy();
540
+ }
541
+ };
542
+ var hmac = (hash, key, message) => new _HMAC(hash, key).update(message).digest();
543
+ hmac.create = (hash, key) => new _HMAC(hash, key);
544
+
545
+ // node_modules/@noble/hashes/hkdf.js
546
+ function extract(hash, ikm, salt) {
547
+ ahash(hash);
548
+ if (salt === void 0)
549
+ salt = new Uint8Array(hash.outputLen);
550
+ return hmac(hash, salt, ikm);
551
+ }
552
+ var HKDF_COUNTER = /* @__PURE__ */ Uint8Array.of(0);
553
+ var EMPTY_BUFFER = /* @__PURE__ */ Uint8Array.of();
554
+ function expand(hash, prk, info, length = 32) {
555
+ ahash(hash);
556
+ anumber(length, "length");
557
+ const olen = hash.outputLen;
558
+ if (length > 255 * olen)
559
+ throw new Error("Length must be <= 255*HashLen");
560
+ const blocks = Math.ceil(length / olen);
561
+ if (info === void 0)
562
+ info = EMPTY_BUFFER;
563
+ else
564
+ abytes(info, void 0, "info");
565
+ const okm = new Uint8Array(blocks * olen);
566
+ const HMAC = hmac.create(hash, prk);
567
+ const HMACTmp = HMAC._cloneInto();
568
+ const T = new Uint8Array(HMAC.outputLen);
569
+ for (let counter = 0; counter < blocks; counter++) {
570
+ HKDF_COUNTER[0] = counter + 1;
571
+ HMACTmp.update(counter === 0 ? EMPTY_BUFFER : T).update(info).update(HKDF_COUNTER).digestInto(T);
572
+ okm.set(T, olen * counter);
573
+ HMAC._cloneInto(HMACTmp);
574
+ }
575
+ HMAC.destroy();
576
+ HMACTmp.destroy();
577
+ clean(T, HKDF_COUNTER);
578
+ return okm.slice(0, length);
579
+ }
580
+ var hkdf = (hash, ikm, salt, info, length) => expand(hash, extract(hash, ikm, salt), info, length);
581
+
582
+ // node_modules/@noble/hashes/_md.js
583
+ function Chi(a, b, c) {
584
+ return a & b ^ ~a & c;
585
+ }
586
+ function Maj(a, b, c) {
587
+ return a & b ^ a & c ^ b & c;
588
+ }
589
+ var HashMD = class {
590
+ blockLen;
591
+ outputLen;
592
+ padOffset;
593
+ isLE;
594
+ // For partial updates less than block size
595
+ buffer;
596
+ view;
597
+ finished = false;
598
+ length = 0;
599
+ pos = 0;
600
+ destroyed = false;
601
+ constructor(blockLen, outputLen, padOffset, isLE) {
602
+ this.blockLen = blockLen;
603
+ this.outputLen = outputLen;
604
+ this.padOffset = padOffset;
605
+ this.isLE = isLE;
606
+ this.buffer = new Uint8Array(blockLen);
607
+ this.view = createView(this.buffer);
608
+ }
609
+ update(data) {
610
+ aexists(this);
611
+ abytes(data);
612
+ const { view, buffer, blockLen } = this;
613
+ const len = data.length;
614
+ for (let pos = 0; pos < len; ) {
615
+ const take = Math.min(blockLen - this.pos, len - pos);
616
+ if (take === blockLen) {
617
+ const dataView = createView(data);
618
+ for (; blockLen <= len - pos; pos += blockLen)
619
+ this.process(dataView, pos);
620
+ continue;
621
+ }
622
+ buffer.set(data.subarray(pos, pos + take), this.pos);
623
+ this.pos += take;
624
+ pos += take;
625
+ if (this.pos === blockLen) {
626
+ this.process(view, 0);
627
+ this.pos = 0;
628
+ }
629
+ }
630
+ this.length += data.length;
631
+ this.roundClean();
632
+ return this;
633
+ }
634
+ digestInto(out) {
635
+ aexists(this);
636
+ aoutput(out, this);
637
+ this.finished = true;
638
+ const { buffer, view, blockLen, isLE } = this;
639
+ let { pos } = this;
640
+ buffer[pos++] = 128;
641
+ clean(this.buffer.subarray(pos));
642
+ if (this.padOffset > blockLen - pos) {
643
+ this.process(view, 0);
644
+ pos = 0;
645
+ }
646
+ for (let i = pos; i < blockLen; i++)
647
+ buffer[i] = 0;
648
+ view.setBigUint64(blockLen - 8, BigInt(this.length * 8), isLE);
649
+ this.process(view, 0);
650
+ const oview = createView(out);
651
+ const len = this.outputLen;
652
+ if (len % 4)
653
+ throw new Error("_sha2: outputLen must be aligned to 32bit");
654
+ const outLen = len / 4;
655
+ const state = this.get();
656
+ if (outLen > state.length)
657
+ throw new Error("_sha2: outputLen bigger than state");
658
+ for (let i = 0; i < outLen; i++)
659
+ oview.setUint32(4 * i, state[i], isLE);
660
+ }
661
+ digest() {
662
+ const { buffer, outputLen } = this;
663
+ this.digestInto(buffer);
664
+ const res = buffer.slice(0, outputLen);
665
+ this.destroy();
666
+ return res;
667
+ }
668
+ _cloneInto(to) {
669
+ to ||= new this.constructor();
670
+ to.set(...this.get());
671
+ const { blockLen, buffer, length, finished, destroyed, pos } = this;
672
+ to.destroyed = destroyed;
673
+ to.finished = finished;
674
+ to.length = length;
675
+ to.pos = pos;
676
+ if (length % blockLen)
677
+ to.buffer.set(buffer);
678
+ return to;
679
+ }
680
+ clone() {
681
+ return this._cloneInto();
682
+ }
683
+ };
684
+ var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
685
+ 1779033703,
686
+ 3144134277,
687
+ 1013904242,
688
+ 2773480762,
689
+ 1359893119,
690
+ 2600822924,
691
+ 528734635,
692
+ 1541459225
693
+ ]);
694
+
695
+ // node_modules/@noble/hashes/sha2.js
696
+ var SHA256_K = /* @__PURE__ */ Uint32Array.from([
697
+ 1116352408,
698
+ 1899447441,
699
+ 3049323471,
700
+ 3921009573,
701
+ 961987163,
702
+ 1508970993,
703
+ 2453635748,
704
+ 2870763221,
705
+ 3624381080,
706
+ 310598401,
707
+ 607225278,
708
+ 1426881987,
709
+ 1925078388,
710
+ 2162078206,
711
+ 2614888103,
712
+ 3248222580,
713
+ 3835390401,
714
+ 4022224774,
715
+ 264347078,
716
+ 604807628,
717
+ 770255983,
718
+ 1249150122,
719
+ 1555081692,
720
+ 1996064986,
721
+ 2554220882,
722
+ 2821834349,
723
+ 2952996808,
724
+ 3210313671,
725
+ 3336571891,
726
+ 3584528711,
727
+ 113926993,
728
+ 338241895,
729
+ 666307205,
730
+ 773529912,
731
+ 1294757372,
732
+ 1396182291,
733
+ 1695183700,
734
+ 1986661051,
735
+ 2177026350,
736
+ 2456956037,
737
+ 2730485921,
738
+ 2820302411,
739
+ 3259730800,
740
+ 3345764771,
741
+ 3516065817,
742
+ 3600352804,
743
+ 4094571909,
744
+ 275423344,
745
+ 430227734,
746
+ 506948616,
747
+ 659060556,
748
+ 883997877,
749
+ 958139571,
750
+ 1322822218,
751
+ 1537002063,
752
+ 1747873779,
753
+ 1955562222,
754
+ 2024104815,
755
+ 2227730452,
756
+ 2361852424,
757
+ 2428436474,
758
+ 2756734187,
759
+ 3204031479,
760
+ 3329325298
761
+ ]);
762
+ var SHA256_W = /* @__PURE__ */ new Uint32Array(64);
763
+ var SHA2_32B = class extends HashMD {
764
+ constructor(outputLen) {
765
+ super(64, outputLen, 8, false);
766
+ }
767
+ get() {
768
+ const { A, B, C, D, E, F, G, H } = this;
769
+ return [A, B, C, D, E, F, G, H];
770
+ }
771
+ // prettier-ignore
772
+ set(A, B, C, D, E, F, G, H) {
773
+ this.A = A | 0;
774
+ this.B = B | 0;
775
+ this.C = C | 0;
776
+ this.D = D | 0;
777
+ this.E = E | 0;
778
+ this.F = F | 0;
779
+ this.G = G | 0;
780
+ this.H = H | 0;
781
+ }
782
+ process(view, offset) {
783
+ for (let i = 0; i < 16; i++, offset += 4)
784
+ SHA256_W[i] = view.getUint32(offset, false);
785
+ for (let i = 16; i < 64; i++) {
786
+ const W15 = SHA256_W[i - 15];
787
+ const W2 = SHA256_W[i - 2];
788
+ const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
789
+ const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
790
+ SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
791
+ }
792
+ let { A, B, C, D, E, F, G, H } = this;
793
+ for (let i = 0; i < 64; i++) {
794
+ const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
795
+ const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
796
+ const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
797
+ const T2 = sigma0 + Maj(A, B, C) | 0;
798
+ H = G;
799
+ G = F;
800
+ F = E;
801
+ E = D + T1 | 0;
802
+ D = C;
803
+ C = B;
804
+ B = A;
805
+ A = T1 + T2 | 0;
806
+ }
807
+ A = A + this.A | 0;
808
+ B = B + this.B | 0;
809
+ C = C + this.C | 0;
810
+ D = D + this.D | 0;
811
+ E = E + this.E | 0;
812
+ F = F + this.F | 0;
813
+ G = G + this.G | 0;
814
+ H = H + this.H | 0;
815
+ this.set(A, B, C, D, E, F, G, H);
816
+ }
817
+ roundClean() {
818
+ clean(SHA256_W);
819
+ }
820
+ destroy() {
821
+ this.set(0, 0, 0, 0, 0, 0, 0, 0);
822
+ clean(this.buffer);
823
+ }
824
+ };
825
+ var _SHA256 = class extends SHA2_32B {
826
+ // We cannot use array here since array allows indexing by variable
827
+ // which means optimizer/compiler cannot use registers.
828
+ A = SHA256_IV[0] | 0;
829
+ B = SHA256_IV[1] | 0;
830
+ C = SHA256_IV[2] | 0;
831
+ D = SHA256_IV[3] | 0;
832
+ E = SHA256_IV[4] | 0;
833
+ F = SHA256_IV[5] | 0;
834
+ G = SHA256_IV[6] | 0;
835
+ H = SHA256_IV[7] | 0;
836
+ constructor() {
837
+ super(32);
838
+ }
839
+ };
840
+ var sha256 = /* @__PURE__ */ createHasher(
841
+ () => new _SHA256(),
842
+ /* @__PURE__ */ oidNist(1)
843
+ );
844
+
845
+ // transport/NostrTransportProvider.ts
255
846
  import {
256
847
  NostrKeyManager,
257
848
  NIP04,
849
+ NIP17,
258
850
  Event as NostrEventClass,
259
- hashNametag
851
+ EventKinds,
852
+ hashNametag,
853
+ NostrClient,
854
+ Filter
260
855
  } from "@unicitylabs/nostr-js-sdk";
261
856
 
857
+ // core/crypto.ts
858
+ import * as bip39 from "bip39";
859
+ import CryptoJS from "crypto-js";
860
+ import elliptic from "elliptic";
861
+
862
+ // core/bech32.ts
863
+ var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
864
+ var GENERATOR = [996825010, 642813549, 513874426, 1027748829, 705979059];
865
+ function convertBits(data, fromBits, toBits, pad) {
866
+ let acc = 0;
867
+ let bits = 0;
868
+ const ret = [];
869
+ const maxv = (1 << toBits) - 1;
870
+ for (let i = 0; i < data.length; i++) {
871
+ const value = data[i];
872
+ if (value < 0 || value >> fromBits !== 0) return null;
873
+ acc = acc << fromBits | value;
874
+ bits += fromBits;
875
+ while (bits >= toBits) {
876
+ bits -= toBits;
877
+ ret.push(acc >> bits & maxv);
878
+ }
879
+ }
880
+ if (pad) {
881
+ if (bits > 0) {
882
+ ret.push(acc << toBits - bits & maxv);
883
+ }
884
+ } else if (bits >= fromBits || acc << toBits - bits & maxv) {
885
+ return null;
886
+ }
887
+ return ret;
888
+ }
889
+ function hrpExpand(hrp) {
890
+ const ret = [];
891
+ for (let i = 0; i < hrp.length; i++) ret.push(hrp.charCodeAt(i) >> 5);
892
+ ret.push(0);
893
+ for (let i = 0; i < hrp.length; i++) ret.push(hrp.charCodeAt(i) & 31);
894
+ return ret;
895
+ }
896
+ function bech32Polymod(values) {
897
+ let chk = 1;
898
+ for (let p = 0; p < values.length; p++) {
899
+ const top = chk >> 25;
900
+ chk = (chk & 33554431) << 5 ^ values[p];
901
+ for (let i = 0; i < 5; i++) {
902
+ if (top >> i & 1) chk ^= GENERATOR[i];
903
+ }
904
+ }
905
+ return chk;
906
+ }
907
+ function bech32Checksum(hrp, data) {
908
+ const values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
909
+ const mod = bech32Polymod(values) ^ 1;
910
+ const ret = [];
911
+ for (let p = 0; p < 6; p++) {
912
+ ret.push(mod >> 5 * (5 - p) & 31);
913
+ }
914
+ return ret;
915
+ }
916
+ function encodeBech32(hrp, version, program) {
917
+ if (version < 0 || version > 16) {
918
+ throw new Error("Invalid witness version");
919
+ }
920
+ const converted = convertBits(Array.from(program), 8, 5, true);
921
+ if (!converted) {
922
+ throw new Error("Failed to convert bits");
923
+ }
924
+ const data = [version].concat(converted);
925
+ const checksum = bech32Checksum(hrp, data);
926
+ const combined = data.concat(checksum);
927
+ let out = hrp + "1";
928
+ for (let i = 0; i < combined.length; i++) {
929
+ out += CHARSET[combined[i]];
930
+ }
931
+ return out;
932
+ }
933
+
934
+ // core/crypto.ts
935
+ var ec = new elliptic.ec("secp256k1");
936
+ var CURVE_ORDER = BigInt(
937
+ "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
938
+ );
939
+ function getPublicKey(privateKey, compressed = true) {
940
+ const keyPair = ec.keyFromPrivate(privateKey, "hex");
941
+ return keyPair.getPublic(compressed, "hex");
942
+ }
943
+ function sha2562(data, inputEncoding = "hex") {
944
+ const parsed = inputEncoding === "hex" ? CryptoJS.enc.Hex.parse(data) : CryptoJS.enc.Utf8.parse(data);
945
+ return CryptoJS.SHA256(parsed).toString();
946
+ }
947
+ function ripemd160(data, inputEncoding = "hex") {
948
+ const parsed = inputEncoding === "hex" ? CryptoJS.enc.Hex.parse(data) : CryptoJS.enc.Utf8.parse(data);
949
+ return CryptoJS.RIPEMD160(parsed).toString();
950
+ }
951
+ function hash160(data) {
952
+ const sha = sha2562(data, "hex");
953
+ return ripemd160(sha, "hex");
954
+ }
955
+ function hash160ToBytes(hash160Hex) {
956
+ const matches = hash160Hex.match(/../g);
957
+ if (!matches) return new Uint8Array(0);
958
+ return Uint8Array.from(matches.map((x) => parseInt(x, 16)));
959
+ }
960
+ function publicKeyToAddress(publicKey, prefix = "alpha", witnessVersion = 0) {
961
+ const pubKeyHash = hash160(publicKey);
962
+ const programBytes = hash160ToBytes(pubKeyHash);
963
+ return encodeBech32(prefix, witnessVersion, programBytes);
964
+ }
965
+
262
966
  // transport/websocket.ts
263
- var WebSocketReadyState = {
264
- CONNECTING: 0,
265
- OPEN: 1,
266
- CLOSING: 2,
267
- CLOSED: 3
268
- };
269
967
  function defaultUUIDGenerator() {
270
968
  if (typeof crypto !== "undefined" && crypto.randomUUID) {
271
969
  return crypto.randomUUID();
@@ -277,124 +975,61 @@ function defaultUUIDGenerator() {
277
975
  });
278
976
  }
279
977
 
280
- // constants.ts
281
- var STORAGE_PREFIX = "sphere_";
282
- var STORAGE_KEYS = {
283
- /** Encrypted BIP39 mnemonic */
284
- MNEMONIC: `${STORAGE_PREFIX}mnemonic`,
285
- /** Encrypted master private key */
286
- MASTER_KEY: `${STORAGE_PREFIX}master_key`,
287
- /** BIP32 chain code */
288
- CHAIN_CODE: `${STORAGE_PREFIX}chain_code`,
289
- /** HD derivation path (full path like m/44'/0'/0'/0/0) */
290
- DERIVATION_PATH: `${STORAGE_PREFIX}derivation_path`,
291
- /** Base derivation path (like m/44'/0'/0' without chain/index) */
292
- BASE_PATH: `${STORAGE_PREFIX}base_path`,
293
- /** Derivation mode: bip32, wif_hmac, legacy_hmac */
294
- DERIVATION_MODE: `${STORAGE_PREFIX}derivation_mode`,
295
- /** Wallet source: mnemonic, file, unknown */
296
- WALLET_SOURCE: `${STORAGE_PREFIX}wallet_source`,
297
- /** Wallet existence flag */
298
- WALLET_EXISTS: `${STORAGE_PREFIX}wallet_exists`,
299
- /** Registered nametag (legacy - single address) */
300
- NAMETAG: `${STORAGE_PREFIX}nametag`,
301
- /** Current active address index */
302
- CURRENT_ADDRESS_INDEX: `${STORAGE_PREFIX}current_address_index`,
303
- /** Address nametags map (JSON: { "0": "alice", "1": "bob" }) */
304
- ADDRESS_NAMETAGS: `${STORAGE_PREFIX}address_nametags`,
305
- /** Token data */
306
- TOKENS: `${STORAGE_PREFIX}tokens`,
307
- /** Pending transfers */
308
- PENDING_TRANSFERS: `${STORAGE_PREFIX}pending_transfers`,
309
- /** Transfer outbox */
310
- OUTBOX: `${STORAGE_PREFIX}outbox`,
311
- /** Conversations */
312
- CONVERSATIONS: `${STORAGE_PREFIX}conversations`,
313
- /** Messages */
314
- MESSAGES: `${STORAGE_PREFIX}messages`,
315
- /** Transaction history */
316
- TRANSACTION_HISTORY: `${STORAGE_PREFIX}transaction_history`,
317
- /** Archived tokens (spent token history) */
318
- ARCHIVED_TOKENS: `${STORAGE_PREFIX}archived_tokens`,
319
- /** Tombstones (records of deleted/spent tokens) */
320
- TOMBSTONES: `${STORAGE_PREFIX}tombstones`,
321
- /** Forked tokens (alternative histories) */
322
- FORKED_TOKENS: `${STORAGE_PREFIX}forked_tokens`
323
- };
324
- var DEFAULT_NOSTR_RELAYS = [
325
- "wss://relay.unicity.network",
326
- "wss://relay.damus.io",
327
- "wss://nos.lol",
328
- "wss://relay.nostr.band"
329
- ];
330
- var NOSTR_EVENT_KINDS = {
331
- /** NIP-04 encrypted direct message */
332
- DIRECT_MESSAGE: 4,
333
- /** Token transfer (Unicity custom - 31113) */
334
- TOKEN_TRANSFER: 31113,
335
- /** Payment request (Unicity custom - 31115) */
336
- PAYMENT_REQUEST: 31115,
337
- /** Payment request response (Unicity custom - 31116) */
338
- PAYMENT_REQUEST_RESPONSE: 31116,
339
- /** Nametag binding (NIP-78 app-specific data) */
340
- NAMETAG_BINDING: 30078,
341
- /** Public broadcast */
342
- BROADCAST: 1
343
- };
344
- var DEFAULT_AGGREGATOR_URL = "https://aggregator.unicity.network/rpc";
345
- var DEV_AGGREGATOR_URL = "https://dev-aggregator.dyndns.org/rpc";
346
- var TEST_AGGREGATOR_URL = "https://goggregator-test.unicity.network";
347
- var DEFAULT_AGGREGATOR_TIMEOUT = 3e4;
348
- var DEFAULT_IPFS_GATEWAYS = [
349
- "https://ipfs.unicity.network",
350
- "https://dweb.link",
351
- "https://ipfs.io"
352
- ];
353
- var DEFAULT_BASE_PATH = "m/44'/0'/0'";
354
- var DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0`;
355
- var DEFAULT_ELECTRUM_URL = "wss://fulcrum.alpha.unicity.network:50004";
356
- var TEST_ELECTRUM_URL = "wss://fulcrum.alpha.testnet.unicity.network:50004";
357
- var TEST_NOSTR_RELAYS = [
358
- "wss://nostr-relay.testnet.unicity.network"
359
- ];
360
- var NETWORKS = {
361
- mainnet: {
362
- name: "Mainnet",
363
- aggregatorUrl: DEFAULT_AGGREGATOR_URL,
364
- nostrRelays: DEFAULT_NOSTR_RELAYS,
365
- ipfsGateways: DEFAULT_IPFS_GATEWAYS,
366
- electrumUrl: DEFAULT_ELECTRUM_URL
367
- },
368
- testnet: {
369
- name: "Testnet",
370
- aggregatorUrl: TEST_AGGREGATOR_URL,
371
- nostrRelays: TEST_NOSTR_RELAYS,
372
- ipfsGateways: DEFAULT_IPFS_GATEWAYS,
373
- electrumUrl: TEST_ELECTRUM_URL
374
- },
375
- dev: {
376
- name: "Development",
377
- aggregatorUrl: DEV_AGGREGATOR_URL,
378
- nostrRelays: TEST_NOSTR_RELAYS,
379
- ipfsGateways: DEFAULT_IPFS_GATEWAYS,
380
- electrumUrl: TEST_ELECTRUM_URL
381
- }
382
- };
383
- var TIMEOUTS = {
384
- /** WebSocket connection timeout */
385
- WEBSOCKET_CONNECT: 1e4,
386
- /** Nostr relay reconnect delay */
387
- NOSTR_RECONNECT_DELAY: 3e3,
388
- /** Max reconnect attempts */
389
- MAX_RECONNECT_ATTEMPTS: 5,
390
- /** Proof polling interval */
391
- PROOF_POLL_INTERVAL: 1e3,
392
- /** Sync interval */
393
- SYNC_INTERVAL: 6e4
394
- };
395
-
396
978
  // transport/NostrTransportProvider.ts
397
979
  var EVENT_KINDS = NOSTR_EVENT_KINDS;
980
+ function deriveNametagEncryptionKey(privateKeyHex) {
981
+ const privateKeyBytes = Buffer2.from(privateKeyHex, "hex");
982
+ const saltInput = new TextEncoder().encode("sphere-nametag-salt");
983
+ const salt = sha256(saltInput);
984
+ const info = new TextEncoder().encode("nametag-encryption");
985
+ return hkdf(sha256, privateKeyBytes, salt, info, 32);
986
+ }
987
+ async function encryptNametag(nametag, privateKeyHex) {
988
+ const key = deriveNametagEncryptionKey(privateKeyHex);
989
+ const iv = crypto.getRandomValues(new Uint8Array(12));
990
+ const encoder = new TextEncoder();
991
+ const data = encoder.encode(nametag);
992
+ const cryptoKey = await crypto.subtle.importKey(
993
+ "raw",
994
+ new Uint8Array(key).buffer,
995
+ { name: "AES-GCM" },
996
+ false,
997
+ ["encrypt"]
998
+ );
999
+ const encrypted = await crypto.subtle.encrypt(
1000
+ { name: "AES-GCM", iv: new Uint8Array(iv).buffer },
1001
+ cryptoKey,
1002
+ new Uint8Array(data).buffer
1003
+ );
1004
+ const combined = new Uint8Array(iv.length + encrypted.byteLength);
1005
+ combined.set(iv, 0);
1006
+ combined.set(new Uint8Array(encrypted), iv.length);
1007
+ return Buffer2.from(combined).toString("base64");
1008
+ }
1009
+ async function decryptNametag(encryptedBase64, privateKeyHex) {
1010
+ try {
1011
+ const key = deriveNametagEncryptionKey(privateKeyHex);
1012
+ const combined = Buffer2.from(encryptedBase64, "base64");
1013
+ const iv = combined.slice(0, 12);
1014
+ const ciphertext = combined.slice(12);
1015
+ const cryptoKey = await crypto.subtle.importKey(
1016
+ "raw",
1017
+ new Uint8Array(key).buffer,
1018
+ { name: "AES-GCM" },
1019
+ false,
1020
+ ["decrypt"]
1021
+ );
1022
+ const decrypted = await crypto.subtle.decrypt(
1023
+ { name: "AES-GCM", iv: new Uint8Array(iv).buffer },
1024
+ cryptoKey,
1025
+ new Uint8Array(ciphertext).buffer
1026
+ );
1027
+ const decoder = new TextDecoder();
1028
+ return decoder.decode(decrypted);
1029
+ } catch {
1030
+ return null;
1031
+ }
1032
+ }
398
1033
  var NostrTransportProvider = class {
399
1034
  id = "nostr";
400
1035
  name = "Nostr Transport";
@@ -404,9 +1039,10 @@ var NostrTransportProvider = class {
404
1039
  identity = null;
405
1040
  keyManager = null;
406
1041
  status = "disconnected";
407
- // WebSocket connections to relays
408
- connections = /* @__PURE__ */ new Map();
409
- reconnectAttempts = /* @__PURE__ */ new Map();
1042
+ // NostrClient from nostr-js-sdk handles all WebSocket management,
1043
+ // keepalive pings, reconnection, and NIP-42 authentication
1044
+ nostrClient = null;
1045
+ mainSubscriptionId = null;
410
1046
  // Event handlers
411
1047
  messageHandlers = /* @__PURE__ */ new Set();
412
1048
  transferHandlers = /* @__PURE__ */ new Set();
@@ -414,9 +1050,6 @@ var NostrTransportProvider = class {
414
1050
  paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
415
1051
  broadcastHandlers = /* @__PURE__ */ new Map();
416
1052
  eventCallbacks = /* @__PURE__ */ new Set();
417
- // Subscriptions
418
- subscriptions = /* @__PURE__ */ new Map();
419
- // subId -> relays
420
1053
  constructor(config) {
421
1054
  this.config = {
422
1055
  relays: config.relays ?? [...DEFAULT_NOSTR_RELAYS],
@@ -436,16 +1069,43 @@ var NostrTransportProvider = class {
436
1069
  if (this.status === "connected") return;
437
1070
  this.status = "connecting";
438
1071
  try {
439
- const connectPromises = this.config.relays.map(
440
- (relay) => this.connectToRelay(relay)
441
- );
442
- await Promise.allSettled(connectPromises);
443
- if (this.connections.size === 0) {
1072
+ if (!this.keyManager) {
1073
+ const tempKey = Buffer2.alloc(32);
1074
+ crypto.getRandomValues(tempKey);
1075
+ this.keyManager = NostrKeyManager.fromPrivateKey(tempKey);
1076
+ }
1077
+ this.nostrClient = new NostrClient(this.keyManager, {
1078
+ autoReconnect: this.config.autoReconnect,
1079
+ reconnectIntervalMs: this.config.reconnectDelay,
1080
+ maxReconnectIntervalMs: this.config.reconnectDelay * 16,
1081
+ // exponential backoff cap
1082
+ pingIntervalMs: 15e3
1083
+ // 15 second keepalive pings (more aggressive to prevent drops)
1084
+ });
1085
+ this.nostrClient.addConnectionListener({
1086
+ onConnect: (url) => {
1087
+ this.log("NostrClient connected to relay:", url);
1088
+ this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
1089
+ },
1090
+ onDisconnect: (url, reason) => {
1091
+ this.log("NostrClient disconnected from relay:", url, "reason:", reason);
1092
+ },
1093
+ onReconnecting: (url, attempt) => {
1094
+ this.log("NostrClient reconnecting to relay:", url, "attempt:", attempt);
1095
+ this.emitEvent({ type: "transport:reconnecting", timestamp: Date.now() });
1096
+ },
1097
+ onReconnected: (url) => {
1098
+ this.log("NostrClient reconnected to relay:", url);
1099
+ this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
1100
+ }
1101
+ });
1102
+ await this.nostrClient.connect(...this.config.relays);
1103
+ if (!this.nostrClient.isConnected()) {
444
1104
  throw new Error("Failed to connect to any relay");
445
1105
  }
446
1106
  this.status = "connected";
447
1107
  this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
448
- this.log("Connected to", this.connections.size, "relays");
1108
+ this.log("Connected to", this.nostrClient.getConnectedRelays().size, "relays");
449
1109
  if (this.identity) {
450
1110
  this.subscribeToEvents();
451
1111
  }
@@ -455,17 +1115,19 @@ var NostrTransportProvider = class {
455
1115
  }
456
1116
  }
457
1117
  async disconnect() {
458
- for (const [url, ws] of this.connections) {
459
- ws.close();
460
- this.connections.delete(url);
1118
+ if (this.nostrClient) {
1119
+ this.nostrClient.disconnect();
1120
+ this.nostrClient = null;
461
1121
  }
462
- this.subscriptions.clear();
1122
+ this.mainSubscriptionId = null;
1123
+ this.walletSubscriptionId = null;
1124
+ this.chatSubscriptionId = null;
463
1125
  this.status = "disconnected";
464
1126
  this.emitEvent({ type: "transport:disconnected", timestamp: Date.now() });
465
1127
  this.log("Disconnected from all relays");
466
1128
  }
467
1129
  isConnected() {
468
- return this.status === "connected" && this.connections.size > 0;
1130
+ return this.status === "connected" && this.nostrClient?.isConnected() === true;
469
1131
  }
470
1132
  getStatus() {
471
1133
  return this.status;
@@ -483,7 +1145,8 @@ var NostrTransportProvider = class {
483
1145
  * Get list of currently connected relay URLs
484
1146
  */
485
1147
  getConnectedRelays() {
486
- return Array.from(this.connections.keys());
1148
+ if (!this.nostrClient) return [];
1149
+ return Array.from(this.nostrClient.getConnectedRelays());
487
1150
  }
488
1151
  /**
489
1152
  * Add a new relay dynamically
@@ -495,9 +1158,9 @@ var NostrTransportProvider = class {
495
1158
  return false;
496
1159
  }
497
1160
  this.config.relays.push(relayUrl);
498
- if (this.status === "connected") {
1161
+ if (this.status === "connected" && this.nostrClient) {
499
1162
  try {
500
- await this.connectToRelay(relayUrl);
1163
+ await this.nostrClient.connect(relayUrl);
501
1164
  this.log("Added and connected to relay:", relayUrl);
502
1165
  this.emitEvent({
503
1166
  type: "transport:relay_added",
@@ -525,6 +1188,8 @@ var NostrTransportProvider = class {
525
1188
  /**
526
1189
  * Remove a relay dynamically
527
1190
  * Will disconnect from the relay if connected
1191
+ * NOTE: NostrClient doesn't support removing individual relays at runtime.
1192
+ * We remove from config so it won't be used on next connect().
528
1193
  */
529
1194
  async removeRelay(relayUrl) {
530
1195
  const index = this.config.relays.indexOf(relayUrl);
@@ -533,19 +1198,13 @@ var NostrTransportProvider = class {
533
1198
  return false;
534
1199
  }
535
1200
  this.config.relays.splice(index, 1);
536
- const ws = this.connections.get(relayUrl);
537
- if (ws) {
538
- ws.close();
539
- this.connections.delete(relayUrl);
540
- this.reconnectAttempts.delete(relayUrl);
541
- this.log("Removed and disconnected from relay:", relayUrl);
542
- }
1201
+ this.log("Removed relay from config:", relayUrl);
543
1202
  this.emitEvent({
544
1203
  type: "transport:relay_removed",
545
1204
  timestamp: Date.now(),
546
1205
  data: { relay: relayUrl }
547
1206
  });
548
- if (this.connections.size === 0 && this.status === "connected") {
1207
+ if (this.nostrClient && !this.nostrClient.isConnected() && this.status === "connected") {
549
1208
  this.status = "error";
550
1209
  this.emitEvent({
551
1210
  type: "transport:error",
@@ -565,19 +1224,49 @@ var NostrTransportProvider = class {
565
1224
  * Check if a relay is currently connected
566
1225
  */
567
1226
  isRelayConnected(relayUrl) {
568
- const ws = this.connections.get(relayUrl);
569
- return ws !== void 0 && ws.readyState === WebSocketReadyState.OPEN;
1227
+ if (!this.nostrClient) return false;
1228
+ return this.nostrClient.getConnectedRelays().has(relayUrl);
570
1229
  }
571
1230
  // ===========================================================================
572
1231
  // TransportProvider Implementation
573
1232
  // ===========================================================================
574
1233
  setIdentity(identity) {
575
1234
  this.identity = identity;
576
- const secretKey = Buffer.from(identity.privateKey, "hex");
1235
+ const secretKey = Buffer2.from(identity.privateKey, "hex");
577
1236
  this.keyManager = NostrKeyManager.fromPrivateKey(secretKey);
578
1237
  const nostrPubkey = this.keyManager.getPublicKeyHex();
579
1238
  this.log("Identity set, Nostr pubkey:", nostrPubkey.slice(0, 16) + "...");
580
- if (this.isConnected()) {
1239
+ if (this.nostrClient && this.status === "connected") {
1240
+ this.log("Identity changed while connected - recreating NostrClient");
1241
+ const oldClient = this.nostrClient;
1242
+ this.nostrClient = new NostrClient(this.keyManager, {
1243
+ autoReconnect: this.config.autoReconnect,
1244
+ reconnectIntervalMs: this.config.reconnectDelay,
1245
+ maxReconnectIntervalMs: this.config.reconnectDelay * 16,
1246
+ pingIntervalMs: 15e3
1247
+ // 15 second keepalive pings
1248
+ });
1249
+ this.nostrClient.addConnectionListener({
1250
+ onConnect: (url) => {
1251
+ this.log("NostrClient connected to relay:", url);
1252
+ },
1253
+ onDisconnect: (url, reason) => {
1254
+ this.log("NostrClient disconnected from relay:", url, "reason:", reason);
1255
+ },
1256
+ onReconnecting: (url, attempt) => {
1257
+ this.log("NostrClient reconnecting to relay:", url, "attempt:", attempt);
1258
+ },
1259
+ onReconnected: (url) => {
1260
+ this.log("NostrClient reconnected to relay:", url);
1261
+ }
1262
+ });
1263
+ this.nostrClient.connect(...this.config.relays).then(() => {
1264
+ this.subscribeToEvents();
1265
+ oldClient.disconnect();
1266
+ }).catch((err) => {
1267
+ this.log("Failed to reconnect with new identity:", err);
1268
+ });
1269
+ } else if (this.isConnected()) {
581
1270
  this.subscribeToEvents();
582
1271
  }
583
1272
  }
@@ -593,18 +1282,17 @@ var NostrTransportProvider = class {
593
1282
  }
594
1283
  async sendMessage(recipientPubkey, content) {
595
1284
  this.ensureReady();
596
- const event = await this.createEncryptedEvent(
597
- EVENT_KINDS.DIRECT_MESSAGE,
598
- content,
599
- [["p", recipientPubkey]]
600
- );
601
- await this.publishEvent(event);
1285
+ const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
1286
+ const senderNametag = this.identity?.nametag;
1287
+ const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
1288
+ const giftWrap = NIP17.createGiftWrap(this.keyManager, nostrRecipient, wrappedContent);
1289
+ await this.publishEvent(giftWrap);
602
1290
  this.emitEvent({
603
1291
  type: "message:sent",
604
1292
  timestamp: Date.now(),
605
1293
  data: { recipient: recipientPubkey }
606
1294
  });
607
- return event.id;
1295
+ return giftWrap.id;
608
1296
  }
609
1297
  onMessage(handler) {
610
1298
  this.messageHandlers.add(handler);
@@ -721,6 +1409,118 @@ var NostrTransportProvider = class {
721
1409
  if (pubkeyTag?.[1]) return pubkeyTag[1];
722
1410
  return null;
723
1411
  }
1412
+ async resolveNametagInfo(nametag) {
1413
+ this.ensureReady();
1414
+ const hashedNametag = hashNametag(nametag);
1415
+ let events = await this.queryEvents({
1416
+ kinds: [EVENT_KINDS.NAMETAG_BINDING],
1417
+ "#t": [hashedNametag],
1418
+ limit: 1
1419
+ });
1420
+ if (events.length === 0) {
1421
+ events = await this.queryEvents({
1422
+ kinds: [EVENT_KINDS.NAMETAG_BINDING],
1423
+ "#d": [hashedNametag],
1424
+ limit: 1
1425
+ });
1426
+ }
1427
+ if (events.length === 0) return null;
1428
+ const bindingEvent = events[0];
1429
+ try {
1430
+ const content = JSON.parse(bindingEvent.content);
1431
+ if (content.public_key && content.l1_address) {
1432
+ const l3Address = `PROXY:${hashedNametag}`;
1433
+ return {
1434
+ nametag,
1435
+ transportPubkey: bindingEvent.pubkey,
1436
+ chainPubkey: content.public_key,
1437
+ l1Address: content.l1_address,
1438
+ directAddress: content.direct_address || "",
1439
+ proxyAddress: l3Address,
1440
+ timestamp: bindingEvent.created_at * 1e3
1441
+ };
1442
+ }
1443
+ this.log("Legacy nametag event without extended fields:", nametag);
1444
+ const pubkeyTag = bindingEvent.tags.find((t) => t[0] === "pubkey");
1445
+ const l1Tag = bindingEvent.tags.find((t) => t[0] === "l1");
1446
+ if (pubkeyTag?.[1] && l1Tag?.[1]) {
1447
+ const l3Address = `PROXY:${hashedNametag}`;
1448
+ return {
1449
+ nametag,
1450
+ transportPubkey: bindingEvent.pubkey,
1451
+ chainPubkey: pubkeyTag[1],
1452
+ l1Address: l1Tag[1],
1453
+ directAddress: "",
1454
+ proxyAddress: l3Address,
1455
+ timestamp: bindingEvent.created_at * 1e3
1456
+ };
1457
+ }
1458
+ return {
1459
+ nametag,
1460
+ transportPubkey: bindingEvent.pubkey,
1461
+ chainPubkey: "",
1462
+ // Cannot derive from 32-byte Nostr pubkey
1463
+ l1Address: "",
1464
+ // Cannot derive without 33-byte pubkey
1465
+ directAddress: "",
1466
+ proxyAddress: `PROXY:${hashedNametag}`,
1467
+ timestamp: bindingEvent.created_at * 1e3
1468
+ };
1469
+ } catch {
1470
+ return {
1471
+ nametag,
1472
+ transportPubkey: bindingEvent.pubkey,
1473
+ chainPubkey: "",
1474
+ l1Address: "",
1475
+ directAddress: "",
1476
+ proxyAddress: `PROXY:${hashedNametag}`,
1477
+ timestamp: bindingEvent.created_at * 1e3
1478
+ };
1479
+ }
1480
+ }
1481
+ /**
1482
+ * Recover nametag for the current identity by searching for encrypted nametag events
1483
+ * Used after wallet import to recover associated nametag
1484
+ * @returns Decrypted nametag or null if none found
1485
+ */
1486
+ async recoverNametag() {
1487
+ this.ensureReady();
1488
+ if (!this.identity || !this.keyManager) {
1489
+ throw new Error("Identity not set");
1490
+ }
1491
+ const nostrPubkey = this.getNostrPubkey();
1492
+ this.log("Searching for nametag events for pubkey:", nostrPubkey.slice(0, 16) + "...");
1493
+ const events = await this.queryEvents({
1494
+ kinds: [EVENT_KINDS.NAMETAG_BINDING],
1495
+ authors: [nostrPubkey],
1496
+ limit: 10
1497
+ // Get recent events in case of updates
1498
+ });
1499
+ if (events.length === 0) {
1500
+ this.log("No nametag events found for this pubkey");
1501
+ return null;
1502
+ }
1503
+ events.sort((a, b) => b.created_at - a.created_at);
1504
+ for (const event of events) {
1505
+ try {
1506
+ const content = JSON.parse(event.content);
1507
+ if (content.encrypted_nametag) {
1508
+ const decrypted = await decryptNametag(
1509
+ content.encrypted_nametag,
1510
+ this.identity.privateKey
1511
+ );
1512
+ if (decrypted) {
1513
+ this.log("Recovered nametag:", decrypted);
1514
+ return decrypted;
1515
+ }
1516
+ }
1517
+ } catch {
1518
+ continue;
1519
+ }
1520
+ }
1521
+ this.log("Could not decrypt nametag from any event");
1522
+ return null;
1523
+ }
724
1524
  async publishNametag(nametag, address) {
725
1525
  this.ensureReady();
726
1526
  const hashedNametag = hashNametag(nametag);
@@ -731,8 +1531,11 @@ var NostrTransportProvider = class {
731
1531
  await this.publishEvent(event);
732
1532
  this.log("Published nametag binding:", nametag);
733
1533
  }
734
- async registerNametag(nametag, _publicKey) {
1534
+ async registerNametag(nametag, _publicKey, directAddress = "") {
735
1535
  this.ensureReady();
1536
+ if (!this.identity) {
1537
+ throw new Error("Identity not set");
1538
+ }
736
1539
  const nostrPubkey = this.getNostrPubkey();
737
1540
  const existing = await this.resolveNametag(nametag);
738
1541
  this.log("registerNametag:", nametag, "existing:", existing, "myPubkey:", nostrPubkey);
@@ -740,27 +1543,42 @@ var NostrTransportProvider = class {
740
1543
  this.log("Nametag already taken:", nametag, "- owner:", existing);
741
1544
  return false;
742
1545
  }
1546
+ const privateKeyHex = this.identity.privateKey;
1547
+ const compressedPubkey = getPublicKey(privateKeyHex, true);
1548
+ const l1Address = publicKeyToAddress(compressedPubkey, "alpha");
1549
+ const encryptedNametag = await encryptNametag(nametag, privateKeyHex);
743
1550
  const hashedNametag = hashNametag(nametag);
744
1551
  const content = JSON.stringify({
745
1552
  nametag_hash: hashedNametag,
746
1553
  address: nostrPubkey,
747
- verified: Date.now()
1554
+ verified: Date.now(),
1555
+ // Extended fields for nametag recovery and address lookup
1556
+ encrypted_nametag: encryptedNametag,
1557
+ public_key: compressedPubkey,
1558
+ l1_address: l1Address,
1559
+ direct_address: directAddress
748
1560
  });
749
1561
  const event = await this.createEvent(EVENT_KINDS.NAMETAG_BINDING, content, [
750
1562
  ["d", hashedNametag],
751
1563
  ["nametag", hashedNametag],
752
1564
  ["t", hashedNametag],
753
- ["address", nostrPubkey]
1565
+ ["address", nostrPubkey],
1566
+ // Extended tags for indexing
1567
+ ["pubkey", compressedPubkey],
1568
+ ["l1", l1Address]
754
1569
  ]);
755
1570
  await this.publishEvent(event);
756
- this.log("Registered nametag:", nametag, "for pubkey:", nostrPubkey.slice(0, 16) + "...");
1571
+ this.log("Registered nametag:", nametag, "for pubkey:", nostrPubkey.slice(0, 16) + "...", "l1:", l1Address.slice(0, 12) + "...");
757
1572
  return true;
758
1573
  }
1574
+ // Track broadcast subscriptions
1575
+ broadcastSubscriptions = /* @__PURE__ */ new Map();
1576
+ // key -> subId
759
1577
  subscribeToBroadcast(tags, handler) {
760
1578
  const key = tags.sort().join(":");
761
1579
  if (!this.broadcastHandlers.has(key)) {
762
1580
  this.broadcastHandlers.set(key, /* @__PURE__ */ new Set());
763
- if (this.isConnected()) {
1581
+ if (this.isConnected() && this.nostrClient) {
764
1582
  this.subscribeToTags(tags);
765
1583
  }
766
1584
  }
@@ -769,6 +1587,11 @@ var NostrTransportProvider = class {
769
1587
  this.broadcastHandlers.get(key)?.delete(handler);
770
1588
  if (this.broadcastHandlers.get(key)?.size === 0) {
771
1589
  this.broadcastHandlers.delete(key);
1590
+ const subId = this.broadcastSubscriptions.get(key);
1591
+ if (subId && this.nostrClient) {
1592
+ this.nostrClient.unsubscribe(subId);
1593
+ this.broadcastSubscriptions.delete(key);
1594
+ }
772
1595
  }
773
1596
  };
774
1597
  }
@@ -787,81 +1610,19 @@ var NostrTransportProvider = class {
787
1610
  return () => this.eventCallbacks.delete(callback);
788
1611
  }
789
1612
  // ===========================================================================
790
- // Private: Connection Management
791
- // ===========================================================================
792
- async connectToRelay(url) {
793
- return new Promise((resolve, reject) => {
794
- const ws = this.config.createWebSocket(url);
795
- const timeout = setTimeout(() => {
796
- ws.close();
797
- reject(new Error(`Connection timeout: ${url}`));
798
- }, this.config.timeout);
799
- ws.onopen = () => {
800
- clearTimeout(timeout);
801
- this.connections.set(url, ws);
802
- this.reconnectAttempts.set(url, 0);
803
- this.log("Connected to relay:", url);
804
- resolve();
805
- };
806
- ws.onerror = (error) => {
807
- clearTimeout(timeout);
808
- this.log("Relay error:", url, error);
809
- reject(error);
810
- };
811
- ws.onclose = () => {
812
- this.connections.delete(url);
813
- if (this.config.autoReconnect && this.status === "connected") {
814
- this.scheduleReconnect(url);
815
- }
816
- };
817
- ws.onmessage = (event) => {
818
- this.handleRelayMessage(url, event.data);
819
- };
820
- });
821
- }
822
- scheduleReconnect(url) {
823
- const attempts = this.reconnectAttempts.get(url) ?? 0;
824
- if (attempts >= this.config.maxReconnectAttempts) {
825
- this.log("Max reconnect attempts reached for:", url);
826
- return;
827
- }
828
- this.reconnectAttempts.set(url, attempts + 1);
829
- const delay = this.config.reconnectDelay * Math.pow(2, attempts);
830
- this.emitEvent({ type: "transport:reconnecting", timestamp: Date.now() });
831
- setTimeout(() => {
832
- this.connectToRelay(url).catch(() => {
833
- });
834
- }, delay);
835
- }
836
- // ===========================================================================
837
1613
  // Private: Message Handling
838
1614
  // ===========================================================================
839
- handleRelayMessage(relay, data) {
840
- try {
841
- const message = JSON.parse(data);
842
- const [type, ...args] = message;
843
- switch (type) {
844
- case "EVENT":
845
- this.handleEvent(args[1]);
846
- break;
847
- case "EOSE":
848
- break;
849
- case "OK":
850
- break;
851
- case "NOTICE":
852
- this.log("Relay notice:", relay, args[0]);
853
- break;
854
- }
855
- } catch (error) {
856
- this.log("Failed to parse relay message:", error);
857
- }
858
- }
859
1615
  async handleEvent(event) {
1616
+ this.log("Processing event kind:", event.kind, "id:", event.id?.slice(0, 12));
860
1617
  try {
861
1618
  switch (event.kind) {
862
1619
  case EVENT_KINDS.DIRECT_MESSAGE:
863
1620
  await this.handleDirectMessage(event);
864
1621
  break;
1622
+ case EventKinds.GIFT_WRAP:
1623
+ this.log("Handling gift wrap (NIP-17 DM)");
1624
+ await this.handleGiftWrap(event);
1625
+ break;
865
1626
  case EVENT_KINDS.TOKEN_TRANSFER:
866
1627
  await this.handleTokenTransfer(event);
867
1628
  break;
@@ -880,23 +1641,54 @@ var NostrTransportProvider = class {
880
1641
  }
881
1642
  }
882
1643
  async handleDirectMessage(event) {
883
- if (!this.identity || !this.keyManager) return;
884
- if (event.pubkey === this.keyManager.getPublicKeyHex()) return;
885
- const content = await this.decryptContent(event.content, event.pubkey);
886
- const message = {
887
- id: event.id,
888
- senderPubkey: event.pubkey,
889
- content,
890
- timestamp: event.created_at * 1e3,
891
- encrypted: true
892
- };
893
- this.emitEvent({ type: "message:received", timestamp: Date.now() });
894
- for (const handler of this.messageHandlers) {
1644
+ this.log("Ignoring NIP-04 kind 4 event (DMs use NIP-17):", event.id?.slice(0, 12));
1645
+ }
1646
+ async handleGiftWrap(event) {
1647
+ if (!this.identity || !this.keyManager) {
1648
+ this.log("handleGiftWrap: no identity/keyManager");
1649
+ return;
1650
+ }
1651
+ try {
1652
+ const pm = NIP17.unwrap(event, this.keyManager);
1653
+ this.log("Gift wrap unwrapped, sender:", pm.senderPubkey?.slice(0, 16), "kind:", pm.kind);
1654
+ if (pm.senderPubkey === this.keyManager.getPublicKeyHex()) {
1655
+ this.log("Skipping own message");
1656
+ return;
1657
+ }
1658
+ if (pm.kind !== EventKinds.CHAT_MESSAGE) {
1659
+ this.log("Skipping non-chat message, kind:", pm.kind);
1660
+ return;
1661
+ }
1662
+ let content = pm.content;
1663
+ let senderNametag;
895
1664
  try {
896
- handler(message);
897
- } catch (error) {
898
- this.log("Message handler error:", error);
1665
+ const parsed = JSON.parse(content);
1666
+ if (typeof parsed === "object" && parsed.text !== void 0) {
1667
+ content = parsed.text;
1668
+ senderNametag = parsed.senderNametag || void 0;
1669
+ }
1670
+ } catch {
899
1671
  }
1672
+ this.log("DM received from:", senderNametag || pm.senderPubkey?.slice(0, 16), "content:", content?.slice(0, 50));
1673
+ const message = {
1674
+ id: pm.eventId,
1675
+ senderTransportPubkey: pm.senderPubkey,
1676
+ senderNametag,
1677
+ content,
1678
+ timestamp: pm.timestamp * 1e3,
1679
+ encrypted: true
1680
+ };
1681
+ this.emitEvent({ type: "message:received", timestamp: Date.now() });
1682
+ this.log("Dispatching to", this.messageHandlers.size, "handlers");
1683
+ for (const handler of this.messageHandlers) {
1684
+ try {
1685
+ handler(message);
1686
+ } catch (error) {
1687
+ this.log("Message handler error:", error);
1688
+ }
1689
+ }
1690
+ } catch (err) {
1691
+ this.log("Gift wrap decrypt failed (expected if not for us):", err?.message?.slice(0, 50));
900
1692
  }
901
1693
  }
902
1694
  async handleTokenTransfer(event) {
@@ -905,7 +1697,7 @@ var NostrTransportProvider = class {
905
1697
  const payload = JSON.parse(content);
906
1698
  const transfer = {
907
1699
  id: event.id,
908
- senderPubkey: event.pubkey,
1700
+ senderTransportPubkey: event.pubkey,
909
1701
  payload,
910
1702
  timestamp: event.created_at * 1e3
911
1703
  };
@@ -925,7 +1717,7 @@ var NostrTransportProvider = class {
925
1717
  const requestData = JSON.parse(content);
926
1718
  const request = {
927
1719
  id: event.id,
928
- senderPubkey: event.pubkey,
1720
+ senderTransportPubkey: event.pubkey,
929
1721
  request: {
930
1722
  requestId: requestData.requestId,
931
1723
  amount: requestData.amount,
@@ -955,7 +1747,7 @@ var NostrTransportProvider = class {
955
1747
  const responseData = JSON.parse(content);
956
1748
  const response = {
957
1749
  id: event.id,
958
- responderPubkey: event.pubkey,
1750
+ responderTransportPubkey: event.pubkey,
959
1751
  response: {
960
1752
  requestId: responseData.requestId,
961
1753
  responseType: responseData.responseType,
@@ -980,7 +1772,7 @@ var NostrTransportProvider = class {
980
1772
  const tags = event.tags.filter((t) => t[0] === "t").map((t) => t[1]);
981
1773
  const broadcast = {
982
1774
  id: event.id,
983
- authorPubkey: event.pubkey,
1775
+ authorTransportPubkey: event.pubkey,
984
1776
  content: event.content,
985
1777
  tags,
986
1778
  timestamp: event.created_at * 1e3
@@ -1035,109 +1827,149 @@ var NostrTransportProvider = class {
1035
1827
  return this.createEvent(kind, encrypted, tags);
1036
1828
  }
1037
1829
  async publishEvent(event) {
1038
- const message = JSON.stringify(["EVENT", event]);
1039
- const publishPromises = Array.from(this.connections.values()).map((ws) => {
1040
- return new Promise((resolve, reject) => {
1041
- if (ws.readyState !== WebSocketReadyState.OPEN) {
1042
- reject(new Error("WebSocket not open"));
1043
- return;
1044
- }
1045
- ws.send(message);
1046
- resolve();
1047
- });
1048
- });
1049
- await Promise.any(publishPromises);
1830
+ if (!this.nostrClient) {
1831
+ throw new Error("NostrClient not initialized");
1832
+ }
1833
+ const sdkEvent = NostrEventClass.fromJSON(event);
1834
+ await this.nostrClient.publishEvent(sdkEvent);
1050
1835
  }
1051
- async queryEvents(filter) {
1052
- if (this.connections.size === 0) {
1836
+ async queryEvents(filterObj) {
1837
+ if (!this.nostrClient || !this.nostrClient.isConnected()) {
1053
1838
  throw new Error("No connected relays");
1054
1839
  }
1055
- const queryPromises = Array.from(this.connections.values()).map(
1056
- (ws) => this.queryEventsFromRelay(ws, filter)
1057
- );
1058
- const results = await Promise.allSettled(queryPromises);
1059
- for (const result of results) {
1060
- if (result.status === "fulfilled" && result.value.length > 0) {
1061
- return result.value;
1062
- }
1063
- }
1064
- return [];
1065
- }
1066
- async queryEventsFromRelay(ws, filter) {
1067
- const subId = this.config.generateUUID().slice(0, 8);
1068
1840
  const events = [];
1841
+ const filter = new Filter(filterObj);
1069
1842
  return new Promise((resolve) => {
1070
1843
  const timeout = setTimeout(() => {
1071
- this.unsubscribeFromRelay(ws, subId);
1844
+ if (subId) {
1845
+ this.nostrClient?.unsubscribe(subId);
1846
+ }
1072
1847
  resolve(events);
1073
1848
  }, 5e3);
1074
- const originalHandler = ws.onmessage;
1075
- ws.onmessage = (event) => {
1076
- const message = JSON.parse(event.data);
1077
- const [type, sid, data] = message;
1078
- if (sid !== subId) {
1079
- originalHandler?.call(ws, event);
1080
- return;
1081
- }
1082
- if (type === "EVENT") {
1083
- events.push(data);
1084
- } else if (type === "EOSE") {
1849
+ const subId = this.nostrClient.subscribe(filter, {
1850
+ onEvent: (event) => {
1851
+ events.push({
1852
+ id: event.id,
1853
+ kind: event.kind,
1854
+ content: event.content,
1855
+ tags: event.tags,
1856
+ pubkey: event.pubkey,
1857
+ created_at: event.created_at,
1858
+ sig: event.sig
1859
+ });
1860
+ },
1861
+ onEndOfStoredEvents: () => {
1085
1862
  clearTimeout(timeout);
1086
- ws.onmessage = originalHandler;
1087
- this.unsubscribeFromRelay(ws, subId);
1863
+ this.nostrClient?.unsubscribe(subId);
1088
1864
  resolve(events);
1089
1865
  }
1090
- };
1091
- ws.send(JSON.stringify(["REQ", subId, filter]));
1866
+ });
1092
1867
  });
1093
1868
  }
1094
- unsubscribeFromRelay(ws, subId) {
1095
- if (ws.readyState === WebSocketReadyState.OPEN) {
1096
- ws.send(JSON.stringify(["CLOSE", subId]));
1097
- }
1098
- }
1099
1869
  // ===========================================================================
1100
1870
  // Private: Subscriptions
1101
1871
  // ===========================================================================
1872
+ // Track subscription IDs for cleanup
1873
+ walletSubscriptionId = null;
1874
+ chatSubscriptionId = null;
1102
1875
  subscribeToEvents() {
1103
- if (!this.identity || !this.keyManager) return;
1104
- const subId = "main";
1876
+ this.log("subscribeToEvents called, identity:", !!this.identity, "keyManager:", !!this.keyManager, "nostrClient:", !!this.nostrClient);
1877
+ if (!this.identity || !this.keyManager || !this.nostrClient) {
1878
+ this.log("subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
1879
+ return;
1880
+ }
1881
+ if (this.walletSubscriptionId) {
1882
+ this.nostrClient.unsubscribe(this.walletSubscriptionId);
1883
+ this.walletSubscriptionId = null;
1884
+ }
1885
+ if (this.chatSubscriptionId) {
1886
+ this.nostrClient.unsubscribe(this.chatSubscriptionId);
1887
+ this.chatSubscriptionId = null;
1888
+ }
1889
+ if (this.mainSubscriptionId) {
1890
+ this.nostrClient.unsubscribe(this.mainSubscriptionId);
1891
+ this.mainSubscriptionId = null;
1892
+ }
1105
1893
  const nostrPubkey = this.keyManager.getPublicKeyHex();
1106
- const filter = {
1107
- kinds: [
1108
- EVENT_KINDS.DIRECT_MESSAGE,
1109
- EVENT_KINDS.TOKEN_TRANSFER,
1110
- EVENT_KINDS.PAYMENT_REQUEST,
1111
- EVENT_KINDS.PAYMENT_REQUEST_RESPONSE
1112
- ],
1113
- "#p": [nostrPubkey],
1114
- since: Math.floor(Date.now() / 1e3) - 86400
1115
- // Last 24h
1116
- };
1117
- const message = JSON.stringify(["REQ", subId, filter]);
1118
- for (const ws of this.connections.values()) {
1119
- if (ws.readyState === WebSocketReadyState.OPEN) {
1120
- ws.send(message);
1894
+ this.log("Subscribing with Nostr pubkey:", nostrPubkey);
1895
+ const walletFilter = new Filter();
1896
+ walletFilter.kinds = [
1897
+ EVENT_KINDS.DIRECT_MESSAGE,
1898
+ EVENT_KINDS.TOKEN_TRANSFER,
1899
+ EVENT_KINDS.PAYMENT_REQUEST,
1900
+ EVENT_KINDS.PAYMENT_REQUEST_RESPONSE
1901
+ ];
1902
+ walletFilter["#p"] = [nostrPubkey];
1903
+ walletFilter.since = Math.floor(Date.now() / 1e3) - 86400;
1904
+ this.walletSubscriptionId = this.nostrClient.subscribe(walletFilter, {
1905
+ onEvent: (event) => {
1906
+ this.log("Received wallet event kind:", event.kind, "id:", event.id?.slice(0, 12));
1907
+ this.handleEvent({
1908
+ id: event.id,
1909
+ kind: event.kind,
1910
+ content: event.content,
1911
+ tags: event.tags,
1912
+ pubkey: event.pubkey,
1913
+ created_at: event.created_at,
1914
+ sig: event.sig
1915
+ });
1916
+ },
1917
+ onEndOfStoredEvents: () => {
1918
+ this.log("Wallet subscription ready (EOSE)");
1919
+ },
1920
+ onError: (_subId, error) => {
1921
+ this.log("Wallet subscription error:", error);
1121
1922
  }
1122
- }
1123
- this.subscriptions.set(subId, Array.from(this.connections.keys()));
1124
- this.log("Subscribed to events");
1923
+ });
1924
+ this.log("Wallet subscription created, subId:", this.walletSubscriptionId);
1925
+ const chatFilter = new Filter();
1926
+ chatFilter.kinds = [EventKinds.GIFT_WRAP];
1927
+ chatFilter["#p"] = [nostrPubkey];
1928
+ this.chatSubscriptionId = this.nostrClient.subscribe(chatFilter, {
1929
+ onEvent: (event) => {
1930
+ this.log("Received chat event kind:", event.kind, "id:", event.id?.slice(0, 12));
1931
+ this.handleEvent({
1932
+ id: event.id,
1933
+ kind: event.kind,
1934
+ content: event.content,
1935
+ tags: event.tags,
1936
+ pubkey: event.pubkey,
1937
+ created_at: event.created_at,
1938
+ sig: event.sig
1939
+ });
1940
+ },
1941
+ onEndOfStoredEvents: () => {
1942
+ this.log("Chat subscription ready (EOSE)");
1943
+ },
1944
+ onError: (_subId, error) => {
1945
+ this.log("Chat subscription error:", error);
1946
+ }
1947
+ });
1948
+ this.log("Chat subscription created, subId:", this.chatSubscriptionId);
1125
1949
  }
1126
1950
  subscribeToTags(tags) {
1127
- const subId = `tags:${tags.join(":")}`;
1128
- const filter = {
1951
+ if (!this.nostrClient) return;
1952
+ const key = tags.sort().join(":");
1953
+ const filter = new Filter({
1129
1954
  kinds: [EVENT_KINDS.BROADCAST],
1130
1955
  "#t": tags,
1131
1956
  since: Math.floor(Date.now() / 1e3) - 3600
1132
1957
  // Last hour
1133
- };
1134
- const message = JSON.stringify(["REQ", subId, filter]);
1135
- for (const ws of this.connections.values()) {
1136
- if (ws.readyState === WebSocketReadyState.OPEN) {
1137
- ws.send(message);
1958
+ });
1959
+ const subId = this.nostrClient.subscribe(filter, {
1960
+ onEvent: (event) => {
1961
+ this.handleBroadcast({
1962
+ id: event.id,
1963
+ kind: event.kind,
1964
+ content: event.content,
1965
+ tags: event.tags,
1966
+ pubkey: event.pubkey,
1967
+ created_at: event.created_at,
1968
+ sig: event.sig
1969
+ });
1138
1970
  }
1139
- }
1140
- this.subscriptions.set(subId, Array.from(this.connections.keys()));
1971
+ });
1972
+ this.broadcastSubscriptions.set(key, subId);
1141
1973
  }
1142
1974
  // ===========================================================================
1143
1975
  // Private: Encryption
@@ -1598,13 +2430,72 @@ var UnicityAggregatorProvider = class {
1598
2430
  };
1599
2431
  var UnicityOracleProvider = UnicityAggregatorProvider;
1600
2432
 
2433
+ // assets/trustbase.ts
2434
+ var TRUSTBASE_TESTNET = {
2435
+ version: 1,
2436
+ networkId: 3,
2437
+ epoch: 1,
2438
+ epochStartRound: 1,
2439
+ rootNodes: [
2440
+ {
2441
+ nodeId: "16Uiu2HAkyQRiA7pMgzgLj9GgaBJEJa8zmx9dzqUDa6WxQPJ82ghU",
2442
+ sigKey: "0x039afb2acb65f5fbc272d8907f763d0a5d189aadc9b97afdcc5897ea4dd112e68b",
2443
+ stake: 1
2444
+ }
2445
+ ],
2446
+ quorumThreshold: 1,
2447
+ stateHash: "",
2448
+ changeRecordHash: "",
2449
+ previousEntryHash: "",
2450
+ signatures: {
2451
+ "16Uiu2HAkyQRiA7pMgzgLj9GgaBJEJa8zmx9dzqUDa6WxQPJ82ghU": "0xf157c9fdd8a378e3ca70d354ccc4475ab2cd8de360127bc46b0aeab4b453a80f07fd9136a5843b60a8babaff23e20acc8879861f7651440a5e2829f7541b31f100"
2452
+ }
2453
+ };
2454
+ var TRUSTBASE_MAINNET = null;
2455
+ var TRUSTBASE_DEV = TRUSTBASE_TESTNET;
2456
+
2457
+ // impl/shared/trustbase-loader.ts
2458
+ function getEmbeddedTrustBase(network) {
2459
+ switch (network) {
2460
+ case "mainnet":
2461
+ return TRUSTBASE_MAINNET;
2462
+ case "testnet":
2463
+ return TRUSTBASE_TESTNET;
2464
+ case "dev":
2465
+ return TRUSTBASE_DEV;
2466
+ default:
2467
+ return TRUSTBASE_TESTNET;
2468
+ }
2469
+ }
2470
+ var BaseTrustBaseLoader = class {
2471
+ network;
2472
+ constructor(network = "testnet") {
2473
+ this.network = network;
2474
+ }
2475
+ async load() {
2476
+ const external = await this.loadFromExternal();
2477
+ if (external) {
2478
+ return external;
2479
+ }
2480
+ return getEmbeddedTrustBase(this.network);
2481
+ }
2482
+ };
2483
+
1601
2484
  // impl/nodejs/oracle/index.ts
1602
- var NodeTrustBaseLoader = class {
2485
+ var NodeTrustBaseLoader = class extends BaseTrustBaseLoader {
1603
2486
  filePath;
1604
- constructor(filePath = "./trustbase-testnet.json") {
1605
- this.filePath = filePath;
2487
+ constructor(filePathOrNetwork) {
2488
+ if (!filePathOrNetwork) {
2489
+ super("testnet");
2490
+ } else if (filePathOrNetwork.includes("/") || filePathOrNetwork.includes(".")) {
2491
+ super("testnet");
2492
+ this.filePath = filePathOrNetwork;
2493
+ } else {
2494
+ super(filePathOrNetwork);
2495
+ }
1606
2496
  }
1607
- async load() {
2497
+ async loadFromExternal() {
2498
+ if (!this.filePath) return null;
1608
2499
  try {
1609
2500
  if (fs3.existsSync(this.filePath)) {
1610
2501
  const content = fs3.readFileSync(this.filePath, "utf-8");
@@ -1615,14 +2506,14 @@ var NodeTrustBaseLoader = class {
1615
2506
  return null;
1616
2507
  }
1617
2508
  };
1618
- function createNodeTrustBaseLoader(filePath) {
1619
- return new NodeTrustBaseLoader(filePath);
2509
+ function createNodeTrustBaseLoader(filePathOrNetwork) {
2510
+ return new NodeTrustBaseLoader(filePathOrNetwork);
1620
2511
  }
1621
2512
  function createUnicityAggregatorProvider(config) {
1622
- const { trustBasePath, ...restConfig } = config;
2513
+ const { trustBasePath, network, ...restConfig } = config;
1623
2514
  return new UnicityAggregatorProvider({
1624
2515
  ...restConfig,
1625
- trustBaseLoader: createNodeTrustBaseLoader(trustBasePath)
2516
+ trustBaseLoader: createNodeTrustBaseLoader(trustBasePath ?? network ?? "testnet")
1626
2517
  });
1627
2518
  }
1628
2519
  var createUnicityOracleProvider = createUnicityAggregatorProvider;
@@ -1656,7 +2547,7 @@ function resolveOracleConfig(network, config) {
1656
2547
  const networkConfig = getNetworkConfig(network);
1657
2548
  return {
1658
2549
  url: config?.url ?? networkConfig.aggregatorUrl,
1659
- apiKey: config?.apiKey,
2550
+ apiKey: config?.apiKey ?? DEFAULT_AGGREGATOR_API_KEY,
1660
2551
  timeout: config?.timeout,
1661
2552
  skipVerification: config?.skipVerification,
1662
2553
  debug: config?.debug,
@@ -1701,7 +2592,8 @@ function createNodeProviders(config) {
1701
2592
  timeout: oracleConfig.timeout,
1702
2593
  trustBasePath: oracleConfig.trustBasePath,
1703
2594
  skipVerification: oracleConfig.skipVerification,
1704
- debug: oracleConfig.debug
2595
+ debug: oracleConfig.debug,
2596
+ network
1705
2597
  }),
1706
2598
  l1: l1Config
1707
2599
  };
@@ -1722,4 +2614,9 @@ export {
1722
2614
  createUnicityAggregatorProvider,
1723
2615
  createUnicityOracleProvider
1724
2616
  };
2617
+ /*! Bundled license information:
2618
+
2619
+ @noble/hashes/utils.js:
2620
+ (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
2621
+ */
1725
2622
  //# sourceMappingURL=index.js.map