@unicitylabs/sphere-sdk 0.1.8 → 0.2.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.
@@ -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
  /**
@@ -634,15 +885,36 @@ interface TransportProvider extends BaseProvider {
634
885
  * @returns Unsubscribe function
635
886
  */
636
887
  onTokenTransfer(handler: TokenTransferHandler): () => void;
888
+ /**
889
+ * Resolve any identifier to full peer information.
890
+ * Accepts @nametag, bare nametag, DIRECT://, PROXY://, L1 address, chain pubkey, or transport pubkey.
891
+ * @param identifier - Any supported identifier format
892
+ * @returns PeerInfo or null if not found
893
+ */
894
+ resolve?(identifier: string): Promise<PeerInfo | null>;
637
895
  /**
638
896
  * Resolve nametag to public key
639
897
  */
640
898
  resolveNametag?(nametag: string): Promise<string | null>;
641
899
  /**
642
- * Resolve nametag to full address information
900
+ * Resolve nametag to full peer information
643
901
  * Returns transportPubkey, chainPubkey, l1Address, directAddress, proxyAddress
644
902
  */
645
- resolveNametagInfo?(nametag: string): Promise<NametagInfo | null>;
903
+ resolveNametagInfo?(nametag: string): Promise<PeerInfo | null>;
904
+ /**
905
+ * Resolve a DIRECT://, PROXY://, or L1 address to full peer info.
906
+ * Performs reverse lookup: address → binding event → PeerInfo.
907
+ * @param address - L3 address (DIRECT://... or PROXY://...) or L1 address (alpha1...)
908
+ * @returns PeerInfo or null if no binding found for this address
909
+ */
910
+ resolveAddressInfo?(address: string): Promise<PeerInfo | null>;
911
+ /**
912
+ * Resolve transport pubkey to full peer info.
913
+ * Queries binding events authored by the given transport pubkey.
914
+ * @param transportPubkey - Transport-specific pubkey (e.g. 64-char hex string)
915
+ * @returns PeerInfo or null if no binding found
916
+ */
917
+ resolveTransportPubkeyInfo?(transportPubkey: string): Promise<PeerInfo | null>;
646
918
  /**
647
919
  * Recover nametag for current identity by decrypting stored encrypted nametag
648
920
  * Used after wallet import to recover associated nametag
@@ -650,14 +922,20 @@ interface TransportProvider extends BaseProvider {
650
922
  */
651
923
  recoverNametag?(): Promise<string | null>;
652
924
  /**
925
+ * Publish identity binding event.
926
+ * Without nametag: publishes base binding (chainPubkey, l1Address, directAddress).
927
+ * With nametag: adds nametag hash, proxy address, encrypted nametag for recovery.
928
+ * Uses parameterized replaceable event (kind 30078, d=hash(nostrPubkey)).
929
+ * @returns true if successful, false if nametag is taken by another pubkey
930
+ */
931
+ publishIdentityBinding?(chainPubkey: string, l1Address: string, directAddress: string, nametag?: string): Promise<boolean>;
932
+ /**
933
+ * @deprecated Use publishIdentityBinding instead
653
934
  * 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
935
  */
659
936
  registerNametag?(nametag: string, chainPubkey: string, directAddress: string): Promise<boolean>;
660
937
  /**
938
+ * @deprecated Use publishIdentityBinding instead
661
939
  * Publish nametag binding
662
940
  */
663
941
  publishNametag?(nametag: string, address: string): Promise<void>;
@@ -717,7 +995,54 @@ interface TransportProvider extends BaseProvider {
717
995
  * Check if a relay is currently connected
718
996
  */
719
997
  isRelayConnected?(relayUrl: string): boolean;
998
+ /**
999
+ * Send an instant split bundle to a recipient.
1000
+ * This is a specialized method for INSTANT_SPLIT V5 bundles.
1001
+ *
1002
+ * @param recipientTransportPubkey - Transport-specific pubkey for messaging
1003
+ * @param bundle - The InstantSplitBundleV5 to send
1004
+ * @returns Event ID
1005
+ */
1006
+ sendInstantSplitBundle?(recipientTransportPubkey: string, bundle: InstantSplitBundlePayload): Promise<string>;
1007
+ /**
1008
+ * Subscribe to incoming instant split bundles.
1009
+ *
1010
+ * @param handler - Handler for received bundles
1011
+ * @returns Unsubscribe function
1012
+ */
1013
+ onInstantSplitReceived?(handler: InstantSplitBundleHandler): () => void;
1014
+ }
1015
+ /**
1016
+ * Payload for sending instant split bundles
1017
+ */
1018
+ interface InstantSplitBundlePayload {
1019
+ /** The bundle JSON string (InstantSplitBundleV5 serialized) */
1020
+ bundle: string;
1021
+ /** Optional memo */
1022
+ memo?: string;
1023
+ /** Sender info */
1024
+ sender?: {
1025
+ transportPubkey: string;
1026
+ nametag?: string;
1027
+ };
720
1028
  }
1029
+ /**
1030
+ * Incoming instant split bundle
1031
+ */
1032
+ interface IncomingInstantSplitBundle {
1033
+ /** Event ID */
1034
+ id: string;
1035
+ /** Transport-specific pubkey of sender */
1036
+ senderTransportPubkey: string;
1037
+ /** The bundle JSON string */
1038
+ bundle: string;
1039
+ /** Timestamp */
1040
+ timestamp: number;
1041
+ }
1042
+ /**
1043
+ * Handler for instant split bundles
1044
+ */
1045
+ type InstantSplitBundleHandler = (bundle: IncomingInstantSplitBundle) => void;
721
1046
  interface IncomingMessage {
722
1047
  id: string;
723
1048
  /** Transport-specific pubkey of sender */
@@ -818,12 +1143,13 @@ interface IncomingBroadcast {
818
1143
  }
819
1144
  type BroadcastHandler = (broadcast: IncomingBroadcast) => void;
820
1145
  /**
821
- * Full nametag address information
822
- * Used for resolving nametag to all address formats
1146
+ * Resolved peer identity information.
1147
+ * Returned by resolve methods contains all public address formats for a peer.
1148
+ * Fields nametag and proxyAddress are optional (only present if nametag is registered).
823
1149
  */
824
- interface NametagInfo {
825
- /** Nametag name (without @) */
826
- nametag: string;
1150
+ interface PeerInfo {
1151
+ /** Nametag name (without @), if registered */
1152
+ nametag?: string;
827
1153
  /** Transport-specific pubkey (for messaging/encryption) */
828
1154
  transportPubkey: string;
829
1155
  /** 33-byte compressed secp256k1 public key (for L3 chain) */
@@ -832,8 +1158,8 @@ interface NametagInfo {
832
1158
  l1Address: string;
833
1159
  /** L3 DIRECT address (DIRECT://...) */
834
1160
  directAddress: string;
835
- /** L3 PROXY address derived from nametag hash (PROXY:...) */
836
- proxyAddress: string;
1161
+ /** L3 PROXY address derived from nametag hash (PROXY:...), only if nametag registered */
1162
+ proxyAddress?: string;
837
1163
  /** Event timestamp */
838
1164
  timestamp: number;
839
1165
  }
@@ -920,7 +1246,6 @@ declare class L1PaymentsModule {
920
1246
  private _initialized;
921
1247
  private _config;
922
1248
  private _identity?;
923
- private _chainCode?;
924
1249
  private _addresses;
925
1250
  private _wallet?;
926
1251
  private _transport?;
@@ -1017,6 +1342,14 @@ interface StorageProvider extends BaseProvider {
1017
1342
  * Clear all keys with optional prefix filter
1018
1343
  */
1019
1344
  clear(prefix?: string): Promise<void>;
1345
+ /**
1346
+ * Save tracked addresses (only user state: index, hidden, timestamps)
1347
+ */
1348
+ saveTrackedAddresses(entries: TrackedAddressEntry[]): Promise<void>;
1349
+ /**
1350
+ * Load tracked addresses
1351
+ */
1352
+ loadTrackedAddresses(): Promise<TrackedAddressEntry[]>;
1020
1353
  }
1021
1354
  /**
1022
1355
  * Storage result types
@@ -1275,6 +1608,65 @@ interface MintResult {
1275
1608
  error?: string;
1276
1609
  }
1277
1610
 
1611
+ /**
1612
+ * Price Provider Interface
1613
+ *
1614
+ * Platform-independent abstraction for fetching token market prices.
1615
+ * Does not extend BaseProvider — stateless HTTP client with internal caching.
1616
+ */
1617
+ /**
1618
+ * Supported price provider platforms
1619
+ */
1620
+ type PricePlatform = 'coingecko';
1621
+ /**
1622
+ * Price data for a single token
1623
+ */
1624
+ interface TokenPrice {
1625
+ /** Token name used by the price platform (e.g., "bitcoin") */
1626
+ readonly tokenName: string;
1627
+ /** Price in USD */
1628
+ readonly priceUsd: number;
1629
+ /** Price in EUR (if available) */
1630
+ readonly priceEur?: number;
1631
+ /** 24h price change percentage (if available) */
1632
+ readonly change24h?: number;
1633
+ /** Timestamp when this price was fetched */
1634
+ readonly timestamp: number;
1635
+ }
1636
+ /**
1637
+ * Price data provider
1638
+ *
1639
+ * Fetches current market prices for tokens. Implementations handle
1640
+ * caching internally to avoid excessive API calls.
1641
+ *
1642
+ * @example
1643
+ * ```ts
1644
+ * const provider = new CoinGeckoPriceProvider({ apiKey: 'CG-xxx' });
1645
+ * const prices = await provider.getPrices(['bitcoin', 'ethereum']);
1646
+ * console.log(prices.get('bitcoin')?.priceUsd); // 97500
1647
+ * ```
1648
+ */
1649
+ interface PriceProvider {
1650
+ /** Platform identifier (e.g., 'coingecko') */
1651
+ readonly platform: PricePlatform;
1652
+ /**
1653
+ * Get prices for multiple tokens by their platform-compatible names
1654
+ * @param tokenNames - Array of token names (e.g., ['bitcoin', 'ethereum'])
1655
+ * @returns Map of token name to price data
1656
+ */
1657
+ getPrices(tokenNames: string[]): Promise<Map<string, TokenPrice>>;
1658
+ /**
1659
+ * Get price for a single token
1660
+ * @param tokenName - Token name (e.g., 'bitcoin')
1661
+ * @returns Token price or null if not available
1662
+ */
1663
+ getPrice(tokenName: string): Promise<TokenPrice | null>;
1664
+ /**
1665
+ * Clear cached prices
1666
+ */
1667
+ clearCache(): void;
1668
+ }
1669
+
1278
1670
  /**
1279
1671
  * Payments Module
1280
1672
  * Platform-independent token operations with full wallet repository functionality
@@ -1327,6 +1719,8 @@ interface PaymentsModuleDependencies {
1327
1719
  chainCode?: string;
1328
1720
  /** Additional L1 addresses to watch */
1329
1721
  l1Addresses?: string[];
1722
+ /** Price provider (optional — enables fiat value display) */
1723
+ price?: PriceProvider;
1330
1724
  }
1331
1725
  declare class PaymentsModule {
1332
1726
  private readonly moduleConfig;
@@ -1348,9 +1742,15 @@ declare class PaymentsModule {
1348
1742
  private unsubscribeTransfers;
1349
1743
  private unsubscribePaymentRequests;
1350
1744
  private unsubscribePaymentRequestResponses;
1745
+ private proofPollingJobs;
1746
+ private proofPollingInterval;
1747
+ private static readonly PROOF_POLLING_INTERVAL_MS;
1748
+ private static readonly PROOF_POLLING_MAX_ATTEMPTS;
1351
1749
  constructor(config?: PaymentsModuleConfig);
1352
1750
  /** Get module configuration */
1353
1751
  getConfig(): Omit<Required<PaymentsModuleConfig>, 'l1'>;
1752
+ /** Price provider (optional) */
1753
+ private priceProvider;
1354
1754
  private log;
1355
1755
  /**
1356
1756
  * Initialize module with dependencies
@@ -1385,6 +1785,39 @@ declare class PaymentsModule {
1385
1785
  * Get coin icon URL from coinId
1386
1786
  */
1387
1787
  private getCoinIconUrl;
1788
+ /**
1789
+ * Send tokens using INSTANT_SPLIT V5 optimized flow.
1790
+ *
1791
+ * This achieves ~2.3s critical path latency instead of ~42s by:
1792
+ * 1. Waiting only for burn proof (required)
1793
+ * 2. Creating transfer commitment from mint data (no mint proof needed)
1794
+ * 3. Sending bundle via Nostr immediately
1795
+ * 4. Processing mints in background
1796
+ *
1797
+ * @param request - Transfer request with recipient, amount, and coinId
1798
+ * @param options - Optional instant split configuration
1799
+ * @returns InstantSplitResult with timing info
1800
+ */
1801
+ sendInstant(request: TransferRequest, options?: InstantSplitOptions): Promise<InstantSplitResult>;
1802
+ /**
1803
+ * Process a received INSTANT_SPLIT bundle.
1804
+ *
1805
+ * This should be called when receiving an instant split bundle via transport.
1806
+ * It handles the recipient-side processing:
1807
+ * 1. Validate burn transaction
1808
+ * 2. Submit and wait for mint proof
1809
+ * 3. Submit and wait for transfer proof
1810
+ * 4. Finalize and save the token
1811
+ *
1812
+ * @param bundle - The received InstantSplitBundle (V4 or V5)
1813
+ * @param senderPubkey - Sender's public key for verification
1814
+ * @returns Processing result with finalized token
1815
+ */
1816
+ processInstantSplitBundle(bundle: InstantSplitBundle, senderPubkey: string): Promise<InstantSplitProcessResult>;
1817
+ /**
1818
+ * Check if a payload is an instant split bundle
1819
+ */
1820
+ isInstantSplitBundle(payload: unknown): payload is InstantSplitBundle;
1388
1821
  /**
1389
1822
  * Send a payment request to someone
1390
1823
  * @param recipientPubkeyOrNametag - Recipient's pubkey or @nametag
@@ -1474,14 +1907,19 @@ declare class PaymentsModule {
1474
1907
  */
1475
1908
  private sendPaymentRequestResponse;
1476
1909
  /**
1477
- * Get balance for coin type
1910
+ * Set or update price provider
1911
+ */
1912
+ setPriceProvider(provider: PriceProvider): void;
1913
+ /**
1914
+ * Get total portfolio value in USD
1915
+ * Returns null if PriceProvider is not configured
1478
1916
  */
1479
- getBalance(coinId?: string): TokenBalance[];
1917
+ getBalance(): Promise<number | null>;
1480
1918
  /**
1481
- * Get aggregated assets (tokens grouped by coinId)
1919
+ * Get aggregated assets (tokens grouped by coinId) with price data
1482
1920
  * Only includes confirmed tokens
1483
1921
  */
1484
- getAssets(coinId?: string): Asset[];
1922
+ getAssets(coinId?: string): Promise<Asset[]>;
1485
1923
  /**
1486
1924
  * Get all tokens
1487
1925
  */
@@ -1495,7 +1933,9 @@ declare class PaymentsModule {
1495
1933
  getToken(id: string): Token | undefined;
1496
1934
  /**
1497
1935
  * Add a token
1498
- * @returns false if duplicate
1936
+ * Tokens are uniquely identified by (tokenId, stateHash) composite key.
1937
+ * Multiple historic states of the same token can coexist.
1938
+ * @returns false if exact duplicate (same tokenId AND same stateHash)
1499
1939
  */
1500
1940
  addToken(token: Token, skipHistory?: boolean): Promise<boolean>;
1501
1941
  /**
@@ -1646,12 +2086,11 @@ declare class PaymentsModule {
1646
2086
  * Detect if a string is an L3 address (not a nametag)
1647
2087
  * Returns true for: hex pubkeys (64+ chars), PROXY:, DIRECT: prefixed addresses
1648
2088
  */
1649
- private isL3Address;
1650
2089
  /**
1651
- * Resolve recipient to Nostr pubkey for messaging
1652
- * Supports: nametag (with or without @), hex pubkey
2090
+ * Resolve recipient to transport pubkey for messaging.
2091
+ * Uses pre-resolved PeerInfo if available, otherwise resolves via transport.
1653
2092
  */
1654
- private resolveRecipient;
2093
+ private resolveTransportPubkey;
1655
2094
  /**
1656
2095
  * Create SDK TransferCommitment for a token transfer
1657
2096
  */
@@ -1665,15 +2104,25 @@ declare class PaymentsModule {
1665
2104
  */
1666
2105
  private createDirectAddressFromPubkey;
1667
2106
  /**
1668
- * Resolve nametag to 33-byte compressed public key using resolveNametagInfo
1669
- * Returns null if nametag not found or publicKey not available
2107
+ * Resolve recipient to IAddress for L3 transfers.
2108
+ * Uses pre-resolved PeerInfo when available to avoid redundant network queries.
1670
2109
  */
1671
- private resolveNametagToPublicKey;
2110
+ private resolveRecipientAddress;
1672
2111
  /**
1673
- * Resolve recipient to IAddress for L3 transfers
1674
- * Supports: nametag (with or without @), PROXY:, DIRECT:, hex pubkey
2112
+ * Handle NOSTR-FIRST commitment-only transfer (recipient side)
2113
+ * This is called when receiving a transfer with only commitmentData and no proof yet.
2114
+ * We create the token as 'submitted', submit commitment (idempotent), and poll for proof.
1675
2115
  */
1676
- private resolveRecipientAddress;
2116
+ private handleCommitmentOnlyTransfer;
2117
+ /**
2118
+ * Shared finalization logic for received transfers.
2119
+ * Handles both PROXY (with nametag token + address validation) and DIRECT schemes.
2120
+ */
2121
+ private finalizeTransferToken;
2122
+ /**
2123
+ * Finalize a received token after proof is available
2124
+ */
2125
+ private finalizeReceivedToken;
1677
2126
  private handleIncomingTransfer;
1678
2127
  private archiveToken;
1679
2128
  private save;
@@ -1682,6 +2131,27 @@ declare class PaymentsModule {
1682
2131
  private loadOutbox;
1683
2132
  private createStorageData;
1684
2133
  private loadFromStorageData;
2134
+ /**
2135
+ * Submit commitment to aggregator and start background proof polling
2136
+ * (NOSTR-FIRST pattern: fire-and-forget submission)
2137
+ */
2138
+ private submitAndPollForProof;
2139
+ /**
2140
+ * Add a proof polling job to the queue
2141
+ */
2142
+ private addProofPollingJob;
2143
+ /**
2144
+ * Start the proof polling interval if not already running
2145
+ */
2146
+ private startProofPolling;
2147
+ /**
2148
+ * Stop the proof polling interval
2149
+ */
2150
+ private stopProofPolling;
2151
+ /**
2152
+ * Process all pending proof polling jobs
2153
+ */
2154
+ private processProofPollingQueue;
1685
2155
  private ensureInitialized;
1686
2156
  }
1687
2157
 
@@ -1828,6 +2298,8 @@ interface SphereCreateOptions {
1828
2298
  oracle: OracleProvider;
1829
2299
  /** L1 (ALPHA blockchain) configuration */
1830
2300
  l1?: L1Config;
2301
+ /** Optional price provider for fiat conversion */
2302
+ price?: PriceProvider;
1831
2303
  /**
1832
2304
  * Network type (mainnet, testnet, dev) - informational only.
1833
2305
  * Actual network configuration comes from provider URLs.
@@ -1847,6 +2319,8 @@ interface SphereLoadOptions {
1847
2319
  oracle: OracleProvider;
1848
2320
  /** L1 (ALPHA blockchain) configuration */
1849
2321
  l1?: L1Config;
2322
+ /** Optional price provider for fiat conversion */
2323
+ price?: PriceProvider;
1850
2324
  /**
1851
2325
  * Network type (mainnet, testnet, dev) - informational only.
1852
2326
  * Actual network configuration comes from provider URLs.
@@ -1880,6 +2354,8 @@ interface SphereImportOptions {
1880
2354
  oracle: OracleProvider;
1881
2355
  /** L1 (ALPHA blockchain) configuration */
1882
2356
  l1?: L1Config;
2357
+ /** Optional price provider for fiat conversion */
2358
+ price?: PriceProvider;
1883
2359
  }
1884
2360
  /** L1 (ALPHA blockchain) configuration */
1885
2361
  interface L1Config {
@@ -1910,6 +2386,8 @@ interface SphereInitOptions {
1910
2386
  nametag?: string;
1911
2387
  /** L1 (ALPHA blockchain) configuration */
1912
2388
  l1?: L1Config;
2389
+ /** Optional price provider for fiat conversion */
2390
+ price?: PriceProvider;
1913
2391
  /**
1914
2392
  * Network type (mainnet, testnet, dev) - informational only.
1915
2393
  * Actual network configuration comes from provider URLs.
@@ -1936,12 +2414,19 @@ declare class Sphere {
1936
2414
  private _derivationMode;
1937
2415
  private _basePath;
1938
2416
  private _currentAddressIndex;
1939
- /** Map of addressId -> (nametagIndex -> nametag). Supports multiple nametags per address (e.g., from Nostr recovery) */
2417
+ /** Registry of all tracked (activated) addresses, keyed by HD index */
2418
+ private _trackedAddresses;
2419
+ /** Reverse lookup: addressId -> HD index */
2420
+ private _addressIdToIndex;
2421
+ /** Nametag cache: addressId -> (nametagIndex -> nametag). Separate from tracked addresses. */
1940
2422
  private _addressNametags;
2423
+ /** Cached PROXY address (computed once when nametag is set) */
2424
+ private _cachedProxyAddress;
1941
2425
  private _storage;
1942
2426
  private _tokenStorageProviders;
1943
2427
  private _transport;
1944
2428
  private _oracle;
2429
+ private _priceProvider;
1945
2430
  private _payments;
1946
2431
  private _communications;
1947
2432
  private eventHandlers;
@@ -1989,10 +2474,28 @@ declare class Sphere {
1989
2474
  */
1990
2475
  static import(options: SphereImportOptions): Promise<Sphere>;
1991
2476
  /**
1992
- * Clear wallet data from storage
1993
- * Note: Token data is cleared via TokenStorageProvider, not here
2477
+ * Clear all SDK-owned wallet data from storage.
2478
+ *
2479
+ * Removes wallet keys, per-address data, and optionally token storage.
2480
+ * Does NOT affect application-level data stored outside the SDK.
2481
+ *
2482
+ * @param storageOrOptions - StorageProvider (backward compatible) or options object
2483
+ *
2484
+ * @example
2485
+ * // New usage (recommended) - clears wallet keys AND token data
2486
+ * await Sphere.clear({
2487
+ * storage: providers.storage,
2488
+ * tokenStorage: providers.tokenStorage,
2489
+ * });
2490
+ *
2491
+ * @example
2492
+ * // Legacy usage - clears only wallet keys
2493
+ * await Sphere.clear(storage);
1994
2494
  */
1995
- static clear(storage: StorageProvider): Promise<void>;
2495
+ static clear(storageOrOptions: StorageProvider | {
2496
+ storage: StorageProvider;
2497
+ tokenStorage?: TokenStorageProvider<TxfStorageDataBase>;
2498
+ }): Promise<void>;
1996
2499
  /**
1997
2500
  * Get current instance
1998
2501
  */
@@ -2041,6 +2544,10 @@ declare class Sphere {
2041
2544
  * Check if a token storage provider is registered
2042
2545
  */
2043
2546
  hasTokenStorageProvider(providerId: string): boolean;
2547
+ /**
2548
+ * Set or update the price provider after initialization
2549
+ */
2550
+ setPriceProvider(provider: PriceProvider): void;
2044
2551
  getTransport(): TransportProvider;
2045
2552
  getAggregator(): OracleProvider;
2046
2553
  /**
@@ -2224,14 +2731,40 @@ declare class Sphere {
2224
2731
  getNametagsForAddress(addressId?: string): Map<number, string> | undefined;
2225
2732
  /**
2226
2733
  * Get all registered address nametags
2227
- *
2734
+ * @deprecated Use getActiveAddresses() or getAllTrackedAddresses() instead
2228
2735
  * @returns Map of addressId to (nametagIndex -> nametag)
2229
2736
  */
2230
2737
  getAllAddressNametags(): Map<string, Map<number, string>>;
2231
2738
  /**
2232
- * Get current address identifier (DIRECT://xxx format)
2739
+ * Get all active (non-hidden) tracked addresses.
2740
+ * Returns addresses that have been activated through create, switchToAddress,
2741
+ * registerNametag, or nametag recovery.
2742
+ *
2743
+ * @returns Array of TrackedAddress entries sorted by index, excluding hidden ones
2233
2744
  */
2234
- private getCurrentAddressId;
2745
+ getActiveAddresses(): TrackedAddress[];
2746
+ /**
2747
+ * Get all tracked addresses, including hidden ones.
2748
+ *
2749
+ * @returns Array of all TrackedAddress entries sorted by index
2750
+ */
2751
+ getAllTrackedAddresses(): TrackedAddress[];
2752
+ /**
2753
+ * Get tracked address info by index.
2754
+ *
2755
+ * @param index - Address index
2756
+ * @returns TrackedAddress or undefined if not tracked
2757
+ */
2758
+ getTrackedAddress(index: number): TrackedAddress | undefined;
2759
+ /**
2760
+ * Set visibility of a tracked address.
2761
+ * Hidden addresses are not returned by getActiveAddresses() but remain tracked.
2762
+ *
2763
+ * @param index - Address index to hide/unhide
2764
+ * @param hidden - true to hide, false to show
2765
+ * @throws Error if address index is not tracked
2766
+ */
2767
+ setAddressHidden(index: number, hidden: boolean): Promise<void>;
2235
2768
  /**
2236
2769
  * Switch to a different address by index
2237
2770
  * This changes the active identity to the derived address at the specified index.
@@ -2277,6 +2810,12 @@ declare class Sphere {
2277
2810
  * ```
2278
2811
  */
2279
2812
  deriveAddress(index: number, isChange?: boolean): AddressInfo;
2813
+ /**
2814
+ * Internal address derivation without ensureReady() check.
2815
+ * Used during initialization (loadTrackedAddresses, ensureAddressTracked)
2816
+ * when _initialized is still false.
2817
+ */
2818
+ private _deriveAddressInternal;
2280
2819
  /**
2281
2820
  * Derive address at a full BIP32 path
2282
2821
  *
@@ -2336,6 +2875,21 @@ declare class Sphere {
2336
2875
  * @returns PROXY address string or undefined if no nametag
2337
2876
  */
2338
2877
  getProxyAddress(): string | undefined;
2878
+ /**
2879
+ * Resolve any identifier to full peer information.
2880
+ * Accepts @nametag, bare nametag, DIRECT://, PROXY://, L1 address, or transport pubkey.
2881
+ *
2882
+ * @example
2883
+ * ```ts
2884
+ * const peer = await sphere.resolve('@alice');
2885
+ * const peer = await sphere.resolve('DIRECT://...');
2886
+ * const peer = await sphere.resolve('alpha1...');
2887
+ * const peer = await sphere.resolve('ab12cd...'); // 64-char hex transport pubkey
2888
+ * ```
2889
+ */
2890
+ resolve(identifier: string): Promise<PeerInfo | null>;
2891
+ /** Compute and cache the PROXY address from the current nametag */
2892
+ private _updateCachedProxyAddress;
2339
2893
  /**
2340
2894
  * Register a nametag for the current active address
2341
2895
  * Each address can have its own independent nametag
@@ -2356,10 +2910,9 @@ declare class Sphere {
2356
2910
  */
2357
2911
  registerNametag(nametag: string): Promise<void>;
2358
2912
  /**
2359
- * Persist address nametags to storage
2360
- * Format: { "DIRECT://abc...xyz": { "0": "alice", "1": "alice2" }, ... }
2913
+ * Persist tracked addresses to storage (only minimal fields via StorageProvider)
2361
2914
  */
2362
- private persistAddressNametags;
2915
+ private persistTrackedAddresses;
2363
2916
  /**
2364
2917
  * Mint a nametag token on-chain (like Sphere wallet and lottery)
2365
2918
  * This creates the nametag token required for receiving tokens via PROXY addresses (@nametag)
@@ -2386,22 +2939,42 @@ declare class Sphere {
2386
2939
  */
2387
2940
  isNametagAvailable(nametag: string): Promise<boolean>;
2388
2941
  /**
2389
- * Load address nametags from storage
2390
- * Supports new format: { "DIRECT://abc...xyz": { "0": "alice" } }
2391
- * And legacy format: { "0": "alice" } (migrates to new format on save)
2942
+ * Load tracked addresses from storage.
2943
+ * Falls back to migrating from old ADDRESS_NAMETAGS format.
2944
+ */
2945
+ private loadTrackedAddresses;
2946
+ /**
2947
+ * Migrate from old ADDRESS_NAMETAGS format to tracked addresses.
2948
+ * Scans HD indices 0..19 to match addressIds from the old format.
2949
+ * Populates both _trackedAddresses and _addressNametags.
2950
+ */
2951
+ private migrateFromOldNametagFormat;
2952
+ /**
2953
+ * Ensure an address is tracked in the registry.
2954
+ * If not yet tracked, derives full info and creates the entry.
2955
+ */
2956
+ private ensureAddressTracked;
2957
+ /**
2958
+ * Persist nametag cache to storage.
2959
+ * Format: { addressId: { "0": "alice", "1": "alice2" } }
2960
+ */
2961
+ private persistAddressNametags;
2962
+ /**
2963
+ * Load nametag cache from storage.
2392
2964
  */
2393
2965
  private loadAddressNametags;
2394
2966
  /**
2395
- * Sync nametag with Nostr on wallet load
2396
- * If local nametag exists but not registered on Nostr, re-register it
2967
+ * Publish identity binding via transport.
2968
+ * Always publishes base identity (chainPubkey, l1Address, directAddress).
2969
+ * If nametag is set, also publishes nametag hash, proxy address, encrypted nametag.
2397
2970
  */
2398
- private syncNametagWithNostr;
2971
+ private syncIdentityWithTransport;
2399
2972
  /**
2400
- * Recover nametag from Nostr after wallet import
2973
+ * Recover nametag from transport after wallet import.
2401
2974
  * Searches for encrypted nametag events authored by this wallet's pubkey
2402
- * and decrypts them to restore the nametag association
2975
+ * and decrypts them to restore the nametag association.
2403
2976
  */
2404
- private recoverNametagFromNostr;
2977
+ private recoverNametagFromTransport;
2405
2978
  /**
2406
2979
  * Validate nametag format
2407
2980
  */