{"version":3,"sources":["/home/runner/work/cofhesdk/cofhesdk/packages/react/dist/index.cjs","../src/index.ts","../src/hooks/useCofheDecryptionActivity.ts"],"names":[],"mappings":"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;AACa,QAAA;AAChD,MAAA;AACF,IAAA;AAEuC,IAAA;AACR,IAAA;AAGgB,IAAA;AACtB,IAAA;AACX,MAAA;AAC2C,MAAA;AAC7B,MAAA;AACS,MAAA;AAC+B,MAAA;AACpE,IAAA;AAOK,IAAA;AACuC,MAAA;AAC2B,MAAA;AAC3D,MAAA;AACL,QAAA;AAC2C,QAAA;AAC9C,QAAA;AACa,QAAA;AACiC,UAAA;AACI,UAAA;AAClD,QAAA;AACD,MAAA;AACH,IAAA;AAGsC,IAAA;AACxB,MAAA;AACiD,MAAA;AACd,MAAA;AACD,MAAA;AAIhC,MAAA;AAQmB,MAAA;AAC7B,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACI,MAAA;AACO,QAAA;AACe,QAAA;AACZ,QAAA;AACC,QAAA;AACgB,QAAA;AACQ,QAAA;AACD,QAAA;AACP,QAAA;AACjB,UAAA;AACqC,UAAA;AAC9C,QAAA;AAC2B,UAAA;AACc,UAAA;AAChD,QAAA;AAC6C,MAAA;AAEnC,QAAA;AACqB,QAAA;AACjC,MAAA;AAEkB,MAAA;AACV,MAAA;AACC,QAAA;AACC,QAAA;AACwB,QAAA;AAChC,QAAA;AACe,QAAA;AACA,QAAA;AACK,QAAA;AACG,QAAA;AAChB,QAAA;AACP,QAAA;AACA,QAAA;AACA,QAAA;AACW,QAAA;AACC,QAAA;AACZ,QAAA;AACD,MAAA;AACH,IAAA;AAGmC,IAAA;AACb,MAAA;AAKZ,MAAA;AAMA,MAAA;AACY,QAAA;AACV,QAAA;AACe,QAAA;AACf,QAAA;AACS,QAAA;AACA,QAAA;AACK,QAAA;AACG,QAAA;AAClB,QAAA;AACP,QAAA;AACa,QAAA;AACF,QAAA;AACG,QAAA;AACf,MAAA;AACH,IAAA;AAGiD,IAAA;AACY,MAAA;AAC7D,IAAA;AAEe,IAAA;AACD,MAAA;AAC+B,MAAA;AACU,MAAA;AACJ,MAAA;AACI,MAAA;AACU,MAAA;AACjE,IAAA;AAGgD,IAAA;AACrC,MAAA;AACF,MAAA;AACA,MAAA;AACE,MAAA;AACH,MAAA;AACE,MAAA;AACJ,MAAA;AACN,IAAA;AACqD,IAAA;AAEQ,IAAA;AACC,IAAA;AAEJ,EAAA;AAE/B,EAAA;AAC/B;AFhFwE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/cofhesdk/cofhesdk/packages/react/dist/index.cjs","sourcesContent":[null,"// Providers\nexport { CofheProvider, useCofheContext } from './providers/index';\n\n// Hooks\nexport {\n  ETH_ADDRESS_LOWERCASE,\n  getCofheTokenClaimUnshieldedCallArgs,\n  getCofheTokenShieldCallArgs,\n  getCofheTokenUnshieldCallArgs,\n  useCofheActiveACP,\n  useCofheAllACPs,\n  useCofheClient,\n  useCofheConnection,\n  useCofheEnabled,\n  useCofheEncrypt,\n  useCofhePublicClient,\n  useCofheRemoveACP,\n  useCofheSelectACP,\n  useCofheSimulateWriteContract,\n  useCofheStatuses,\n  useCofheTokenClaimable,\n  useCofheTokenClaims,\n  useCofheTokenApprove,\n  useCofheTokenClaimUnshielded,\n  useCofheTokenDecryptedBalance,\n  useCofheTokenPublicBalance,\n  useCofheTokenLists,\n  useCofheToken,\n  useKnownCofheToken,\n  useCofheTokens,\n  useCofheTokensClaimable,\n  useCofheTokenShield,\n  useCofheTokensWithExistingEncryptedBalances,\n  useCofheTokenTransactions,\n  useCofheTokenTransfer,\n  useResolvedCofheToken,\n  useCofheTransactions,\n  addCofheTransaction,\n  useCofheTokenUnshield,\n  useCofheWalletClient,\n  useCofheWriteContract,\n  useCoingeckoContractMarketChartRange,\n  useCoingeckoUsdPrice,\n  useTokenAllowance,\n  useTokensWithPublicBalances,\n  useTransactionReceiptsByHash,\n  type ClaimableAmountByTokenAddress,\n  type CoingeckoMarketChartPoint,\n  type Erc20Pair,\n  type UnshieldClaim,\n  type UnshieldClaimsSummary,\n  type UnshieldClaimsSummaryByTokenAddress,\n  type UseCoingeckoContractMarketChartRangeInput,\n  type UseCoingeckoContractMarketChartRangeOptions,\n  type UseCoingeckoUsdPriceInput,\n  type UseCoingeckoUsdPriceOptions,\n  type UseCofheTokenTransactionsInput,\n  type UseCofheTokenTransactionsResult,\n  type UseTransactionReceiptsByHashInput,\n  type AddCofheTransactionInput,\n} from '@/hooks/index';\n\nexport { useCofheEncryptAndWriteContract } from '@/hooks/useCofheEncryptAndWriteContract';\nexport { useCofheReadContractAndDecrypt } from '@/hooks/useCofheReadContractAndDecrypt';\n\n// Utils\nexport {\n  FheTypesList,\n  formatRelativeTime,\n  getBlockExplorerAddressUrl,\n  getBlockExplorerTokenUrl,\n  getBlockExplorerTxUrl,\n  getBlockExplorerUrl,\n  isEthPair,\n  isWrappedEthToken,\n  truncateHash,\n} from './utils/index';\n\n// Stores\nexport { useCustomTokensStore } from './stores/customTokensStore';\n\nexport {\n  actionToString,\n  isCustomTransactionActionType,\n  statusToString,\n  transactionMatchesToken,\n  TransactionActionType,\n  TransactionStatus,\n  useTransactionStore,\n  type Transaction,\n  type BuiltInTransactionActionType,\n  type CustomTransactionActionType,\n  type JsonValue,\n  type NewTransaction,\n  type TransactionActionString,\n  type TransactionStatusString,\n  type TransactionStore,\n} from './stores/transactionStore';\n\nexport { createCofheConfig } from './config';\nexport type { CofheReactLogger, CofheReactLoggerMethod } from './config';\n\n// Types\nexport type {\n  CofheClientConfig,\n  CofheContextValue,\n  CofheProviderProps,\n  TransactionRenderer,\n  TransactionRendererProps,\n  TransactionRenderers,\n} from './types/index';\n\nexport { createCofheClient } from '@cofhe/sdk/web';\n\nexport { useCofheCreateACP } from '@/hooks/acps/useCofheCreateACP';\nexport { useCofheNavigateToCreateACP } from '@/hooks/acps/useCofheNavigateToCreateACP';\nexport { useCofheAutoConnect } from '@/hooks/useCofheAutoConnect';\nexport { CofheFloatingButtonProvider, sortCofheStatuses } from './components/index';\nexport { COFHE_STATUS_IDS, CofheStatusActionIntents } from './components/CofheFloatingButton/types';\n\nexport type { CofheConfigWithReact as CofhesdkConfigWithReact } from './config';\n\nexport type { CofheStatus, CofheStatusActionIntent, CofheStatusId } from './components/CofheFloatingButton/types';\nexport type { ConfidentialToken } from './types/token';\nexport { getConfidentialDecimals, getPublicDecimals, getPublicSymbol } from './types/token';\nexport { scaleAmount, quantizeAmount } from './utils/format';\n\nexport type { CofheDecryptMeta, CofheQueryMeta } from './meta';\n\nexport { useCofheDecryptionActivity } from '@/hooks/useCofheDecryptionActivity';\nexport type {\n  DecryptionActivityRow,\n  DecryptionActivityResult,\n  DecryptionActivityCategory,\n  UseCofheDecryptionActivityOptions,\n  FetchStageState,\n  DecryptStageState,\n  ACPState,\n} from '@/hooks/useCofheDecryptionActivity';\n\nexport { useInternalQueryClient } from './providers/index';\n\n// Type bridges for cross-repo / multi-instance viem peer dependency scenarios\nexport {\n  asCofhePublicClient,\n  tryAsCofhePublicClient,\n  asCofheWalletClient,\n  tryAsCofheWalletClient,\n} from './utils/viemClientBridge';\nexport type {\n  PublicClientLike as CofhePublicClientLike,\n  WalletClientLike as CofheWalletClientLike,\n} from './utils/viemClientBridge';\n\n// Block-aware cache invalidation primitives.\n//\n// These are the building blocks behind the SDK's own \"wait until the serving RPC\n// node has the mined block before returning a read\" behaviour. They are exported\n// so consumers (e.g. a wagmi-based app) can build the same reliability into their\n// own reads + post-tx invalidation, sharing THIS module's single invalidation-\n// context store — so an invalidation registered here is honoured by both the\n// SDK's confidential reads and the consumer's own block-aware queries.\nexport {\n  invalidateQueriesWithContext,\n  withInvalidationContext,\n  findMatchingInvalidationContext,\n  consumeInvalidationContext,\n  type InvalidationContextQueryFilters,\n  type QueryFunctionWithInvalidationContext,\n} from './utils/invalidationContext';\nexport {\n  maybeWaitUntilRpcAware,\n  maybeWaitUntilRpcAwareAndReadContract,\n  type WaitUntilRpcAwareAndReadContractOptions,\n  type ReadContractResult,\n} from './utils/waitUntilRpcAwareAndReadContract';\nexport { resolveReceiptBlockHash } from './utils/resolveReceiptBlockHash';\nexport { useInvalidationContextStore, type InvalidationContextEntry } from './stores/invalidationContextStore';\n","import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react';\nimport { useInternalQueryClient } from '@/providers';\nimport { useCofheAccount, useCofheChainId } from './useCofheConnection';\nimport { useCofheActiveACP } from './useCofheACPs';\nimport type { CofheQueryMeta } from '@/meta';\n\n// ============================================================================\n// Decryption activity — the lifecycle view of confidential reads + decryptions.\n// ============================================================================\n//\n// A confidential value is a two-stage pipeline, both stages living as react-query\n// queries on the SDK's internal QueryClient:\n//   A) FETCH  — read the ciphertext handle on-chain  (`cofheReadContract`),\n//               whose data carries the `ctHash`.\n//   B) DECRYPT— decrypt that handle off-chain         (`decryptCiphertext`, keyed\n//               BY the ctHash).\n//\n// This hook observes the cache, correlates the two stages by ctHash, and returns\n// one row per confidential value with a recognizable contract/method/label (from\n// each query's `meta`) — surfacing states a plain error/loading flag can't:\n//   • \"blocked\" — gated off by a missing/invalid acp (never runs, never errors)\n//   • \"stale\"   — fetched, but the decrypt is pending far too long / never lands\n// Consumers render the rows; no query-key parsing or side registries required.\n\nconst DEFAULT_STALE_MS = 25_000;\nconst nowSec = () => Math.floor(Date.now() / 1000);\n\n/** 'idle' = a read/decrypt query exists but is disabled/never-ran (fetchStatus 'idle'), vs 'pending' (in flight). */\nexport type FetchStageState = 'ok' | 'pending' | 'idle' | 'error' | 'empty';\nexport type DecryptStageState = 'ok' | 'pending' | 'idle' | 'stale' | 'error' | 'blocked' | 'absent';\nexport type ACPState = 'valid' | 'expired' | 'unsigned' | 'none';\n/** Coarse bucket for filtering: something wrong / decrypted / nothing conclusive. */\nexport type DecryptionActivityCategory = 'error' | 'success' | 'idle';\n\nexport interface DecryptionActivityRow {\n  key: string;\n  /** Canonical decimal ctHash, when known. */\n  ctHash?: string;\n  /** 0x…-shortened ctHash for display. */\n  ctHashShort?: string;\n  /** The originating query metadata (contract / method / label / consumer fields). */\n  meta?: CofheQueryMeta;\n  /** Convenience shortcuts pulled from meta. */\n  chainId?: number;\n  address?: string;\n  functionName?: string;\n  label?: string;\n  fetch: FetchStageState;\n  decrypt: DecryptStageState;\n  category: DecryptionActivityCategory;\n  /** Why the row is blocked, e.g. \"acp none\". */\n  blockedReason?: string;\n  /** How long the decrypt has been pending, ms (drives \"stale\"). */\n  pendingMs?: number;\n  updatedAt: number;\n  /** Whether re-running is possible (false for gated/idle reads with nothing enabled). */\n  canRetry: boolean;\n  /** Re-run this value's read and/or decrypt. */\n  retry: () => void;\n}\n\nexport interface DecryptionActivityResult {\n  rows: DecryptionActivityRow[];\n  counts: { total: number; ok: number; pending: number; stale: number; blocked: number; error: number };\n  acp: { state: ACPState; expiresInSec?: number };\n  /** Wipe the whole SDK query cache; mounted reads/decryptions refetch from scratch. */\n  clearCache: () => void;\n}\n\nexport interface UseCofheDecryptionActivityOptions {\n  /** How long a decrypt may stay pending before it's reported \"stale\" (default 25s). */\n  staleMs?: number;\n}\n\n// Minimal structural view of a cached query — enough to classify without depending\n// on react-query's concrete generic types.\ninterface QueryLike {\n  queryKey: unknown;\n  queryHash: string;\n  meta?: CofheQueryMeta;\n  state: { status: 'pending' | 'error' | 'success'; fetchStatus?: string; data?: unknown; dataUpdatedAt?: number };\n}\ninterface CacheLike {\n  getAll: () => QueryLike[];\n  subscribe: (cb: () => void) => () => void;\n}\ninterface ClientLike {\n  getQueryCache: () => CacheLike;\n  refetchQueries: (filters: { queryKey: unknown; exact?: boolean }) => Promise<unknown>;\n  clear: () => void;\n}\n\nfunction normalizeCt(x: unknown): string | undefined {\n  if (x === null || x === undefined) return undefined;\n  try {\n    return BigInt(x as string | number | bigint).toString();\n  } catch {\n    const s = String(x);\n    return s && s !== '[object Object]' ? s : undefined;\n  }\n}\n\n// A ctHash of 0 is not a real handle: reads return it for an absent/encrypted-zero\n// value and the decrypt is gated on `ctHash > 0n`, so treat it as \"no handle\".\nfunction isZeroCt(ct: string | undefined): boolean {\n  if (!ct) return false;\n  try {\n    return BigInt(ct) === 0n;\n  } catch {\n    return false;\n  }\n}\n\nfunction shortCt(decimal: string): string {\n  try {\n    const hex = BigInt(decimal).toString(16);\n    return hex.length > 10 ? `0x${hex.slice(0, 6)}…${hex.slice(-4)}` : `0x${hex}`;\n  } catch {\n    return decimal;\n  }\n}\n\nfunction extractCtHash(data: unknown): string | undefined {\n  if (data === null || data === undefined) return undefined;\n  if (typeof data === 'bigint') return normalizeCt(data);\n  if (typeof data === 'object' && data !== null && 'ctHash' in (data as Record<string, unknown>)) {\n    return normalizeCt((data as { ctHash: unknown }).ctHash);\n  }\n  return undefined;\n}\n\n// The read key bakes the transient activeACPHash + enabled flags into its tail,\n// so the SAME value gets a fresh cache entry each time an ACP appears/rotates.\n// Its stable identity is [prefix, chainId, address, fn, args] (indices 0-4).\nfunction stableReadKey(k: unknown[]): string {\n  try {\n    return JSON.stringify(k.slice(0, 5));\n  } catch {\n    return String(k[1]) + '|' + String(k[2]) + '|' + String(k[3]);\n  }\n}\n\n// Progress of a read: ran (success/error) > in flight > idle/never-ran.\nfunction readProgress(q: QueryLike): number {\n  if (q.state.status === 'success' || q.state.status === 'error') return 2;\n  if (q.state.fetchStatus === 'fetching') return 1;\n  return 0;\n}\nfunction isFresherRead(candidate: QueryLike, current: QueryLike): boolean {\n  const pc = readProgress(candidate);\n  const pk = readProgress(current);\n  if (pc !== pk) return pc > pk;\n  return (candidate.state.dataUpdatedAt ?? 0) > (current.state.dataUpdatedAt ?? 0);\n}\n\nfunction categoryFor(fetch: FetchStageState, decrypt: DecryptStageState): DecryptionActivityCategory {\n  if (fetch === 'error' || decrypt === 'error' || decrypt === 'blocked' || decrypt === 'stale') return 'error';\n  if (decrypt === 'ok') return 'success';\n  return 'idle';\n}\n\n/**\n * Observe the ctHash-fetch → decrypt pipeline on the SDK's QueryClient, correlated\n * by ctHash and cross-referenced against the active acp. Must be used inside the\n * Cofhe provider.\n */\nexport function useCofheDecryptionActivity(options?: UseCofheDecryptionActivityOptions): DecryptionActivityResult {\n  const staleMs = options?.staleMs ?? DEFAULT_STALE_MS;\n  const queryClient = useInternalQueryClient() as unknown as ClientLike;\n  const account = useCofheAccount();\n  const chainId = useCofheChainId();\n  const active = useCofheActiveACP();\n  const acpObj = active?.acp;\n\n  // Track when each decrypt first went pending, so \"stale\" is real elapsed time\n  // (react-query doesn't expose a fetch-start timestamp for a never-resolved query).\n  const pendingSinceRef = useRef(new Map<string, number>());\n\n  // Re-render on cache changes AND on a slow tick (staleness is time-based, not\n  // event-driven — a query sitting pending emits no cache events).\n  //\n  // Coalesce cache events to one bump per animation frame. `cache.subscribe` fires\n  // synchronously on EVERY cache event (including observer option/result updates),\n  // and a consumer re-render can itself emit fresh cache events — an unbounded\n  // bump→render→event→bump storm that pins the main thread and starves unrelated\n  // updates (e.g. a route change never commits). One bump per frame keeps the view\n  // live while capping re-renders so the loop can't run away.\n  const [version, bump] = useReducer((n: number) => n + 1, 0);\n  useEffect(() => {\n    const cache = queryClient.getQueryCache();\n    let scheduled = false;\n    const schedule = () => {\n      if (scheduled) return;\n      scheduled = true;\n      const run = () => {\n        scheduled = false;\n        bump();\n      };\n      if (typeof requestAnimationFrame === 'function') requestAnimationFrame(run);\n      else setTimeout(run, 16);\n    };\n    const unsub = cache.subscribe(schedule);\n    const tick = setInterval(bump, 5_000);\n    return () => {\n      unsub();\n      clearInterval(tick);\n    };\n  }, [queryClient]);\n\n  const clearCache = useCallback(() => queryClient.clear(), [queryClient]);\n\n  const data = useMemo(() => {\n    const cache = queryClient.getQueryCache();\n    const queries = cache.getAll();\n    const now = Date.now();\n\n    const refetch = (key: unknown) => void queryClient.refetchQueries({ queryKey: key, exact: true });\n\n    const acpState: ACPState = !acpObj\n      ? 'none'\n      : !acpObj.issuerSignature || acpObj.issuerSignature === '0x'\n        ? 'unsigned'\n        : acpObj.expiration <= nowSec()\n          ? 'expired'\n          : 'valid';\n    const acpValid = acpState === 'valid';\n\n    // Index decrypt queries by ctHash (source contract + label come from meta).\n    const decryptByCt = new Map<\n      string,\n      { state: 'ok' | 'pending' | 'idle' | 'error'; pendingMs?: number; key?: unknown; meta?: CofheQueryMeta }\n    >();\n    const seenPending = new Set<string>();\n    for (const q of queries) {\n      const k = q.queryKey;\n      if (!Array.isArray(k) || k[0] !== 'decryptCiphertext') continue;\n      const ct = normalizeCt(k[1]);\n      if (!ct || isZeroCt(ct)) continue;\n      const base = { key: q.queryKey, meta: q.meta };\n      if (q.state.status === 'success') {\n        decryptByCt.set(ct, { state: 'ok', ...base });\n        pendingSinceRef.current.delete(ct);\n      } else if (q.state.status === 'error') {\n        decryptByCt.set(ct, { state: 'error', ...base });\n        pendingSinceRef.current.delete(ct);\n      } else if (q.state.fetchStatus === 'fetching') {\n        seenPending.add(ct);\n        let since = pendingSinceRef.current.get(ct);\n        if (since === undefined) {\n          since = now;\n          pendingSinceRef.current.set(ct, since);\n        }\n        decryptByCt.set(ct, { state: 'pending', pendingMs: now - since, ...base });\n      } else {\n        // status 'pending' but fetchStatus 'idle' → disabled / never ran.\n        pendingSinceRef.current.delete(ct);\n        decryptByCt.set(ct, { state: 'idle', ...base });\n      }\n    }\n\n    const rows: DecryptionActivityRow[] = [];\n    const usedCt = new Set<string>();\n\n    // Collapse per-acp/enabled key rotations to one entry per value.\n    const readByValue = new Map<string, QueryLike>();\n    for (const q of queries) {\n      const k = q.queryKey;\n      if (!Array.isArray(k) || k[0] !== 'cofheReadContract') continue;\n      const id = stableReadKey(k);\n      const existing = readByValue.get(id);\n      if (!existing || isFresherRead(q, existing)) readByValue.set(id, q);\n    }\n\n    const pushRow = (\n      row: Omit<DecryptionActivityRow, 'category' | 'canRetry' | 'retry'> & {\n        fetchKey?: unknown;\n        decryptKey?: unknown;\n      }\n    ) => {\n      const { fetchKey, decryptKey, ...rest } = row;\n      const canRetry = rest.fetch !== 'idle' && (fetchKey !== undefined || decryptKey !== undefined);\n      rows.push({\n        ...rest,\n        category: categoryFor(rest.fetch, rest.decrypt),\n        canRetry,\n        retry: () => {\n          if (fetchKey !== undefined) refetch(fetchKey);\n          if (decryptKey !== undefined) refetch(decryptKey);\n        },\n      });\n    };\n\n    // One row per confidential value; attach its decrypt stage by ctHash.\n    for (const q of readByValue.values()) {\n      const m = q.meta;\n      const ctRaw = q.state.status === 'success' ? extractCtHash(q.state.data) : undefined;\n      const ct = ctRaw && !isZeroCt(ctRaw) ? ctRaw : undefined;\n      const fetchInFlight = q.state.fetchStatus === 'fetching';\n      const fetchState: FetchStageState =\n        q.state.status === 'error'\n          ? 'error'\n          : q.state.status === 'pending'\n            ? fetchInFlight\n              ? 'pending'\n              : 'idle'\n            : ct\n              ? 'ok'\n              : 'empty';\n\n      let decrypt: DecryptStageState = 'absent';\n      let blockedReason: string | undefined;\n      let pendingMs: number | undefined;\n      let decryptKey: unknown;\n      let decryptMeta: CofheQueryMeta | undefined;\n      if (ct) {\n        usedCt.add(ct);\n        const d = decryptByCt.get(ct);\n        decryptKey = d?.key;\n        decryptMeta = d?.meta;\n        if (d?.state === 'ok') decrypt = 'ok';\n        else if (d?.state === 'error') decrypt = 'error';\n        else if (d?.state === 'idle') decrypt = 'idle';\n        else if (d?.state === 'pending') {\n          pendingMs = d.pendingMs;\n          decrypt = (d.pendingMs ?? 0) > staleMs ? 'stale' : 'pending';\n        } else {\n          decrypt = acpValid ? 'absent' : 'blocked';\n          if (!acpValid) blockedReason = `acp ${acpState}`;\n        }\n      } else if (fetchState === 'idle' && !acpValid) {\n        // The read is gated off (no valid acp) — blocked before any ctHash.\n        decrypt = 'blocked';\n        blockedReason = `acp ${acpState}`;\n      }\n\n      const meta = m ?? decryptMeta;\n      pushRow({\n        key: q.queryHash,\n        ctHash: ct,\n        ctHashShort: ct ? shortCt(ct) : undefined,\n        meta,\n        chainId: meta?.chainId,\n        address: meta?.address,\n        functionName: meta?.functionName,\n        label: meta?.consumer?.label,\n        fetch: fetchState,\n        decrypt,\n        blockedReason,\n        pendingMs,\n        updatedAt: now,\n        fetchKey: q.queryKey,\n        decryptKey,\n      });\n    }\n\n    // Orphan decrypts (a ctHash being decrypted with no fetch query in cache).\n    for (const [ct, d] of decryptByCt) {\n      if (usedCt.has(ct)) continue;\n      const decrypt: DecryptStageState =\n        d.state === 'ok'\n          ? 'ok'\n          : d.state === 'error'\n            ? 'error'\n            : d.state === 'idle'\n              ? 'idle'\n              : (d.pendingMs ?? 0) > staleMs\n                ? 'stale'\n                : 'pending';\n      pushRow({\n        key: `decrypt:${ct}`,\n        ctHash: ct,\n        ctHashShort: shortCt(ct),\n        meta: d.meta,\n        chainId: d.meta?.chainId,\n        address: d.meta?.address,\n        functionName: d.meta?.functionName,\n        label: d.meta?.consumer?.label,\n        fetch: 'ok',\n        decrypt,\n        pendingMs: d.pendingMs,\n        updatedAt: now,\n        decryptKey: d.key,\n      });\n    }\n\n    // Drop ctHashes we no longer see, so the pending-since map can't grow unbounded.\n    for (const ct of pendingSinceRef.current.keys()) {\n      if (!seenPending.has(ct)) pendingSinceRef.current.delete(ct);\n    }\n\n    const counts = {\n      total: rows.length,\n      ok: rows.filter((r) => r.decrypt === 'ok').length,\n      pending: rows.filter((r) => r.decrypt === 'pending').length,\n      stale: rows.filter((r) => r.decrypt === 'stale').length,\n      blocked: rows.filter((r) => r.decrypt === 'blocked').length,\n      error: rows.filter((r) => r.decrypt === 'error' || r.fetch === 'error').length,\n    };\n\n    // Most actionable first: blocked/stale/error, then pending, then done.\n    const rank: Record<DecryptStageState, number> = {\n      blocked: 0,\n      stale: 1,\n      error: 2,\n      pending: 3,\n      idle: 4,\n      absent: 5,\n      ok: 6,\n    };\n    rows.sort((a, b) => rank[a.decrypt] - rank[b.decrypt]);\n\n    const expiresInSec = acpObj ? acpObj.expiration - nowSec() : undefined;\n    return { rows, acp: { state: acpState, expiresInSec }, counts };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [queryClient, account, chainId, acpObj, staleMs, version]);\n\n  return { ...data, clearCache };\n}\n"]}