@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.
@@ -76,7 +76,17 @@ var STORAGE_KEYS_GLOBAL = {
76
76
  /** Active addresses registry (JSON: TrackedAddressesStorage) */
77
77
  TRACKED_ADDRESSES: "tracked_addresses",
78
78
  /** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */
79
- LAST_WALLET_EVENT_TS: "last_wallet_event_ts"
79
+ LAST_WALLET_EVENT_TS: "last_wallet_event_ts",
80
+ /** Group chat: joined groups */
81
+ GROUP_CHAT_GROUPS: "group_chat_groups",
82
+ /** Group chat: messages */
83
+ GROUP_CHAT_MESSAGES: "group_chat_messages",
84
+ /** Group chat: members */
85
+ GROUP_CHAT_MEMBERS: "group_chat_members",
86
+ /** Group chat: processed event IDs for deduplication */
87
+ GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events",
88
+ /** Group chat: last used relay URL (stale data detection) */
89
+ GROUP_CHAT_RELAY_URL: "group_chat_relay_url"
80
90
  };
81
91
  var STORAGE_KEYS_ADDRESS = {
82
92
  /** Pending transfers for this address */
@@ -157,27 +167,33 @@ var TEST_ELECTRUM_URL = "wss://fulcrum.alpha.testnet.unicity.network:50004";
157
167
  var TEST_NOSTR_RELAYS = [
158
168
  "wss://nostr-relay.testnet.unicity.network"
159
169
  ];
170
+ var DEFAULT_GROUP_RELAYS = [
171
+ "wss://sphere-relay.unicity.network"
172
+ ];
160
173
  var NETWORKS = {
161
174
  mainnet: {
162
175
  name: "Mainnet",
163
176
  aggregatorUrl: DEFAULT_AGGREGATOR_URL,
164
177
  nostrRelays: DEFAULT_NOSTR_RELAYS,
165
178
  ipfsGateways: DEFAULT_IPFS_GATEWAYS,
166
- electrumUrl: DEFAULT_ELECTRUM_URL
179
+ electrumUrl: DEFAULT_ELECTRUM_URL,
180
+ groupRelays: DEFAULT_GROUP_RELAYS
167
181
  },
168
182
  testnet: {
169
183
  name: "Testnet",
170
184
  aggregatorUrl: TEST_AGGREGATOR_URL,
171
185
  nostrRelays: TEST_NOSTR_RELAYS,
172
186
  ipfsGateways: DEFAULT_IPFS_GATEWAYS,
173
- electrumUrl: TEST_ELECTRUM_URL
187
+ electrumUrl: TEST_ELECTRUM_URL,
188
+ groupRelays: DEFAULT_GROUP_RELAYS
174
189
  },
175
190
  dev: {
176
191
  name: "Development",
177
192
  aggregatorUrl: DEV_AGGREGATOR_URL,
178
193
  nostrRelays: TEST_NOSTR_RELAYS,
179
194
  ipfsGateways: DEFAULT_IPFS_GATEWAYS,
180
- electrumUrl: TEST_ELECTRUM_URL
195
+ electrumUrl: TEST_ELECTRUM_URL,
196
+ groupRelays: DEFAULT_GROUP_RELAYS
181
197
  }
182
198
  };
183
199
  var TIMEOUTS = {
@@ -494,34 +510,6 @@ var FileTokenStorageProvider = class {
494
510
  return false;
495
511
  }
496
512
  }
497
- async deleteToken(tokenId) {
498
- const filePath = path2.join(this.tokensDir, `${tokenId}.json`);
499
- if (fs2.existsSync(filePath)) {
500
- fs2.unlinkSync(filePath);
501
- }
502
- }
503
- async saveToken(tokenId, tokenData) {
504
- fs2.writeFileSync(
505
- path2.join(this.tokensDir, `${tokenId}.json`),
506
- JSON.stringify(tokenData, null, 2)
507
- );
508
- }
509
- async getToken(tokenId) {
510
- const filePath = path2.join(this.tokensDir, `${tokenId}.json`);
511
- if (!fs2.existsSync(filePath)) {
512
- return null;
513
- }
514
- try {
515
- const content = fs2.readFileSync(filePath, "utf-8");
516
- return JSON.parse(content);
517
- } catch {
518
- return null;
519
- }
520
- }
521
- async listTokenIds() {
522
- const files = fs2.readdirSync(this.tokensDir).filter((f) => f.endsWith(".json") && f !== "_meta.json");
523
- return files.map((f) => path2.basename(f, ".json"));
524
- }
525
513
  };
526
514
  function createFileTokenStorageProvider(config) {
527
515
  return new FileTokenStorageProvider(config);
@@ -3657,9 +3645,13 @@ function mergeTxfData(local, remote) {
3657
3645
  remote._invalid ?? [],
3658
3646
  "tokenId"
3659
3647
  );
3648
+ const localNametags = local._nametags ?? [];
3649
+ const remoteNametags = remote._nametags ?? [];
3650
+ const mergedNametags = mergeNametagsByName(localNametags, remoteNametags);
3660
3651
  const merged = {
3661
3652
  _meta: mergedMeta,
3662
3653
  _tombstones: mergedTombstones.length > 0 ? mergedTombstones : void 0,
3654
+ _nametags: mergedNametags.length > 0 ? mergedNametags : void 0,
3663
3655
  _outbox: mergedOutbox.length > 0 ? mergedOutbox : void 0,
3664
3656
  _sent: mergedSent.length > 0 ? mergedSent : void 0,
3665
3657
  _invalid: mergedInvalid.length > 0 ? mergedInvalid : void 0,
@@ -3686,6 +3678,7 @@ function getTokenKeys(data) {
3686
3678
  "_sent",
3687
3679
  "_invalid",
3688
3680
  "_nametag",
3681
+ "_nametags",
3689
3682
  "_mintOutbox",
3690
3683
  "_invalidatedNametags",
3691
3684
  "_integrity"
@@ -3693,7 +3686,7 @@ function getTokenKeys(data) {
3693
3686
  const keys = /* @__PURE__ */ new Set();
3694
3687
  for (const key of Object.keys(data)) {
3695
3688
  if (reservedKeys.has(key)) continue;
3696
- if (key.startsWith("archived-") || key.startsWith("_forked_") || key.startsWith("nametag-")) continue;
3689
+ if (key.startsWith("archived-") || key.startsWith("_forked_")) continue;
3697
3690
  keys.add(key);
3698
3691
  }
3699
3692
  return keys;
@@ -3708,6 +3701,18 @@ function isTokenTombstoned(tokenId, localToken, remoteToken, tombstoneKeys) {
3708
3701
  void remoteToken;
3709
3702
  return false;
3710
3703
  }
3704
+ function mergeNametagsByName(local, remote) {
3705
+ const seen = /* @__PURE__ */ new Map();
3706
+ for (const item of local) {
3707
+ if (item.name) seen.set(item.name, item);
3708
+ }
3709
+ for (const item of remote) {
3710
+ if (item.name && !seen.has(item.name)) {
3711
+ seen.set(item.name, item);
3712
+ }
3713
+ }
3714
+ return Array.from(seen.values());
3715
+ }
3711
3716
  function mergeArrayById(local, remote, idField) {
3712
3717
  const seen = /* @__PURE__ */ new Map();
3713
3718
  for (const item of local) {
@@ -4062,14 +4067,11 @@ var AsyncSerialQueue = class {
4062
4067
  var WriteBuffer = class {
4063
4068
  /** Full TXF data from save() calls — latest wins */
4064
4069
  txfData = null;
4065
- /** Individual token mutations: key -> { op: 'save'|'delete', data? } */
4066
- tokenMutations = /* @__PURE__ */ new Map();
4067
4070
  get isEmpty() {
4068
- return this.txfData === null && this.tokenMutations.size === 0;
4071
+ return this.txfData === null;
4069
4072
  }
4070
4073
  clear() {
4071
4074
  this.txfData = null;
4072
- this.tokenMutations.clear();
4073
4075
  }
4074
4076
  /**
4075
4077
  * Merge another buffer's contents into this one (for rollback).
@@ -4079,11 +4081,6 @@ var WriteBuffer = class {
4079
4081
  if (other.txfData && !this.txfData) {
4080
4082
  this.txfData = other.txfData;
4081
4083
  }
4082
- for (const [id, mutation] of other.tokenMutations) {
4083
- if (!this.tokenMutations.has(id)) {
4084
- this.tokenMutations.set(id, mutation);
4085
- }
4086
- }
4087
4084
  }
4088
4085
  };
4089
4086
 
@@ -4123,9 +4120,6 @@ var IpfsStorageProvider = class {
4123
4120
  subscriptionClient = null;
4124
4121
  /** Unsubscribe function from subscription client */
4125
4122
  subscriptionUnsubscribe = null;
4126
- /** In-memory buffer for individual token save/delete calls */
4127
- tokenBuffer = /* @__PURE__ */ new Map();
4128
- deletedTokenIds = /* @__PURE__ */ new Set();
4129
4123
  /** Write-behind buffer: serializes flush / sync / shutdown */
4130
4124
  flushQueue = new AsyncSerialQueue();
4131
4125
  /** Pending mutations not yet flushed to IPFS */
@@ -4314,14 +4308,6 @@ var IpfsStorageProvider = class {
4314
4308
  metaUpdate.lastCid = this.remoteCid;
4315
4309
  }
4316
4310
  const updatedData = { ...data, _meta: metaUpdate };
4317
- for (const [tokenId, tokenData] of this.tokenBuffer) {
4318
- if (!this.deletedTokenIds.has(tokenId)) {
4319
- updatedData[tokenId] = tokenData;
4320
- }
4321
- }
4322
- for (const tokenId of this.deletedTokenIds) {
4323
- delete updatedData[tokenId];
4324
- }
4325
4311
  const { cid } = await this.httpClient.upload(updatedData);
4326
4312
  this.log(`Content uploaded: CID=${cid}`);
4327
4313
  const baseSeq = this.ipnsSequenceNumber > this.lastKnownRemoteSequence ? this.ipnsSequenceNumber : this.lastKnownRemoteSequence;
@@ -4360,7 +4346,6 @@ var IpfsStorageProvider = class {
4360
4346
  lastCid: cid,
4361
4347
  version: this.dataVersion
4362
4348
  });
4363
- this.deletedTokenIds.clear();
4364
4349
  this.emitEvent({
4365
4350
  type: "storage:saved",
4366
4351
  timestamp: Date.now(),
@@ -4472,7 +4457,6 @@ var IpfsStorageProvider = class {
4472
4457
  if (typeof remoteVersion === "number" && remoteVersion > this.dataVersion) {
4473
4458
  this.dataVersion = remoteVersion;
4474
4459
  }
4475
- this.populateTokenBuffer(data);
4476
4460
  this.emitEvent({
4477
4461
  type: "storage:loaded",
4478
4462
  timestamp: Date.now(),
@@ -4518,7 +4502,7 @@ var IpfsStorageProvider = class {
4518
4502
  this.emitEvent({ type: "sync:completed", timestamp: Date.now() });
4519
4503
  return {
4520
4504
  success: saveResult2.success,
4521
- merged: this.enrichWithTokenBuffer(localData),
4505
+ merged: localData,
4522
4506
  added: 0,
4523
4507
  removed: 0,
4524
4508
  conflicts: 0,
@@ -4533,7 +4517,7 @@ var IpfsStorageProvider = class {
4533
4517
  this.emitEvent({ type: "sync:completed", timestamp: Date.now() });
4534
4518
  return {
4535
4519
  success: true,
4536
- merged: this.enrichWithTokenBuffer(localData),
4520
+ merged: localData,
4537
4521
  added: 0,
4538
4522
  removed: 0,
4539
4523
  conflicts: 0
@@ -4556,7 +4540,7 @@ var IpfsStorageProvider = class {
4556
4540
  });
4557
4541
  return {
4558
4542
  success: saveResult.success,
4559
- merged: this.enrichWithTokenBuffer(merged),
4543
+ merged,
4560
4544
  added,
4561
4545
  removed,
4562
4546
  conflicts,
@@ -4582,21 +4566,6 @@ var IpfsStorageProvider = class {
4582
4566
  // ---------------------------------------------------------------------------
4583
4567
  // Private Helpers
4584
4568
  // ---------------------------------------------------------------------------
4585
- /**
4586
- * Enrich TXF data with individually-buffered tokens before returning to caller.
4587
- * PaymentsModule.createStorageData() passes empty tokens (they're stored via
4588
- * saveToken()), but loadFromStorageData() needs them in the merged result.
4589
- */
4590
- enrichWithTokenBuffer(data) {
4591
- if (this.tokenBuffer.size === 0) return data;
4592
- const enriched = { ...data };
4593
- for (const [tokenId, tokenData] of this.tokenBuffer) {
4594
- if (!this.deletedTokenIds.has(tokenId)) {
4595
- enriched[tokenId] = tokenData;
4596
- }
4597
- }
4598
- return enriched;
4599
- }
4600
4569
  // ---------------------------------------------------------------------------
4601
4570
  // Optional Methods
4602
4571
  // ---------------------------------------------------------------------------
@@ -4626,8 +4595,6 @@ var IpfsStorageProvider = class {
4626
4595
  const result = await this._doSave(emptyData);
4627
4596
  if (result.success) {
4628
4597
  this.cache.clear();
4629
- this.tokenBuffer.clear();
4630
- this.deletedTokenIds.clear();
4631
4598
  await this.statePersistence.clear(this.ipnsName);
4632
4599
  }
4633
4600
  return result.success;
@@ -4638,27 +4605,6 @@ var IpfsStorageProvider = class {
4638
4605
  this.eventCallbacks.delete(callback);
4639
4606
  };
4640
4607
  }
4641
- async saveToken(tokenId, tokenData) {
4642
- this.pendingBuffer.tokenMutations.set(tokenId, { op: "save", data: tokenData });
4643
- this.tokenBuffer.set(tokenId, tokenData);
4644
- this.deletedTokenIds.delete(tokenId);
4645
- this.scheduleFlush();
4646
- }
4647
- async getToken(tokenId) {
4648
- if (this.deletedTokenIds.has(tokenId)) return null;
4649
- return this.tokenBuffer.get(tokenId) ?? null;
4650
- }
4651
- async listTokenIds() {
4652
- return Array.from(this.tokenBuffer.keys()).filter(
4653
- (id) => !this.deletedTokenIds.has(id)
4654
- );
4655
- }
4656
- async deleteToken(tokenId) {
4657
- this.pendingBuffer.tokenMutations.set(tokenId, { op: "delete" });
4658
- this.tokenBuffer.delete(tokenId);
4659
- this.deletedTokenIds.add(tokenId);
4660
- this.scheduleFlush();
4661
- }
4662
4608
  // ---------------------------------------------------------------------------
4663
4609
  // Public Accessors
4664
4610
  // ---------------------------------------------------------------------------
@@ -4750,26 +4696,6 @@ var IpfsStorageProvider = class {
4750
4696
  console.log(`[IPFS-Storage] ${message}`);
4751
4697
  }
4752
4698
  }
4753
- META_KEYS = /* @__PURE__ */ new Set([
4754
- "_meta",
4755
- "_tombstones",
4756
- "_outbox",
4757
- "_sent",
4758
- "_invalid",
4759
- "_nametag",
4760
- "_mintOutbox",
4761
- "_invalidatedNametags",
4762
- "_integrity"
4763
- ]);
4764
- populateTokenBuffer(data) {
4765
- this.tokenBuffer.clear();
4766
- this.deletedTokenIds.clear();
4767
- for (const key of Object.keys(data)) {
4768
- if (!this.META_KEYS.has(key)) {
4769
- this.tokenBuffer.set(key, data[key]);
4770
- }
4771
- }
4772
- }
4773
4699
  };
4774
4700
 
4775
4701
  // impl/nodejs/ipfs/nodejs-ipfs-state-persistence.ts
@@ -4995,6 +4921,20 @@ function resolvePriceConfig(config) {
4995
4921
  debug: config.debug
4996
4922
  };
4997
4923
  }
4924
+ function resolveGroupChatConfig(network, config) {
4925
+ if (!config) return void 0;
4926
+ if (config === true) {
4927
+ const netConfig2 = getNetworkConfig(network);
4928
+ return { relays: [...netConfig2.groupRelays] };
4929
+ }
4930
+ if (typeof config === "object" && config.enabled === false) {
4931
+ return void 0;
4932
+ }
4933
+ const netConfig = getNetworkConfig(network);
4934
+ return {
4935
+ relays: config.relays ?? [...netConfig.groupRelays]
4936
+ };
4937
+ }
4998
4938
 
4999
4939
  // impl/nodejs/index.ts
5000
4940
  function createNodeProviders(config) {
@@ -5008,8 +4948,10 @@ function createNodeProviders(config) {
5008
4948
  });
5009
4949
  const ipfsSync = config?.tokenSync?.ipfs;
5010
4950
  const ipfsTokenStorage = ipfsSync?.enabled ? createNodeIpfsStorageProvider(ipfsSync.config, storage) : void 0;
4951
+ const groupChat = resolveGroupChatConfig(network, config?.groupChat);
5011
4952
  return {
5012
4953
  storage,
4954
+ groupChat,
5013
4955
  tokenStorage: createFileTokenStorageProvider({
5014
4956
  tokensDir: config?.tokensDir ?? "./sphere-tokens"
5015
4957
  }),