@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
package/dist/index.d.cts CHANGED
@@ -1964,7 +1964,7 @@ declare const STORAGE_KEYS_GLOBAL: {
1964
1964
  * Example: sphere_DIRECT_abc123_xyz789_pending_transfers
1965
1965
  *
1966
1966
  * Note: Token data is server custody (wallet-api backend), not here; the
1967
- * payments vertical's durable client state self-prefixes `pv2:{network}:{pubkey}:`.
1967
+ * payments vertical's durable client state self-prefixes `pv2g2:{network}:{pubkey}:`.
1968
1968
  */
1969
1969
  declare const STORAGE_KEYS_ADDRESS: {
1970
1970
  /** Transfer outbox for this address (pre-flip key name; kept as the network-scoping witness) */
@@ -2743,8 +2743,6 @@ type InitProgressCallback = (progress: InitProgress) => void;
2743
2743
  interface SphereWalletApiOptions {
2744
2744
  /** Wallet-api transport config — REQUIRED (init throws INVALID_CONFIG without it). */
2745
2745
  walletApi?: WalletApiTransportConfig;
2746
- /** @deprecated The payments-v2 vertical is the only path since the P11 flip — accepted as a no-op for one release. */
2747
- paymentsV2?: boolean;
2748
2746
  /** @deprecated REMOVED with the P11 flip — any truthy value throws INVALID_CONFIG (invoicing no longer exists in the SDK). */
2749
2747
  accounting?: unknown;
2750
2748
  /** @deprecated REMOVED with the P11 flip — any truthy value throws INVALID_CONFIG (swaps no longer exist in the SDK). */
@@ -3076,11 +3074,10 @@ declare class Sphere {
3076
3074
  */
3077
3075
  private static resolveMarketConfig;
3078
3076
  /**
3079
- * The ONE sanctioned refusal fossil of the P11 flip (revisit-and-delete after
3080
- * one release): `accounting`/`swap` were public init flags — silently
3081
- * ignoring them would hide that invoices/swaps no longer exist in the SDK.
3082
- * `paymentsV2: true` stays an accepted no-op for the same one release (the
3083
- * live frontend passes it today).
3077
+ * The ONE sanctioned refusal fossil of the P11 flip: `accounting`/`swap` were
3078
+ * public init flags — silently ignoring them would hide that invoices/swaps no
3079
+ * longer exist in the SDK. Kept through 0.15.0, where consumers re-integrate
3080
+ * across the wire break and a silent no-op would be worst.
3084
3081
  */
3085
3082
  private static refuseRetiredModuleOptions;
3086
3083
  /**
@@ -3107,7 +3104,7 @@ declare class Sphere {
3107
3104
  static import(options: SphereImportOptions): Promise<Sphere>;
3108
3105
  /**
3109
3106
  * Clear all SDK-owned wallet data from storage: wallet keys, per-address
3110
- * data, and the payments vertical's `pv2:{network}:{pubkey}:*` scoped KV
3107
+ * data, and the payments vertical's `pv2g2:{network}:{pubkey}:*` scoped KV
3111
3108
  * (refresh token, cursors, journals — all live in the plain StorageProvider,
3112
3109
  * so the full KV wipe below removes them). Tokens are server-custody; the
3113
3110
  * only local token artifacts are orphaned `sphere-token-storage-*` IndexedDB
@@ -3153,8 +3150,6 @@ declare class Sphere {
3153
3150
  static generateMnemonic(strength?: 128 | 256): string;
3154
3151
  /** Payments (the §4 facade — the active address's running vertical). */
3155
3152
  get payments(): PaymentsV2;
3156
- /** @deprecated Alias of {@link payments} kept for one release (the live frontend calls it). Null when no vertical runs. */
3157
- get paymentsV2(): PaymentsV2 | null;
3158
3153
  /** Communications module */
3159
3154
  get communications(): CommunicationsModule;
3160
3155
  /** Group chat module (NIP-29). Null if not configured. */
package/dist/index.d.ts CHANGED
@@ -1964,7 +1964,7 @@ declare const STORAGE_KEYS_GLOBAL: {
1964
1964
  * Example: sphere_DIRECT_abc123_xyz789_pending_transfers
1965
1965
  *
1966
1966
  * Note: Token data is server custody (wallet-api backend), not here; the
1967
- * payments vertical's durable client state self-prefixes `pv2:{network}:{pubkey}:`.
1967
+ * payments vertical's durable client state self-prefixes `pv2g2:{network}:{pubkey}:`.
1968
1968
  */
1969
1969
  declare const STORAGE_KEYS_ADDRESS: {
1970
1970
  /** Transfer outbox for this address (pre-flip key name; kept as the network-scoping witness) */
@@ -2743,8 +2743,6 @@ type InitProgressCallback = (progress: InitProgress) => void;
2743
2743
  interface SphereWalletApiOptions {
2744
2744
  /** Wallet-api transport config — REQUIRED (init throws INVALID_CONFIG without it). */
2745
2745
  walletApi?: WalletApiTransportConfig;
2746
- /** @deprecated The payments-v2 vertical is the only path since the P11 flip — accepted as a no-op for one release. */
2747
- paymentsV2?: boolean;
2748
2746
  /** @deprecated REMOVED with the P11 flip — any truthy value throws INVALID_CONFIG (invoicing no longer exists in the SDK). */
2749
2747
  accounting?: unknown;
2750
2748
  /** @deprecated REMOVED with the P11 flip — any truthy value throws INVALID_CONFIG (swaps no longer exist in the SDK). */
@@ -3076,11 +3074,10 @@ declare class Sphere {
3076
3074
  */
3077
3075
  private static resolveMarketConfig;
3078
3076
  /**
3079
- * The ONE sanctioned refusal fossil of the P11 flip (revisit-and-delete after
3080
- * one release): `accounting`/`swap` were public init flags — silently
3081
- * ignoring them would hide that invoices/swaps no longer exist in the SDK.
3082
- * `paymentsV2: true` stays an accepted no-op for the same one release (the
3083
- * live frontend passes it today).
3077
+ * The ONE sanctioned refusal fossil of the P11 flip: `accounting`/`swap` were
3078
+ * public init flags — silently ignoring them would hide that invoices/swaps no
3079
+ * longer exist in the SDK. Kept through 0.15.0, where consumers re-integrate
3080
+ * across the wire break and a silent no-op would be worst.
3084
3081
  */
3085
3082
  private static refuseRetiredModuleOptions;
3086
3083
  /**
@@ -3107,7 +3104,7 @@ declare class Sphere {
3107
3104
  static import(options: SphereImportOptions): Promise<Sphere>;
3108
3105
  /**
3109
3106
  * Clear all SDK-owned wallet data from storage: wallet keys, per-address
3110
- * data, and the payments vertical's `pv2:{network}:{pubkey}:*` scoped KV
3107
+ * data, and the payments vertical's `pv2g2:{network}:{pubkey}:*` scoped KV
3111
3108
  * (refresh token, cursors, journals — all live in the plain StorageProvider,
3112
3109
  * so the full KV wipe below removes them). Tokens are server-custody; the
3113
3110
  * only local token artifacts are orphaned `sphere-token-storage-*` IndexedDB
@@ -3153,8 +3150,6 @@ declare class Sphere {
3153
3150
  static generateMnemonic(strength?: 128 | 256): string;
3154
3151
  /** Payments (the §4 facade — the active address's running vertical). */
3155
3152
  get payments(): PaymentsV2;
3156
- /** @deprecated Alias of {@link payments} kept for one release (the live frontend calls it). Null when no vertical runs. */
3157
- get paymentsV2(): PaymentsV2 | null;
3158
3153
  /** Communications module */
3159
3154
  get communications(): CommunicationsModule;
3160
3155
  /** Group chat module (NIP-29). Null if not configured. */
package/dist/index.js CHANGED
@@ -9401,7 +9401,15 @@ function resolveProofTimeoutMs(configured) {
9401
9401
  return configured;
9402
9402
  }
9403
9403
  var TRANSIENT_HTTP_STATUSES = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
9404
+ var TransientSubmitAnswer = class extends Error {
9405
+ constructor(status) {
9406
+ super(`gateway is not ready to certify yet (${status})`);
9407
+ this.status = status;
9408
+ this.name = "TransientSubmitAnswer";
9409
+ }
9410
+ };
9404
9411
  function isTransientGatewayError(err) {
9412
+ if (err instanceof TransientSubmitAnswer) return true;
9405
9413
  return err instanceof JsonRpcNetworkError && TRANSIENT_HTTP_STATUSES.has(err.status);
9406
9414
  }
9407
9415
  function pause(ms, signal) {
@@ -9522,37 +9530,9 @@ function certificationFailure(err, failLabel, submitError, submitRejectedCleanly
9522
9530
  );
9523
9531
  }
9524
9532
 
9525
- // token-engine/token-blob.ts
9526
- var TOKEN_BLOB_TAG = 39051n;
9527
- var TOKEN_BLOB_VERSION = 1;
9528
- function decodeTokenBlob(bytes) {
9529
- const tag = CborDeserializer.decodeTag(bytes);
9530
- if (tag.tag !== TOKEN_BLOB_TAG) {
9531
- throw new CborError(`Invalid TokenBlob tag: ${tag.tag}`);
9532
- }
9533
- const fields = CborDeserializer.decodeArray(tag.data, 4);
9534
- const v = Number(CborDeserializer.decodeUnsignedInteger(fields[0]));
9535
- if (v !== TOKEN_BLOB_VERSION) {
9536
- throw new CborError(`Unsupported TokenBlob version: ${v}`);
9537
- }
9538
- return {
9539
- v,
9540
- network: Number(CborDeserializer.decodeUnsignedInteger(fields[1])),
9541
- tokenId: CborDeserializer.decodeTextString(fields[2]),
9542
- token: CborDeserializer.decodeByteString(fields[3])
9543
- };
9544
- }
9545
- function unwrapTokenBlobBytes(bytes) {
9546
- try {
9547
- return decodeTokenBlob(bytes).token;
9548
- } catch {
9549
- return bytes;
9550
- }
9551
- }
9552
-
9553
9533
  // token-engine/blob-keys.ts
9554
9534
  async function deriveDeliveryKeys(blobBytes) {
9555
- const token = await Token.fromCBOR(unwrapTokenBlobBytes(blobBytes));
9535
+ const token = await Token.fromCBOR(blobBytes);
9556
9536
  const stateHash = HexConverter.encode((await token.latestTransaction.calculateStateHash()).imprint);
9557
9537
  return { tokenId: HexConverter.encode(token.id.bytes), stateHash };
9558
9538
  }
@@ -9606,8 +9586,8 @@ function deriveRealization(privKeyHex, transferId, index, field) {
9606
9586
  }
9607
9587
 
9608
9588
  // token-engine/split-checkpoint.ts
9609
- var CHECKPOINT_VERSION = 1;
9610
- var CHECKPOINT_SDK_VERSION = "@unicitylabs/state-transition-sdk@2.0.2";
9589
+ var CHECKPOINT_VERSION = 2;
9590
+ var CHECKPOINT_SDK_VERSION = "@unicitylabs/state-transition-sdk@3.0.1";
9611
9591
  function bytesEqual(a, b) {
9612
9592
  if (a.length !== b.length) return false;
9613
9593
  for (let i = 0; i < a.length; i++) {
@@ -9757,13 +9737,11 @@ var SphereTokenEngine = class {
9757
9737
  const data = params.value ? await SpherePaymentData.fromValue(params.value).encode() : null;
9758
9738
  const transferId = this.resolveTransferId(options);
9759
9739
  const opIndex = this.resolveOpIndex(options);
9760
- const mintTx = await MintTransaction.create(
9761
- this.deps.networkId,
9762
- recipient,
9740
+ const mintTx = await MintTransaction.create(this.deps.networkId, recipient, {
9763
9741
  data,
9764
- new TokenType(deriveRealization(this.privateKeyHex, transferId, opIndex, "tokenType")),
9765
- TokenSalt.fromBytes(deriveRealization(this.privateKeyHex, transferId, opIndex, "mint"))
9766
- );
9742
+ tokenType: new TokenType(deriveRealization(this.privateKeyHex, transferId, opIndex, "tokenType")),
9743
+ salt: TokenSalt.fromBytes(deriveRealization(this.privateKeyHex, transferId, opIndex, "mint"))
9744
+ });
9767
9745
  const certificationData = await CertificationData.fromMintTransaction(mintTx);
9768
9746
  const proof = await this.submitAndAwaitProof(
9769
9747
  certificationData,
@@ -9780,7 +9758,7 @@ var SphereTokenEngine = class {
9780
9758
  const recipient = SignaturePredicate.create(params.recipientPubkey);
9781
9759
  const tokenType = params.tokenType ? new TokenType(params.tokenType) : TokenType.generate();
9782
9760
  const salt = params.salt ? TokenSalt.fromBytes(params.salt) : TokenSalt.generate();
9783
- const mintTx = await MintTransaction.create(this.deps.networkId, recipient, params.data, tokenType, salt);
9761
+ const mintTx = await MintTransaction.create(this.deps.networkId, recipient, { data: params.data, tokenType, salt });
9784
9762
  const certificationData = await CertificationData.fromMintTransaction(mintTx);
9785
9763
  const proof = await this.submitAndAwaitProof(
9786
9764
  certificationData,
@@ -9800,7 +9778,9 @@ var SphereTokenEngine = class {
9800
9778
  const stateMask = StateMask.fromBytes(
9801
9779
  deriveRealization(this.privateKeyHex, transferId, this.resolveOpIndex(options), "stateMask")
9802
9780
  );
9803
- const transferTx = await TransferTransaction.create(params.token.sdkToken, recipient, stateMask, params.data ?? null);
9781
+ const transferTx = await TransferTransaction.create(params.token.sdkToken, recipient, stateMask, {
9782
+ data: params.data ?? null
9783
+ });
9804
9784
  const unlockScript = await SignaturePredicateUnlockScript.create(transferTx, this.deps.signingService);
9805
9785
  const certificationData = await CertificationData.fromTransaction(transferTx, unlockScript);
9806
9786
  const proof = await this.submitAndAwaitProof(
@@ -9827,12 +9807,11 @@ var SphereTokenEngine = class {
9827
9807
  TokenSalt.fromBytes(deriveRealization(this.privateKeyHex, transferId, i, "salt"))
9828
9808
  )
9829
9809
  );
9830
- const split = await TokenSplit.split(
9831
- params.token.sdkToken,
9832
- decodeSpherePaymentData,
9833
- requests,
9834
- StateMask.fromBytes(deriveRealization(this.privateKeyHex, transferId, this.resolveOpIndex(options), "burn"))
9835
- );
9810
+ const split = await TokenSplit.split(params.token.sdkToken, decodeSpherePaymentData, requests, {
9811
+ burnStateMask: StateMask.fromBytes(
9812
+ deriveRealization(this.privateKeyHex, transferId, this.resolveOpIndex(options), "burn")
9813
+ )
9814
+ });
9836
9815
  const burntToken = await this.resolveBurntToken({ params, split, transferId, options });
9837
9816
  const settled = [];
9838
9817
  for (let start = 0; start < split.tokens.length; start += MAX_MINT_CONCURRENCY) {
@@ -9912,9 +9891,14 @@ var SphereTokenEngine = class {
9912
9891
  await Promise.all(
9913
9892
  ctx.split.tokens.map(async (leg) => {
9914
9893
  const data = await leg.paymentData.encode();
9915
- const probe = await MintTransaction.create(leg.networkId, leg.recipient, data, leg.tokenType, leg.salt, null);
9894
+ const probe = await MintTransaction.create(leg.networkId, leg.recipient, {
9895
+ data,
9896
+ tokenType: leg.tokenType,
9897
+ salt: leg.salt,
9898
+ justification: null
9899
+ });
9916
9900
  const response = await this.deps.client.getInclusionProof(await StateId.fromTransaction(probe));
9917
- if (response.inclusionProof.certificationData !== null) {
9901
+ if (response.inclusionProof !== null) {
9918
9902
  throw new SplitCheckpointLostError(
9919
9903
  "a split mint leaf is certified on-chain but no burn checkpoint exists \u2014 outputs are unrecoverable"
9920
9904
  );
@@ -9926,14 +9910,12 @@ var SphereTokenEngine = class {
9926
9910
  async mintSplitOutput(splitToken, burntToken, options) {
9927
9911
  const data = await splitToken.paymentData.encode();
9928
9912
  const justification = SplitMintJustification.create(burntToken, splitToken.proofs).toCBOR();
9929
- const mintTx = await MintTransaction.create(
9930
- splitToken.networkId,
9931
- splitToken.recipient,
9913
+ const mintTx = await MintTransaction.create(splitToken.networkId, splitToken.recipient, {
9932
9914
  data,
9933
- splitToken.tokenType,
9934
- splitToken.salt,
9915
+ tokenType: splitToken.tokenType,
9916
+ salt: splitToken.salt,
9935
9917
  justification
9936
- );
9918
+ });
9937
9919
  const certData = await CertificationData.fromMintTransaction(mintTx);
9938
9920
  const proof = await this.submitSplitMintLeg(certData, mintTx, options);
9939
9921
  const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
@@ -9978,7 +9960,7 @@ var SphereTokenEngine = class {
9978
9960
  );
9979
9961
  const stateId = await StateId.fromTransaction(probe);
9980
9962
  const response = await this.deps.client.getInclusionProof(stateId);
9981
- return response.inclusionProof.certificationData !== null;
9963
+ return response.inclusionProof !== null;
9982
9964
  }
9983
9965
  // ── serialization ────────────────────────────────────────────────────────────
9984
9966
  /** @inheritDoc — SDK-derived (Token.fromCBOR → latestTransaction.calculateStateHash().imprint). */
@@ -10062,7 +10044,13 @@ var SphereTokenEngine = class {
10062
10044
  let submitRejectedCleanly = false;
10063
10045
  try {
10064
10046
  const { value: response, retried } = await retryTransient(
10065
- () => this.deps.client.submitCertificationRequest(certificationData),
10047
+ async () => {
10048
+ const answer = await this.deps.client.submitCertificationRequest(certificationData);
10049
+ if (String(answer.status) === String(CertificationStatus.SERVICE_NOT_READY)) {
10050
+ throw new TransientSubmitAnswer(String(answer.status));
10051
+ }
10052
+ return answer;
10053
+ },
10066
10054
  signal,
10067
10055
  this.deps.proofPollIntervalMs ?? DEFAULT_PROOF_POLL_INTERVAL_MS
10068
10056
  );
@@ -10117,8 +10105,6 @@ var SphereTokenEngine = class {
10117
10105
  }
10118
10106
  }
10119
10107
  const blob = {
10120
- v: TOKEN_BLOB_VERSION,
10121
- network: sdkToken.genesis.networkId.id,
10122
10108
  tokenId: HexConverter.encode(sdkToken.id.bytes),
10123
10109
  token: sdkToken.toCBOR()
10124
10110
  };
@@ -11262,8 +11248,19 @@ function coalesced(run, observe) {
11262
11248
  }
11263
11249
 
11264
11250
  // modules/payments-v2/stores.ts
11251
+ var KV_PREFIX = "pv2g2:";
11252
+ var SUPERSEDED_KV_PREFIXES = ["pv2:"];
11253
+ async function sweepSupersededState(storage) {
11254
+ for (const prefix of SUPERSEDED_KV_PREFIXES) {
11255
+ try {
11256
+ await storage.clear(prefix);
11257
+ } catch (err) {
11258
+ logger.warn("PaymentsV2", `could not clear superseded state under "${prefix}": ${String(err)}`);
11259
+ }
11260
+ }
11261
+ }
11265
11262
  function createScopedKV(storage, network, chainPubkey) {
11266
- const prefix = `pv2:${network}:${chainPubkey}:`;
11263
+ const prefix = `${KV_PREFIX}${network}:${chainPubkey}:`;
11267
11264
  return {
11268
11265
  async get(key) {
11269
11266
  const raw = await storage.get(prefix + key);
@@ -11845,12 +11842,7 @@ var TransferMachine = class {
11845
11842
  let total = 0n;
11846
11843
  for (const outcome of committed) {
11847
11844
  if (outcome.recipientBlob === void 0) continue;
11848
- const token = await engine.decodeToken({
11849
- v: TOKEN_BLOB_VERSION,
11850
- network: 0,
11851
- tokenId: "",
11852
- token: outcome.recipientBlob
11853
- });
11845
+ const token = await engine.decodeToken({ tokenId: "", token: outcome.recipientBlob });
11854
11846
  total += engine.balanceOf(token, coinId);
11855
11847
  }
11856
11848
  return total.toString();
@@ -11966,8 +11958,9 @@ async function fetchSources(deps, jobs) {
11966
11958
  const engine = deps.engine();
11967
11959
  for (const [tokenId, bytes] of blobs) {
11968
11960
  try {
11969
- decoded.set(tokenId, await engine.decodeToken({ v: TOKEN_BLOB_VERSION, network: 0, tokenId, token: bytes }));
11970
- } catch {
11961
+ decoded.set(tokenId, await engine.decodeToken({ tokenId, token: bytes }));
11962
+ } catch (err) {
11963
+ logger.warn("PaymentsV2", `resume: source ${tokenId} did not decode \u2014 ${String(err)}`);
11971
11964
  }
11972
11965
  }
11973
11966
  return decoded;
@@ -12994,22 +12987,24 @@ var Receive = class {
12994
12987
  * the tokens just accepted, and the balance would still only move at the end.
12995
12988
  * Ordering is preserved: store() still precedes the ack (§5.7).
12996
12989
  */
12997
- async maybeRefresh(deps, pending, epochOf) {
12990
+ async maybeRefresh(ctx) {
12991
+ const { deps, pending, pageEpoch } = ctx;
12998
12992
  if (deps.refreshView === void 0) return;
12999
12993
  const now = deps.now?.() ?? Date.now();
13000
12994
  if (now - this.lastRefreshAt < REFRESH_INTERVAL_MS) return;
13001
12995
  this.lastRefreshAt = now;
13002
- await flushAcks(deps, pending, epochOf);
13003
- deps.refreshView();
12996
+ if ((await flushAcks(deps, pending, pageEpoch)).progressed) deps.refreshView();
13004
12997
  }
13005
12998
  /** One listing pass: process each entry, flushing and refreshing as it goes. */
13006
12999
  async drainPages(ctx, basis) {
13007
- const { deps, engine, pending, stored, pageEpoch } = ctx;
13000
+ const { deps, pending, pageEpoch, claimable } = ctx;
13008
13001
  for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
13009
- const storedBefore = stored.length;
13010
- await this.processEntry(deps, engine, entry, pending, stored);
13011
- if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
13012
- if (stored.length > storedBefore) await this.maybeRefresh(deps, pending, pageEpoch);
13002
+ const claimableBefore = claimable.n;
13003
+ await this.processEntry(ctx, entry);
13004
+ if (pending.length >= ACK_BATCH_SIZE) {
13005
+ if (!(await flushAcks(deps, pending, pageEpoch)).progressed) return;
13006
+ }
13007
+ if (claimable.n > claimableBefore) await this.maybeRefresh(ctx);
13013
13008
  }
13014
13009
  await flushAcks(deps, pending, pageEpoch);
13015
13010
  }
@@ -13020,11 +13015,16 @@ var Receive = class {
13020
13015
  const stored = [];
13021
13016
  const pending = [];
13022
13017
  const pageEpoch = () => deps.delivery.incomingEpoch() ?? deps.syncEpoch();
13018
+ const claimable = { n: 0 };
13019
+ const finish = () => {
13020
+ if (claimable.n > 0) deps.refreshView?.();
13021
+ return stored;
13022
+ };
13023
13023
  try {
13024
13024
  const record = await deps.kv.get(STORE_KEYS.streamCursor("mailbox"));
13025
13025
  let basis = record !== null && record.syncEpoch === deps.syncEpoch() ? record : null;
13026
13026
  for (let pass = 0; pass < 2; pass++) {
13027
- await this.drainPages({ deps, engine, pending, stored, pageEpoch }, basis);
13027
+ await this.drainPages({ deps, engine, pending, stored, pageEpoch, claimable }, basis);
13028
13028
  const served = deps.delivery.incomingEpoch();
13029
13029
  if (basis === null || served === null || served === basis.syncEpoch) break;
13030
13030
  await deps.kv.remove(STORE_KEYS.streamCursor("mailbox"));
@@ -13032,22 +13032,25 @@ var Receive = class {
13032
13032
  }
13033
13033
  } catch (err) {
13034
13034
  logger.warn("PaymentsV2", "receive drain interrupted \u2014 unacked entries retry next drain:", err);
13035
- if (isRetryableAckError(err)) return stored;
13035
+ if (isRetryableAckError(err)) return finish();
13036
13036
  await flushAcks(deps, pending, pageEpoch).catch((flushErr) => {
13037
13037
  logger.warn("PaymentsV2", "receive ack flush failed \u2014 cursor holds at the acked prefix:", flushErr);
13038
+ return { progressed: false };
13038
13039
  });
13039
13040
  }
13040
- if (stored.length > 0) deps.refreshView?.();
13041
- return stored;
13041
+ return finish();
13042
13042
  }
13043
- async processEntry(deps, engine, entry, pending, stored) {
13043
+ async processEntry(ctx, entry) {
13044
+ const { deps, engine, pending, stored, claimable } = ctx;
13044
13045
  const screened = await screen(deps, engine, entry);
13045
13046
  if (screened.kind === "ack") {
13046
13047
  pending.push(screened.ack);
13048
+ if (screened.ack.disposition === "claimed") claimable.n += 1;
13047
13049
  return;
13048
13050
  }
13049
13051
  await deps.view.store(screened.record);
13050
13052
  pending.push(claimAck(entry));
13053
+ claimable.n += 1;
13051
13054
  const transfer = await announce(deps, entry, screened.record);
13052
13055
  stored.push(transfer);
13053
13056
  deps.emit("transfer:incoming", transfer);
@@ -13057,13 +13060,9 @@ async function screen(deps, engine, entry) {
13057
13060
  const blobBytes = await entry.fetchBlob();
13058
13061
  let token;
13059
13062
  try {
13060
- token = await engine.decodeToken({
13061
- v: TOKEN_BLOB_VERSION,
13062
- network: 0,
13063
- tokenId: "",
13064
- token: blobBytes
13065
- });
13066
- } catch {
13063
+ token = await engine.decodeToken({ tokenId: "", token: blobBytes });
13064
+ } catch (err) {
13065
+ logger.warn("PaymentsV2", `receive: undecodable blob rejected \u2014 ${String(err)}`);
13067
13066
  return { kind: "ack", ack: rejectAck(entry, "invalid") };
13068
13067
  }
13069
13068
  const verdict = await engine.verify(token);
@@ -13108,7 +13107,8 @@ async function announce(deps, entry, record) {
13108
13107
  };
13109
13108
  }
13110
13109
  async function flushAcks(deps, pending, epochOf) {
13111
- if (pending.length === 0) return;
13110
+ if (pending.length === 0) return { progressed: true };
13111
+ const before = pending.length;
13112
13112
  let lastAcked = null;
13113
13113
  try {
13114
13114
  const settled = await settleAcks(deps, pending);
@@ -13126,6 +13126,7 @@ async function flushAcks(deps, pending, epochOf) {
13126
13126
  await deps.kv.set(STORE_KEYS.streamCursor("mailbox"), record);
13127
13127
  }
13128
13128
  }
13129
+ return { progressed: pending.length < before };
13129
13130
  }
13130
13131
  async function settleAcks(deps, pending) {
13131
13132
  const batch = deps.delivery.ackBatch?.bind(deps.delivery);
@@ -14464,7 +14465,7 @@ var PaymentsFacade = class {
14464
14465
  if (bytes === void 0) {
14465
14466
  throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
14466
14467
  }
14467
- const token = await engine.decodeToken({ v: TOKEN_BLOB_VERSION, network: 0, tokenId, token: bytes });
14468
+ const token = await engine.decodeToken({ tokenId, token: bytes });
14468
14469
  const keys = await engine.deliveryKeys(bytes);
14469
14470
  spentStates[tokenId] = { local: keys.stateHash, protocol: keys.stateHash };
14470
14471
  sources.set(tokenId, token);
@@ -15646,7 +15647,9 @@ var WalletApiDeliveryPort = class {
15646
15647
  await this.settleClaims(acks, settled, outcomes);
15647
15648
  await this.settleRejects(acks, settled);
15648
15649
  } catch (err) {
15649
- await this.addSeenAll(settled).catch(() => void 0);
15650
+ await this.addSeenAll(settled).catch((seenErr) => {
15651
+ logger.warn("WalletApiDelivery", `seen-set write failed for ${String(settled.length)} committed acks:`, seenErr);
15652
+ });
15650
15653
  throw asRetryable(err);
15651
15654
  }
15652
15655
  await this.addSeenAll(settled);
@@ -15909,6 +15912,7 @@ function composePaymentsV2(spec) {
15909
15912
  const network = spec.composition.network;
15910
15913
  const identitySlice = { privateKey: identity.privateKey, chainPubkey: identity.chainPubkey };
15911
15914
  const kv = createScopedKV(host.storage, network, identity.chainPubkey);
15915
+ void sweepSupersededState(host.storage);
15912
15916
  const fieldKey = deriveFieldEncryptionKey(identity.privateKey);
15913
15917
  const transport = spec.composition.factory({
15914
15918
  identity: identitySlice,
@@ -16200,11 +16204,10 @@ var Sphere = class _Sphere {
16200
16204
  return config;
16201
16205
  }
16202
16206
  /**
16203
- * The ONE sanctioned refusal fossil of the P11 flip (revisit-and-delete after
16204
- * one release): `accounting`/`swap` were public init flags — silently
16205
- * ignoring them would hide that invoices/swaps no longer exist in the SDK.
16206
- * `paymentsV2: true` stays an accepted no-op for the same one release (the
16207
- * live frontend passes it today).
16207
+ * The ONE sanctioned refusal fossil of the P11 flip: `accounting`/`swap` were
16208
+ * public init flags — silently ignoring them would hide that invoices/swaps no
16209
+ * longer exist in the SDK. Kept through 0.15.0, where consumers re-integrate
16210
+ * across the wire break and a silent no-op would be worst.
16208
16211
  */
16209
16212
  static refuseRetiredModuleOptions(options) {
16210
16213
  if (options.accounting) {
@@ -16471,7 +16474,7 @@ var Sphere = class _Sphere {
16471
16474
  }
16472
16475
  /**
16473
16476
  * Clear all SDK-owned wallet data from storage: wallet keys, per-address
16474
- * data, and the payments vertical's `pv2:{network}:{pubkey}:*` scoped KV
16477
+ * data, and the payments vertical's `pv2g2:{network}:{pubkey}:*` scoped KV
16475
16478
  * (refresh token, cursors, journals — all live in the plain StorageProvider,
16476
16479
  * so the full KV wipe below removes them). Tokens are server-custody; the
16477
16480
  * only local token artifacts are orphaned `sphere-token-storage-*` IndexedDB
@@ -16581,10 +16584,6 @@ var Sphere = class _Sphere {
16581
16584
  }
16582
16585
  return facade;
16583
16586
  }
16584
- /** @deprecated Alias of {@link payments} kept for one release (the live frontend calls it). Null when no vertical runs. */
16585
- get paymentsV2() {
16586
- return this._paymentsV2Active?.facade ?? null;
16587
- }
16588
16587
  /** Communications module */
16589
16588
  get communications() {
16590
16589
  this.ensureReady();