@unicitylabs/sphere-sdk 0.2.3 → 0.2.5

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.
package/dist/index.cjs CHANGED
@@ -498,6 +498,7 @@ __export(index_exports, {
498
498
  TokenRegistry: () => TokenRegistry,
499
499
  TokenValidator: () => TokenValidator,
500
500
  archivedKeyFromTokenId: () => archivedKeyFromTokenId,
501
+ areSameNametag: () => import_nostr_js_sdk3.areSameNametag,
501
502
  base58Decode: () => base58Decode,
502
503
  base58Encode: () => base58Encode2,
503
504
  buildTxfStorageData: () => buildTxfStorageData,
@@ -545,6 +546,7 @@ __export(index_exports, {
545
546
  hasUncommittedTransactions: () => hasUncommittedTransactions,
546
547
  hasValidTxfData: () => hasValidTxfData,
547
548
  hash160: () => hash160,
549
+ hashNametag: () => import_nostr_js_sdk3.hashNametag,
548
550
  hexToBytes: () => hexToBytes,
549
551
  identityFromMnemonicSync: () => identityFromMnemonicSync,
550
552
  initSphere: () => initSphere,
@@ -556,10 +558,12 @@ __export(index_exports, {
556
558
  isKnownToken: () => isKnownToken,
557
559
  isPaymentSessionTerminal: () => isPaymentSessionTerminal,
558
560
  isPaymentSessionTimedOut: () => isPaymentSessionTimedOut,
561
+ isPhoneNumber: () => import_nostr_js_sdk3.isPhoneNumber,
559
562
  isSQLiteDatabase: () => isSQLiteDatabase,
560
563
  isTextWalletEncrypted: () => isTextWalletEncrypted,
561
564
  isTokenKey: () => isTokenKey,
562
565
  isValidBech32: () => isValidBech32,
566
+ isValidNametag: () => isValidNametag,
563
567
  isValidPrivateKey: () => isValidPrivateKey,
564
568
  isValidTokenId: () => isValidTokenId,
565
569
  isWalletDatEncrypted: () => isWalletDatEncrypted,
@@ -567,6 +571,7 @@ __export(index_exports, {
567
571
  keyFromTokenId: () => keyFromTokenId,
568
572
  loadSphere: () => loadSphere,
569
573
  mnemonicToSeedSync: () => mnemonicToSeedSync2,
574
+ normalizeNametag: () => import_nostr_js_sdk3.normalizeNametag,
570
575
  normalizeSdkTokenToStorage: () => normalizeSdkTokenToStorage,
571
576
  objectToTxf: () => objectToTxf,
572
577
  parseAndDecryptWalletDat: () => parseAndDecryptWalletDat,
@@ -2414,6 +2419,7 @@ var import_MintCommitment = require("@unicitylabs/state-transition-sdk/lib/trans
2414
2419
  var import_HashAlgorithm2 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
2415
2420
  var import_UnmaskedPredicate2 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
2416
2421
  var import_InclusionProofUtils2 = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils");
2422
+ var import_nostr_js_sdk = require("@unicitylabs/nostr-js-sdk");
2417
2423
  var UNICITY_TOKEN_TYPE_HEX = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
2418
2424
  var NametagMinter = class {
2419
2425
  client;
@@ -2438,7 +2444,8 @@ var NametagMinter = class {
2438
2444
  */
2439
2445
  async isNametagAvailable(nametag) {
2440
2446
  try {
2441
- const cleanNametag = nametag.replace("@", "").trim();
2447
+ const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
2448
+ const cleanNametag = (0, import_nostr_js_sdk.normalizeNametag)(stripped);
2442
2449
  const nametagTokenId = await import_TokenId2.TokenId.fromNameTag(cleanNametag);
2443
2450
  const isMinted = await this.client.isMinted(this.trustBase, nametagTokenId);
2444
2451
  return !isMinted;
@@ -2455,7 +2462,8 @@ var NametagMinter = class {
2455
2462
  * @returns MintNametagResult with token if successful
2456
2463
  */
2457
2464
  async mintNametag(nametag, ownerAddress) {
2458
- const cleanNametag = nametag.replace("@", "").trim();
2465
+ const stripped = nametag.startsWith("@") ? nametag.slice(1) : nametag;
2466
+ const cleanNametag = (0, import_nostr_js_sdk.normalizeNametag)(stripped);
2459
2467
  this.log(`Starting mint for nametag: ${cleanNametag}`);
2460
2468
  try {
2461
2469
  const nametagTokenId = await import_TokenId2.TokenId.fromNameTag(cleanNametag);
@@ -2609,7 +2617,9 @@ var STORAGE_KEYS_ADDRESS = {
2609
2617
  /** Messages for this address */
2610
2618
  MESSAGES: "messages",
2611
2619
  /** Transaction history for this address */
2612
- TRANSACTION_HISTORY: "transaction_history"
2620
+ TRANSACTION_HISTORY: "transaction_history",
2621
+ /** Pending V5 finalization tokens (unconfirmed instant split tokens) */
2622
+ PENDING_V5_TOKENS: "pending_v5_tokens"
2613
2623
  };
2614
2624
  var STORAGE_KEYS = {
2615
2625
  ...STORAGE_KEYS_GLOBAL,
@@ -3024,6 +3034,18 @@ function parseTxfStorageData(data) {
3024
3034
  result.validationErrors.push(`Forked token ${parsed.tokenId}: invalid structure`);
3025
3035
  }
3026
3036
  }
3037
+ } else if (key.startsWith("token-")) {
3038
+ try {
3039
+ const entry = storageData[key];
3040
+ const txfToken = entry?.token;
3041
+ if (txfToken?.genesis?.data?.tokenId) {
3042
+ const tokenId = txfToken.genesis.data.tokenId;
3043
+ const token = txfToToken(tokenId, txfToken);
3044
+ result.tokens.push(token);
3045
+ }
3046
+ } catch (err) {
3047
+ result.validationErrors.push(`Token ${key}: ${err}`);
3048
+ }
3027
3049
  }
3028
3050
  }
3029
3051
  return result;
@@ -3599,8 +3621,9 @@ var InstantSplitExecutor = class {
3599
3621
  const criticalPathDuration = performance.now() - startTime;
3600
3622
  console.log(`[InstantSplit] V5 complete in ${criticalPathDuration.toFixed(0)}ms`);
3601
3623
  options?.onNostrDelivered?.(nostrEventId);
3624
+ let backgroundPromise;
3602
3625
  if (!options?.skipBackground) {
3603
- this.submitBackgroundV5(senderMintCommitment, recipientMintCommitment, transferCommitment, {
3626
+ backgroundPromise = this.submitBackgroundV5(senderMintCommitment, recipientMintCommitment, transferCommitment, {
3604
3627
  signingService: this.signingService,
3605
3628
  tokenType: tokenToSplit.type,
3606
3629
  coinId,
@@ -3616,7 +3639,8 @@ var InstantSplitExecutor = class {
3616
3639
  nostrEventId,
3617
3640
  splitGroupId,
3618
3641
  criticalPathDurationMs: criticalPathDuration,
3619
- backgroundStarted: !options?.skipBackground
3642
+ backgroundStarted: !options?.skipBackground,
3643
+ backgroundPromise
3620
3644
  };
3621
3645
  } catch (error) {
3622
3646
  const duration = performance.now() - startTime;
@@ -3678,7 +3702,7 @@ var InstantSplitExecutor = class {
3678
3702
  this.client.submitMintCommitment(recipientMintCommitment).then((res) => ({ type: "recipientMint", status: res.status })).catch((err) => ({ type: "recipientMint", status: "ERROR", error: err })),
3679
3703
  this.client.submitTransferCommitment(transferCommitment).then((res) => ({ type: "transfer", status: res.status })).catch((err) => ({ type: "transfer", status: "ERROR", error: err }))
3680
3704
  ]);
3681
- submissions.then(async (results) => {
3705
+ return submissions.then(async (results) => {
3682
3706
  const submitDuration = performance.now() - startTime;
3683
3707
  console.log(`[InstantSplit] Background: Submissions complete in ${submitDuration.toFixed(0)}ms`);
3684
3708
  context.onProgress?.({
@@ -4143,6 +4167,11 @@ var import_AddressScheme = require("@unicitylabs/state-transition-sdk/lib/addres
4143
4167
  var import_UnmaskedPredicate5 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
4144
4168
  var import_TokenState5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenState");
4145
4169
  var import_HashAlgorithm5 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
4170
+ var import_TokenType3 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
4171
+ var import_MintCommitment3 = require("@unicitylabs/state-transition-sdk/lib/transaction/MintCommitment");
4172
+ var import_MintTransactionData3 = require("@unicitylabs/state-transition-sdk/lib/transaction/MintTransactionData");
4173
+ var import_InclusionProofUtils5 = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils");
4174
+ var import_InclusionProof = require("@unicitylabs/state-transition-sdk/lib/transaction/InclusionProof");
4146
4175
  function enrichWithRegistry(info) {
4147
4176
  const registry = TokenRegistry.getInstance();
4148
4177
  const def = registry.getDefinition(info.coinId);
@@ -4340,6 +4369,13 @@ function extractTokenStateKey(token) {
4340
4369
  if (!tokenId || !stateHash) return null;
4341
4370
  return createTokenStateKey(tokenId, stateHash);
4342
4371
  }
4372
+ function fromHex4(hex) {
4373
+ const bytes = new Uint8Array(hex.length / 2);
4374
+ for (let i = 0; i < hex.length; i += 2) {
4375
+ bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
4376
+ }
4377
+ return bytes;
4378
+ }
4343
4379
  function hasSameGenesisTokenId(t1, t2) {
4344
4380
  const id1 = extractTokenIdFromSdkData(t1.sdkData);
4345
4381
  const id2 = extractTokenIdFromSdkData(t2.sdkData);
@@ -4429,6 +4465,7 @@ var PaymentsModule = class _PaymentsModule {
4429
4465
  // Token State
4430
4466
  tokens = /* @__PURE__ */ new Map();
4431
4467
  pendingTransfers = /* @__PURE__ */ new Map();
4468
+ pendingBackgroundTasks = [];
4432
4469
  // Repository State (tombstones, archives, forked, history)
4433
4470
  tombstones = [];
4434
4471
  archivedTokens = /* @__PURE__ */ new Map();
@@ -4453,6 +4490,12 @@ var PaymentsModule = class _PaymentsModule {
4453
4490
  // Poll every 2s
4454
4491
  static PROOF_POLLING_MAX_ATTEMPTS = 30;
4455
4492
  // Max 30 attempts (~60s)
4493
+ // Storage event subscriptions (push-based sync)
4494
+ storageEventUnsubscribers = [];
4495
+ syncDebounceTimer = null;
4496
+ static SYNC_DEBOUNCE_MS = 500;
4497
+ /** Sync coalescing: concurrent sync() calls share the same operation */
4498
+ _syncInProgress = null;
4456
4499
  constructor(config) {
4457
4500
  this.moduleConfig = {
4458
4501
  autoSync: config?.autoSync ?? true,
@@ -4463,7 +4506,11 @@ var PaymentsModule = class _PaymentsModule {
4463
4506
  };
4464
4507
  this.l1 = config?.l1 === null ? null : new L1PaymentsModule(config?.l1);
4465
4508
  }
4466
- /** Get module configuration */
4509
+ /**
4510
+ * Get the current module configuration (excluding L1 config).
4511
+ *
4512
+ * @returns Resolved configuration with all defaults applied.
4513
+ */
4467
4514
  getConfig() {
4468
4515
  return this.moduleConfig;
4469
4516
  }
@@ -4504,9 +4551,9 @@ var PaymentsModule = class _PaymentsModule {
4504
4551
  transport: deps.transport
4505
4552
  });
4506
4553
  }
4507
- this.unsubscribeTransfers = deps.transport.onTokenTransfer((transfer) => {
4508
- this.handleIncomingTransfer(transfer);
4509
- });
4554
+ this.unsubscribeTransfers = deps.transport.onTokenTransfer(
4555
+ (transfer) => this.handleIncomingTransfer(transfer)
4556
+ );
4510
4557
  if (deps.transport.onPaymentRequest) {
4511
4558
  this.unsubscribePaymentRequests = deps.transport.onPaymentRequest((request) => {
4512
4559
  this.handleIncomingPaymentRequest(request);
@@ -4517,9 +4564,14 @@ var PaymentsModule = class _PaymentsModule {
4517
4564
  this.handlePaymentRequestResponse(response);
4518
4565
  });
4519
4566
  }
4567
+ this.subscribeToStorageEvents();
4520
4568
  }
4521
4569
  /**
4522
- * Load tokens from storage
4570
+ * Load all token data from storage providers and restore wallet state.
4571
+ *
4572
+ * Loads tokens, nametag data, transaction history, and pending transfers
4573
+ * from configured storage providers. Restores pending V5 tokens and
4574
+ * triggers a fire-and-forget {@link resolveUnconfirmed} call.
4523
4575
  */
4524
4576
  async load() {
4525
4577
  this.ensureInitialized();
@@ -4536,6 +4588,7 @@ var PaymentsModule = class _PaymentsModule {
4536
4588
  console.error(`[Payments] Failed to load from provider ${id}:`, err);
4537
4589
  }
4538
4590
  }
4591
+ await this.loadPendingV5Tokens();
4539
4592
  await this.loadTokensFromFileStorage();
4540
4593
  await this.loadNametagFromFileStorage();
4541
4594
  const historyData = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.TRANSACTION_HISTORY);
@@ -4553,9 +4606,14 @@ var PaymentsModule = class _PaymentsModule {
4553
4606
  this.pendingTransfers.set(transfer.id, transfer);
4554
4607
  }
4555
4608
  }
4609
+ this.resolveUnconfirmed().catch(() => {
4610
+ });
4556
4611
  }
4557
4612
  /**
4558
- * Cleanup resources
4613
+ * Cleanup all subscriptions, polling jobs, and pending resolvers.
4614
+ *
4615
+ * Should be called when the wallet is being shut down or the module is
4616
+ * no longer needed. Also destroys the L1 sub-module if present.
4559
4617
  */
4560
4618
  destroy() {
4561
4619
  this.unsubscribeTransfers?.();
@@ -4573,6 +4631,7 @@ var PaymentsModule = class _PaymentsModule {
4573
4631
  resolver.reject(new Error("Module destroyed"));
4574
4632
  }
4575
4633
  this.pendingResponseResolvers.clear();
4634
+ this.unsubscribeStorageEvents();
4576
4635
  if (this.l1) {
4577
4636
  this.l1.destroy();
4578
4637
  }
@@ -4589,7 +4648,8 @@ var PaymentsModule = class _PaymentsModule {
4589
4648
  const result = {
4590
4649
  id: crypto.randomUUID(),
4591
4650
  status: "pending",
4592
- tokens: []
4651
+ tokens: [],
4652
+ tokenTransfers: []
4593
4653
  };
4594
4654
  try {
4595
4655
  const peerInfo = await this.deps.transport.resolve?.(request.recipient) ?? null;
@@ -4626,69 +4686,147 @@ var PaymentsModule = class _PaymentsModule {
4626
4686
  await this.saveToOutbox(result, recipientPubkey);
4627
4687
  result.status = "submitted";
4628
4688
  const recipientNametag = request.recipient.startsWith("@") ? request.recipient.slice(1) : void 0;
4689
+ const transferMode = request.transferMode ?? "instant";
4629
4690
  if (splitPlan.requiresSplit && splitPlan.tokenToSplit) {
4630
- this.log("Executing token split...");
4631
- const executor = new TokenSplitExecutor({
4632
- stateTransitionClient: stClient,
4633
- trustBase,
4634
- signingService
4635
- });
4636
- const splitResult = await executor.executeSplit(
4637
- splitPlan.tokenToSplit.sdkToken,
4638
- splitPlan.splitAmount,
4639
- splitPlan.remainderAmount,
4640
- splitPlan.coinId,
4641
- recipientAddress
4642
- );
4643
- const changeTokenData = splitResult.tokenForSender.toJSON();
4644
- const changeToken = {
4645
- id: crypto.randomUUID(),
4646
- coinId: request.coinId,
4647
- symbol: this.getCoinSymbol(request.coinId),
4648
- name: this.getCoinName(request.coinId),
4649
- decimals: this.getCoinDecimals(request.coinId),
4650
- iconUrl: this.getCoinIconUrl(request.coinId),
4651
- amount: splitPlan.remainderAmount.toString(),
4652
- status: "confirmed",
4653
- createdAt: Date.now(),
4654
- updatedAt: Date.now(),
4655
- sdkData: JSON.stringify(changeTokenData)
4656
- };
4657
- await this.addToken(changeToken, true);
4658
- this.log(`Change token saved: ${changeToken.id}, amount: ${changeToken.amount}`);
4659
- console.log(`[Payments] Sending split token to ${recipientPubkey.slice(0, 8)}... via Nostr`);
4660
- await this.deps.transport.sendTokenTransfer(recipientPubkey, {
4661
- sourceToken: JSON.stringify(splitResult.tokenForRecipient.toJSON()),
4662
- transferTx: JSON.stringify(splitResult.recipientTransferTx.toJSON()),
4663
- memo: request.memo
4664
- });
4665
- console.log(`[Payments] Split token sent successfully`);
4666
- await this.removeToken(splitPlan.tokenToSplit.uiToken.id, recipientNametag, true);
4667
- result.txHash = "split-" + Date.now().toString(16);
4668
- this.log(`Split transfer completed`);
4691
+ if (transferMode === "conservative") {
4692
+ this.log("Executing conservative split...");
4693
+ const splitExecutor = new TokenSplitExecutor({
4694
+ stateTransitionClient: stClient,
4695
+ trustBase,
4696
+ signingService
4697
+ });
4698
+ const splitResult = await splitExecutor.executeSplit(
4699
+ splitPlan.tokenToSplit.sdkToken,
4700
+ splitPlan.splitAmount,
4701
+ splitPlan.remainderAmount,
4702
+ splitPlan.coinId,
4703
+ recipientAddress
4704
+ );
4705
+ const changeTokenData = splitResult.tokenForSender.toJSON();
4706
+ const changeUiToken = {
4707
+ id: crypto.randomUUID(),
4708
+ coinId: request.coinId,
4709
+ symbol: this.getCoinSymbol(request.coinId),
4710
+ name: this.getCoinName(request.coinId),
4711
+ decimals: this.getCoinDecimals(request.coinId),
4712
+ iconUrl: this.getCoinIconUrl(request.coinId),
4713
+ amount: splitPlan.remainderAmount.toString(),
4714
+ status: "confirmed",
4715
+ createdAt: Date.now(),
4716
+ updatedAt: Date.now(),
4717
+ sdkData: JSON.stringify(changeTokenData)
4718
+ };
4719
+ await this.addToken(changeUiToken, true);
4720
+ this.log(`Conservative split: change token saved: ${changeUiToken.id}`);
4721
+ await this.deps.transport.sendTokenTransfer(recipientPubkey, {
4722
+ sourceToken: JSON.stringify(splitResult.tokenForRecipient.toJSON()),
4723
+ transferTx: JSON.stringify(splitResult.recipientTransferTx.toJSON()),
4724
+ memo: request.memo
4725
+ });
4726
+ const splitCommitmentRequestId = splitResult.recipientTransferTx?.data?.requestId ?? splitResult.recipientTransferTx?.requestId;
4727
+ const splitRequestIdHex = splitCommitmentRequestId instanceof Uint8Array ? Array.from(splitCommitmentRequestId).map((b) => b.toString(16).padStart(2, "0")).join("") : splitCommitmentRequestId ? String(splitCommitmentRequestId) : void 0;
4728
+ await this.removeToken(splitPlan.tokenToSplit.uiToken.id, recipientNametag, true);
4729
+ result.tokenTransfers.push({
4730
+ sourceTokenId: splitPlan.tokenToSplit.uiToken.id,
4731
+ method: "split",
4732
+ requestIdHex: splitRequestIdHex
4733
+ });
4734
+ this.log(`Conservative split transfer completed`);
4735
+ } else {
4736
+ this.log("Executing instant split...");
4737
+ const devMode = this.deps.oracle.isDevMode?.() ?? false;
4738
+ const executor = new InstantSplitExecutor({
4739
+ stateTransitionClient: stClient,
4740
+ trustBase,
4741
+ signingService,
4742
+ devMode
4743
+ });
4744
+ const instantResult = await executor.executeSplitInstant(
4745
+ splitPlan.tokenToSplit.sdkToken,
4746
+ splitPlan.splitAmount,
4747
+ splitPlan.remainderAmount,
4748
+ splitPlan.coinId,
4749
+ recipientAddress,
4750
+ this.deps.transport,
4751
+ recipientPubkey,
4752
+ {
4753
+ onChangeTokenCreated: async (changeToken) => {
4754
+ const changeTokenData = changeToken.toJSON();
4755
+ const uiToken = {
4756
+ id: crypto.randomUUID(),
4757
+ coinId: request.coinId,
4758
+ symbol: this.getCoinSymbol(request.coinId),
4759
+ name: this.getCoinName(request.coinId),
4760
+ decimals: this.getCoinDecimals(request.coinId),
4761
+ iconUrl: this.getCoinIconUrl(request.coinId),
4762
+ amount: splitPlan.remainderAmount.toString(),
4763
+ status: "confirmed",
4764
+ createdAt: Date.now(),
4765
+ updatedAt: Date.now(),
4766
+ sdkData: JSON.stringify(changeTokenData)
4767
+ };
4768
+ await this.addToken(uiToken, true);
4769
+ this.log(`Change token saved via background: ${uiToken.id}`);
4770
+ },
4771
+ onStorageSync: async () => {
4772
+ await this.save();
4773
+ return true;
4774
+ }
4775
+ }
4776
+ );
4777
+ if (!instantResult.success) {
4778
+ throw new Error(instantResult.error || "Instant split failed");
4779
+ }
4780
+ if (instantResult.backgroundPromise) {
4781
+ this.pendingBackgroundTasks.push(instantResult.backgroundPromise);
4782
+ }
4783
+ await this.removeToken(splitPlan.tokenToSplit.uiToken.id, recipientNametag);
4784
+ result.tokenTransfers.push({
4785
+ sourceTokenId: splitPlan.tokenToSplit.uiToken.id,
4786
+ method: "split",
4787
+ splitGroupId: instantResult.splitGroupId,
4788
+ nostrEventId: instantResult.nostrEventId
4789
+ });
4790
+ this.log(`Instant split transfer completed`);
4791
+ }
4669
4792
  }
4670
4793
  for (const tokenWithAmount of splitPlan.tokensToTransferDirectly) {
4671
4794
  const token = tokenWithAmount.uiToken;
4672
4795
  const commitment = await this.createSdkCommitment(token, recipientAddress, signingService);
4673
- const response = await stClient.submitTransferCommitment(commitment);
4674
- if (response.status !== "SUCCESS" && response.status !== "REQUEST_ID_EXISTS") {
4675
- throw new Error(`Transfer commitment failed: ${response.status}`);
4676
- }
4677
- if (!this.deps.oracle.waitForProofSdk) {
4678
- throw new Error("Oracle provider must implement waitForProofSdk()");
4796
+ if (transferMode === "conservative") {
4797
+ console.log(`[Payments] CONSERVATIVE: Sending direct token ${token.id.slice(0, 8)}... to ${recipientPubkey.slice(0, 8)}...`);
4798
+ const submitResponse = await stClient.submitTransferCommitment(commitment);
4799
+ if (submitResponse.status !== "SUCCESS" && submitResponse.status !== "REQUEST_ID_EXISTS") {
4800
+ throw new Error(`Transfer commitment failed: ${submitResponse.status}`);
4801
+ }
4802
+ const inclusionProof = await (0, import_InclusionProofUtils5.waitInclusionProof)(trustBase, stClient, commitment);
4803
+ const transferTx = commitment.toTransaction(inclusionProof);
4804
+ await this.deps.transport.sendTokenTransfer(recipientPubkey, {
4805
+ sourceToken: JSON.stringify(tokenWithAmount.sdkToken.toJSON()),
4806
+ transferTx: JSON.stringify(transferTx.toJSON()),
4807
+ memo: request.memo
4808
+ });
4809
+ console.log(`[Payments] CONSERVATIVE: Direct token sent successfully`);
4810
+ } else {
4811
+ console.log(`[Payments] NOSTR-FIRST: Sending direct token ${token.id.slice(0, 8)}... to ${recipientPubkey.slice(0, 8)}...`);
4812
+ await this.deps.transport.sendTokenTransfer(recipientPubkey, {
4813
+ sourceToken: JSON.stringify(tokenWithAmount.sdkToken.toJSON()),
4814
+ commitmentData: JSON.stringify(commitment.toJSON()),
4815
+ memo: request.memo
4816
+ });
4817
+ console.log(`[Payments] NOSTR-FIRST: Direct token sent successfully`);
4818
+ stClient.submitTransferCommitment(commitment).catch(
4819
+ (err) => console.error("[Payments] Background commitment submit failed:", err)
4820
+ );
4679
4821
  }
4680
- const inclusionProof = await this.deps.oracle.waitForProofSdk(commitment);
4681
- const transferTx = commitment.toTransaction(inclusionProof);
4682
4822
  const requestIdBytes = commitment.requestId;
4683
- result.txHash = requestIdBytes instanceof Uint8Array ? Array.from(requestIdBytes).map((b) => b.toString(16).padStart(2, "0")).join("") : String(requestIdBytes);
4684
- console.log(`[Payments] Sending direct token ${token.id.slice(0, 8)}... to ${recipientPubkey.slice(0, 8)}... via Nostr`);
4685
- await this.deps.transport.sendTokenTransfer(recipientPubkey, {
4686
- sourceToken: JSON.stringify(tokenWithAmount.sdkToken.toJSON()),
4687
- transferTx: JSON.stringify(transferTx.toJSON()),
4688
- memo: request.memo
4823
+ const requestIdHex = requestIdBytes instanceof Uint8Array ? Array.from(requestIdBytes).map((b) => b.toString(16).padStart(2, "0")).join("") : String(requestIdBytes);
4824
+ result.tokenTransfers.push({
4825
+ sourceTokenId: token.id,
4826
+ method: "direct",
4827
+ requestIdHex
4689
4828
  });
4690
- console.log(`[Payments] Direct token sent successfully`);
4691
- this.log(`Token ${token.id} transferred, txHash: ${result.txHash}`);
4829
+ this.log(`Token ${token.id} sent via ${transferMode.toUpperCase()}, requestId: ${requestIdHex}`);
4692
4830
  await this.removeToken(token.id, recipientNametag, true);
4693
4831
  }
4694
4832
  result.status = "delivered";
@@ -4701,7 +4839,8 @@ var PaymentsModule = class _PaymentsModule {
4701
4839
  coinId: request.coinId,
4702
4840
  symbol: this.getCoinSymbol(request.coinId),
4703
4841
  timestamp: Date.now(),
4704
- recipientNametag
4842
+ recipientNametag,
4843
+ transferId: result.id
4705
4844
  });
4706
4845
  this.deps.emitEvent("transfer:confirmed", result);
4707
4846
  return result;
@@ -4837,6 +4976,9 @@ var PaymentsModule = class _PaymentsModule {
4837
4976
  }
4838
4977
  );
4839
4978
  if (result.success) {
4979
+ if (result.backgroundPromise) {
4980
+ this.pendingBackgroundTasks.push(result.backgroundPromise);
4981
+ }
4840
4982
  const recipientNametag = request.recipient.startsWith("@") ? request.recipient.slice(1) : void 0;
4841
4983
  await this.removeToken(tokenToSplit.id, recipientNametag, true);
4842
4984
  await this.addToHistory({
@@ -4878,6 +5020,63 @@ var PaymentsModule = class _PaymentsModule {
4878
5020
  */
4879
5021
  async processInstantSplitBundle(bundle, senderPubkey) {
4880
5022
  this.ensureInitialized();
5023
+ if (!isInstantSplitBundleV5(bundle)) {
5024
+ return this.processInstantSplitBundleSync(bundle, senderPubkey);
5025
+ }
5026
+ try {
5027
+ const deterministicId = `v5split_${bundle.splitGroupId}`;
5028
+ if (this.tokens.has(deterministicId)) {
5029
+ this.log(`V5 bundle ${deterministicId.slice(0, 16)}... already exists, skipping duplicate`);
5030
+ return { success: true, durationMs: 0 };
5031
+ }
5032
+ const registry = TokenRegistry.getInstance();
5033
+ const pendingData = {
5034
+ type: "v5_bundle",
5035
+ stage: "RECEIVED",
5036
+ bundleJson: JSON.stringify(bundle),
5037
+ senderPubkey,
5038
+ savedAt: Date.now(),
5039
+ attemptCount: 0
5040
+ };
5041
+ const uiToken = {
5042
+ id: deterministicId,
5043
+ coinId: bundle.coinId,
5044
+ symbol: registry.getSymbol(bundle.coinId) || bundle.coinId,
5045
+ name: registry.getName(bundle.coinId) || bundle.coinId,
5046
+ decimals: registry.getDecimals(bundle.coinId) ?? 8,
5047
+ amount: bundle.amount,
5048
+ status: "submitted",
5049
+ // UNCONFIRMED
5050
+ createdAt: Date.now(),
5051
+ updatedAt: Date.now(),
5052
+ sdkData: JSON.stringify({ _pendingFinalization: pendingData })
5053
+ };
5054
+ await this.addToken(uiToken, false);
5055
+ this.log(`V5 bundle saved as unconfirmed: ${uiToken.id.slice(0, 8)}...`);
5056
+ this.deps.emitEvent("transfer:incoming", {
5057
+ id: bundle.splitGroupId,
5058
+ senderPubkey,
5059
+ tokens: [uiToken],
5060
+ receivedAt: Date.now()
5061
+ });
5062
+ await this.save();
5063
+ this.resolveUnconfirmed().catch(() => {
5064
+ });
5065
+ return { success: true, durationMs: 0 };
5066
+ } catch (error) {
5067
+ const errorMessage = error instanceof Error ? error.message : String(error);
5068
+ return {
5069
+ success: false,
5070
+ error: errorMessage,
5071
+ durationMs: 0
5072
+ };
5073
+ }
5074
+ }
5075
+ /**
5076
+ * Synchronous V4 bundle processing (dev mode only).
5077
+ * Kept for backward compatibility with V4 bundles.
5078
+ */
5079
+ async processInstantSplitBundleSync(bundle, senderPubkey) {
4881
5080
  try {
4882
5081
  const signingService = await this.createSigningService();
4883
5082
  const stClient = this.deps.oracle.getStateTransitionClient?.();
@@ -4963,7 +5162,10 @@ var PaymentsModule = class _PaymentsModule {
4963
5162
  }
4964
5163
  }
4965
5164
  /**
4966
- * Check if a payload is an instant split bundle
5165
+ * Type-guard: check whether a payload is a valid {@link InstantSplitBundle} (V4 or V5).
5166
+ *
5167
+ * @param payload - The object to test.
5168
+ * @returns `true` if the payload matches the InstantSplitBundle shape.
4967
5169
  */
4968
5170
  isInstantSplitBundle(payload) {
4969
5171
  return isInstantSplitBundle(payload);
@@ -5044,39 +5246,57 @@ var PaymentsModule = class _PaymentsModule {
5044
5246
  return [...this.paymentRequests];
5045
5247
  }
5046
5248
  /**
5047
- * Get pending payment requests count
5249
+ * Get the count of payment requests with status `'pending'`.
5250
+ *
5251
+ * @returns Number of pending incoming payment requests.
5048
5252
  */
5049
5253
  getPendingPaymentRequestsCount() {
5050
5254
  return this.paymentRequests.filter((r) => r.status === "pending").length;
5051
5255
  }
5052
5256
  /**
5053
- * Accept a payment request (marks it as accepted, user should then call send())
5257
+ * Accept a payment request and notify the requester.
5258
+ *
5259
+ * Marks the request as `'accepted'` and sends a response via transport.
5260
+ * The caller should subsequently call {@link send} to fulfill the payment.
5261
+ *
5262
+ * @param requestId - ID of the incoming payment request to accept.
5054
5263
  */
5055
5264
  async acceptPaymentRequest(requestId2) {
5056
5265
  this.updatePaymentRequestStatus(requestId2, "accepted");
5057
5266
  await this.sendPaymentRequestResponse(requestId2, "accepted");
5058
5267
  }
5059
5268
  /**
5060
- * Reject a payment request
5269
+ * Reject a payment request and notify the requester.
5270
+ *
5271
+ * @param requestId - ID of the incoming payment request to reject.
5061
5272
  */
5062
5273
  async rejectPaymentRequest(requestId2) {
5063
5274
  this.updatePaymentRequestStatus(requestId2, "rejected");
5064
5275
  await this.sendPaymentRequestResponse(requestId2, "rejected");
5065
5276
  }
5066
5277
  /**
5067
- * Mark a payment request as paid (after successful transfer)
5278
+ * Mark a payment request as paid (local status update only).
5279
+ *
5280
+ * Typically called after a successful {@link send} to record that the
5281
+ * request has been fulfilled.
5282
+ *
5283
+ * @param requestId - ID of the incoming payment request to mark as paid.
5068
5284
  */
5069
5285
  markPaymentRequestPaid(requestId2) {
5070
5286
  this.updatePaymentRequestStatus(requestId2, "paid");
5071
5287
  }
5072
5288
  /**
5073
- * Clear processed (non-pending) payment requests
5289
+ * Remove all non-pending incoming payment requests from memory.
5290
+ *
5291
+ * Keeps only requests with status `'pending'`.
5074
5292
  */
5075
5293
  clearProcessedPaymentRequests() {
5076
5294
  this.paymentRequests = this.paymentRequests.filter((r) => r.status === "pending");
5077
5295
  }
5078
5296
  /**
5079
- * Remove a specific payment request
5297
+ * Remove a specific incoming payment request by ID.
5298
+ *
5299
+ * @param requestId - ID of the payment request to remove.
5080
5300
  */
5081
5301
  removePaymentRequest(requestId2) {
5082
5302
  this.paymentRequests = this.paymentRequests.filter((r) => r.id !== requestId2);
@@ -5201,7 +5421,11 @@ var PaymentsModule = class _PaymentsModule {
5201
5421
  });
5202
5422
  }
5203
5423
  /**
5204
- * Cancel waiting for a payment response
5424
+ * Cancel an active {@link waitForPaymentResponse} call.
5425
+ *
5426
+ * The pending promise is rejected with a `'Cancelled'` error.
5427
+ *
5428
+ * @param requestId - The outgoing request ID whose wait should be cancelled.
5205
5429
  */
5206
5430
  cancelWaitForPaymentResponse(requestId2) {
5207
5431
  const resolver = this.pendingResponseResolvers.get(requestId2);
@@ -5212,14 +5436,16 @@ var PaymentsModule = class _PaymentsModule {
5212
5436
  }
5213
5437
  }
5214
5438
  /**
5215
- * Remove an outgoing payment request
5439
+ * Remove an outgoing payment request and cancel any pending wait.
5440
+ *
5441
+ * @param requestId - ID of the outgoing request to remove.
5216
5442
  */
5217
5443
  removeOutgoingPaymentRequest(requestId2) {
5218
5444
  this.outgoingPaymentRequests.delete(requestId2);
5219
5445
  this.cancelWaitForPaymentResponse(requestId2);
5220
5446
  }
5221
5447
  /**
5222
- * Clear completed/expired outgoing payment requests
5448
+ * Remove all outgoing payment requests that are `'paid'`, `'rejected'`, or `'expired'`.
5223
5449
  */
5224
5450
  clearCompletedOutgoingPaymentRequests() {
5225
5451
  for (const [id, request] of this.outgoingPaymentRequests) {
@@ -5291,6 +5517,71 @@ var PaymentsModule = class _PaymentsModule {
5291
5517
  }
5292
5518
  }
5293
5519
  // ===========================================================================
5520
+ // Public API - Receive
5521
+ // ===========================================================================
5522
+ /**
5523
+ * Fetch and process pending incoming transfers from the transport layer.
5524
+ *
5525
+ * Performs a one-shot query to fetch all pending events, processes them
5526
+ * through the existing pipeline, and resolves after all stored events
5527
+ * are handled. Useful for batch/CLI apps that need explicit receive.
5528
+ *
5529
+ * When `finalize` is true, polls resolveUnconfirmed() + load() until all
5530
+ * tokens are confirmed or the timeout expires. Otherwise calls
5531
+ * resolveUnconfirmed() once to submit pending commitments.
5532
+ *
5533
+ * @param options - Optional receive options including finalization control
5534
+ * @param callback - Optional callback invoked for each newly received transfer
5535
+ * @returns ReceiveResult with transfers and finalization metadata
5536
+ */
5537
+ async receive(options, callback) {
5538
+ this.ensureInitialized();
5539
+ if (!this.deps.transport.fetchPendingEvents) {
5540
+ throw new Error("Transport provider does not support fetchPendingEvents");
5541
+ }
5542
+ const opts = options ?? {};
5543
+ const tokensBefore = new Set(this.tokens.keys());
5544
+ await this.deps.transport.fetchPendingEvents();
5545
+ await this.load();
5546
+ const received = [];
5547
+ for (const [tokenId, token] of this.tokens) {
5548
+ if (!tokensBefore.has(tokenId)) {
5549
+ const transfer = {
5550
+ id: tokenId,
5551
+ senderPubkey: "",
5552
+ tokens: [token],
5553
+ receivedAt: Date.now()
5554
+ };
5555
+ received.push(transfer);
5556
+ if (callback) callback(transfer);
5557
+ }
5558
+ }
5559
+ const result = { transfers: received };
5560
+ if (opts.finalize) {
5561
+ const timeout = opts.timeout ?? 6e4;
5562
+ const pollInterval = opts.pollInterval ?? 2e3;
5563
+ const startTime = Date.now();
5564
+ while (Date.now() - startTime < timeout) {
5565
+ const resolution = await this.resolveUnconfirmed();
5566
+ result.finalization = resolution;
5567
+ if (opts.onProgress) opts.onProgress(resolution);
5568
+ const stillUnconfirmed = Array.from(this.tokens.values()).some(
5569
+ (t) => t.status === "submitted" || t.status === "pending"
5570
+ );
5571
+ if (!stillUnconfirmed) break;
5572
+ await new Promise((r) => setTimeout(r, pollInterval));
5573
+ await this.load();
5574
+ }
5575
+ result.finalizationDurationMs = Date.now() - startTime;
5576
+ result.timedOut = Array.from(this.tokens.values()).some(
5577
+ (t) => t.status === "submitted" || t.status === "pending"
5578
+ );
5579
+ } else {
5580
+ result.finalization = await this.resolveUnconfirmed();
5581
+ }
5582
+ return result;
5583
+ }
5584
+ // ===========================================================================
5294
5585
  // Public API - Balance & Tokens
5295
5586
  // ===========================================================================
5296
5587
  /**
@@ -5300,10 +5591,20 @@ var PaymentsModule = class _PaymentsModule {
5300
5591
  this.priceProvider = provider;
5301
5592
  }
5302
5593
  /**
5303
- * Get total portfolio value in USD
5304
- * Returns null if PriceProvider is not configured
5594
+ * Wait for all pending background operations (e.g., instant split change token creation).
5595
+ * Call this before process exit to ensure all tokens are saved.
5305
5596
  */
5306
- async getBalance() {
5597
+ async waitForPendingOperations() {
5598
+ if (this.pendingBackgroundTasks.length > 0) {
5599
+ await Promise.allSettled(this.pendingBackgroundTasks);
5600
+ this.pendingBackgroundTasks = [];
5601
+ }
5602
+ }
5603
+ /**
5604
+ * Get total portfolio value in USD.
5605
+ * Returns null if PriceProvider is not configured.
5606
+ */
5607
+ async getFiatBalance() {
5307
5608
  const assets = await this.getAssets();
5308
5609
  if (!this.priceProvider) {
5309
5610
  return null;
@@ -5319,19 +5620,95 @@ var PaymentsModule = class _PaymentsModule {
5319
5620
  return hasAnyPrice ? total : null;
5320
5621
  }
5321
5622
  /**
5322
- * Get aggregated assets (tokens grouped by coinId) with price data
5323
- * Only includes confirmed tokens
5623
+ * Get token balances grouped by coin type.
5624
+ *
5625
+ * Returns an array of {@link Asset} objects, one per coin type held.
5626
+ * Each entry includes confirmed and unconfirmed breakdowns. Tokens with
5627
+ * status `'spent'`, `'invalid'`, or `'transferring'` are excluded.
5628
+ *
5629
+ * This is synchronous — no price data is included. Use {@link getAssets}
5630
+ * for the async version with fiat pricing.
5631
+ *
5632
+ * @param coinId - Optional coin ID to filter by (e.g. hex string). When omitted, all coin types are returned.
5633
+ * @returns Array of balance summaries (synchronous — no await needed).
5634
+ */
5635
+ getBalance(coinId) {
5636
+ return this.aggregateTokens(coinId);
5637
+ }
5638
+ /**
5639
+ * Get aggregated assets (tokens grouped by coinId) with price data.
5640
+ * Includes both confirmed and unconfirmed tokens with breakdown.
5324
5641
  */
5325
5642
  async getAssets(coinId) {
5643
+ const rawAssets = this.aggregateTokens(coinId);
5644
+ if (!this.priceProvider || rawAssets.length === 0) {
5645
+ return rawAssets;
5646
+ }
5647
+ try {
5648
+ const registry = TokenRegistry.getInstance();
5649
+ const nameToCoins = /* @__PURE__ */ new Map();
5650
+ for (const asset of rawAssets) {
5651
+ const def = registry.getDefinition(asset.coinId);
5652
+ if (def?.name) {
5653
+ const existing = nameToCoins.get(def.name);
5654
+ if (existing) {
5655
+ existing.push(asset.coinId);
5656
+ } else {
5657
+ nameToCoins.set(def.name, [asset.coinId]);
5658
+ }
5659
+ }
5660
+ }
5661
+ if (nameToCoins.size > 0) {
5662
+ const tokenNames = Array.from(nameToCoins.keys());
5663
+ const prices = await this.priceProvider.getPrices(tokenNames);
5664
+ return rawAssets.map((raw) => {
5665
+ const def = registry.getDefinition(raw.coinId);
5666
+ const price = def?.name ? prices.get(def.name) : void 0;
5667
+ let fiatValueUsd = null;
5668
+ let fiatValueEur = null;
5669
+ if (price) {
5670
+ const humanAmount = Number(raw.totalAmount) / Math.pow(10, raw.decimals);
5671
+ fiatValueUsd = humanAmount * price.priceUsd;
5672
+ if (price.priceEur != null) {
5673
+ fiatValueEur = humanAmount * price.priceEur;
5674
+ }
5675
+ }
5676
+ return {
5677
+ ...raw,
5678
+ priceUsd: price?.priceUsd ?? null,
5679
+ priceEur: price?.priceEur ?? null,
5680
+ change24h: price?.change24h ?? null,
5681
+ fiatValueUsd,
5682
+ fiatValueEur
5683
+ };
5684
+ });
5685
+ }
5686
+ } catch (error) {
5687
+ console.warn("[Payments] Failed to fetch prices, returning assets without price data:", error);
5688
+ }
5689
+ return rawAssets;
5690
+ }
5691
+ /**
5692
+ * Aggregate tokens by coinId with confirmed/unconfirmed breakdown.
5693
+ * Excludes tokens with status 'spent', 'invalid', or 'transferring'.
5694
+ */
5695
+ aggregateTokens(coinId) {
5326
5696
  const assetsMap = /* @__PURE__ */ new Map();
5327
5697
  for (const token of this.tokens.values()) {
5328
- if (token.status !== "confirmed") continue;
5698
+ if (token.status === "spent" || token.status === "invalid" || token.status === "transferring") continue;
5329
5699
  if (coinId && token.coinId !== coinId) continue;
5330
5700
  const key = token.coinId;
5701
+ const amount = BigInt(token.amount);
5702
+ const isConfirmed = token.status === "confirmed";
5331
5703
  const existing = assetsMap.get(key);
5332
5704
  if (existing) {
5333
- existing.totalAmount = (BigInt(existing.totalAmount) + BigInt(token.amount)).toString();
5334
- existing.tokenCount++;
5705
+ if (isConfirmed) {
5706
+ existing.confirmedAmount += amount;
5707
+ existing.confirmedTokenCount++;
5708
+ } else {
5709
+ existing.unconfirmedAmount += amount;
5710
+ existing.unconfirmedTokenCount++;
5711
+ }
5335
5712
  } else {
5336
5713
  assetsMap.set(key, {
5337
5714
  coinId: token.coinId,
@@ -5339,78 +5716,42 @@ var PaymentsModule = class _PaymentsModule {
5339
5716
  name: token.name,
5340
5717
  decimals: token.decimals,
5341
5718
  iconUrl: token.iconUrl,
5342
- totalAmount: token.amount,
5343
- tokenCount: 1
5719
+ confirmedAmount: isConfirmed ? amount : 0n,
5720
+ unconfirmedAmount: isConfirmed ? 0n : amount,
5721
+ confirmedTokenCount: isConfirmed ? 1 : 0,
5722
+ unconfirmedTokenCount: isConfirmed ? 0 : 1
5344
5723
  });
5345
5724
  }
5346
5725
  }
5347
- const rawAssets = Array.from(assetsMap.values());
5348
- let priceMap = null;
5349
- if (this.priceProvider && rawAssets.length > 0) {
5350
- try {
5351
- const registry = TokenRegistry.getInstance();
5352
- const nameToCoins = /* @__PURE__ */ new Map();
5353
- for (const asset of rawAssets) {
5354
- const def = registry.getDefinition(asset.coinId);
5355
- if (def?.name) {
5356
- const existing = nameToCoins.get(def.name);
5357
- if (existing) {
5358
- existing.push(asset.coinId);
5359
- } else {
5360
- nameToCoins.set(def.name, [asset.coinId]);
5361
- }
5362
- }
5363
- }
5364
- if (nameToCoins.size > 0) {
5365
- const tokenNames = Array.from(nameToCoins.keys());
5366
- const prices = await this.priceProvider.getPrices(tokenNames);
5367
- priceMap = /* @__PURE__ */ new Map();
5368
- for (const [name, coinIds] of nameToCoins) {
5369
- const price = prices.get(name);
5370
- if (price) {
5371
- for (const cid of coinIds) {
5372
- priceMap.set(cid, {
5373
- priceUsd: price.priceUsd,
5374
- priceEur: price.priceEur,
5375
- change24h: price.change24h
5376
- });
5377
- }
5378
- }
5379
- }
5380
- }
5381
- } catch (error) {
5382
- console.warn("[Payments] Failed to fetch prices, returning assets without price data:", error);
5383
- }
5384
- }
5385
- return rawAssets.map((raw) => {
5386
- const price = priceMap?.get(raw.coinId);
5387
- let fiatValueUsd = null;
5388
- let fiatValueEur = null;
5389
- if (price) {
5390
- const humanAmount = Number(raw.totalAmount) / Math.pow(10, raw.decimals);
5391
- fiatValueUsd = humanAmount * price.priceUsd;
5392
- if (price.priceEur != null) {
5393
- fiatValueEur = humanAmount * price.priceEur;
5394
- }
5395
- }
5726
+ return Array.from(assetsMap.values()).map((raw) => {
5727
+ const totalAmount = (raw.confirmedAmount + raw.unconfirmedAmount).toString();
5396
5728
  return {
5397
5729
  coinId: raw.coinId,
5398
5730
  symbol: raw.symbol,
5399
5731
  name: raw.name,
5400
5732
  decimals: raw.decimals,
5401
5733
  iconUrl: raw.iconUrl,
5402
- totalAmount: raw.totalAmount,
5403
- tokenCount: raw.tokenCount,
5404
- priceUsd: price?.priceUsd ?? null,
5405
- priceEur: price?.priceEur ?? null,
5406
- change24h: price?.change24h ?? null,
5407
- fiatValueUsd,
5408
- fiatValueEur
5734
+ totalAmount,
5735
+ tokenCount: raw.confirmedTokenCount + raw.unconfirmedTokenCount,
5736
+ confirmedAmount: raw.confirmedAmount.toString(),
5737
+ unconfirmedAmount: raw.unconfirmedAmount.toString(),
5738
+ confirmedTokenCount: raw.confirmedTokenCount,
5739
+ unconfirmedTokenCount: raw.unconfirmedTokenCount,
5740
+ priceUsd: null,
5741
+ priceEur: null,
5742
+ change24h: null,
5743
+ fiatValueUsd: null,
5744
+ fiatValueEur: null
5409
5745
  };
5410
5746
  });
5411
5747
  }
5412
5748
  /**
5413
- * Get all tokens
5749
+ * Get all tokens, optionally filtered by coin type and/or status.
5750
+ *
5751
+ * @param filter - Optional filter criteria.
5752
+ * @param filter.coinId - Return only tokens of this coin type.
5753
+ * @param filter.status - Return only tokens with this status (e.g. `'submitted'` for unconfirmed).
5754
+ * @returns Array of matching {@link Token} objects (synchronous).
5414
5755
  */
5415
5756
  getTokens(filter) {
5416
5757
  let tokens = Array.from(this.tokens.values());
@@ -5423,19 +5764,327 @@ var PaymentsModule = class _PaymentsModule {
5423
5764
  return tokens;
5424
5765
  }
5425
5766
  /**
5426
- * Get single token
5767
+ * Get a single token by its local ID.
5768
+ *
5769
+ * @param id - The local UUID assigned when the token was added.
5770
+ * @returns The token, or `undefined` if not found.
5427
5771
  */
5428
5772
  getToken(id) {
5429
5773
  return this.tokens.get(id);
5430
5774
  }
5431
5775
  // ===========================================================================
5776
+ // Public API - Unconfirmed Token Resolution
5777
+ // ===========================================================================
5778
+ /**
5779
+ * Attempt to resolve unconfirmed (status `'submitted'`) tokens by acquiring
5780
+ * their missing aggregator proofs.
5781
+ *
5782
+ * Each unconfirmed V5 token progresses through stages:
5783
+ * `RECEIVED` → `MINT_SUBMITTED` → `MINT_PROVEN` → `TRANSFER_SUBMITTED` → `FINALIZED`
5784
+ *
5785
+ * Uses 500 ms quick-timeouts per proof check so the call returns quickly even
5786
+ * when proofs are not yet available. Tokens that exceed 50 failed attempts are
5787
+ * marked `'invalid'`.
5788
+ *
5789
+ * Automatically called (fire-and-forget) by {@link load}.
5790
+ *
5791
+ * @returns Summary with counts of resolved, still-pending, and failed tokens plus per-token details.
5792
+ */
5793
+ async resolveUnconfirmed() {
5794
+ this.ensureInitialized();
5795
+ const result = {
5796
+ resolved: 0,
5797
+ stillPending: 0,
5798
+ failed: 0,
5799
+ details: []
5800
+ };
5801
+ const stClient = this.deps.oracle.getStateTransitionClient?.();
5802
+ const trustBase = this.deps.oracle.getTrustBase?.();
5803
+ if (!stClient || !trustBase) return result;
5804
+ const signingService = await this.createSigningService();
5805
+ for (const [tokenId, token] of this.tokens) {
5806
+ if (token.status !== "submitted") continue;
5807
+ const pending2 = this.parsePendingFinalization(token.sdkData);
5808
+ if (!pending2) {
5809
+ result.stillPending++;
5810
+ continue;
5811
+ }
5812
+ if (pending2.type === "v5_bundle") {
5813
+ const progress = await this.resolveV5Token(tokenId, token, pending2, stClient, trustBase, signingService);
5814
+ result.details.push({ tokenId, stage: pending2.stage, status: progress });
5815
+ if (progress === "resolved") result.resolved++;
5816
+ else if (progress === "failed") result.failed++;
5817
+ else result.stillPending++;
5818
+ }
5819
+ }
5820
+ if (result.resolved > 0 || result.failed > 0) {
5821
+ await this.save();
5822
+ }
5823
+ return result;
5824
+ }
5825
+ // ===========================================================================
5826
+ // Private - V5 Lazy Resolution Helpers
5827
+ // ===========================================================================
5828
+ /**
5829
+ * Process a single V5 token through its finalization stages with quick-timeout proof checks.
5830
+ */
5831
+ async resolveV5Token(tokenId, token, pending2, stClient, trustBase, signingService) {
5832
+ const bundle = JSON.parse(pending2.bundleJson);
5833
+ pending2.attemptCount++;
5834
+ pending2.lastAttemptAt = Date.now();
5835
+ try {
5836
+ if (pending2.stage === "RECEIVED") {
5837
+ const mintDataJson = JSON.parse(bundle.recipientMintData);
5838
+ const mintData = await import_MintTransactionData3.MintTransactionData.fromJSON(mintDataJson);
5839
+ const mintCommitment = await import_MintCommitment3.MintCommitment.create(mintData);
5840
+ const mintResponse = await stClient.submitMintCommitment(mintCommitment);
5841
+ if (mintResponse.status !== "SUCCESS" && mintResponse.status !== "REQUEST_ID_EXISTS") {
5842
+ throw new Error(`Mint submission failed: ${mintResponse.status}`);
5843
+ }
5844
+ pending2.stage = "MINT_SUBMITTED";
5845
+ this.updatePendingFinalization(token, pending2);
5846
+ }
5847
+ if (pending2.stage === "MINT_SUBMITTED") {
5848
+ const mintDataJson = JSON.parse(bundle.recipientMintData);
5849
+ const mintData = await import_MintTransactionData3.MintTransactionData.fromJSON(mintDataJson);
5850
+ const mintCommitment = await import_MintCommitment3.MintCommitment.create(mintData);
5851
+ const proof = await this.quickProofCheck(stClient, trustBase, mintCommitment);
5852
+ if (!proof) {
5853
+ this.updatePendingFinalization(token, pending2);
5854
+ return "pending";
5855
+ }
5856
+ pending2.mintProofJson = JSON.stringify(proof);
5857
+ pending2.stage = "MINT_PROVEN";
5858
+ this.updatePendingFinalization(token, pending2);
5859
+ }
5860
+ if (pending2.stage === "MINT_PROVEN") {
5861
+ const transferCommitmentJson = JSON.parse(bundle.transferCommitment);
5862
+ const transferCommitment = await import_TransferCommitment4.TransferCommitment.fromJSON(transferCommitmentJson);
5863
+ const transferResponse = await stClient.submitTransferCommitment(transferCommitment);
5864
+ if (transferResponse.status !== "SUCCESS" && transferResponse.status !== "REQUEST_ID_EXISTS") {
5865
+ throw new Error(`Transfer submission failed: ${transferResponse.status}`);
5866
+ }
5867
+ pending2.stage = "TRANSFER_SUBMITTED";
5868
+ this.updatePendingFinalization(token, pending2);
5869
+ }
5870
+ if (pending2.stage === "TRANSFER_SUBMITTED") {
5871
+ const transferCommitmentJson = JSON.parse(bundle.transferCommitment);
5872
+ const transferCommitment = await import_TransferCommitment4.TransferCommitment.fromJSON(transferCommitmentJson);
5873
+ const proof = await this.quickProofCheck(stClient, trustBase, transferCommitment);
5874
+ if (!proof) {
5875
+ this.updatePendingFinalization(token, pending2);
5876
+ return "pending";
5877
+ }
5878
+ const finalizedToken = await this.finalizeFromV5Bundle(bundle, pending2, signingService, stClient, trustBase);
5879
+ const confirmedToken = {
5880
+ id: token.id,
5881
+ coinId: token.coinId,
5882
+ symbol: token.symbol,
5883
+ name: token.name,
5884
+ decimals: token.decimals,
5885
+ iconUrl: token.iconUrl,
5886
+ amount: token.amount,
5887
+ status: "confirmed",
5888
+ createdAt: token.createdAt,
5889
+ updatedAt: Date.now(),
5890
+ sdkData: JSON.stringify(finalizedToken.toJSON())
5891
+ };
5892
+ this.tokens.set(tokenId, confirmedToken);
5893
+ await this.saveTokenToFileStorage(confirmedToken);
5894
+ await this.addToHistory({
5895
+ type: "RECEIVED",
5896
+ amount: confirmedToken.amount,
5897
+ coinId: confirmedToken.coinId,
5898
+ symbol: confirmedToken.symbol || "UNK",
5899
+ timestamp: Date.now(),
5900
+ senderPubkey: pending2.senderPubkey
5901
+ });
5902
+ this.log(`V5 token resolved: ${tokenId.slice(0, 8)}...`);
5903
+ return "resolved";
5904
+ }
5905
+ return "pending";
5906
+ } catch (error) {
5907
+ console.error(`[Payments] resolveV5Token failed for ${tokenId.slice(0, 8)}:`, error);
5908
+ if (pending2.attemptCount > 50) {
5909
+ token.status = "invalid";
5910
+ token.updatedAt = Date.now();
5911
+ this.tokens.set(tokenId, token);
5912
+ return "failed";
5913
+ }
5914
+ this.updatePendingFinalization(token, pending2);
5915
+ return "pending";
5916
+ }
5917
+ }
5918
+ /**
5919
+ * Non-blocking proof check with 500ms timeout.
5920
+ */
5921
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5922
+ async quickProofCheck(stClient, trustBase, commitment, timeoutMs = 500) {
5923
+ try {
5924
+ const proof = await Promise.race([
5925
+ (0, import_InclusionProofUtils5.waitInclusionProof)(trustBase, stClient, commitment),
5926
+ new Promise((resolve) => setTimeout(() => resolve(null), timeoutMs))
5927
+ ]);
5928
+ return proof;
5929
+ } catch {
5930
+ return null;
5931
+ }
5932
+ }
5933
+ /**
5934
+ * Perform V5 bundle finalization from stored bundle data and proofs.
5935
+ * Extracted from InstantSplitProcessor.processV5Bundle() steps 4-10.
5936
+ */
5937
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5938
+ async finalizeFromV5Bundle(bundle, pending2, signingService, stClient, trustBase) {
5939
+ const mintDataJson = JSON.parse(bundle.recipientMintData);
5940
+ const mintData = await import_MintTransactionData3.MintTransactionData.fromJSON(mintDataJson);
5941
+ const mintCommitment = await import_MintCommitment3.MintCommitment.create(mintData);
5942
+ const mintProofJson = JSON.parse(pending2.mintProofJson);
5943
+ const mintProof = import_InclusionProof.InclusionProof.fromJSON(mintProofJson);
5944
+ const mintTransaction = mintCommitment.toTransaction(mintProof);
5945
+ const tokenType = new import_TokenType3.TokenType(fromHex4(bundle.tokenTypeHex));
5946
+ const senderMintedStateJson = JSON.parse(bundle.mintedTokenStateJson);
5947
+ const tokenJson = {
5948
+ version: "2.0",
5949
+ state: senderMintedStateJson,
5950
+ genesis: mintTransaction.toJSON(),
5951
+ transactions: [],
5952
+ nametags: []
5953
+ };
5954
+ const mintedToken = await import_Token6.Token.fromJSON(tokenJson);
5955
+ const transferCommitmentJson = JSON.parse(bundle.transferCommitment);
5956
+ const transferCommitment = await import_TransferCommitment4.TransferCommitment.fromJSON(transferCommitmentJson);
5957
+ const transferProof = await (0, import_InclusionProofUtils5.waitInclusionProof)(trustBase, stClient, transferCommitment);
5958
+ const transferTransaction = transferCommitment.toTransaction(transferProof);
5959
+ const transferSalt = fromHex4(bundle.transferSaltHex);
5960
+ const recipientPredicate = await import_UnmaskedPredicate5.UnmaskedPredicate.create(
5961
+ mintData.tokenId,
5962
+ tokenType,
5963
+ signingService,
5964
+ import_HashAlgorithm5.HashAlgorithm.SHA256,
5965
+ transferSalt
5966
+ );
5967
+ const recipientState = new import_TokenState5.TokenState(recipientPredicate, null);
5968
+ let nametagTokens = [];
5969
+ const recipientAddressStr = bundle.recipientAddressJson;
5970
+ if (recipientAddressStr.startsWith("PROXY://")) {
5971
+ if (bundle.nametagTokenJson) {
5972
+ try {
5973
+ const nametagToken = await import_Token6.Token.fromJSON(JSON.parse(bundle.nametagTokenJson));
5974
+ const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
5975
+ const proxy = await ProxyAddress.fromTokenId(nametagToken.id);
5976
+ if (proxy.address === recipientAddressStr) {
5977
+ nametagTokens = [nametagToken];
5978
+ }
5979
+ } catch {
5980
+ }
5981
+ }
5982
+ if (nametagTokens.length === 0 && this.nametag?.token) {
5983
+ try {
5984
+ const nametagToken = await import_Token6.Token.fromJSON(this.nametag.token);
5985
+ const { ProxyAddress } = await import("@unicitylabs/state-transition-sdk/lib/address/ProxyAddress");
5986
+ const proxy = await ProxyAddress.fromTokenId(nametagToken.id);
5987
+ if (proxy.address === recipientAddressStr) {
5988
+ nametagTokens = [nametagToken];
5989
+ }
5990
+ } catch {
5991
+ }
5992
+ }
5993
+ }
5994
+ return stClient.finalizeTransaction(trustBase, mintedToken, recipientState, transferTransaction, nametagTokens);
5995
+ }
5996
+ /**
5997
+ * Parse pending finalization metadata from token's sdkData.
5998
+ */
5999
+ parsePendingFinalization(sdkData) {
6000
+ if (!sdkData) return null;
6001
+ try {
6002
+ const data = JSON.parse(sdkData);
6003
+ if (data._pendingFinalization && data._pendingFinalization.type === "v5_bundle") {
6004
+ return data._pendingFinalization;
6005
+ }
6006
+ return null;
6007
+ } catch {
6008
+ return null;
6009
+ }
6010
+ }
6011
+ /**
6012
+ * Update pending finalization metadata in token's sdkData.
6013
+ * Creates a new token object since sdkData is readonly.
6014
+ */
6015
+ updatePendingFinalization(token, pending2) {
6016
+ const updated = {
6017
+ id: token.id,
6018
+ coinId: token.coinId,
6019
+ symbol: token.symbol,
6020
+ name: token.name,
6021
+ decimals: token.decimals,
6022
+ iconUrl: token.iconUrl,
6023
+ amount: token.amount,
6024
+ status: token.status,
6025
+ createdAt: token.createdAt,
6026
+ updatedAt: Date.now(),
6027
+ sdkData: JSON.stringify({ _pendingFinalization: pending2 })
6028
+ };
6029
+ this.tokens.set(token.id, updated);
6030
+ }
6031
+ /**
6032
+ * Save pending V5 tokens to key-value storage.
6033
+ * These tokens can't be serialized to TXF format (no genesis/state),
6034
+ * so we persist them separately and restore on load().
6035
+ */
6036
+ async savePendingV5Tokens() {
6037
+ const pendingTokens = [];
6038
+ for (const token of this.tokens.values()) {
6039
+ if (this.parsePendingFinalization(token.sdkData)) {
6040
+ pendingTokens.push(token);
6041
+ }
6042
+ }
6043
+ if (pendingTokens.length > 0) {
6044
+ await this.deps.storage.set(
6045
+ STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS,
6046
+ JSON.stringify(pendingTokens)
6047
+ );
6048
+ } else {
6049
+ await this.deps.storage.set(STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS, "");
6050
+ }
6051
+ }
6052
+ /**
6053
+ * Load pending V5 tokens from key-value storage and merge into tokens map.
6054
+ * Called during load() to restore tokens that TXF format can't represent.
6055
+ */
6056
+ async loadPendingV5Tokens() {
6057
+ const data = await this.deps.storage.get(STORAGE_KEYS_ADDRESS.PENDING_V5_TOKENS);
6058
+ if (!data) return;
6059
+ try {
6060
+ const pendingTokens = JSON.parse(data);
6061
+ for (const token of pendingTokens) {
6062
+ if (!this.tokens.has(token.id)) {
6063
+ this.tokens.set(token.id, token);
6064
+ }
6065
+ }
6066
+ if (pendingTokens.length > 0) {
6067
+ this.log(`Restored ${pendingTokens.length} pending V5 token(s)`);
6068
+ }
6069
+ } catch {
6070
+ }
6071
+ }
6072
+ // ===========================================================================
5432
6073
  // Public API - Token Operations
5433
6074
  // ===========================================================================
5434
6075
  /**
5435
- * Add a token
5436
- * Tokens are uniquely identified by (tokenId, stateHash) composite key.
5437
- * Multiple historic states of the same token can coexist.
5438
- * @returns false if exact duplicate (same tokenId AND same stateHash)
6076
+ * Add a token to the wallet.
6077
+ *
6078
+ * Tokens are uniquely identified by a `(tokenId, stateHash)` composite key.
6079
+ * Duplicate detection:
6080
+ * - **Tombstoned** — rejected if the exact `(tokenId, stateHash)` pair has a tombstone.
6081
+ * - **Exact duplicate** — rejected if a token with the same composite key already exists.
6082
+ * - **State replacement** — if the same `tokenId` exists with a *different* `stateHash`,
6083
+ * the old state is archived and replaced with the incoming one.
6084
+ *
6085
+ * @param token - The token to add.
6086
+ * @param skipHistory - When `true`, do not create a `RECEIVED` transaction history entry (default `false`).
6087
+ * @returns `true` if the token was added, `false` if rejected as duplicate or tombstoned.
5439
6088
  */
5440
6089
  async addToken(token, skipHistory = false) {
5441
6090
  this.ensureInitialized();
@@ -5493,7 +6142,9 @@ var PaymentsModule = class _PaymentsModule {
5493
6142
  });
5494
6143
  }
5495
6144
  await this.save();
5496
- await this.saveTokenToFileStorage(token);
6145
+ if (!this.parsePendingFinalization(token.sdkData)) {
6146
+ await this.saveTokenToFileStorage(token);
6147
+ }
5497
6148
  this.log(`Added token ${token.id}, total: ${this.tokens.size}`);
5498
6149
  return true;
5499
6150
  }
@@ -5550,6 +6201,9 @@ var PaymentsModule = class _PaymentsModule {
5550
6201
  const data = fileData;
5551
6202
  const tokenJson = data.token;
5552
6203
  if (!tokenJson) continue;
6204
+ if (typeof tokenJson === "object" && tokenJson !== null && "_pendingFinalization" in tokenJson) {
6205
+ continue;
6206
+ }
5553
6207
  let sdkTokenId;
5554
6208
  if (typeof tokenJson === "object" && tokenJson !== null) {
5555
6209
  const tokenObj = tokenJson;
@@ -5601,7 +6255,12 @@ var PaymentsModule = class _PaymentsModule {
5601
6255
  this.log(`Loaded ${this.tokens.size} tokens from file storage`);
5602
6256
  }
5603
6257
  /**
5604
- * Update an existing token
6258
+ * Update an existing token or add it if not found.
6259
+ *
6260
+ * Looks up the token by genesis `tokenId` (from `sdkData`) first, then by
6261
+ * `token.id`. If no match is found, falls back to {@link addToken}.
6262
+ *
6263
+ * @param token - The token with updated data. Must include a valid `id`.
5605
6264
  */
5606
6265
  async updateToken(token) {
5607
6266
  this.ensureInitialized();
@@ -5625,7 +6284,15 @@ var PaymentsModule = class _PaymentsModule {
5625
6284
  this.log(`Updated token ${token.id}`);
5626
6285
  }
5627
6286
  /**
5628
- * Remove a token by ID
6287
+ * Remove a token from the wallet.
6288
+ *
6289
+ * The token is archived first, then a tombstone `(tokenId, stateHash)` is
6290
+ * created to prevent re-addition via Nostr re-delivery. A `SENT` history
6291
+ * entry is created unless `skipHistory` is `true`.
6292
+ *
6293
+ * @param tokenId - Local UUID of the token to remove.
6294
+ * @param recipientNametag - Optional nametag of the transfer recipient (for history).
6295
+ * @param skipHistory - When `true`, skip creating a transaction history entry (default `false`).
5629
6296
  */
5630
6297
  async removeToken(tokenId, recipientNametag, skipHistory = false) {
5631
6298
  this.ensureInitialized();
@@ -5687,13 +6354,22 @@ var PaymentsModule = class _PaymentsModule {
5687
6354
  // Public API - Tombstones
5688
6355
  // ===========================================================================
5689
6356
  /**
5690
- * Get all tombstones
6357
+ * Get all tombstone entries.
6358
+ *
6359
+ * Each tombstone is keyed by `(tokenId, stateHash)` and prevents a spent
6360
+ * token state from being re-added (e.g. via Nostr re-delivery).
6361
+ *
6362
+ * @returns A shallow copy of the tombstone array.
5691
6363
  */
5692
6364
  getTombstones() {
5693
6365
  return [...this.tombstones];
5694
6366
  }
5695
6367
  /**
5696
- * Check if token state is tombstoned
6368
+ * Check whether a specific `(tokenId, stateHash)` combination is tombstoned.
6369
+ *
6370
+ * @param tokenId - The genesis token ID.
6371
+ * @param stateHash - The state hash of the token version to check.
6372
+ * @returns `true` if the exact combination has been tombstoned.
5697
6373
  */
5698
6374
  isStateTombstoned(tokenId, stateHash) {
5699
6375
  return this.tombstones.some(
@@ -5701,8 +6377,13 @@ var PaymentsModule = class _PaymentsModule {
5701
6377
  );
5702
6378
  }
5703
6379
  /**
5704
- * Merge remote tombstones
5705
- * @returns number of local tokens removed
6380
+ * Merge tombstones received from a remote sync source.
6381
+ *
6382
+ * Any local token whose `(tokenId, stateHash)` matches a remote tombstone is
6383
+ * removed. The remote tombstones are then added to the local set (union merge).
6384
+ *
6385
+ * @param remoteTombstones - Tombstone entries from the remote source.
6386
+ * @returns Number of local tokens that were removed.
5706
6387
  */
5707
6388
  async mergeTombstones(remoteTombstones) {
5708
6389
  this.ensureInitialized();
@@ -5738,7 +6419,9 @@ var PaymentsModule = class _PaymentsModule {
5738
6419
  return removedCount;
5739
6420
  }
5740
6421
  /**
5741
- * Prune old tombstones
6422
+ * Remove tombstones older than `maxAge` and cap the list at 100 entries.
6423
+ *
6424
+ * @param maxAge - Maximum age in milliseconds (default: 30 days).
5742
6425
  */
5743
6426
  async pruneTombstones(maxAge) {
5744
6427
  const originalCount = this.tombstones.length;
@@ -5752,20 +6435,38 @@ var PaymentsModule = class _PaymentsModule {
5752
6435
  // Public API - Archives
5753
6436
  // ===========================================================================
5754
6437
  /**
5755
- * Get archived tokens
6438
+ * Get all archived (spent/superseded) tokens in TXF format.
6439
+ *
6440
+ * Archived tokens are kept for recovery and sync purposes. The map key is
6441
+ * the genesis token ID.
6442
+ *
6443
+ * @returns A shallow copy of the archived token map.
5756
6444
  */
5757
6445
  getArchivedTokens() {
5758
6446
  return new Map(this.archivedTokens);
5759
6447
  }
5760
6448
  /**
5761
- * Get best archived version of a token
6449
+ * Get the best (most committed transactions) archived version of a token.
6450
+ *
6451
+ * Searches both archived and forked token maps and returns the version with
6452
+ * the highest number of committed transactions.
6453
+ *
6454
+ * @param tokenId - The genesis token ID to look up.
6455
+ * @returns The best TXF token version, or `null` if not found.
5762
6456
  */
5763
6457
  getBestArchivedVersion(tokenId) {
5764
6458
  return findBestTokenVersion(tokenId, this.archivedTokens, this.forkedTokens);
5765
6459
  }
5766
6460
  /**
5767
- * Merge remote archived tokens
5768
- * @returns number of tokens updated/added
6461
+ * Merge archived tokens from a remote sync source.
6462
+ *
6463
+ * For each remote token:
6464
+ * - If missing locally, it is added.
6465
+ * - If the remote version is an incremental update of the local, it replaces it.
6466
+ * - If the histories diverge (fork), the remote version is stored via {@link storeForkedToken}.
6467
+ *
6468
+ * @param remoteArchived - Map of genesis token ID → TXF token from remote.
6469
+ * @returns Number of tokens that were updated or added locally.
5769
6470
  */
5770
6471
  async mergeArchivedTokens(remoteArchived) {
5771
6472
  let mergedCount = 0;
@@ -5788,7 +6489,11 @@ var PaymentsModule = class _PaymentsModule {
5788
6489
  return mergedCount;
5789
6490
  }
5790
6491
  /**
5791
- * Prune archived tokens
6492
+ * Prune archived tokens to keep at most `maxCount` entries.
6493
+ *
6494
+ * Oldest entries (by insertion order) are removed first.
6495
+ *
6496
+ * @param maxCount - Maximum number of archived tokens to retain (default: 100).
5792
6497
  */
5793
6498
  async pruneArchivedTokens(maxCount = 100) {
5794
6499
  if (this.archivedTokens.size <= maxCount) return;
@@ -5801,13 +6506,24 @@ var PaymentsModule = class _PaymentsModule {
5801
6506
  // Public API - Forked Tokens
5802
6507
  // ===========================================================================
5803
6508
  /**
5804
- * Get forked tokens
6509
+ * Get all forked token versions.
6510
+ *
6511
+ * Forked tokens represent alternative histories detected during sync.
6512
+ * The map key is `{tokenId}_{stateHash}`.
6513
+ *
6514
+ * @returns A shallow copy of the forked tokens map.
5805
6515
  */
5806
6516
  getForkedTokens() {
5807
6517
  return new Map(this.forkedTokens);
5808
6518
  }
5809
6519
  /**
5810
- * Store a forked token
6520
+ * Store a forked token version (alternative history).
6521
+ *
6522
+ * No-op if the exact `(tokenId, stateHash)` key already exists.
6523
+ *
6524
+ * @param tokenId - Genesis token ID.
6525
+ * @param stateHash - State hash of this forked version.
6526
+ * @param txfToken - The TXF token data to store.
5811
6527
  */
5812
6528
  async storeForkedToken(tokenId, stateHash, txfToken) {
5813
6529
  const key = `${tokenId}_${stateHash}`;
@@ -5817,8 +6533,10 @@ var PaymentsModule = class _PaymentsModule {
5817
6533
  await this.save();
5818
6534
  }
5819
6535
  /**
5820
- * Merge remote forked tokens
5821
- * @returns number of tokens added
6536
+ * Merge forked tokens from a remote sync source. Only new keys are added.
6537
+ *
6538
+ * @param remoteForked - Map of `{tokenId}_{stateHash}` → TXF token from remote.
6539
+ * @returns Number of new forked tokens added.
5822
6540
  */
5823
6541
  async mergeForkedTokens(remoteForked) {
5824
6542
  let addedCount = 0;
@@ -5834,7 +6552,9 @@ var PaymentsModule = class _PaymentsModule {
5834
6552
  return addedCount;
5835
6553
  }
5836
6554
  /**
5837
- * Prune forked tokens
6555
+ * Prune forked tokens to keep at most `maxCount` entries.
6556
+ *
6557
+ * @param maxCount - Maximum number of forked tokens to retain (default: 50).
5838
6558
  */
5839
6559
  async pruneForkedTokens(maxCount = 50) {
5840
6560
  if (this.forkedTokens.size <= maxCount) return;
@@ -5847,13 +6567,19 @@ var PaymentsModule = class _PaymentsModule {
5847
6567
  // Public API - Transaction History
5848
6568
  // ===========================================================================
5849
6569
  /**
5850
- * Get transaction history
6570
+ * Get the transaction history sorted newest-first.
6571
+ *
6572
+ * @returns Array of {@link TransactionHistoryEntry} objects in descending timestamp order.
5851
6573
  */
5852
6574
  getHistory() {
5853
6575
  return [...this.transactionHistory].sort((a, b) => b.timestamp - a.timestamp);
5854
6576
  }
5855
6577
  /**
5856
- * Add to transaction history
6578
+ * Append an entry to the transaction history.
6579
+ *
6580
+ * A unique `id` is auto-generated. The entry is immediately persisted to storage.
6581
+ *
6582
+ * @param entry - History entry fields (without `id`).
5857
6583
  */
5858
6584
  async addToHistory(entry) {
5859
6585
  this.ensureInitialized();
@@ -5871,7 +6597,11 @@ var PaymentsModule = class _PaymentsModule {
5871
6597
  // Public API - Nametag
5872
6598
  // ===========================================================================
5873
6599
  /**
5874
- * Set nametag for current identity
6600
+ * Set the nametag data for the current identity.
6601
+ *
6602
+ * Persists to both key-value storage and file storage (lottery compatibility).
6603
+ *
6604
+ * @param nametag - The nametag data including minted token JSON.
5875
6605
  */
5876
6606
  async setNametag(nametag) {
5877
6607
  this.ensureInitialized();
@@ -5881,19 +6611,23 @@ var PaymentsModule = class _PaymentsModule {
5881
6611
  this.log(`Nametag set: ${nametag.name}`);
5882
6612
  }
5883
6613
  /**
5884
- * Get nametag
6614
+ * Get the current nametag data.
6615
+ *
6616
+ * @returns The nametag data, or `null` if no nametag is set.
5885
6617
  */
5886
6618
  getNametag() {
5887
6619
  return this.nametag;
5888
6620
  }
5889
6621
  /**
5890
- * Check if has nametag
6622
+ * Check whether a nametag is currently set.
6623
+ *
6624
+ * @returns `true` if nametag data is present.
5891
6625
  */
5892
6626
  hasNametag() {
5893
6627
  return this.nametag !== null;
5894
6628
  }
5895
6629
  /**
5896
- * Clear nametag
6630
+ * Remove the current nametag data from memory and storage.
5897
6631
  */
5898
6632
  async clearNametag() {
5899
6633
  this.ensureInitialized();
@@ -5987,9 +6721,9 @@ var PaymentsModule = class _PaymentsModule {
5987
6721
  try {
5988
6722
  const signingService = await this.createSigningService();
5989
6723
  const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
5990
- const { TokenType: TokenType5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
6724
+ const { TokenType: TokenType6 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
5991
6725
  const UNICITY_TOKEN_TYPE_HEX3 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
5992
- const tokenType = new TokenType5(Buffer.from(UNICITY_TOKEN_TYPE_HEX3, "hex"));
6726
+ const tokenType = new TokenType6(Buffer.from(UNICITY_TOKEN_TYPE_HEX3, "hex"));
5993
6727
  const addressRef = await UnmaskedPredicateReference4.create(
5994
6728
  tokenType,
5995
6729
  signingService.algorithm,
@@ -6050,11 +6784,27 @@ var PaymentsModule = class _PaymentsModule {
6050
6784
  // Public API - Sync & Validate
6051
6785
  // ===========================================================================
6052
6786
  /**
6053
- * Sync with all token storage providers (IPFS, MongoDB, etc.)
6054
- * Syncs with each provider and merges results
6787
+ * Sync local token state with all configured token storage providers (IPFS, file, etc.).
6788
+ *
6789
+ * For each provider, the local data is packaged into TXF storage format, sent
6790
+ * to the provider's `sync()` method, and the merged result is applied locally.
6791
+ * Emits `sync:started`, `sync:completed`, and `sync:error` events.
6792
+ *
6793
+ * @returns Summary with counts of tokens added and removed during sync.
6055
6794
  */
6056
6795
  async sync() {
6057
6796
  this.ensureInitialized();
6797
+ if (this._syncInProgress) {
6798
+ return this._syncInProgress;
6799
+ }
6800
+ this._syncInProgress = this._doSync();
6801
+ try {
6802
+ return await this._syncInProgress;
6803
+ } finally {
6804
+ this._syncInProgress = null;
6805
+ }
6806
+ }
6807
+ async _doSync() {
6058
6808
  this.deps.emitEvent("sync:started", { source: "payments" });
6059
6809
  try {
6060
6810
  const providers = this.getTokenStorageProviders();
@@ -6092,6 +6842,9 @@ var PaymentsModule = class _PaymentsModule {
6092
6842
  });
6093
6843
  }
6094
6844
  }
6845
+ if (totalAdded > 0 || totalRemoved > 0) {
6846
+ await this.save();
6847
+ }
6095
6848
  this.deps.emitEvent("sync:completed", {
6096
6849
  source: "payments",
6097
6850
  count: this.tokens.size
@@ -6105,6 +6858,66 @@ var PaymentsModule = class _PaymentsModule {
6105
6858
  throw error;
6106
6859
  }
6107
6860
  }
6861
+ // ===========================================================================
6862
+ // Storage Event Subscription (Push-Based Sync)
6863
+ // ===========================================================================
6864
+ /**
6865
+ * Subscribe to 'storage:remote-updated' events from all token storage providers.
6866
+ * When a provider emits this event, a debounced sync is triggered.
6867
+ */
6868
+ subscribeToStorageEvents() {
6869
+ this.unsubscribeStorageEvents();
6870
+ const providers = this.getTokenStorageProviders();
6871
+ for (const [providerId, provider] of providers) {
6872
+ if (provider.onEvent) {
6873
+ const unsub = provider.onEvent((event) => {
6874
+ if (event.type === "storage:remote-updated") {
6875
+ this.log("Remote update detected from provider", providerId, event.data);
6876
+ this.debouncedSyncFromRemoteUpdate(providerId, event.data);
6877
+ }
6878
+ });
6879
+ this.storageEventUnsubscribers.push(unsub);
6880
+ }
6881
+ }
6882
+ }
6883
+ /**
6884
+ * Unsubscribe from all storage provider events and clear debounce timer.
6885
+ */
6886
+ unsubscribeStorageEvents() {
6887
+ for (const unsub of this.storageEventUnsubscribers) {
6888
+ unsub();
6889
+ }
6890
+ this.storageEventUnsubscribers = [];
6891
+ if (this.syncDebounceTimer) {
6892
+ clearTimeout(this.syncDebounceTimer);
6893
+ this.syncDebounceTimer = null;
6894
+ }
6895
+ }
6896
+ /**
6897
+ * Debounced sync triggered by a storage:remote-updated event.
6898
+ * Waits 500ms to batch rapid updates, then performs sync.
6899
+ */
6900
+ debouncedSyncFromRemoteUpdate(providerId, eventData) {
6901
+ if (this.syncDebounceTimer) {
6902
+ clearTimeout(this.syncDebounceTimer);
6903
+ }
6904
+ this.syncDebounceTimer = setTimeout(() => {
6905
+ this.syncDebounceTimer = null;
6906
+ this.sync().then((result) => {
6907
+ const data = eventData;
6908
+ this.deps?.emitEvent("sync:remote-update", {
6909
+ providerId,
6910
+ name: data?.name ?? "",
6911
+ sequence: data?.sequence ?? 0,
6912
+ cid: data?.cid ?? "",
6913
+ added: result.added,
6914
+ removed: result.removed
6915
+ });
6916
+ }).catch((err) => {
6917
+ this.log("Auto-sync from remote update failed:", err);
6918
+ });
6919
+ }, _PaymentsModule.SYNC_DEBOUNCE_MS);
6920
+ }
6108
6921
  /**
6109
6922
  * Get all active token storage providers
6110
6923
  */
@@ -6120,15 +6933,24 @@ var PaymentsModule = class _PaymentsModule {
6120
6933
  return /* @__PURE__ */ new Map();
6121
6934
  }
6122
6935
  /**
6123
- * Update token storage providers (called when providers are added/removed dynamically)
6936
+ * Replace the set of token storage providers at runtime.
6937
+ *
6938
+ * Use when providers are added or removed dynamically (e.g. IPFS node started).
6939
+ *
6940
+ * @param providers - New map of provider ID → TokenStorageProvider.
6124
6941
  */
6125
6942
  updateTokenStorageProviders(providers) {
6126
6943
  if (this.deps) {
6127
6944
  this.deps.tokenStorageProviders = providers;
6945
+ this.subscribeToStorageEvents();
6128
6946
  }
6129
6947
  }
6130
6948
  /**
6131
- * Validate tokens with aggregator
6949
+ * Validate all tokens against the aggregator (oracle provider).
6950
+ *
6951
+ * Tokens that fail validation or are detected as spent are marked `'invalid'`.
6952
+ *
6953
+ * @returns Object with arrays of valid and invalid tokens.
6132
6954
  */
6133
6955
  async validate() {
6134
6956
  this.ensureInitialized();
@@ -6149,7 +6971,9 @@ var PaymentsModule = class _PaymentsModule {
6149
6971
  return { valid, invalid };
6150
6972
  }
6151
6973
  /**
6152
- * Get pending transfers
6974
+ * Get all in-progress (pending) outgoing transfers.
6975
+ *
6976
+ * @returns Array of {@link TransferResult} objects for transfers that have not yet completed.
6153
6977
  */
6154
6978
  getPendingTransfers() {
6155
6979
  return Array.from(this.pendingTransfers.values());
@@ -6213,9 +7037,9 @@ var PaymentsModule = class _PaymentsModule {
6213
7037
  */
6214
7038
  async createDirectAddressFromPubkey(pubkeyHex) {
6215
7039
  const { UnmaskedPredicateReference: UnmaskedPredicateReference4 } = await import("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
6216
- const { TokenType: TokenType5 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
7040
+ const { TokenType: TokenType6 } = await import("@unicitylabs/state-transition-sdk/lib/token/TokenType");
6217
7041
  const UNICITY_TOKEN_TYPE_HEX3 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
6218
- const tokenType = new TokenType5(Buffer.from(UNICITY_TOKEN_TYPE_HEX3, "hex"));
7042
+ const tokenType = new TokenType6(Buffer.from(UNICITY_TOKEN_TYPE_HEX3, "hex"));
6219
7043
  const pubkeyBytes = new Uint8Array(
6220
7044
  pubkeyHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
6221
7045
  );
@@ -6427,7 +7251,8 @@ var PaymentsModule = class _PaymentsModule {
6427
7251
  this.deps.emitEvent("transfer:confirmed", {
6428
7252
  id: crypto.randomUUID(),
6429
7253
  status: "completed",
6430
- tokens: [finalizedToken]
7254
+ tokens: [finalizedToken],
7255
+ tokenTransfers: []
6431
7256
  });
6432
7257
  await this.addToHistory({
6433
7258
  type: "RECEIVED",
@@ -6450,14 +7275,26 @@ var PaymentsModule = class _PaymentsModule {
6450
7275
  async handleIncomingTransfer(transfer) {
6451
7276
  try {
6452
7277
  const payload = transfer.payload;
7278
+ let instantBundle = null;
6453
7279
  if (isInstantSplitBundle(payload)) {
7280
+ instantBundle = payload;
7281
+ } else if (payload.token) {
7282
+ try {
7283
+ const inner = typeof payload.token === "string" ? JSON.parse(payload.token) : payload.token;
7284
+ if (isInstantSplitBundle(inner)) {
7285
+ instantBundle = inner;
7286
+ }
7287
+ } catch {
7288
+ }
7289
+ }
7290
+ if (instantBundle) {
6454
7291
  this.log("Processing INSTANT_SPLIT bundle...");
6455
7292
  try {
6456
7293
  if (!this.nametag) {
6457
7294
  await this.loadNametagFromFileStorage();
6458
7295
  }
6459
7296
  const result = await this.processInstantSplitBundle(
6460
- payload,
7297
+ instantBundle,
6461
7298
  transfer.senderTransportPubkey
6462
7299
  );
6463
7300
  if (result.success) {
@@ -6470,6 +7307,11 @@ var PaymentsModule = class _PaymentsModule {
6470
7307
  }
6471
7308
  return;
6472
7309
  }
7310
+ if (payload.sourceToken && payload.commitmentData && !payload.transferTx) {
7311
+ this.log("Processing NOSTR-FIRST commitment-only transfer...");
7312
+ await this.handleCommitmentOnlyTransfer(transfer, payload);
7313
+ return;
7314
+ }
6473
7315
  let tokenData;
6474
7316
  let finalizedSdkToken = null;
6475
7317
  if (payload.sourceToken && payload.transferTx) {
@@ -6625,6 +7467,7 @@ var PaymentsModule = class _PaymentsModule {
6625
7467
  console.error(`[Payments] Failed to save to provider ${id}:`, err);
6626
7468
  }
6627
7469
  }
7470
+ await this.savePendingV5Tokens();
6628
7471
  }
6629
7472
  async saveToOutbox(transfer, recipient) {
6630
7473
  const outbox = await this.loadOutbox();
@@ -6642,8 +7485,7 @@ var PaymentsModule = class _PaymentsModule {
6642
7485
  }
6643
7486
  async createStorageData() {
6644
7487
  return await buildTxfStorageData(
6645
- [],
6646
- // Empty - active tokens stored as token-xxx files
7488
+ Array.from(this.tokens.values()),
6647
7489
  {
6648
7490
  version: 1,
6649
7491
  address: this.deps.identity.l1Address,
@@ -6828,7 +7670,7 @@ function createPaymentsModule(config) {
6828
7670
  // modules/payments/TokenRecoveryService.ts
6829
7671
  var import_TokenId4 = require("@unicitylabs/state-transition-sdk/lib/token/TokenId");
6830
7672
  var import_TokenState6 = require("@unicitylabs/state-transition-sdk/lib/token/TokenState");
6831
- var import_TokenType3 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
7673
+ var import_TokenType4 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
6832
7674
  var import_CoinId5 = require("@unicitylabs/state-transition-sdk/lib/token/fungible/CoinId");
6833
7675
  var import_HashAlgorithm6 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
6834
7676
  var import_UnmaskedPredicate6 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicate");
@@ -7892,15 +8734,20 @@ async function parseAndDecryptWalletDat(data, password, onProgress) {
7892
8734
 
7893
8735
  // core/Sphere.ts
7894
8736
  var import_SigningService2 = require("@unicitylabs/state-transition-sdk/lib/sign/SigningService");
7895
- var import_TokenType4 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
8737
+ var import_TokenType5 = require("@unicitylabs/state-transition-sdk/lib/token/TokenType");
7896
8738
  var import_HashAlgorithm7 = require("@unicitylabs/state-transition-sdk/lib/hash/HashAlgorithm");
7897
8739
  var import_UnmaskedPredicateReference3 = require("@unicitylabs/state-transition-sdk/lib/predicate/embedded/UnmaskedPredicateReference");
8740
+ var import_nostr_js_sdk2 = require("@unicitylabs/nostr-js-sdk");
8741
+ function isValidNametag(nametag) {
8742
+ if ((0, import_nostr_js_sdk2.isPhoneNumber)(nametag)) return true;
8743
+ return /^[a-z0-9_-]{3,20}$/.test(nametag);
8744
+ }
7898
8745
  var UNICITY_TOKEN_TYPE_HEX2 = "f8aa13834268d29355ff12183066f0cb902003629bbc5eb9ef0efbe397867509";
7899
8746
  async function deriveL3PredicateAddress(privateKey) {
7900
8747
  const secret = Buffer.from(privateKey, "hex");
7901
8748
  const signingService = await import_SigningService2.SigningService.createFromSecret(secret);
7902
8749
  const tokenTypeBytes = Buffer.from(UNICITY_TOKEN_TYPE_HEX2, "hex");
7903
- const tokenType = new import_TokenType4.TokenType(tokenTypeBytes);
8750
+ const tokenType = new import_TokenType5.TokenType(tokenTypeBytes);
7904
8751
  const predicateRef = import_UnmaskedPredicateReference3.UnmaskedPredicateReference.create(
7905
8752
  tokenType,
7906
8753
  signingService.algorithm,
@@ -8176,6 +9023,14 @@ var Sphere = class _Sphere {
8176
9023
  console.log("[Sphere.import] Registering nametag...");
8177
9024
  await sphere.registerNametag(options.nametag);
8178
9025
  }
9026
+ if (sphere._tokenStorageProviders.size > 0) {
9027
+ try {
9028
+ const syncResult = await sphere._payments.sync();
9029
+ console.log(`[Sphere.import] Auto-sync: +${syncResult.added} -${syncResult.removed}`);
9030
+ } catch (err) {
9031
+ console.warn("[Sphere.import] Auto-sync failed (non-fatal):", err);
9032
+ }
9033
+ }
8179
9034
  console.log("[Sphere.import] Import complete");
8180
9035
  return sphere;
8181
9036
  }
@@ -9046,9 +9901,9 @@ var Sphere = class _Sphere {
9046
9901
  if (index < 0) {
9047
9902
  throw new Error("Address index must be non-negative");
9048
9903
  }
9049
- const newNametag = options?.nametag?.startsWith("@") ? options.nametag.slice(1) : options?.nametag;
9050
- if (newNametag && !this.validateNametag(newNametag)) {
9051
- throw new Error("Invalid nametag format. Use alphanumeric characters, 3-20 chars.");
9904
+ const newNametag = options?.nametag ? this.cleanNametag(options.nametag) : void 0;
9905
+ if (newNametag && !isValidNametag(newNametag)) {
9906
+ throw new Error("Invalid nametag format. Use lowercase alphanumeric, underscore, or hyphen (3-20 chars), or a valid phone number.");
9052
9907
  }
9053
9908
  const addressInfo = this.deriveAddress(index, false);
9054
9909
  const ipnsHash = sha256(addressInfo.publicKey, "hex").slice(0, 40);
@@ -9432,9 +10287,9 @@ var Sphere = class _Sphere {
9432
10287
  */
9433
10288
  async registerNametag(nametag) {
9434
10289
  this.ensureReady();
9435
- const cleanNametag = nametag.startsWith("@") ? nametag.slice(1) : nametag;
9436
- if (!this.validateNametag(cleanNametag)) {
9437
- throw new Error("Invalid nametag format. Use alphanumeric characters, 3-20 chars.");
10290
+ const cleanNametag = this.cleanNametag(nametag);
10291
+ if (!isValidNametag(cleanNametag)) {
10292
+ throw new Error("Invalid nametag format. Use lowercase alphanumeric, underscore, or hyphen (3-20 chars), or a valid phone number.");
9438
10293
  }
9439
10294
  if (this._identity?.nametag) {
9440
10295
  throw new Error(`Nametag already registered for address ${this._currentAddressIndex}: @${this._identity.nametag}`);
@@ -9743,13 +10598,11 @@ var Sphere = class _Sphere {
9743
10598
  }
9744
10599
  }
9745
10600
  /**
9746
- * Validate nametag format
10601
+ * Strip @ prefix and normalize a nametag (lowercase, phone E.164, strip @unicity suffix).
9747
10602
  */
9748
- validateNametag(nametag) {
9749
- const pattern = new RegExp(
9750
- `^[a-zA-Z0-9_-]{${LIMITS.NAMETAG_MIN_LENGTH},${LIMITS.NAMETAG_MAX_LENGTH}}$`
9751
- );
9752
- return pattern.test(nametag);
10603
+ cleanNametag(raw) {
10604
+ const stripped = raw.startsWith("@") ? raw.slice(1) : raw;
10605
+ return (0, import_nostr_js_sdk2.normalizeNametag)(stripped);
9753
10606
  }
9754
10607
  // ===========================================================================
9755
10608
  // Public Methods - Lifecycle
@@ -10449,6 +11302,9 @@ function createTokenValidator(options) {
10449
11302
  return new TokenValidator(options);
10450
11303
  }
10451
11304
 
11305
+ // index.ts
11306
+ var import_nostr_js_sdk3 = require("@unicitylabs/nostr-js-sdk");
11307
+
10452
11308
  // price/CoinGeckoPriceProvider.ts
10453
11309
  var CoinGeckoPriceProvider = class {
10454
11310
  platform = "coingecko";
@@ -10585,6 +11441,7 @@ function createPriceProvider(config) {
10585
11441
  TokenRegistry,
10586
11442
  TokenValidator,
10587
11443
  archivedKeyFromTokenId,
11444
+ areSameNametag,
10588
11445
  base58Decode,
10589
11446
  base58Encode,
10590
11447
  buildTxfStorageData,
@@ -10632,6 +11489,7 @@ function createPriceProvider(config) {
10632
11489
  hasUncommittedTransactions,
10633
11490
  hasValidTxfData,
10634
11491
  hash160,
11492
+ hashNametag,
10635
11493
  hexToBytes,
10636
11494
  identityFromMnemonicSync,
10637
11495
  initSphere,
@@ -10643,10 +11501,12 @@ function createPriceProvider(config) {
10643
11501
  isKnownToken,
10644
11502
  isPaymentSessionTerminal,
10645
11503
  isPaymentSessionTimedOut,
11504
+ isPhoneNumber,
10646
11505
  isSQLiteDatabase,
10647
11506
  isTextWalletEncrypted,
10648
11507
  isTokenKey,
10649
11508
  isValidBech32,
11509
+ isValidNametag,
10650
11510
  isValidPrivateKey,
10651
11511
  isValidTokenId,
10652
11512
  isWalletDatEncrypted,
@@ -10654,6 +11514,7 @@ function createPriceProvider(config) {
10654
11514
  keyFromTokenId,
10655
11515
  loadSphere,
10656
11516
  mnemonicToSeedSync,
11517
+ normalizeNametag,
10657
11518
  normalizeSdkTokenToStorage,
10658
11519
  objectToTxf,
10659
11520
  parseAndDecryptWalletDat,