@unicitylabs/sphere-sdk 0.14.3 → 0.14.4

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.
@@ -341,7 +341,11 @@ interface EngineConfig {
341
341
  readonly trustBaseJson: unknown;
342
342
  /** Inclusion-proof poll cadence in ms (engine owns the await policy; Spike S1). */
343
343
  readonly proofPollIntervalMs?: number;
344
- /** Inclusion-proof overall timeout in ms (0/undefined = no engine-side cap). */
344
+ /**
345
+ * Deadline for ONE inclusion-proof wait, in ms. Undefined → 10s. Must be
346
+ * positive: an unbounded wait hangs the op and with it teardown (#739), so
347
+ * there is no "no cap" value — 0 or negative throws INVALID_CONFIG.
348
+ */
345
349
  readonly proofTimeoutMs?: number;
346
350
  /**
347
351
  * Opt in to parallel verification. Omitted → the sequential verifier, i.e. the
@@ -341,7 +341,11 @@ interface EngineConfig {
341
341
  readonly trustBaseJson: unknown;
342
342
  /** Inclusion-proof poll cadence in ms (engine owns the await policy; Spike S1). */
343
343
  readonly proofPollIntervalMs?: number;
344
- /** Inclusion-proof overall timeout in ms (0/undefined = no engine-side cap). */
344
+ /**
345
+ * Deadline for ONE inclusion-proof wait, in ms. Undefined → 10s. Must be
346
+ * positive: an unbounded wait hangs the op and with it teardown (#739), so
347
+ * there is no "no cap" value — 0 or negative throws INVALID_CONFIG.
348
+ */
345
349
  readonly proofTimeoutMs?: number;
346
350
  /**
347
351
  * Opt in to parallel verification. Omitted → the sequential verifier, i.e. the
@@ -7,6 +7,7 @@ import { CertificationResponse, CertificationStatus } from "@unicitylabs/state-t
7
7
  import { StateId } from "@unicitylabs/state-transition-sdk/lib/api/StateId.js";
8
8
  import { InclusionProof } from "@unicitylabs/state-transition-sdk/lib/api/InclusionProof.js";
9
9
  import { InclusionProofResponse } from "@unicitylabs/state-transition-sdk/lib/api/InclusionProofResponse.js";
10
+ import { JsonRpcNetworkError } from "@unicitylabs/state-transition-sdk/lib/api/json-rpc/JsonRpcNetworkError.js";
10
11
  import { RootTrustBase } from "@unicitylabs/state-transition-sdk/lib/api/bft/RootTrustBase.js";
11
12
  import { waitInclusionProof } from "@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js";
12
13
  import { InclusionProofVerificationStatus } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/rule/InclusionProofVerificationRule.js";
@@ -284,6 +285,86 @@ function decodeSpherePaymentData(bytes) {
284
285
  return Promise.resolve(SpherePaymentData.fromCBOR(bytes));
285
286
  }
286
287
 
288
+ // token-engine/proof-wait.ts
289
+ var DEFAULT_PROOF_TIMEOUT_MS = 1e4;
290
+ function resolveProofTimeoutMs(configured) {
291
+ if (configured === void 0) return DEFAULT_PROOF_TIMEOUT_MS;
292
+ if (!Number.isFinite(configured) || configured <= 0) {
293
+ throw new SphereError(
294
+ `proofTimeoutMs must be a positive number of milliseconds (got ${String(configured)}); an unbounded inclusion-proof wait is not supported`,
295
+ "INVALID_CONFIG"
296
+ );
297
+ }
298
+ return configured;
299
+ }
300
+ function lateDeliveringSignal(signal) {
301
+ return new Proxy(signal, {
302
+ get(target, prop) {
303
+ if (prop === "addEventListener") {
304
+ return (type, listener, options) => {
305
+ if (type === "abort" && target.aborted) {
306
+ queueMicrotask(() => {
307
+ listener.call(target, new Event("abort"));
308
+ });
309
+ return;
310
+ }
311
+ target.addEventListener(type, listener, options);
312
+ };
313
+ }
314
+ const value = Reflect.get(target, prop, target);
315
+ return typeof value === "function" ? value.bind(target) : value;
316
+ }
317
+ });
318
+ }
319
+ function rejectOnAbort(signal) {
320
+ return new Promise((_resolve, reject) => {
321
+ if (signal.aborted) {
322
+ reject(signal.reason);
323
+ return;
324
+ }
325
+ signal.addEventListener(
326
+ "abort",
327
+ () => {
328
+ reject(signal.reason);
329
+ },
330
+ { once: true }
331
+ );
332
+ });
333
+ }
334
+ async function awaitProofBounded(deps, transaction, external) {
335
+ const controller = new AbortController();
336
+ const relayAbort = () => {
337
+ controller.abort(external?.reason);
338
+ };
339
+ if (external !== void 0) {
340
+ if (external.aborted) relayAbort();
341
+ else external.addEventListener("abort", relayAbort, { once: true });
342
+ }
343
+ const timer = setTimeout(() => {
344
+ controller.abort(
345
+ new SphereError(
346
+ `inclusion proof not confirmed within ${String(deps.timeoutMs)} ms`,
347
+ "AGGREGATOR_ERROR"
348
+ )
349
+ );
350
+ }, deps.timeoutMs);
351
+ const wait = waitInclusionProof(
352
+ deps.client,
353
+ deps.trustBase,
354
+ deps.predicateVerifier,
355
+ transaction,
356
+ lateDeliveringSignal(controller.signal),
357
+ deps.intervalMs
358
+ );
359
+ wait.catch(() => void 0);
360
+ try {
361
+ return await Promise.race([wait, rejectOnAbort(controller.signal)]);
362
+ } finally {
363
+ clearTimeout(timer);
364
+ external?.removeEventListener("abort", relayAbort);
365
+ }
366
+ }
367
+
287
368
  // core/uuid.ts
288
369
  function randomUUID() {
289
370
  const c = globalThis.crypto;
@@ -941,11 +1022,13 @@ var SphereTokenEngine = class {
941
1022
  constructor(deps) {
942
1023
  this.deps = deps;
943
1024
  this.privateKeyHex = HexConverter.encode(deps.privateKey);
1025
+ this.proofTimeoutMs = resolveProofTimeoutMs(deps.proofTimeoutMs);
944
1026
  }
945
1027
  /** F13 advertisement: mint under a (transferId, opIndex) seed is idempotent-recoverable — a same-seed re-call converges, never double-mints. */
946
1028
  deterministicMint = true;
947
1029
  /** Hex form of the wallet key — deriveRealization's ikm input (Part E.1). */
948
1030
  privateKeyHex;
1031
+ proofTimeoutMs;
949
1032
  // ── identity ────────────────────────────────────────────────────────────────
950
1033
  getIdentity() {
951
1034
  return { chainPubkey: new Uint8Array(this.deps.signingService.publicKey) };
@@ -1288,14 +1371,17 @@ var SphereTokenEngine = class {
1288
1371
  submitError = err ?? new SphereError(`${failLabel}: submit failed`, failCode);
1289
1372
  }
1290
1373
  try {
1291
- return await waitInclusionProof(
1292
- this.deps.client,
1293
- this.deps.trustBase,
1294
- this.deps.predicateVerifier,
1374
+ return await awaitProofBounded(
1375
+ {
1376
+ client: this.deps.client,
1377
+ trustBase: this.deps.trustBase,
1378
+ predicateVerifier: this.deps.predicateVerifier,
1379
+ timeoutMs: this.proofTimeoutMs,
1380
+ intervalMs: this.deps.proofPollIntervalMs ?? DEFAULT_PROOF_POLL_INTERVAL_MS
1381
+ // #683
1382
+ },
1295
1383
  transaction,
1296
- options?.signal,
1297
- this.deps.proofPollIntervalMs ?? DEFAULT_PROOF_POLL_INTERVAL_MS
1298
- // #683 finer poll cadence
1384
+ options?.signal
1299
1385
  );
1300
1386
  } catch (err) {
1301
1387
  if (err instanceof Error && err.message === `Invalid inclusion proof status: ${InclusionProofVerificationStatus.TRANSACTION_HASH_MISMATCH}`) {
@@ -1311,6 +1397,13 @@ var SphereTokenEngine = class {
1311
1397
  );
1312
1398
  }
1313
1399
  }
1400
+ /**
1401
+ * One inclusion-proof wait, with the deadline OWNED here (#739). The SDK's own
1402
+ * default deadline is unreliable — see lateDeliveringSignal — and a lost one
1403
+ * means an op that never settles, which also wedges PaymentsFacade.stop().
1404
+ * A deadline hit throws, and the caller maps that to ProofUnconfirmedError
1405
+ * (keep-open), which is the correct posture: the spend may be on-chain.
1406
+ */
1314
1407
  /** Fail fast if this engine's key does not own the token's current state. */
1315
1408
  assertOwned(token) {
1316
1409
  if (!this.isOwnedBy(token, this.deps.signingService.publicKey)) {
@@ -1410,6 +1503,8 @@ async function createSphereTokenEngine(config) {
1410
1503
  networkId: trustBase.networkId,
1411
1504
  // #683: forward the (optional) proof-poll cadence; undefined → the engine default.
1412
1505
  proofPollIntervalMs: config.proofPollIntervalMs,
1506
+ proofTimeoutMs: config.proofTimeoutMs,
1507
+ // #739: was declared but never wired
1413
1508
  // Opt-in parallel verification (2.0.2). Absent → the sequential verifier.
1414
1509
  ...config.verification ? { tokenVerifier: createWorkerTokenVerifier(config.verification) } : {}
1415
1510
  };