@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,3 +1,126 @@
1
+ // constants.ts
2
+ var STORAGE_KEYS_GLOBAL = {
3
+ /** Encrypted BIP39 mnemonic */
4
+ MNEMONIC: "mnemonic",
5
+ /** Encrypted master private key */
6
+ MASTER_KEY: "master_key",
7
+ /** BIP32 chain code */
8
+ CHAIN_CODE: "chain_code",
9
+ /** HD derivation path (full path like m/44'/0'/0'/0/0) */
10
+ DERIVATION_PATH: "derivation_path",
11
+ /** Base derivation path (like m/44'/0'/0' without chain/index) */
12
+ BASE_PATH: "base_path",
13
+ /** Derivation mode: bip32, wif_hmac, legacy_hmac */
14
+ DERIVATION_MODE: "derivation_mode",
15
+ /** Wallet source: mnemonic, file, unknown */
16
+ WALLET_SOURCE: "wallet_source",
17
+ /** Wallet existence flag */
18
+ WALLET_EXISTS: "wallet_exists",
19
+ /** Current active address index */
20
+ CURRENT_ADDRESS_INDEX: "current_address_index",
21
+ /** Index of address nametags (JSON: { "0": "alice", "1": "bob" }) - for discovery */
22
+ ADDRESS_NAMETAGS: "address_nametags"
23
+ };
24
+ var STORAGE_KEYS_ADDRESS = {
25
+ /** Pending transfers for this address */
26
+ PENDING_TRANSFERS: "pending_transfers",
27
+ /** Transfer outbox for this address */
28
+ OUTBOX: "outbox",
29
+ /** Conversations for this address */
30
+ CONVERSATIONS: "conversations",
31
+ /** Messages for this address */
32
+ MESSAGES: "messages",
33
+ /** Transaction history for this address */
34
+ TRANSACTION_HISTORY: "transaction_history"
35
+ };
36
+ var STORAGE_KEYS = {
37
+ ...STORAGE_KEYS_GLOBAL,
38
+ ...STORAGE_KEYS_ADDRESS
39
+ };
40
+ function getAddressId(directAddress) {
41
+ let hash = directAddress;
42
+ if (hash.startsWith("DIRECT://")) {
43
+ hash = hash.slice(9);
44
+ } else if (hash.startsWith("DIRECT:")) {
45
+ hash = hash.slice(7);
46
+ }
47
+ const first = hash.slice(0, 6).toLowerCase();
48
+ const last = hash.slice(-6).toLowerCase();
49
+ return `DIRECT_${first}_${last}`;
50
+ }
51
+ var DEFAULT_NOSTR_RELAYS = [
52
+ "wss://relay.unicity.network",
53
+ "wss://relay.damus.io",
54
+ "wss://nos.lol",
55
+ "wss://relay.nostr.band"
56
+ ];
57
+ var NOSTR_EVENT_KINDS = {
58
+ /** NIP-04 encrypted direct message */
59
+ DIRECT_MESSAGE: 4,
60
+ /** Token transfer (Unicity custom - 31113) */
61
+ TOKEN_TRANSFER: 31113,
62
+ /** Payment request (Unicity custom - 31115) */
63
+ PAYMENT_REQUEST: 31115,
64
+ /** Payment request response (Unicity custom - 31116) */
65
+ PAYMENT_REQUEST_RESPONSE: 31116,
66
+ /** Nametag binding (NIP-78 app-specific data) */
67
+ NAMETAG_BINDING: 30078,
68
+ /** Public broadcast */
69
+ BROADCAST: 1
70
+ };
71
+ var DEFAULT_AGGREGATOR_URL = "https://aggregator.unicity.network/rpc";
72
+ var DEV_AGGREGATOR_URL = "https://dev-aggregator.dyndns.org/rpc";
73
+ var TEST_AGGREGATOR_URL = "https://goggregator-test.unicity.network";
74
+ var DEFAULT_AGGREGATOR_TIMEOUT = 3e4;
75
+ var DEFAULT_AGGREGATOR_API_KEY = "sk_06365a9c44654841a366068bcfc68986";
76
+ var DEFAULT_IPFS_GATEWAYS = [
77
+ "https://ipfs.unicity.network",
78
+ "https://dweb.link",
79
+ "https://ipfs.io"
80
+ ];
81
+ var DEFAULT_BASE_PATH = "m/44'/0'/0'";
82
+ var DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0`;
83
+ var DEFAULT_ELECTRUM_URL = "wss://fulcrum.alpha.unicity.network:50004";
84
+ var TEST_ELECTRUM_URL = "wss://fulcrum.alpha.testnet.unicity.network:50004";
85
+ var TEST_NOSTR_RELAYS = [
86
+ "wss://nostr-relay.testnet.unicity.network"
87
+ ];
88
+ var NETWORKS = {
89
+ mainnet: {
90
+ name: "Mainnet",
91
+ aggregatorUrl: DEFAULT_AGGREGATOR_URL,
92
+ nostrRelays: DEFAULT_NOSTR_RELAYS,
93
+ ipfsGateways: DEFAULT_IPFS_GATEWAYS,
94
+ electrumUrl: DEFAULT_ELECTRUM_URL
95
+ },
96
+ testnet: {
97
+ name: "Testnet",
98
+ aggregatorUrl: TEST_AGGREGATOR_URL,
99
+ nostrRelays: TEST_NOSTR_RELAYS,
100
+ ipfsGateways: DEFAULT_IPFS_GATEWAYS,
101
+ electrumUrl: TEST_ELECTRUM_URL
102
+ },
103
+ dev: {
104
+ name: "Development",
105
+ aggregatorUrl: DEV_AGGREGATOR_URL,
106
+ nostrRelays: TEST_NOSTR_RELAYS,
107
+ ipfsGateways: DEFAULT_IPFS_GATEWAYS,
108
+ electrumUrl: TEST_ELECTRUM_URL
109
+ }
110
+ };
111
+ var TIMEOUTS = {
112
+ /** WebSocket connection timeout */
113
+ WEBSOCKET_CONNECT: 1e4,
114
+ /** Nostr relay reconnect delay */
115
+ NOSTR_RECONNECT_DELAY: 3e3,
116
+ /** Max reconnect attempts */
117
+ MAX_RECONNECT_ATTEMPTS: 5,
118
+ /** Proof polling interval */
119
+ PROOF_POLL_INTERVAL: 1e3,
120
+ /** Sync interval */
121
+ SYNC_INTERVAL: 6e4
122
+ };
123
+
1
124
  // impl/browser/storage/LocalStorageProvider.ts
2
125
  var LocalStorageProvider = class {
3
126
  id = "localStorage";
@@ -47,7 +170,7 @@ var LocalStorageProvider = class {
47
170
  // ===========================================================================
48
171
  setIdentity(identity) {
49
172
  this.identity = identity;
50
- this.log("Identity set:", identity.address);
173
+ this.log("Identity set:", identity.l1Address);
51
174
  }
52
175
  async get(key) {
53
176
  this.ensureConnected();
@@ -114,8 +237,12 @@ var LocalStorageProvider = class {
114
237
  // Private Methods
115
238
  // ===========================================================================
116
239
  getFullKey(key) {
117
- const addressPart = this.identity?.address ?? "default";
118
- return `${this.config.prefix}${addressPart}_${key}`;
240
+ const isPerAddressKey = Object.values(STORAGE_KEYS_ADDRESS).includes(key);
241
+ if (isPerAddressKey && this.identity?.directAddress) {
242
+ const addressId = getAddressId(this.identity.directAddress);
243
+ return `${this.config.prefix}${addressId}_${key}`;
244
+ }
245
+ return `${this.config.prefix}${key}`;
119
246
  }
120
247
  ensureConnected() {
121
248
  if (this.status !== "connected") {
@@ -170,17 +297,21 @@ var IndexedDBTokenStorageProvider = class {
170
297
  id = "indexeddb-token-storage";
171
298
  name = "IndexedDB Token Storage";
172
299
  type = "local";
300
+ dbNamePrefix;
173
301
  dbName;
174
302
  db = null;
175
303
  status = "disconnected";
176
304
  identity = null;
177
305
  constructor(config) {
178
- const prefix = config?.dbNamePrefix ?? DB_NAME;
179
- this.dbName = prefix;
306
+ this.dbNamePrefix = config?.dbNamePrefix ?? DB_NAME;
307
+ this.dbName = this.dbNamePrefix;
180
308
  }
181
309
  setIdentity(identity) {
182
310
  this.identity = identity;
183
- this.dbName = `${DB_NAME}-${identity.address.slice(0, 20)}`;
311
+ if (identity.directAddress) {
312
+ const addressId = getAddressId(identity.directAddress);
313
+ this.dbName = `${this.dbNamePrefix}-${addressId}`;
314
+ }
184
315
  }
185
316
  async initialize() {
186
317
  try {
@@ -225,7 +356,7 @@ var IndexedDBTokenStorageProvider = class {
225
356
  const data = {
226
357
  _meta: {
227
358
  version: 1,
228
- address: this.identity?.address ?? "",
359
+ address: this.identity?.l1Address ?? "",
229
360
  formatVersion: "2.0",
230
361
  updatedAt: Date.now()
231
362
  }
@@ -458,14 +589,565 @@ function createIndexedDBTokenStorageProvider(config) {
458
589
  }
459
590
 
460
591
  // transport/NostrTransportProvider.ts
461
- import { Buffer } from "buffer";
592
+ import { Buffer as Buffer2 } from "buffer";
593
+
594
+ // node_modules/@noble/hashes/utils.js
595
+ function isBytes(a) {
596
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
597
+ }
598
+ function anumber(n, title = "") {
599
+ if (!Number.isSafeInteger(n) || n < 0) {
600
+ const prefix = title && `"${title}" `;
601
+ throw new Error(`${prefix}expected integer >= 0, got ${n}`);
602
+ }
603
+ }
604
+ function abytes(value, length, title = "") {
605
+ const bytes = isBytes(value);
606
+ const len = value?.length;
607
+ const needsLen = length !== void 0;
608
+ if (!bytes || needsLen && len !== length) {
609
+ const prefix = title && `"${title}" `;
610
+ const ofLen = needsLen ? ` of length ${length}` : "";
611
+ const got = bytes ? `length=${len}` : `type=${typeof value}`;
612
+ throw new Error(prefix + "expected Uint8Array" + ofLen + ", got " + got);
613
+ }
614
+ return value;
615
+ }
616
+ function ahash(h) {
617
+ if (typeof h !== "function" || typeof h.create !== "function")
618
+ throw new Error("Hash must wrapped by utils.createHasher");
619
+ anumber(h.outputLen);
620
+ anumber(h.blockLen);
621
+ }
622
+ function aexists(instance, checkFinished = true) {
623
+ if (instance.destroyed)
624
+ throw new Error("Hash instance has been destroyed");
625
+ if (checkFinished && instance.finished)
626
+ throw new Error("Hash#digest() has already been called");
627
+ }
628
+ function aoutput(out, instance) {
629
+ abytes(out, void 0, "digestInto() output");
630
+ const min = instance.outputLen;
631
+ if (out.length < min) {
632
+ throw new Error('"digestInto() output" expected to be of length >=' + min);
633
+ }
634
+ }
635
+ function clean(...arrays) {
636
+ for (let i = 0; i < arrays.length; i++) {
637
+ arrays[i].fill(0);
638
+ }
639
+ }
640
+ function createView(arr) {
641
+ return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
642
+ }
643
+ function rotr(word, shift) {
644
+ return word << 32 - shift | word >>> shift;
645
+ }
646
+ function createHasher(hashCons, info = {}) {
647
+ const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
648
+ const tmp = hashCons(void 0);
649
+ hashC.outputLen = tmp.outputLen;
650
+ hashC.blockLen = tmp.blockLen;
651
+ hashC.create = (opts) => hashCons(opts);
652
+ Object.assign(hashC, info);
653
+ return Object.freeze(hashC);
654
+ }
655
+ var oidNist = (suffix) => ({
656
+ oid: Uint8Array.from([6, 9, 96, 134, 72, 1, 101, 3, 4, 2, suffix])
657
+ });
658
+
659
+ // node_modules/@noble/hashes/hmac.js
660
+ var _HMAC = class {
661
+ oHash;
662
+ iHash;
663
+ blockLen;
664
+ outputLen;
665
+ finished = false;
666
+ destroyed = false;
667
+ constructor(hash, key) {
668
+ ahash(hash);
669
+ abytes(key, void 0, "key");
670
+ this.iHash = hash.create();
671
+ if (typeof this.iHash.update !== "function")
672
+ throw new Error("Expected instance of class which extends utils.Hash");
673
+ this.blockLen = this.iHash.blockLen;
674
+ this.outputLen = this.iHash.outputLen;
675
+ const blockLen = this.blockLen;
676
+ const pad = new Uint8Array(blockLen);
677
+ pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
678
+ for (let i = 0; i < pad.length; i++)
679
+ pad[i] ^= 54;
680
+ this.iHash.update(pad);
681
+ this.oHash = hash.create();
682
+ for (let i = 0; i < pad.length; i++)
683
+ pad[i] ^= 54 ^ 92;
684
+ this.oHash.update(pad);
685
+ clean(pad);
686
+ }
687
+ update(buf) {
688
+ aexists(this);
689
+ this.iHash.update(buf);
690
+ return this;
691
+ }
692
+ digestInto(out) {
693
+ aexists(this);
694
+ abytes(out, this.outputLen, "output");
695
+ this.finished = true;
696
+ this.iHash.digestInto(out);
697
+ this.oHash.update(out);
698
+ this.oHash.digestInto(out);
699
+ this.destroy();
700
+ }
701
+ digest() {
702
+ const out = new Uint8Array(this.oHash.outputLen);
703
+ this.digestInto(out);
704
+ return out;
705
+ }
706
+ _cloneInto(to) {
707
+ to ||= Object.create(Object.getPrototypeOf(this), {});
708
+ const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
709
+ to = to;
710
+ to.finished = finished;
711
+ to.destroyed = destroyed;
712
+ to.blockLen = blockLen;
713
+ to.outputLen = outputLen;
714
+ to.oHash = oHash._cloneInto(to.oHash);
715
+ to.iHash = iHash._cloneInto(to.iHash);
716
+ return to;
717
+ }
718
+ clone() {
719
+ return this._cloneInto();
720
+ }
721
+ destroy() {
722
+ this.destroyed = true;
723
+ this.oHash.destroy();
724
+ this.iHash.destroy();
725
+ }
726
+ };
727
+ var hmac = (hash, key, message) => new _HMAC(hash, key).update(message).digest();
728
+ hmac.create = (hash, key) => new _HMAC(hash, key);
729
+
730
+ // node_modules/@noble/hashes/hkdf.js
731
+ function extract(hash, ikm, salt) {
732
+ ahash(hash);
733
+ if (salt === void 0)
734
+ salt = new Uint8Array(hash.outputLen);
735
+ return hmac(hash, salt, ikm);
736
+ }
737
+ var HKDF_COUNTER = /* @__PURE__ */ Uint8Array.of(0);
738
+ var EMPTY_BUFFER = /* @__PURE__ */ Uint8Array.of();
739
+ function expand(hash, prk, info, length = 32) {
740
+ ahash(hash);
741
+ anumber(length, "length");
742
+ const olen = hash.outputLen;
743
+ if (length > 255 * olen)
744
+ throw new Error("Length must be <= 255*HashLen");
745
+ const blocks = Math.ceil(length / olen);
746
+ if (info === void 0)
747
+ info = EMPTY_BUFFER;
748
+ else
749
+ abytes(info, void 0, "info");
750
+ const okm = new Uint8Array(blocks * olen);
751
+ const HMAC = hmac.create(hash, prk);
752
+ const HMACTmp = HMAC._cloneInto();
753
+ const T = new Uint8Array(HMAC.outputLen);
754
+ for (let counter = 0; counter < blocks; counter++) {
755
+ HKDF_COUNTER[0] = counter + 1;
756
+ HMACTmp.update(counter === 0 ? EMPTY_BUFFER : T).update(info).update(HKDF_COUNTER).digestInto(T);
757
+ okm.set(T, olen * counter);
758
+ HMAC._cloneInto(HMACTmp);
759
+ }
760
+ HMAC.destroy();
761
+ HMACTmp.destroy();
762
+ clean(T, HKDF_COUNTER);
763
+ return okm.slice(0, length);
764
+ }
765
+ var hkdf = (hash, ikm, salt, info, length) => expand(hash, extract(hash, ikm, salt), info, length);
766
+
767
+ // node_modules/@noble/hashes/_md.js
768
+ function Chi(a, b, c) {
769
+ return a & b ^ ~a & c;
770
+ }
771
+ function Maj(a, b, c) {
772
+ return a & b ^ a & c ^ b & c;
773
+ }
774
+ var HashMD = class {
775
+ blockLen;
776
+ outputLen;
777
+ padOffset;
778
+ isLE;
779
+ // For partial updates less than block size
780
+ buffer;
781
+ view;
782
+ finished = false;
783
+ length = 0;
784
+ pos = 0;
785
+ destroyed = false;
786
+ constructor(blockLen, outputLen, padOffset, isLE) {
787
+ this.blockLen = blockLen;
788
+ this.outputLen = outputLen;
789
+ this.padOffset = padOffset;
790
+ this.isLE = isLE;
791
+ this.buffer = new Uint8Array(blockLen);
792
+ this.view = createView(this.buffer);
793
+ }
794
+ update(data) {
795
+ aexists(this);
796
+ abytes(data);
797
+ const { view, buffer, blockLen } = this;
798
+ const len = data.length;
799
+ for (let pos = 0; pos < len; ) {
800
+ const take = Math.min(blockLen - this.pos, len - pos);
801
+ if (take === blockLen) {
802
+ const dataView = createView(data);
803
+ for (; blockLen <= len - pos; pos += blockLen)
804
+ this.process(dataView, pos);
805
+ continue;
806
+ }
807
+ buffer.set(data.subarray(pos, pos + take), this.pos);
808
+ this.pos += take;
809
+ pos += take;
810
+ if (this.pos === blockLen) {
811
+ this.process(view, 0);
812
+ this.pos = 0;
813
+ }
814
+ }
815
+ this.length += data.length;
816
+ this.roundClean();
817
+ return this;
818
+ }
819
+ digestInto(out) {
820
+ aexists(this);
821
+ aoutput(out, this);
822
+ this.finished = true;
823
+ const { buffer, view, blockLen, isLE } = this;
824
+ let { pos } = this;
825
+ buffer[pos++] = 128;
826
+ clean(this.buffer.subarray(pos));
827
+ if (this.padOffset > blockLen - pos) {
828
+ this.process(view, 0);
829
+ pos = 0;
830
+ }
831
+ for (let i = pos; i < blockLen; i++)
832
+ buffer[i] = 0;
833
+ view.setBigUint64(blockLen - 8, BigInt(this.length * 8), isLE);
834
+ this.process(view, 0);
835
+ const oview = createView(out);
836
+ const len = this.outputLen;
837
+ if (len % 4)
838
+ throw new Error("_sha2: outputLen must be aligned to 32bit");
839
+ const outLen = len / 4;
840
+ const state = this.get();
841
+ if (outLen > state.length)
842
+ throw new Error("_sha2: outputLen bigger than state");
843
+ for (let i = 0; i < outLen; i++)
844
+ oview.setUint32(4 * i, state[i], isLE);
845
+ }
846
+ digest() {
847
+ const { buffer, outputLen } = this;
848
+ this.digestInto(buffer);
849
+ const res = buffer.slice(0, outputLen);
850
+ this.destroy();
851
+ return res;
852
+ }
853
+ _cloneInto(to) {
854
+ to ||= new this.constructor();
855
+ to.set(...this.get());
856
+ const { blockLen, buffer, length, finished, destroyed, pos } = this;
857
+ to.destroyed = destroyed;
858
+ to.finished = finished;
859
+ to.length = length;
860
+ to.pos = pos;
861
+ if (length % blockLen)
862
+ to.buffer.set(buffer);
863
+ return to;
864
+ }
865
+ clone() {
866
+ return this._cloneInto();
867
+ }
868
+ };
869
+ var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
870
+ 1779033703,
871
+ 3144134277,
872
+ 1013904242,
873
+ 2773480762,
874
+ 1359893119,
875
+ 2600822924,
876
+ 528734635,
877
+ 1541459225
878
+ ]);
879
+
880
+ // node_modules/@noble/hashes/sha2.js
881
+ var SHA256_K = /* @__PURE__ */ Uint32Array.from([
882
+ 1116352408,
883
+ 1899447441,
884
+ 3049323471,
885
+ 3921009573,
886
+ 961987163,
887
+ 1508970993,
888
+ 2453635748,
889
+ 2870763221,
890
+ 3624381080,
891
+ 310598401,
892
+ 607225278,
893
+ 1426881987,
894
+ 1925078388,
895
+ 2162078206,
896
+ 2614888103,
897
+ 3248222580,
898
+ 3835390401,
899
+ 4022224774,
900
+ 264347078,
901
+ 604807628,
902
+ 770255983,
903
+ 1249150122,
904
+ 1555081692,
905
+ 1996064986,
906
+ 2554220882,
907
+ 2821834349,
908
+ 2952996808,
909
+ 3210313671,
910
+ 3336571891,
911
+ 3584528711,
912
+ 113926993,
913
+ 338241895,
914
+ 666307205,
915
+ 773529912,
916
+ 1294757372,
917
+ 1396182291,
918
+ 1695183700,
919
+ 1986661051,
920
+ 2177026350,
921
+ 2456956037,
922
+ 2730485921,
923
+ 2820302411,
924
+ 3259730800,
925
+ 3345764771,
926
+ 3516065817,
927
+ 3600352804,
928
+ 4094571909,
929
+ 275423344,
930
+ 430227734,
931
+ 506948616,
932
+ 659060556,
933
+ 883997877,
934
+ 958139571,
935
+ 1322822218,
936
+ 1537002063,
937
+ 1747873779,
938
+ 1955562222,
939
+ 2024104815,
940
+ 2227730452,
941
+ 2361852424,
942
+ 2428436474,
943
+ 2756734187,
944
+ 3204031479,
945
+ 3329325298
946
+ ]);
947
+ var SHA256_W = /* @__PURE__ */ new Uint32Array(64);
948
+ var SHA2_32B = class extends HashMD {
949
+ constructor(outputLen) {
950
+ super(64, outputLen, 8, false);
951
+ }
952
+ get() {
953
+ const { A, B, C, D, E, F, G, H } = this;
954
+ return [A, B, C, D, E, F, G, H];
955
+ }
956
+ // prettier-ignore
957
+ set(A, B, C, D, E, F, G, H) {
958
+ this.A = A | 0;
959
+ this.B = B | 0;
960
+ this.C = C | 0;
961
+ this.D = D | 0;
962
+ this.E = E | 0;
963
+ this.F = F | 0;
964
+ this.G = G | 0;
965
+ this.H = H | 0;
966
+ }
967
+ process(view, offset) {
968
+ for (let i = 0; i < 16; i++, offset += 4)
969
+ SHA256_W[i] = view.getUint32(offset, false);
970
+ for (let i = 16; i < 64; i++) {
971
+ const W15 = SHA256_W[i - 15];
972
+ const W2 = SHA256_W[i - 2];
973
+ const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
974
+ const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
975
+ SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
976
+ }
977
+ let { A, B, C, D, E, F, G, H } = this;
978
+ for (let i = 0; i < 64; i++) {
979
+ const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
980
+ const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
981
+ const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
982
+ const T2 = sigma0 + Maj(A, B, C) | 0;
983
+ H = G;
984
+ G = F;
985
+ F = E;
986
+ E = D + T1 | 0;
987
+ D = C;
988
+ C = B;
989
+ B = A;
990
+ A = T1 + T2 | 0;
991
+ }
992
+ A = A + this.A | 0;
993
+ B = B + this.B | 0;
994
+ C = C + this.C | 0;
995
+ D = D + this.D | 0;
996
+ E = E + this.E | 0;
997
+ F = F + this.F | 0;
998
+ G = G + this.G | 0;
999
+ H = H + this.H | 0;
1000
+ this.set(A, B, C, D, E, F, G, H);
1001
+ }
1002
+ roundClean() {
1003
+ clean(SHA256_W);
1004
+ }
1005
+ destroy() {
1006
+ this.set(0, 0, 0, 0, 0, 0, 0, 0);
1007
+ clean(this.buffer);
1008
+ }
1009
+ };
1010
+ var _SHA256 = class extends SHA2_32B {
1011
+ // We cannot use array here since array allows indexing by variable
1012
+ // which means optimizer/compiler cannot use registers.
1013
+ A = SHA256_IV[0] | 0;
1014
+ B = SHA256_IV[1] | 0;
1015
+ C = SHA256_IV[2] | 0;
1016
+ D = SHA256_IV[3] | 0;
1017
+ E = SHA256_IV[4] | 0;
1018
+ F = SHA256_IV[5] | 0;
1019
+ G = SHA256_IV[6] | 0;
1020
+ H = SHA256_IV[7] | 0;
1021
+ constructor() {
1022
+ super(32);
1023
+ }
1024
+ };
1025
+ var sha256 = /* @__PURE__ */ createHasher(
1026
+ () => new _SHA256(),
1027
+ /* @__PURE__ */ oidNist(1)
1028
+ );
1029
+
1030
+ // transport/NostrTransportProvider.ts
462
1031
  import {
463
1032
  NostrKeyManager,
464
1033
  NIP04,
1034
+ NIP17,
465
1035
  Event as NostrEventClass,
466
- hashNametag
1036
+ EventKinds,
1037
+ hashNametag,
1038
+ NostrClient,
1039
+ Filter
467
1040
  } from "@unicitylabs/nostr-js-sdk";
468
1041
 
1042
+ // core/crypto.ts
1043
+ import * as bip39 from "bip39";
1044
+ import CryptoJS from "crypto-js";
1045
+ import elliptic from "elliptic";
1046
+
1047
+ // core/bech32.ts
1048
+ var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
1049
+ var GENERATOR = [996825010, 642813549, 513874426, 1027748829, 705979059];
1050
+ function convertBits(data, fromBits, toBits, pad) {
1051
+ let acc = 0;
1052
+ let bits = 0;
1053
+ const ret = [];
1054
+ const maxv = (1 << toBits) - 1;
1055
+ for (let i = 0; i < data.length; i++) {
1056
+ const value = data[i];
1057
+ if (value < 0 || value >> fromBits !== 0) return null;
1058
+ acc = acc << fromBits | value;
1059
+ bits += fromBits;
1060
+ while (bits >= toBits) {
1061
+ bits -= toBits;
1062
+ ret.push(acc >> bits & maxv);
1063
+ }
1064
+ }
1065
+ if (pad) {
1066
+ if (bits > 0) {
1067
+ ret.push(acc << toBits - bits & maxv);
1068
+ }
1069
+ } else if (bits >= fromBits || acc << toBits - bits & maxv) {
1070
+ return null;
1071
+ }
1072
+ return ret;
1073
+ }
1074
+ function hrpExpand(hrp) {
1075
+ const ret = [];
1076
+ for (let i = 0; i < hrp.length; i++) ret.push(hrp.charCodeAt(i) >> 5);
1077
+ ret.push(0);
1078
+ for (let i = 0; i < hrp.length; i++) ret.push(hrp.charCodeAt(i) & 31);
1079
+ return ret;
1080
+ }
1081
+ function bech32Polymod(values) {
1082
+ let chk = 1;
1083
+ for (let p = 0; p < values.length; p++) {
1084
+ const top = chk >> 25;
1085
+ chk = (chk & 33554431) << 5 ^ values[p];
1086
+ for (let i = 0; i < 5; i++) {
1087
+ if (top >> i & 1) chk ^= GENERATOR[i];
1088
+ }
1089
+ }
1090
+ return chk;
1091
+ }
1092
+ function bech32Checksum(hrp, data) {
1093
+ const values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
1094
+ const mod = bech32Polymod(values) ^ 1;
1095
+ const ret = [];
1096
+ for (let p = 0; p < 6; p++) {
1097
+ ret.push(mod >> 5 * (5 - p) & 31);
1098
+ }
1099
+ return ret;
1100
+ }
1101
+ function encodeBech32(hrp, version, program) {
1102
+ if (version < 0 || version > 16) {
1103
+ throw new Error("Invalid witness version");
1104
+ }
1105
+ const converted = convertBits(Array.from(program), 8, 5, true);
1106
+ if (!converted) {
1107
+ throw new Error("Failed to convert bits");
1108
+ }
1109
+ const data = [version].concat(converted);
1110
+ const checksum = bech32Checksum(hrp, data);
1111
+ const combined = data.concat(checksum);
1112
+ let out = hrp + "1";
1113
+ for (let i = 0; i < combined.length; i++) {
1114
+ out += CHARSET[combined[i]];
1115
+ }
1116
+ return out;
1117
+ }
1118
+
1119
+ // core/crypto.ts
1120
+ var ec = new elliptic.ec("secp256k1");
1121
+ var CURVE_ORDER = BigInt(
1122
+ "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
1123
+ );
1124
+ function getPublicKey(privateKey, compressed = true) {
1125
+ const keyPair = ec.keyFromPrivate(privateKey, "hex");
1126
+ return keyPair.getPublic(compressed, "hex");
1127
+ }
1128
+ function sha2562(data, inputEncoding = "hex") {
1129
+ const parsed = inputEncoding === "hex" ? CryptoJS.enc.Hex.parse(data) : CryptoJS.enc.Utf8.parse(data);
1130
+ return CryptoJS.SHA256(parsed).toString();
1131
+ }
1132
+ function ripemd160(data, inputEncoding = "hex") {
1133
+ const parsed = inputEncoding === "hex" ? CryptoJS.enc.Hex.parse(data) : CryptoJS.enc.Utf8.parse(data);
1134
+ return CryptoJS.RIPEMD160(parsed).toString();
1135
+ }
1136
+ function hash160(data) {
1137
+ const sha = sha2562(data, "hex");
1138
+ return ripemd160(sha, "hex");
1139
+ }
1140
+ function hash160ToBytes(hash160Hex) {
1141
+ const matches = hash160Hex.match(/../g);
1142
+ if (!matches) return new Uint8Array(0);
1143
+ return Uint8Array.from(matches.map((x) => parseInt(x, 16)));
1144
+ }
1145
+ function publicKeyToAddress(publicKey, prefix = "alpha", witnessVersion = 0) {
1146
+ const pubKeyHash = hash160(publicKey);
1147
+ const programBytes = hash160ToBytes(pubKeyHash);
1148
+ return encodeBech32(prefix, witnessVersion, programBytes);
1149
+ }
1150
+
469
1151
  // transport/websocket.ts
470
1152
  var WebSocketReadyState = {
471
1153
  CONNECTING: 0,
@@ -484,124 +1166,61 @@ function defaultUUIDGenerator() {
484
1166
  });
485
1167
  }
486
1168
 
487
- // constants.ts
488
- var STORAGE_PREFIX = "sphere_";
489
- var STORAGE_KEYS = {
490
- /** Encrypted BIP39 mnemonic */
491
- MNEMONIC: `${STORAGE_PREFIX}mnemonic`,
492
- /** Encrypted master private key */
493
- MASTER_KEY: `${STORAGE_PREFIX}master_key`,
494
- /** BIP32 chain code */
495
- CHAIN_CODE: `${STORAGE_PREFIX}chain_code`,
496
- /** HD derivation path (full path like m/44'/0'/0'/0/0) */
497
- DERIVATION_PATH: `${STORAGE_PREFIX}derivation_path`,
498
- /** Base derivation path (like m/44'/0'/0' without chain/index) */
499
- BASE_PATH: `${STORAGE_PREFIX}base_path`,
500
- /** Derivation mode: bip32, wif_hmac, legacy_hmac */
501
- DERIVATION_MODE: `${STORAGE_PREFIX}derivation_mode`,
502
- /** Wallet source: mnemonic, file, unknown */
503
- WALLET_SOURCE: `${STORAGE_PREFIX}wallet_source`,
504
- /** Wallet existence flag */
505
- WALLET_EXISTS: `${STORAGE_PREFIX}wallet_exists`,
506
- /** Registered nametag (legacy - single address) */
507
- NAMETAG: `${STORAGE_PREFIX}nametag`,
508
- /** Current active address index */
509
- CURRENT_ADDRESS_INDEX: `${STORAGE_PREFIX}current_address_index`,
510
- /** Address nametags map (JSON: { "0": "alice", "1": "bob" }) */
511
- ADDRESS_NAMETAGS: `${STORAGE_PREFIX}address_nametags`,
512
- /** Token data */
513
- TOKENS: `${STORAGE_PREFIX}tokens`,
514
- /** Pending transfers */
515
- PENDING_TRANSFERS: `${STORAGE_PREFIX}pending_transfers`,
516
- /** Transfer outbox */
517
- OUTBOX: `${STORAGE_PREFIX}outbox`,
518
- /** Conversations */
519
- CONVERSATIONS: `${STORAGE_PREFIX}conversations`,
520
- /** Messages */
521
- MESSAGES: `${STORAGE_PREFIX}messages`,
522
- /** Transaction history */
523
- TRANSACTION_HISTORY: `${STORAGE_PREFIX}transaction_history`,
524
- /** Archived tokens (spent token history) */
525
- ARCHIVED_TOKENS: `${STORAGE_PREFIX}archived_tokens`,
526
- /** Tombstones (records of deleted/spent tokens) */
527
- TOMBSTONES: `${STORAGE_PREFIX}tombstones`,
528
- /** Forked tokens (alternative histories) */
529
- FORKED_TOKENS: `${STORAGE_PREFIX}forked_tokens`
530
- };
531
- var DEFAULT_NOSTR_RELAYS = [
532
- "wss://relay.unicity.network",
533
- "wss://relay.damus.io",
534
- "wss://nos.lol",
535
- "wss://relay.nostr.band"
536
- ];
537
- var NOSTR_EVENT_KINDS = {
538
- /** NIP-04 encrypted direct message */
539
- DIRECT_MESSAGE: 4,
540
- /** Token transfer (Unicity custom - 31113) */
541
- TOKEN_TRANSFER: 31113,
542
- /** Payment request (Unicity custom - 31115) */
543
- PAYMENT_REQUEST: 31115,
544
- /** Payment request response (Unicity custom - 31116) */
545
- PAYMENT_REQUEST_RESPONSE: 31116,
546
- /** Nametag binding (NIP-78 app-specific data) */
547
- NAMETAG_BINDING: 30078,
548
- /** Public broadcast */
549
- BROADCAST: 1
550
- };
551
- var DEFAULT_AGGREGATOR_URL = "https://aggregator.unicity.network/rpc";
552
- var DEV_AGGREGATOR_URL = "https://dev-aggregator.dyndns.org/rpc";
553
- var TEST_AGGREGATOR_URL = "https://goggregator-test.unicity.network";
554
- var DEFAULT_AGGREGATOR_TIMEOUT = 3e4;
555
- var DEFAULT_IPFS_GATEWAYS = [
556
- "https://ipfs.unicity.network",
557
- "https://dweb.link",
558
- "https://ipfs.io"
559
- ];
560
- var DEFAULT_BASE_PATH = "m/44'/0'/0'";
561
- var DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0`;
562
- var DEFAULT_ELECTRUM_URL = "wss://fulcrum.alpha.unicity.network:50004";
563
- var TEST_ELECTRUM_URL = "wss://fulcrum.alpha.testnet.unicity.network:50004";
564
- var TEST_NOSTR_RELAYS = [
565
- "wss://nostr-relay.testnet.unicity.network"
566
- ];
567
- var NETWORKS = {
568
- mainnet: {
569
- name: "Mainnet",
570
- aggregatorUrl: DEFAULT_AGGREGATOR_URL,
571
- nostrRelays: DEFAULT_NOSTR_RELAYS,
572
- ipfsGateways: DEFAULT_IPFS_GATEWAYS,
573
- electrumUrl: DEFAULT_ELECTRUM_URL
574
- },
575
- testnet: {
576
- name: "Testnet",
577
- aggregatorUrl: TEST_AGGREGATOR_URL,
578
- nostrRelays: TEST_NOSTR_RELAYS,
579
- ipfsGateways: DEFAULT_IPFS_GATEWAYS,
580
- electrumUrl: TEST_ELECTRUM_URL
581
- },
582
- dev: {
583
- name: "Development",
584
- aggregatorUrl: DEV_AGGREGATOR_URL,
585
- nostrRelays: TEST_NOSTR_RELAYS,
586
- ipfsGateways: DEFAULT_IPFS_GATEWAYS,
587
- electrumUrl: TEST_ELECTRUM_URL
588
- }
589
- };
590
- var TIMEOUTS = {
591
- /** WebSocket connection timeout */
592
- WEBSOCKET_CONNECT: 1e4,
593
- /** Nostr relay reconnect delay */
594
- NOSTR_RECONNECT_DELAY: 3e3,
595
- /** Max reconnect attempts */
596
- MAX_RECONNECT_ATTEMPTS: 5,
597
- /** Proof polling interval */
598
- PROOF_POLL_INTERVAL: 1e3,
599
- /** Sync interval */
600
- SYNC_INTERVAL: 6e4
601
- };
602
-
603
1169
  // transport/NostrTransportProvider.ts
604
1170
  var EVENT_KINDS = NOSTR_EVENT_KINDS;
1171
+ function deriveNametagEncryptionKey(privateKeyHex) {
1172
+ const privateKeyBytes = Buffer2.from(privateKeyHex, "hex");
1173
+ const saltInput = new TextEncoder().encode("sphere-nametag-salt");
1174
+ const salt = sha256(saltInput);
1175
+ const info = new TextEncoder().encode("nametag-encryption");
1176
+ return hkdf(sha256, privateKeyBytes, salt, info, 32);
1177
+ }
1178
+ async function encryptNametag(nametag, privateKeyHex) {
1179
+ const key = deriveNametagEncryptionKey(privateKeyHex);
1180
+ const iv = crypto.getRandomValues(new Uint8Array(12));
1181
+ const encoder = new TextEncoder();
1182
+ const data = encoder.encode(nametag);
1183
+ const cryptoKey = await crypto.subtle.importKey(
1184
+ "raw",
1185
+ new Uint8Array(key).buffer,
1186
+ { name: "AES-GCM" },
1187
+ false,
1188
+ ["encrypt"]
1189
+ );
1190
+ const encrypted = await crypto.subtle.encrypt(
1191
+ { name: "AES-GCM", iv: new Uint8Array(iv).buffer },
1192
+ cryptoKey,
1193
+ new Uint8Array(data).buffer
1194
+ );
1195
+ const combined = new Uint8Array(iv.length + encrypted.byteLength);
1196
+ combined.set(iv, 0);
1197
+ combined.set(new Uint8Array(encrypted), iv.length);
1198
+ return Buffer2.from(combined).toString("base64");
1199
+ }
1200
+ async function decryptNametag(encryptedBase64, privateKeyHex) {
1201
+ try {
1202
+ const key = deriveNametagEncryptionKey(privateKeyHex);
1203
+ const combined = Buffer2.from(encryptedBase64, "base64");
1204
+ const iv = combined.slice(0, 12);
1205
+ const ciphertext = combined.slice(12);
1206
+ const cryptoKey = await crypto.subtle.importKey(
1207
+ "raw",
1208
+ new Uint8Array(key).buffer,
1209
+ { name: "AES-GCM" },
1210
+ false,
1211
+ ["decrypt"]
1212
+ );
1213
+ const decrypted = await crypto.subtle.decrypt(
1214
+ { name: "AES-GCM", iv: new Uint8Array(iv).buffer },
1215
+ cryptoKey,
1216
+ new Uint8Array(ciphertext).buffer
1217
+ );
1218
+ const decoder = new TextDecoder();
1219
+ return decoder.decode(decrypted);
1220
+ } catch {
1221
+ return null;
1222
+ }
1223
+ }
605
1224
  var NostrTransportProvider = class {
606
1225
  id = "nostr";
607
1226
  name = "Nostr Transport";
@@ -611,9 +1230,10 @@ var NostrTransportProvider = class {
611
1230
  identity = null;
612
1231
  keyManager = null;
613
1232
  status = "disconnected";
614
- // WebSocket connections to relays
615
- connections = /* @__PURE__ */ new Map();
616
- reconnectAttempts = /* @__PURE__ */ new Map();
1233
+ // NostrClient from nostr-js-sdk handles all WebSocket management,
1234
+ // keepalive pings, reconnection, and NIP-42 authentication
1235
+ nostrClient = null;
1236
+ mainSubscriptionId = null;
617
1237
  // Event handlers
618
1238
  messageHandlers = /* @__PURE__ */ new Set();
619
1239
  transferHandlers = /* @__PURE__ */ new Set();
@@ -621,9 +1241,6 @@ var NostrTransportProvider = class {
621
1241
  paymentRequestResponseHandlers = /* @__PURE__ */ new Set();
622
1242
  broadcastHandlers = /* @__PURE__ */ new Map();
623
1243
  eventCallbacks = /* @__PURE__ */ new Set();
624
- // Subscriptions
625
- subscriptions = /* @__PURE__ */ new Map();
626
- // subId -> relays
627
1244
  constructor(config) {
628
1245
  this.config = {
629
1246
  relays: config.relays ?? [...DEFAULT_NOSTR_RELAYS],
@@ -643,16 +1260,43 @@ var NostrTransportProvider = class {
643
1260
  if (this.status === "connected") return;
644
1261
  this.status = "connecting";
645
1262
  try {
646
- const connectPromises = this.config.relays.map(
647
- (relay) => this.connectToRelay(relay)
648
- );
649
- await Promise.allSettled(connectPromises);
650
- if (this.connections.size === 0) {
1263
+ if (!this.keyManager) {
1264
+ const tempKey = Buffer2.alloc(32);
1265
+ crypto.getRandomValues(tempKey);
1266
+ this.keyManager = NostrKeyManager.fromPrivateKey(tempKey);
1267
+ }
1268
+ this.nostrClient = new NostrClient(this.keyManager, {
1269
+ autoReconnect: this.config.autoReconnect,
1270
+ reconnectIntervalMs: this.config.reconnectDelay,
1271
+ maxReconnectIntervalMs: this.config.reconnectDelay * 16,
1272
+ // exponential backoff cap
1273
+ pingIntervalMs: 15e3
1274
+ // 15 second keepalive pings (more aggressive to prevent drops)
1275
+ });
1276
+ this.nostrClient.addConnectionListener({
1277
+ onConnect: (url) => {
1278
+ this.log("NostrClient connected to relay:", url);
1279
+ this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
1280
+ },
1281
+ onDisconnect: (url, reason) => {
1282
+ this.log("NostrClient disconnected from relay:", url, "reason:", reason);
1283
+ },
1284
+ onReconnecting: (url, attempt) => {
1285
+ this.log("NostrClient reconnecting to relay:", url, "attempt:", attempt);
1286
+ this.emitEvent({ type: "transport:reconnecting", timestamp: Date.now() });
1287
+ },
1288
+ onReconnected: (url) => {
1289
+ this.log("NostrClient reconnected to relay:", url);
1290
+ this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
1291
+ }
1292
+ });
1293
+ await this.nostrClient.connect(...this.config.relays);
1294
+ if (!this.nostrClient.isConnected()) {
651
1295
  throw new Error("Failed to connect to any relay");
652
1296
  }
653
1297
  this.status = "connected";
654
1298
  this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
655
- this.log("Connected to", this.connections.size, "relays");
1299
+ this.log("Connected to", this.nostrClient.getConnectedRelays().size, "relays");
656
1300
  if (this.identity) {
657
1301
  this.subscribeToEvents();
658
1302
  }
@@ -662,17 +1306,19 @@ var NostrTransportProvider = class {
662
1306
  }
663
1307
  }
664
1308
  async disconnect() {
665
- for (const [url, ws] of this.connections) {
666
- ws.close();
667
- this.connections.delete(url);
1309
+ if (this.nostrClient) {
1310
+ this.nostrClient.disconnect();
1311
+ this.nostrClient = null;
668
1312
  }
669
- this.subscriptions.clear();
1313
+ this.mainSubscriptionId = null;
1314
+ this.walletSubscriptionId = null;
1315
+ this.chatSubscriptionId = null;
670
1316
  this.status = "disconnected";
671
1317
  this.emitEvent({ type: "transport:disconnected", timestamp: Date.now() });
672
1318
  this.log("Disconnected from all relays");
673
1319
  }
674
1320
  isConnected() {
675
- return this.status === "connected" && this.connections.size > 0;
1321
+ return this.status === "connected" && this.nostrClient?.isConnected() === true;
676
1322
  }
677
1323
  getStatus() {
678
1324
  return this.status;
@@ -690,7 +1336,8 @@ var NostrTransportProvider = class {
690
1336
  * Get list of currently connected relay URLs
691
1337
  */
692
1338
  getConnectedRelays() {
693
- return Array.from(this.connections.keys());
1339
+ if (!this.nostrClient) return [];
1340
+ return Array.from(this.nostrClient.getConnectedRelays());
694
1341
  }
695
1342
  /**
696
1343
  * Add a new relay dynamically
@@ -702,9 +1349,9 @@ var NostrTransportProvider = class {
702
1349
  return false;
703
1350
  }
704
1351
  this.config.relays.push(relayUrl);
705
- if (this.status === "connected") {
1352
+ if (this.status === "connected" && this.nostrClient) {
706
1353
  try {
707
- await this.connectToRelay(relayUrl);
1354
+ await this.nostrClient.connect(relayUrl);
708
1355
  this.log("Added and connected to relay:", relayUrl);
709
1356
  this.emitEvent({
710
1357
  type: "transport:relay_added",
@@ -732,6 +1379,8 @@ var NostrTransportProvider = class {
732
1379
  /**
733
1380
  * Remove a relay dynamically
734
1381
  * Will disconnect from the relay if connected
1382
+ * NOTE: NostrClient doesn't support removing individual relays at runtime.
1383
+ * We remove from config so it won't be used on next connect().
735
1384
  */
736
1385
  async removeRelay(relayUrl) {
737
1386
  const index = this.config.relays.indexOf(relayUrl);
@@ -740,19 +1389,13 @@ var NostrTransportProvider = class {
740
1389
  return false;
741
1390
  }
742
1391
  this.config.relays.splice(index, 1);
743
- const ws = this.connections.get(relayUrl);
744
- if (ws) {
745
- ws.close();
746
- this.connections.delete(relayUrl);
747
- this.reconnectAttempts.delete(relayUrl);
748
- this.log("Removed and disconnected from relay:", relayUrl);
749
- }
1392
+ this.log("Removed relay from config:", relayUrl);
750
1393
  this.emitEvent({
751
1394
  type: "transport:relay_removed",
752
1395
  timestamp: Date.now(),
753
1396
  data: { relay: relayUrl }
754
1397
  });
755
- if (this.connections.size === 0 && this.status === "connected") {
1398
+ if (this.nostrClient && !this.nostrClient.isConnected() && this.status === "connected") {
756
1399
  this.status = "error";
757
1400
  this.emitEvent({
758
1401
  type: "transport:error",
@@ -772,19 +1415,49 @@ var NostrTransportProvider = class {
772
1415
  * Check if a relay is currently connected
773
1416
  */
774
1417
  isRelayConnected(relayUrl) {
775
- const ws = this.connections.get(relayUrl);
776
- return ws !== void 0 && ws.readyState === WebSocketReadyState.OPEN;
1418
+ if (!this.nostrClient) return false;
1419
+ return this.nostrClient.getConnectedRelays().has(relayUrl);
777
1420
  }
778
1421
  // ===========================================================================
779
1422
  // TransportProvider Implementation
780
1423
  // ===========================================================================
781
1424
  setIdentity(identity) {
782
1425
  this.identity = identity;
783
- const secretKey = Buffer.from(identity.privateKey, "hex");
1426
+ const secretKey = Buffer2.from(identity.privateKey, "hex");
784
1427
  this.keyManager = NostrKeyManager.fromPrivateKey(secretKey);
785
1428
  const nostrPubkey = this.keyManager.getPublicKeyHex();
786
1429
  this.log("Identity set, Nostr pubkey:", nostrPubkey.slice(0, 16) + "...");
787
- if (this.isConnected()) {
1430
+ if (this.nostrClient && this.status === "connected") {
1431
+ this.log("Identity changed while connected - recreating NostrClient");
1432
+ const oldClient = this.nostrClient;
1433
+ this.nostrClient = new NostrClient(this.keyManager, {
1434
+ autoReconnect: this.config.autoReconnect,
1435
+ reconnectIntervalMs: this.config.reconnectDelay,
1436
+ maxReconnectIntervalMs: this.config.reconnectDelay * 16,
1437
+ pingIntervalMs: 15e3
1438
+ // 15 second keepalive pings
1439
+ });
1440
+ this.nostrClient.addConnectionListener({
1441
+ onConnect: (url) => {
1442
+ this.log("NostrClient connected to relay:", url);
1443
+ },
1444
+ onDisconnect: (url, reason) => {
1445
+ this.log("NostrClient disconnected from relay:", url, "reason:", reason);
1446
+ },
1447
+ onReconnecting: (url, attempt) => {
1448
+ this.log("NostrClient reconnecting to relay:", url, "attempt:", attempt);
1449
+ },
1450
+ onReconnected: (url) => {
1451
+ this.log("NostrClient reconnected to relay:", url);
1452
+ }
1453
+ });
1454
+ this.nostrClient.connect(...this.config.relays).then(() => {
1455
+ this.subscribeToEvents();
1456
+ oldClient.disconnect();
1457
+ }).catch((err) => {
1458
+ this.log("Failed to reconnect with new identity:", err);
1459
+ });
1460
+ } else if (this.isConnected()) {
788
1461
  this.subscribeToEvents();
789
1462
  }
790
1463
  }
@@ -800,18 +1473,17 @@ var NostrTransportProvider = class {
800
1473
  }
801
1474
  async sendMessage(recipientPubkey, content) {
802
1475
  this.ensureReady();
803
- const event = await this.createEncryptedEvent(
804
- EVENT_KINDS.DIRECT_MESSAGE,
805
- content,
806
- [["p", recipientPubkey]]
807
- );
808
- await this.publishEvent(event);
1476
+ const nostrRecipient = recipientPubkey.length === 66 && (recipientPubkey.startsWith("02") || recipientPubkey.startsWith("03")) ? recipientPubkey.slice(2) : recipientPubkey;
1477
+ const senderNametag = this.identity?.nametag;
1478
+ const wrappedContent = senderNametag ? JSON.stringify({ senderNametag, text: content }) : content;
1479
+ const giftWrap = NIP17.createGiftWrap(this.keyManager, nostrRecipient, wrappedContent);
1480
+ await this.publishEvent(giftWrap);
809
1481
  this.emitEvent({
810
1482
  type: "message:sent",
811
1483
  timestamp: Date.now(),
812
1484
  data: { recipient: recipientPubkey }
813
1485
  });
814
- return event.id;
1486
+ return giftWrap.id;
815
1487
  }
816
1488
  onMessage(handler) {
817
1489
  this.messageHandlers.add(handler);
@@ -928,6 +1600,118 @@ var NostrTransportProvider = class {
928
1600
  if (pubkeyTag?.[1]) return pubkeyTag[1];
929
1601
  return null;
930
1602
  }
1603
+ async resolveNametagInfo(nametag) {
1604
+ this.ensureReady();
1605
+ const hashedNametag = hashNametag(nametag);
1606
+ let events = await this.queryEvents({
1607
+ kinds: [EVENT_KINDS.NAMETAG_BINDING],
1608
+ "#t": [hashedNametag],
1609
+ limit: 1
1610
+ });
1611
+ if (events.length === 0) {
1612
+ events = await this.queryEvents({
1613
+ kinds: [EVENT_KINDS.NAMETAG_BINDING],
1614
+ "#d": [hashedNametag],
1615
+ limit: 1
1616
+ });
1617
+ }
1618
+ if (events.length === 0) return null;
1619
+ const bindingEvent = events[0];
1620
+ try {
1621
+ const content = JSON.parse(bindingEvent.content);
1622
+ if (content.public_key && content.l1_address) {
1623
+ const l3Address = `PROXY:${hashedNametag}`;
1624
+ return {
1625
+ nametag,
1626
+ transportPubkey: bindingEvent.pubkey,
1627
+ chainPubkey: content.public_key,
1628
+ l1Address: content.l1_address,
1629
+ directAddress: content.direct_address || "",
1630
+ proxyAddress: l3Address,
1631
+ timestamp: bindingEvent.created_at * 1e3
1632
+ };
1633
+ }
1634
+ this.log("Legacy nametag event without extended fields:", nametag);
1635
+ const pubkeyTag = bindingEvent.tags.find((t) => t[0] === "pubkey");
1636
+ const l1Tag = bindingEvent.tags.find((t) => t[0] === "l1");
1637
+ if (pubkeyTag?.[1] && l1Tag?.[1]) {
1638
+ const l3Address = `PROXY:${hashedNametag}`;
1639
+ return {
1640
+ nametag,
1641
+ transportPubkey: bindingEvent.pubkey,
1642
+ chainPubkey: pubkeyTag[1],
1643
+ l1Address: l1Tag[1],
1644
+ directAddress: "",
1645
+ proxyAddress: l3Address,
1646
+ timestamp: bindingEvent.created_at * 1e3
1647
+ };
1648
+ }
1649
+ return {
1650
+ nametag,
1651
+ transportPubkey: bindingEvent.pubkey,
1652
+ chainPubkey: "",
1653
+ // Cannot derive from 32-byte Nostr pubkey
1654
+ l1Address: "",
1655
+ // Cannot derive without 33-byte pubkey
1656
+ directAddress: "",
1657
+ proxyAddress: `PROXY:${hashedNametag}`,
1658
+ timestamp: bindingEvent.created_at * 1e3
1659
+ };
1660
+ } catch {
1661
+ return {
1662
+ nametag,
1663
+ transportPubkey: bindingEvent.pubkey,
1664
+ chainPubkey: "",
1665
+ l1Address: "",
1666
+ directAddress: "",
1667
+ proxyAddress: `PROXY:${hashedNametag}`,
1668
+ timestamp: bindingEvent.created_at * 1e3
1669
+ };
1670
+ }
1671
+ }
1672
+ /**
1673
+ * Recover nametag for the current identity by searching for encrypted nametag events
1674
+ * Used after wallet import to recover associated nametag
1675
+ * @returns Decrypted nametag or null if none found
1676
+ */
1677
+ async recoverNametag() {
1678
+ this.ensureReady();
1679
+ if (!this.identity || !this.keyManager) {
1680
+ throw new Error("Identity not set");
1681
+ }
1682
+ const nostrPubkey = this.getNostrPubkey();
1683
+ this.log("Searching for nametag events for pubkey:", nostrPubkey.slice(0, 16) + "...");
1684
+ const events = await this.queryEvents({
1685
+ kinds: [EVENT_KINDS.NAMETAG_BINDING],
1686
+ authors: [nostrPubkey],
1687
+ limit: 10
1688
+ // Get recent events in case of updates
1689
+ });
1690
+ if (events.length === 0) {
1691
+ this.log("No nametag events found for this pubkey");
1692
+ return null;
1693
+ }
1694
+ events.sort((a, b) => b.created_at - a.created_at);
1695
+ for (const event of events) {
1696
+ try {
1697
+ const content = JSON.parse(event.content);
1698
+ if (content.encrypted_nametag) {
1699
+ const decrypted = await decryptNametag(
1700
+ content.encrypted_nametag,
1701
+ this.identity.privateKey
1702
+ );
1703
+ if (decrypted) {
1704
+ this.log("Recovered nametag:", decrypted);
1705
+ return decrypted;
1706
+ }
1707
+ }
1708
+ } catch {
1709
+ continue;
1710
+ }
1711
+ }
1712
+ this.log("Could not decrypt nametag from any event");
1713
+ return null;
1714
+ }
931
1715
  async publishNametag(nametag, address) {
932
1716
  this.ensureReady();
933
1717
  const hashedNametag = hashNametag(nametag);
@@ -938,8 +1722,11 @@ var NostrTransportProvider = class {
938
1722
  await this.publishEvent(event);
939
1723
  this.log("Published nametag binding:", nametag);
940
1724
  }
941
- async registerNametag(nametag, _publicKey) {
1725
+ async registerNametag(nametag, _publicKey, directAddress = "") {
942
1726
  this.ensureReady();
1727
+ if (!this.identity) {
1728
+ throw new Error("Identity not set");
1729
+ }
943
1730
  const nostrPubkey = this.getNostrPubkey();
944
1731
  const existing = await this.resolveNametag(nametag);
945
1732
  this.log("registerNametag:", nametag, "existing:", existing, "myPubkey:", nostrPubkey);
@@ -947,27 +1734,42 @@ var NostrTransportProvider = class {
947
1734
  this.log("Nametag already taken:", nametag, "- owner:", existing);
948
1735
  return false;
949
1736
  }
1737
+ const privateKeyHex = this.identity.privateKey;
1738
+ const compressedPubkey = getPublicKey(privateKeyHex, true);
1739
+ const l1Address = publicKeyToAddress(compressedPubkey, "alpha");
1740
+ const encryptedNametag = await encryptNametag(nametag, privateKeyHex);
950
1741
  const hashedNametag = hashNametag(nametag);
951
1742
  const content = JSON.stringify({
952
1743
  nametag_hash: hashedNametag,
953
1744
  address: nostrPubkey,
954
- verified: Date.now()
1745
+ verified: Date.now(),
1746
+ // Extended fields for nametag recovery and address lookup
1747
+ encrypted_nametag: encryptedNametag,
1748
+ public_key: compressedPubkey,
1749
+ l1_address: l1Address,
1750
+ direct_address: directAddress
955
1751
  });
956
1752
  const event = await this.createEvent(EVENT_KINDS.NAMETAG_BINDING, content, [
957
1753
  ["d", hashedNametag],
958
1754
  ["nametag", hashedNametag],
959
1755
  ["t", hashedNametag],
960
- ["address", nostrPubkey]
1756
+ ["address", nostrPubkey],
1757
+ // Extended tags for indexing
1758
+ ["pubkey", compressedPubkey],
1759
+ ["l1", l1Address]
961
1760
  ]);
962
1761
  await this.publishEvent(event);
963
- this.log("Registered nametag:", nametag, "for pubkey:", nostrPubkey.slice(0, 16) + "...");
1762
+ this.log("Registered nametag:", nametag, "for pubkey:", nostrPubkey.slice(0, 16) + "...", "l1:", l1Address.slice(0, 12) + "...");
964
1763
  return true;
965
1764
  }
1765
+ // Track broadcast subscriptions
1766
+ broadcastSubscriptions = /* @__PURE__ */ new Map();
1767
+ // key -> subId
966
1768
  subscribeToBroadcast(tags, handler) {
967
1769
  const key = tags.sort().join(":");
968
1770
  if (!this.broadcastHandlers.has(key)) {
969
1771
  this.broadcastHandlers.set(key, /* @__PURE__ */ new Set());
970
- if (this.isConnected()) {
1772
+ if (this.isConnected() && this.nostrClient) {
971
1773
  this.subscribeToTags(tags);
972
1774
  }
973
1775
  }
@@ -976,6 +1778,11 @@ var NostrTransportProvider = class {
976
1778
  this.broadcastHandlers.get(key)?.delete(handler);
977
1779
  if (this.broadcastHandlers.get(key)?.size === 0) {
978
1780
  this.broadcastHandlers.delete(key);
1781
+ const subId = this.broadcastSubscriptions.get(key);
1782
+ if (subId && this.nostrClient) {
1783
+ this.nostrClient.unsubscribe(subId);
1784
+ this.broadcastSubscriptions.delete(key);
1785
+ }
979
1786
  }
980
1787
  };
981
1788
  }
@@ -994,81 +1801,19 @@ var NostrTransportProvider = class {
994
1801
  return () => this.eventCallbacks.delete(callback);
995
1802
  }
996
1803
  // ===========================================================================
997
- // Private: Connection Management
998
- // ===========================================================================
999
- async connectToRelay(url) {
1000
- return new Promise((resolve, reject) => {
1001
- const ws = this.config.createWebSocket(url);
1002
- const timeout = setTimeout(() => {
1003
- ws.close();
1004
- reject(new Error(`Connection timeout: ${url}`));
1005
- }, this.config.timeout);
1006
- ws.onopen = () => {
1007
- clearTimeout(timeout);
1008
- this.connections.set(url, ws);
1009
- this.reconnectAttempts.set(url, 0);
1010
- this.log("Connected to relay:", url);
1011
- resolve();
1012
- };
1013
- ws.onerror = (error) => {
1014
- clearTimeout(timeout);
1015
- this.log("Relay error:", url, error);
1016
- reject(error);
1017
- };
1018
- ws.onclose = () => {
1019
- this.connections.delete(url);
1020
- if (this.config.autoReconnect && this.status === "connected") {
1021
- this.scheduleReconnect(url);
1022
- }
1023
- };
1024
- ws.onmessage = (event) => {
1025
- this.handleRelayMessage(url, event.data);
1026
- };
1027
- });
1028
- }
1029
- scheduleReconnect(url) {
1030
- const attempts = this.reconnectAttempts.get(url) ?? 0;
1031
- if (attempts >= this.config.maxReconnectAttempts) {
1032
- this.log("Max reconnect attempts reached for:", url);
1033
- return;
1034
- }
1035
- this.reconnectAttempts.set(url, attempts + 1);
1036
- const delay = this.config.reconnectDelay * Math.pow(2, attempts);
1037
- this.emitEvent({ type: "transport:reconnecting", timestamp: Date.now() });
1038
- setTimeout(() => {
1039
- this.connectToRelay(url).catch(() => {
1040
- });
1041
- }, delay);
1042
- }
1043
- // ===========================================================================
1044
1804
  // Private: Message Handling
1045
1805
  // ===========================================================================
1046
- handleRelayMessage(relay, data) {
1047
- try {
1048
- const message = JSON.parse(data);
1049
- const [type, ...args] = message;
1050
- switch (type) {
1051
- case "EVENT":
1052
- this.handleEvent(args[1]);
1053
- break;
1054
- case "EOSE":
1055
- break;
1056
- case "OK":
1057
- break;
1058
- case "NOTICE":
1059
- this.log("Relay notice:", relay, args[0]);
1060
- break;
1061
- }
1062
- } catch (error) {
1063
- this.log("Failed to parse relay message:", error);
1064
- }
1065
- }
1066
1806
  async handleEvent(event) {
1807
+ this.log("Processing event kind:", event.kind, "id:", event.id?.slice(0, 12));
1067
1808
  try {
1068
1809
  switch (event.kind) {
1069
1810
  case EVENT_KINDS.DIRECT_MESSAGE:
1070
1811
  await this.handleDirectMessage(event);
1071
1812
  break;
1813
+ case EventKinds.GIFT_WRAP:
1814
+ this.log("Handling gift wrap (NIP-17 DM)");
1815
+ await this.handleGiftWrap(event);
1816
+ break;
1072
1817
  case EVENT_KINDS.TOKEN_TRANSFER:
1073
1818
  await this.handleTokenTransfer(event);
1074
1819
  break;
@@ -1087,23 +1832,54 @@ var NostrTransportProvider = class {
1087
1832
  }
1088
1833
  }
1089
1834
  async handleDirectMessage(event) {
1090
- if (!this.identity || !this.keyManager) return;
1091
- if (event.pubkey === this.keyManager.getPublicKeyHex()) return;
1092
- const content = await this.decryptContent(event.content, event.pubkey);
1093
- const message = {
1094
- id: event.id,
1095
- senderPubkey: event.pubkey,
1096
- content,
1097
- timestamp: event.created_at * 1e3,
1098
- encrypted: true
1099
- };
1100
- this.emitEvent({ type: "message:received", timestamp: Date.now() });
1101
- for (const handler of this.messageHandlers) {
1835
+ this.log("Ignoring NIP-04 kind 4 event (DMs use NIP-17):", event.id?.slice(0, 12));
1836
+ }
1837
+ async handleGiftWrap(event) {
1838
+ if (!this.identity || !this.keyManager) {
1839
+ this.log("handleGiftWrap: no identity/keyManager");
1840
+ return;
1841
+ }
1842
+ try {
1843
+ const pm = NIP17.unwrap(event, this.keyManager);
1844
+ this.log("Gift wrap unwrapped, sender:", pm.senderPubkey?.slice(0, 16), "kind:", pm.kind);
1845
+ if (pm.senderPubkey === this.keyManager.getPublicKeyHex()) {
1846
+ this.log("Skipping own message");
1847
+ return;
1848
+ }
1849
+ if (pm.kind !== EventKinds.CHAT_MESSAGE) {
1850
+ this.log("Skipping non-chat message, kind:", pm.kind);
1851
+ return;
1852
+ }
1853
+ let content = pm.content;
1854
+ let senderNametag;
1102
1855
  try {
1103
- handler(message);
1104
- } catch (error) {
1105
- this.log("Message handler error:", error);
1856
+ const parsed = JSON.parse(content);
1857
+ if (typeof parsed === "object" && parsed.text !== void 0) {
1858
+ content = parsed.text;
1859
+ senderNametag = parsed.senderNametag || void 0;
1860
+ }
1861
+ } catch {
1862
+ }
1863
+ this.log("DM received from:", senderNametag || pm.senderPubkey?.slice(0, 16), "content:", content?.slice(0, 50));
1864
+ const message = {
1865
+ id: pm.eventId,
1866
+ senderTransportPubkey: pm.senderPubkey,
1867
+ senderNametag,
1868
+ content,
1869
+ timestamp: pm.timestamp * 1e3,
1870
+ encrypted: true
1871
+ };
1872
+ this.emitEvent({ type: "message:received", timestamp: Date.now() });
1873
+ this.log("Dispatching to", this.messageHandlers.size, "handlers");
1874
+ for (const handler of this.messageHandlers) {
1875
+ try {
1876
+ handler(message);
1877
+ } catch (error) {
1878
+ this.log("Message handler error:", error);
1879
+ }
1106
1880
  }
1881
+ } catch (err) {
1882
+ this.log("Gift wrap decrypt failed (expected if not for us):", err?.message?.slice(0, 50));
1107
1883
  }
1108
1884
  }
1109
1885
  async handleTokenTransfer(event) {
@@ -1112,7 +1888,7 @@ var NostrTransportProvider = class {
1112
1888
  const payload = JSON.parse(content);
1113
1889
  const transfer = {
1114
1890
  id: event.id,
1115
- senderPubkey: event.pubkey,
1891
+ senderTransportPubkey: event.pubkey,
1116
1892
  payload,
1117
1893
  timestamp: event.created_at * 1e3
1118
1894
  };
@@ -1132,7 +1908,7 @@ var NostrTransportProvider = class {
1132
1908
  const requestData = JSON.parse(content);
1133
1909
  const request = {
1134
1910
  id: event.id,
1135
- senderPubkey: event.pubkey,
1911
+ senderTransportPubkey: event.pubkey,
1136
1912
  request: {
1137
1913
  requestId: requestData.requestId,
1138
1914
  amount: requestData.amount,
@@ -1162,7 +1938,7 @@ var NostrTransportProvider = class {
1162
1938
  const responseData = JSON.parse(content);
1163
1939
  const response = {
1164
1940
  id: event.id,
1165
- responderPubkey: event.pubkey,
1941
+ responderTransportPubkey: event.pubkey,
1166
1942
  response: {
1167
1943
  requestId: responseData.requestId,
1168
1944
  responseType: responseData.responseType,
@@ -1187,7 +1963,7 @@ var NostrTransportProvider = class {
1187
1963
  const tags = event.tags.filter((t) => t[0] === "t").map((t) => t[1]);
1188
1964
  const broadcast = {
1189
1965
  id: event.id,
1190
- authorPubkey: event.pubkey,
1966
+ authorTransportPubkey: event.pubkey,
1191
1967
  content: event.content,
1192
1968
  tags,
1193
1969
  timestamp: event.created_at * 1e3
@@ -1242,109 +2018,149 @@ var NostrTransportProvider = class {
1242
2018
  return this.createEvent(kind, encrypted, tags);
1243
2019
  }
1244
2020
  async publishEvent(event) {
1245
- const message = JSON.stringify(["EVENT", event]);
1246
- const publishPromises = Array.from(this.connections.values()).map((ws) => {
1247
- return new Promise((resolve, reject) => {
1248
- if (ws.readyState !== WebSocketReadyState.OPEN) {
1249
- reject(new Error("WebSocket not open"));
1250
- return;
1251
- }
1252
- ws.send(message);
1253
- resolve();
1254
- });
1255
- });
1256
- await Promise.any(publishPromises);
2021
+ if (!this.nostrClient) {
2022
+ throw new Error("NostrClient not initialized");
2023
+ }
2024
+ const sdkEvent = NostrEventClass.fromJSON(event);
2025
+ await this.nostrClient.publishEvent(sdkEvent);
1257
2026
  }
1258
- async queryEvents(filter) {
1259
- if (this.connections.size === 0) {
2027
+ async queryEvents(filterObj) {
2028
+ if (!this.nostrClient || !this.nostrClient.isConnected()) {
1260
2029
  throw new Error("No connected relays");
1261
2030
  }
1262
- const queryPromises = Array.from(this.connections.values()).map(
1263
- (ws) => this.queryEventsFromRelay(ws, filter)
1264
- );
1265
- const results = await Promise.allSettled(queryPromises);
1266
- for (const result of results) {
1267
- if (result.status === "fulfilled" && result.value.length > 0) {
1268
- return result.value;
1269
- }
1270
- }
1271
- return [];
1272
- }
1273
- async queryEventsFromRelay(ws, filter) {
1274
- const subId = this.config.generateUUID().slice(0, 8);
1275
2031
  const events = [];
2032
+ const filter = new Filter(filterObj);
1276
2033
  return new Promise((resolve) => {
1277
2034
  const timeout = setTimeout(() => {
1278
- this.unsubscribeFromRelay(ws, subId);
2035
+ if (subId) {
2036
+ this.nostrClient?.unsubscribe(subId);
2037
+ }
1279
2038
  resolve(events);
1280
2039
  }, 5e3);
1281
- const originalHandler = ws.onmessage;
1282
- ws.onmessage = (event) => {
1283
- const message = JSON.parse(event.data);
1284
- const [type, sid, data] = message;
1285
- if (sid !== subId) {
1286
- originalHandler?.call(ws, event);
1287
- return;
1288
- }
1289
- if (type === "EVENT") {
1290
- events.push(data);
1291
- } else if (type === "EOSE") {
2040
+ const subId = this.nostrClient.subscribe(filter, {
2041
+ onEvent: (event) => {
2042
+ events.push({
2043
+ id: event.id,
2044
+ kind: event.kind,
2045
+ content: event.content,
2046
+ tags: event.tags,
2047
+ pubkey: event.pubkey,
2048
+ created_at: event.created_at,
2049
+ sig: event.sig
2050
+ });
2051
+ },
2052
+ onEndOfStoredEvents: () => {
1292
2053
  clearTimeout(timeout);
1293
- ws.onmessage = originalHandler;
1294
- this.unsubscribeFromRelay(ws, subId);
2054
+ this.nostrClient?.unsubscribe(subId);
1295
2055
  resolve(events);
1296
2056
  }
1297
- };
1298
- ws.send(JSON.stringify(["REQ", subId, filter]));
2057
+ });
1299
2058
  });
1300
2059
  }
1301
- unsubscribeFromRelay(ws, subId) {
1302
- if (ws.readyState === WebSocketReadyState.OPEN) {
1303
- ws.send(JSON.stringify(["CLOSE", subId]));
1304
- }
1305
- }
1306
2060
  // ===========================================================================
1307
2061
  // Private: Subscriptions
1308
2062
  // ===========================================================================
2063
+ // Track subscription IDs for cleanup
2064
+ walletSubscriptionId = null;
2065
+ chatSubscriptionId = null;
1309
2066
  subscribeToEvents() {
1310
- if (!this.identity || !this.keyManager) return;
1311
- const subId = "main";
2067
+ this.log("subscribeToEvents called, identity:", !!this.identity, "keyManager:", !!this.keyManager, "nostrClient:", !!this.nostrClient);
2068
+ if (!this.identity || !this.keyManager || !this.nostrClient) {
2069
+ this.log("subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
2070
+ return;
2071
+ }
2072
+ if (this.walletSubscriptionId) {
2073
+ this.nostrClient.unsubscribe(this.walletSubscriptionId);
2074
+ this.walletSubscriptionId = null;
2075
+ }
2076
+ if (this.chatSubscriptionId) {
2077
+ this.nostrClient.unsubscribe(this.chatSubscriptionId);
2078
+ this.chatSubscriptionId = null;
2079
+ }
2080
+ if (this.mainSubscriptionId) {
2081
+ this.nostrClient.unsubscribe(this.mainSubscriptionId);
2082
+ this.mainSubscriptionId = null;
2083
+ }
1312
2084
  const nostrPubkey = this.keyManager.getPublicKeyHex();
1313
- const filter = {
1314
- kinds: [
1315
- EVENT_KINDS.DIRECT_MESSAGE,
1316
- EVENT_KINDS.TOKEN_TRANSFER,
1317
- EVENT_KINDS.PAYMENT_REQUEST,
1318
- EVENT_KINDS.PAYMENT_REQUEST_RESPONSE
1319
- ],
1320
- "#p": [nostrPubkey],
1321
- since: Math.floor(Date.now() / 1e3) - 86400
1322
- // Last 24h
1323
- };
1324
- const message = JSON.stringify(["REQ", subId, filter]);
1325
- for (const ws of this.connections.values()) {
1326
- if (ws.readyState === WebSocketReadyState.OPEN) {
1327
- ws.send(message);
2085
+ this.log("Subscribing with Nostr pubkey:", nostrPubkey);
2086
+ const walletFilter = new Filter();
2087
+ walletFilter.kinds = [
2088
+ EVENT_KINDS.DIRECT_MESSAGE,
2089
+ EVENT_KINDS.TOKEN_TRANSFER,
2090
+ EVENT_KINDS.PAYMENT_REQUEST,
2091
+ EVENT_KINDS.PAYMENT_REQUEST_RESPONSE
2092
+ ];
2093
+ walletFilter["#p"] = [nostrPubkey];
2094
+ walletFilter.since = Math.floor(Date.now() / 1e3) - 86400;
2095
+ this.walletSubscriptionId = this.nostrClient.subscribe(walletFilter, {
2096
+ onEvent: (event) => {
2097
+ this.log("Received wallet event kind:", event.kind, "id:", event.id?.slice(0, 12));
2098
+ this.handleEvent({
2099
+ id: event.id,
2100
+ kind: event.kind,
2101
+ content: event.content,
2102
+ tags: event.tags,
2103
+ pubkey: event.pubkey,
2104
+ created_at: event.created_at,
2105
+ sig: event.sig
2106
+ });
2107
+ },
2108
+ onEndOfStoredEvents: () => {
2109
+ this.log("Wallet subscription ready (EOSE)");
2110
+ },
2111
+ onError: (_subId, error) => {
2112
+ this.log("Wallet subscription error:", error);
1328
2113
  }
1329
- }
1330
- this.subscriptions.set(subId, Array.from(this.connections.keys()));
1331
- this.log("Subscribed to events");
2114
+ });
2115
+ this.log("Wallet subscription created, subId:", this.walletSubscriptionId);
2116
+ const chatFilter = new Filter();
2117
+ chatFilter.kinds = [EventKinds.GIFT_WRAP];
2118
+ chatFilter["#p"] = [nostrPubkey];
2119
+ this.chatSubscriptionId = this.nostrClient.subscribe(chatFilter, {
2120
+ onEvent: (event) => {
2121
+ this.log("Received chat event kind:", event.kind, "id:", event.id?.slice(0, 12));
2122
+ this.handleEvent({
2123
+ id: event.id,
2124
+ kind: event.kind,
2125
+ content: event.content,
2126
+ tags: event.tags,
2127
+ pubkey: event.pubkey,
2128
+ created_at: event.created_at,
2129
+ sig: event.sig
2130
+ });
2131
+ },
2132
+ onEndOfStoredEvents: () => {
2133
+ this.log("Chat subscription ready (EOSE)");
2134
+ },
2135
+ onError: (_subId, error) => {
2136
+ this.log("Chat subscription error:", error);
2137
+ }
2138
+ });
2139
+ this.log("Chat subscription created, subId:", this.chatSubscriptionId);
1332
2140
  }
1333
2141
  subscribeToTags(tags) {
1334
- const subId = `tags:${tags.join(":")}`;
1335
- const filter = {
2142
+ if (!this.nostrClient) return;
2143
+ const key = tags.sort().join(":");
2144
+ const filter = new Filter({
1336
2145
  kinds: [EVENT_KINDS.BROADCAST],
1337
2146
  "#t": tags,
1338
2147
  since: Math.floor(Date.now() / 1e3) - 3600
1339
2148
  // Last hour
1340
- };
1341
- const message = JSON.stringify(["REQ", subId, filter]);
1342
- for (const ws of this.connections.values()) {
1343
- if (ws.readyState === WebSocketReadyState.OPEN) {
1344
- ws.send(message);
2149
+ });
2150
+ const subId = this.nostrClient.subscribe(filter, {
2151
+ onEvent: (event) => {
2152
+ this.handleBroadcast({
2153
+ id: event.id,
2154
+ kind: event.kind,
2155
+ content: event.content,
2156
+ tags: event.tags,
2157
+ pubkey: event.pubkey,
2158
+ created_at: event.created_at,
2159
+ sig: event.sig
2160
+ });
1345
2161
  }
1346
- }
1347
- this.subscriptions.set(subId, Array.from(this.connections.keys()));
2162
+ });
2163
+ this.broadcastSubscriptions.set(key, subId);
1348
2164
  }
1349
2165
  // ===========================================================================
1350
2166
  // Private: Encryption
@@ -1800,13 +2616,70 @@ var UnicityAggregatorProvider = class {
1800
2616
  };
1801
2617
  var UnicityOracleProvider = UnicityAggregatorProvider;
1802
2618
 
2619
+ // assets/trustbase.ts
2620
+ var TRUSTBASE_TESTNET = {
2621
+ version: 1,
2622
+ networkId: 3,
2623
+ epoch: 1,
2624
+ epochStartRound: 1,
2625
+ rootNodes: [
2626
+ {
2627
+ nodeId: "16Uiu2HAkyQRiA7pMgzgLj9GgaBJEJa8zmx9dzqUDa6WxQPJ82ghU",
2628
+ sigKey: "0x039afb2acb65f5fbc272d8907f763d0a5d189aadc9b97afdcc5897ea4dd112e68b",
2629
+ stake: 1
2630
+ }
2631
+ ],
2632
+ quorumThreshold: 1,
2633
+ stateHash: "",
2634
+ changeRecordHash: "",
2635
+ previousEntryHash: "",
2636
+ signatures: {
2637
+ "16Uiu2HAkyQRiA7pMgzgLj9GgaBJEJa8zmx9dzqUDa6WxQPJ82ghU": "0xf157c9fdd8a378e3ca70d354ccc4475ab2cd8de360127bc46b0aeab4b453a80f07fd9136a5843b60a8babaff23e20acc8879861f7651440a5e2829f7541b31f100"
2638
+ }
2639
+ };
2640
+ var TRUSTBASE_MAINNET = null;
2641
+ var TRUSTBASE_DEV = TRUSTBASE_TESTNET;
2642
+
2643
+ // impl/shared/trustbase-loader.ts
2644
+ function getEmbeddedTrustBase(network) {
2645
+ switch (network) {
2646
+ case "mainnet":
2647
+ return TRUSTBASE_MAINNET;
2648
+ case "testnet":
2649
+ return TRUSTBASE_TESTNET;
2650
+ case "dev":
2651
+ return TRUSTBASE_DEV;
2652
+ default:
2653
+ return TRUSTBASE_TESTNET;
2654
+ }
2655
+ }
2656
+ var BaseTrustBaseLoader = class {
2657
+ network;
2658
+ constructor(network = "testnet") {
2659
+ this.network = network;
2660
+ }
2661
+ async load() {
2662
+ const external = await this.loadFromExternal();
2663
+ if (external) {
2664
+ return external;
2665
+ }
2666
+ return getEmbeddedTrustBase(this.network);
2667
+ }
2668
+ };
2669
+
1803
2670
  // impl/browser/oracle/index.ts
1804
- var BrowserTrustBaseLoader = class {
2671
+ var BrowserTrustBaseLoader = class extends BaseTrustBaseLoader {
1805
2672
  url;
1806
- constructor(url = "/trustbase-testnet.json") {
1807
- this.url = url;
2673
+ constructor(networkOrUrl = "testnet") {
2674
+ if (networkOrUrl.startsWith("/") || networkOrUrl.startsWith("http")) {
2675
+ super("testnet");
2676
+ this.url = networkOrUrl;
2677
+ } else {
2678
+ super(networkOrUrl);
2679
+ }
1808
2680
  }
1809
- async load() {
2681
+ async loadFromExternal() {
2682
+ if (!this.url) return null;
1810
2683
  try {
1811
2684
  const response = await fetch(this.url);
1812
2685
  if (response.ok) {
@@ -1817,14 +2690,14 @@ var BrowserTrustBaseLoader = class {
1817
2690
  return null;
1818
2691
  }
1819
2692
  };
1820
- function createBrowserTrustBaseLoader(url) {
1821
- return new BrowserTrustBaseLoader(url);
2693
+ function createBrowserTrustBaseLoader(networkOrUrl) {
2694
+ return new BrowserTrustBaseLoader(networkOrUrl);
1822
2695
  }
1823
2696
  function createUnicityAggregatorProvider(config) {
1824
- const { trustBaseUrl, ...restConfig } = config;
2697
+ const { trustBaseUrl, network, ...restConfig } = config;
1825
2698
  return new UnicityAggregatorProvider({
1826
2699
  ...restConfig,
1827
- trustBaseLoader: createBrowserTrustBaseLoader(trustBaseUrl)
2700
+ trustBaseLoader: createBrowserTrustBaseLoader(trustBaseUrl ?? network ?? "testnet")
1828
2701
  });
1829
2702
  }
1830
2703
  var createUnicityOracleProvider = createUnicityAggregatorProvider;
@@ -1918,7 +2791,7 @@ function resolveOracleConfig(network, config) {
1918
2791
  const networkConfig = getNetworkConfig(network);
1919
2792
  return {
1920
2793
  url: config?.url ?? networkConfig.aggregatorUrl,
1921
- apiKey: config?.apiKey,
2794
+ apiKey: config?.apiKey ?? DEFAULT_AGGREGATOR_API_KEY,
1922
2795
  timeout: config?.timeout,
1923
2796
  skipVerification: config?.skipVerification,
1924
2797
  debug: config?.debug,
@@ -2016,7 +2889,8 @@ function createBrowserProviders(config) {
2016
2889
  apiKey: oracleConfig.apiKey,
2017
2890
  timeout: oracleConfig.timeout,
2018
2891
  skipVerification: oracleConfig.skipVerification,
2019
- debug: oracleConfig.debug
2892
+ debug: oracleConfig.debug,
2893
+ network
2020
2894
  }),
2021
2895
  tokenStorage: createIndexedDBTokenStorageProvider(),
2022
2896
  l1: l1Config,
@@ -2050,4 +2924,9 @@ export {
2050
2924
  readFileAsText,
2051
2925
  readFileAsUint8Array
2052
2926
  };
2927
+ /*! Bundled license information:
2928
+
2929
+ @noble/hashes/utils.js:
2930
+ (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
2931
+ */
2053
2932
  //# sourceMappingURL=index.js.map