Source mirror · commit 369512f · 2026-10-26 · MIT · canonical repo: gitlab.com/jiapunk/upgrade-airlock
git clone https://upgrade-airlock.pages.dev/source/upgrade-airlock.git
upgrade-airlock / web / src / derive.ts
web/src/derive.ts49 lines · 2363 bytes
1// Pure helpers for numbers on screen. No DOM, no URL, so the tests run them in Node.
2import { formatEther, formatGwei } from 'viem'
3import type { TxCost } from './store'
4import { toBig } from './types'
5
6const HEX = /^0x[0-9a-fA-F]+$/
7
8/** Explorer link; null without a prefix (local fork, offline) or when the value is not hex */
9export function explorerUrl(base: string | null | undefined, value: string | null | undefined): string | null {
10 if (!base || !value || !HEX.test(value)) return null
11 return base + value
12}
13
14/** gasUsed x effectiveGasPrice from a receipt; null when a field is missing (never a made-up default) */
15export function txCost(r: TxCost | undefined): { gasUsed: bigint; priceWei: bigint; costWei: bigint } | null {
16 const gasUsed = toBig(r?.gasUsed)
17 const priceWei = toBig(r?.effectiveGasPrice)
18 if (gasUsed === null || priceWei === null) return null
19 return { gasUsed, priceWei, costWei: gasUsed * priceWei }
20}
21
22/** 4 significant digits with thousands separators (12345.6 -> 12,350; 0.0001234 -> 0.0001234) */
23const sig4 = (x: number): string => x.toLocaleString('en-US', { maximumSignificantDigits: 4 })
24
25/** Fees on Ethereum: Sepolia gas prices can be a few dozen wei, so small amounts stay in wei, then gwei / ETH */
26export function fmtEth(wei: bigint): string {
27 if (wei < 1_000_000_000n) return `${wei.toLocaleString('en-US')} wei`
28 if (wei < 1_000_000_000_000_000n) return `${sig4(Number(formatGwei(wei)))} gwei`
29 return `${sig4(Number(formatEther(wei)))} ETH`
30}
31
32/** Gas price: below 0.001 gwei it is written in wei */
33export function fmtGasPrice(wei: bigint): string {
34 if (wei < 1_000_000n) return `${wei.toLocaleString('en-US')} wei`
35 return `${sig4(Number(formatGwei(wei)))} gwei`
36}
37
38/** A measured block time is believable (a fast-mined fork measures close to 0) */
39export function plausibleBlockMs(bt: number | null, minMs: number): number | null {
40 return bt !== null && bt >= minMs ? bt : null
41}
42
43/** The workflow id carried by a Verdict, or null when it is not a real one: the CRE CLI simulator writes a
44 * placeholder (0x1111…1111), and a zero or repeated-digit id says nothing about which workflow ran */
45export function realWorkflowId(w: string | null | undefined): string | null {
46 if (!w || !/^0x[0-9a-fA-F]{64}$/.test(w)) return null
47 if (/^0x([0-9a-fA-F])\1{63}$/.test(w)) return null
48 return w
49}