@unicitylabs/sphere-sdk 0.7.1-dev.2 → 0.7.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.
@@ -768,8 +768,22 @@ interface MultiAddressTransportMuxConfig {
768
768
  storage?: TransportStorageAdapter;
769
769
  /** Private key for the Mux's NostrClient identity. If provided, the Mux
770
770
  * authenticates as this key — required for relays that filter gift-wrap
771
- * event delivery to the recipient's subscription. */
771
+ * event delivery to the recipient's subscription.
772
+ * Ignored when {@link sharedNostrClient} is set. */
772
773
  identityPrivateKey?: Uint8Array;
774
+ /**
775
+ * Optional pre-existing {@link NostrClient} to reuse instead of opening a
776
+ * fresh WebSocket per relay (#123). When set, the Mux skips both the
777
+ * {@code new NostrClient(...)} construction and {@code connect()} — it
778
+ * only registers subscription/connection listeners on the shared
779
+ * client. The Mux does NOT take ownership: its {@code disconnect()}
780
+ * leaves the client connected, since the caller (e.g. the original
781
+ * {@link NostrTransportProvider}) still uses it for resolve calls.
782
+ *
783
+ * Use a getter when the client may be created lazily (e.g. before the
784
+ * provider has connected).
785
+ */
786
+ sharedNostrClient?: NostrClient | null | (() => NostrClient | null);
773
787
  }
774
788
  declare class MultiAddressTransportMux {
775
789
  private config;
@@ -783,14 +797,14 @@ declare class MultiAddressTransportMux {
783
797
  private chatSubscriptionId;
784
798
  private chatEoseFired;
785
799
  private resubscribeTimer;
786
- private lastWalletEventAt;
787
- private lastChatEventAt;
788
- private healthCheckTimer;
789
800
  private chatEoseHandlers;
790
801
  private processedEventIds;
791
802
  private static readonly MAX_PROCESSED_IDS;
792
803
  private eventCallbacks;
793
804
  private readonly identityPrivateKey;
805
+ private readonly sharedNostrClientGetter;
806
+ private usingSharedClient;
807
+ private connectionListener;
794
808
  constructor(config: MultiAddressTransportMuxConfig);
795
809
  /**
796
810
  * Add an address to the multiplexer.
@@ -815,6 +829,58 @@ declare class MultiAddressTransportMux {
815
829
  connect(): Promise<void>;
816
830
  disconnect(): Promise<void>;
817
831
  isConnected(): boolean;
832
+ /**
833
+ * Build the connection listener used by both {@link connect} and
834
+ * {@link rebindToSharedClient}.
835
+ *
836
+ * Behavioral notes:
837
+ * - When the Mux is sharing a {@link NostrClient} with the host
838
+ * transport (#123), we deliberately do NOT emit
839
+ * {@code transport:connected} / {@code transport:reconnecting} here
840
+ * — the host transport's own listener already emits those for the
841
+ * same socket event. Re-subscribing after a reconnect IS still our
842
+ * responsibility, since the host has
843
+ * {@code suppressSubscriptions()}'d its own filters.
844
+ * - {@code onConnect} does not emit {@code transport:connected}.
845
+ * The SDK only fires {@code onConnect} on the initial socket
846
+ * connection (subsequent reconnects use {@code onReconnected}),
847
+ * and {@link connect()}'s bottom already emits
848
+ * {@code transport:connected} once that returns. Emitting here too
849
+ * would double-fire on every initial connect.
850
+ * - Each callback bails out early when the Mux is not in an active
851
+ * state ({@code disconnected} / {@code error}). Listeners are
852
+ * removed on {@code disconnect()} before the callback can fire,
853
+ * so this guard is mainly defense-in-depth against any in-flight
854
+ * callback that lands during teardown — but having it at the top
855
+ * means we never emit a misleading {@code transport:connected}
856
+ * from a Mux that has already torn down.
857
+ */
858
+ private buildConnectionListener;
859
+ /**
860
+ * Re-attach to a freshly-created shared NostrClient.
861
+ *
862
+ * Call this after the host (e.g. {@link NostrTransportProvider}) has
863
+ * recreated its NostrClient — typically because the wallet's active
864
+ * identity changed and the SDK's NostrClient does not support
865
+ * changing identity at runtime. The previous client has already
866
+ * been disconnected by the host, so its server-side subscriptions
867
+ * are gone — we just adopt the new client and re-issue our own.
868
+ *
869
+ * The caller is responsible for ordering: by the time rebind runs,
870
+ * the host transport's new NostrClient must already be created and
871
+ * connected. In Sphere this is guaranteed because we await
872
+ * {@code transport.setIdentity()} before calling rebind.
873
+ *
874
+ * Returns silently in two cases that are not caller errors:
875
+ * - the Mux owns its own client (not sharing) — nothing to rebind
876
+ * - the shared client reference hasn't changed (rebind is a no-op)
877
+ *
878
+ * Throws otherwise (rather than silently no-op'ing) so a wiring
879
+ * mistake — for instance, calling rebind before the host's new
880
+ * client is ready — surfaces immediately instead of leaving the
881
+ * Mux pinned to a stale client.
882
+ */
883
+ rebindToSharedClient(): Promise<void>;
818
884
  /**
819
885
  * One-shot fetch of pending events from the relay.
820
886
  * Creates a temporary subscription, waits for EOSE (or timeout),
@@ -835,7 +901,6 @@ declare class MultiAddressTransportMux {
835
901
  * Called whenever addresses are added/removed.
836
902
  */
837
903
  private updateSubscriptions;
838
- private startHealthCheck;
839
904
  /**
840
905
  * Schedule a re-subscription after a relay-initiated subscription closure.
841
906
  * Debounced: if both wallet and chat subscriptions fire onError in quick
@@ -4248,6 +4313,42 @@ declare class PaymentsModule {
4248
4313
  * @returns MintNametagResult with success status and token if successful
4249
4314
  */
4250
4315
  mintNametag(nametag: string): Promise<MintNametagResult>;
4316
+ /**
4317
+ * Mint a fungible token directly to this wallet (genesis mint).
4318
+ *
4319
+ * Useful for test setups that need to seed a wallet with specific token
4320
+ * balances WITHOUT depending on the testnet faucet HTTP service. The
4321
+ * resulting token has the canonical CoinId bytes (passed in `coinIdHex`)
4322
+ * — when those bytes match a registered symbol in the TokenRegistry,
4323
+ * the token shows up under the symbol's name (e.g. "UCT"). There is no
4324
+ * cryptographic restriction on which key may issue a given CoinId; the
4325
+ * aggregator records the mint regardless of issuer identity.
4326
+ *
4327
+ * The flow:
4328
+ * 1. Generate a random TokenId.
4329
+ * 2. Build TokenCoinData with [(coinId, amount)].
4330
+ * 3. Build MintTransactionData with recipient = self (UnmaskedPredicate
4331
+ * from this wallet's signing service).
4332
+ * 4. Submit MintCommitment to the aggregator.
4333
+ * 5. Wait for the inclusion proof.
4334
+ * 6. Construct an SDK Token via Token.mint().
4335
+ * 7. Convert to wallet Token format and call addToken().
4336
+ *
4337
+ * @param coinIdHex - 64-char lowercase hex CoinId. Must match the bytes
4338
+ * used by the registered symbol if you want the wallet to recognize
4339
+ * the token as that symbol (e.g. UCT's coinId from the public registry).
4340
+ * @param amount - Amount in smallest units (multiply by 10^decimals
4341
+ * when converting from human values).
4342
+ * @returns Result with the resulting wallet Token and its on-chain id.
4343
+ */
4344
+ mintFungibleToken(coinIdHex: string, amount: bigint): Promise<{
4345
+ success: true;
4346
+ token: Token;
4347
+ tokenId: string;
4348
+ } | {
4349
+ success: false;
4350
+ error: string;
4351
+ }>;
4251
4352
  /**
4252
4353
  * Check if a nametag is available for minting
4253
4354
  * @param nametag - The nametag to check (e.g., "alice" or "@alice")
@@ -4727,6 +4828,24 @@ declare class AccountingModule {
4727
4828
  private dirtyLedgerEntries;
4728
4829
  /** Count of unknown (not in invoiceTermsCache) invoice IDs in the ledger. */
4729
4830
  private unknownLedgerCount;
4831
+ /**
4832
+ * Per-unknown-invoice first-seen timestamp for TTL eviction.
4833
+ *
4834
+ * W1 (steelman round-4): without TTL, an attacker who can deliver 500
4835
+ * inbound transfers with synthesized memo invoiceIds permanently exhausts
4836
+ * the unknown-ledger cap, after which legitimate orphan transfers (out-of-
4837
+ * order delivery for real swaps) are silently dropped at the cap-check.
4838
+ *
4839
+ * Round-5 perf: gated by `unknownLedgerNextSweepMs` to amortize the
4840
+ * sweep cost. The naive every-call sweep is O(N) where N=cap=500;
4841
+ * combined with the per-token cleanup loop inside the sweep it became
4842
+ * O(N×M) on every transfer under flood. Now we sweep at most every
4843
+ * `UNKNOWN_LEDGER_SWEEP_INTERVAL_MS` (60s) UNLESS the cap is currently
4844
+ * full, in which case we sweep on each call (the only path that can
4845
+ * actually drop a legitimate orphan).
4846
+ */
4847
+ private unknownLedgerFirstSeen;
4848
+ private unknownLedgerNextSweepMs;
4730
4849
  /** W17: Tracks whether tokenScanState has been mutated since last flush. */
4731
4850
  private tokenScanDirty;
4732
4851
  /** W2 fix: Serialization guard for _flushDirtyLedgerEntries. */
@@ -4911,6 +5030,21 @@ declare class AccountingModule {
4911
5030
  * @throws {SphereError} `NOT_INITIALIZED` — module not initialized.
4912
5031
  */
4913
5032
  getInvoice(invoiceId: string): InvoiceRef | null;
5033
+ /**
5034
+ * Return the set of token IDs that are currently linked to the given
5035
+ * invoice. Populated by both the on-chain `_processTokenTransactions`
5036
+ * path (tokens with `inv:` references) and the transport-memo orphan
5037
+ * buffering path in `_handleIncomingTransfer`.
5038
+ *
5039
+ * Used by callers that want to scope per-invoice operations (e.g.
5040
+ * SwapModule.verifyPayout's L3 validation) to only the tokens that
5041
+ * cover this invoice — avoiding false negatives when the wallet
5042
+ * contains unrelated tokens of the same currency in unconfirmed or
5043
+ * spent state.
5044
+ *
5045
+ * Returns an empty set if no tokens are currently linked.
5046
+ */
5047
+ getTokenIdsForInvoice(invoiceId: string): Set<string>;
4914
5048
  /**
4915
5049
  * Explicitly close an invoice. Only target parties may close (§8.3).
4916
5050
  *
@@ -5258,6 +5392,35 @@ declare class AccountingModule {
5258
5392
  * await and the null assignment, so we loop until the field is null.
5259
5393
  */
5260
5394
  private _drainFlushPromise;
5395
+ /**
5396
+ * Synchronously persist any pending provisional ledger entry for `invoiceId`
5397
+ * before returning to the caller. Used by `payInvoice` and
5398
+ * `returnInvoicePayment` to make the in-memory provisional entry durable
5399
+ * inside the same per-invoice gate that wrote it, closing the
5400
+ * crash-mid-conclude race that produces over-coverage on receivers.
5401
+ *
5402
+ * Implementation:
5403
+ * 1. Schedule a flush via the existing `_flushPromise` chain (so
5404
+ * concurrent `_handleTokenChange` callers waiting on the chain
5405
+ * observe ours as part of the sequence).
5406
+ * 2. Await OUR flush directly — NOT `_drainFlushPromise()`, which would
5407
+ * spin while concurrent token changes keep extending the chain and
5408
+ * hold the per-invoice gate for an unbounded number of additional
5409
+ * flushes. We only need OUR provisional entry durable.
5410
+ * 3. `_flushDirtyLedgerEntries` swallows per-invoice `storage.set`
5411
+ * rejections internally (sets a local `step1Failed` flag), leaving
5412
+ * the dirty entry on the set without re-throwing. So we post-check
5413
+ * `dirtyLedgerEntries.has(invoiceId)` and throw a `STORAGE_ERROR`
5414
+ * `SphereError` if our entry is still dirty — propagating to the
5415
+ * caller so they learn about the durability failure rather than
5416
+ * receiving a silent "success" return that lies on disk.
5417
+ *
5418
+ * @param invoiceId The invoice whose provisional entry must be durable.
5419
+ * @param callContext Used in the error message so the caller is named
5420
+ * ('payInvoice' / 'returnInvoicePayment') without
5421
+ * forcing a stack-trace inspection.
5422
+ */
5423
+ private _persistProvisionalAndVerify;
5261
5424
  /**
5262
5425
  * Handle an incoming transfer event from PaymentsModule (§6.2).
5263
5426
  *
@@ -6328,6 +6491,23 @@ type LegacyFileType = 'dat' | 'txt' | 'json' | 'mnemonic' | 'unknown';
6328
6491
  */
6329
6492
  type DecryptionProgressCallback = (iteration: number, total: number) => Promise<void> | void;
6330
6493
 
6494
+ /**
6495
+ * Compute the Unicity L3 DIRECT:// address that corresponds to a given
6496
+ * compressed secp256k1 public key.
6497
+ *
6498
+ * Deterministic — the underlying primitive `UnmaskedPredicateReference.create`
6499
+ * only uses the public key, so the private key is never needed. This lets
6500
+ * a backend trust only one thing from the client (the chain pubkey, whose
6501
+ * ownership the client proves via signature) and derive everything else
6502
+ * locally. Closes the entire class of "client claims an identifier the
6503
+ * server can't verify" bugs at the API level.
6504
+ *
6505
+ * @param chainPubkey 33-byte compressed secp256k1 pubkey, hex-encoded
6506
+ * (66 chars, leading 02 or 03).
6507
+ * @throws if chainPubkey doesn't match the compressed-secp256k1 format.
6508
+ */
6509
+ declare function computeDirectAddressFromChainPubkey(chainPubkey: string): Promise<string>;
6510
+
6331
6511
  declare function isValidNametag(nametag: string): boolean;
6332
6512
 
6333
6513
  /** Steps reported by the onProgress callback during wallet init/create/load/import */
@@ -6569,6 +6749,7 @@ interface SphereInitResult {
6569
6749
  /** Generated mnemonic (only if autoGenerate was used) */
6570
6750
  generatedMnemonic?: string;
6571
6751
  }
6752
+
6572
6753
  /**
6573
6754
  * Holds all per-address module instances.
6574
6755
  * Each HD address gets its own set so modules can run independently in background.
@@ -7815,4 +7996,4 @@ interface CheckNetworkHealthOptions {
7815
7996
  */
7816
7997
  declare function checkNetworkHealth(network?: NetworkType, options?: CheckNetworkHealthOptions): Promise<NetworkHealthResult>;
7817
7998
 
7818
- export { type AddressInfo, type AddressModuleSet, CHARSET, type CheckNetworkHealthOptions, CurrencyUtils, DEFAULT_DERIVATION_PATH, DEFAULT_TOKEN_DECIMALS, type DerivedKey, type DiscoverAddressProgress, type DiscoverAddressesOptions, type DiscoverAddressesResult, type DiscoveredAddress, type EncryptedData, type EncryptionOptions, type InitProgress, type InitProgressCallback, type InitProgressStep, type KeyPair, type L1Config, type LogHandler, type LogLevel, type LoggerConfig, type MasterKey, SIGN_MESSAGE_PREFIX, type ScanAddressProgress, type ScanAddressesOptions, type ScanAddressesResult, type ScannedAddressResult, Sphere, type SphereCreateOptions, SphereError, type SphereErrorCode, type SphereImportOptions, type SphereInitOptions, type SphereInitResult, type SphereLoadOptions, base58Decode, base58Encode, bytesToHex, checkNetworkHealth, computeHash160, convertBits, createAddress, createBech32, createKeyPair, createSphere, decodeBech32, decrypt, decryptJson, decryptMnemonic, decryptSimple, decryptWithSalt, deriveAddressInfo, deriveChildKey, deriveKeyAtPath, deserializeEncrypted, discoverAddressesImpl, doubleSha256, ec, encodeBech32, encrypt, encryptMnemonic, encryptSimple, entropyToMnemonic, extractFromText, findPattern, formatAmount, generateAddressInfo, generateMasterKey, generateMnemonic, generateRandomKey, getAddressHrp, getPublicKey, getSphere, hash160, hash160ToBytes, hashSignMessage, hexToBytes, identityFromMnemonic, identityFromMnemonicSync, importSphere, initSphere, isEncryptedData, isSphereError, isValidBech32, isValidNametag, isValidPrivateKey, loadSphere, logger, mnemonicToEntropy, mnemonicToSeed, mnemonicToSeedSync, privateKeyToAddressInfo, publicKeyToAddress, randomBytes, randomHex, randomUUID, ripemd160, scanAddressesImpl, serializeEncrypted, sha256, signMessage, sleep, sphereExists, toHumanReadable, toSmallestUnit, validateMnemonic, verifySignedMessage };
7999
+ export { type AddressInfo, type AddressModuleSet, CHARSET, type CheckNetworkHealthOptions, CurrencyUtils, DEFAULT_DERIVATION_PATH, DEFAULT_TOKEN_DECIMALS, type DerivedKey, type DiscoverAddressProgress, type DiscoverAddressesOptions, type DiscoverAddressesResult, type DiscoveredAddress, type EncryptedData, type EncryptionOptions, type InitProgress, type InitProgressCallback, type InitProgressStep, type KeyPair, type L1Config, type LogHandler, type LogLevel, type LoggerConfig, type MasterKey, SIGN_MESSAGE_PREFIX, type ScanAddressProgress, type ScanAddressesOptions, type ScanAddressesResult, type ScannedAddressResult, Sphere, type SphereCreateOptions, SphereError, type SphereErrorCode, type SphereImportOptions, type SphereInitOptions, type SphereInitResult, type SphereLoadOptions, base58Decode, base58Encode, bytesToHex, checkNetworkHealth, computeDirectAddressFromChainPubkey, computeHash160, convertBits, createAddress, createBech32, createKeyPair, createSphere, decodeBech32, decrypt, decryptJson, decryptMnemonic, decryptSimple, decryptWithSalt, deriveAddressInfo, deriveChildKey, deriveKeyAtPath, deserializeEncrypted, discoverAddressesImpl, doubleSha256, ec, encodeBech32, encrypt, encryptMnemonic, encryptSimple, entropyToMnemonic, extractFromText, findPattern, formatAmount, generateAddressInfo, generateMasterKey, generateMnemonic, generateRandomKey, getAddressHrp, getPublicKey, getSphere, hash160, hash160ToBytes, hashSignMessage, hexToBytes, identityFromMnemonic, identityFromMnemonicSync, importSphere, initSphere, isEncryptedData, isSphereError, isValidBech32, isValidNametag, isValidPrivateKey, loadSphere, logger, mnemonicToEntropy, mnemonicToSeed, mnemonicToSeedSync, privateKeyToAddressInfo, publicKeyToAddress, randomBytes, randomHex, randomUUID, ripemd160, scanAddressesImpl, serializeEncrypted, sha256, signMessage, sleep, sphereExists, toHumanReadable, toSmallestUnit, validateMnemonic, verifySignedMessage };
@@ -768,8 +768,22 @@ interface MultiAddressTransportMuxConfig {
768
768
  storage?: TransportStorageAdapter;
769
769
  /** Private key for the Mux's NostrClient identity. If provided, the Mux
770
770
  * authenticates as this key — required for relays that filter gift-wrap
771
- * event delivery to the recipient's subscription. */
771
+ * event delivery to the recipient's subscription.
772
+ * Ignored when {@link sharedNostrClient} is set. */
772
773
  identityPrivateKey?: Uint8Array;
774
+ /**
775
+ * Optional pre-existing {@link NostrClient} to reuse instead of opening a
776
+ * fresh WebSocket per relay (#123). When set, the Mux skips both the
777
+ * {@code new NostrClient(...)} construction and {@code connect()} — it
778
+ * only registers subscription/connection listeners on the shared
779
+ * client. The Mux does NOT take ownership: its {@code disconnect()}
780
+ * leaves the client connected, since the caller (e.g. the original
781
+ * {@link NostrTransportProvider}) still uses it for resolve calls.
782
+ *
783
+ * Use a getter when the client may be created lazily (e.g. before the
784
+ * provider has connected).
785
+ */
786
+ sharedNostrClient?: NostrClient | null | (() => NostrClient | null);
773
787
  }
774
788
  declare class MultiAddressTransportMux {
775
789
  private config;
@@ -783,14 +797,14 @@ declare class MultiAddressTransportMux {
783
797
  private chatSubscriptionId;
784
798
  private chatEoseFired;
785
799
  private resubscribeTimer;
786
- private lastWalletEventAt;
787
- private lastChatEventAt;
788
- private healthCheckTimer;
789
800
  private chatEoseHandlers;
790
801
  private processedEventIds;
791
802
  private static readonly MAX_PROCESSED_IDS;
792
803
  private eventCallbacks;
793
804
  private readonly identityPrivateKey;
805
+ private readonly sharedNostrClientGetter;
806
+ private usingSharedClient;
807
+ private connectionListener;
794
808
  constructor(config: MultiAddressTransportMuxConfig);
795
809
  /**
796
810
  * Add an address to the multiplexer.
@@ -815,6 +829,58 @@ declare class MultiAddressTransportMux {
815
829
  connect(): Promise<void>;
816
830
  disconnect(): Promise<void>;
817
831
  isConnected(): boolean;
832
+ /**
833
+ * Build the connection listener used by both {@link connect} and
834
+ * {@link rebindToSharedClient}.
835
+ *
836
+ * Behavioral notes:
837
+ * - When the Mux is sharing a {@link NostrClient} with the host
838
+ * transport (#123), we deliberately do NOT emit
839
+ * {@code transport:connected} / {@code transport:reconnecting} here
840
+ * — the host transport's own listener already emits those for the
841
+ * same socket event. Re-subscribing after a reconnect IS still our
842
+ * responsibility, since the host has
843
+ * {@code suppressSubscriptions()}'d its own filters.
844
+ * - {@code onConnect} does not emit {@code transport:connected}.
845
+ * The SDK only fires {@code onConnect} on the initial socket
846
+ * connection (subsequent reconnects use {@code onReconnected}),
847
+ * and {@link connect()}'s bottom already emits
848
+ * {@code transport:connected} once that returns. Emitting here too
849
+ * would double-fire on every initial connect.
850
+ * - Each callback bails out early when the Mux is not in an active
851
+ * state ({@code disconnected} / {@code error}). Listeners are
852
+ * removed on {@code disconnect()} before the callback can fire,
853
+ * so this guard is mainly defense-in-depth against any in-flight
854
+ * callback that lands during teardown — but having it at the top
855
+ * means we never emit a misleading {@code transport:connected}
856
+ * from a Mux that has already torn down.
857
+ */
858
+ private buildConnectionListener;
859
+ /**
860
+ * Re-attach to a freshly-created shared NostrClient.
861
+ *
862
+ * Call this after the host (e.g. {@link NostrTransportProvider}) has
863
+ * recreated its NostrClient — typically because the wallet's active
864
+ * identity changed and the SDK's NostrClient does not support
865
+ * changing identity at runtime. The previous client has already
866
+ * been disconnected by the host, so its server-side subscriptions
867
+ * are gone — we just adopt the new client and re-issue our own.
868
+ *
869
+ * The caller is responsible for ordering: by the time rebind runs,
870
+ * the host transport's new NostrClient must already be created and
871
+ * connected. In Sphere this is guaranteed because we await
872
+ * {@code transport.setIdentity()} before calling rebind.
873
+ *
874
+ * Returns silently in two cases that are not caller errors:
875
+ * - the Mux owns its own client (not sharing) — nothing to rebind
876
+ * - the shared client reference hasn't changed (rebind is a no-op)
877
+ *
878
+ * Throws otherwise (rather than silently no-op'ing) so a wiring
879
+ * mistake — for instance, calling rebind before the host's new
880
+ * client is ready — surfaces immediately instead of leaving the
881
+ * Mux pinned to a stale client.
882
+ */
883
+ rebindToSharedClient(): Promise<void>;
818
884
  /**
819
885
  * One-shot fetch of pending events from the relay.
820
886
  * Creates a temporary subscription, waits for EOSE (or timeout),
@@ -835,7 +901,6 @@ declare class MultiAddressTransportMux {
835
901
  * Called whenever addresses are added/removed.
836
902
  */
837
903
  private updateSubscriptions;
838
- private startHealthCheck;
839
904
  /**
840
905
  * Schedule a re-subscription after a relay-initiated subscription closure.
841
906
  * Debounced: if both wallet and chat subscriptions fire onError in quick
@@ -4248,6 +4313,42 @@ declare class PaymentsModule {
4248
4313
  * @returns MintNametagResult with success status and token if successful
4249
4314
  */
4250
4315
  mintNametag(nametag: string): Promise<MintNametagResult>;
4316
+ /**
4317
+ * Mint a fungible token directly to this wallet (genesis mint).
4318
+ *
4319
+ * Useful for test setups that need to seed a wallet with specific token
4320
+ * balances WITHOUT depending on the testnet faucet HTTP service. The
4321
+ * resulting token has the canonical CoinId bytes (passed in `coinIdHex`)
4322
+ * — when those bytes match a registered symbol in the TokenRegistry,
4323
+ * the token shows up under the symbol's name (e.g. "UCT"). There is no
4324
+ * cryptographic restriction on which key may issue a given CoinId; the
4325
+ * aggregator records the mint regardless of issuer identity.
4326
+ *
4327
+ * The flow:
4328
+ * 1. Generate a random TokenId.
4329
+ * 2. Build TokenCoinData with [(coinId, amount)].
4330
+ * 3. Build MintTransactionData with recipient = self (UnmaskedPredicate
4331
+ * from this wallet's signing service).
4332
+ * 4. Submit MintCommitment to the aggregator.
4333
+ * 5. Wait for the inclusion proof.
4334
+ * 6. Construct an SDK Token via Token.mint().
4335
+ * 7. Convert to wallet Token format and call addToken().
4336
+ *
4337
+ * @param coinIdHex - 64-char lowercase hex CoinId. Must match the bytes
4338
+ * used by the registered symbol if you want the wallet to recognize
4339
+ * the token as that symbol (e.g. UCT's coinId from the public registry).
4340
+ * @param amount - Amount in smallest units (multiply by 10^decimals
4341
+ * when converting from human values).
4342
+ * @returns Result with the resulting wallet Token and its on-chain id.
4343
+ */
4344
+ mintFungibleToken(coinIdHex: string, amount: bigint): Promise<{
4345
+ success: true;
4346
+ token: Token;
4347
+ tokenId: string;
4348
+ } | {
4349
+ success: false;
4350
+ error: string;
4351
+ }>;
4251
4352
  /**
4252
4353
  * Check if a nametag is available for minting
4253
4354
  * @param nametag - The nametag to check (e.g., "alice" or "@alice")
@@ -4727,6 +4828,24 @@ declare class AccountingModule {
4727
4828
  private dirtyLedgerEntries;
4728
4829
  /** Count of unknown (not in invoiceTermsCache) invoice IDs in the ledger. */
4729
4830
  private unknownLedgerCount;
4831
+ /**
4832
+ * Per-unknown-invoice first-seen timestamp for TTL eviction.
4833
+ *
4834
+ * W1 (steelman round-4): without TTL, an attacker who can deliver 500
4835
+ * inbound transfers with synthesized memo invoiceIds permanently exhausts
4836
+ * the unknown-ledger cap, after which legitimate orphan transfers (out-of-
4837
+ * order delivery for real swaps) are silently dropped at the cap-check.
4838
+ *
4839
+ * Round-5 perf: gated by `unknownLedgerNextSweepMs` to amortize the
4840
+ * sweep cost. The naive every-call sweep is O(N) where N=cap=500;
4841
+ * combined with the per-token cleanup loop inside the sweep it became
4842
+ * O(N×M) on every transfer under flood. Now we sweep at most every
4843
+ * `UNKNOWN_LEDGER_SWEEP_INTERVAL_MS` (60s) UNLESS the cap is currently
4844
+ * full, in which case we sweep on each call (the only path that can
4845
+ * actually drop a legitimate orphan).
4846
+ */
4847
+ private unknownLedgerFirstSeen;
4848
+ private unknownLedgerNextSweepMs;
4730
4849
  /** W17: Tracks whether tokenScanState has been mutated since last flush. */
4731
4850
  private tokenScanDirty;
4732
4851
  /** W2 fix: Serialization guard for _flushDirtyLedgerEntries. */
@@ -4911,6 +5030,21 @@ declare class AccountingModule {
4911
5030
  * @throws {SphereError} `NOT_INITIALIZED` — module not initialized.
4912
5031
  */
4913
5032
  getInvoice(invoiceId: string): InvoiceRef | null;
5033
+ /**
5034
+ * Return the set of token IDs that are currently linked to the given
5035
+ * invoice. Populated by both the on-chain `_processTokenTransactions`
5036
+ * path (tokens with `inv:` references) and the transport-memo orphan
5037
+ * buffering path in `_handleIncomingTransfer`.
5038
+ *
5039
+ * Used by callers that want to scope per-invoice operations (e.g.
5040
+ * SwapModule.verifyPayout's L3 validation) to only the tokens that
5041
+ * cover this invoice — avoiding false negatives when the wallet
5042
+ * contains unrelated tokens of the same currency in unconfirmed or
5043
+ * spent state.
5044
+ *
5045
+ * Returns an empty set if no tokens are currently linked.
5046
+ */
5047
+ getTokenIdsForInvoice(invoiceId: string): Set<string>;
4914
5048
  /**
4915
5049
  * Explicitly close an invoice. Only target parties may close (§8.3).
4916
5050
  *
@@ -5258,6 +5392,35 @@ declare class AccountingModule {
5258
5392
  * await and the null assignment, so we loop until the field is null.
5259
5393
  */
5260
5394
  private _drainFlushPromise;
5395
+ /**
5396
+ * Synchronously persist any pending provisional ledger entry for `invoiceId`
5397
+ * before returning to the caller. Used by `payInvoice` and
5398
+ * `returnInvoicePayment` to make the in-memory provisional entry durable
5399
+ * inside the same per-invoice gate that wrote it, closing the
5400
+ * crash-mid-conclude race that produces over-coverage on receivers.
5401
+ *
5402
+ * Implementation:
5403
+ * 1. Schedule a flush via the existing `_flushPromise` chain (so
5404
+ * concurrent `_handleTokenChange` callers waiting on the chain
5405
+ * observe ours as part of the sequence).
5406
+ * 2. Await OUR flush directly — NOT `_drainFlushPromise()`, which would
5407
+ * spin while concurrent token changes keep extending the chain and
5408
+ * hold the per-invoice gate for an unbounded number of additional
5409
+ * flushes. We only need OUR provisional entry durable.
5410
+ * 3. `_flushDirtyLedgerEntries` swallows per-invoice `storage.set`
5411
+ * rejections internally (sets a local `step1Failed` flag), leaving
5412
+ * the dirty entry on the set without re-throwing. So we post-check
5413
+ * `dirtyLedgerEntries.has(invoiceId)` and throw a `STORAGE_ERROR`
5414
+ * `SphereError` if our entry is still dirty — propagating to the
5415
+ * caller so they learn about the durability failure rather than
5416
+ * receiving a silent "success" return that lies on disk.
5417
+ *
5418
+ * @param invoiceId The invoice whose provisional entry must be durable.
5419
+ * @param callContext Used in the error message so the caller is named
5420
+ * ('payInvoice' / 'returnInvoicePayment') without
5421
+ * forcing a stack-trace inspection.
5422
+ */
5423
+ private _persistProvisionalAndVerify;
5261
5424
  /**
5262
5425
  * Handle an incoming transfer event from PaymentsModule (§6.2).
5263
5426
  *
@@ -6328,6 +6491,23 @@ type LegacyFileType = 'dat' | 'txt' | 'json' | 'mnemonic' | 'unknown';
6328
6491
  */
6329
6492
  type DecryptionProgressCallback = (iteration: number, total: number) => Promise<void> | void;
6330
6493
 
6494
+ /**
6495
+ * Compute the Unicity L3 DIRECT:// address that corresponds to a given
6496
+ * compressed secp256k1 public key.
6497
+ *
6498
+ * Deterministic — the underlying primitive `UnmaskedPredicateReference.create`
6499
+ * only uses the public key, so the private key is never needed. This lets
6500
+ * a backend trust only one thing from the client (the chain pubkey, whose
6501
+ * ownership the client proves via signature) and derive everything else
6502
+ * locally. Closes the entire class of "client claims an identifier the
6503
+ * server can't verify" bugs at the API level.
6504
+ *
6505
+ * @param chainPubkey 33-byte compressed secp256k1 pubkey, hex-encoded
6506
+ * (66 chars, leading 02 or 03).
6507
+ * @throws if chainPubkey doesn't match the compressed-secp256k1 format.
6508
+ */
6509
+ declare function computeDirectAddressFromChainPubkey(chainPubkey: string): Promise<string>;
6510
+
6331
6511
  declare function isValidNametag(nametag: string): boolean;
6332
6512
 
6333
6513
  /** Steps reported by the onProgress callback during wallet init/create/load/import */
@@ -6569,6 +6749,7 @@ interface SphereInitResult {
6569
6749
  /** Generated mnemonic (only if autoGenerate was used) */
6570
6750
  generatedMnemonic?: string;
6571
6751
  }
6752
+
6572
6753
  /**
6573
6754
  * Holds all per-address module instances.
6574
6755
  * Each HD address gets its own set so modules can run independently in background.
@@ -7815,4 +7996,4 @@ interface CheckNetworkHealthOptions {
7815
7996
  */
7816
7997
  declare function checkNetworkHealth(network?: NetworkType, options?: CheckNetworkHealthOptions): Promise<NetworkHealthResult>;
7817
7998
 
7818
- export { type AddressInfo, type AddressModuleSet, CHARSET, type CheckNetworkHealthOptions, CurrencyUtils, DEFAULT_DERIVATION_PATH, DEFAULT_TOKEN_DECIMALS, type DerivedKey, type DiscoverAddressProgress, type DiscoverAddressesOptions, type DiscoverAddressesResult, type DiscoveredAddress, type EncryptedData, type EncryptionOptions, type InitProgress, type InitProgressCallback, type InitProgressStep, type KeyPair, type L1Config, type LogHandler, type LogLevel, type LoggerConfig, type MasterKey, SIGN_MESSAGE_PREFIX, type ScanAddressProgress, type ScanAddressesOptions, type ScanAddressesResult, type ScannedAddressResult, Sphere, type SphereCreateOptions, SphereError, type SphereErrorCode, type SphereImportOptions, type SphereInitOptions, type SphereInitResult, type SphereLoadOptions, base58Decode, base58Encode, bytesToHex, checkNetworkHealth, computeHash160, convertBits, createAddress, createBech32, createKeyPair, createSphere, decodeBech32, decrypt, decryptJson, decryptMnemonic, decryptSimple, decryptWithSalt, deriveAddressInfo, deriveChildKey, deriveKeyAtPath, deserializeEncrypted, discoverAddressesImpl, doubleSha256, ec, encodeBech32, encrypt, encryptMnemonic, encryptSimple, entropyToMnemonic, extractFromText, findPattern, formatAmount, generateAddressInfo, generateMasterKey, generateMnemonic, generateRandomKey, getAddressHrp, getPublicKey, getSphere, hash160, hash160ToBytes, hashSignMessage, hexToBytes, identityFromMnemonic, identityFromMnemonicSync, importSphere, initSphere, isEncryptedData, isSphereError, isValidBech32, isValidNametag, isValidPrivateKey, loadSphere, logger, mnemonicToEntropy, mnemonicToSeed, mnemonicToSeedSync, privateKeyToAddressInfo, publicKeyToAddress, randomBytes, randomHex, randomUUID, ripemd160, scanAddressesImpl, serializeEncrypted, sha256, signMessage, sleep, sphereExists, toHumanReadable, toSmallestUnit, validateMnemonic, verifySignedMessage };
7999
+ export { type AddressInfo, type AddressModuleSet, CHARSET, type CheckNetworkHealthOptions, CurrencyUtils, DEFAULT_DERIVATION_PATH, DEFAULT_TOKEN_DECIMALS, type DerivedKey, type DiscoverAddressProgress, type DiscoverAddressesOptions, type DiscoverAddressesResult, type DiscoveredAddress, type EncryptedData, type EncryptionOptions, type InitProgress, type InitProgressCallback, type InitProgressStep, type KeyPair, type L1Config, type LogHandler, type LogLevel, type LoggerConfig, type MasterKey, SIGN_MESSAGE_PREFIX, type ScanAddressProgress, type ScanAddressesOptions, type ScanAddressesResult, type ScannedAddressResult, Sphere, type SphereCreateOptions, SphereError, type SphereErrorCode, type SphereImportOptions, type SphereInitOptions, type SphereInitResult, type SphereLoadOptions, base58Decode, base58Encode, bytesToHex, checkNetworkHealth, computeDirectAddressFromChainPubkey, computeHash160, convertBits, createAddress, createBech32, createKeyPair, createSphere, decodeBech32, decrypt, decryptJson, decryptMnemonic, decryptSimple, decryptWithSalt, deriveAddressInfo, deriveChildKey, deriveKeyAtPath, deserializeEncrypted, discoverAddressesImpl, doubleSha256, ec, encodeBech32, encrypt, encryptMnemonic, encryptSimple, entropyToMnemonic, extractFromText, findPattern, formatAmount, generateAddressInfo, generateMasterKey, generateMnemonic, generateRandomKey, getAddressHrp, getPublicKey, getSphere, hash160, hash160ToBytes, hashSignMessage, hexToBytes, identityFromMnemonic, identityFromMnemonicSync, importSphere, initSphere, isEncryptedData, isSphereError, isValidBech32, isValidNametag, isValidPrivateKey, loadSphere, logger, mnemonicToEntropy, mnemonicToSeed, mnemonicToSeedSync, privateKeyToAddressInfo, publicKeyToAddress, randomBytes, randomHex, randomUUID, ripemd160, scanAddressesImpl, serializeEncrypted, sha256, signMessage, sleep, sphereExists, toHumanReadable, toSmallestUnit, validateMnemonic, verifySignedMessage };