@unicitylabs/sphere-sdk 0.1.9 → 0.2.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.
@@ -272,6 +272,211 @@ interface TombstoneEntry {
272
272
  timestamp: number;
273
273
  }
274
274
 
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
+ /**
417
+ * Result of an instant split send operation
418
+ */
419
+ interface InstantSplitResult {
420
+ /** Whether the operation succeeded (Nostr delivery) */
421
+ success: boolean;
422
+ /** Nostr event ID (if delivered) */
423
+ nostrEventId?: string;
424
+ /** Split group ID for recovery correlation */
425
+ splitGroupId?: string;
426
+ /** Time taken for critical path (Nostr delivery) in ms */
427
+ criticalPathDurationMs: number;
428
+ /** Error message (if failed) */
429
+ error?: string;
430
+ /** Whether background processing was started */
431
+ 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;
445
+ }
446
+ /**
447
+ * Options for instant split send operation
448
+ */
449
+ interface InstantSplitOptions {
450
+ /** Timeout for Nostr delivery in ms (default: 30000) */
451
+ nostrTimeoutMs?: number;
452
+ /** Timeout for burn proof wait in ms (default: 60000) */
453
+ burnProofTimeoutMs?: number;
454
+ /** Timeout for mint proof wait in ms (default: 60000) */
455
+ mintProofTimeoutMs?: number;
456
+ /** Skip background processing (for testing) */
457
+ skipBackground?: boolean;
458
+ /** Use dev mode (V4 flow without SplitMintReason validation) */
459
+ devMode?: boolean;
460
+ /** Callback when burn is completed */
461
+ onBurnCompleted?: (burnTxJson: string) => void;
462
+ /** Callback when Nostr delivery is completed */
463
+ onNostrDelivered?: (eventId: string) => void;
464
+ /** Callback for background progress updates */
465
+ onBackgroundProgress?: (status: BackgroundProgressStatus) => void;
466
+ /** Callback when change token is created (background) */
467
+ onChangeTokenCreated?: (token: any) => Promise<void>;
468
+ /** Callback to trigger storage sync (background) */
469
+ onStorageSync?: () => Promise<boolean>;
470
+ }
471
+ /**
472
+ * Background processing status
473
+ */
474
+ interface BackgroundProgressStatus {
475
+ stage: 'MINTS_SUBMITTED' | 'MINTS_PROVEN' | 'CHANGE_TOKEN_SAVED' | 'STORAGE_SYNCED' | 'COMPLETED' | 'FAILED';
476
+ message: string;
477
+ error?: string;
478
+ }
479
+
275
480
  /**
276
481
  * SDK2 Core Types
277
482
  * Platform-independent type definitions
@@ -302,7 +507,7 @@ interface Identity {
302
507
  interface FullIdentity extends Identity {
303
508
  readonly privateKey: string;
304
509
  }
305
- type TokenStatus = 'pending' | 'confirmed' | 'transferring' | 'spent' | 'invalid';
510
+ type TokenStatus = 'pending' | 'submitted' | 'confirmed' | 'transferring' | 'spent' | 'invalid';
306
511
  interface Token {
307
512
  readonly id: string;
308
513
  readonly coinId: string;
@@ -316,14 +521,6 @@ interface Token {
316
521
  updatedAt: number;
317
522
  readonly sdkData?: string;
318
523
  }
319
- interface TokenBalance {
320
- readonly coinId: string;
321
- readonly symbol: string;
322
- readonly name: string;
323
- readonly totalAmount: string;
324
- readonly tokenCount: number;
325
- readonly decimals: number;
326
- }
327
524
  interface Asset {
328
525
  readonly coinId: string;
329
526
  readonly symbol: string;
@@ -332,13 +529,26 @@ interface Asset {
332
529
  readonly iconUrl?: string;
333
530
  readonly totalAmount: string;
334
531
  readonly tokenCount: number;
532
+ /** Price per whole unit in USD (null if PriceProvider not configured) */
533
+ readonly priceUsd: number | null;
534
+ /** Price per whole unit in EUR (null if PriceProvider not configured) */
535
+ readonly priceEur: number | null;
536
+ /** 24h price change percentage (null if unavailable) */
537
+ readonly change24h: number | null;
538
+ /** Total fiat value in USD: (totalAmount / 10^decimals) * priceUsd */
539
+ readonly fiatValueUsd: number | null;
540
+ /** Total fiat value in EUR */
541
+ readonly fiatValueEur: number | null;
335
542
  }
336
543
  type TransferStatus = 'pending' | 'submitted' | 'confirmed' | 'delivered' | 'completed' | 'failed';
544
+ type AddressMode = 'auto' | 'direct' | 'proxy';
337
545
  interface TransferRequest {
338
546
  readonly coinId: string;
339
547
  readonly amount: string;
340
548
  readonly recipient: string;
341
549
  readonly memo?: string;
550
+ /** Address mode: 'auto' (default) uses directAddress if available, 'direct' forces DIRECT, 'proxy' forces PROXY */
551
+ readonly addressMode?: AddressMode;
342
552
  }
343
553
  interface TransferResult {
344
554
  readonly id: string;
@@ -491,7 +701,37 @@ interface BroadcastMessage {
491
701
  readonly timestamp: number;
492
702
  readonly tags?: string[];
493
703
  }
494
- 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';
704
+ /**
705
+ * Minimal data stored in persistent storage for a tracked address.
706
+ * Only contains user state — derived fields are computed on load.
707
+ */
708
+ interface TrackedAddressEntry {
709
+ /** HD derivation index (0, 1, 2, ...) */
710
+ readonly index: number;
711
+ /** Whether this address is hidden from UI display */
712
+ hidden: boolean;
713
+ /** Timestamp (ms) when this address was first activated */
714
+ readonly createdAt: number;
715
+ /** Timestamp (ms) of last modification */
716
+ updatedAt: number;
717
+ }
718
+ /**
719
+ * Full tracked address with derived fields and nametag (available in memory).
720
+ * Returned by Sphere.getActiveAddresses() / getAllTrackedAddresses().
721
+ */
722
+ interface TrackedAddress extends TrackedAddressEntry {
723
+ /** Short address identifier (e.g., "DIRECT_abc123_xyz789") */
724
+ readonly addressId: string;
725
+ /** L1 bech32 address (alpha1...) */
726
+ readonly l1Address: string;
727
+ /** L3 DIRECT address (DIRECT://...) */
728
+ readonly directAddress: string;
729
+ /** 33-byte compressed secp256k1 public key */
730
+ readonly chainPubkey: string;
731
+ /** Primary nametag (from nametag cache, without @ prefix) */
732
+ readonly nametag?: string;
733
+ }
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';
495
735
  interface SphereEventMap {
496
736
  'transfer:incoming': IncomingTransfer;
497
737
  'transfer:confirmed': TransferResult;
@@ -539,6 +779,17 @@ interface SphereEventMap {
539
779
  nametag?: string;
540
780
  addressIndex: number;
541
781
  };
782
+ 'address:activated': {
783
+ address: TrackedAddress;
784
+ };
785
+ 'address:hidden': {
786
+ index: number;
787
+ addressId: string;
788
+ };
789
+ 'address:unhidden': {
790
+ index: number;
791
+ addressId: string;
792
+ };
542
793
  }
543
794
  type SphereEventHandler<T extends SphereEventType> = (data: SphereEventMap[T]) => void;
544
795
  /**
@@ -609,9 +860,10 @@ interface WalletJSONExportOptions {
609
860
  */
610
861
  interface TransportProvider extends BaseProvider {
611
862
  /**
612
- * Set identity for signing/encryption
863
+ * Set identity for signing/encryption.
864
+ * If the transport is already connected, reconnects with the new identity.
613
865
  */
614
- setIdentity(identity: FullIdentity): void;
866
+ setIdentity(identity: FullIdentity): void | Promise<void>;
615
867
  /**
616
868
  * Send encrypted direct message
617
869
  * @param recipientTransportPubkey - Transport-specific pubkey for messaging
@@ -634,15 +886,36 @@ interface TransportProvider extends BaseProvider {
634
886
  * @returns Unsubscribe function
635
887
  */
636
888
  onTokenTransfer(handler: TokenTransferHandler): () => void;
889
+ /**
890
+ * Resolve any identifier to full peer information.
891
+ * Accepts @nametag, bare nametag, DIRECT://, PROXY://, L1 address, chain pubkey, or transport pubkey.
892
+ * @param identifier - Any supported identifier format
893
+ * @returns PeerInfo or null if not found
894
+ */
895
+ resolve?(identifier: string): Promise<PeerInfo | null>;
637
896
  /**
638
897
  * Resolve nametag to public key
639
898
  */
640
899
  resolveNametag?(nametag: string): Promise<string | null>;
641
900
  /**
642
- * Resolve nametag to full address information
901
+ * Resolve nametag to full peer information
643
902
  * Returns transportPubkey, chainPubkey, l1Address, directAddress, proxyAddress
644
903
  */
645
- resolveNametagInfo?(nametag: string): Promise<NametagInfo | null>;
904
+ resolveNametagInfo?(nametag: string): Promise<PeerInfo | null>;
905
+ /**
906
+ * Resolve a DIRECT://, PROXY://, or L1 address to full peer info.
907
+ * Performs reverse lookup: address → binding event → PeerInfo.
908
+ * @param address - L3 address (DIRECT://... or PROXY://...) or L1 address (alpha1...)
909
+ * @returns PeerInfo or null if no binding found for this address
910
+ */
911
+ resolveAddressInfo?(address: string): Promise<PeerInfo | null>;
912
+ /**
913
+ * Resolve transport pubkey to full peer info.
914
+ * Queries binding events authored by the given transport pubkey.
915
+ * @param transportPubkey - Transport-specific pubkey (e.g. 64-char hex string)
916
+ * @returns PeerInfo or null if no binding found
917
+ */
918
+ resolveTransportPubkeyInfo?(transportPubkey: string): Promise<PeerInfo | null>;
646
919
  /**
647
920
  * Recover nametag for current identity by decrypting stored encrypted nametag
648
921
  * Used after wallet import to recover associated nametag
@@ -650,14 +923,20 @@ interface TransportProvider extends BaseProvider {
650
923
  */
651
924
  recoverNametag?(): Promise<string | null>;
652
925
  /**
926
+ * Publish identity binding event.
927
+ * Without nametag: publishes base binding (chainPubkey, l1Address, directAddress).
928
+ * With nametag: adds nametag hash, proxy address, encrypted nametag for recovery.
929
+ * Uses parameterized replaceable event (kind 30078, d=hash(nostrPubkey)).
930
+ * @returns true if successful, false if nametag is taken by another pubkey
931
+ */
932
+ publishIdentityBinding?(chainPubkey: string, l1Address: string, directAddress: string, nametag?: string): Promise<boolean>;
933
+ /**
934
+ * @deprecated Use publishIdentityBinding instead
653
935
  * Register a nametag for this identity
654
- * @param nametag - Nametag to register
655
- * @param chainPubkey - 33-byte compressed secp256k1 public key (for L1/L3)
656
- * @param directAddress - L3 DIRECT address (DIRECT://...)
657
- * @returns true if successful, false if already taken
658
936
  */
659
937
  registerNametag?(nametag: string, chainPubkey: string, directAddress: string): Promise<boolean>;
660
938
  /**
939
+ * @deprecated Use publishIdentityBinding instead
661
940
  * Publish nametag binding
662
941
  */
663
942
  publishNametag?(nametag: string, address: string): Promise<void>;
@@ -717,7 +996,54 @@ interface TransportProvider extends BaseProvider {
717
996
  * Check if a relay is currently connected
718
997
  */
719
998
  isRelayConnected?(relayUrl: string): boolean;
999
+ /**
1000
+ * Send an instant split bundle to a recipient.
1001
+ * This is a specialized method for INSTANT_SPLIT V5 bundles.
1002
+ *
1003
+ * @param recipientTransportPubkey - Transport-specific pubkey for messaging
1004
+ * @param bundle - The InstantSplitBundleV5 to send
1005
+ * @returns Event ID
1006
+ */
1007
+ sendInstantSplitBundle?(recipientTransportPubkey: string, bundle: InstantSplitBundlePayload): Promise<string>;
1008
+ /**
1009
+ * Subscribe to incoming instant split bundles.
1010
+ *
1011
+ * @param handler - Handler for received bundles
1012
+ * @returns Unsubscribe function
1013
+ */
1014
+ onInstantSplitReceived?(handler: InstantSplitBundleHandler): () => void;
720
1015
  }
1016
+ /**
1017
+ * Payload for sending instant split bundles
1018
+ */
1019
+ interface InstantSplitBundlePayload {
1020
+ /** The bundle JSON string (InstantSplitBundleV5 serialized) */
1021
+ bundle: string;
1022
+ /** Optional memo */
1023
+ memo?: string;
1024
+ /** Sender info */
1025
+ sender?: {
1026
+ transportPubkey: string;
1027
+ nametag?: string;
1028
+ };
1029
+ }
1030
+ /**
1031
+ * Incoming instant split bundle
1032
+ */
1033
+ interface IncomingInstantSplitBundle {
1034
+ /** Event ID */
1035
+ id: string;
1036
+ /** Transport-specific pubkey of sender */
1037
+ senderTransportPubkey: string;
1038
+ /** The bundle JSON string */
1039
+ bundle: string;
1040
+ /** Timestamp */
1041
+ timestamp: number;
1042
+ }
1043
+ /**
1044
+ * Handler for instant split bundles
1045
+ */
1046
+ type InstantSplitBundleHandler = (bundle: IncomingInstantSplitBundle) => void;
721
1047
  interface IncomingMessage {
722
1048
  id: string;
723
1049
  /** Transport-specific pubkey of sender */
@@ -818,12 +1144,13 @@ interface IncomingBroadcast {
818
1144
  }
819
1145
  type BroadcastHandler = (broadcast: IncomingBroadcast) => void;
820
1146
  /**
821
- * Full nametag address information
822
- * Used for resolving nametag to all address formats
1147
+ * Resolved peer identity information.
1148
+ * Returned by resolve methods contains all public address formats for a peer.
1149
+ * Fields nametag and proxyAddress are optional (only present if nametag is registered).
823
1150
  */
824
- interface NametagInfo {
825
- /** Nametag name (without @) */
826
- nametag: string;
1151
+ interface PeerInfo {
1152
+ /** Nametag name (without @), if registered */
1153
+ nametag?: string;
827
1154
  /** Transport-specific pubkey (for messaging/encryption) */
828
1155
  transportPubkey: string;
829
1156
  /** 33-byte compressed secp256k1 public key (for L3 chain) */
@@ -832,8 +1159,8 @@ interface NametagInfo {
832
1159
  l1Address: string;
833
1160
  /** L3 DIRECT address (DIRECT://...) */
834
1161
  directAddress: string;
835
- /** L3 PROXY address derived from nametag hash (PROXY:...) */
836
- proxyAddress: string;
1162
+ /** L3 PROXY address derived from nametag hash (PROXY:...), only if nametag registered */
1163
+ proxyAddress?: string;
837
1164
  /** Event timestamp */
838
1165
  timestamp: number;
839
1166
  }
@@ -920,7 +1247,6 @@ declare class L1PaymentsModule {
920
1247
  private _initialized;
921
1248
  private _config;
922
1249
  private _identity?;
923
- private _chainCode?;
924
1250
  private _addresses;
925
1251
  private _wallet?;
926
1252
  private _transport?;
@@ -1017,6 +1343,14 @@ interface StorageProvider extends BaseProvider {
1017
1343
  * Clear all keys with optional prefix filter
1018
1344
  */
1019
1345
  clear(prefix?: string): Promise<void>;
1346
+ /**
1347
+ * Save tracked addresses (only user state: index, hidden, timestamps)
1348
+ */
1349
+ saveTrackedAddresses(entries: TrackedAddressEntry[]): Promise<void>;
1350
+ /**
1351
+ * Load tracked addresses
1352
+ */
1353
+ loadTrackedAddresses(): Promise<TrackedAddressEntry[]>;
1020
1354
  }
1021
1355
  /**
1022
1356
  * Storage result types
@@ -1275,6 +1609,65 @@ interface MintResult {
1275
1609
  error?: string;
1276
1610
  }
1277
1611
 
1612
+ /**
1613
+ * Price Provider Interface
1614
+ *
1615
+ * Platform-independent abstraction for fetching token market prices.
1616
+ * Does not extend BaseProvider — stateless HTTP client with internal caching.
1617
+ */
1618
+ /**
1619
+ * Supported price provider platforms
1620
+ */
1621
+ type PricePlatform = 'coingecko';
1622
+ /**
1623
+ * Price data for a single token
1624
+ */
1625
+ interface TokenPrice {
1626
+ /** Token name used by the price platform (e.g., "bitcoin") */
1627
+ readonly tokenName: string;
1628
+ /** Price in USD */
1629
+ readonly priceUsd: number;
1630
+ /** Price in EUR (if available) */
1631
+ readonly priceEur?: number;
1632
+ /** 24h price change percentage (if available) */
1633
+ readonly change24h?: number;
1634
+ /** Timestamp when this price was fetched */
1635
+ readonly timestamp: number;
1636
+ }
1637
+ /**
1638
+ * Price data provider
1639
+ *
1640
+ * Fetches current market prices for tokens. Implementations handle
1641
+ * caching internally to avoid excessive API calls.
1642
+ *
1643
+ * @example
1644
+ * ```ts
1645
+ * const provider = new CoinGeckoPriceProvider({ apiKey: 'CG-xxx' });
1646
+ * const prices = await provider.getPrices(['bitcoin', 'ethereum']);
1647
+ * console.log(prices.get('bitcoin')?.priceUsd); // 97500
1648
+ * ```
1649
+ */
1650
+ interface PriceProvider {
1651
+ /** Platform identifier (e.g., 'coingecko') */
1652
+ readonly platform: PricePlatform;
1653
+ /**
1654
+ * Get prices for multiple tokens by their platform-compatible names
1655
+ * @param tokenNames - Array of token names (e.g., ['bitcoin', 'ethereum'])
1656
+ * @returns Map of token name to price data
1657
+ */
1658
+ getPrices(tokenNames: string[]): Promise<Map<string, TokenPrice>>;
1659
+ /**
1660
+ * Get price for a single token
1661
+ * @param tokenName - Token name (e.g., 'bitcoin')
1662
+ * @returns Token price or null if not available
1663
+ */
1664
+ getPrice(tokenName: string): Promise<TokenPrice | null>;
1665
+ /**
1666
+ * Clear cached prices
1667
+ */
1668
+ clearCache(): void;
1669
+ }
1670
+
1278
1671
  /**
1279
1672
  * Payments Module
1280
1673
  * Platform-independent token operations with full wallet repository functionality
@@ -1327,6 +1720,8 @@ interface PaymentsModuleDependencies {
1327
1720
  chainCode?: string;
1328
1721
  /** Additional L1 addresses to watch */
1329
1722
  l1Addresses?: string[];
1723
+ /** Price provider (optional — enables fiat value display) */
1724
+ price?: PriceProvider;
1330
1725
  }
1331
1726
  declare class PaymentsModule {
1332
1727
  private readonly moduleConfig;
@@ -1348,9 +1743,15 @@ declare class PaymentsModule {
1348
1743
  private unsubscribeTransfers;
1349
1744
  private unsubscribePaymentRequests;
1350
1745
  private unsubscribePaymentRequestResponses;
1746
+ private proofPollingJobs;
1747
+ private proofPollingInterval;
1748
+ private static readonly PROOF_POLLING_INTERVAL_MS;
1749
+ private static readonly PROOF_POLLING_MAX_ATTEMPTS;
1351
1750
  constructor(config?: PaymentsModuleConfig);
1352
1751
  /** Get module configuration */
1353
1752
  getConfig(): Omit<Required<PaymentsModuleConfig>, 'l1'>;
1753
+ /** Price provider (optional) */
1754
+ private priceProvider;
1354
1755
  private log;
1355
1756
  /**
1356
1757
  * Initialize module with dependencies
@@ -1385,6 +1786,39 @@ declare class PaymentsModule {
1385
1786
  * Get coin icon URL from coinId
1386
1787
  */
1387
1788
  private getCoinIconUrl;
1789
+ /**
1790
+ * Send tokens using INSTANT_SPLIT V5 optimized flow.
1791
+ *
1792
+ * This achieves ~2.3s critical path latency instead of ~42s by:
1793
+ * 1. Waiting only for burn proof (required)
1794
+ * 2. Creating transfer commitment from mint data (no mint proof needed)
1795
+ * 3. Sending bundle via Nostr immediately
1796
+ * 4. Processing mints in background
1797
+ *
1798
+ * @param request - Transfer request with recipient, amount, and coinId
1799
+ * @param options - Optional instant split configuration
1800
+ * @returns InstantSplitResult with timing info
1801
+ */
1802
+ sendInstant(request: TransferRequest, options?: InstantSplitOptions): Promise<InstantSplitResult>;
1803
+ /**
1804
+ * Process a received INSTANT_SPLIT bundle.
1805
+ *
1806
+ * This should be called when receiving an instant split bundle via transport.
1807
+ * It handles the recipient-side processing:
1808
+ * 1. Validate burn transaction
1809
+ * 2. Submit and wait for mint proof
1810
+ * 3. Submit and wait for transfer proof
1811
+ * 4. Finalize and save the token
1812
+ *
1813
+ * @param bundle - The received InstantSplitBundle (V4 or V5)
1814
+ * @param senderPubkey - Sender's public key for verification
1815
+ * @returns Processing result with finalized token
1816
+ */
1817
+ processInstantSplitBundle(bundle: InstantSplitBundle, senderPubkey: string): Promise<InstantSplitProcessResult>;
1818
+ /**
1819
+ * Check if a payload is an instant split bundle
1820
+ */
1821
+ isInstantSplitBundle(payload: unknown): payload is InstantSplitBundle;
1388
1822
  /**
1389
1823
  * Send a payment request to someone
1390
1824
  * @param recipientPubkeyOrNametag - Recipient's pubkey or @nametag
@@ -1474,14 +1908,19 @@ declare class PaymentsModule {
1474
1908
  */
1475
1909
  private sendPaymentRequestResponse;
1476
1910
  /**
1477
- * Get balance for coin type
1911
+ * Set or update price provider
1912
+ */
1913
+ setPriceProvider(provider: PriceProvider): void;
1914
+ /**
1915
+ * Get total portfolio value in USD
1916
+ * Returns null if PriceProvider is not configured
1478
1917
  */
1479
- getBalance(coinId?: string): TokenBalance[];
1918
+ getBalance(): Promise<number | null>;
1480
1919
  /**
1481
- * Get aggregated assets (tokens grouped by coinId)
1920
+ * Get aggregated assets (tokens grouped by coinId) with price data
1482
1921
  * Only includes confirmed tokens
1483
1922
  */
1484
- getAssets(coinId?: string): Asset[];
1923
+ getAssets(coinId?: string): Promise<Asset[]>;
1485
1924
  /**
1486
1925
  * Get all tokens
1487
1926
  */
@@ -1495,7 +1934,9 @@ declare class PaymentsModule {
1495
1934
  getToken(id: string): Token | undefined;
1496
1935
  /**
1497
1936
  * Add a token
1498
- * @returns false if duplicate
1937
+ * Tokens are uniquely identified by (tokenId, stateHash) composite key.
1938
+ * Multiple historic states of the same token can coexist.
1939
+ * @returns false if exact duplicate (same tokenId AND same stateHash)
1499
1940
  */
1500
1941
  addToken(token: Token, skipHistory?: boolean): Promise<boolean>;
1501
1942
  /**
@@ -1646,12 +2087,11 @@ declare class PaymentsModule {
1646
2087
  * Detect if a string is an L3 address (not a nametag)
1647
2088
  * Returns true for: hex pubkeys (64+ chars), PROXY:, DIRECT: prefixed addresses
1648
2089
  */
1649
- private isL3Address;
1650
2090
  /**
1651
- * Resolve recipient to Nostr pubkey for messaging
1652
- * Supports: nametag (with or without @), hex pubkey
2091
+ * Resolve recipient to transport pubkey for messaging.
2092
+ * Uses pre-resolved PeerInfo if available, otherwise resolves via transport.
1653
2093
  */
1654
- private resolveRecipient;
2094
+ private resolveTransportPubkey;
1655
2095
  /**
1656
2096
  * Create SDK TransferCommitment for a token transfer
1657
2097
  */
@@ -1665,15 +2105,25 @@ declare class PaymentsModule {
1665
2105
  */
1666
2106
  private createDirectAddressFromPubkey;
1667
2107
  /**
1668
- * Resolve nametag to 33-byte compressed public key using resolveNametagInfo
1669
- * Returns null if nametag not found or publicKey not available
2108
+ * Resolve recipient to IAddress for L3 transfers.
2109
+ * Uses pre-resolved PeerInfo when available to avoid redundant network queries.
1670
2110
  */
1671
- private resolveNametagToPublicKey;
2111
+ private resolveRecipientAddress;
1672
2112
  /**
1673
- * Resolve recipient to IAddress for L3 transfers
1674
- * Supports: nametag (with or without @), PROXY:, DIRECT:, hex pubkey
2113
+ * Handle NOSTR-FIRST commitment-only transfer (recipient side)
2114
+ * This is called when receiving a transfer with only commitmentData and no proof yet.
2115
+ * We create the token as 'submitted', submit commitment (idempotent), and poll for proof.
1675
2116
  */
1676
- private resolveRecipientAddress;
2117
+ private handleCommitmentOnlyTransfer;
2118
+ /**
2119
+ * Shared finalization logic for received transfers.
2120
+ * Handles both PROXY (with nametag token + address validation) and DIRECT schemes.
2121
+ */
2122
+ private finalizeTransferToken;
2123
+ /**
2124
+ * Finalize a received token after proof is available
2125
+ */
2126
+ private finalizeReceivedToken;
1677
2127
  private handleIncomingTransfer;
1678
2128
  private archiveToken;
1679
2129
  private save;
@@ -1682,6 +2132,27 @@ declare class PaymentsModule {
1682
2132
  private loadOutbox;
1683
2133
  private createStorageData;
1684
2134
  private loadFromStorageData;
2135
+ /**
2136
+ * Submit commitment to aggregator and start background proof polling
2137
+ * (NOSTR-FIRST pattern: fire-and-forget submission)
2138
+ */
2139
+ private submitAndPollForProof;
2140
+ /**
2141
+ * Add a proof polling job to the queue
2142
+ */
2143
+ private addProofPollingJob;
2144
+ /**
2145
+ * Start the proof polling interval if not already running
2146
+ */
2147
+ private startProofPolling;
2148
+ /**
2149
+ * Stop the proof polling interval
2150
+ */
2151
+ private stopProofPolling;
2152
+ /**
2153
+ * Process all pending proof polling jobs
2154
+ */
2155
+ private processProofPollingQueue;
1685
2156
  private ensureInitialized;
1686
2157
  }
1687
2158
 
@@ -1828,6 +2299,8 @@ interface SphereCreateOptions {
1828
2299
  oracle: OracleProvider;
1829
2300
  /** L1 (ALPHA blockchain) configuration */
1830
2301
  l1?: L1Config;
2302
+ /** Optional price provider for fiat conversion */
2303
+ price?: PriceProvider;
1831
2304
  /**
1832
2305
  * Network type (mainnet, testnet, dev) - informational only.
1833
2306
  * Actual network configuration comes from provider URLs.
@@ -1847,6 +2320,8 @@ interface SphereLoadOptions {
1847
2320
  oracle: OracleProvider;
1848
2321
  /** L1 (ALPHA blockchain) configuration */
1849
2322
  l1?: L1Config;
2323
+ /** Optional price provider for fiat conversion */
2324
+ price?: PriceProvider;
1850
2325
  /**
1851
2326
  * Network type (mainnet, testnet, dev) - informational only.
1852
2327
  * Actual network configuration comes from provider URLs.
@@ -1880,6 +2355,8 @@ interface SphereImportOptions {
1880
2355
  oracle: OracleProvider;
1881
2356
  /** L1 (ALPHA blockchain) configuration */
1882
2357
  l1?: L1Config;
2358
+ /** Optional price provider for fiat conversion */
2359
+ price?: PriceProvider;
1883
2360
  }
1884
2361
  /** L1 (ALPHA blockchain) configuration */
1885
2362
  interface L1Config {
@@ -1910,6 +2387,8 @@ interface SphereInitOptions {
1910
2387
  nametag?: string;
1911
2388
  /** L1 (ALPHA blockchain) configuration */
1912
2389
  l1?: L1Config;
2390
+ /** Optional price provider for fiat conversion */
2391
+ price?: PriceProvider;
1913
2392
  /**
1914
2393
  * Network type (mainnet, testnet, dev) - informational only.
1915
2394
  * Actual network configuration comes from provider URLs.
@@ -1936,7 +2415,11 @@ declare class Sphere {
1936
2415
  private _derivationMode;
1937
2416
  private _basePath;
1938
2417
  private _currentAddressIndex;
1939
- /** Map of addressId -> (nametagIndex -> nametag). Supports multiple nametags per address (e.g., from Nostr recovery) */
2418
+ /** Registry of all tracked (activated) addresses, keyed by HD index */
2419
+ private _trackedAddresses;
2420
+ /** Reverse lookup: addressId -> HD index */
2421
+ private _addressIdToIndex;
2422
+ /** Nametag cache: addressId -> (nametagIndex -> nametag). Separate from tracked addresses. */
1940
2423
  private _addressNametags;
1941
2424
  /** Cached PROXY address (computed once when nametag is set) */
1942
2425
  private _cachedProxyAddress;
@@ -1944,6 +2427,7 @@ declare class Sphere {
1944
2427
  private _tokenStorageProviders;
1945
2428
  private _transport;
1946
2429
  private _oracle;
2430
+ private _priceProvider;
1947
2431
  private _payments;
1948
2432
  private _communications;
1949
2433
  private eventHandlers;
@@ -2061,6 +2545,10 @@ declare class Sphere {
2061
2545
  * Check if a token storage provider is registered
2062
2546
  */
2063
2547
  hasTokenStorageProvider(providerId: string): boolean;
2548
+ /**
2549
+ * Set or update the price provider after initialization
2550
+ */
2551
+ setPriceProvider(provider: PriceProvider): void;
2064
2552
  getTransport(): TransportProvider;
2065
2553
  getAggregator(): OracleProvider;
2066
2554
  /**
@@ -2244,14 +2732,40 @@ declare class Sphere {
2244
2732
  getNametagsForAddress(addressId?: string): Map<number, string> | undefined;
2245
2733
  /**
2246
2734
  * Get all registered address nametags
2247
- *
2735
+ * @deprecated Use getActiveAddresses() or getAllTrackedAddresses() instead
2248
2736
  * @returns Map of addressId to (nametagIndex -> nametag)
2249
2737
  */
2250
2738
  getAllAddressNametags(): Map<string, Map<number, string>>;
2251
2739
  /**
2252
- * Get current address identifier (DIRECT://xxx format)
2740
+ * Get all active (non-hidden) tracked addresses.
2741
+ * Returns addresses that have been activated through create, switchToAddress,
2742
+ * registerNametag, or nametag recovery.
2743
+ *
2744
+ * @returns Array of TrackedAddress entries sorted by index, excluding hidden ones
2745
+ */
2746
+ getActiveAddresses(): TrackedAddress[];
2747
+ /**
2748
+ * Get all tracked addresses, including hidden ones.
2749
+ *
2750
+ * @returns Array of all TrackedAddress entries sorted by index
2751
+ */
2752
+ getAllTrackedAddresses(): TrackedAddress[];
2753
+ /**
2754
+ * Get tracked address info by index.
2755
+ *
2756
+ * @param index - Address index
2757
+ * @returns TrackedAddress or undefined if not tracked
2253
2758
  */
2254
- private getCurrentAddressId;
2759
+ getTrackedAddress(index: number): TrackedAddress | undefined;
2760
+ /**
2761
+ * Set visibility of a tracked address.
2762
+ * Hidden addresses are not returned by getActiveAddresses() but remain tracked.
2763
+ *
2764
+ * @param index - Address index to hide/unhide
2765
+ * @param hidden - true to hide, false to show
2766
+ * @throws Error if address index is not tracked
2767
+ */
2768
+ setAddressHidden(index: number, hidden: boolean): Promise<void>;
2255
2769
  /**
2256
2770
  * Switch to a different address by index
2257
2771
  * This changes the active identity to the derived address at the specified index.
@@ -2271,7 +2785,9 @@ declare class Sphere {
2271
2785
  * await sphere.switchToAddress(0);
2272
2786
  * ```
2273
2787
  */
2274
- switchToAddress(index: number): Promise<void>;
2788
+ switchToAddress(index: number, options?: {
2789
+ nametag?: string;
2790
+ }): Promise<void>;
2275
2791
  /**
2276
2792
  * Re-initialize modules after address switch
2277
2793
  */
@@ -2297,6 +2813,12 @@ declare class Sphere {
2297
2813
  * ```
2298
2814
  */
2299
2815
  deriveAddress(index: number, isChange?: boolean): AddressInfo;
2816
+ /**
2817
+ * Internal address derivation without ensureReady() check.
2818
+ * Used during initialization (loadTrackedAddresses, ensureAddressTracked)
2819
+ * when _initialized is still false.
2820
+ */
2821
+ private _deriveAddressInternal;
2300
2822
  /**
2301
2823
  * Derive address at a full BIP32 path
2302
2824
  *
@@ -2356,6 +2878,19 @@ declare class Sphere {
2356
2878
  * @returns PROXY address string or undefined if no nametag
2357
2879
  */
2358
2880
  getProxyAddress(): string | undefined;
2881
+ /**
2882
+ * Resolve any identifier to full peer information.
2883
+ * Accepts @nametag, bare nametag, DIRECT://, PROXY://, L1 address, or transport pubkey.
2884
+ *
2885
+ * @example
2886
+ * ```ts
2887
+ * const peer = await sphere.resolve('@alice');
2888
+ * const peer = await sphere.resolve('DIRECT://...');
2889
+ * const peer = await sphere.resolve('alpha1...');
2890
+ * const peer = await sphere.resolve('ab12cd...'); // 64-char hex transport pubkey
2891
+ * ```
2892
+ */
2893
+ resolve(identifier: string): Promise<PeerInfo | null>;
2359
2894
  /** Compute and cache the PROXY address from the current nametag */
2360
2895
  private _updateCachedProxyAddress;
2361
2896
  /**
@@ -2378,10 +2913,9 @@ declare class Sphere {
2378
2913
  */
2379
2914
  registerNametag(nametag: string): Promise<void>;
2380
2915
  /**
2381
- * Persist address nametags to storage
2382
- * Format: { "DIRECT://abc...xyz": { "0": "alice", "1": "alice2" }, ... }
2916
+ * Persist tracked addresses to storage (only minimal fields via StorageProvider)
2383
2917
  */
2384
- private persistAddressNametags;
2918
+ private persistTrackedAddresses;
2385
2919
  /**
2386
2920
  * Mint a nametag token on-chain (like Sphere wallet and lottery)
2387
2921
  * This creates the nametag token required for receiving tokens via PROXY addresses (@nametag)
@@ -2408,22 +2942,42 @@ declare class Sphere {
2408
2942
  */
2409
2943
  isNametagAvailable(nametag: string): Promise<boolean>;
2410
2944
  /**
2411
- * Load address nametags from storage
2412
- * Supports new format: { "DIRECT://abc...xyz": { "0": "alice" } }
2413
- * And legacy format: { "0": "alice" } (migrates to new format on save)
2945
+ * Load tracked addresses from storage.
2946
+ * Falls back to migrating from old ADDRESS_NAMETAGS format.
2947
+ */
2948
+ private loadTrackedAddresses;
2949
+ /**
2950
+ * Migrate from old ADDRESS_NAMETAGS format to tracked addresses.
2951
+ * Scans HD indices 0..19 to match addressIds from the old format.
2952
+ * Populates both _trackedAddresses and _addressNametags.
2953
+ */
2954
+ private migrateFromOldNametagFormat;
2955
+ /**
2956
+ * Ensure an address is tracked in the registry.
2957
+ * If not yet tracked, derives full info and creates the entry.
2958
+ */
2959
+ private ensureAddressTracked;
2960
+ /**
2961
+ * Persist nametag cache to storage.
2962
+ * Format: { addressId: { "0": "alice", "1": "alice2" } }
2963
+ */
2964
+ private persistAddressNametags;
2965
+ /**
2966
+ * Load nametag cache from storage.
2414
2967
  */
2415
2968
  private loadAddressNametags;
2416
2969
  /**
2417
- * Sync nametag with Nostr on wallet load
2418
- * If local nametag exists but not registered on Nostr, re-register it
2970
+ * Publish identity binding via transport.
2971
+ * Always publishes base identity (chainPubkey, l1Address, directAddress).
2972
+ * If nametag is set, also publishes nametag hash, proxy address, encrypted nametag.
2419
2973
  */
2420
- private syncNametagWithNostr;
2974
+ private syncIdentityWithTransport;
2421
2975
  /**
2422
- * Recover nametag from Nostr after wallet import
2976
+ * Recover nametag from transport after wallet import.
2423
2977
  * Searches for encrypted nametag events authored by this wallet's pubkey
2424
- * and decrypts them to restore the nametag association
2978
+ * and decrypts them to restore the nametag association.
2425
2979
  */
2426
- private recoverNametagFromNostr;
2980
+ private recoverNametagFromTransport;
2427
2981
  /**
2428
2982
  * Validate nametag format
2429
2983
  */