@rhinestone/deposit-modal 0.16.0 → 0.17.0

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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  MODAL_VERSION,
3
3
  serviceHeaders
4
- } from "./chunk-3HCCR54R.mjs";
4
+ } from "./chunk-XCBDJPBD.mjs";
5
5
  import {
6
6
  CHAIN_DISPLAY_ORDER,
7
7
  HYPERCORE_CHAIN_ID,
@@ -650,12 +650,37 @@ function parseQuotePreview(raw) {
650
650
  );
651
651
  if (Math.abs(totalUsd - breakdownTotalUsd) > 1e-6) return null;
652
652
  const breakdown = Object.fromEntries(parsedEntries);
653
+ const timing = parseDepositTiming(obj.timing);
653
654
  return {
654
655
  settlementLayer: obj.settlementLayer,
655
656
  expiresAt: obj.expiresAt,
656
657
  estimatedFillTimeSeconds: obj.estimatedFillTimeSeconds,
657
658
  fees: { total: { usd: totalUsd }, breakdown },
658
- output
659
+ output,
660
+ ...timing ? { timing } : {}
661
+ };
662
+ }
663
+ function parseDepositTiming(raw) {
664
+ if (typeof raw !== "object" || raw === null) return void 0;
665
+ const o = raw;
666
+ const seconds = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
667
+ const expectedSeconds = seconds(o.expectedSeconds);
668
+ const softDelaySeconds = seconds(o.softDelaySeconds);
669
+ const escalatedDelaySeconds = seconds(o.escalatedDelaySeconds);
670
+ if (expectedSeconds === void 0 || softDelaySeconds === void 0 || escalatedDelaySeconds === void 0) {
671
+ return void 0;
672
+ }
673
+ return { expectedSeconds, softDelaySeconds, escalatedDelaySeconds };
674
+ }
675
+ function normalizeHistoryTiming(raw) {
676
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
677
+ return raw;
678
+ }
679
+ const { timing: rawTiming, ...row } = raw;
680
+ const timing = parseDepositTiming(rawTiming);
681
+ return {
682
+ ...row,
683
+ ...timing ? { timing } : {}
659
684
  };
660
685
  }
661
686
  function parseQuoteOutput(raw) {
@@ -1197,7 +1222,7 @@ function createDepositService(baseUrl, options) {
1197
1222
  );
1198
1223
  }
1199
1224
  const data = await response.json();
1200
- const deposits = Array.isArray(data?.deposits) ? data.deposits : [];
1225
+ const deposits = Array.isArray(data?.deposits) ? data.deposits.map(normalizeHistoryTiming) : [];
1201
1226
  const nextCursor = data?.nextCursor ?? data?.next_cursor ?? null;
1202
1227
  debugLog(debug, scope, "fetchDepositHistory:success", {
1203
1228
  recipient: shortRef(params.recipient),
@@ -3351,8 +3376,84 @@ function TokenIcon({ symbol, className, fallback }) {
3351
3376
  }
3352
3377
  TokenIcon.displayName = "TokenIcon";
3353
3378
 
3379
+ // src/core/deposit-timing.ts
3380
+ var FALLBACK_SOFT_DELAY_MS = {
3381
+ confirming: 90 * 1e3,
3382
+ received: 90 * 1e3,
3383
+ bridging: 4 * 60 * 1e3
3384
+ };
3385
+ var FALLBACK_ESCALATED_DELAY_MS = 10 * 60 * 1e3;
3386
+ var MIN_SOFT_DELAY_MS = 90 * 1e3;
3387
+ function formatExpectedDuration(seconds) {
3388
+ if (!Number.isFinite(seconds) || seconds <= 0) return "a few seconds";
3389
+ if (seconds < 60) {
3390
+ const whole = Math.round(seconds);
3391
+ return `~${whole} second${whole === 1 ? "" : "s"}`;
3392
+ }
3393
+ return `~${Math.max(1, Math.round(seconds / 60))} min`;
3394
+ }
3395
+ function resolveServedThresholds(timing) {
3396
+ const softMs = Math.max(timing.softDelaySeconds * 1e3, MIN_SOFT_DELAY_MS);
3397
+ return {
3398
+ softMs,
3399
+ escalatedMs: Math.max(timing.escalatedDelaySeconds * 1e3, softMs)
3400
+ };
3401
+ }
3402
+ function resolveServedTimingState(timing, totalElapsedMs) {
3403
+ const { softMs, escalatedMs } = resolveServedThresholds(timing);
3404
+ if (totalElapsedMs >= escalatedMs) return "escalated";
3405
+ if (totalElapsedMs >= softMs) return "soft";
3406
+ return "expected";
3407
+ }
3408
+ function resolvePendingTimingState({
3409
+ timing,
3410
+ totalElapsedMs,
3411
+ phaseId,
3412
+ phaseElapsedMs
3413
+ }) {
3414
+ if (timing) return resolveServedTimingState(timing, totalElapsedMs);
3415
+ if (totalElapsedMs >= FALLBACK_ESCALATED_DELAY_MS) return "escalated";
3416
+ if (phaseId && phaseElapsedMs >= FALLBACK_SOFT_DELAY_MS[phaseId]) {
3417
+ return "soft";
3418
+ }
3419
+ return "expected";
3420
+ }
3421
+ var STATE_ORDER = {
3422
+ expected: 0,
3423
+ soft: 1,
3424
+ escalated: 2
3425
+ };
3426
+ function advancePendingTimingState(previous, next) {
3427
+ return STATE_ORDER[next] > STATE_ORDER[previous] ? next : previous;
3428
+ }
3429
+
3430
+ // src/core/history-timing.ts
3431
+ var TIMED_HISTORY_STATUSES = /* @__PURE__ */ new Set(["pending", "processing", "delayed"]);
3432
+ function resolveHistoryTiming({
3433
+ status,
3434
+ timing,
3435
+ createdAt,
3436
+ nowMs
3437
+ }) {
3438
+ if (!TIMED_HISTORY_STATUSES.has(status) || !timing || !createdAt) {
3439
+ return void 0;
3440
+ }
3441
+ const createdAtMs = Date.parse(createdAt);
3442
+ if (!Number.isFinite(createdAtMs)) return void 0;
3443
+ const elapsedMs = Math.max(0, nowMs - createdAtMs);
3444
+ const state = resolveServedTimingState(timing, elapsedMs);
3445
+ const { softMs, escalatedMs } = resolveServedThresholds(timing);
3446
+ const nextTransitionAtMs = state === "expected" ? createdAtMs + softMs : state === "soft" ? createdAtMs + escalatedMs : void 0;
3447
+ return {
3448
+ state,
3449
+ expectedDuration: formatExpectedDuration(timing.expectedSeconds),
3450
+ ...nextTransitionAtMs !== void 0 ? { nextTransitionAtMs } : {}
3451
+ };
3452
+ }
3453
+
3354
3454
  // src/components/history/DepositHistoryPanel.tsx
3355
3455
  import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
3456
+ var MAX_TIMER_DELAY_MS = 2147e6;
3356
3457
  function shortenHash(hash) {
3357
3458
  if (hash.length <= 14) return hash;
3358
3459
  return `${hash.slice(0, 6)}\u2026${hash.slice(-4)}`;
@@ -3472,6 +3573,48 @@ function DepositHistoryPanel({
3472
3573
  () => filterVisibleDeposits(deposits),
3473
3574
  [deposits]
3474
3575
  );
3576
+ const [nowMs, setNowMs] = useState3(() => Date.now());
3577
+ const timingPresentations = useMemo4(
3578
+ () => visibleDeposits.map(
3579
+ (deposit) => resolveHistoryTiming({
3580
+ status: deposit.status,
3581
+ timing: deposit.timing,
3582
+ createdAt: deposit.createdAt,
3583
+ nowMs
3584
+ })
3585
+ ),
3586
+ [nowMs, visibleDeposits]
3587
+ );
3588
+ useEffect5(() => {
3589
+ setNowMs(Date.now());
3590
+ }, [visibleDeposits]);
3591
+ useEffect5(() => {
3592
+ const nextBoundary = timingPresentations.reduce(
3593
+ (earliest, presentation) => {
3594
+ const next = presentation?.nextTransitionAtMs;
3595
+ if (next === void 0) return earliest;
3596
+ return earliest === void 0 || next < earliest ? next : earliest;
3597
+ },
3598
+ void 0
3599
+ );
3600
+ if (nextBoundary === void 0) return;
3601
+ let timeout;
3602
+ const updateAtBoundary = () => {
3603
+ const current = Date.now();
3604
+ if (current < nextBoundary) {
3605
+ timeout = setTimeout(
3606
+ updateAtBoundary,
3607
+ Math.min(nextBoundary - current, MAX_TIMER_DELAY_MS)
3608
+ );
3609
+ return;
3610
+ }
3611
+ setNowMs(current);
3612
+ };
3613
+ updateAtBoundary();
3614
+ return () => {
3615
+ if (timeout !== void 0) clearTimeout(timeout);
3616
+ };
3617
+ }, [timingPresentations]);
3475
3618
  return /* @__PURE__ */ jsxs8(
3476
3619
  "div",
3477
3620
  {
@@ -3548,6 +3691,7 @@ function DepositHistoryPanel({
3548
3691
  HistoryCard,
3549
3692
  {
3550
3693
  deposit,
3694
+ timing: timingPresentations[i],
3551
3695
  position: i,
3552
3696
  recipient,
3553
3697
  onRecover,
@@ -3575,6 +3719,7 @@ function DepositHistoryPanel({
3575
3719
  }
3576
3720
  function HistoryCard({
3577
3721
  deposit,
3722
+ timing,
3578
3723
  position,
3579
3724
  recipient,
3580
3725
  onRecover,
@@ -3682,6 +3827,16 @@ function HistoryCard({
3682
3827
  }
3683
3828
  )
3684
3829
  ] }),
3830
+ timing && /* @__PURE__ */ jsx13(
3831
+ "span",
3832
+ {
3833
+ className: "rs-history-card-timing",
3834
+ "data-state": timing.state,
3835
+ "aria-live": "polite",
3836
+ "aria-atomic": "true",
3837
+ children: timing.state === "expected" ? `Usually arrives in ${timing.expectedDuration}` : timing.state === "soft" ? "Taking longer than usual" : "Taking much longer than usual"
3838
+ }
3839
+ ),
3685
3840
  recoverable && /* @__PURE__ */ jsxs8(
3686
3841
  Button,
3687
3842
  {
@@ -6325,12 +6480,6 @@ var MAX_POLL_INTERVAL = 3e4;
6325
6480
  var BACKOFF_MULTIPLIER = 1.5;
6326
6481
  var SWAPPED_RECEIPT_STATUS_RETRY_INTERVAL_MS = 1e3;
6327
6482
  var SWAPPED_FIAT_STATUS_MAX_ATTEMPTS = 12;
6328
- var ESCALATED_DELAY_MS = 10 * 60 * 1e3;
6329
- var SOFT_DELAY_MS = {
6330
- confirming: 90 * 1e3,
6331
- received: 90 * 1e3,
6332
- bridging: 4 * 60 * 1e3
6333
- };
6334
6483
  var PHASE_TIMINGS_PREFIX = "rhinestone:phase-timings";
6335
6484
  var PAYMENT_METHOD_LABELS = {
6336
6485
  creditcard: "Card",
@@ -6610,7 +6759,6 @@ function ProcessingStep({
6610
6759
  }
6611
6760
  return { type: "processing" };
6612
6761
  });
6613
- const [elapsedSeconds, setElapsedSeconds] = useState13(0);
6614
6762
  const [phaseTimings, setPhaseTimings] = useState13(() => {
6615
6763
  const saved = loadPhaseTimings(txHash);
6616
6764
  if (saved) {
@@ -6619,7 +6767,15 @@ function ProcessingStep({
6619
6767
  }
6620
6768
  return { startedAt: startTimeRef.current };
6621
6769
  });
6622
- const [hasEscalatedDelay, setHasEscalatedDelay] = useState13(false);
6770
+ const [elapsedSeconds, setElapsedSeconds] = useState13(
6771
+ () => Math.max(0, Math.floor((Date.now() - startTimeRef.current) / 1e3))
6772
+ );
6773
+ const [latchedTiming, setLatchedTiming] = useState13(quote?.timing);
6774
+ if (latchedTiming === void 0 && quote?.timing !== void 0) {
6775
+ setLatchedTiming(quote.timing);
6776
+ }
6777
+ const timingStateRef = useRef10("expected");
6778
+ const servedTimingLatchRef = useRef10(false);
6623
6779
  const updatePhaseTimings = useCallback9(
6624
6780
  (updater) => {
6625
6781
  setPhaseTimings((previous) => {
@@ -6925,21 +7081,6 @@ function ProcessingStep({
6925
7081
  state.type,
6926
7082
  txHash
6927
7083
  ]);
6928
- useEffect14(() => {
6929
- if (directTransfer || state.type !== "processing") return;
6930
- const timeoutId = setTimeout(() => {
6931
- if (escalatedDelayRef.current) return;
6932
- escalatedDelayRef.current = true;
6933
- setHasEscalatedDelay(true);
6934
- const message = "Transfer is taking longer than expected. Your funds are safe and processing will continue automatically.";
6935
- debugLog(debug, "processing", "state:delay-escalated", {
6936
- txHash,
6937
- timeoutMs: ESCALATED_DELAY_MS
6938
- });
6939
- onErrorRef.current?.(message, "PROCESS_TIMEOUT");
6940
- }, ESCALATED_DELAY_MS);
6941
- return () => clearTimeout(timeoutId);
6942
- }, [debug, directTransfer, onErrorRef, state.type, txHash]);
6943
7084
  const isComplete = state.type === "complete";
6944
7085
  const isFailed = state.type === "failed";
6945
7086
  const isProcessing = state.type === "processing";
@@ -7039,9 +7180,43 @@ function ProcessingStep({
7039
7180
  const currentPhaseId = getCurrentPhaseId(state, phaseTimings);
7040
7181
  const activePhaseStartedAt = currentPhaseId ? getPhaseStartTime(currentPhaseId, phaseTimings) : void 0;
7041
7182
  const activePhaseElapsedMs = isProcessing && activePhaseStartedAt !== void 0 ? timelineNowMs - activePhaseStartedAt : 0;
7042
- const delayPhaseId = isProcessing && currentPhaseId && activePhaseElapsedMs >= SOFT_DELAY_MS[currentPhaseId] ? currentPhaseId : void 0;
7043
- void delayPhaseId;
7044
- void hasEscalatedDelay;
7183
+ if (latchedTiming !== void 0 && !servedTimingLatchRef.current) {
7184
+ servedTimingLatchRef.current = true;
7185
+ timingStateRef.current = "expected";
7186
+ }
7187
+ const timingState = isProcessing ? advancePendingTimingState(
7188
+ timingStateRef.current,
7189
+ resolvePendingTimingState({
7190
+ timing: latchedTiming,
7191
+ totalElapsedMs: elapsedSeconds * 1e3,
7192
+ phaseId: currentPhaseId,
7193
+ phaseElapsedMs: activePhaseElapsedMs
7194
+ })
7195
+ ) : timingStateRef.current;
7196
+ timingStateRef.current = timingState;
7197
+ const expectedDuration = latchedTiming && timingState === "expected" ? formatExpectedDuration(latchedTiming.expectedSeconds) : void 0;
7198
+ useEffect14(() => {
7199
+ if (directTransfer || !isProcessing || timingState !== "escalated") return;
7200
+ if (escalatedDelayRef.current) return;
7201
+ escalatedDelayRef.current = true;
7202
+ debugLog(debug, "processing", "state:delay-escalated", {
7203
+ txHash,
7204
+ timeoutMs: latchedTiming ? resolveServedThresholds(latchedTiming).escalatedMs : FALLBACK_ESCALATED_DELAY_MS,
7205
+ served: latchedTiming !== void 0
7206
+ });
7207
+ onErrorRef.current?.(
7208
+ "Transfer is taking longer than expected. Your funds are safe and processing will continue automatically.",
7209
+ "PROCESS_TIMEOUT"
7210
+ );
7211
+ }, [
7212
+ debug,
7213
+ directTransfer,
7214
+ isProcessing,
7215
+ latchedTiming,
7216
+ onErrorRef,
7217
+ timingState,
7218
+ txHash
7219
+ ]);
7045
7220
  const sourceChainIcon = getChainIcon(displaySourceChain);
7046
7221
  const targetChainIcon = getChainIcon(targetChainId);
7047
7222
  const sourceChainName = getChainName(displaySourceChain);
@@ -7270,6 +7445,11 @@ function ProcessingStep({
7270
7445
  })()
7271
7446
  ] }),
7272
7447
  isFailed && failureMessage && /* @__PURE__ */ jsx27(Callout, { variant: "error", children: failureMessage }),
7448
+ isProcessing && expectedDuration !== void 0 && /* @__PURE__ */ jsxs20("p", { className: "rs-pending-expectation", children: [
7449
+ "Usually arrives in ",
7450
+ expectedDuration
7451
+ ] }),
7452
+ isProcessing && latchedTiming !== void 0 && timingState !== "expected" && /* @__PURE__ */ jsx27(Callout, { variant: "warning", children: timingState === "soft" ? "Taking longer than usual. Your funds are safe and this will complete automatically." : "This is taking much longer than usual. Your funds are safe and processing continues automatically." }),
7273
7453
  isProcessing && /* @__PURE__ */ jsx27(
7274
7454
  Button,
7275
7455
  {
@@ -7559,6 +7739,7 @@ export {
7559
7739
  isUnsupportedChainSwitchError,
7560
7740
  formatUserError,
7561
7741
  Tooltip,
7742
+ formatExpectedDuration,
7562
7743
  markSubmissionUncertain,
7563
7744
  isSubmissionUncertain,
7564
7745
  formatTokenAmount,
@@ -1,5 +1,5 @@
1
1
  // src/core/version.ts
2
- var MODAL_VERSION = "0.16.0";
2
+ var MODAL_VERSION = "0.17.0";
3
3
  var SERVICE_HEADERS = {
4
4
  "Content-Type": "application/json",
5
5
  "x-deposit-modal-version": MODAL_VERSION