@unicitylabs/sphere-sdk 0.14.5 → 0.14.6

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.
@@ -349,7 +349,18 @@ function pause(ms, signal) {
349
349
  );
350
350
  });
351
351
  }
352
- async function awaitProofBounded(deps, transaction, external) {
352
+ async function retryTransient(attempt, signal, intervalMs) {
353
+ for (; ; ) {
354
+ try {
355
+ return await attempt();
356
+ } catch (err) {
357
+ if (signal.aborted) throw err;
358
+ if (!isTransientGatewayError(err)) throw err;
359
+ await pause(intervalMs, signal);
360
+ }
361
+ }
362
+ }
363
+ function certificationDeadline(timeoutMs, external, label) {
353
364
  const controller = new AbortController();
354
365
  const relayAbort = () => {
355
366
  controller.abort(external?.reason);
@@ -359,47 +370,29 @@ async function awaitProofBounded(deps, transaction, external) {
359
370
  else external.addEventListener("abort", relayAbort, { once: true });
360
371
  }
361
372
  const timer = setTimeout(() => {
362
- controller.abort(
363
- new SphereError(
364
- `inclusion proof not confirmed within ${String(deps.timeoutMs)} ms`,
365
- "AGGREGATOR_ERROR"
366
- )
367
- );
368
- }, deps.timeoutMs);
369
- try {
370
- for (; ; ) {
371
- try {
372
- return await (0, import_InclusionProofUtils.waitInclusionProof)(
373
- deps.client,
374
- deps.trustBase,
375
- deps.predicateVerifier,
376
- transaction,
377
- controller.signal,
378
- deps.intervalMs
379
- );
380
- } catch (err) {
381
- if (controller.signal.aborted) throw err;
382
- if (!isTransientGatewayError(err)) throw err;
383
- await pause(deps.intervalMs, controller.signal);
384
- }
373
+ controller.abort(new SphereError(`${label} within ${String(timeoutMs)} ms`, "AGGREGATOR_ERROR"));
374
+ }, timeoutMs);
375
+ return {
376
+ signal: controller.signal,
377
+ dispose: () => {
378
+ clearTimeout(timer);
379
+ external?.removeEventListener("abort", relayAbort);
385
380
  }
386
- } finally {
387
- clearTimeout(timer);
388
- external?.removeEventListener("abort", relayAbort);
389
- }
381
+ };
390
382
  }
391
-
392
- // core/uuid.ts
393
- function randomUUID() {
394
- const c = globalThis.crypto;
395
- if (typeof c !== "undefined" && typeof c.randomUUID === "function") {
396
- return c.randomUUID();
397
- }
398
- const b = c.getRandomValues(new Uint8Array(16));
399
- b[6] = b[6] & 15 | 64;
400
- b[8] = b[8] & 63 | 128;
401
- const h = Array.from(b, (x) => x.toString(16).padStart(2, "0"));
402
- return `${h[0]}${h[1]}${h[2]}${h[3]}-${h[4]}${h[5]}-${h[6]}${h[7]}-${h[8]}${h[9]}-${h[10]}${h[11]}${h[12]}${h[13]}${h[14]}${h[15]}`;
383
+ async function awaitProofBounded(deps, transaction, signal) {
384
+ return retryTransient(
385
+ () => (0, import_InclusionProofUtils.waitInclusionProof)(
386
+ deps.client,
387
+ deps.trustBase,
388
+ deps.predicateVerifier,
389
+ transaction,
390
+ signal,
391
+ deps.intervalMs
392
+ ),
393
+ signal,
394
+ deps.intervalMs
395
+ );
403
396
  }
404
397
 
405
398
  // token-engine/errors.ts
@@ -441,6 +434,34 @@ var CheckpointTrustbaseMismatchError = class extends SphereError {
441
434
  }
442
435
  };
443
436
 
437
+ // token-engine/certification-outcome.ts
438
+ function certificationFailure(err, failLabel, submitError, submitRejectedCleanly) {
439
+ if (err instanceof Error && err.message === `Invalid inclusion proof status: ${import_InclusionProofVerificationRule.InclusionProofVerificationStatus.TRANSACTION_HASH_MISMATCH}`) {
440
+ return new TransferConflictError(
441
+ `${failLabel}: the source state was already consumed by a different transaction (lost race \u2014 abort this intent and re-plan under a new transferId)`,
442
+ err
443
+ );
444
+ }
445
+ if (submitRejectedCleanly) return submitError;
446
+ return new ProofUnconfirmedError(
447
+ `${failLabel}: certification unconfirmed \u2014 the source spend may be on-chain; keep the intent open and resume under the same transferId`,
448
+ submitError ?? err
449
+ );
450
+ }
451
+
452
+ // core/uuid.ts
453
+ function randomUUID() {
454
+ const c = globalThis.crypto;
455
+ if (typeof c !== "undefined" && typeof c.randomUUID === "function") {
456
+ return c.randomUUID();
457
+ }
458
+ const b = c.getRandomValues(new Uint8Array(16));
459
+ b[6] = b[6] & 15 | 64;
460
+ b[8] = b[8] & 63 | 128;
461
+ const h = Array.from(b, (x) => x.toString(16).padStart(2, "0"));
462
+ return `${h[0]}${h[1]}${h[2]}${h[3]}-${h[4]}${h[5]}-${h[6]}${h[7]}-${h[8]}${h[9]}-${h[10]}${h[11]}${h[12]}${h[13]}${h[14]}${h[15]}`;
463
+ }
464
+
444
465
  // token-engine/token-blob.ts
445
466
  var TOKEN_BLOB_TAG = 39051n;
446
467
  var TOKEN_BLOB_VERSION = 1;
@@ -1383,10 +1404,28 @@ var SphereTokenEngine = class {
1383
1404
  * failure, not a resume case).
1384
1405
  */
1385
1406
  async submitAndAwaitProof(certificationData, transaction, failLabel, failCode, options) {
1407
+ const deadline = certificationDeadline(
1408
+ this.proofTimeoutMs,
1409
+ options?.signal,
1410
+ `${failLabel}: not certified`
1411
+ );
1412
+ try {
1413
+ return await this.submitThenProve(certificationData, transaction, failLabel, failCode, deadline.signal);
1414
+ } finally {
1415
+ deadline.dispose();
1416
+ }
1417
+ }
1418
+ /** One certification attempt under one deadline: the metered submit retries
1419
+ * transient answers instead of falling through to a proof that cannot exist. */
1420
+ async submitThenProve(certificationData, transaction, failLabel, failCode, signal) {
1386
1421
  let submitError = null;
1387
1422
  let submitRejectedCleanly = false;
1388
1423
  try {
1389
- const response = await this.deps.client.submitCertificationRequest(certificationData);
1424
+ const response = await retryTransient(
1425
+ () => this.deps.client.submitCertificationRequest(certificationData),
1426
+ signal,
1427
+ this.deps.proofPollIntervalMs ?? DEFAULT_PROOF_POLL_INTERVAL_MS
1428
+ );
1390
1429
  if (response.status !== import_CertificationResponse.CertificationStatus.SUCCESS) {
1391
1430
  submitError = new SphereError(`${failLabel}: ${response.status}`, failCode);
1392
1431
  submitRejectedCleanly = CLEAN_REJECT_STATUSES.has(response.status);
@@ -1400,33 +1439,21 @@ var SphereTokenEngine = class {
1400
1439
  client: this.deps.client,
1401
1440
  trustBase: this.deps.trustBase,
1402
1441
  predicateVerifier: this.deps.predicateVerifier,
1403
- timeoutMs: this.proofTimeoutMs,
1404
1442
  intervalMs: this.deps.proofPollIntervalMs ?? DEFAULT_PROOF_POLL_INTERVAL_MS
1405
1443
  // #683
1406
1444
  },
1407
1445
  transaction,
1408
- options?.signal
1446
+ signal
1409
1447
  );
1410
1448
  } catch (err) {
1411
- if (err instanceof Error && err.message === `Invalid inclusion proof status: ${import_InclusionProofVerificationRule.InclusionProofVerificationStatus.TRANSACTION_HASH_MISMATCH}`) {
1412
- throw new TransferConflictError(
1413
- `${failLabel}: the source state was already consumed by a different transaction (lost race \u2014 abort this intent and re-plan under a new transferId)`,
1414
- err
1415
- );
1416
- }
1417
- if (submitRejectedCleanly) throw submitError;
1418
- throw new ProofUnconfirmedError(
1419
- `${failLabel}: certification unconfirmed \u2014 the source spend may be on-chain; keep the intent open and resume under the same transferId`,
1420
- submitError ?? err
1421
- );
1449
+ throw certificationFailure(err, failLabel, submitError, submitRejectedCleanly);
1422
1450
  }
1423
1451
  }
1424
1452
  /**
1425
- * One inclusion-proof wait, with the deadline OWNED here (#739). The SDK's own
1426
- * default deadline is unreliable — see lateDeliveringSignal — and a lost one
1427
- * means an op that never settles, which also wedges PaymentsFacade.stop().
1428
- * A deadline hit throws, and the caller maps that to ProofUnconfirmedError
1429
- * (keep-open), which is the correct posture: the spend may be on-chain.
1453
+ * One inclusion-proof wait, with the deadline OWNED here (#739) so it is bounded
1454
+ * and configurable rather than the SDK's fixed default. A hit throws, and the
1455
+ * caller maps it to ProofUnconfirmedError (keep-open) — the correct posture,
1456
+ * since the spend may be on-chain.
1430
1457
  */
1431
1458
  /** Fail fast if this engine's key does not own the token's current state. */
1432
1459
  assertOwned(token) {