@unicitylabs/sphere-sdk 0.2.3 → 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.
@@ -1,6 +1,73 @@
1
1
  import { Token as Token$1 } from '@unicitylabs/state-transition-sdk/lib/token/Token';
2
2
  import elliptic from 'elliptic';
3
3
 
4
+ /**
5
+ * Group Chat Types (NIP-29)
6
+ * Plain interfaces for SDK consumers — no classes, no UI helpers.
7
+ */
8
+ declare const GroupRole: {
9
+ readonly ADMIN: "ADMIN";
10
+ readonly MODERATOR: "MODERATOR";
11
+ readonly MEMBER: "MEMBER";
12
+ };
13
+ type GroupRole = (typeof GroupRole)[keyof typeof GroupRole];
14
+ declare const GroupVisibility: {
15
+ readonly PUBLIC: "PUBLIC";
16
+ readonly PRIVATE: "PRIVATE";
17
+ };
18
+ type GroupVisibility = (typeof GroupVisibility)[keyof typeof GroupVisibility];
19
+ interface GroupData {
20
+ id: string;
21
+ relayUrl: string;
22
+ name: string;
23
+ description?: string;
24
+ picture?: string;
25
+ visibility: GroupVisibility;
26
+ createdAt: number;
27
+ updatedAt?: number;
28
+ memberCount?: number;
29
+ unreadCount?: number;
30
+ lastMessageTime?: number;
31
+ lastMessageText?: string;
32
+ /** When the current user joined this group locally (used to filter old events) */
33
+ localJoinedAt?: number;
34
+ }
35
+ interface GroupMessageData {
36
+ id?: string;
37
+ groupId: string;
38
+ content: string;
39
+ timestamp: number;
40
+ senderPubkey: string;
41
+ senderNametag?: string;
42
+ replyToId?: string;
43
+ previousIds?: string[];
44
+ }
45
+ interface GroupMemberData {
46
+ pubkey: string;
47
+ groupId: string;
48
+ role: GroupRole;
49
+ nametag?: string;
50
+ joinedAt: number;
51
+ }
52
+ interface GroupChatModuleConfig {
53
+ /** Override relay URLs (default: from network config) */
54
+ relays?: string[];
55
+ /** Default message fetch limit (default: 50) */
56
+ defaultMessageLimit?: number;
57
+ /** Max previous message IDs in ordering tags (default: 3) */
58
+ maxPreviousTags?: number;
59
+ /** Reconnect delay in ms (default: 3000) */
60
+ reconnectDelayMs?: number;
61
+ /** Max reconnect attempts (default: 5) */
62
+ maxReconnectAttempts?: number;
63
+ }
64
+ interface CreateGroupOptions {
65
+ name: string;
66
+ description?: string;
67
+ picture?: string;
68
+ visibility?: GroupVisibility;
69
+ }
70
+
4
71
  /**
5
72
  * Cryptographic utilities for SDK2
6
73
  *
@@ -272,147 +339,6 @@ interface TombstoneEntry {
272
339
  timestamp: number;
273
340
  }
274
341
 
275
- /**
276
- * INSTANT_SPLIT V5 Types
277
- *
278
- * Optimized token split transfer types that achieve ~2.3s critical path latency
279
- * instead of the standard ~42s sequential flow.
280
- *
281
- * Key Insight: TransferCommitment.create() only needs token.state, NOT the mint proof.
282
- * This allows creating transfer commitments immediately after mint data creation,
283
- * without waiting for mint proofs.
284
- *
285
- * V5 Flow (Production Mode):
286
- * 1. Create burn commitment, submit to aggregator
287
- * 2. Wait for burn inclusion proof (~2s - unavoidable)
288
- * 3. Create mint commitments with proper SplitMintReason (requires burn proof)
289
- * 4. Create transfer commitment from mint data (no mint proof needed)
290
- * 5. Package bundle -> send via Nostr -> SUCCESS (~2.3s total!)
291
- * 6. Background: submit mints, wait for proofs, save change token, sync storage
292
- */
293
- type SdkToken = any;
294
- /**
295
- * Bundle payload for INSTANT_SPLIT V5 (Production Mode)
296
- *
297
- * V5 achieves ~2.3s sender latency while working with production aggregators:
298
- * - Burn proof is required for creating SplitMintReason
299
- * - Transfer commitment is created from mint data WITHOUT waiting for mint proof
300
- * - Mints are submitted in background after Nostr delivery
301
- *
302
- * Security: Burn is proven on-chain before mints can be created, preventing double-spend.
303
- */
304
- interface InstantSplitBundleV5 {
305
- /** Bundle version - V5 is production mode (proper SplitMintReason) */
306
- version: '5.0';
307
- /** Bundle type identifier */
308
- type: 'INSTANT_SPLIT';
309
- /**
310
- * Burn TRANSACTION JSON (WITH inclusion proof!)
311
- * V5 sends the proven burn transaction so recipient can verify burn completed.
312
- */
313
- burnTransaction: string;
314
- /**
315
- * Recipient's MintTransactionData JSON (contains proper SplitMintReason in V5)
316
- * The SplitMintReason references the burn transaction.
317
- */
318
- recipientMintData: string;
319
- /**
320
- * Pre-created TransferCommitment JSON (recipient submits and waits for proof)
321
- * Created from mint data WITHOUT any proofs.
322
- */
323
- transferCommitment: string;
324
- /** Payment amount (display metadata) */
325
- amount: string;
326
- /** Coin ID hex */
327
- coinId: string;
328
- /** Token type hex */
329
- tokenTypeHex: string;
330
- /** Split group ID for recovery correlation */
331
- splitGroupId: string;
332
- /** Sender's pubkey for acknowledgment */
333
- senderPubkey: string;
334
- /** Salt for recipient predicate creation (hex) */
335
- recipientSaltHex: string;
336
- /** Salt for transfer commitment creation (hex) */
337
- transferSaltHex: string;
338
- /**
339
- * Serialized TokenState JSON for the intermediate minted token.
340
- *
341
- * In V5, the mint is to sender's address first, then transferred to recipient.
342
- * The recipient needs this state to reconstruct the minted token before applying transfer.
343
- * Without this, the recipient can't create a matching predicate (they don't have sender's signing key).
344
- */
345
- mintedTokenStateJson: string;
346
- /**
347
- * Serialized TokenState JSON for the final recipient state (after transfer).
348
- *
349
- * The sender creates the transfer commitment targeting the recipient's PROXY address.
350
- * The recipient can't recreate this state correctly because their signingService
351
- * creates predicates for their DIRECT address, not the PROXY address.
352
- * This is optional - recipient can create their own if they have the correct address.
353
- */
354
- finalRecipientStateJson: string;
355
- /**
356
- * Serialized recipient address JSON (PROXY or DIRECT).
357
- *
358
- * Used by the recipient to identify which nametag token is being targeted.
359
- * For PROXY address transfers, the recipient needs to find the matching
360
- * nametag token and pass it to finalizeTransaction() for verification.
361
- */
362
- recipientAddressJson: string;
363
- /**
364
- * Serialized nametag token JSON (for PROXY address transfers).
365
- *
366
- * For PROXY address transfers, the sender includes the nametag token
367
- * so the recipient can verify they're authorized to receive at this address.
368
- * This is REQUIRED for PROXY addresses - transfers without this will fail.
369
- */
370
- nametagTokenJson?: string;
371
- }
372
- /**
373
- * Bundle payload for INSTANT_SPLIT V4 (Dev Mode Only - True Nostr-First Split)
374
- *
375
- * V4 achieves near-zero sender latency (~0.3s) by:
376
- * 1. Creating ALL commitments locally BEFORE any aggregator submission
377
- * 2. Persisting via Nostr FIRST
378
- * 3. Then submitting ALL to aggregator in background
379
- *
380
- * NOTE: V4 only works in dev mode. Production requires V5 with proper SplitMintReason.
381
- */
382
- interface InstantSplitBundleV4 {
383
- /** Bundle version - V4 is true Nostr-first (dev mode only) */
384
- version: '4.0';
385
- /** Bundle type identifier */
386
- type: 'INSTANT_SPLIT';
387
- /**
388
- * Burn commitment JSON (NOT transaction - no proof yet!)
389
- * Both sender and recipient submit this to aggregator.
390
- */
391
- burnCommitment: string;
392
- /** Recipient's MintTransactionData JSON (they recreate commitment and submit) */
393
- recipientMintData: string;
394
- /**
395
- * Pre-created TransferCommitment JSON (recipient submits and waits for proof)
396
- * Created from mint data WITHOUT any proofs.
397
- */
398
- transferCommitment: string;
399
- /** Payment amount (display metadata) */
400
- amount: string;
401
- /** Coin ID hex */
402
- coinId: string;
403
- /** Token type hex */
404
- tokenTypeHex: string;
405
- /** Split group ID for recovery correlation */
406
- splitGroupId: string;
407
- /** Sender's pubkey for acknowledgment */
408
- senderPubkey: string;
409
- /** Salt for recipient predicate creation (hex) */
410
- recipientSaltHex: string;
411
- /** Salt for transfer commitment creation (hex) */
412
- transferSaltHex: string;
413
- }
414
- /** Union type for all InstantSplit bundle versions */
415
- type InstantSplitBundle = InstantSplitBundleV4 | InstantSplitBundleV5;
416
342
  /**
417
343
  * Result of an instant split send operation
418
344
  */
@@ -429,19 +355,8 @@ interface InstantSplitResult {
429
355
  error?: string;
430
356
  /** Whether background processing was started */
431
357
  backgroundStarted?: boolean;
432
- }
433
- /**
434
- * Result from processing an INSTANT_SPLIT bundle (recipient side)
435
- */
436
- interface InstantSplitProcessResult {
437
- /** Whether processing succeeded */
438
- success: boolean;
439
- /** The finalized SDK token (if successful) */
440
- token?: SdkToken;
441
- /** Error message (if failed) */
442
- error?: string;
443
- /** Processing duration in ms */
444
- durationMs: number;
358
+ /** Promise that resolves when background processing completes (change token saved) */
359
+ backgroundPromise?: Promise<void>;
445
360
  }
446
361
  /**
447
362
  * Options for instant split send operation
@@ -476,6 +391,17 @@ interface BackgroundProgressStatus {
476
391
  message: string;
477
392
  error?: string;
478
393
  }
394
+ /** Result of resolveUnconfirmed() */
395
+ interface UnconfirmedResolutionResult {
396
+ resolved: number;
397
+ stillPending: number;
398
+ failed: number;
399
+ details: Array<{
400
+ tokenId: string;
401
+ stage: string;
402
+ status: 'resolved' | 'pending' | 'failed';
403
+ }>;
404
+ }
479
405
 
480
406
  /**
481
407
  * SDK2 Core Types
@@ -529,6 +455,14 @@ interface Asset {
529
455
  readonly iconUrl?: string;
530
456
  readonly totalAmount: string;
531
457
  readonly tokenCount: number;
458
+ /** Sum of confirmed token amounts (smallest units) */
459
+ readonly confirmedAmount: string;
460
+ /** Sum of unconfirmed (submitted/pending) token amounts (smallest units) */
461
+ readonly unconfirmedAmount: string;
462
+ /** Number of confirmed tokens aggregated */
463
+ readonly confirmedTokenCount: number;
464
+ /** Number of unconfirmed tokens aggregated */
465
+ readonly unconfirmedTokenCount: number;
532
466
  /** Price per whole unit in USD (null if PriceProvider not configured) */
533
467
  readonly priceUsd: number | null;
534
468
  /** Price per whole unit in EUR (null if PriceProvider not configured) */
@@ -542,6 +476,7 @@ interface Asset {
542
476
  }
543
477
  type TransferStatus = 'pending' | 'submitted' | 'confirmed' | 'delivered' | 'completed' | 'failed';
544
478
  type AddressMode = 'auto' | 'direct' | 'proxy';
479
+ type TransferMode = 'instant' | 'conservative';
545
480
  interface TransferRequest {
546
481
  readonly coinId: string;
547
482
  readonly amount: string;
@@ -549,12 +484,31 @@ interface TransferRequest {
549
484
  readonly memo?: string;
550
485
  /** Address mode: 'auto' (default) uses directAddress if available, 'direct' forces DIRECT, 'proxy' forces PROXY */
551
486
  readonly addressMode?: AddressMode;
487
+ /** Transfer mode: 'instant' (default) sends via Nostr immediately, 'conservative' collects all proofs first */
488
+ readonly transferMode?: TransferMode;
489
+ }
490
+ /**
491
+ * Per-token transfer detail tracking the on-chain commitment or split operation
492
+ * for each source token involved in a transfer.
493
+ */
494
+ interface TokenTransferDetail {
495
+ /** Source token ID that was consumed in this transfer */
496
+ readonly sourceTokenId: string;
497
+ /** Transfer method used for this token */
498
+ readonly method: 'direct' | 'split';
499
+ /** Aggregator commitment request ID hex (for direct transfers) */
500
+ readonly requestIdHex?: string;
501
+ /** Split group ID (for split transfers — correlates sender/recipient/change tokens) */
502
+ readonly splitGroupId?: string;
503
+ /** Nostr event ID (for split transfers delivered via Nostr) */
504
+ readonly nostrEventId?: string;
552
505
  }
553
506
  interface TransferResult {
554
507
  readonly id: string;
555
508
  status: TransferStatus;
556
509
  readonly tokens: Token[];
557
- txHash?: string;
510
+ /** Per-token transfer details — one entry per source token consumed */
511
+ readonly tokenTransfers: TokenTransferDetail[];
558
512
  error?: string;
559
513
  }
560
514
  interface IncomingTransfer {
@@ -731,7 +685,7 @@ interface TrackedAddress extends TrackedAddressEntry {
731
685
  /** Primary nametag (from nametag cache, without @ prefix) */
732
686
  readonly nametag?: string;
733
687
  }
734
- type SphereEventType = 'transfer:incoming' | 'transfer:confirmed' | 'transfer:failed' | 'payment_request:incoming' | 'payment_request:accepted' | 'payment_request:rejected' | 'payment_request:paid' | 'payment_request:response' | 'message:dm' | 'message:broadcast' | 'sync:started' | 'sync:completed' | 'sync:provider' | 'sync:error' | 'connection:changed' | 'nametag:registered' | 'nametag:recovered' | 'identity:changed' | 'address:activated' | 'address:hidden' | 'address:unhidden';
688
+ type SphereEventType = 'transfer:incoming' | 'transfer:confirmed' | 'transfer:failed' | 'payment_request:incoming' | 'payment_request:accepted' | 'payment_request:rejected' | 'payment_request:paid' | 'payment_request:response' | 'message:dm' | 'message:broadcast' | 'sync:started' | 'sync:completed' | 'sync:provider' | 'sync:error' | 'connection:changed' | 'nametag:registered' | 'nametag:recovered' | 'identity:changed' | 'address:activated' | 'address:hidden' | 'address:unhidden' | 'sync:remote-update' | 'groupchat:message' | 'groupchat:joined' | 'groupchat:left' | 'groupchat:kicked' | 'groupchat:group_deleted' | 'groupchat:updated' | 'groupchat:connection';
735
689
  interface SphereEventMap {
736
690
  'transfer:incoming': IncomingTransfer;
737
691
  'transfer:confirmed': TransferResult;
@@ -790,6 +744,34 @@ interface SphereEventMap {
790
744
  index: number;
791
745
  addressId: string;
792
746
  };
747
+ 'sync:remote-update': {
748
+ providerId: string;
749
+ name: string;
750
+ sequence: number;
751
+ cid: string;
752
+ added: number;
753
+ removed: number;
754
+ };
755
+ 'groupchat:message': GroupMessageData;
756
+ 'groupchat:joined': {
757
+ groupId: string;
758
+ groupName: string;
759
+ };
760
+ 'groupchat:left': {
761
+ groupId: string;
762
+ };
763
+ 'groupchat:kicked': {
764
+ groupId: string;
765
+ groupName: string;
766
+ };
767
+ 'groupchat:group_deleted': {
768
+ groupId: string;
769
+ groupName: string;
770
+ };
771
+ 'groupchat:updated': Record<string, never>;
772
+ 'groupchat:connection': {
773
+ connected: boolean;
774
+ };
793
775
  }
794
776
  type SphereEventHandler<T extends SphereEventType> = (data: SphereEventMap[T]) => void;
795
777
  /**
@@ -1012,6 +994,12 @@ interface TransportProvider extends BaseProvider {
1012
994
  * @returns Unsubscribe function
1013
995
  */
1014
996
  onInstantSplitReceived?(handler: InstantSplitBundleHandler): () => void;
997
+ /**
998
+ * Fetch pending events from transport (one-shot query).
999
+ * Creates a temporary subscription, processes events through normal handlers,
1000
+ * and resolves after EOSE (End Of Stored Events).
1001
+ */
1002
+ fetchPendingEvents?(): Promise<void>;
1015
1003
  }
1016
1004
  /**
1017
1005
  * Payload for sending instant split bundles
@@ -1076,7 +1064,7 @@ interface IncomingTokenTransfer {
1076
1064
  payload: TokenTransferPayload;
1077
1065
  timestamp: number;
1078
1066
  }
1079
- type TokenTransferHandler = (transfer: IncomingTokenTransfer) => void;
1067
+ type TokenTransferHandler = (transfer: IncomingTokenTransfer) => void | Promise<void>;
1080
1068
  interface PaymentRequestPayload {
1081
1069
  /** Amount requested (in smallest units) */
1082
1070
  amount: string | bigint;
@@ -1425,24 +1413,8 @@ interface TokenStorageProvider<TData = unknown> extends BaseProvider {
1425
1413
  * Subscribe to storage events
1426
1414
  */
1427
1415
  onEvent?(callback: StorageEventCallback): () => void;
1428
- /**
1429
- * Save individual token (for file-based storage like lottery pattern)
1430
- */
1431
- saveToken?(tokenId: string, tokenData: unknown): Promise<void>;
1432
- /**
1433
- * Get individual token
1434
- */
1435
- getToken?(tokenId: string): Promise<unknown | null>;
1436
- /**
1437
- * List all token IDs
1438
- */
1439
- listTokenIds?(): Promise<string[]>;
1440
- /**
1441
- * Delete individual token
1442
- */
1443
- deleteToken?(tokenId: string): Promise<void>;
1444
1416
  }
1445
- type StorageEventType = 'storage:saving' | 'storage:saved' | 'storage:loading' | 'storage:loaded' | 'storage:error' | 'sync:started' | 'sync:completed' | 'sync:conflict' | 'sync:error';
1417
+ type StorageEventType = 'storage:saving' | 'storage:saved' | 'storage:loading' | 'storage:loaded' | 'storage:error' | 'storage:remote-updated' | 'sync:started' | 'sync:completed' | 'sync:conflict' | 'sync:error';
1446
1418
  interface StorageEvent {
1447
1419
  type: StorageEventType;
1448
1420
  timestamp: number;
@@ -1698,7 +1670,30 @@ interface TransactionHistoryEntry {
1698
1670
  timestamp: number;
1699
1671
  recipientNametag?: string;
1700
1672
  senderPubkey?: string;
1701
- txHash?: string;
1673
+ /** TransferResult.id that created this entry (links history to transfer operation) */
1674
+ transferId?: string;
1675
+ }
1676
+ interface ReceiveOptions {
1677
+ /** Wait for all unconfirmed tokens to be finalized (default: false).
1678
+ * When false, calls resolveUnconfirmed() once to submit pending commitments.
1679
+ * When true, polls resolveUnconfirmed() + load() until all confirmed or timeout. */
1680
+ finalize?: boolean;
1681
+ /** Finalization timeout in ms (default: 60000). Only used when finalize=true. */
1682
+ timeout?: number;
1683
+ /** Poll interval in ms (default: 2000). Only used when finalize=true. */
1684
+ pollInterval?: number;
1685
+ /** Progress callback after each resolveUnconfirmed() poll. Only used when finalize=true. */
1686
+ onProgress?: (result: UnconfirmedResolutionResult) => void;
1687
+ }
1688
+ interface ReceiveResult {
1689
+ /** Newly received incoming transfers. */
1690
+ transfers: IncomingTransfer[];
1691
+ /** Finalization result (from resolveUnconfirmed). */
1692
+ finalization?: UnconfirmedResolutionResult;
1693
+ /** Whether finalization timed out (only when finalize=true). */
1694
+ timedOut?: boolean;
1695
+ /** Duration of finalization in ms (only when finalize=true). */
1696
+ finalizationDurationMs?: number;
1702
1697
  }
1703
1698
  interface PaymentsModuleConfig {
1704
1699
  /** Auto-sync after operations */
@@ -1738,11 +1733,12 @@ declare class PaymentsModule {
1738
1733
  readonly l1: L1PaymentsModule | null;
1739
1734
  private tokens;
1740
1735
  private pendingTransfers;
1736
+ private pendingBackgroundTasks;
1741
1737
  private tombstones;
1742
1738
  private archivedTokens;
1743
1739
  private forkedTokens;
1744
1740
  private transactionHistory;
1745
- private nametag;
1741
+ private nametags;
1746
1742
  private paymentRequests;
1747
1743
  private paymentRequestHandlers;
1748
1744
  private outgoingPaymentRequests;
@@ -1755,8 +1751,17 @@ declare class PaymentsModule {
1755
1751
  private proofPollingInterval;
1756
1752
  private static readonly PROOF_POLLING_INTERVAL_MS;
1757
1753
  private static readonly PROOF_POLLING_MAX_ATTEMPTS;
1754
+ private storageEventUnsubscribers;
1755
+ private syncDebounceTimer;
1756
+ private static readonly SYNC_DEBOUNCE_MS;
1757
+ /** Sync coalescing: concurrent sync() calls share the same operation */
1758
+ private _syncInProgress;
1758
1759
  constructor(config?: PaymentsModuleConfig);
1759
- /** Get module configuration */
1760
+ /**
1761
+ * Get the current module configuration (excluding L1 config).
1762
+ *
1763
+ * @returns Resolved configuration with all defaults applied.
1764
+ */
1760
1765
  getConfig(): Omit<Required<PaymentsModuleConfig>, 'l1'>;
1761
1766
  /** Price provider (optional) */
1762
1767
  private priceProvider;
@@ -1766,11 +1771,18 @@ declare class PaymentsModule {
1766
1771
  */
1767
1772
  initialize(deps: PaymentsModuleDependencies): void;
1768
1773
  /**
1769
- * Load tokens from storage
1774
+ * Load all token data from storage providers and restore wallet state.
1775
+ *
1776
+ * Loads tokens, nametag data, transaction history, and pending transfers
1777
+ * from configured storage providers. Restores pending V5 tokens and
1778
+ * triggers a fire-and-forget {@link resolveUnconfirmed} call.
1770
1779
  */
1771
1780
  load(): Promise<void>;
1772
1781
  /**
1773
- * Cleanup resources
1782
+ * Cleanup all subscriptions, polling jobs, and pending resolvers.
1783
+ *
1784
+ * Should be called when the wallet is being shut down or the module is
1785
+ * no longer needed. Also destroys the L1 sub-module if present.
1774
1786
  */
1775
1787
  destroy(): void;
1776
1788
  /**
@@ -1822,11 +1834,19 @@ declare class PaymentsModule {
1822
1834
  * @param senderPubkey - Sender's public key for verification
1823
1835
  * @returns Processing result with finalized token
1824
1836
  */
1825
- processInstantSplitBundle(bundle: InstantSplitBundle, senderPubkey: string): Promise<InstantSplitProcessResult>;
1837
+ private processInstantSplitBundle;
1826
1838
  /**
1827
- * Check if a payload is an instant split bundle
1839
+ * Synchronous V4 bundle processing (dev mode only).
1840
+ * Kept for backward compatibility with V4 bundles.
1828
1841
  */
1829
- isInstantSplitBundle(payload: unknown): payload is InstantSplitBundle;
1842
+ private processInstantSplitBundleSync;
1843
+ /**
1844
+ * Type-guard: check whether a payload is a valid {@link InstantSplitBundle} (V4 or V5).
1845
+ *
1846
+ * @param payload - The object to test.
1847
+ * @returns `true` if the payload matches the InstantSplitBundle shape.
1848
+ */
1849
+ private isInstantSplitBundle;
1830
1850
  /**
1831
1851
  * Send a payment request to someone
1832
1852
  * @param recipientPubkeyOrNametag - Recipient's pubkey or @nametag
@@ -1848,27 +1868,45 @@ declare class PaymentsModule {
1848
1868
  status?: PaymentRequestStatus;
1849
1869
  }): IncomingPaymentRequest$1[];
1850
1870
  /**
1851
- * Get pending payment requests count
1871
+ * Get the count of payment requests with status `'pending'`.
1872
+ *
1873
+ * @returns Number of pending incoming payment requests.
1852
1874
  */
1853
1875
  getPendingPaymentRequestsCount(): number;
1854
1876
  /**
1855
- * Accept a payment request (marks it as accepted, user should then call send())
1877
+ * Accept a payment request and notify the requester.
1878
+ *
1879
+ * Marks the request as `'accepted'` and sends a response via transport.
1880
+ * The caller should subsequently call {@link send} to fulfill the payment.
1881
+ *
1882
+ * @param requestId - ID of the incoming payment request to accept.
1856
1883
  */
1857
1884
  acceptPaymentRequest(requestId: string): Promise<void>;
1858
1885
  /**
1859
- * Reject a payment request
1886
+ * Reject a payment request and notify the requester.
1887
+ *
1888
+ * @param requestId - ID of the incoming payment request to reject.
1860
1889
  */
1861
1890
  rejectPaymentRequest(requestId: string): Promise<void>;
1862
1891
  /**
1863
- * Mark a payment request as paid (after successful transfer)
1892
+ * Mark a payment request as paid (local status update only).
1893
+ *
1894
+ * Typically called after a successful {@link send} to record that the
1895
+ * request has been fulfilled.
1896
+ *
1897
+ * @param requestId - ID of the incoming payment request to mark as paid.
1864
1898
  */
1865
1899
  markPaymentRequestPaid(requestId: string): void;
1866
1900
  /**
1867
- * Clear processed (non-pending) payment requests
1901
+ * Remove all non-pending incoming payment requests from memory.
1902
+ *
1903
+ * Keeps only requests with status `'pending'`.
1868
1904
  */
1869
1905
  clearProcessedPaymentRequests(): void;
1870
1906
  /**
1871
- * Remove a specific payment request
1907
+ * Remove a specific incoming payment request by ID.
1908
+ *
1909
+ * @param requestId - ID of the payment request to remove.
1872
1910
  */
1873
1911
  removePaymentRequest(requestId: string): void;
1874
1912
  /**
@@ -1899,15 +1937,21 @@ declare class PaymentsModule {
1899
1937
  */
1900
1938
  waitForPaymentResponse(requestId: string, timeoutMs?: number): Promise<PaymentRequestResponse>;
1901
1939
  /**
1902
- * Cancel waiting for a payment response
1940
+ * Cancel an active {@link waitForPaymentResponse} call.
1941
+ *
1942
+ * The pending promise is rejected with a `'Cancelled'` error.
1943
+ *
1944
+ * @param requestId - The outgoing request ID whose wait should be cancelled.
1903
1945
  */
1904
1946
  cancelWaitForPaymentResponse(requestId: string): void;
1905
1947
  /**
1906
- * Remove an outgoing payment request
1948
+ * Remove an outgoing payment request and cancel any pending wait.
1949
+ *
1950
+ * @param requestId - ID of the outgoing request to remove.
1907
1951
  */
1908
1952
  removeOutgoingPaymentRequest(requestId: string): void;
1909
1953
  /**
1910
- * Clear completed/expired outgoing payment requests
1954
+ * Remove all outgoing payment requests that are `'paid'`, `'rejected'`, or `'expired'`.
1911
1955
  */
1912
1956
  clearCompletedOutgoingPaymentRequests(): void;
1913
1957
  private handlePaymentRequestResponse;
@@ -1915,147 +1959,312 @@ declare class PaymentsModule {
1915
1959
  * Send a response to a payment request (used internally by accept/reject/pay methods)
1916
1960
  */
1917
1961
  private sendPaymentRequestResponse;
1962
+ /**
1963
+ * Fetch and process pending incoming transfers from the transport layer.
1964
+ *
1965
+ * Performs a one-shot query to fetch all pending events, processes them
1966
+ * through the existing pipeline, and resolves after all stored events
1967
+ * are handled. Useful for batch/CLI apps that need explicit receive.
1968
+ *
1969
+ * When `finalize` is true, polls resolveUnconfirmed() + load() until all
1970
+ * tokens are confirmed or the timeout expires. Otherwise calls
1971
+ * resolveUnconfirmed() once to submit pending commitments.
1972
+ *
1973
+ * @param options - Optional receive options including finalization control
1974
+ * @param callback - Optional callback invoked for each newly received transfer
1975
+ * @returns ReceiveResult with transfers and finalization metadata
1976
+ */
1977
+ receive(options?: ReceiveOptions, callback?: (transfer: IncomingTransfer) => void): Promise<ReceiveResult>;
1918
1978
  /**
1919
1979
  * Set or update price provider
1920
1980
  */
1921
1981
  setPriceProvider(provider: PriceProvider): void;
1922
1982
  /**
1923
- * Get total portfolio value in USD
1924
- * Returns null if PriceProvider is not configured
1983
+ * Wait for all pending background operations (e.g., instant split change token creation).
1984
+ * Call this before process exit to ensure all tokens are saved.
1925
1985
  */
1926
- getBalance(): Promise<number | null>;
1986
+ waitForPendingOperations(): Promise<void>;
1927
1987
  /**
1928
- * Get aggregated assets (tokens grouped by coinId) with price data
1929
- * Only includes confirmed tokens
1988
+ * Get total portfolio value in USD.
1989
+ * Returns null if PriceProvider is not configured.
1990
+ */
1991
+ getFiatBalance(): Promise<number | null>;
1992
+ /**
1993
+ * Get token balances grouped by coin type.
1994
+ *
1995
+ * Returns an array of {@link Asset} objects, one per coin type held.
1996
+ * Each entry includes confirmed and unconfirmed breakdowns. Tokens with
1997
+ * status `'spent'`, `'invalid'`, or `'transferring'` are excluded.
1998
+ *
1999
+ * This is synchronous — no price data is included. Use {@link getAssets}
2000
+ * for the async version with fiat pricing.
2001
+ *
2002
+ * @param coinId - Optional coin ID to filter by (e.g. hex string). When omitted, all coin types are returned.
2003
+ * @returns Array of balance summaries (synchronous — no await needed).
2004
+ */
2005
+ getBalance(coinId?: string): Asset[];
2006
+ /**
2007
+ * Get aggregated assets (tokens grouped by coinId) with price data.
2008
+ * Includes both confirmed and unconfirmed tokens with breakdown.
1930
2009
  */
1931
2010
  getAssets(coinId?: string): Promise<Asset[]>;
1932
2011
  /**
1933
- * Get all tokens
2012
+ * Aggregate tokens by coinId with confirmed/unconfirmed breakdown.
2013
+ * Excludes tokens with status 'spent', 'invalid', or 'transferring'.
2014
+ */
2015
+ private aggregateTokens;
2016
+ /**
2017
+ * Get all tokens, optionally filtered by coin type and/or status.
2018
+ *
2019
+ * @param filter - Optional filter criteria.
2020
+ * @param filter.coinId - Return only tokens of this coin type.
2021
+ * @param filter.status - Return only tokens with this status (e.g. `'submitted'` for unconfirmed).
2022
+ * @returns Array of matching {@link Token} objects (synchronous).
1934
2023
  */
1935
2024
  getTokens(filter?: {
1936
2025
  coinId?: string;
1937
2026
  status?: TokenStatus;
1938
2027
  }): Token[];
1939
2028
  /**
1940
- * Get single token
2029
+ * Get a single token by its local ID.
2030
+ *
2031
+ * @param id - The local UUID assigned when the token was added.
2032
+ * @returns The token, or `undefined` if not found.
1941
2033
  */
1942
2034
  getToken(id: string): Token | undefined;
1943
2035
  /**
1944
- * Add a token
1945
- * Tokens are uniquely identified by (tokenId, stateHash) composite key.
1946
- * Multiple historic states of the same token can coexist.
1947
- * @returns false if exact duplicate (same tokenId AND same stateHash)
2036
+ * Attempt to resolve unconfirmed (status `'submitted'`) tokens by acquiring
2037
+ * their missing aggregator proofs.
2038
+ *
2039
+ * Each unconfirmed V5 token progresses through stages:
2040
+ * `RECEIVED` → `MINT_SUBMITTED` → `MINT_PROVEN` → `TRANSFER_SUBMITTED` → `FINALIZED`
2041
+ *
2042
+ * Uses 500 ms quick-timeouts per proof check so the call returns quickly even
2043
+ * when proofs are not yet available. Tokens that exceed 50 failed attempts are
2044
+ * marked `'invalid'`.
2045
+ *
2046
+ * Automatically called (fire-and-forget) by {@link load}.
2047
+ *
2048
+ * @returns Summary with counts of resolved, still-pending, and failed tokens plus per-token details.
2049
+ */
2050
+ resolveUnconfirmed(): Promise<UnconfirmedResolutionResult>;
2051
+ /**
2052
+ * Process a single V5 token through its finalization stages with quick-timeout proof checks.
1948
2053
  */
1949
- addToken(token: Token, skipHistory?: boolean): Promise<boolean>;
2054
+ private resolveV5Token;
1950
2055
  /**
1951
- * Save token as individual file to token storage providers
1952
- * Similar to lottery's saveReceivedToken() pattern
2056
+ * Non-blocking proof check with 500ms timeout.
1953
2057
  */
1954
- private saveTokenToFileStorage;
2058
+ private quickProofCheck;
1955
2059
  /**
1956
- * Load tokens from file storage providers (lottery compatibility)
1957
- * This loads tokens from file-based storage that may have been saved
1958
- * by other applications using the same storage directory.
2060
+ * Perform V5 bundle finalization from stored bundle data and proofs.
2061
+ * Extracted from InstantSplitProcessor.processV5Bundle() steps 4-10.
1959
2062
  */
1960
- private loadTokensFromFileStorage;
2063
+ private finalizeFromV5Bundle;
1961
2064
  /**
1962
- * Update an existing token
2065
+ * Parse pending finalization metadata from token's sdkData.
1963
2066
  */
1964
- updateToken(token: Token): Promise<void>;
2067
+ private parsePendingFinalization;
1965
2068
  /**
1966
- * Remove a token by ID
2069
+ * Update pending finalization metadata in token's sdkData.
2070
+ * Creates a new token object since sdkData is readonly.
1967
2071
  */
1968
- removeToken(tokenId: string, recipientNametag?: string, skipHistory?: boolean): Promise<void>;
2072
+ private updatePendingFinalization;
2073
+ /**
2074
+ * Save pending V5 tokens to key-value storage.
2075
+ * These tokens can't be serialized to TXF format (no genesis/state),
2076
+ * so we persist them separately and restore on load().
2077
+ */
2078
+ private savePendingV5Tokens;
1969
2079
  /**
1970
- * Delete physical token file(s) from all storage providers.
1971
- * Finds files by matching the SDK token ID prefix in the filename.
2080
+ * Load pending V5 tokens from key-value storage and merge into tokens map.
2081
+ * Called during load() to restore tokens that TXF format can't represent.
1972
2082
  */
1973
- private deleteTokenFiles;
2083
+ private loadPendingV5Tokens;
1974
2084
  /**
1975
- * Get all tombstones
2085
+ * Add a token to the wallet.
2086
+ *
2087
+ * Tokens are uniquely identified by a `(tokenId, stateHash)` composite key.
2088
+ * Duplicate detection:
2089
+ * - **Tombstoned** — rejected if the exact `(tokenId, stateHash)` pair has a tombstone.
2090
+ * - **Exact duplicate** — rejected if a token with the same composite key already exists.
2091
+ * - **State replacement** — if the same `tokenId` exists with a *different* `stateHash`,
2092
+ * the old state is archived and replaced with the incoming one.
2093
+ *
2094
+ * @param token - The token to add.
2095
+ * @param skipHistory - When `true`, do not create a `RECEIVED` transaction history entry (default `false`).
2096
+ * @returns `true` if the token was added, `false` if rejected as duplicate or tombstoned.
2097
+ */
2098
+ addToken(token: Token, skipHistory?: boolean): Promise<boolean>;
2099
+ /**
2100
+ * Update an existing token or add it if not found.
2101
+ *
2102
+ * Looks up the token by genesis `tokenId` (from `sdkData`) first, then by
2103
+ * `token.id`. If no match is found, falls back to {@link addToken}.
2104
+ *
2105
+ * @param token - The token with updated data. Must include a valid `id`.
2106
+ */
2107
+ updateToken(token: Token): Promise<void>;
2108
+ /**
2109
+ * Remove a token from the wallet.
2110
+ *
2111
+ * The token is archived first, then a tombstone `(tokenId, stateHash)` is
2112
+ * created to prevent re-addition via Nostr re-delivery. A `SENT` history
2113
+ * entry is created unless `skipHistory` is `true`.
2114
+ *
2115
+ * @param tokenId - Local UUID of the token to remove.
2116
+ * @param recipientNametag - Optional nametag of the transfer recipient (for history).
2117
+ * @param skipHistory - When `true`, skip creating a transaction history entry (default `false`).
2118
+ */
2119
+ removeToken(tokenId: string, recipientNametag?: string, skipHistory?: boolean): Promise<void>;
2120
+ /**
2121
+ * Get all tombstone entries.
2122
+ *
2123
+ * Each tombstone is keyed by `(tokenId, stateHash)` and prevents a spent
2124
+ * token state from being re-added (e.g. via Nostr re-delivery).
2125
+ *
2126
+ * @returns A shallow copy of the tombstone array.
1976
2127
  */
1977
2128
  getTombstones(): TombstoneEntry[];
1978
2129
  /**
1979
- * Check if token state is tombstoned
2130
+ * Check whether a specific `(tokenId, stateHash)` combination is tombstoned.
2131
+ *
2132
+ * @param tokenId - The genesis token ID.
2133
+ * @param stateHash - The state hash of the token version to check.
2134
+ * @returns `true` if the exact combination has been tombstoned.
1980
2135
  */
1981
2136
  isStateTombstoned(tokenId: string, stateHash: string): boolean;
1982
2137
  /**
1983
- * Merge remote tombstones
1984
- * @returns number of local tokens removed
2138
+ * Merge tombstones received from a remote sync source.
2139
+ *
2140
+ * Any local token whose `(tokenId, stateHash)` matches a remote tombstone is
2141
+ * removed. The remote tombstones are then added to the local set (union merge).
2142
+ *
2143
+ * @param remoteTombstones - Tombstone entries from the remote source.
2144
+ * @returns Number of local tokens that were removed.
1985
2145
  */
1986
2146
  mergeTombstones(remoteTombstones: TombstoneEntry[]): Promise<number>;
1987
2147
  /**
1988
- * Prune old tombstones
2148
+ * Remove tombstones older than `maxAge` and cap the list at 100 entries.
2149
+ *
2150
+ * @param maxAge - Maximum age in milliseconds (default: 30 days).
1989
2151
  */
1990
2152
  pruneTombstones(maxAge?: number): Promise<void>;
1991
2153
  /**
1992
- * Get archived tokens
2154
+ * Get all archived (spent/superseded) tokens in TXF format.
2155
+ *
2156
+ * Archived tokens are kept for recovery and sync purposes. The map key is
2157
+ * the genesis token ID.
2158
+ *
2159
+ * @returns A shallow copy of the archived token map.
1993
2160
  */
1994
2161
  getArchivedTokens(): Map<string, TxfToken>;
1995
2162
  /**
1996
- * Get best archived version of a token
2163
+ * Get the best (most committed transactions) archived version of a token.
2164
+ *
2165
+ * Searches both archived and forked token maps and returns the version with
2166
+ * the highest number of committed transactions.
2167
+ *
2168
+ * @param tokenId - The genesis token ID to look up.
2169
+ * @returns The best TXF token version, or `null` if not found.
1997
2170
  */
1998
2171
  getBestArchivedVersion(tokenId: string): TxfToken | null;
1999
2172
  /**
2000
- * Merge remote archived tokens
2001
- * @returns number of tokens updated/added
2173
+ * Merge archived tokens from a remote sync source.
2174
+ *
2175
+ * For each remote token:
2176
+ * - If missing locally, it is added.
2177
+ * - If the remote version is an incremental update of the local, it replaces it.
2178
+ * - If the histories diverge (fork), the remote version is stored via {@link storeForkedToken}.
2179
+ *
2180
+ * @param remoteArchived - Map of genesis token ID → TXF token from remote.
2181
+ * @returns Number of tokens that were updated or added locally.
2002
2182
  */
2003
2183
  mergeArchivedTokens(remoteArchived: Map<string, TxfToken>): Promise<number>;
2004
2184
  /**
2005
- * Prune archived tokens
2185
+ * Prune archived tokens to keep at most `maxCount` entries.
2186
+ *
2187
+ * Oldest entries (by insertion order) are removed first.
2188
+ *
2189
+ * @param maxCount - Maximum number of archived tokens to retain (default: 100).
2006
2190
  */
2007
2191
  pruneArchivedTokens(maxCount?: number): Promise<void>;
2008
2192
  /**
2009
- * Get forked tokens
2193
+ * Get all forked token versions.
2194
+ *
2195
+ * Forked tokens represent alternative histories detected during sync.
2196
+ * The map key is `{tokenId}_{stateHash}`.
2197
+ *
2198
+ * @returns A shallow copy of the forked tokens map.
2010
2199
  */
2011
2200
  getForkedTokens(): Map<string, TxfToken>;
2012
2201
  /**
2013
- * Store a forked token
2202
+ * Store a forked token version (alternative history).
2203
+ *
2204
+ * No-op if the exact `(tokenId, stateHash)` key already exists.
2205
+ *
2206
+ * @param tokenId - Genesis token ID.
2207
+ * @param stateHash - State hash of this forked version.
2208
+ * @param txfToken - The TXF token data to store.
2014
2209
  */
2015
2210
  storeForkedToken(tokenId: string, stateHash: string, txfToken: TxfToken): Promise<void>;
2016
2211
  /**
2017
- * Merge remote forked tokens
2018
- * @returns number of tokens added
2212
+ * Merge forked tokens from a remote sync source. Only new keys are added.
2213
+ *
2214
+ * @param remoteForked - Map of `{tokenId}_{stateHash}` → TXF token from remote.
2215
+ * @returns Number of new forked tokens added.
2019
2216
  */
2020
2217
  mergeForkedTokens(remoteForked: Map<string, TxfToken>): Promise<number>;
2021
2218
  /**
2022
- * Prune forked tokens
2219
+ * Prune forked tokens to keep at most `maxCount` entries.
2220
+ *
2221
+ * @param maxCount - Maximum number of forked tokens to retain (default: 50).
2023
2222
  */
2024
2223
  pruneForkedTokens(maxCount?: number): Promise<void>;
2025
2224
  /**
2026
- * Get transaction history
2225
+ * Get the transaction history sorted newest-first.
2226
+ *
2227
+ * @returns Array of {@link TransactionHistoryEntry} objects in descending timestamp order.
2027
2228
  */
2028
2229
  getHistory(): TransactionHistoryEntry[];
2029
2230
  /**
2030
- * Add to transaction history
2231
+ * Append an entry to the transaction history.
2232
+ *
2233
+ * A unique `id` is auto-generated. The entry is immediately persisted to storage.
2234
+ *
2235
+ * @param entry - History entry fields (without `id`).
2031
2236
  */
2032
2237
  addToHistory(entry: Omit<TransactionHistoryEntry, 'id'>): Promise<void>;
2033
2238
  /**
2034
- * Set nametag for current identity
2239
+ * Set the nametag data for the current identity.
2240
+ *
2241
+ * Persists to both key-value storage and file storage (lottery compatibility).
2242
+ *
2243
+ * @param nametag - The nametag data including minted token JSON.
2035
2244
  */
2036
2245
  setNametag(nametag: NametagData): Promise<void>;
2037
2246
  /**
2038
- * Get nametag
2247
+ * Get the current (first) nametag data.
2248
+ *
2249
+ * @returns The nametag data, or `null` if no nametag is set.
2039
2250
  */
2040
2251
  getNametag(): NametagData | null;
2041
2252
  /**
2042
- * Check if has nametag
2043
- */
2044
- hasNametag(): boolean;
2045
- /**
2046
- * Clear nametag
2253
+ * Get all nametag data entries.
2254
+ *
2255
+ * @returns A copy of the nametags array.
2047
2256
  */
2048
- clearNametag(): Promise<void>;
2257
+ getNametags(): NametagData[];
2049
2258
  /**
2050
- * Save nametag to file storage for lottery compatibility
2051
- * Creates file: nametag-{name}.json
2259
+ * Check whether a nametag is currently set.
2260
+ *
2261
+ * @returns `true` if nametag data is present.
2052
2262
  */
2053
- private saveNametagToFileStorage;
2263
+ hasNametag(): boolean;
2054
2264
  /**
2055
- * Load nametag from file storage (lottery compatibility)
2056
- * Looks for file: nametag-{name}.json
2265
+ * Remove all nametag data from memory and storage.
2057
2266
  */
2058
- private loadNametagFromFileStorage;
2267
+ clearNametag(): Promise<void>;
2059
2268
  /**
2060
2269
  * Mint a nametag token on-chain (like Sphere wallet and lottery)
2061
2270
  * This creates the nametag token required for receiving tokens via PROXY addresses
@@ -2070,30 +2279,60 @@ declare class PaymentsModule {
2070
2279
  */
2071
2280
  isNametagAvailable(nametag: string): Promise<boolean>;
2072
2281
  /**
2073
- * Sync with all token storage providers (IPFS, MongoDB, etc.)
2074
- * Syncs with each provider and merges results
2282
+ * Sync local token state with all configured token storage providers (IPFS, file, etc.).
2283
+ *
2284
+ * For each provider, the local data is packaged into TXF storage format, sent
2285
+ * to the provider's `sync()` method, and the merged result is applied locally.
2286
+ * Emits `sync:started`, `sync:completed`, and `sync:error` events.
2287
+ *
2288
+ * @returns Summary with counts of tokens added and removed during sync.
2075
2289
  */
2076
2290
  sync(): Promise<{
2077
2291
  added: number;
2078
2292
  removed: number;
2079
2293
  }>;
2294
+ private _doSync;
2295
+ /**
2296
+ * Subscribe to 'storage:remote-updated' events from all token storage providers.
2297
+ * When a provider emits this event, a debounced sync is triggered.
2298
+ */
2299
+ private subscribeToStorageEvents;
2300
+ /**
2301
+ * Unsubscribe from all storage provider events and clear debounce timer.
2302
+ */
2303
+ private unsubscribeStorageEvents;
2304
+ /**
2305
+ * Debounced sync triggered by a storage:remote-updated event.
2306
+ * Waits 500ms to batch rapid updates, then performs sync.
2307
+ */
2308
+ private debouncedSyncFromRemoteUpdate;
2080
2309
  /**
2081
2310
  * Get all active token storage providers
2082
2311
  */
2083
2312
  private getTokenStorageProviders;
2084
2313
  /**
2085
- * Update token storage providers (called when providers are added/removed dynamically)
2314
+ * Replace the set of token storage providers at runtime.
2315
+ *
2316
+ * Use when providers are added or removed dynamically (e.g. IPFS node started).
2317
+ *
2318
+ * @param providers - New map of provider ID → TokenStorageProvider.
2086
2319
  */
2087
2320
  updateTokenStorageProviders(providers: Map<string, TokenStorageProvider<TxfStorageDataBase>>): void;
2088
2321
  /**
2089
- * Validate tokens with aggregator
2322
+ * Validate all tokens against the aggregator (oracle provider).
2323
+ *
2324
+ * Tokens that fail validation or are detected as spent are marked `'invalid'`.
2325
+ *
2326
+ * @returns Object with arrays of valid and invalid tokens.
2090
2327
  */
2091
2328
  validate(): Promise<{
2092
2329
  valid: Token[];
2093
2330
  invalid: Token[];
2094
2331
  }>;
2095
2332
  /**
2096
- * Get pending transfers
2333
+ * Get all in-progress (pending) outgoing transfers.
2334
+ *
2335
+ * @returns Array of {@link TransferResult} objects for transfers that have not yet completed.
2097
2336
  */
2098
2337
  getPendingTransfers(): TransferResult[];
2099
2338
  /**
@@ -2258,6 +2497,126 @@ declare class CommunicationsModule {
2258
2497
  private ensureInitialized;
2259
2498
  }
2260
2499
 
2500
+ /**
2501
+ * Group Chat Module (NIP-29)
2502
+ *
2503
+ * Relay-based group chat using NIP-29 protocol on a dedicated Nostr relay.
2504
+ * Embeds its own NostrClient — does NOT share the wallet's TransportProvider.
2505
+ */
2506
+
2507
+ interface GroupChatModuleDependencies {
2508
+ identity: FullIdentity;
2509
+ storage: StorageProvider;
2510
+ emitEvent: <T extends SphereEventType>(type: T, data: SphereEventMap[T]) => void;
2511
+ }
2512
+ declare class GroupChatModule {
2513
+ private config;
2514
+ private deps;
2515
+ private client;
2516
+ private keyManager;
2517
+ private connected;
2518
+ private connecting;
2519
+ private connectPromise;
2520
+ private reconnectAttempts;
2521
+ private reconnectTimer;
2522
+ private subscriptionIds;
2523
+ private groups;
2524
+ private messages;
2525
+ private members;
2526
+ private processedEventIds;
2527
+ private pendingLeaves;
2528
+ private persistTimer;
2529
+ private persistPromise;
2530
+ private relayAdminPubkeys;
2531
+ private relayAdminFetchPromise;
2532
+ private messageHandlers;
2533
+ constructor(config?: GroupChatModuleConfig);
2534
+ initialize(deps: GroupChatModuleDependencies): void;
2535
+ load(): Promise<void>;
2536
+ destroy(): void;
2537
+ private destroyConnection;
2538
+ connect(): Promise<void>;
2539
+ getConnectionStatus(): boolean;
2540
+ private doConnect;
2541
+ private scheduleReconnect;
2542
+ private subscribeToJoinedGroups;
2543
+ private subscribeToGroup;
2544
+ private handleGroupEvent;
2545
+ private handleMetadataEvent;
2546
+ private handleModerationEvent;
2547
+ private updateMembersFromEvent;
2548
+ private updateAdminsFromEvent;
2549
+ private restoreJoinedGroups;
2550
+ fetchAvailableGroups(): Promise<GroupData[]>;
2551
+ joinGroup(groupId: string, inviteCode?: string): Promise<boolean>;
2552
+ leaveGroup(groupId: string): Promise<boolean>;
2553
+ createGroup(options: CreateGroupOptions): Promise<GroupData | null>;
2554
+ deleteGroup(groupId: string): Promise<boolean>;
2555
+ createInvite(groupId: string): Promise<string | null>;
2556
+ sendMessage(groupId: string, content: string, replyToId?: string): Promise<GroupMessageData | null>;
2557
+ fetchMessages(groupId: string, since?: number, limit?: number): Promise<GroupMessageData[]>;
2558
+ getGroups(): GroupData[];
2559
+ getGroup(groupId: string): GroupData | null;
2560
+ getMessages(groupId: string): GroupMessageData[];
2561
+ getMembers(groupId: string): GroupMemberData[];
2562
+ getMember(groupId: string, pubkey: string): GroupMemberData | null;
2563
+ getTotalUnreadCount(): number;
2564
+ markGroupAsRead(groupId: string): void;
2565
+ kickUser(groupId: string, userPubkey: string, reason?: string): Promise<boolean>;
2566
+ deleteMessage(groupId: string, messageId: string): Promise<boolean>;
2567
+ isCurrentUserAdmin(groupId: string): boolean;
2568
+ isCurrentUserModerator(groupId: string): boolean;
2569
+ /**
2570
+ * Check if current user can moderate a group:
2571
+ * - Group admin/moderator can always moderate their group
2572
+ * - Relay admins can moderate public groups
2573
+ */
2574
+ canModerateGroup(groupId: string): Promise<boolean>;
2575
+ isCurrentUserRelayAdmin(): Promise<boolean>;
2576
+ getCurrentUserRole(groupId: string): GroupRole | null;
2577
+ onMessage(handler: (message: GroupMessageData) => void): () => void;
2578
+ getRelayUrls(): string[];
2579
+ getMyPublicKey(): string | null;
2580
+ private fetchRelayAdmins;
2581
+ private doFetchRelayAdmins;
2582
+ private fetchGroupMetadataInternal;
2583
+ private fetchAndSaveMembers;
2584
+ private fetchGroupMembersInternal;
2585
+ private fetchGroupAdminsInternal;
2586
+ private saveMessageToMemory;
2587
+ private deleteMessageFromMemory;
2588
+ private saveMemberToMemory;
2589
+ private removeMemberFromMemory;
2590
+ private removeGroupFromMemory;
2591
+ private updateGroupLastMessage;
2592
+ private updateMemberNametag;
2593
+ private addProcessedEventId;
2594
+ /** Schedule a debounced persist (coalesces rapid event bursts). */
2595
+ private schedulePersist;
2596
+ /** Persist immediately (for explicit flush points). */
2597
+ private persistAll;
2598
+ private doPersistAll;
2599
+ private persistGroups;
2600
+ private persistMessages;
2601
+ private persistMembers;
2602
+ private persistProcessedEvents;
2603
+ private checkAndClearOnRelayChange;
2604
+ private wrapMessageContent;
2605
+ private unwrapMessageContent;
2606
+ private getGroupIdFromEvent;
2607
+ private getGroupIdFromMetadataEvent;
2608
+ private extractReplyTo;
2609
+ private extractPreviousIds;
2610
+ private parseGroupMetadata;
2611
+ /** Subscribe and track the subscription ID for cleanup. */
2612
+ private trackSubscription;
2613
+ /** Subscribe for a one-shot fetch, auto-unsubscribe on EOSE or timeout. */
2614
+ private oneshotSubscription;
2615
+ private ensureInitialized;
2616
+ private ensureConnected;
2617
+ private randomId;
2618
+ }
2619
+
2261
2620
  /** Network configurations */
2262
2621
  declare const NETWORKS: {
2263
2622
  readonly mainnet: {
@@ -2266,6 +2625,7 @@ declare const NETWORKS: {
2266
2625
  readonly nostrRelays: readonly ["wss://relay.unicity.network", "wss://relay.damus.io", "wss://nos.lol", "wss://relay.nostr.band"];
2267
2626
  readonly ipfsGateways: readonly ["https://ipfs.unicity.network", "https://dweb.link", "https://ipfs.io"];
2268
2627
  readonly electrumUrl: "wss://fulcrum.alpha.unicity.network:50004";
2628
+ readonly groupRelays: readonly ["wss://sphere-relay.unicity.network"];
2269
2629
  };
2270
2630
  readonly testnet: {
2271
2631
  readonly name: "Testnet";
@@ -2273,6 +2633,7 @@ declare const NETWORKS: {
2273
2633
  readonly nostrRelays: readonly ["wss://nostr-relay.testnet.unicity.network"];
2274
2634
  readonly ipfsGateways: readonly ["https://ipfs.unicity.network", "https://dweb.link", "https://ipfs.io"];
2275
2635
  readonly electrumUrl: "wss://fulcrum.alpha.testnet.unicity.network:50004";
2636
+ readonly groupRelays: readonly ["wss://sphere-relay.unicity.network"];
2276
2637
  };
2277
2638
  readonly dev: {
2278
2639
  readonly name: "Development";
@@ -2280,6 +2641,7 @@ declare const NETWORKS: {
2280
2641
  readonly nostrRelays: readonly ["wss://nostr-relay.testnet.unicity.network"];
2281
2642
  readonly ipfsGateways: readonly ["https://ipfs.unicity.network", "https://dweb.link", "https://ipfs.io"];
2282
2643
  readonly electrumUrl: "wss://fulcrum.alpha.testnet.unicity.network:50004";
2644
+ readonly groupRelays: readonly ["wss://sphere-relay.unicity.network"];
2283
2645
  };
2284
2646
  };
2285
2647
  type NetworkType = keyof typeof NETWORKS;
@@ -2365,6 +2727,8 @@ type LegacyFileType = 'dat' | 'txt' | 'json' | 'mnemonic' | 'unknown';
2365
2727
  */
2366
2728
  type DecryptionProgressCallback = (iteration: number, total: number) => Promise<void> | void;
2367
2729
 
2730
+ declare function isValidNametag(nametag: string): boolean;
2731
+
2368
2732
  /** Options for creating a new wallet */
2369
2733
  interface SphereCreateOptions {
2370
2734
  /** BIP39 mnemonic (12 or 24 words) */
@@ -2391,6 +2755,8 @@ interface SphereCreateOptions {
2391
2755
  * Use createBrowserProviders({ network: 'testnet' }) to set up testnet providers.
2392
2756
  */
2393
2757
  network?: NetworkType;
2758
+ /** Group chat configuration (NIP-29). Omit to disable groupchat. */
2759
+ groupChat?: GroupChatModuleConfig | boolean;
2394
2760
  }
2395
2761
  /** Options for loading existing wallet */
2396
2762
  interface SphereLoadOptions {
@@ -2412,6 +2778,8 @@ interface SphereLoadOptions {
2412
2778
  * Use createBrowserProviders({ network: 'testnet' }) to set up testnet providers.
2413
2779
  */
2414
2780
  network?: NetworkType;
2781
+ /** Group chat configuration (NIP-29). Omit to disable groupchat. */
2782
+ groupChat?: GroupChatModuleConfig | boolean;
2415
2783
  }
2416
2784
  /** Options for importing a wallet */
2417
2785
  interface SphereImportOptions {
@@ -2441,6 +2809,8 @@ interface SphereImportOptions {
2441
2809
  l1?: L1Config;
2442
2810
  /** Optional price provider for fiat conversion */
2443
2811
  price?: PriceProvider;
2812
+ /** Group chat configuration (NIP-29). Omit to disable groupchat. */
2813
+ groupChat?: GroupChatModuleConfig | boolean;
2444
2814
  }
2445
2815
  /** L1 (ALPHA blockchain) configuration */
2446
2816
  interface L1Config {
@@ -2479,6 +2849,13 @@ interface SphereInitOptions {
2479
2849
  * Use createBrowserProviders({ network: 'testnet' }) to set up testnet providers.
2480
2850
  */
2481
2851
  network?: NetworkType;
2852
+ /**
2853
+ * Group chat configuration (NIP-29).
2854
+ * - `true`: Enable with network-default relays
2855
+ * - `GroupChatModuleConfig`: Enable with custom config
2856
+ * - Omit/undefined: No groupchat module
2857
+ */
2858
+ groupChat?: GroupChatModuleConfig | boolean;
2482
2859
  }
2483
2860
  /** Result of init operation */
2484
2861
  interface SphereInitResult {
@@ -2514,6 +2891,7 @@ declare class Sphere {
2514
2891
  private _priceProvider;
2515
2892
  private _payments;
2516
2893
  private _communications;
2894
+ private _groupChat;
2517
2895
  private eventHandlers;
2518
2896
  private constructor();
2519
2897
  /**
@@ -2546,6 +2924,18 @@ declare class Sphere {
2546
2924
  * ```
2547
2925
  */
2548
2926
  static init(options: SphereInitOptions): Promise<SphereInitResult>;
2927
+ /**
2928
+ * Resolve groupChat config from init/create/load options.
2929
+ * - `true` → use network-default relays
2930
+ * - `GroupChatModuleConfig` → pass through
2931
+ * - `undefined` → no groupchat
2932
+ */
2933
+ /**
2934
+ * Resolve GroupChat config from Sphere.init() options.
2935
+ * Note: impl/shared/resolvers.ts has a similar resolver for provider-level config
2936
+ * (different input shape: { enabled?, relays? }). Both fill relay URLs from network defaults.
2937
+ */
2938
+ private static resolveGroupChatConfig;
2549
2939
  /**
2550
2940
  * Create new wallet with mnemonic
2551
2941
  */
@@ -2602,6 +2992,8 @@ declare class Sphere {
2602
2992
  get payments(): PaymentsModule;
2603
2993
  /** Communications module */
2604
2994
  get communications(): CommunicationsModule;
2995
+ /** Group chat module (NIP-29). Null if not configured. */
2996
+ get groupChat(): GroupChatModule | null;
2605
2997
  /** Current identity (public info only) */
2606
2998
  get identity(): Identity | null;
2607
2999
  /** Is ready */
@@ -3095,9 +3487,9 @@ declare class Sphere {
3095
3487
  */
3096
3488
  private recoverNametagFromTransport;
3097
3489
  /**
3098
- * Validate nametag format
3490
+ * Strip @ prefix and normalize a nametag (lowercase, phone E.164, strip @unicity suffix).
3099
3491
  */
3100
- private validateNametag;
3492
+ private cleanNametag;
3101
3493
  destroy(): Promise<void>;
3102
3494
  private storeMnemonic;
3103
3495
  private storeMasterKey;
@@ -3378,4 +3770,4 @@ declare function randomHex(byteLength: number): string;
3378
3770
  */
3379
3771
  declare function randomUUID(): string;
3380
3772
 
3381
- export { type AddressInfo, CHARSET, CurrencyUtils, DEFAULT_DERIVATION_PATH, DEFAULT_TOKEN_DECIMALS, type DerivedKey, type EncryptedData, type EncryptionOptions, type KeyPair, type L1Config, type MasterKey, type ScanAddressProgress, type ScanAddressesOptions, type ScanAddressesResult, type ScannedAddressResult, Sphere, type SphereCreateOptions, type SphereImportOptions, type SphereInitOptions, type SphereInitResult, type SphereLoadOptions, base58Decode, base58Encode, bytesToHex, computeHash160, convertBits, createAddress, createBech32, createKeyPair, createSphere, decodeBech32, decrypt, decryptJson, decryptMnemonic, decryptSimple, decryptWithSalt, deriveAddressInfo, deriveChildKey, deriveKeyAtPath, deserializeEncrypted, doubleSha256, ec, encodeBech32, encrypt, encryptMnemonic, encryptSimple, entropyToMnemonic, extractFromText, findPattern, formatAmount, generateAddressInfo, generateMasterKey, generateMnemonic, generateRandomKey, getAddressHrp, getPublicKey, getSphere, hash160, hash160ToBytes, hexToBytes, identityFromMnemonic, identityFromMnemonicSync, importSphere, initSphere, isEncryptedData, isValidBech32, isValidPrivateKey, loadSphere, mnemonicToEntropy, mnemonicToSeed, mnemonicToSeedSync, privateKeyToAddressInfo, publicKeyToAddress, randomBytes, randomHex, randomUUID, ripemd160, scanAddressesImpl, serializeEncrypted, sha256, sleep, sphereExists, toHumanReadable, toSmallestUnit, validateMnemonic };
3773
+ export { type AddressInfo, CHARSET, CurrencyUtils, DEFAULT_DERIVATION_PATH, DEFAULT_TOKEN_DECIMALS, type DerivedKey, type EncryptedData, type EncryptionOptions, type KeyPair, type L1Config, type MasterKey, type ScanAddressProgress, type ScanAddressesOptions, type ScanAddressesResult, type ScannedAddressResult, Sphere, type SphereCreateOptions, type SphereImportOptions, type SphereInitOptions, type SphereInitResult, type SphereLoadOptions, base58Decode, base58Encode, bytesToHex, computeHash160, convertBits, createAddress, createBech32, createKeyPair, createSphere, decodeBech32, decrypt, decryptJson, decryptMnemonic, decryptSimple, decryptWithSalt, deriveAddressInfo, deriveChildKey, deriveKeyAtPath, deserializeEncrypted, doubleSha256, ec, encodeBech32, encrypt, encryptMnemonic, encryptSimple, entropyToMnemonic, extractFromText, findPattern, formatAmount, generateAddressInfo, generateMasterKey, generateMnemonic, generateRandomKey, getAddressHrp, getPublicKey, getSphere, hash160, hash160ToBytes, hexToBytes, identityFromMnemonic, identityFromMnemonicSync, importSphere, initSphere, isEncryptedData, isValidBech32, isValidNametag, isValidPrivateKey, loadSphere, mnemonicToEntropy, mnemonicToSeed, mnemonicToSeedSync, privateKeyToAddressInfo, publicKeyToAddress, randomBytes, randomHex, randomUUID, ripemd160, scanAddressesImpl, serializeEncrypted, sha256, sleep, sphereExists, toHumanReadable, toSmallestUnit, validateMnemonic };