@unicitylabs/sphere-sdk 0.2.5 → 0.3.1

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.
@@ -84,7 +84,17 @@ var STORAGE_KEYS_GLOBAL = {
84
84
  /** Active addresses registry (JSON: TrackedAddressesStorage) */
85
85
  TRACKED_ADDRESSES: "tracked_addresses",
86
86
  /** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */
87
- LAST_WALLET_EVENT_TS: "last_wallet_event_ts"
87
+ LAST_WALLET_EVENT_TS: "last_wallet_event_ts",
88
+ /** Group chat: joined groups */
89
+ GROUP_CHAT_GROUPS: "group_chat_groups",
90
+ /** Group chat: messages */
91
+ GROUP_CHAT_MESSAGES: "group_chat_messages",
92
+ /** Group chat: members */
93
+ GROUP_CHAT_MEMBERS: "group_chat_members",
94
+ /** Group chat: processed event IDs for deduplication */
95
+ GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events",
96
+ /** Group chat: last used relay URL (stale data detection) */
97
+ GROUP_CHAT_RELAY_URL: "group_chat_relay_url"
88
98
  };
89
99
  var STORAGE_KEYS_ADDRESS = {
90
100
  /** Pending transfers for this address */
@@ -165,27 +175,33 @@ var TEST_ELECTRUM_URL = "wss://fulcrum.alpha.testnet.unicity.network:50004";
165
175
  var TEST_NOSTR_RELAYS = [
166
176
  "wss://nostr-relay.testnet.unicity.network"
167
177
  ];
178
+ var DEFAULT_GROUP_RELAYS = [
179
+ "wss://sphere-relay.unicity.network"
180
+ ];
168
181
  var NETWORKS = {
169
182
  mainnet: {
170
183
  name: "Mainnet",
171
184
  aggregatorUrl: DEFAULT_AGGREGATOR_URL,
172
185
  nostrRelays: DEFAULT_NOSTR_RELAYS,
173
186
  ipfsGateways: DEFAULT_IPFS_GATEWAYS,
174
- electrumUrl: DEFAULT_ELECTRUM_URL
187
+ electrumUrl: DEFAULT_ELECTRUM_URL,
188
+ groupRelays: DEFAULT_GROUP_RELAYS
175
189
  },
176
190
  testnet: {
177
191
  name: "Testnet",
178
192
  aggregatorUrl: TEST_AGGREGATOR_URL,
179
193
  nostrRelays: TEST_NOSTR_RELAYS,
180
194
  ipfsGateways: DEFAULT_IPFS_GATEWAYS,
181
- electrumUrl: TEST_ELECTRUM_URL
195
+ electrumUrl: TEST_ELECTRUM_URL,
196
+ groupRelays: DEFAULT_GROUP_RELAYS
182
197
  },
183
198
  dev: {
184
199
  name: "Development",
185
200
  aggregatorUrl: DEV_AGGREGATOR_URL,
186
201
  nostrRelays: TEST_NOSTR_RELAYS,
187
202
  ipfsGateways: DEFAULT_IPFS_GATEWAYS,
188
- electrumUrl: TEST_ELECTRUM_URL
203
+ electrumUrl: TEST_ELECTRUM_URL,
204
+ groupRelays: DEFAULT_GROUP_RELAYS
189
205
  }
190
206
  };
191
207
  var TIMEOUTS = {
@@ -605,29 +621,6 @@ var IndexedDBTokenStorageProvider = class {
605
621
  }
606
622
  }
607
623
  // =========================================================================
608
- // Helper methods for individual token operations
609
- // =========================================================================
610
- async deleteToken(tokenId) {
611
- if (this.db) {
612
- await this.deleteFromStore(STORE_TOKENS, tokenId);
613
- }
614
- }
615
- async saveToken(tokenId, tokenData) {
616
- if (this.db) {
617
- await this.putToStore(STORE_TOKENS, tokenId, { id: tokenId, data: tokenData });
618
- }
619
- }
620
- async getToken(tokenId) {
621
- if (!this.db) return null;
622
- const result = await this.getFromStore(STORE_TOKENS, tokenId);
623
- return result?.data ?? null;
624
- }
625
- async listTokenIds() {
626
- if (!this.db) return [];
627
- const tokens = await this.getAllFromStore(STORE_TOKENS);
628
- return tokens.map((t) => t.id);
629
- }
630
- // =========================================================================
631
624
  // Private IndexedDB helpers
632
625
  // =========================================================================
633
626
  openDatabase() {
@@ -3900,9 +3893,13 @@ function mergeTxfData(local, remote) {
3900
3893
  remote._invalid ?? [],
3901
3894
  "tokenId"
3902
3895
  );
3896
+ const localNametags = local._nametags ?? [];
3897
+ const remoteNametags = remote._nametags ?? [];
3898
+ const mergedNametags = mergeNametagsByName(localNametags, remoteNametags);
3903
3899
  const merged = {
3904
3900
  _meta: mergedMeta,
3905
3901
  _tombstones: mergedTombstones.length > 0 ? mergedTombstones : void 0,
3902
+ _nametags: mergedNametags.length > 0 ? mergedNametags : void 0,
3906
3903
  _outbox: mergedOutbox.length > 0 ? mergedOutbox : void 0,
3907
3904
  _sent: mergedSent.length > 0 ? mergedSent : void 0,
3908
3905
  _invalid: mergedInvalid.length > 0 ? mergedInvalid : void 0,
@@ -3929,6 +3926,7 @@ function getTokenKeys(data) {
3929
3926
  "_sent",
3930
3927
  "_invalid",
3931
3928
  "_nametag",
3929
+ "_nametags",
3932
3930
  "_mintOutbox",
3933
3931
  "_invalidatedNametags",
3934
3932
  "_integrity"
@@ -3936,7 +3934,7 @@ function getTokenKeys(data) {
3936
3934
  const keys = /* @__PURE__ */ new Set();
3937
3935
  for (const key of Object.keys(data)) {
3938
3936
  if (reservedKeys.has(key)) continue;
3939
- if (key.startsWith("archived-") || key.startsWith("_forked_") || key.startsWith("nametag-")) continue;
3937
+ if (key.startsWith("archived-") || key.startsWith("_forked_")) continue;
3940
3938
  keys.add(key);
3941
3939
  }
3942
3940
  return keys;
@@ -3951,6 +3949,18 @@ function isTokenTombstoned(tokenId, localToken, remoteToken, tombstoneKeys) {
3951
3949
  void remoteToken;
3952
3950
  return false;
3953
3951
  }
3952
+ function mergeNametagsByName(local, remote) {
3953
+ const seen = /* @__PURE__ */ new Map();
3954
+ for (const item of local) {
3955
+ if (item.name) seen.set(item.name, item);
3956
+ }
3957
+ for (const item of remote) {
3958
+ if (item.name && !seen.has(item.name)) {
3959
+ seen.set(item.name, item);
3960
+ }
3961
+ }
3962
+ return Array.from(seen.values());
3963
+ }
3954
3964
  function mergeArrayById(local, remote, idField) {
3955
3965
  const seen = /* @__PURE__ */ new Map();
3956
3966
  for (const item of local) {
@@ -4305,14 +4315,11 @@ var AsyncSerialQueue = class {
4305
4315
  var WriteBuffer = class {
4306
4316
  /** Full TXF data from save() calls — latest wins */
4307
4317
  txfData = null;
4308
- /** Individual token mutations: key -> { op: 'save'|'delete', data? } */
4309
- tokenMutations = /* @__PURE__ */ new Map();
4310
4318
  get isEmpty() {
4311
- return this.txfData === null && this.tokenMutations.size === 0;
4319
+ return this.txfData === null;
4312
4320
  }
4313
4321
  clear() {
4314
4322
  this.txfData = null;
4315
- this.tokenMutations.clear();
4316
4323
  }
4317
4324
  /**
4318
4325
  * Merge another buffer's contents into this one (for rollback).
@@ -4322,11 +4329,6 @@ var WriteBuffer = class {
4322
4329
  if (other.txfData && !this.txfData) {
4323
4330
  this.txfData = other.txfData;
4324
4331
  }
4325
- for (const [id, mutation] of other.tokenMutations) {
4326
- if (!this.tokenMutations.has(id)) {
4327
- this.tokenMutations.set(id, mutation);
4328
- }
4329
- }
4330
4332
  }
4331
4333
  };
4332
4334
 
@@ -4366,9 +4368,6 @@ var IpfsStorageProvider = class {
4366
4368
  subscriptionClient = null;
4367
4369
  /** Unsubscribe function from subscription client */
4368
4370
  subscriptionUnsubscribe = null;
4369
- /** In-memory buffer for individual token save/delete calls */
4370
- tokenBuffer = /* @__PURE__ */ new Map();
4371
- deletedTokenIds = /* @__PURE__ */ new Set();
4372
4371
  /** Write-behind buffer: serializes flush / sync / shutdown */
4373
4372
  flushQueue = new AsyncSerialQueue();
4374
4373
  /** Pending mutations not yet flushed to IPFS */
@@ -4557,14 +4556,6 @@ var IpfsStorageProvider = class {
4557
4556
  metaUpdate.lastCid = this.remoteCid;
4558
4557
  }
4559
4558
  const updatedData = { ...data, _meta: metaUpdate };
4560
- for (const [tokenId, tokenData] of this.tokenBuffer) {
4561
- if (!this.deletedTokenIds.has(tokenId)) {
4562
- updatedData[tokenId] = tokenData;
4563
- }
4564
- }
4565
- for (const tokenId of this.deletedTokenIds) {
4566
- delete updatedData[tokenId];
4567
- }
4568
4559
  const { cid } = await this.httpClient.upload(updatedData);
4569
4560
  this.log(`Content uploaded: CID=${cid}`);
4570
4561
  const baseSeq = this.ipnsSequenceNumber > this.lastKnownRemoteSequence ? this.ipnsSequenceNumber : this.lastKnownRemoteSequence;
@@ -4603,7 +4594,6 @@ var IpfsStorageProvider = class {
4603
4594
  lastCid: cid,
4604
4595
  version: this.dataVersion
4605
4596
  });
4606
- this.deletedTokenIds.clear();
4607
4597
  this.emitEvent({
4608
4598
  type: "storage:saved",
4609
4599
  timestamp: Date.now(),
@@ -4715,7 +4705,6 @@ var IpfsStorageProvider = class {
4715
4705
  if (typeof remoteVersion === "number" && remoteVersion > this.dataVersion) {
4716
4706
  this.dataVersion = remoteVersion;
4717
4707
  }
4718
- this.populateTokenBuffer(data);
4719
4708
  this.emitEvent({
4720
4709
  type: "storage:loaded",
4721
4710
  timestamp: Date.now(),
@@ -4761,7 +4750,7 @@ var IpfsStorageProvider = class {
4761
4750
  this.emitEvent({ type: "sync:completed", timestamp: Date.now() });
4762
4751
  return {
4763
4752
  success: saveResult2.success,
4764
- merged: this.enrichWithTokenBuffer(localData),
4753
+ merged: localData,
4765
4754
  added: 0,
4766
4755
  removed: 0,
4767
4756
  conflicts: 0,
@@ -4776,7 +4765,7 @@ var IpfsStorageProvider = class {
4776
4765
  this.emitEvent({ type: "sync:completed", timestamp: Date.now() });
4777
4766
  return {
4778
4767
  success: true,
4779
- merged: this.enrichWithTokenBuffer(localData),
4768
+ merged: localData,
4780
4769
  added: 0,
4781
4770
  removed: 0,
4782
4771
  conflicts: 0
@@ -4799,7 +4788,7 @@ var IpfsStorageProvider = class {
4799
4788
  });
4800
4789
  return {
4801
4790
  success: saveResult.success,
4802
- merged: this.enrichWithTokenBuffer(merged),
4791
+ merged,
4803
4792
  added,
4804
4793
  removed,
4805
4794
  conflicts,
@@ -4825,21 +4814,6 @@ var IpfsStorageProvider = class {
4825
4814
  // ---------------------------------------------------------------------------
4826
4815
  // Private Helpers
4827
4816
  // ---------------------------------------------------------------------------
4828
- /**
4829
- * Enrich TXF data with individually-buffered tokens before returning to caller.
4830
- * PaymentsModule.createStorageData() passes empty tokens (they're stored via
4831
- * saveToken()), but loadFromStorageData() needs them in the merged result.
4832
- */
4833
- enrichWithTokenBuffer(data) {
4834
- if (this.tokenBuffer.size === 0) return data;
4835
- const enriched = { ...data };
4836
- for (const [tokenId, tokenData] of this.tokenBuffer) {
4837
- if (!this.deletedTokenIds.has(tokenId)) {
4838
- enriched[tokenId] = tokenData;
4839
- }
4840
- }
4841
- return enriched;
4842
- }
4843
4817
  // ---------------------------------------------------------------------------
4844
4818
  // Optional Methods
4845
4819
  // ---------------------------------------------------------------------------
@@ -4869,8 +4843,6 @@ var IpfsStorageProvider = class {
4869
4843
  const result = await this._doSave(emptyData);
4870
4844
  if (result.success) {
4871
4845
  this.cache.clear();
4872
- this.tokenBuffer.clear();
4873
- this.deletedTokenIds.clear();
4874
4846
  await this.statePersistence.clear(this.ipnsName);
4875
4847
  }
4876
4848
  return result.success;
@@ -4881,27 +4853,6 @@ var IpfsStorageProvider = class {
4881
4853
  this.eventCallbacks.delete(callback);
4882
4854
  };
4883
4855
  }
4884
- async saveToken(tokenId, tokenData) {
4885
- this.pendingBuffer.tokenMutations.set(tokenId, { op: "save", data: tokenData });
4886
- this.tokenBuffer.set(tokenId, tokenData);
4887
- this.deletedTokenIds.delete(tokenId);
4888
- this.scheduleFlush();
4889
- }
4890
- async getToken(tokenId) {
4891
- if (this.deletedTokenIds.has(tokenId)) return null;
4892
- return this.tokenBuffer.get(tokenId) ?? null;
4893
- }
4894
- async listTokenIds() {
4895
- return Array.from(this.tokenBuffer.keys()).filter(
4896
- (id) => !this.deletedTokenIds.has(id)
4897
- );
4898
- }
4899
- async deleteToken(tokenId) {
4900
- this.pendingBuffer.tokenMutations.set(tokenId, { op: "delete" });
4901
- this.tokenBuffer.delete(tokenId);
4902
- this.deletedTokenIds.add(tokenId);
4903
- this.scheduleFlush();
4904
- }
4905
4856
  // ---------------------------------------------------------------------------
4906
4857
  // Public Accessors
4907
4858
  // ---------------------------------------------------------------------------
@@ -4993,26 +4944,6 @@ var IpfsStorageProvider = class {
4993
4944
  console.log(`[IPFS-Storage] ${message}`);
4994
4945
  }
4995
4946
  }
4996
- META_KEYS = /* @__PURE__ */ new Set([
4997
- "_meta",
4998
- "_tombstones",
4999
- "_outbox",
5000
- "_sent",
5001
- "_invalid",
5002
- "_nametag",
5003
- "_mintOutbox",
5004
- "_invalidatedNametags",
5005
- "_integrity"
5006
- ]);
5007
- populateTokenBuffer(data) {
5008
- this.tokenBuffer.clear();
5009
- this.deletedTokenIds.clear();
5010
- for (const key of Object.keys(data)) {
5011
- if (!this.META_KEYS.has(key)) {
5012
- this.tokenBuffer.set(key, data[key]);
5013
- }
5014
- }
5015
- }
5016
4947
  };
5017
4948
 
5018
4949
  // impl/browser/ipfs/browser-ipfs-state-persistence.ts
@@ -5251,6 +5182,20 @@ function resolveArrayConfig(defaults, replace, additional) {
5251
5182
  }
5252
5183
  return result;
5253
5184
  }
5185
+ function resolveGroupChatConfig(network, config) {
5186
+ if (!config) return void 0;
5187
+ if (config === true) {
5188
+ const netConfig2 = getNetworkConfig(network);
5189
+ return { relays: [...netConfig2.groupRelays] };
5190
+ }
5191
+ if (typeof config === "object" && config.enabled === false) {
5192
+ return void 0;
5193
+ }
5194
+ const netConfig = getNetworkConfig(network);
5195
+ return {
5196
+ relays: config.relays ?? [...netConfig.groupRelays]
5197
+ };
5198
+ }
5254
5199
 
5255
5200
  // impl/browser/index.ts
5256
5201
  if (typeof globalThis.Buffer === "undefined") {
@@ -5316,8 +5261,10 @@ function createBrowserProviders(config) {
5316
5261
  debug: config?.tokenSync?.ipfs?.useDht
5317
5262
  // reuse debug-like flag
5318
5263
  }) : void 0;
5264
+ const groupChat = resolveGroupChatConfig(network, config?.groupChat);
5319
5265
  return {
5320
5266
  storage,
5267
+ groupChat,
5321
5268
  transport: createNostrTransportProvider({
5322
5269
  relays: transportConfig.relays,
5323
5270
  timeout: transportConfig.timeout,