@unicitylabs/sphere-sdk 0.14.11-dev.8 → 0.15.0-dev.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.
Files changed (46) hide show
  1. package/README.md +55 -17
  2. package/dist/connect/index.cjs +10 -17
  3. package/dist/connect/index.cjs.map +1 -1
  4. package/dist/connect/index.js +10 -17
  5. package/dist/connect/index.js.map +1 -1
  6. package/dist/core/index.cjs +98 -99
  7. package/dist/core/index.cjs.map +1 -1
  8. package/dist/core/index.d.cts +5 -14
  9. package/dist/core/index.d.ts +5 -14
  10. package/dist/core/index.js +98 -99
  11. package/dist/core/index.js.map +1 -1
  12. package/dist/impl/browser/connect/index.cjs +3 -3
  13. package/dist/impl/browser/connect/index.cjs.map +1 -1
  14. package/dist/impl/browser/connect/index.js +3 -3
  15. package/dist/impl/browser/connect/index.js.map +1 -1
  16. package/dist/impl/browser/index.cjs.map +1 -1
  17. package/dist/impl/browser/index.js.map +1 -1
  18. package/dist/impl/nodejs/connect/index.cjs +2 -2
  19. package/dist/impl/nodejs/connect/index.cjs.map +1 -1
  20. package/dist/impl/nodejs/connect/index.js +2 -2
  21. package/dist/impl/nodejs/connect/index.js.map +1 -1
  22. package/dist/impl/nodejs/index.cjs.map +1 -1
  23. package/dist/impl/nodejs/index.js.map +1 -1
  24. package/dist/impl/wallet-api-v2/index.cjs +95 -1
  25. package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
  26. package/dist/impl/wallet-api-v2/index.js +95 -1
  27. package/dist/impl/wallet-api-v2/index.js.map +1 -1
  28. package/dist/index.cjs +98 -99
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +6 -11
  31. package/dist/index.d.ts +6 -11
  32. package/dist/index.js +98 -99
  33. package/dist/index.js.map +1 -1
  34. package/dist/modules/payments-v2/index.cjs +149 -193
  35. package/dist/modules/payments-v2/index.cjs.map +1 -1
  36. package/dist/modules/payments-v2/index.d.cts +8 -5
  37. package/dist/modules/payments-v2/index.d.ts +8 -5
  38. package/dist/modules/payments-v2/index.js +146 -192
  39. package/dist/modules/payments-v2/index.js.map +1 -1
  40. package/dist/token-engine/index.cjs +43 -57
  41. package/dist/token-engine/index.cjs.map +1 -1
  42. package/dist/token-engine/index.d.cts +0 -4
  43. package/dist/token-engine/index.d.ts +0 -4
  44. package/dist/token-engine/index.js +43 -57
  45. package/dist/token-engine/index.js.map +1 -1
  46. package/package.json +2 -2
@@ -2286,10 +2286,6 @@ interface SphereValue {
2286
2286
  * `token`, so it is NOT stored — only cached at runtime on SphereToken.value.
2287
2287
  */
2288
2288
  interface TokenBlob {
2289
- /** Blob format version (sphere storage migrations; independent of SDK CBOR). */
2290
- readonly v: number;
2291
- /** NetworkId.id the token belongs to (mainnet=1 / testnet=2 / local=3). */
2292
- readonly network: number;
2293
2289
  /**
2294
2290
  * Genesis-stable token id — 64-char lowercase hex of the v2 `TokenId.bytes`
2295
2291
  * (same across every state of the token). Stored on the blob so dedup / listing
@@ -3048,8 +3044,6 @@ type InitProgressCallback = (progress: InitProgress) => void;
3048
3044
  interface SphereWalletApiOptions {
3049
3045
  /** Wallet-api transport config — REQUIRED (init throws INVALID_CONFIG without it). */
3050
3046
  walletApi?: WalletApiTransportConfig;
3051
- /** @deprecated The payments-v2 vertical is the only path since the P11 flip — accepted as a no-op for one release. */
3052
- paymentsV2?: boolean;
3053
3047
  /** @deprecated REMOVED with the P11 flip — any truthy value throws INVALID_CONFIG (invoicing no longer exists in the SDK). */
3054
3048
  accounting?: unknown;
3055
3049
  /** @deprecated REMOVED with the P11 flip — any truthy value throws INVALID_CONFIG (swaps no longer exist in the SDK). */
@@ -3398,11 +3392,10 @@ declare class Sphere {
3398
3392
  */
3399
3393
  private static resolveMarketConfig;
3400
3394
  /**
3401
- * The ONE sanctioned refusal fossil of the P11 flip (revisit-and-delete after
3402
- * one release): `accounting`/`swap` were public init flags — silently
3403
- * ignoring them would hide that invoices/swaps no longer exist in the SDK.
3404
- * `paymentsV2: true` stays an accepted no-op for the same one release (the
3405
- * live frontend passes it today).
3395
+ * The ONE sanctioned refusal fossil of the P11 flip: `accounting`/`swap` were
3396
+ * public init flags — silently ignoring them would hide that invoices/swaps no
3397
+ * longer exist in the SDK. Kept through 0.15.0, where consumers re-integrate
3398
+ * across the wire break and a silent no-op would be worst.
3406
3399
  */
3407
3400
  private static refuseRetiredModuleOptions;
3408
3401
  /**
@@ -3429,7 +3422,7 @@ declare class Sphere {
3429
3422
  static import(options: SphereImportOptions): Promise<Sphere>;
3430
3423
  /**
3431
3424
  * Clear all SDK-owned wallet data from storage: wallet keys, per-address
3432
- * data, and the payments vertical's `pv2:{network}:{pubkey}:*` scoped KV
3425
+ * data, and the payments vertical's `pv2g2:{network}:{pubkey}:*` scoped KV
3433
3426
  * (refresh token, cursors, journals — all live in the plain StorageProvider,
3434
3427
  * so the full KV wipe below removes them). Tokens are server-custody; the
3435
3428
  * only local token artifacts are orphaned `sphere-token-storage-*` IndexedDB
@@ -3475,8 +3468,6 @@ declare class Sphere {
3475
3468
  static generateMnemonic(strength?: 128 | 256): string;
3476
3469
  /** Payments (the §4 facade — the active address's running vertical). */
3477
3470
  get payments(): PaymentsV2;
3478
- /** @deprecated Alias of {@link payments} kept for one release (the live frontend calls it). Null when no vertical runs. */
3479
- get paymentsV2(): PaymentsV2 | null;
3480
3471
  /** Communications module */
3481
3472
  get communications(): CommunicationsModule;
3482
3473
  /** Group chat module (NIP-29). Null if not configured. */
@@ -2286,10 +2286,6 @@ interface SphereValue {
2286
2286
  * `token`, so it is NOT stored — only cached at runtime on SphereToken.value.
2287
2287
  */
2288
2288
  interface TokenBlob {
2289
- /** Blob format version (sphere storage migrations; independent of SDK CBOR). */
2290
- readonly v: number;
2291
- /** NetworkId.id the token belongs to (mainnet=1 / testnet=2 / local=3). */
2292
- readonly network: number;
2293
2289
  /**
2294
2290
  * Genesis-stable token id — 64-char lowercase hex of the v2 `TokenId.bytes`
2295
2291
  * (same across every state of the token). Stored on the blob so dedup / listing
@@ -3048,8 +3044,6 @@ type InitProgressCallback = (progress: InitProgress) => void;
3048
3044
  interface SphereWalletApiOptions {
3049
3045
  /** Wallet-api transport config — REQUIRED (init throws INVALID_CONFIG without it). */
3050
3046
  walletApi?: WalletApiTransportConfig;
3051
- /** @deprecated The payments-v2 vertical is the only path since the P11 flip — accepted as a no-op for one release. */
3052
- paymentsV2?: boolean;
3053
3047
  /** @deprecated REMOVED with the P11 flip — any truthy value throws INVALID_CONFIG (invoicing no longer exists in the SDK). */
3054
3048
  accounting?: unknown;
3055
3049
  /** @deprecated REMOVED with the P11 flip — any truthy value throws INVALID_CONFIG (swaps no longer exist in the SDK). */
@@ -3398,11 +3392,10 @@ declare class Sphere {
3398
3392
  */
3399
3393
  private static resolveMarketConfig;
3400
3394
  /**
3401
- * The ONE sanctioned refusal fossil of the P11 flip (revisit-and-delete after
3402
- * one release): `accounting`/`swap` were public init flags — silently
3403
- * ignoring them would hide that invoices/swaps no longer exist in the SDK.
3404
- * `paymentsV2: true` stays an accepted no-op for the same one release (the
3405
- * live frontend passes it today).
3395
+ * The ONE sanctioned refusal fossil of the P11 flip: `accounting`/`swap` were
3396
+ * public init flags — silently ignoring them would hide that invoices/swaps no
3397
+ * longer exist in the SDK. Kept through 0.15.0, where consumers re-integrate
3398
+ * across the wire break and a silent no-op would be worst.
3406
3399
  */
3407
3400
  private static refuseRetiredModuleOptions;
3408
3401
  /**
@@ -3429,7 +3422,7 @@ declare class Sphere {
3429
3422
  static import(options: SphereImportOptions): Promise<Sphere>;
3430
3423
  /**
3431
3424
  * Clear all SDK-owned wallet data from storage: wallet keys, per-address
3432
- * data, and the payments vertical's `pv2:{network}:{pubkey}:*` scoped KV
3425
+ * data, and the payments vertical's `pv2g2:{network}:{pubkey}:*` scoped KV
3433
3426
  * (refresh token, cursors, journals — all live in the plain StorageProvider,
3434
3427
  * so the full KV wipe below removes them). Tokens are server-custody; the
3435
3428
  * only local token artifacts are orphaned `sphere-token-storage-*` IndexedDB
@@ -3475,8 +3468,6 @@ declare class Sphere {
3475
3468
  static generateMnemonic(strength?: 128 | 256): string;
3476
3469
  /** Payments (the §4 facade — the active address's running vertical). */
3477
3470
  get payments(): PaymentsV2;
3478
- /** @deprecated Alias of {@link payments} kept for one release (the live frontend calls it). Null when no vertical runs. */
3479
- get paymentsV2(): PaymentsV2 | null;
3480
3471
  /** Communications module */
3481
3472
  get communications(): CommunicationsModule;
3482
3473
  /** Group chat module (NIP-29). Null if not configured. */
@@ -9385,7 +9385,15 @@ function resolveProofTimeoutMs(configured) {
9385
9385
  return configured;
9386
9386
  }
9387
9387
  var TRANSIENT_HTTP_STATUSES = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
9388
+ var TransientSubmitAnswer = class extends Error {
9389
+ constructor(status) {
9390
+ super(`gateway is not ready to certify yet (${status})`);
9391
+ this.status = status;
9392
+ this.name = "TransientSubmitAnswer";
9393
+ }
9394
+ };
9388
9395
  function isTransientGatewayError(err) {
9396
+ if (err instanceof TransientSubmitAnswer) return true;
9389
9397
  return err instanceof JsonRpcNetworkError && TRANSIENT_HTTP_STATUSES.has(err.status);
9390
9398
  }
9391
9399
  function pause(ms, signal) {
@@ -9506,37 +9514,9 @@ function certificationFailure(err, failLabel, submitError, submitRejectedCleanly
9506
9514
  );
9507
9515
  }
9508
9516
 
9509
- // token-engine/token-blob.ts
9510
- var TOKEN_BLOB_TAG = 39051n;
9511
- var TOKEN_BLOB_VERSION = 1;
9512
- function decodeTokenBlob(bytes) {
9513
- const tag = CborDeserializer.decodeTag(bytes);
9514
- if (tag.tag !== TOKEN_BLOB_TAG) {
9515
- throw new CborError(`Invalid TokenBlob tag: ${tag.tag}`);
9516
- }
9517
- const fields = CborDeserializer.decodeArray(tag.data, 4);
9518
- const v = Number(CborDeserializer.decodeUnsignedInteger(fields[0]));
9519
- if (v !== TOKEN_BLOB_VERSION) {
9520
- throw new CborError(`Unsupported TokenBlob version: ${v}`);
9521
- }
9522
- return {
9523
- v,
9524
- network: Number(CborDeserializer.decodeUnsignedInteger(fields[1])),
9525
- tokenId: CborDeserializer.decodeTextString(fields[2]),
9526
- token: CborDeserializer.decodeByteString(fields[3])
9527
- };
9528
- }
9529
- function unwrapTokenBlobBytes(bytes) {
9530
- try {
9531
- return decodeTokenBlob(bytes).token;
9532
- } catch {
9533
- return bytes;
9534
- }
9535
- }
9536
-
9537
9517
  // token-engine/blob-keys.ts
9538
9518
  async function deriveDeliveryKeys(blobBytes) {
9539
- const token = await Token.fromCBOR(unwrapTokenBlobBytes(blobBytes));
9519
+ const token = await Token.fromCBOR(blobBytes);
9540
9520
  const stateHash = HexConverter.encode((await token.latestTransaction.calculateStateHash()).imprint);
9541
9521
  return { tokenId: HexConverter.encode(token.id.bytes), stateHash };
9542
9522
  }
@@ -9590,8 +9570,8 @@ function deriveRealization(privKeyHex, transferId, index, field) {
9590
9570
  }
9591
9571
 
9592
9572
  // token-engine/split-checkpoint.ts
9593
- var CHECKPOINT_VERSION = 1;
9594
- var CHECKPOINT_SDK_VERSION = "@unicitylabs/state-transition-sdk@2.0.2";
9573
+ var CHECKPOINT_VERSION = 2;
9574
+ var CHECKPOINT_SDK_VERSION = "@unicitylabs/state-transition-sdk@3.0.1";
9595
9575
  function bytesEqual(a, b) {
9596
9576
  if (a.length !== b.length) return false;
9597
9577
  for (let i = 0; i < a.length; i++) {
@@ -9741,13 +9721,11 @@ var SphereTokenEngine = class {
9741
9721
  const data = params.value ? await SpherePaymentData.fromValue(params.value).encode() : null;
9742
9722
  const transferId = this.resolveTransferId(options);
9743
9723
  const opIndex = this.resolveOpIndex(options);
9744
- const mintTx = await MintTransaction.create(
9745
- this.deps.networkId,
9746
- recipient,
9724
+ const mintTx = await MintTransaction.create(this.deps.networkId, recipient, {
9747
9725
  data,
9748
- new TokenType(deriveRealization(this.privateKeyHex, transferId, opIndex, "tokenType")),
9749
- TokenSalt.fromBytes(deriveRealization(this.privateKeyHex, transferId, opIndex, "mint"))
9750
- );
9726
+ tokenType: new TokenType(deriveRealization(this.privateKeyHex, transferId, opIndex, "tokenType")),
9727
+ salt: TokenSalt.fromBytes(deriveRealization(this.privateKeyHex, transferId, opIndex, "mint"))
9728
+ });
9751
9729
  const certificationData = await CertificationData.fromMintTransaction(mintTx);
9752
9730
  const proof = await this.submitAndAwaitProof(
9753
9731
  certificationData,
@@ -9764,7 +9742,7 @@ var SphereTokenEngine = class {
9764
9742
  const recipient = SignaturePredicate.create(params.recipientPubkey);
9765
9743
  const tokenType = params.tokenType ? new TokenType(params.tokenType) : TokenType.generate();
9766
9744
  const salt = params.salt ? TokenSalt.fromBytes(params.salt) : TokenSalt.generate();
9767
- const mintTx = await MintTransaction.create(this.deps.networkId, recipient, params.data, tokenType, salt);
9745
+ const mintTx = await MintTransaction.create(this.deps.networkId, recipient, { data: params.data, tokenType, salt });
9768
9746
  const certificationData = await CertificationData.fromMintTransaction(mintTx);
9769
9747
  const proof = await this.submitAndAwaitProof(
9770
9748
  certificationData,
@@ -9784,7 +9762,9 @@ var SphereTokenEngine = class {
9784
9762
  const stateMask = StateMask.fromBytes(
9785
9763
  deriveRealization(this.privateKeyHex, transferId, this.resolveOpIndex(options), "stateMask")
9786
9764
  );
9787
- const transferTx = await TransferTransaction.create(params.token.sdkToken, recipient, stateMask, params.data ?? null);
9765
+ const transferTx = await TransferTransaction.create(params.token.sdkToken, recipient, stateMask, {
9766
+ data: params.data ?? null
9767
+ });
9788
9768
  const unlockScript = await SignaturePredicateUnlockScript.create(transferTx, this.deps.signingService);
9789
9769
  const certificationData = await CertificationData.fromTransaction(transferTx, unlockScript);
9790
9770
  const proof = await this.submitAndAwaitProof(
@@ -9811,12 +9791,11 @@ var SphereTokenEngine = class {
9811
9791
  TokenSalt.fromBytes(deriveRealization(this.privateKeyHex, transferId, i, "salt"))
9812
9792
  )
9813
9793
  );
9814
- const split = await TokenSplit.split(
9815
- params.token.sdkToken,
9816
- decodeSpherePaymentData,
9817
- requests,
9818
- StateMask.fromBytes(deriveRealization(this.privateKeyHex, transferId, this.resolveOpIndex(options), "burn"))
9819
- );
9794
+ const split = await TokenSplit.split(params.token.sdkToken, decodeSpherePaymentData, requests, {
9795
+ burnStateMask: StateMask.fromBytes(
9796
+ deriveRealization(this.privateKeyHex, transferId, this.resolveOpIndex(options), "burn")
9797
+ )
9798
+ });
9820
9799
  const burntToken = await this.resolveBurntToken({ params, split, transferId, options });
9821
9800
  const settled = [];
9822
9801
  for (let start = 0; start < split.tokens.length; start += MAX_MINT_CONCURRENCY) {
@@ -9896,9 +9875,14 @@ var SphereTokenEngine = class {
9896
9875
  await Promise.all(
9897
9876
  ctx.split.tokens.map(async (leg) => {
9898
9877
  const data = await leg.paymentData.encode();
9899
- const probe = await MintTransaction.create(leg.networkId, leg.recipient, data, leg.tokenType, leg.salt, null);
9878
+ const probe = await MintTransaction.create(leg.networkId, leg.recipient, {
9879
+ data,
9880
+ tokenType: leg.tokenType,
9881
+ salt: leg.salt,
9882
+ justification: null
9883
+ });
9900
9884
  const response = await this.deps.client.getInclusionProof(await StateId.fromTransaction(probe));
9901
- if (response.inclusionProof.certificationData !== null) {
9885
+ if (response.inclusionProof !== null) {
9902
9886
  throw new SplitCheckpointLostError(
9903
9887
  "a split mint leaf is certified on-chain but no burn checkpoint exists \u2014 outputs are unrecoverable"
9904
9888
  );
@@ -9910,14 +9894,12 @@ var SphereTokenEngine = class {
9910
9894
  async mintSplitOutput(splitToken, burntToken, options) {
9911
9895
  const data = await splitToken.paymentData.encode();
9912
9896
  const justification = SplitMintJustification.create(burntToken, splitToken.proofs).toCBOR();
9913
- const mintTx = await MintTransaction.create(
9914
- splitToken.networkId,
9915
- splitToken.recipient,
9897
+ const mintTx = await MintTransaction.create(splitToken.networkId, splitToken.recipient, {
9916
9898
  data,
9917
- splitToken.tokenType,
9918
- splitToken.salt,
9899
+ tokenType: splitToken.tokenType,
9900
+ salt: splitToken.salt,
9919
9901
  justification
9920
- );
9902
+ });
9921
9903
  const certData = await CertificationData.fromMintTransaction(mintTx);
9922
9904
  const proof = await this.submitSplitMintLeg(certData, mintTx, options);
9923
9905
  const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
@@ -9962,7 +9944,7 @@ var SphereTokenEngine = class {
9962
9944
  );
9963
9945
  const stateId = await StateId.fromTransaction(probe);
9964
9946
  const response = await this.deps.client.getInclusionProof(stateId);
9965
- return response.inclusionProof.certificationData !== null;
9947
+ return response.inclusionProof !== null;
9966
9948
  }
9967
9949
  // ── serialization ────────────────────────────────────────────────────────────
9968
9950
  /** @inheritDoc — SDK-derived (Token.fromCBOR → latestTransaction.calculateStateHash().imprint). */
@@ -10046,7 +10028,13 @@ var SphereTokenEngine = class {
10046
10028
  let submitRejectedCleanly = false;
10047
10029
  try {
10048
10030
  const { value: response, retried } = await retryTransient(
10049
- () => this.deps.client.submitCertificationRequest(certificationData),
10031
+ async () => {
10032
+ const answer = await this.deps.client.submitCertificationRequest(certificationData);
10033
+ if (String(answer.status) === String(CertificationStatus.SERVICE_NOT_READY)) {
10034
+ throw new TransientSubmitAnswer(String(answer.status));
10035
+ }
10036
+ return answer;
10037
+ },
10050
10038
  signal,
10051
10039
  this.deps.proofPollIntervalMs ?? DEFAULT_PROOF_POLL_INTERVAL_MS
10052
10040
  );
@@ -10101,8 +10089,6 @@ var SphereTokenEngine = class {
10101
10089
  }
10102
10090
  }
10103
10091
  const blob = {
10104
- v: TOKEN_BLOB_VERSION,
10105
- network: sdkToken.genesis.networkId.id,
10106
10092
  tokenId: HexConverter.encode(sdkToken.id.bytes),
10107
10093
  token: sdkToken.toCBOR()
10108
10094
  };
@@ -11246,8 +11232,19 @@ function coalesced(run, observe) {
11246
11232
  }
11247
11233
 
11248
11234
  // modules/payments-v2/stores.ts
11235
+ var KV_PREFIX = "pv2g2:";
11236
+ var SUPERSEDED_KV_PREFIXES = ["pv2:"];
11237
+ async function sweepSupersededState(storage) {
11238
+ for (const prefix of SUPERSEDED_KV_PREFIXES) {
11239
+ try {
11240
+ await storage.clear(prefix);
11241
+ } catch (err) {
11242
+ logger.warn("PaymentsV2", `could not clear superseded state under "${prefix}": ${String(err)}`);
11243
+ }
11244
+ }
11245
+ }
11249
11246
  function createScopedKV(storage, network, chainPubkey) {
11250
- const prefix = `pv2:${network}:${chainPubkey}:`;
11247
+ const prefix = `${KV_PREFIX}${network}:${chainPubkey}:`;
11251
11248
  return {
11252
11249
  async get(key) {
11253
11250
  const raw = await storage.get(prefix + key);
@@ -11829,12 +11826,7 @@ var TransferMachine = class {
11829
11826
  let total = 0n;
11830
11827
  for (const outcome of committed) {
11831
11828
  if (outcome.recipientBlob === void 0) continue;
11832
- const token = await engine.decodeToken({
11833
- v: TOKEN_BLOB_VERSION,
11834
- network: 0,
11835
- tokenId: "",
11836
- token: outcome.recipientBlob
11837
- });
11829
+ const token = await engine.decodeToken({ tokenId: "", token: outcome.recipientBlob });
11838
11830
  total += engine.balanceOf(token, coinId);
11839
11831
  }
11840
11832
  return total.toString();
@@ -11950,8 +11942,9 @@ async function fetchSources(deps, jobs) {
11950
11942
  const engine = deps.engine();
11951
11943
  for (const [tokenId, bytes] of blobs) {
11952
11944
  try {
11953
- decoded.set(tokenId, await engine.decodeToken({ v: TOKEN_BLOB_VERSION, network: 0, tokenId, token: bytes }));
11954
- } catch {
11945
+ decoded.set(tokenId, await engine.decodeToken({ tokenId, token: bytes }));
11946
+ } catch (err) {
11947
+ logger.warn("PaymentsV2", `resume: source ${tokenId} did not decode \u2014 ${String(err)}`);
11955
11948
  }
11956
11949
  }
11957
11950
  return decoded;
@@ -12978,22 +12971,24 @@ var Receive = class {
12978
12971
  * the tokens just accepted, and the balance would still only move at the end.
12979
12972
  * Ordering is preserved: store() still precedes the ack (§5.7).
12980
12973
  */
12981
- async maybeRefresh(deps, pending, epochOf) {
12974
+ async maybeRefresh(ctx) {
12975
+ const { deps, pending, pageEpoch } = ctx;
12982
12976
  if (deps.refreshView === void 0) return;
12983
12977
  const now = deps.now?.() ?? Date.now();
12984
12978
  if (now - this.lastRefreshAt < REFRESH_INTERVAL_MS) return;
12985
12979
  this.lastRefreshAt = now;
12986
- await flushAcks(deps, pending, epochOf);
12987
- deps.refreshView();
12980
+ if ((await flushAcks(deps, pending, pageEpoch)).progressed) deps.refreshView();
12988
12981
  }
12989
12982
  /** One listing pass: process each entry, flushing and refreshing as it goes. */
12990
12983
  async drainPages(ctx, basis) {
12991
- const { deps, engine, pending, stored, pageEpoch } = ctx;
12984
+ const { deps, pending, pageEpoch, claimable } = ctx;
12992
12985
  for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
12993
- const storedBefore = stored.length;
12994
- await this.processEntry(deps, engine, entry, pending, stored);
12995
- if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
12996
- if (stored.length > storedBefore) await this.maybeRefresh(deps, pending, pageEpoch);
12986
+ const claimableBefore = claimable.n;
12987
+ await this.processEntry(ctx, entry);
12988
+ if (pending.length >= ACK_BATCH_SIZE) {
12989
+ if (!(await flushAcks(deps, pending, pageEpoch)).progressed) return;
12990
+ }
12991
+ if (claimable.n > claimableBefore) await this.maybeRefresh(ctx);
12997
12992
  }
12998
12993
  await flushAcks(deps, pending, pageEpoch);
12999
12994
  }
@@ -13004,11 +12999,16 @@ var Receive = class {
13004
12999
  const stored = [];
13005
13000
  const pending = [];
13006
13001
  const pageEpoch = () => deps.delivery.incomingEpoch() ?? deps.syncEpoch();
13002
+ const claimable = { n: 0 };
13003
+ const finish = () => {
13004
+ if (claimable.n > 0) deps.refreshView?.();
13005
+ return stored;
13006
+ };
13007
13007
  try {
13008
13008
  const record = await deps.kv.get(STORE_KEYS.streamCursor("mailbox"));
13009
13009
  let basis = record !== null && record.syncEpoch === deps.syncEpoch() ? record : null;
13010
13010
  for (let pass = 0; pass < 2; pass++) {
13011
- await this.drainPages({ deps, engine, pending, stored, pageEpoch }, basis);
13011
+ await this.drainPages({ deps, engine, pending, stored, pageEpoch, claimable }, basis);
13012
13012
  const served = deps.delivery.incomingEpoch();
13013
13013
  if (basis === null || served === null || served === basis.syncEpoch) break;
13014
13014
  await deps.kv.remove(STORE_KEYS.streamCursor("mailbox"));
@@ -13016,22 +13016,25 @@ var Receive = class {
13016
13016
  }
13017
13017
  } catch (err) {
13018
13018
  logger.warn("PaymentsV2", "receive drain interrupted \u2014 unacked entries retry next drain:", err);
13019
- if (isRetryableAckError(err)) return stored;
13019
+ if (isRetryableAckError(err)) return finish();
13020
13020
  await flushAcks(deps, pending, pageEpoch).catch((flushErr) => {
13021
13021
  logger.warn("PaymentsV2", "receive ack flush failed \u2014 cursor holds at the acked prefix:", flushErr);
13022
+ return { progressed: false };
13022
13023
  });
13023
13024
  }
13024
- if (stored.length > 0) deps.refreshView?.();
13025
- return stored;
13025
+ return finish();
13026
13026
  }
13027
- async processEntry(deps, engine, entry, pending, stored) {
13027
+ async processEntry(ctx, entry) {
13028
+ const { deps, engine, pending, stored, claimable } = ctx;
13028
13029
  const screened = await screen(deps, engine, entry);
13029
13030
  if (screened.kind === "ack") {
13030
13031
  pending.push(screened.ack);
13032
+ if (screened.ack.disposition === "claimed") claimable.n += 1;
13031
13033
  return;
13032
13034
  }
13033
13035
  await deps.view.store(screened.record);
13034
13036
  pending.push(claimAck(entry));
13037
+ claimable.n += 1;
13035
13038
  const transfer = await announce(deps, entry, screened.record);
13036
13039
  stored.push(transfer);
13037
13040
  deps.emit("transfer:incoming", transfer);
@@ -13041,13 +13044,9 @@ async function screen(deps, engine, entry) {
13041
13044
  const blobBytes = await entry.fetchBlob();
13042
13045
  let token;
13043
13046
  try {
13044
- token = await engine.decodeToken({
13045
- v: TOKEN_BLOB_VERSION,
13046
- network: 0,
13047
- tokenId: "",
13048
- token: blobBytes
13049
- });
13050
- } catch {
13047
+ token = await engine.decodeToken({ tokenId: "", token: blobBytes });
13048
+ } catch (err) {
13049
+ logger.warn("PaymentsV2", `receive: undecodable blob rejected \u2014 ${String(err)}`);
13051
13050
  return { kind: "ack", ack: rejectAck(entry, "invalid") };
13052
13051
  }
13053
13052
  const verdict = await engine.verify(token);
@@ -13092,7 +13091,8 @@ async function announce(deps, entry, record) {
13092
13091
  };
13093
13092
  }
13094
13093
  async function flushAcks(deps, pending, epochOf) {
13095
- if (pending.length === 0) return;
13094
+ if (pending.length === 0) return { progressed: true };
13095
+ const before = pending.length;
13096
13096
  let lastAcked = null;
13097
13097
  try {
13098
13098
  const settled = await settleAcks(deps, pending);
@@ -13110,6 +13110,7 @@ async function flushAcks(deps, pending, epochOf) {
13110
13110
  await deps.kv.set(STORE_KEYS.streamCursor("mailbox"), record);
13111
13111
  }
13112
13112
  }
13113
+ return { progressed: pending.length < before };
13113
13114
  }
13114
13115
  async function settleAcks(deps, pending) {
13115
13116
  const batch = deps.delivery.ackBatch?.bind(deps.delivery);
@@ -14448,7 +14449,7 @@ var PaymentsFacade = class {
14448
14449
  if (bytes === void 0) {
14449
14450
  throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
14450
14451
  }
14451
- const token = await engine.decodeToken({ v: TOKEN_BLOB_VERSION, network: 0, tokenId, token: bytes });
14452
+ const token = await engine.decodeToken({ tokenId, token: bytes });
14452
14453
  const keys = await engine.deliveryKeys(bytes);
14453
14454
  spentStates[tokenId] = { local: keys.stateHash, protocol: keys.stateHash };
14454
14455
  sources.set(tokenId, token);
@@ -15630,7 +15631,9 @@ var WalletApiDeliveryPort = class {
15630
15631
  await this.settleClaims(acks, settled, outcomes);
15631
15632
  await this.settleRejects(acks, settled);
15632
15633
  } catch (err) {
15633
- await this.addSeenAll(settled).catch(() => void 0);
15634
+ await this.addSeenAll(settled).catch((seenErr) => {
15635
+ logger.warn("WalletApiDelivery", `seen-set write failed for ${String(settled.length)} committed acks:`, seenErr);
15636
+ });
15634
15637
  throw asRetryable(err);
15635
15638
  }
15636
15639
  await this.addSeenAll(settled);
@@ -15893,6 +15896,7 @@ function composePaymentsV2(spec) {
15893
15896
  const network = spec.composition.network;
15894
15897
  const identitySlice = { privateKey: identity.privateKey, chainPubkey: identity.chainPubkey };
15895
15898
  const kv = createScopedKV(host.storage, network, identity.chainPubkey);
15899
+ void sweepSupersededState(host.storage);
15896
15900
  const fieldKey = deriveFieldEncryptionKey(identity.privateKey);
15897
15901
  const transport = spec.composition.factory({
15898
15902
  identity: identitySlice,
@@ -16184,11 +16188,10 @@ var Sphere = class _Sphere {
16184
16188
  return config;
16185
16189
  }
16186
16190
  /**
16187
- * The ONE sanctioned refusal fossil of the P11 flip (revisit-and-delete after
16188
- * one release): `accounting`/`swap` were public init flags — silently
16189
- * ignoring them would hide that invoices/swaps no longer exist in the SDK.
16190
- * `paymentsV2: true` stays an accepted no-op for the same one release (the
16191
- * live frontend passes it today).
16191
+ * The ONE sanctioned refusal fossil of the P11 flip: `accounting`/`swap` were
16192
+ * public init flags — silently ignoring them would hide that invoices/swaps no
16193
+ * longer exist in the SDK. Kept through 0.15.0, where consumers re-integrate
16194
+ * across the wire break and a silent no-op would be worst.
16192
16195
  */
16193
16196
  static refuseRetiredModuleOptions(options) {
16194
16197
  if (options.accounting) {
@@ -16455,7 +16458,7 @@ var Sphere = class _Sphere {
16455
16458
  }
16456
16459
  /**
16457
16460
  * Clear all SDK-owned wallet data from storage: wallet keys, per-address
16458
- * data, and the payments vertical's `pv2:{network}:{pubkey}:*` scoped KV
16461
+ * data, and the payments vertical's `pv2g2:{network}:{pubkey}:*` scoped KV
16459
16462
  * (refresh token, cursors, journals — all live in the plain StorageProvider,
16460
16463
  * so the full KV wipe below removes them). Tokens are server-custody; the
16461
16464
  * only local token artifacts are orphaned `sphere-token-storage-*` IndexedDB
@@ -16565,10 +16568,6 @@ var Sphere = class _Sphere {
16565
16568
  }
16566
16569
  return facade;
16567
16570
  }
16568
- /** @deprecated Alias of {@link payments} kept for one release (the live frontend calls it). Null when no vertical runs. */
16569
- get paymentsV2() {
16570
- return this._paymentsV2Active?.facade ?? null;
16571
- }
16572
16571
  /** Communications module */
16573
16572
  get communications() {
16574
16573
  this.ensureReady();