@unicitylabs/sphere-sdk 0.2.5 → 0.3.0

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.
@@ -26,7 +26,17 @@ var STORAGE_KEYS_GLOBAL = {
26
26
  /** Active addresses registry (JSON: TrackedAddressesStorage) */
27
27
  TRACKED_ADDRESSES: "tracked_addresses",
28
28
  /** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */
29
- LAST_WALLET_EVENT_TS: "last_wallet_event_ts"
29
+ LAST_WALLET_EVENT_TS: "last_wallet_event_ts",
30
+ /** Group chat: joined groups */
31
+ GROUP_CHAT_GROUPS: "group_chat_groups",
32
+ /** Group chat: messages */
33
+ GROUP_CHAT_MESSAGES: "group_chat_messages",
34
+ /** Group chat: members */
35
+ GROUP_CHAT_MEMBERS: "group_chat_members",
36
+ /** Group chat: processed event IDs for deduplication */
37
+ GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events",
38
+ /** Group chat: last used relay URL (stale data detection) */
39
+ GROUP_CHAT_RELAY_URL: "group_chat_relay_url"
30
40
  };
31
41
  var STORAGE_KEYS_ADDRESS = {
32
42
  /** Pending transfers for this address */
@@ -107,27 +117,33 @@ var TEST_ELECTRUM_URL = "wss://fulcrum.alpha.testnet.unicity.network:50004";
107
117
  var TEST_NOSTR_RELAYS = [
108
118
  "wss://nostr-relay.testnet.unicity.network"
109
119
  ];
120
+ var DEFAULT_GROUP_RELAYS = [
121
+ "wss://sphere-relay.unicity.network"
122
+ ];
110
123
  var NETWORKS = {
111
124
  mainnet: {
112
125
  name: "Mainnet",
113
126
  aggregatorUrl: DEFAULT_AGGREGATOR_URL,
114
127
  nostrRelays: DEFAULT_NOSTR_RELAYS,
115
128
  ipfsGateways: DEFAULT_IPFS_GATEWAYS,
116
- electrumUrl: DEFAULT_ELECTRUM_URL
129
+ electrumUrl: DEFAULT_ELECTRUM_URL,
130
+ groupRelays: DEFAULT_GROUP_RELAYS
117
131
  },
118
132
  testnet: {
119
133
  name: "Testnet",
120
134
  aggregatorUrl: TEST_AGGREGATOR_URL,
121
135
  nostrRelays: TEST_NOSTR_RELAYS,
122
136
  ipfsGateways: DEFAULT_IPFS_GATEWAYS,
123
- electrumUrl: TEST_ELECTRUM_URL
137
+ electrumUrl: TEST_ELECTRUM_URL,
138
+ groupRelays: DEFAULT_GROUP_RELAYS
124
139
  },
125
140
  dev: {
126
141
  name: "Development",
127
142
  aggregatorUrl: DEV_AGGREGATOR_URL,
128
143
  nostrRelays: TEST_NOSTR_RELAYS,
129
144
  ipfsGateways: DEFAULT_IPFS_GATEWAYS,
130
- electrumUrl: TEST_ELECTRUM_URL
145
+ electrumUrl: TEST_ELECTRUM_URL,
146
+ groupRelays: DEFAULT_GROUP_RELAYS
131
147
  }
132
148
  };
133
149
  var TIMEOUTS = {
@@ -547,29 +563,6 @@ var IndexedDBTokenStorageProvider = class {
547
563
  }
548
564
  }
549
565
  // =========================================================================
550
- // Helper methods for individual token operations
551
- // =========================================================================
552
- async deleteToken(tokenId) {
553
- if (this.db) {
554
- await this.deleteFromStore(STORE_TOKENS, tokenId);
555
- }
556
- }
557
- async saveToken(tokenId, tokenData) {
558
- if (this.db) {
559
- await this.putToStore(STORE_TOKENS, tokenId, { id: tokenId, data: tokenData });
560
- }
561
- }
562
- async getToken(tokenId) {
563
- if (!this.db) return null;
564
- const result = await this.getFromStore(STORE_TOKENS, tokenId);
565
- return result?.data ?? null;
566
- }
567
- async listTokenIds() {
568
- if (!this.db) return [];
569
- const tokens = await this.getAllFromStore(STORE_TOKENS);
570
- return tokens.map((t) => t.id);
571
- }
572
- // =========================================================================
573
566
  // Private IndexedDB helpers
574
567
  // =========================================================================
575
568
  openDatabase() {
@@ -3851,9 +3844,13 @@ function mergeTxfData(local, remote) {
3851
3844
  remote._invalid ?? [],
3852
3845
  "tokenId"
3853
3846
  );
3847
+ const localNametags = local._nametags ?? [];
3848
+ const remoteNametags = remote._nametags ?? [];
3849
+ const mergedNametags = mergeNametagsByName(localNametags, remoteNametags);
3854
3850
  const merged = {
3855
3851
  _meta: mergedMeta,
3856
3852
  _tombstones: mergedTombstones.length > 0 ? mergedTombstones : void 0,
3853
+ _nametags: mergedNametags.length > 0 ? mergedNametags : void 0,
3857
3854
  _outbox: mergedOutbox.length > 0 ? mergedOutbox : void 0,
3858
3855
  _sent: mergedSent.length > 0 ? mergedSent : void 0,
3859
3856
  _invalid: mergedInvalid.length > 0 ? mergedInvalid : void 0,
@@ -3880,6 +3877,7 @@ function getTokenKeys(data) {
3880
3877
  "_sent",
3881
3878
  "_invalid",
3882
3879
  "_nametag",
3880
+ "_nametags",
3883
3881
  "_mintOutbox",
3884
3882
  "_invalidatedNametags",
3885
3883
  "_integrity"
@@ -3887,7 +3885,7 @@ function getTokenKeys(data) {
3887
3885
  const keys = /* @__PURE__ */ new Set();
3888
3886
  for (const key of Object.keys(data)) {
3889
3887
  if (reservedKeys.has(key)) continue;
3890
- if (key.startsWith("archived-") || key.startsWith("_forked_") || key.startsWith("nametag-")) continue;
3888
+ if (key.startsWith("archived-") || key.startsWith("_forked_")) continue;
3891
3889
  keys.add(key);
3892
3890
  }
3893
3891
  return keys;
@@ -3902,6 +3900,18 @@ function isTokenTombstoned(tokenId, localToken, remoteToken, tombstoneKeys) {
3902
3900
  void remoteToken;
3903
3901
  return false;
3904
3902
  }
3903
+ function mergeNametagsByName(local, remote) {
3904
+ const seen = /* @__PURE__ */ new Map();
3905
+ for (const item of local) {
3906
+ if (item.name) seen.set(item.name, item);
3907
+ }
3908
+ for (const item of remote) {
3909
+ if (item.name && !seen.has(item.name)) {
3910
+ seen.set(item.name, item);
3911
+ }
3912
+ }
3913
+ return Array.from(seen.values());
3914
+ }
3905
3915
  function mergeArrayById(local, remote, idField) {
3906
3916
  const seen = /* @__PURE__ */ new Map();
3907
3917
  for (const item of local) {
@@ -4256,14 +4266,11 @@ var AsyncSerialQueue = class {
4256
4266
  var WriteBuffer = class {
4257
4267
  /** Full TXF data from save() calls — latest wins */
4258
4268
  txfData = null;
4259
- /** Individual token mutations: key -> { op: 'save'|'delete', data? } */
4260
- tokenMutations = /* @__PURE__ */ new Map();
4261
4269
  get isEmpty() {
4262
- return this.txfData === null && this.tokenMutations.size === 0;
4270
+ return this.txfData === null;
4263
4271
  }
4264
4272
  clear() {
4265
4273
  this.txfData = null;
4266
- this.tokenMutations.clear();
4267
4274
  }
4268
4275
  /**
4269
4276
  * Merge another buffer's contents into this one (for rollback).
@@ -4273,11 +4280,6 @@ var WriteBuffer = class {
4273
4280
  if (other.txfData && !this.txfData) {
4274
4281
  this.txfData = other.txfData;
4275
4282
  }
4276
- for (const [id, mutation] of other.tokenMutations) {
4277
- if (!this.tokenMutations.has(id)) {
4278
- this.tokenMutations.set(id, mutation);
4279
- }
4280
- }
4281
4283
  }
4282
4284
  };
4283
4285
 
@@ -4317,9 +4319,6 @@ var IpfsStorageProvider = class {
4317
4319
  subscriptionClient = null;
4318
4320
  /** Unsubscribe function from subscription client */
4319
4321
  subscriptionUnsubscribe = null;
4320
- /** In-memory buffer for individual token save/delete calls */
4321
- tokenBuffer = /* @__PURE__ */ new Map();
4322
- deletedTokenIds = /* @__PURE__ */ new Set();
4323
4322
  /** Write-behind buffer: serializes flush / sync / shutdown */
4324
4323
  flushQueue = new AsyncSerialQueue();
4325
4324
  /** Pending mutations not yet flushed to IPFS */
@@ -4508,14 +4507,6 @@ var IpfsStorageProvider = class {
4508
4507
  metaUpdate.lastCid = this.remoteCid;
4509
4508
  }
4510
4509
  const updatedData = { ...data, _meta: metaUpdate };
4511
- for (const [tokenId, tokenData] of this.tokenBuffer) {
4512
- if (!this.deletedTokenIds.has(tokenId)) {
4513
- updatedData[tokenId] = tokenData;
4514
- }
4515
- }
4516
- for (const tokenId of this.deletedTokenIds) {
4517
- delete updatedData[tokenId];
4518
- }
4519
4510
  const { cid } = await this.httpClient.upload(updatedData);
4520
4511
  this.log(`Content uploaded: CID=${cid}`);
4521
4512
  const baseSeq = this.ipnsSequenceNumber > this.lastKnownRemoteSequence ? this.ipnsSequenceNumber : this.lastKnownRemoteSequence;
@@ -4554,7 +4545,6 @@ var IpfsStorageProvider = class {
4554
4545
  lastCid: cid,
4555
4546
  version: this.dataVersion
4556
4547
  });
4557
- this.deletedTokenIds.clear();
4558
4548
  this.emitEvent({
4559
4549
  type: "storage:saved",
4560
4550
  timestamp: Date.now(),
@@ -4666,7 +4656,6 @@ var IpfsStorageProvider = class {
4666
4656
  if (typeof remoteVersion === "number" && remoteVersion > this.dataVersion) {
4667
4657
  this.dataVersion = remoteVersion;
4668
4658
  }
4669
- this.populateTokenBuffer(data);
4670
4659
  this.emitEvent({
4671
4660
  type: "storage:loaded",
4672
4661
  timestamp: Date.now(),
@@ -4712,7 +4701,7 @@ var IpfsStorageProvider = class {
4712
4701
  this.emitEvent({ type: "sync:completed", timestamp: Date.now() });
4713
4702
  return {
4714
4703
  success: saveResult2.success,
4715
- merged: this.enrichWithTokenBuffer(localData),
4704
+ merged: localData,
4716
4705
  added: 0,
4717
4706
  removed: 0,
4718
4707
  conflicts: 0,
@@ -4727,7 +4716,7 @@ var IpfsStorageProvider = class {
4727
4716
  this.emitEvent({ type: "sync:completed", timestamp: Date.now() });
4728
4717
  return {
4729
4718
  success: true,
4730
- merged: this.enrichWithTokenBuffer(localData),
4719
+ merged: localData,
4731
4720
  added: 0,
4732
4721
  removed: 0,
4733
4722
  conflicts: 0
@@ -4750,7 +4739,7 @@ var IpfsStorageProvider = class {
4750
4739
  });
4751
4740
  return {
4752
4741
  success: saveResult.success,
4753
- merged: this.enrichWithTokenBuffer(merged),
4742
+ merged,
4754
4743
  added,
4755
4744
  removed,
4756
4745
  conflicts,
@@ -4776,21 +4765,6 @@ var IpfsStorageProvider = class {
4776
4765
  // ---------------------------------------------------------------------------
4777
4766
  // Private Helpers
4778
4767
  // ---------------------------------------------------------------------------
4779
- /**
4780
- * Enrich TXF data with individually-buffered tokens before returning to caller.
4781
- * PaymentsModule.createStorageData() passes empty tokens (they're stored via
4782
- * saveToken()), but loadFromStorageData() needs them in the merged result.
4783
- */
4784
- enrichWithTokenBuffer(data) {
4785
- if (this.tokenBuffer.size === 0) return data;
4786
- const enriched = { ...data };
4787
- for (const [tokenId, tokenData] of this.tokenBuffer) {
4788
- if (!this.deletedTokenIds.has(tokenId)) {
4789
- enriched[tokenId] = tokenData;
4790
- }
4791
- }
4792
- return enriched;
4793
- }
4794
4768
  // ---------------------------------------------------------------------------
4795
4769
  // Optional Methods
4796
4770
  // ---------------------------------------------------------------------------
@@ -4820,8 +4794,6 @@ var IpfsStorageProvider = class {
4820
4794
  const result = await this._doSave(emptyData);
4821
4795
  if (result.success) {
4822
4796
  this.cache.clear();
4823
- this.tokenBuffer.clear();
4824
- this.deletedTokenIds.clear();
4825
4797
  await this.statePersistence.clear(this.ipnsName);
4826
4798
  }
4827
4799
  return result.success;
@@ -4832,27 +4804,6 @@ var IpfsStorageProvider = class {
4832
4804
  this.eventCallbacks.delete(callback);
4833
4805
  };
4834
4806
  }
4835
- async saveToken(tokenId, tokenData) {
4836
- this.pendingBuffer.tokenMutations.set(tokenId, { op: "save", data: tokenData });
4837
- this.tokenBuffer.set(tokenId, tokenData);
4838
- this.deletedTokenIds.delete(tokenId);
4839
- this.scheduleFlush();
4840
- }
4841
- async getToken(tokenId) {
4842
- if (this.deletedTokenIds.has(tokenId)) return null;
4843
- return this.tokenBuffer.get(tokenId) ?? null;
4844
- }
4845
- async listTokenIds() {
4846
- return Array.from(this.tokenBuffer.keys()).filter(
4847
- (id) => !this.deletedTokenIds.has(id)
4848
- );
4849
- }
4850
- async deleteToken(tokenId) {
4851
- this.pendingBuffer.tokenMutations.set(tokenId, { op: "delete" });
4852
- this.tokenBuffer.delete(tokenId);
4853
- this.deletedTokenIds.add(tokenId);
4854
- this.scheduleFlush();
4855
- }
4856
4807
  // ---------------------------------------------------------------------------
4857
4808
  // Public Accessors
4858
4809
  // ---------------------------------------------------------------------------
@@ -4944,26 +4895,6 @@ var IpfsStorageProvider = class {
4944
4895
  console.log(`[IPFS-Storage] ${message}`);
4945
4896
  }
4946
4897
  }
4947
- META_KEYS = /* @__PURE__ */ new Set([
4948
- "_meta",
4949
- "_tombstones",
4950
- "_outbox",
4951
- "_sent",
4952
- "_invalid",
4953
- "_nametag",
4954
- "_mintOutbox",
4955
- "_invalidatedNametags",
4956
- "_integrity"
4957
- ]);
4958
- populateTokenBuffer(data) {
4959
- this.tokenBuffer.clear();
4960
- this.deletedTokenIds.clear();
4961
- for (const key of Object.keys(data)) {
4962
- if (!this.META_KEYS.has(key)) {
4963
- this.tokenBuffer.set(key, data[key]);
4964
- }
4965
- }
4966
- }
4967
4898
  };
4968
4899
 
4969
4900
  // impl/browser/ipfs/browser-ipfs-state-persistence.ts
@@ -5202,6 +5133,20 @@ function resolveArrayConfig(defaults, replace, additional) {
5202
5133
  }
5203
5134
  return result;
5204
5135
  }
5136
+ function resolveGroupChatConfig(network, config) {
5137
+ if (!config) return void 0;
5138
+ if (config === true) {
5139
+ const netConfig2 = getNetworkConfig(network);
5140
+ return { relays: [...netConfig2.groupRelays] };
5141
+ }
5142
+ if (typeof config === "object" && config.enabled === false) {
5143
+ return void 0;
5144
+ }
5145
+ const netConfig = getNetworkConfig(network);
5146
+ return {
5147
+ relays: config.relays ?? [...netConfig.groupRelays]
5148
+ };
5149
+ }
5205
5150
 
5206
5151
  // impl/browser/index.ts
5207
5152
  if (typeof globalThis.Buffer === "undefined") {
@@ -5267,8 +5212,10 @@ function createBrowserProviders(config) {
5267
5212
  debug: config?.tokenSync?.ipfs?.useDht
5268
5213
  // reuse debug-like flag
5269
5214
  }) : void 0;
5215
+ const groupChat = resolveGroupChatConfig(network, config?.groupChat);
5270
5216
  return {
5271
5217
  storage,
5218
+ groupChat,
5272
5219
  transport: createNostrTransportProvider({
5273
5220
  relays: transportConfig.relays,
5274
5221
  timeout: transportConfig.timeout,