@unicitylabs/sphere-sdk 0.14.4 → 0.14.6-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.
@@ -332,41 +332,35 @@ function resolveProofTimeoutMs(configured) {
332
332
  }
333
333
  return configured;
334
334
  }
335
- function lateDeliveringSignal(signal) {
336
- return new Proxy(signal, {
337
- get(target, prop) {
338
- if (prop === "addEventListener") {
339
- return (type, listener, options) => {
340
- if (type === "abort" && target.aborted) {
341
- queueMicrotask(() => {
342
- listener.call(target, new Event("abort"));
343
- });
344
- return;
345
- }
346
- target.addEventListener(type, listener, options);
347
- };
348
- }
349
- const value = Reflect.get(target, prop, target);
350
- return typeof value === "function" ? value.bind(target) : value;
351
- }
352
- });
335
+ var TRANSIENT_HTTP_STATUSES = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
336
+ function isTransientGatewayError(err) {
337
+ return err instanceof import_JsonRpcNetworkError.JsonRpcNetworkError && TRANSIENT_HTTP_STATUSES.has(err.status);
353
338
  }
354
- function rejectOnAbort(signal) {
355
- return new Promise((_resolve, reject) => {
356
- if (signal.aborted) {
357
- reject(signal.reason);
358
- return;
359
- }
339
+ function pause(ms, signal) {
340
+ return new Promise((resolve) => {
341
+ const timer = setTimeout(resolve, ms);
360
342
  signal.addEventListener(
361
343
  "abort",
362
344
  () => {
363
- reject(signal.reason);
345
+ clearTimeout(timer);
346
+ resolve();
364
347
  },
365
348
  { once: true }
366
349
  );
367
350
  });
368
351
  }
369
- 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) {
370
364
  const controller = new AbortController();
371
365
  const relayAbort = () => {
372
366
  controller.abort(external?.reason);
@@ -376,41 +370,29 @@ async function awaitProofBounded(deps, transaction, external) {
376
370
  else external.addEventListener("abort", relayAbort, { once: true });
377
371
  }
378
372
  const timer = setTimeout(() => {
379
- controller.abort(
380
- new SphereError(
381
- `inclusion proof not confirmed within ${String(deps.timeoutMs)} ms`,
382
- "AGGREGATOR_ERROR"
383
- )
384
- );
385
- }, deps.timeoutMs);
386
- const wait = (0, import_InclusionProofUtils.waitInclusionProof)(
387
- deps.client,
388
- deps.trustBase,
389
- deps.predicateVerifier,
390
- transaction,
391
- lateDeliveringSignal(controller.signal),
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);
380
+ }
381
+ };
382
+ }
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,
392
394
  deps.intervalMs
393
395
  );
394
- wait.catch(() => void 0);
395
- try {
396
- return await Promise.race([wait, rejectOnAbort(controller.signal)]);
397
- } finally {
398
- clearTimeout(timer);
399
- external?.removeEventListener("abort", relayAbort);
400
- }
401
- }
402
-
403
- // core/uuid.ts
404
- function randomUUID() {
405
- const c = globalThis.crypto;
406
- if (typeof c !== "undefined" && typeof c.randomUUID === "function") {
407
- return c.randomUUID();
408
- }
409
- const b = c.getRandomValues(new Uint8Array(16));
410
- b[6] = b[6] & 15 | 64;
411
- b[8] = b[8] & 63 | 128;
412
- const h = Array.from(b, (x) => x.toString(16).padStart(2, "0"));
413
- 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]}`;
414
396
  }
415
397
 
416
398
  // token-engine/errors.ts
@@ -452,6 +434,34 @@ var CheckpointTrustbaseMismatchError = class extends SphereError {
452
434
  }
453
435
  };
454
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
+
455
465
  // token-engine/token-blob.ts
456
466
  var TOKEN_BLOB_TAG = 39051n;
457
467
  var TOKEN_BLOB_VERSION = 1;
@@ -1394,10 +1404,28 @@ var SphereTokenEngine = class {
1394
1404
  * failure, not a resume case).
1395
1405
  */
1396
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) {
1397
1421
  let submitError = null;
1398
1422
  let submitRejectedCleanly = false;
1399
1423
  try {
1400
- 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
+ );
1401
1429
  if (response.status !== import_CertificationResponse.CertificationStatus.SUCCESS) {
1402
1430
  submitError = new SphereError(`${failLabel}: ${response.status}`, failCode);
1403
1431
  submitRejectedCleanly = CLEAN_REJECT_STATUSES.has(response.status);
@@ -1411,33 +1439,21 @@ var SphereTokenEngine = class {
1411
1439
  client: this.deps.client,
1412
1440
  trustBase: this.deps.trustBase,
1413
1441
  predicateVerifier: this.deps.predicateVerifier,
1414
- timeoutMs: this.proofTimeoutMs,
1415
1442
  intervalMs: this.deps.proofPollIntervalMs ?? DEFAULT_PROOF_POLL_INTERVAL_MS
1416
1443
  // #683
1417
1444
  },
1418
1445
  transaction,
1419
- options?.signal
1446
+ signal
1420
1447
  );
1421
1448
  } catch (err) {
1422
- if (err instanceof Error && err.message === `Invalid inclusion proof status: ${import_InclusionProofVerificationRule.InclusionProofVerificationStatus.TRANSACTION_HASH_MISMATCH}`) {
1423
- throw new TransferConflictError(
1424
- `${failLabel}: the source state was already consumed by a different transaction (lost race \u2014 abort this intent and re-plan under a new transferId)`,
1425
- err
1426
- );
1427
- }
1428
- if (submitRejectedCleanly) throw submitError;
1429
- throw new ProofUnconfirmedError(
1430
- `${failLabel}: certification unconfirmed \u2014 the source spend may be on-chain; keep the intent open and resume under the same transferId`,
1431
- submitError ?? err
1432
- );
1449
+ throw certificationFailure(err, failLabel, submitError, submitRejectedCleanly);
1433
1450
  }
1434
1451
  }
1435
1452
  /**
1436
- * One inclusion-proof wait, with the deadline OWNED here (#739). The SDK's own
1437
- * default deadline is unreliable — see lateDeliveringSignal — and a lost one
1438
- * means an op that never settles, which also wedges PaymentsFacade.stop().
1439
- * A deadline hit throws, and the caller maps that to ProofUnconfirmedError
1440
- * (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.
1441
1457
  */
1442
1458
  /** Fail fast if this engine's key does not own the token's current state. */
1443
1459
  assertOwned(token) {