@unicitylabs/sphere-sdk 0.14.5 → 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.
@@ -314,7 +314,18 @@ function pause(ms, signal) {
314
314
  );
315
315
  });
316
316
  }
317
- async function awaitProofBounded(deps, transaction, external) {
317
+ async function retryTransient(attempt, signal, intervalMs) {
318
+ for (; ; ) {
319
+ try {
320
+ return await attempt();
321
+ } catch (err) {
322
+ if (signal.aborted) throw err;
323
+ if (!isTransientGatewayError(err)) throw err;
324
+ await pause(intervalMs, signal);
325
+ }
326
+ }
327
+ }
328
+ function certificationDeadline(timeoutMs, external, label) {
318
329
  const controller = new AbortController();
319
330
  const relayAbort = () => {
320
331
  controller.abort(external?.reason);
@@ -324,47 +335,29 @@ async function awaitProofBounded(deps, transaction, external) {
324
335
  else external.addEventListener("abort", relayAbort, { once: true });
325
336
  }
326
337
  const timer = setTimeout(() => {
327
- controller.abort(
328
- new SphereError(
329
- `inclusion proof not confirmed within ${String(deps.timeoutMs)} ms`,
330
- "AGGREGATOR_ERROR"
331
- )
332
- );
333
- }, deps.timeoutMs);
334
- try {
335
- for (; ; ) {
336
- try {
337
- return await waitInclusionProof(
338
- deps.client,
339
- deps.trustBase,
340
- deps.predicateVerifier,
341
- transaction,
342
- controller.signal,
343
- deps.intervalMs
344
- );
345
- } catch (err) {
346
- if (controller.signal.aborted) throw err;
347
- if (!isTransientGatewayError(err)) throw err;
348
- await pause(deps.intervalMs, controller.signal);
349
- }
338
+ controller.abort(new SphereError(`${label} within ${String(timeoutMs)} ms`, "AGGREGATOR_ERROR"));
339
+ }, timeoutMs);
340
+ return {
341
+ signal: controller.signal,
342
+ dispose: () => {
343
+ clearTimeout(timer);
344
+ external?.removeEventListener("abort", relayAbort);
350
345
  }
351
- } finally {
352
- clearTimeout(timer);
353
- external?.removeEventListener("abort", relayAbort);
354
- }
346
+ };
355
347
  }
356
-
357
- // core/uuid.ts
358
- function randomUUID() {
359
- const c = globalThis.crypto;
360
- if (typeof c !== "undefined" && typeof c.randomUUID === "function") {
361
- return c.randomUUID();
362
- }
363
- const b = c.getRandomValues(new Uint8Array(16));
364
- b[6] = b[6] & 15 | 64;
365
- b[8] = b[8] & 63 | 128;
366
- const h = Array.from(b, (x) => x.toString(16).padStart(2, "0"));
367
- 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]}`;
348
+ async function awaitProofBounded(deps, transaction, signal) {
349
+ return retryTransient(
350
+ () => waitInclusionProof(
351
+ deps.client,
352
+ deps.trustBase,
353
+ deps.predicateVerifier,
354
+ transaction,
355
+ signal,
356
+ deps.intervalMs
357
+ ),
358
+ signal,
359
+ deps.intervalMs
360
+ );
368
361
  }
369
362
 
370
363
  // token-engine/errors.ts
@@ -406,6 +399,34 @@ var CheckpointTrustbaseMismatchError = class extends SphereError {
406
399
  }
407
400
  };
408
401
 
402
+ // token-engine/certification-outcome.ts
403
+ function certificationFailure(err, failLabel, submitError, submitRejectedCleanly) {
404
+ if (err instanceof Error && err.message === `Invalid inclusion proof status: ${InclusionProofVerificationStatus.TRANSACTION_HASH_MISMATCH}`) {
405
+ return new TransferConflictError(
406
+ `${failLabel}: the source state was already consumed by a different transaction (lost race \u2014 abort this intent and re-plan under a new transferId)`,
407
+ err
408
+ );
409
+ }
410
+ if (submitRejectedCleanly) return submitError;
411
+ return new ProofUnconfirmedError(
412
+ `${failLabel}: certification unconfirmed \u2014 the source spend may be on-chain; keep the intent open and resume under the same transferId`,
413
+ submitError ?? err
414
+ );
415
+ }
416
+
417
+ // core/uuid.ts
418
+ function randomUUID() {
419
+ const c = globalThis.crypto;
420
+ if (typeof c !== "undefined" && typeof c.randomUUID === "function") {
421
+ return c.randomUUID();
422
+ }
423
+ const b = c.getRandomValues(new Uint8Array(16));
424
+ b[6] = b[6] & 15 | 64;
425
+ b[8] = b[8] & 63 | 128;
426
+ const h = Array.from(b, (x) => x.toString(16).padStart(2, "0"));
427
+ 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]}`;
428
+ }
429
+
409
430
  // token-engine/token-blob.ts
410
431
  var TOKEN_BLOB_TAG = 39051n;
411
432
  var TOKEN_BLOB_VERSION = 1;
@@ -1348,10 +1369,28 @@ var SphereTokenEngine = class {
1348
1369
  * failure, not a resume case).
1349
1370
  */
1350
1371
  async submitAndAwaitProof(certificationData, transaction, failLabel, failCode, options) {
1372
+ const deadline = certificationDeadline(
1373
+ this.proofTimeoutMs,
1374
+ options?.signal,
1375
+ `${failLabel}: not certified`
1376
+ );
1377
+ try {
1378
+ return await this.submitThenProve(certificationData, transaction, failLabel, failCode, deadline.signal);
1379
+ } finally {
1380
+ deadline.dispose();
1381
+ }
1382
+ }
1383
+ /** One certification attempt under one deadline: the metered submit retries
1384
+ * transient answers instead of falling through to a proof that cannot exist. */
1385
+ async submitThenProve(certificationData, transaction, failLabel, failCode, signal) {
1351
1386
  let submitError = null;
1352
1387
  let submitRejectedCleanly = false;
1353
1388
  try {
1354
- const response = await this.deps.client.submitCertificationRequest(certificationData);
1389
+ const response = await retryTransient(
1390
+ () => this.deps.client.submitCertificationRequest(certificationData),
1391
+ signal,
1392
+ this.deps.proofPollIntervalMs ?? DEFAULT_PROOF_POLL_INTERVAL_MS
1393
+ );
1355
1394
  if (response.status !== CertificationStatus.SUCCESS) {
1356
1395
  submitError = new SphereError(`${failLabel}: ${response.status}`, failCode);
1357
1396
  submitRejectedCleanly = CLEAN_REJECT_STATUSES.has(response.status);
@@ -1365,33 +1404,21 @@ var SphereTokenEngine = class {
1365
1404
  client: this.deps.client,
1366
1405
  trustBase: this.deps.trustBase,
1367
1406
  predicateVerifier: this.deps.predicateVerifier,
1368
- timeoutMs: this.proofTimeoutMs,
1369
1407
  intervalMs: this.deps.proofPollIntervalMs ?? DEFAULT_PROOF_POLL_INTERVAL_MS
1370
1408
  // #683
1371
1409
  },
1372
1410
  transaction,
1373
- options?.signal
1411
+ signal
1374
1412
  );
1375
1413
  } catch (err) {
1376
- if (err instanceof Error && err.message === `Invalid inclusion proof status: ${InclusionProofVerificationStatus.TRANSACTION_HASH_MISMATCH}`) {
1377
- throw new TransferConflictError(
1378
- `${failLabel}: the source state was already consumed by a different transaction (lost race \u2014 abort this intent and re-plan under a new transferId)`,
1379
- err
1380
- );
1381
- }
1382
- if (submitRejectedCleanly) throw submitError;
1383
- throw new ProofUnconfirmedError(
1384
- `${failLabel}: certification unconfirmed \u2014 the source spend may be on-chain; keep the intent open and resume under the same transferId`,
1385
- submitError ?? err
1386
- );
1414
+ throw certificationFailure(err, failLabel, submitError, submitRejectedCleanly);
1387
1415
  }
1388
1416
  }
1389
1417
  /**
1390
- * One inclusion-proof wait, with the deadline OWNED here (#739). The SDK's own
1391
- * default deadline is unreliable — see lateDeliveringSignal — and a lost one
1392
- * means an op that never settles, which also wedges PaymentsFacade.stop().
1393
- * A deadline hit throws, and the caller maps that to ProofUnconfirmedError
1394
- * (keep-open), which is the correct posture: the spend may be on-chain.
1418
+ * One inclusion-proof wait, with the deadline OWNED here (#739) so it is bounded
1419
+ * and configurable rather than the SDK's fixed default. A hit throws, and the
1420
+ * caller maps it to ProofUnconfirmedError (keep-open) — the correct posture,
1421
+ * since the spend may be on-chain.
1395
1422
  */
1396
1423
  /** Fail fast if this engine's key does not own the token's current state. */
1397
1424
  assertOwned(token) {