@unicitylabs/sphere-sdk 0.4.6 → 0.4.8

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.
@@ -30,6 +30,175 @@ var __toESM = (mod2, isNodeMode, target) => (target = mod2 != null ? __create(__
30
30
  ));
31
31
  var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2);
32
32
 
33
+ // constants.ts
34
+ function getAddressId(directAddress) {
35
+ let hash = directAddress;
36
+ if (hash.startsWith("DIRECT://")) {
37
+ hash = hash.slice(9);
38
+ } else if (hash.startsWith("DIRECT:")) {
39
+ hash = hash.slice(7);
40
+ }
41
+ const first = hash.slice(0, 6).toLowerCase();
42
+ const last = hash.slice(-6).toLowerCase();
43
+ return `DIRECT_${first}_${last}`;
44
+ }
45
+ var DEFAULT_ENCRYPTION_KEY, STORAGE_KEYS_GLOBAL, STORAGE_KEYS_ADDRESS, STORAGE_KEYS, DEFAULT_NOSTR_RELAYS, NIP29_KINDS, DEFAULT_AGGREGATOR_URL, DEV_AGGREGATOR_URL, TEST_AGGREGATOR_URL, DEFAULT_IPFS_GATEWAYS, DEFAULT_BASE_PATH, DEFAULT_DERIVATION_PATH, DEFAULT_ELECTRUM_URL, TEST_ELECTRUM_URL, TOKEN_REGISTRY_URL, TOKEN_REGISTRY_REFRESH_INTERVAL, TEST_NOSTR_RELAYS, DEFAULT_GROUP_RELAYS, NETWORKS;
46
+ var init_constants = __esm({
47
+ "constants.ts"() {
48
+ "use strict";
49
+ DEFAULT_ENCRYPTION_KEY = "sphere-default-key";
50
+ STORAGE_KEYS_GLOBAL = {
51
+ /** Encrypted BIP39 mnemonic */
52
+ MNEMONIC: "mnemonic",
53
+ /** Encrypted master private key */
54
+ MASTER_KEY: "master_key",
55
+ /** BIP32 chain code */
56
+ CHAIN_CODE: "chain_code",
57
+ /** HD derivation path (full path like m/44'/0'/0'/0/0) */
58
+ DERIVATION_PATH: "derivation_path",
59
+ /** Base derivation path (like m/44'/0'/0' without chain/index) */
60
+ BASE_PATH: "base_path",
61
+ /** Derivation mode: bip32, wif_hmac, legacy_hmac */
62
+ DERIVATION_MODE: "derivation_mode",
63
+ /** Wallet source: mnemonic, file, unknown */
64
+ WALLET_SOURCE: "wallet_source",
65
+ /** Wallet existence flag */
66
+ WALLET_EXISTS: "wallet_exists",
67
+ /** Current active address index */
68
+ CURRENT_ADDRESS_INDEX: "current_address_index",
69
+ /** Nametag cache per address (separate from tracked addresses registry) */
70
+ ADDRESS_NAMETAGS: "address_nametags",
71
+ /** Active addresses registry (JSON: TrackedAddressesStorage) */
72
+ TRACKED_ADDRESSES: "tracked_addresses",
73
+ /** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */
74
+ LAST_WALLET_EVENT_TS: "last_wallet_event_ts",
75
+ /** Group chat: last used relay URL (stale data detection) — global, same relay for all addresses */
76
+ GROUP_CHAT_RELAY_URL: "group_chat_relay_url",
77
+ /** Cached token registry JSON (fetched from remote) */
78
+ TOKEN_REGISTRY_CACHE: "token_registry_cache",
79
+ /** Timestamp of last token registry cache update (ms since epoch) */
80
+ TOKEN_REGISTRY_CACHE_TS: "token_registry_cache_ts",
81
+ /** Cached price data JSON (from CoinGecko or other provider) */
82
+ PRICE_CACHE: "price_cache",
83
+ /** Timestamp of last price cache update (ms since epoch) */
84
+ PRICE_CACHE_TS: "price_cache_ts"
85
+ };
86
+ STORAGE_KEYS_ADDRESS = {
87
+ /** Pending transfers for this address */
88
+ PENDING_TRANSFERS: "pending_transfers",
89
+ /** Transfer outbox for this address */
90
+ OUTBOX: "outbox",
91
+ /** Conversations for this address */
92
+ CONVERSATIONS: "conversations",
93
+ /** Messages for this address */
94
+ MESSAGES: "messages",
95
+ /** Transaction history for this address */
96
+ TRANSACTION_HISTORY: "transaction_history",
97
+ /** Pending V5 finalization tokens (unconfirmed instant split tokens) */
98
+ PENDING_V5_TOKENS: "pending_v5_tokens",
99
+ /** Group chat: joined groups for this address */
100
+ GROUP_CHAT_GROUPS: "group_chat_groups",
101
+ /** Group chat: messages for this address */
102
+ GROUP_CHAT_MESSAGES: "group_chat_messages",
103
+ /** Group chat: members for this address */
104
+ GROUP_CHAT_MEMBERS: "group_chat_members",
105
+ /** Group chat: processed event IDs for deduplication */
106
+ GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events"
107
+ };
108
+ STORAGE_KEYS = {
109
+ ...STORAGE_KEYS_GLOBAL,
110
+ ...STORAGE_KEYS_ADDRESS
111
+ };
112
+ DEFAULT_NOSTR_RELAYS = [
113
+ "wss://relay.unicity.network",
114
+ "wss://relay.damus.io",
115
+ "wss://nos.lol",
116
+ "wss://relay.nostr.band"
117
+ ];
118
+ NIP29_KINDS = {
119
+ /** Chat message sent to group */
120
+ CHAT_MESSAGE: 9,
121
+ /** Thread root message */
122
+ THREAD_ROOT: 11,
123
+ /** Thread reply message */
124
+ THREAD_REPLY: 12,
125
+ /** User join request */
126
+ JOIN_REQUEST: 9021,
127
+ /** User leave request */
128
+ LEAVE_REQUEST: 9022,
129
+ /** Admin: add/update user */
130
+ PUT_USER: 9e3,
131
+ /** Admin: remove user */
132
+ REMOVE_USER: 9001,
133
+ /** Admin: edit group metadata */
134
+ EDIT_METADATA: 9002,
135
+ /** Admin: delete event */
136
+ DELETE_EVENT: 9005,
137
+ /** Admin: create group */
138
+ CREATE_GROUP: 9007,
139
+ /** Admin: delete group */
140
+ DELETE_GROUP: 9008,
141
+ /** Admin: create invite code */
142
+ CREATE_INVITE: 9009,
143
+ /** Relay-signed group metadata */
144
+ GROUP_METADATA: 39e3,
145
+ /** Relay-signed group admins */
146
+ GROUP_ADMINS: 39001,
147
+ /** Relay-signed group members */
148
+ GROUP_MEMBERS: 39002,
149
+ /** Relay-signed group roles */
150
+ GROUP_ROLES: 39003
151
+ };
152
+ DEFAULT_AGGREGATOR_URL = "https://aggregator.unicity.network/rpc";
153
+ DEV_AGGREGATOR_URL = "https://dev-aggregator.dyndns.org/rpc";
154
+ TEST_AGGREGATOR_URL = "https://goggregator-test.unicity.network";
155
+ DEFAULT_IPFS_GATEWAYS = [
156
+ "https://unicity-ipfs1.dyndns.org"
157
+ ];
158
+ DEFAULT_BASE_PATH = "m/44'/0'/0'";
159
+ DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0`;
160
+ DEFAULT_ELECTRUM_URL = "wss://fulcrum.unicity.network:50004";
161
+ TEST_ELECTRUM_URL = "wss://fulcrum.unicity.network:50004";
162
+ TOKEN_REGISTRY_URL = "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json";
163
+ TOKEN_REGISTRY_REFRESH_INTERVAL = 36e5;
164
+ TEST_NOSTR_RELAYS = [
165
+ "wss://nostr-relay.testnet.unicity.network"
166
+ ];
167
+ DEFAULT_GROUP_RELAYS = [
168
+ "wss://sphere-relay.unicity.network"
169
+ ];
170
+ NETWORKS = {
171
+ mainnet: {
172
+ name: "Mainnet",
173
+ aggregatorUrl: DEFAULT_AGGREGATOR_URL,
174
+ nostrRelays: DEFAULT_NOSTR_RELAYS,
175
+ ipfsGateways: DEFAULT_IPFS_GATEWAYS,
176
+ electrumUrl: DEFAULT_ELECTRUM_URL,
177
+ groupRelays: DEFAULT_GROUP_RELAYS,
178
+ tokenRegistryUrl: TOKEN_REGISTRY_URL
179
+ },
180
+ testnet: {
181
+ name: "Testnet",
182
+ aggregatorUrl: TEST_AGGREGATOR_URL,
183
+ nostrRelays: TEST_NOSTR_RELAYS,
184
+ ipfsGateways: DEFAULT_IPFS_GATEWAYS,
185
+ electrumUrl: TEST_ELECTRUM_URL,
186
+ groupRelays: DEFAULT_GROUP_RELAYS,
187
+ tokenRegistryUrl: TOKEN_REGISTRY_URL
188
+ },
189
+ dev: {
190
+ name: "Development",
191
+ aggregatorUrl: DEV_AGGREGATOR_URL,
192
+ nostrRelays: TEST_NOSTR_RELAYS,
193
+ ipfsGateways: DEFAULT_IPFS_GATEWAYS,
194
+ electrumUrl: TEST_ELECTRUM_URL,
195
+ groupRelays: DEFAULT_GROUP_RELAYS,
196
+ tokenRegistryUrl: TOKEN_REGISTRY_URL
197
+ }
198
+ };
199
+ }
200
+ });
201
+
33
202
  // core/bech32.ts
34
203
  function convertBits(data, fromBits, toBits, pad) {
35
204
  let acc = 0;
@@ -447,7 +616,8 @@ var init_network = __esm({
447
616
  "l1/network.ts"() {
448
617
  "use strict";
449
618
  init_addressToScriptHash();
450
- DEFAULT_ENDPOINT = "wss://fulcrum.unicity.network:50004";
619
+ init_constants();
620
+ DEFAULT_ENDPOINT = DEFAULT_ELECTRUM_URL;
451
621
  ws = null;
452
622
  isConnected = false;
453
623
  isConnecting = false;
@@ -472,7 +642,7 @@ var core_exports = {};
472
642
  __export(core_exports, {
473
643
  CHARSET: () => CHARSET,
474
644
  CurrencyUtils: () => CurrencyUtils,
475
- DEFAULT_DERIVATION_PATH: () => DEFAULT_DERIVATION_PATH,
645
+ DEFAULT_DERIVATION_PATH: () => DEFAULT_DERIVATION_PATH2,
476
646
  DEFAULT_TOKEN_DECIMALS: () => DEFAULT_TOKEN_DECIMALS,
477
647
  Sphere: () => Sphere,
478
648
  base58Decode: () => base58Decode,
@@ -495,6 +665,7 @@ __export(core_exports, {
495
665
  deriveChildKey: () => deriveChildKey,
496
666
  deriveKeyAtPath: () => deriveKeyAtPath,
497
667
  deserializeEncrypted: () => deserializeEncrypted,
668
+ discoverAddressesImpl: () => discoverAddressesImpl,
498
669
  doubleSha256: () => doubleSha256,
499
670
  ec: () => ec,
500
671
  encodeBech32: () => encodeBech32,
@@ -544,6 +715,9 @@ __export(core_exports, {
544
715
  });
545
716
  module.exports = __toCommonJS(core_exports);
546
717
 
718
+ // modules/payments/L1PaymentsModule.ts
719
+ init_constants();
720
+
547
721
  // l1/index.ts
548
722
  init_bech32();
549
723
  init_addressToScriptHash();
@@ -557,7 +731,7 @@ var ec = new import_elliptic.default.ec("secp256k1");
557
731
  var CURVE_ORDER = BigInt(
558
732
  "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
559
733
  );
560
- var DEFAULT_DERIVATION_PATH = "m/44'/0'/0'";
734
+ var DEFAULT_DERIVATION_PATH2 = "m/44'/0'/0'";
561
735
  function generateMnemonic2(strength = 128) {
562
736
  return bip39.generateMnemonic(strength);
563
737
  }
@@ -1578,7 +1752,7 @@ var L1PaymentsModule = class {
1578
1752
  _transport;
1579
1753
  constructor(config) {
1580
1754
  this._config = {
1581
- electrumUrl: config?.electrumUrl ?? "wss://fulcrum.unicity.network:50004",
1755
+ electrumUrl: config?.electrumUrl ?? DEFAULT_ELECTRUM_URL,
1582
1756
  network: config?.network ?? "mainnet",
1583
1757
  defaultFeeRate: config?.defaultFeeRate ?? 10,
1584
1758
  enableVesting: config?.enableVesting ?? true
@@ -2415,168 +2589,8 @@ var NametagMinter = class {
2415
2589
  }
2416
2590
  };
2417
2591
 
2418
- // constants.ts
2419
- var DEFAULT_ENCRYPTION_KEY = "sphere-default-key";
2420
- var STORAGE_KEYS_GLOBAL = {
2421
- /** Encrypted BIP39 mnemonic */
2422
- MNEMONIC: "mnemonic",
2423
- /** Encrypted master private key */
2424
- MASTER_KEY: "master_key",
2425
- /** BIP32 chain code */
2426
- CHAIN_CODE: "chain_code",
2427
- /** HD derivation path (full path like m/44'/0'/0'/0/0) */
2428
- DERIVATION_PATH: "derivation_path",
2429
- /** Base derivation path (like m/44'/0'/0' without chain/index) */
2430
- BASE_PATH: "base_path",
2431
- /** Derivation mode: bip32, wif_hmac, legacy_hmac */
2432
- DERIVATION_MODE: "derivation_mode",
2433
- /** Wallet source: mnemonic, file, unknown */
2434
- WALLET_SOURCE: "wallet_source",
2435
- /** Wallet existence flag */
2436
- WALLET_EXISTS: "wallet_exists",
2437
- /** Current active address index */
2438
- CURRENT_ADDRESS_INDEX: "current_address_index",
2439
- /** Nametag cache per address (separate from tracked addresses registry) */
2440
- ADDRESS_NAMETAGS: "address_nametags",
2441
- /** Active addresses registry (JSON: TrackedAddressesStorage) */
2442
- TRACKED_ADDRESSES: "tracked_addresses",
2443
- /** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */
2444
- LAST_WALLET_EVENT_TS: "last_wallet_event_ts",
2445
- /** Group chat: last used relay URL (stale data detection) — global, same relay for all addresses */
2446
- GROUP_CHAT_RELAY_URL: "group_chat_relay_url",
2447
- /** Cached token registry JSON (fetched from remote) */
2448
- TOKEN_REGISTRY_CACHE: "token_registry_cache",
2449
- /** Timestamp of last token registry cache update (ms since epoch) */
2450
- TOKEN_REGISTRY_CACHE_TS: "token_registry_cache_ts",
2451
- /** Cached price data JSON (from CoinGecko or other provider) */
2452
- PRICE_CACHE: "price_cache",
2453
- /** Timestamp of last price cache update (ms since epoch) */
2454
- PRICE_CACHE_TS: "price_cache_ts"
2455
- };
2456
- var STORAGE_KEYS_ADDRESS = {
2457
- /** Pending transfers for this address */
2458
- PENDING_TRANSFERS: "pending_transfers",
2459
- /** Transfer outbox for this address */
2460
- OUTBOX: "outbox",
2461
- /** Conversations for this address */
2462
- CONVERSATIONS: "conversations",
2463
- /** Messages for this address */
2464
- MESSAGES: "messages",
2465
- /** Transaction history for this address */
2466
- TRANSACTION_HISTORY: "transaction_history",
2467
- /** Pending V5 finalization tokens (unconfirmed instant split tokens) */
2468
- PENDING_V5_TOKENS: "pending_v5_tokens",
2469
- /** Group chat: joined groups for this address */
2470
- GROUP_CHAT_GROUPS: "group_chat_groups",
2471
- /** Group chat: messages for this address */
2472
- GROUP_CHAT_MESSAGES: "group_chat_messages",
2473
- /** Group chat: members for this address */
2474
- GROUP_CHAT_MEMBERS: "group_chat_members",
2475
- /** Group chat: processed event IDs for deduplication */
2476
- GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events"
2477
- };
2478
- var STORAGE_KEYS = {
2479
- ...STORAGE_KEYS_GLOBAL,
2480
- ...STORAGE_KEYS_ADDRESS
2481
- };
2482
- function getAddressId(directAddress) {
2483
- let hash = directAddress;
2484
- if (hash.startsWith("DIRECT://")) {
2485
- hash = hash.slice(9);
2486
- } else if (hash.startsWith("DIRECT:")) {
2487
- hash = hash.slice(7);
2488
- }
2489
- const first = hash.slice(0, 6).toLowerCase();
2490
- const last = hash.slice(-6).toLowerCase();
2491
- return `DIRECT_${first}_${last}`;
2492
- }
2493
- var DEFAULT_NOSTR_RELAYS = [
2494
- "wss://relay.unicity.network",
2495
- "wss://relay.damus.io",
2496
- "wss://nos.lol",
2497
- "wss://relay.nostr.band"
2498
- ];
2499
- var NIP29_KINDS = {
2500
- /** Chat message sent to group */
2501
- CHAT_MESSAGE: 9,
2502
- /** Thread root message */
2503
- THREAD_ROOT: 11,
2504
- /** Thread reply message */
2505
- THREAD_REPLY: 12,
2506
- /** User join request */
2507
- JOIN_REQUEST: 9021,
2508
- /** User leave request */
2509
- LEAVE_REQUEST: 9022,
2510
- /** Admin: add/update user */
2511
- PUT_USER: 9e3,
2512
- /** Admin: remove user */
2513
- REMOVE_USER: 9001,
2514
- /** Admin: edit group metadata */
2515
- EDIT_METADATA: 9002,
2516
- /** Admin: delete event */
2517
- DELETE_EVENT: 9005,
2518
- /** Admin: create group */
2519
- CREATE_GROUP: 9007,
2520
- /** Admin: delete group */
2521
- DELETE_GROUP: 9008,
2522
- /** Admin: create invite code */
2523
- CREATE_INVITE: 9009,
2524
- /** Relay-signed group metadata */
2525
- GROUP_METADATA: 39e3,
2526
- /** Relay-signed group admins */
2527
- GROUP_ADMINS: 39001,
2528
- /** Relay-signed group members */
2529
- GROUP_MEMBERS: 39002,
2530
- /** Relay-signed group roles */
2531
- GROUP_ROLES: 39003
2532
- };
2533
- var DEFAULT_AGGREGATOR_URL = "https://aggregator.unicity.network/rpc";
2534
- var DEV_AGGREGATOR_URL = "https://dev-aggregator.dyndns.org/rpc";
2535
- var TEST_AGGREGATOR_URL = "https://goggregator-test.unicity.network";
2536
- var DEFAULT_IPFS_GATEWAYS = [
2537
- "https://unicity-ipfs1.dyndns.org"
2538
- ];
2539
- var DEFAULT_BASE_PATH = "m/44'/0'/0'";
2540
- var DEFAULT_DERIVATION_PATH2 = `${DEFAULT_BASE_PATH}/0/0`;
2541
- var DEFAULT_ELECTRUM_URL = "wss://fulcrum.alpha.unicity.network:50004";
2542
- var TEST_ELECTRUM_URL = "wss://fulcrum.alpha.testnet.unicity.network:50004";
2543
- var TOKEN_REGISTRY_URL = "https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json";
2544
- var TOKEN_REGISTRY_REFRESH_INTERVAL = 36e5;
2545
- var TEST_NOSTR_RELAYS = [
2546
- "wss://nostr-relay.testnet.unicity.network"
2547
- ];
2548
- var DEFAULT_GROUP_RELAYS = [
2549
- "wss://sphere-relay.unicity.network"
2550
- ];
2551
- var NETWORKS = {
2552
- mainnet: {
2553
- name: "Mainnet",
2554
- aggregatorUrl: DEFAULT_AGGREGATOR_URL,
2555
- nostrRelays: DEFAULT_NOSTR_RELAYS,
2556
- ipfsGateways: DEFAULT_IPFS_GATEWAYS,
2557
- electrumUrl: DEFAULT_ELECTRUM_URL,
2558
- groupRelays: DEFAULT_GROUP_RELAYS,
2559
- tokenRegistryUrl: TOKEN_REGISTRY_URL
2560
- },
2561
- testnet: {
2562
- name: "Testnet",
2563
- aggregatorUrl: TEST_AGGREGATOR_URL,
2564
- nostrRelays: TEST_NOSTR_RELAYS,
2565
- ipfsGateways: DEFAULT_IPFS_GATEWAYS,
2566
- electrumUrl: TEST_ELECTRUM_URL,
2567
- groupRelays: DEFAULT_GROUP_RELAYS,
2568
- tokenRegistryUrl: TOKEN_REGISTRY_URL
2569
- },
2570
- dev: {
2571
- name: "Development",
2572
- aggregatorUrl: DEV_AGGREGATOR_URL,
2573
- nostrRelays: TEST_NOSTR_RELAYS,
2574
- ipfsGateways: DEFAULT_IPFS_GATEWAYS,
2575
- electrumUrl: TEST_ELECTRUM_URL,
2576
- groupRelays: DEFAULT_GROUP_RELAYS,
2577
- tokenRegistryUrl: TOKEN_REGISTRY_URL
2578
- }
2579
- };
2592
+ // modules/payments/PaymentsModule.ts
2593
+ init_constants();
2580
2594
 
2581
2595
  // types/txf.ts
2582
2596
  var ARCHIVED_PREFIX = "archived-";
@@ -2618,6 +2632,7 @@ function parseForkedKey(key) {
2618
2632
  }
2619
2633
 
2620
2634
  // registry/TokenRegistry.ts
2635
+ init_constants();
2621
2636
  var FETCH_TIMEOUT_MS = 1e4;
2622
2637
  var TokenRegistry = class _TokenRegistry {
2623
2638
  static instance = null;
@@ -7430,6 +7445,7 @@ var import_HashAlgorithm6 = require("@unicitylabs/state-transition-sdk/lib/hash/
7430
7445
  var import_UnmaskedPredicate6 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
7431
7446
 
7432
7447
  // modules/communications/CommunicationsModule.ts
7448
+ init_constants();
7433
7449
  var CommunicationsModule = class {
7434
7450
  config;
7435
7451
  deps = null;
@@ -7905,6 +7921,7 @@ function createCommunicationsModule(config) {
7905
7921
 
7906
7922
  // modules/groupchat/GroupChatModule.ts
7907
7923
  var import_nostr_js_sdk2 = require("@unicitylabs/nostr-js-sdk");
7924
+ init_constants();
7908
7925
 
7909
7926
  // modules/groupchat/types.ts
7910
7927
  var GroupRole = {
@@ -8155,10 +8172,12 @@ var GroupChatModule = class {
8155
8172
  if (!this.client) return;
8156
8173
  const groupIds = Array.from(this.groups.keys());
8157
8174
  if (groupIds.length === 0) return;
8175
+ const latestTimestamp = this.getLatestMessageTimestamp(groupIds);
8158
8176
  this.trackSubscription(
8159
8177
  createNip29Filter({
8160
8178
  kinds: [NIP29_KINDS.CHAT_MESSAGE, NIP29_KINDS.THREAD_ROOT, NIP29_KINDS.THREAD_REPLY],
8161
- "#h": groupIds
8179
+ "#h": groupIds,
8180
+ ...latestTimestamp ? { since: latestTimestamp } : {}
8162
8181
  }),
8163
8182
  { onEvent: (event) => this.handleGroupEvent(event) }
8164
8183
  );
@@ -8179,10 +8198,12 @@ var GroupChatModule = class {
8179
8198
  }
8180
8199
  subscribeToGroup(groupId) {
8181
8200
  if (!this.client) return;
8201
+ const latestTimestamp = this.getLatestMessageTimestamp([groupId]);
8182
8202
  this.trackSubscription(
8183
8203
  createNip29Filter({
8184
8204
  kinds: [NIP29_KINDS.CHAT_MESSAGE, NIP29_KINDS.THREAD_ROOT, NIP29_KINDS.THREAD_REPLY],
8185
- "#h": [groupId]
8205
+ "#h": [groupId],
8206
+ ...latestTimestamp ? { since: latestTimestamp } : {}
8186
8207
  }),
8187
8208
  { onEvent: (event) => this.handleGroupEvent(event) }
8188
8209
  );
@@ -8874,6 +8895,23 @@ var GroupChatModule = class {
8874
8895
  getMyPublicKey() {
8875
8896
  return this.keyManager?.getPublicKeyHex() || null;
8876
8897
  }
8898
+ /**
8899
+ * Returns the latest message timestamp (in Nostr seconds) across the given groups,
8900
+ * or 0 if no messages exist. Used to set `since` on subscriptions so the relay
8901
+ * only sends events we don't already have.
8902
+ */
8903
+ getLatestMessageTimestamp(groupIds) {
8904
+ let latest = 0;
8905
+ for (const gid of groupIds) {
8906
+ const msgs = this.messages.get(gid);
8907
+ if (!msgs) continue;
8908
+ for (const m of msgs) {
8909
+ const ts = Math.floor(m.timestamp / 1e3);
8910
+ if (ts > latest) latest = ts;
8911
+ }
8912
+ }
8913
+ return latest;
8914
+ }
8877
8915
  // ===========================================================================
8878
8916
  // Private — Relay Admin
8879
8917
  // ===========================================================================
@@ -11728,6 +11766,9 @@ function createMarketModule(config) {
11728
11766
  return new MarketModule(config);
11729
11767
  }
11730
11768
 
11769
+ // core/Sphere.ts
11770
+ init_constants();
11771
+
11731
11772
  // core/encryption.ts
11732
11773
  var import_crypto_js6 = __toESM(require("crypto-js"), 1);
11733
11774
  var DEFAULT_ITERATIONS = 1e5;
@@ -11916,6 +11957,72 @@ async function scanAddressesImpl(deriveAddress, options = {}) {
11916
11957
  };
11917
11958
  }
11918
11959
 
11960
+ // core/discover.ts
11961
+ async function discoverAddressesImpl(deriveTransportPubkey, batchResolve, options = {}) {
11962
+ const maxAddresses = options.maxAddresses ?? 50;
11963
+ const gapLimit = options.gapLimit ?? 20;
11964
+ const batchSize = options.batchSize ?? 20;
11965
+ const { onProgress, signal } = options;
11966
+ const discovered = /* @__PURE__ */ new Map();
11967
+ let consecutiveEmpty = 0;
11968
+ let scanned = 0;
11969
+ const totalBatches = Math.ceil(maxAddresses / batchSize);
11970
+ for (let batchStart = 0; batchStart < maxAddresses; batchStart += batchSize) {
11971
+ if (signal?.aborted) break;
11972
+ if (consecutiveEmpty >= gapLimit) break;
11973
+ const batchEnd = Math.min(batchStart + batchSize, maxAddresses);
11974
+ const batchIndices = [];
11975
+ const pubkeyToIndex = /* @__PURE__ */ new Map();
11976
+ const pubkeyToInfo = /* @__PURE__ */ new Map();
11977
+ for (let i = batchStart; i < batchEnd; i++) {
11978
+ const info = deriveTransportPubkey(i);
11979
+ batchIndices.push(i);
11980
+ pubkeyToIndex.set(info.transportPubkey, i);
11981
+ pubkeyToInfo.set(info.transportPubkey, info);
11982
+ }
11983
+ const batchPubkeys = Array.from(pubkeyToIndex.keys());
11984
+ onProgress?.({
11985
+ currentBatch: Math.floor(batchStart / batchSize) + 1,
11986
+ totalBatches,
11987
+ discoveredCount: discovered.size,
11988
+ currentGap: consecutiveEmpty,
11989
+ phase: "transport"
11990
+ });
11991
+ const peerInfos = await batchResolve(batchPubkeys);
11992
+ const foundInBatch = /* @__PURE__ */ new Set();
11993
+ for (const peer of peerInfos) {
11994
+ const index = pubkeyToIndex.get(peer.transportPubkey);
11995
+ if (index !== void 0) {
11996
+ foundInBatch.add(index);
11997
+ const derived = pubkeyToInfo.get(peer.transportPubkey);
11998
+ discovered.set(index, {
11999
+ index,
12000
+ l1Address: peer.l1Address || derived.l1Address,
12001
+ directAddress: peer.directAddress || derived.directAddress,
12002
+ chainPubkey: peer.chainPubkey || derived.chainPubkey,
12003
+ nametag: peer.nametag,
12004
+ l1Balance: 0,
12005
+ source: "transport"
12006
+ });
12007
+ }
12008
+ }
12009
+ for (const idx of batchIndices) {
12010
+ scanned++;
12011
+ if (foundInBatch.has(idx)) {
12012
+ consecutiveEmpty = 0;
12013
+ } else {
12014
+ consecutiveEmpty++;
12015
+ if (consecutiveEmpty >= gapLimit) break;
12016
+ }
12017
+ }
12018
+ }
12019
+ return {
12020
+ addresses: Array.from(discovered.values()).sort((a, b) => a.index - b.index),
12021
+ scannedCount: scanned,
12022
+ aborted: signal?.aborted ?? false
12023
+ };
12024
+ }
12025
+
11919
12026
  // core/Sphere.ts
11920
12027
  init_network();
11921
12028
 
@@ -12738,7 +12845,9 @@ var Sphere = class _Sphere {
12738
12845
  price: options.price,
12739
12846
  groupChat,
12740
12847
  market,
12741
- password: options.password
12848
+ password: options.password,
12849
+ discoverAddresses: options.discoverAddresses,
12850
+ onProgress: options.onProgress
12742
12851
  });
12743
12852
  return { sphere: sphere2, created: false };
12744
12853
  }
@@ -12766,7 +12875,9 @@ var Sphere = class _Sphere {
12766
12875
  price: options.price,
12767
12876
  groupChat,
12768
12877
  market,
12769
- password: options.password
12878
+ password: options.password,
12879
+ discoverAddresses: options.discoverAddresses,
12880
+ onProgress: options.onProgress
12770
12881
  });
12771
12882
  return { sphere, created: true, generatedMnemonic };
12772
12883
  }
@@ -12827,6 +12938,7 @@ var Sphere = class _Sphere {
12827
12938
  if (await _Sphere.exists(options.storage)) {
12828
12939
  throw new Error("Wallet already exists. Use Sphere.load() or Sphere.clear() first.");
12829
12940
  }
12941
+ const progress = options.onProgress;
12830
12942
  if (!options.storage.isConnected()) {
12831
12943
  await options.storage.connect();
12832
12944
  }
@@ -12844,20 +12956,39 @@ var Sphere = class _Sphere {
12844
12956
  marketConfig
12845
12957
  );
12846
12958
  sphere._password = options.password ?? null;
12959
+ progress?.({ step: "storing_keys", message: "Storing wallet keys..." });
12847
12960
  await sphere.storeMnemonic(options.mnemonic, options.derivationPath);
12848
12961
  await sphere.initializeIdentityFromMnemonic(options.mnemonic, options.derivationPath);
12962
+ progress?.({ step: "initializing", message: "Initializing wallet..." });
12849
12963
  await sphere.initializeProviders();
12850
12964
  await sphere.initializeModules();
12965
+ progress?.({ step: "finalizing", message: "Finalizing wallet..." });
12851
12966
  await sphere.finalizeWalletCreation();
12852
12967
  sphere._initialized = true;
12853
12968
  _Sphere.instance = sphere;
12854
12969
  await sphere.ensureAddressTracked(0);
12855
12970
  if (options.nametag) {
12971
+ progress?.({ step: "registering_nametag", message: "Registering nametag..." });
12856
12972
  await sphere.registerNametag(options.nametag);
12857
12973
  } else {
12974
+ progress?.({ step: "recovering_nametag", message: "Recovering nametag..." });
12858
12975
  await sphere.recoverNametagFromTransport();
12976
+ progress?.({ step: "syncing_identity", message: "Publishing identity..." });
12859
12977
  await sphere.syncIdentityWithTransport();
12860
12978
  }
12979
+ if (options.discoverAddresses !== false && sphere._transport.discoverAddresses) {
12980
+ progress?.({ step: "discovering_addresses", message: "Discovering addresses..." });
12981
+ try {
12982
+ const discoverOpts = typeof options.discoverAddresses === "object" ? { ...options.discoverAddresses, autoTrack: options.discoverAddresses.autoTrack ?? true } : { autoTrack: true };
12983
+ const result = await sphere.discoverAddresses(discoverOpts);
12984
+ if (result.addresses.length > 0) {
12985
+ console.log(`[Sphere.create] Address discovery: found ${result.addresses.length} address(es)`);
12986
+ }
12987
+ } catch (err) {
12988
+ console.warn("[Sphere.create] Address discovery failed (non-fatal):", err);
12989
+ }
12990
+ }
12991
+ progress?.({ step: "complete", message: "Wallet created" });
12861
12992
  return sphere;
12862
12993
  }
12863
12994
  /**
@@ -12867,6 +12998,7 @@ var Sphere = class _Sphere {
12867
12998
  if (!await _Sphere.exists(options.storage)) {
12868
12999
  throw new Error("No wallet found. Use Sphere.create() to create a new wallet.");
12869
13000
  }
13001
+ const progress = options.onProgress;
12870
13002
  _Sphere.configureTokenRegistry(options.storage, options.network);
12871
13003
  const groupChatConfig = _Sphere.resolveGroupChatConfig(options.groupChat, options.network);
12872
13004
  const marketConfig = _Sphere.resolveMarketConfig(options.market);
@@ -12884,13 +13016,17 @@ var Sphere = class _Sphere {
12884
13016
  if (!options.storage.isConnected()) {
12885
13017
  await options.storage.connect();
12886
13018
  }
13019
+ progress?.({ step: "storing_keys", message: "Loading wallet keys..." });
12887
13020
  await sphere.loadIdentityFromStorage();
13021
+ progress?.({ step: "initializing", message: "Initializing wallet..." });
12888
13022
  await sphere.initializeProviders();
12889
13023
  await sphere.initializeModules();
13024
+ progress?.({ step: "syncing_identity", message: "Publishing identity..." });
12890
13025
  await sphere.syncIdentityWithTransport();
12891
13026
  sphere._initialized = true;
12892
13027
  _Sphere.instance = sphere;
12893
13028
  if (sphere._identity?.nametag && !sphere._payments.hasNametag()) {
13029
+ progress?.({ step: "registering_nametag", message: "Restoring nametag token..." });
12894
13030
  console.log(`[Sphere] Nametag @${sphere._identity.nametag} has no token, attempting to mint...`);
12895
13031
  try {
12896
13032
  const result = await sphere.mintNametag(sphere._identity.nametag);
@@ -12903,6 +13039,19 @@ var Sphere = class _Sphere {
12903
13039
  console.warn(`[Sphere] Nametag token mint failed:`, err);
12904
13040
  }
12905
13041
  }
13042
+ if (options.discoverAddresses !== false && sphere._transport.discoverAddresses && sphere._masterKey) {
13043
+ progress?.({ step: "discovering_addresses", message: "Discovering addresses..." });
13044
+ try {
13045
+ const discoverOpts = typeof options.discoverAddresses === "object" ? { ...options.discoverAddresses, autoTrack: options.discoverAddresses.autoTrack ?? true } : { autoTrack: true };
13046
+ const result = await sphere.discoverAddresses(discoverOpts);
13047
+ if (result.addresses.length > 0) {
13048
+ console.log(`[Sphere.load] Address discovery: found ${result.addresses.length} address(es)`);
13049
+ }
13050
+ } catch (err) {
13051
+ console.warn("[Sphere.load] Address discovery failed (non-fatal):", err);
13052
+ }
13053
+ }
13054
+ progress?.({ step: "complete", message: "Wallet loaded" });
12906
13055
  return sphere;
12907
13056
  }
12908
13057
  /**
@@ -12912,9 +13061,11 @@ var Sphere = class _Sphere {
12912
13061
  if (!options.mnemonic && !options.masterKey) {
12913
13062
  throw new Error("Either mnemonic or masterKey is required");
12914
13063
  }
13064
+ const progress = options.onProgress;
12915
13065
  console.log("[Sphere.import] Starting import...");
12916
13066
  const needsClear = _Sphere.instance !== null || await _Sphere.exists(options.storage);
12917
13067
  if (needsClear) {
13068
+ progress?.({ step: "clearing", message: "Clearing previous wallet data..." });
12918
13069
  console.log("[Sphere.import] Clearing existing wallet data...");
12919
13070
  await _Sphere.clear({ storage: options.storage, tokenStorage: options.tokenStorage });
12920
13071
  console.log("[Sphere.import] Clear done");
@@ -12939,6 +13090,7 @@ var Sphere = class _Sphere {
12939
13090
  marketConfig
12940
13091
  );
12941
13092
  sphere._password = options.password ?? null;
13093
+ progress?.({ step: "storing_keys", message: "Storing wallet keys..." });
12942
13094
  if (options.mnemonic) {
12943
13095
  if (!_Sphere.validateMnemonic(options.mnemonic)) {
12944
13096
  throw new Error("Invalid mnemonic");
@@ -12963,17 +13115,21 @@ var Sphere = class _Sphere {
12963
13115
  options.derivationPath
12964
13116
  );
12965
13117
  }
13118
+ progress?.({ step: "initializing", message: "Initializing wallet..." });
12966
13119
  console.log("[Sphere.import] Initializing providers...");
12967
13120
  await sphere.initializeProviders();
12968
13121
  console.log("[Sphere.import] Providers initialized. Initializing modules...");
12969
13122
  await sphere.initializeModules();
12970
13123
  console.log("[Sphere.import] Modules initialized");
12971
13124
  if (!options.nametag) {
13125
+ progress?.({ step: "recovering_nametag", message: "Recovering nametag..." });
12972
13126
  console.log("[Sphere.import] Recovering nametag from transport...");
12973
13127
  await sphere.recoverNametagFromTransport();
12974
13128
  console.log("[Sphere.import] Nametag recovery done");
13129
+ progress?.({ step: "syncing_identity", message: "Publishing identity..." });
12975
13130
  await sphere.syncIdentityWithTransport();
12976
13131
  }
13132
+ progress?.({ step: "finalizing", message: "Finalizing wallet..." });
12977
13133
  console.log("[Sphere.import] Finalizing wallet creation...");
12978
13134
  await sphere.finalizeWalletCreation();
12979
13135
  sphere._initialized = true;
@@ -12981,10 +13137,12 @@ var Sphere = class _Sphere {
12981
13137
  console.log("[Sphere.import] Tracking address 0...");
12982
13138
  await sphere.ensureAddressTracked(0);
12983
13139
  if (options.nametag) {
13140
+ progress?.({ step: "registering_nametag", message: "Registering nametag..." });
12984
13141
  console.log("[Sphere.import] Registering nametag...");
12985
13142
  await sphere.registerNametag(options.nametag);
12986
13143
  }
12987
13144
  if (sphere._tokenStorageProviders.size > 0) {
13145
+ progress?.({ step: "syncing_tokens", message: "Syncing tokens..." });
12988
13146
  try {
12989
13147
  const syncResult = await sphere._payments.sync();
12990
13148
  console.log(`[Sphere.import] Auto-sync: +${syncResult.added} -${syncResult.removed}`);
@@ -12992,6 +13150,19 @@ var Sphere = class _Sphere {
12992
13150
  console.warn("[Sphere.import] Auto-sync failed (non-fatal):", err);
12993
13151
  }
12994
13152
  }
13153
+ if (options.discoverAddresses !== false && sphere._transport.discoverAddresses) {
13154
+ progress?.({ step: "discovering_addresses", message: "Discovering addresses..." });
13155
+ try {
13156
+ const discoverOpts = typeof options.discoverAddresses === "object" ? { ...options.discoverAddresses, autoTrack: options.discoverAddresses.autoTrack ?? true } : { autoTrack: true };
13157
+ const result = await sphere.discoverAddresses(discoverOpts);
13158
+ if (result.addresses.length > 0) {
13159
+ console.log(`[Sphere.import] Address discovery: found ${result.addresses.length} address(es)`);
13160
+ }
13161
+ } catch (err) {
13162
+ console.warn("[Sphere.import] Address discovery failed (non-fatal):", err);
13163
+ }
13164
+ }
13165
+ progress?.({ step: "complete", message: "Import complete" });
12995
13166
  console.log("[Sphere.import] Import complete");
12996
13167
  return sphere;
12997
13168
  }
@@ -13405,55 +13576,44 @@ var Sphere = class _Sphere {
13405
13576
  * ```
13406
13577
  */
13407
13578
  static async importFromJSON(options) {
13579
+ const { jsonContent, password, ...baseOptions } = options;
13408
13580
  try {
13409
- const data = JSON.parse(options.jsonContent);
13581
+ const data = JSON.parse(jsonContent);
13410
13582
  if (data.version !== "1.0" || data.type !== "sphere-wallet") {
13411
13583
  return { success: false, error: "Invalid wallet format" };
13412
13584
  }
13413
13585
  let mnemonic = data.mnemonic;
13414
13586
  let masterKey = data.wallet.masterPrivateKey;
13415
- if (data.encrypted && options.password) {
13587
+ if (data.encrypted && password) {
13416
13588
  if (mnemonic) {
13417
- const decrypted = decryptSimple(mnemonic, options.password);
13589
+ const decrypted = decryptSimple(mnemonic, password);
13418
13590
  if (!decrypted) {
13419
13591
  return { success: false, error: "Failed to decrypt mnemonic - wrong password?" };
13420
13592
  }
13421
13593
  mnemonic = decrypted;
13422
13594
  }
13423
13595
  if (masterKey) {
13424
- const decrypted = decryptSimple(masterKey, options.password);
13596
+ const decrypted = decryptSimple(masterKey, password);
13425
13597
  if (!decrypted) {
13426
13598
  return { success: false, error: "Failed to decrypt master key - wrong password?" };
13427
13599
  }
13428
13600
  masterKey = decrypted;
13429
13601
  }
13430
- } else if (data.encrypted && !options.password) {
13602
+ } else if (data.encrypted && !password) {
13431
13603
  return { success: false, error: "Password required for encrypted wallet" };
13432
13604
  }
13433
13605
  const basePath = data.wallet.descriptorPath ? `m/${data.wallet.descriptorPath}` : DEFAULT_BASE_PATH;
13434
13606
  if (mnemonic) {
13435
- await _Sphere.import({
13436
- mnemonic,
13437
- basePath,
13438
- storage: options.storage,
13439
- transport: options.transport,
13440
- oracle: options.oracle,
13441
- tokenStorage: options.tokenStorage,
13442
- l1: options.l1
13443
- });
13607
+ await _Sphere.import({ ...baseOptions, mnemonic, basePath });
13444
13608
  return { success: true, mnemonic };
13445
13609
  }
13446
13610
  if (masterKey) {
13447
13611
  await _Sphere.import({
13612
+ ...baseOptions,
13448
13613
  masterKey,
13449
13614
  chainCode: data.wallet.chainCode,
13450
13615
  basePath,
13451
- derivationMode: data.derivationMode || (data.wallet.isBIP32 ? "bip32" : "wif_hmac"),
13452
- storage: options.storage,
13453
- transport: options.transport,
13454
- oracle: options.oracle,
13455
- tokenStorage: options.tokenStorage,
13456
- l1: options.l1
13616
+ derivationMode: data.derivationMode || (data.wallet.isBIP32 ? "bip32" : "wif_hmac")
13457
13617
  });
13458
13618
  return { success: true };
13459
13619
  }
@@ -13496,7 +13656,7 @@ var Sphere = class _Sphere {
13496
13656
  * ```
13497
13657
  */
13498
13658
  static async importFromLegacyFile(options) {
13499
- const { fileContent, fileName, password, onDecryptProgress } = options;
13659
+ const { fileContent, fileName, password, onDecryptProgress, ...baseOptions } = options;
13500
13660
  const fileType = _Sphere.detectLegacyFileType(fileName, fileContent);
13501
13661
  if (fileType === "unknown") {
13502
13662
  return { success: false, error: "Unknown file format" };
@@ -13506,15 +13666,7 @@ var Sphere = class _Sphere {
13506
13666
  if (!_Sphere.validateMnemonic(mnemonic)) {
13507
13667
  return { success: false, error: "Invalid mnemonic phrase" };
13508
13668
  }
13509
- const sphere = await _Sphere.import({
13510
- mnemonic,
13511
- storage: options.storage,
13512
- transport: options.transport,
13513
- oracle: options.oracle,
13514
- tokenStorage: options.tokenStorage,
13515
- nametag: options.nametag,
13516
- l1: options.l1
13517
- });
13669
+ const sphere = await _Sphere.import({ ...baseOptions, mnemonic });
13518
13670
  return { success: true, sphere, mnemonic };
13519
13671
  }
13520
13672
  if (fileType === "dat") {
@@ -13534,16 +13686,11 @@ var Sphere = class _Sphere {
13534
13686
  const { masterKey, chainCode, descriptorPath, derivationMode } = parseResult.data;
13535
13687
  const basePath = descriptorPath ? `m/${descriptorPath}` : DEFAULT_BASE_PATH;
13536
13688
  const sphere = await _Sphere.import({
13689
+ ...baseOptions,
13537
13690
  masterKey,
13538
13691
  chainCode,
13539
13692
  basePath,
13540
- derivationMode: derivationMode || (chainCode ? "bip32" : "wif_hmac"),
13541
- storage: options.storage,
13542
- transport: options.transport,
13543
- oracle: options.oracle,
13544
- tokenStorage: options.tokenStorage,
13545
- nametag: options.nametag,
13546
- l1: options.l1
13693
+ derivationMode: derivationMode || (chainCode ? "bip32" : "wif_hmac")
13547
13694
  });
13548
13695
  return { success: true, sphere };
13549
13696
  }
@@ -13566,16 +13713,11 @@ var Sphere = class _Sphere {
13566
13713
  const { masterKey, chainCode, descriptorPath, derivationMode } = parseResult.data;
13567
13714
  const basePath = descriptorPath ? `m/${descriptorPath}` : DEFAULT_BASE_PATH;
13568
13715
  const sphere = await _Sphere.import({
13716
+ ...baseOptions,
13569
13717
  masterKey,
13570
13718
  chainCode,
13571
13719
  basePath,
13572
- derivationMode: derivationMode || (chainCode ? "bip32" : "wif_hmac"),
13573
- storage: options.storage,
13574
- transport: options.transport,
13575
- oracle: options.oracle,
13576
- tokenStorage: options.tokenStorage,
13577
- nametag: options.nametag,
13578
- l1: options.l1
13720
+ derivationMode: derivationMode || (chainCode ? "bip32" : "wif_hmac")
13579
13721
  });
13580
13722
  return { success: true, sphere };
13581
13723
  }
@@ -13589,13 +13731,9 @@ var Sphere = class _Sphere {
13589
13731
  }
13590
13732
  if (parsed.type === "sphere-wallet") {
13591
13733
  const result = await _Sphere.importFromJSON({
13734
+ ...baseOptions,
13592
13735
  jsonContent: content,
13593
- password,
13594
- storage: options.storage,
13595
- transport: options.transport,
13596
- oracle: options.oracle,
13597
- tokenStorage: options.tokenStorage,
13598
- l1: options.l1
13736
+ password
13599
13737
  });
13600
13738
  if (result.success) {
13601
13739
  const sphere2 = _Sphere.getInstance();
@@ -13637,29 +13775,15 @@ var Sphere = class _Sphere {
13637
13775
  const isBIP32 = derivationMode === "bip32" || !!chainCode;
13638
13776
  const basePath = descriptorPath ? `m/${descriptorPath}` : isBIP32 ? "m/84'/1'/0'" : DEFAULT_BASE_PATH;
13639
13777
  if (mnemonic) {
13640
- const sphere2 = await _Sphere.import({
13641
- mnemonic,
13642
- basePath,
13643
- storage: options.storage,
13644
- transport: options.transport,
13645
- oracle: options.oracle,
13646
- tokenStorage: options.tokenStorage,
13647
- nametag: options.nametag,
13648
- l1: options.l1
13649
- });
13778
+ const sphere2 = await _Sphere.import({ ...baseOptions, mnemonic, basePath });
13650
13779
  return { success: true, sphere: sphere2, mnemonic };
13651
13780
  }
13652
13781
  const sphere = await _Sphere.import({
13782
+ ...baseOptions,
13653
13783
  masterKey,
13654
13784
  chainCode,
13655
13785
  basePath,
13656
- derivationMode: derivationMode || (chainCode ? "bip32" : "wif_hmac"),
13657
- storage: options.storage,
13658
- transport: options.transport,
13659
- oracle: options.oracle,
13660
- tokenStorage: options.tokenStorage,
13661
- nametag: options.nametag,
13662
- l1: options.l1
13786
+ derivationMode: derivationMode || (chainCode ? "bip32" : "wif_hmac")
13663
13787
  });
13664
13788
  return { success: true, sphere };
13665
13789
  }
@@ -13907,6 +14031,23 @@ var Sphere = class _Sphere {
13907
14031
  await provider.initialize();
13908
14032
  }
13909
14033
  await this.reinitializeModulesForNewAddress();
14034
+ this.emitEvent("identity:changed", {
14035
+ l1Address: this._identity.l1Address,
14036
+ directAddress: this._identity.directAddress,
14037
+ chainPubkey: this._identity.chainPubkey,
14038
+ nametag: this._identity.nametag,
14039
+ addressIndex: index
14040
+ });
14041
+ console.log(`[Sphere] Switched to address ${index}:`, this._identity.l1Address);
14042
+ this.postSwitchSync(index, newNametag).catch((err) => {
14043
+ console.warn(`[Sphere] Post-switch sync failed for address ${index}:`, err);
14044
+ });
14045
+ }
14046
+ /**
14047
+ * Background transport sync and nametag operations after address switch.
14048
+ * Runs after switchToAddress returns so L1/L3 queries can start immediately.
14049
+ */
14050
+ async postSwitchSync(index, newNametag) {
13910
14051
  if (!newNametag) {
13911
14052
  await this.syncIdentityWithTransport();
13912
14053
  }
@@ -13929,7 +14070,7 @@ var Sphere = class _Sphere {
13929
14070
  nametag: newNametag,
13930
14071
  addressIndex: index
13931
14072
  });
13932
- } else if (this._identity.nametag && !this._payments.hasNametag()) {
14073
+ } else if (this._identity?.nametag && !this._payments.hasNametag()) {
13933
14074
  console.log(`[Sphere] Nametag @${this._identity.nametag} has no token after switch, minting...`);
13934
14075
  try {
13935
14076
  const result = await this.mintNametag(this._identity.nametag);
@@ -13942,14 +14083,6 @@ var Sphere = class _Sphere {
13942
14083
  console.warn(`[Sphere] Nametag token mint failed after switch:`, err);
13943
14084
  }
13944
14085
  }
13945
- this.emitEvent("identity:changed", {
13946
- l1Address: this._identity.l1Address,
13947
- directAddress: this._identity.directAddress,
13948
- chainPubkey: this._identity.chainPubkey,
13949
- nametag: this._identity.nametag,
13950
- addressIndex: index
13951
- });
13952
- console.log(`[Sphere] Switched to address ${index}:`, this._identity.l1Address);
13953
14086
  }
13954
14087
  /**
13955
14088
  * Re-initialize modules after address switch
@@ -14156,6 +14289,98 @@ var Sphere = class _Sphere {
14156
14289
  await this.persistTrackedAddresses();
14157
14290
  await this.persistAddressNametags();
14158
14291
  }
14292
+ /**
14293
+ * Discover previously used HD addresses.
14294
+ *
14295
+ * Primary: queries Nostr relay for identity binding events (fast, single batch query).
14296
+ * Secondary: runs L1 balance scan to find legacy addresses with no binding event.
14297
+ *
14298
+ * @example
14299
+ * ```ts
14300
+ * const result = await sphere.discoverAddresses();
14301
+ * console.log(`Found ${result.addresses.length} addresses`);
14302
+ *
14303
+ * // With auto-tracking
14304
+ * await sphere.discoverAddresses({ autoTrack: true });
14305
+ * ```
14306
+ */
14307
+ async discoverAddresses(options = {}) {
14308
+ this.ensureReady();
14309
+ if (!this._masterKey) {
14310
+ throw new Error("Address discovery requires HD master key");
14311
+ }
14312
+ if (!this._transport.discoverAddresses) {
14313
+ throw new Error("Transport provider does not support address discovery");
14314
+ }
14315
+ const includeL1Scan = options.includeL1Scan ?? true;
14316
+ const transportResult = await discoverAddressesImpl(
14317
+ (index) => {
14318
+ const addrInfo = this._deriveAddressInternal(index, false);
14319
+ return {
14320
+ transportPubkey: addrInfo.publicKey.slice(2),
14321
+ // x-only 32 bytes
14322
+ chainPubkey: addrInfo.publicKey,
14323
+ l1Address: addrInfo.address,
14324
+ directAddress: ""
14325
+ // not needed for discovery query
14326
+ };
14327
+ },
14328
+ (pubkeys) => this._transport.discoverAddresses(pubkeys),
14329
+ options
14330
+ );
14331
+ if (includeL1Scan) {
14332
+ try {
14333
+ const l1Result = await this.scanAddresses({
14334
+ maxAddresses: options.maxAddresses,
14335
+ gapLimit: options.gapLimit,
14336
+ signal: options.signal,
14337
+ onProgress: options.onProgress ? (p) => options.onProgress({
14338
+ currentBatch: 0,
14339
+ totalBatches: 0,
14340
+ discoveredCount: p.foundCount,
14341
+ currentGap: p.currentGap,
14342
+ phase: "l1"
14343
+ }) : void 0
14344
+ });
14345
+ for (const l1Addr of l1Result.addresses) {
14346
+ if (l1Addr.isChange) continue;
14347
+ const existing = transportResult.addresses.find((a) => a.index === l1Addr.index);
14348
+ if (existing) {
14349
+ existing.l1Balance = l1Addr.balance;
14350
+ existing.source = "both";
14351
+ if (!existing.nametag && l1Addr.nametag) {
14352
+ existing.nametag = l1Addr.nametag;
14353
+ }
14354
+ } else {
14355
+ const addrInfo = this._deriveAddressInternal(l1Addr.index, false);
14356
+ transportResult.addresses.push({
14357
+ index: l1Addr.index,
14358
+ l1Address: l1Addr.address,
14359
+ directAddress: "",
14360
+ chainPubkey: addrInfo.publicKey,
14361
+ nametag: l1Addr.nametag,
14362
+ l1Balance: l1Addr.balance,
14363
+ source: "l1"
14364
+ });
14365
+ }
14366
+ }
14367
+ transportResult.addresses.sort((a, b) => a.index - b.index);
14368
+ } catch (err) {
14369
+ console.warn("[Sphere] L1 scan failed during discovery (non-fatal):", err);
14370
+ }
14371
+ }
14372
+ if (options.autoTrack && transportResult.addresses.length > 0) {
14373
+ await this.trackScannedAddresses(
14374
+ transportResult.addresses.map((a) => ({
14375
+ index: a.index,
14376
+ // Preserve existing hidden state; default to false for newly discovered
14377
+ hidden: this._trackedAddresses.get(a.index)?.hidden ?? false,
14378
+ nametag: a.nametag
14379
+ }))
14380
+ );
14381
+ }
14382
+ return transportResult;
14383
+ }
14159
14384
  // ===========================================================================
14160
14385
  // Public Methods - Status
14161
14386
  // ===========================================================================
@@ -14727,6 +14952,17 @@ var Sphere = class _Sphere {
14727
14952
  return;
14728
14953
  }
14729
14954
  }
14955
+ const needsUpdate = !existing.directAddress || !existing.l1Address || !existing.chainPubkey || this._identity?.nametag && !existing.nametag;
14956
+ if (needsUpdate) {
14957
+ console.log("[Sphere] Existing binding incomplete, re-publishing with full data");
14958
+ await this._transport.publishIdentityBinding(
14959
+ this._identity.chainPubkey,
14960
+ this._identity.l1Address,
14961
+ this._identity.directAddress || "",
14962
+ this._identity?.nametag || existing.nametag || void 0
14963
+ );
14964
+ return;
14965
+ }
14730
14966
  console.log("[Sphere] Existing binding found, skipping re-publish");
14731
14967
  return;
14732
14968
  }
@@ -15220,6 +15456,7 @@ var CurrencyUtils = {
15220
15456
  init_bech32();
15221
15457
 
15222
15458
  // core/network-health.ts
15459
+ init_constants();
15223
15460
  var DEFAULT_TIMEOUT_MS = 5e3;
15224
15461
  async function checkNetworkHealth(network = "testnet", options) {
15225
15462
  const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
@@ -15431,6 +15668,7 @@ async function runCustomCheck(name, checkFn, timeoutMs) {
15431
15668
  deriveChildKey,
15432
15669
  deriveKeyAtPath,
15433
15670
  deserializeEncrypted,
15671
+ discoverAddressesImpl,
15434
15672
  doubleSha256,
15435
15673
  ec,
15436
15674
  encodeBech32,