@orangecheck/design 0.7.5 → 0.7.6
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.
- package/dist/format.d.mts +12 -1
- package/dist/format.d.ts +12 -1
- package/dist/format.js +48 -0
- package/dist/format.js.map +1 -1
- package/dist/format.mjs +47 -1
- package/dist/format.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +43 -1
- package/dist/index.mjs.map +1 -1
- package/dist/primitives.js.map +1 -1
- package/dist/primitives.mjs.map +1 -1
- package/package.json +2 -2
package/dist/format.d.mts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
interface SpotPrice {
|
|
2
|
+
btcUsd: number;
|
|
3
|
+
fetchedAt: string | null;
|
|
4
|
+
}
|
|
5
|
+
interface UseSpotPriceOptions {
|
|
6
|
+
endpoint?: string;
|
|
7
|
+
intervalMs?: number;
|
|
8
|
+
}
|
|
9
|
+
declare function useSpotPrice(opts?: UseSpotPriceOptions): SpotPrice;
|
|
10
|
+
|
|
1
11
|
declare const REFERENCE_BTC_USD = 95000;
|
|
2
12
|
declare function formatSats(sats: number): string;
|
|
3
13
|
declare function formatSatsCompact(sats: number): string;
|
|
4
14
|
declare function satsToUsd(sats: number, btcUsd?: number): string;
|
|
5
15
|
declare function usdToSats(usd: number, btcUsd?: number): number;
|
|
6
16
|
declare function priceBoth(sats: number, btcUsd?: number): string;
|
|
17
|
+
declare function asOf(iso: string | null | undefined): string | null;
|
|
7
18
|
declare function relativeTime(when: string | number | Date): string;
|
|
8
19
|
declare function shortenAddress(value: string, head?: number, tail?: number): string;
|
|
9
20
|
type ExplorerNetwork = 'mainnet' | 'testnet' | 'signet';
|
|
@@ -16,4 +27,4 @@ type ExplorerTarget = {
|
|
|
16
27
|
};
|
|
17
28
|
declare function explorerUrl(target: ExplorerTarget, network?: ExplorerNetwork): string;
|
|
18
29
|
|
|
19
|
-
export { type ExplorerNetwork, type ExplorerTarget, REFERENCE_BTC_USD, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats };
|
|
30
|
+
export { type ExplorerNetwork, type ExplorerTarget, REFERENCE_BTC_USD, type SpotPrice, type UseSpotPriceOptions, asOf, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats, useSpotPrice };
|
package/dist/format.d.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
interface SpotPrice {
|
|
2
|
+
btcUsd: number;
|
|
3
|
+
fetchedAt: string | null;
|
|
4
|
+
}
|
|
5
|
+
interface UseSpotPriceOptions {
|
|
6
|
+
endpoint?: string;
|
|
7
|
+
intervalMs?: number;
|
|
8
|
+
}
|
|
9
|
+
declare function useSpotPrice(opts?: UseSpotPriceOptions): SpotPrice;
|
|
10
|
+
|
|
1
11
|
declare const REFERENCE_BTC_USD = 95000;
|
|
2
12
|
declare function formatSats(sats: number): string;
|
|
3
13
|
declare function formatSatsCompact(sats: number): string;
|
|
4
14
|
declare function satsToUsd(sats: number, btcUsd?: number): string;
|
|
5
15
|
declare function usdToSats(usd: number, btcUsd?: number): number;
|
|
6
16
|
declare function priceBoth(sats: number, btcUsd?: number): string;
|
|
17
|
+
declare function asOf(iso: string | null | undefined): string | null;
|
|
7
18
|
declare function relativeTime(when: string | number | Date): string;
|
|
8
19
|
declare function shortenAddress(value: string, head?: number, tail?: number): string;
|
|
9
20
|
type ExplorerNetwork = 'mainnet' | 'testnet' | 'signet';
|
|
@@ -16,4 +27,4 @@ type ExplorerTarget = {
|
|
|
16
27
|
};
|
|
17
28
|
declare function explorerUrl(target: ExplorerTarget, network?: ExplorerNetwork): string;
|
|
18
29
|
|
|
19
|
-
export { type ExplorerNetwork, type ExplorerTarget, REFERENCE_BTC_USD, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats };
|
|
30
|
+
export { type ExplorerNetwork, type ExplorerTarget, REFERENCE_BTC_USD, type SpotPrice, type UseSpotPriceOptions, asOf, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats, useSpotPrice };
|
package/dist/format.js
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/format/use-spot-price.ts
|
|
6
|
+
function useSpotPrice(opts = {}) {
|
|
7
|
+
const { endpoint = "/api/price/btc-usd", intervalMs = 6e4 } = opts;
|
|
8
|
+
const [state, setState] = react.useState({
|
|
9
|
+
btcUsd: REFERENCE_BTC_USD,
|
|
10
|
+
fetchedAt: null
|
|
11
|
+
});
|
|
12
|
+
react.useEffect(() => {
|
|
13
|
+
let cancelled = false;
|
|
14
|
+
async function pull() {
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch(endpoint);
|
|
17
|
+
if (!res.ok) return;
|
|
18
|
+
const json = await res.json();
|
|
19
|
+
if (cancelled) return;
|
|
20
|
+
if (typeof json.btc_usd === "number" && json.btc_usd > 0) {
|
|
21
|
+
setState({
|
|
22
|
+
btcUsd: Math.round(json.btc_usd),
|
|
23
|
+
fetchedAt: json.fetched_at ?? null
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
void pull();
|
|
30
|
+
const t = setInterval(pull, intervalMs);
|
|
31
|
+
return () => {
|
|
32
|
+
cancelled = true;
|
|
33
|
+
clearInterval(t);
|
|
34
|
+
};
|
|
35
|
+
}, [endpoint, intervalMs]);
|
|
36
|
+
return state;
|
|
37
|
+
}
|
|
38
|
+
|
|
3
39
|
// src/format/index.ts
|
|
4
40
|
var REFERENCE_BTC_USD = 95e3;
|
|
5
41
|
function formatSats(sats) {
|
|
@@ -28,6 +64,16 @@ function usdToSats(usd, btcUsd = REFERENCE_BTC_USD) {
|
|
|
28
64
|
function priceBoth(sats, btcUsd = REFERENCE_BTC_USD) {
|
|
29
65
|
return `${formatSats(sats)} sats \xB7 ${satsToUsd(sats, btcUsd)}`;
|
|
30
66
|
}
|
|
67
|
+
function asOf(iso) {
|
|
68
|
+
if (!iso) return null;
|
|
69
|
+
const t = new Date(iso);
|
|
70
|
+
if (Number.isNaN(t.getTime())) return null;
|
|
71
|
+
return `as of ${t.toLocaleTimeString(void 0, {
|
|
72
|
+
hour: "2-digit",
|
|
73
|
+
minute: "2-digit",
|
|
74
|
+
hour12: false
|
|
75
|
+
})}`;
|
|
76
|
+
}
|
|
31
77
|
function relativeTime(when) {
|
|
32
78
|
const t = when instanceof Date ? when.getTime() : new Date(when).getTime();
|
|
33
79
|
if (Number.isNaN(t)) return "";
|
|
@@ -56,6 +102,7 @@ function explorerUrl(target, network = "mainnet") {
|
|
|
56
102
|
}
|
|
57
103
|
|
|
58
104
|
exports.REFERENCE_BTC_USD = REFERENCE_BTC_USD;
|
|
105
|
+
exports.asOf = asOf;
|
|
59
106
|
exports.explorerUrl = explorerUrl;
|
|
60
107
|
exports.formatSats = formatSats;
|
|
61
108
|
exports.formatSatsCompact = formatSatsCompact;
|
|
@@ -64,5 +111,6 @@ exports.relativeTime = relativeTime;
|
|
|
64
111
|
exports.satsToUsd = satsToUsd;
|
|
65
112
|
exports.shortenAddress = shortenAddress;
|
|
66
113
|
exports.usdToSats = usdToSats;
|
|
114
|
+
exports.useSpotPrice = useSpotPrice;
|
|
67
115
|
//# sourceMappingURL=format.js.map
|
|
68
116
|
//# sourceMappingURL=format.js.map
|
package/dist/format.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/format/index.ts"],"names":[],"mappings":"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","file":"format.js","sourcesContent":["/**\n * @orangecheck/design/format — Bitcoin/money display + formatting utilities.\n *\n * Presentational only. Lifted from the canonical implementations across the\n * family (oc-me-web's utils, oc-vault-web's billing) and standardized so every\n * site formats sats, prices, addresses, and times identically. NOTE: crypto\n * (BIP-322 signing, bech32 validation) deliberately does NOT live here — that\n * belongs in a bitcoin/crypto library, not the design system.\n */\n\n/** Reference BTC/USD rate for sats↔USD display. Override per call when a live\n * rate is available; this is a display affordance, not a price oracle. */\nexport const REFERENCE_BTC_USD = 95_000;\n\n/** Format a satoshi integer as a thousands-separated string (\"12,408\"). */\nexport function formatSats(sats: number): string {\n return new Intl.NumberFormat('en-US').format(Math.round(sats));\n}\n\n/** Compact sats for buttons/chips: \"7k sats\", \"1.5k sats\", \"999 sats\". */\nexport function formatSatsCompact(sats: number): string {\n const n = Math.round(sats);\n if (n < 1000) return `${n} sats`;\n const k = n / 1000;\n return `${Number.isInteger(k) ? k : k.toFixed(1)}k sats`;\n}\n\n/** Convert sats to a USD currency string using the reference (or given) rate. */\nexport function satsToUsd(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n const usd = (sats / 100_000_000) * btcUsd;\n if (usd > 0 && usd < 0.01) return '< $0.01';\n // Whole dollars once >= $100, cents below. min must never exceed max.\n const fractionDigits = usd >= 100 ? 0 : 2;\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: fractionDigits,\n maximumFractionDigits: fractionDigits,\n }).format(usd);\n}\n\n/** Convert a USD amount to sats using the reference (or given) rate. */\nexport function usdToSats(usd: number, btcUsd: number = REFERENCE_BTC_USD): number {\n return Math.round((usd / btcUsd) * 100_000_000);\n}\n\n/** Both denominations as one label: \"7,000 sats · $6.65\". */\nexport function priceBoth(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n return `${formatSats(sats)} sats · ${satsToUsd(sats, btcUsd)}`;\n}\n\n/** Compact relative time — \"now\", \"3m ago\", \"2h ago\", \"4d ago\", or a date. */\nexport function relativeTime(when: string | number | Date): string {\n const t = when instanceof Date ? when.getTime() : new Date(when).getTime();\n if (Number.isNaN(t)) return '';\n const diff = Date.now() - t;\n const m = Math.floor(diff / 60_000);\n if (m < 1) return 'now';\n if (m < 60) return `${m}m ago`;\n const h = Math.floor(m / 60);\n if (h < 24) return `${h}h ago`;\n const d = Math.floor(h / 24);\n if (d < 30) return `${d}d ago`;\n const mo = Math.floor(d / 30);\n if (mo < 12) return `${mo}mo ago`;\n return `${Math.floor(mo / 12)}y ago`;\n}\n\n/**\n * Truncate a Bitcoin/Lightning address (or any long id) for display:\n * \"bc1qxy…wxyz\". Defaults match the family's most common rule (8 head / 4 tail).\n */\nexport function shortenAddress(value: string, head = 8, tail = 4): string {\n if (!value) return '';\n if (value.length <= head + tail + 1) return value;\n return `${value.slice(0, head)}…${value.slice(-tail)}`;\n}\n\nexport type ExplorerNetwork = 'mainnet' | 'testnet' | 'signet';\nexport type ExplorerTarget =\n | { address: string }\n | { tx: string }\n | { block: string | number };\n\n/** A mempool.space explorer URL for an address, tx, or block. */\nexport function explorerUrl(target: ExplorerTarget, network: ExplorerNetwork = 'mainnet'): string {\n const base =\n network === 'mainnet'\n ? 'https://mempool.space'\n : `https://mempool.space/${network}`;\n if ('address' in target) return `${base}/address/${target.address}`;\n if ('tx' in target) return `${base}/tx/${target.tx}`;\n return `${base}/block/${target.block}`;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/format/use-spot-price.ts","../src/format/index.ts"],"names":["useState","useEffect"],"mappings":"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","file":"format.js","sourcesContent":["/**\n * useSpotPrice — live BTC/USD spot rate for display surfaces.\n *\n * Pulls a JSON shape `{ btc_usd: number, fetched_at?: string | null }`\n * from a consumer-hosted endpoint (default `/api/price/btc-usd`),\n * refreshing every 60 seconds on a setInterval. The initial paint\n * reads `REFERENCE_BTC_USD` so the first frame is never NaN.\n *\n * Consumer contract (read this when wiring):\n *\n * The hook makes a relative fetch to `endpoint`. Every consumer that\n * mounts this MUST host that route locally — typically a Next.js\n * pages-router API route that proxies mempool.space's spot feed and\n * caches in-process. The canonical implementation lives at\n * `oc-vault-web/src/pages/api/price/btc-usd.ts` (and the mirror in\n * `oc-me-web`). Both also host the matching server-side fetch +\n * cache in `src/lib/price/feed.ts` — that part stays per-consumer\n * because it's Node-only code that doesn't belong in a UI package.\n *\n * If you don't have the route yet, pass an explicit endpoint\n * (e.g. `useSpotPrice({ endpoint: 'https://other-site.ochk.io/api/price/btc-usd' })`)\n * pointing at a sibling site's endpoint — they're CORS-open.\n *\n * Returns the rich `{ btcUsd, fetchedAt }` shape rather than a bare\n * number so callers that want a provenance caption can render\n * `asOf(fetchedAt)` without a second hook.\n */\n\n'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { REFERENCE_BTC_USD } from './index';\n\nexport interface SpotPrice {\n /** BTC/USD rate. Falls back to `REFERENCE_BTC_USD` until the first\n * fetch lands so the initial paint never reads NaN. */\n btcUsd: number;\n /** ISO timestamp the rate was fetched upstream. `null` until the\n * first successful fetch. Pair with `asOf()` for \"as of HH:MM\". */\n fetchedAt: string | null;\n}\n\nexport interface UseSpotPriceOptions {\n /** Override the endpoint path. Defaults to `/api/price/btc-usd` —\n * the family-canonical proxy route. Useful when consuming a sibling\n * site's endpoint cross-origin, or when self-hosting the proxy at\n * a non-default path. */\n endpoint?: string;\n /** Override the refresh cadence in milliseconds. Defaults to 60_000,\n * matching the upstream mempool.space cache TTL. Lower than ~10s\n * is wasted bandwidth — the underlying feed doesn't refresh faster. */\n intervalMs?: number;\n}\n\ninterface SpotPriceResponse {\n btc_usd?: number;\n fetched_at?: string | null;\n}\n\n/**\n * Hook · returns the latest BTC/USD spot rate from `endpoint`.\n *\n * Behaviour:\n * - First mount: synchronous return of `REFERENCE_BTC_USD` (no NaN),\n * then an async fetch updates state.\n * - Every `intervalMs` thereafter, a background fetch keeps the rate\n * fresh while the tab is alive.\n * - Network blips keep the last-good value — no flash to fallback.\n * - Unmount cancels both the in-flight read (`cancelled` flag) and\n * the interval timer.\n */\nexport function useSpotPrice(opts: UseSpotPriceOptions = {}): SpotPrice {\n const { endpoint = '/api/price/btc-usd', intervalMs = 60_000 } = opts;\n const [state, setState] = useState<SpotPrice>({\n btcUsd: REFERENCE_BTC_USD,\n fetchedAt: null,\n });\n\n useEffect(() => {\n let cancelled = false;\n async function pull() {\n try {\n const res = await fetch(endpoint);\n if (!res.ok) return;\n const json = (await res.json()) as SpotPriceResponse;\n if (cancelled) return;\n if (typeof json.btc_usd === 'number' && json.btc_usd > 0) {\n setState({\n btcUsd: Math.round(json.btc_usd),\n fetchedAt: json.fetched_at ?? null,\n });\n }\n } catch {\n // Network blip · keep the last good value, don't flash a\n // fallback. The next poll will recover.\n }\n }\n void pull();\n const t = setInterval(pull, intervalMs);\n return () => {\n cancelled = true;\n clearInterval(t);\n };\n }, [endpoint, intervalMs]);\n\n return state;\n}\n","/**\n * @orangecheck/design/format — Bitcoin/money display + formatting utilities.\n *\n * Presentational only. Lifted from the canonical implementations across the\n * family (oc-me-web's utils, oc-vault-web's billing) and standardized so every\n * site formats sats, prices, addresses, and times identically. NOTE: crypto\n * (BIP-322 signing, bech32 validation) deliberately does NOT live here — that\n * belongs in a bitcoin/crypto library, not the design system.\n *\n * Includes the `useSpotPrice` React hook for live BTC/USD display. The hook\n * fetches a consumer-hosted endpoint — see `use-spot-price.ts` for the\n * one-route-per-consumer contract.\n */\n\nexport { useSpotPrice, type SpotPrice, type UseSpotPriceOptions } from './use-spot-price';\n\n/** Reference BTC/USD rate for sats↔USD display. Override per call when a live\n * rate is available; this is a display affordance, not a price oracle. */\nexport const REFERENCE_BTC_USD = 95_000;\n\n/** Format a satoshi integer as a thousands-separated string (\"12,408\"). */\nexport function formatSats(sats: number): string {\n return new Intl.NumberFormat('en-US').format(Math.round(sats));\n}\n\n/** Compact sats for buttons/chips: \"7k sats\", \"1.5k sats\", \"999 sats\". */\nexport function formatSatsCompact(sats: number): string {\n const n = Math.round(sats);\n if (n < 1000) return `${n} sats`;\n const k = n / 1000;\n return `${Number.isInteger(k) ? k : k.toFixed(1)}k sats`;\n}\n\n/** Convert sats to a USD currency string using the reference (or given) rate. */\nexport function satsToUsd(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n const usd = (sats / 100_000_000) * btcUsd;\n if (usd > 0 && usd < 0.01) return '< $0.01';\n // Whole dollars once >= $100, cents below. min must never exceed max.\n const fractionDigits = usd >= 100 ? 0 : 2;\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: fractionDigits,\n maximumFractionDigits: fractionDigits,\n }).format(usd);\n}\n\n/** Convert a USD amount to sats using the reference (or given) rate. */\nexport function usdToSats(usd: number, btcUsd: number = REFERENCE_BTC_USD): number {\n return Math.round((usd / btcUsd) * 100_000_000);\n}\n\n/** Both denominations as one label: \"7,000 sats · $6.65\". */\nexport function priceBoth(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n return `${formatSats(sats)} sats · ${satsToUsd(sats, btcUsd)}`;\n}\n\n/**\n * \"as of HH:MM\" provenance footnote. Returns `null` for nullish or\n * unparseable input so callers can render nothing\n * (`{x && <span>{x}</span>}`) rather than gate on a magic empty\n * string. 24-hour, locale-aware. Pairs with `useSpotPrice().fetchedAt`\n * to caption a live-rate display.\n */\nexport function asOf(iso: string | null | undefined): string | null {\n if (!iso) return null;\n const t = new Date(iso);\n if (Number.isNaN(t.getTime())) return null;\n return `as of ${t.toLocaleTimeString(undefined, {\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n })}`;\n}\n\n/** Compact relative time — \"now\", \"3m ago\", \"2h ago\", \"4d ago\", or a date. */\nexport function relativeTime(when: string | number | Date): string {\n const t = when instanceof Date ? when.getTime() : new Date(when).getTime();\n if (Number.isNaN(t)) return '';\n const diff = Date.now() - t;\n const m = Math.floor(diff / 60_000);\n if (m < 1) return 'now';\n if (m < 60) return `${m}m ago`;\n const h = Math.floor(m / 60);\n if (h < 24) return `${h}h ago`;\n const d = Math.floor(h / 24);\n if (d < 30) return `${d}d ago`;\n const mo = Math.floor(d / 30);\n if (mo < 12) return `${mo}mo ago`;\n return `${Math.floor(mo / 12)}y ago`;\n}\n\n/**\n * Truncate a Bitcoin/Lightning address (or any long id) for display:\n * \"bc1qxy…wxyz\". Defaults match the family's most common rule (8 head / 4 tail).\n */\nexport function shortenAddress(value: string, head = 8, tail = 4): string {\n if (!value) return '';\n if (value.length <= head + tail + 1) return value;\n return `${value.slice(0, head)}…${value.slice(-tail)}`;\n}\n\nexport type ExplorerNetwork = 'mainnet' | 'testnet' | 'signet';\nexport type ExplorerTarget =\n | { address: string }\n | { tx: string }\n | { block: string | number };\n\n/** A mempool.space explorer URL for an address, tx, or block. */\nexport function explorerUrl(target: ExplorerTarget, network: ExplorerNetwork = 'mainnet'): string {\n const base =\n network === 'mainnet'\n ? 'https://mempool.space'\n : `https://mempool.space/${network}`;\n if ('address' in target) return `${base}/address/${target.address}`;\n if ('tx' in target) return `${base}/tx/${target.tx}`;\n return `${base}/block/${target.block}`;\n}\n"]}
|
package/dist/format.mjs
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/format/use-spot-price.ts
|
|
4
|
+
function useSpotPrice(opts = {}) {
|
|
5
|
+
const { endpoint = "/api/price/btc-usd", intervalMs = 6e4 } = opts;
|
|
6
|
+
const [state, setState] = useState({
|
|
7
|
+
btcUsd: REFERENCE_BTC_USD,
|
|
8
|
+
fetchedAt: null
|
|
9
|
+
});
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
let cancelled = false;
|
|
12
|
+
async function pull() {
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(endpoint);
|
|
15
|
+
if (!res.ok) return;
|
|
16
|
+
const json = await res.json();
|
|
17
|
+
if (cancelled) return;
|
|
18
|
+
if (typeof json.btc_usd === "number" && json.btc_usd > 0) {
|
|
19
|
+
setState({
|
|
20
|
+
btcUsd: Math.round(json.btc_usd),
|
|
21
|
+
fetchedAt: json.fetched_at ?? null
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
void pull();
|
|
28
|
+
const t = setInterval(pull, intervalMs);
|
|
29
|
+
return () => {
|
|
30
|
+
cancelled = true;
|
|
31
|
+
clearInterval(t);
|
|
32
|
+
};
|
|
33
|
+
}, [endpoint, intervalMs]);
|
|
34
|
+
return state;
|
|
35
|
+
}
|
|
36
|
+
|
|
1
37
|
// src/format/index.ts
|
|
2
38
|
var REFERENCE_BTC_USD = 95e3;
|
|
3
39
|
function formatSats(sats) {
|
|
@@ -26,6 +62,16 @@ function usdToSats(usd, btcUsd = REFERENCE_BTC_USD) {
|
|
|
26
62
|
function priceBoth(sats, btcUsd = REFERENCE_BTC_USD) {
|
|
27
63
|
return `${formatSats(sats)} sats \xB7 ${satsToUsd(sats, btcUsd)}`;
|
|
28
64
|
}
|
|
65
|
+
function asOf(iso) {
|
|
66
|
+
if (!iso) return null;
|
|
67
|
+
const t = new Date(iso);
|
|
68
|
+
if (Number.isNaN(t.getTime())) return null;
|
|
69
|
+
return `as of ${t.toLocaleTimeString(void 0, {
|
|
70
|
+
hour: "2-digit",
|
|
71
|
+
minute: "2-digit",
|
|
72
|
+
hour12: false
|
|
73
|
+
})}`;
|
|
74
|
+
}
|
|
29
75
|
function relativeTime(when) {
|
|
30
76
|
const t = when instanceof Date ? when.getTime() : new Date(when).getTime();
|
|
31
77
|
if (Number.isNaN(t)) return "";
|
|
@@ -53,6 +99,6 @@ function explorerUrl(target, network = "mainnet") {
|
|
|
53
99
|
return `${base}/block/${target.block}`;
|
|
54
100
|
}
|
|
55
101
|
|
|
56
|
-
export { REFERENCE_BTC_USD, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats };
|
|
102
|
+
export { REFERENCE_BTC_USD, asOf, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats, useSpotPrice };
|
|
57
103
|
//# sourceMappingURL=format.mjs.map
|
|
58
104
|
//# sourceMappingURL=format.mjs.map
|
package/dist/format.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/format/index.ts"],"names":[],"mappings":"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","file":"format.mjs","sourcesContent":["/**\n * @orangecheck/design/format — Bitcoin/money display + formatting utilities.\n *\n * Presentational only. Lifted from the canonical implementations across the\n * family (oc-me-web's utils, oc-vault-web's billing) and standardized so every\n * site formats sats, prices, addresses, and times identically. NOTE: crypto\n * (BIP-322 signing, bech32 validation) deliberately does NOT live here — that\n * belongs in a bitcoin/crypto library, not the design system.\n */\n\n/** Reference BTC/USD rate for sats↔USD display. Override per call when a live\n * rate is available; this is a display affordance, not a price oracle. */\nexport const REFERENCE_BTC_USD = 95_000;\n\n/** Format a satoshi integer as a thousands-separated string (\"12,408\"). */\nexport function formatSats(sats: number): string {\n return new Intl.NumberFormat('en-US').format(Math.round(sats));\n}\n\n/** Compact sats for buttons/chips: \"7k sats\", \"1.5k sats\", \"999 sats\". */\nexport function formatSatsCompact(sats: number): string {\n const n = Math.round(sats);\n if (n < 1000) return `${n} sats`;\n const k = n / 1000;\n return `${Number.isInteger(k) ? k : k.toFixed(1)}k sats`;\n}\n\n/** Convert sats to a USD currency string using the reference (or given) rate. */\nexport function satsToUsd(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n const usd = (sats / 100_000_000) * btcUsd;\n if (usd > 0 && usd < 0.01) return '< $0.01';\n // Whole dollars once >= $100, cents below. min must never exceed max.\n const fractionDigits = usd >= 100 ? 0 : 2;\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: fractionDigits,\n maximumFractionDigits: fractionDigits,\n }).format(usd);\n}\n\n/** Convert a USD amount to sats using the reference (or given) rate. */\nexport function usdToSats(usd: number, btcUsd: number = REFERENCE_BTC_USD): number {\n return Math.round((usd / btcUsd) * 100_000_000);\n}\n\n/** Both denominations as one label: \"7,000 sats · $6.65\". */\nexport function priceBoth(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n return `${formatSats(sats)} sats · ${satsToUsd(sats, btcUsd)}`;\n}\n\n/** Compact relative time — \"now\", \"3m ago\", \"2h ago\", \"4d ago\", or a date. */\nexport function relativeTime(when: string | number | Date): string {\n const t = when instanceof Date ? when.getTime() : new Date(when).getTime();\n if (Number.isNaN(t)) return '';\n const diff = Date.now() - t;\n const m = Math.floor(diff / 60_000);\n if (m < 1) return 'now';\n if (m < 60) return `${m}m ago`;\n const h = Math.floor(m / 60);\n if (h < 24) return `${h}h ago`;\n const d = Math.floor(h / 24);\n if (d < 30) return `${d}d ago`;\n const mo = Math.floor(d / 30);\n if (mo < 12) return `${mo}mo ago`;\n return `${Math.floor(mo / 12)}y ago`;\n}\n\n/**\n * Truncate a Bitcoin/Lightning address (or any long id) for display:\n * \"bc1qxy…wxyz\". Defaults match the family's most common rule (8 head / 4 tail).\n */\nexport function shortenAddress(value: string, head = 8, tail = 4): string {\n if (!value) return '';\n if (value.length <= head + tail + 1) return value;\n return `${value.slice(0, head)}…${value.slice(-tail)}`;\n}\n\nexport type ExplorerNetwork = 'mainnet' | 'testnet' | 'signet';\nexport type ExplorerTarget =\n | { address: string }\n | { tx: string }\n | { block: string | number };\n\n/** A mempool.space explorer URL for an address, tx, or block. */\nexport function explorerUrl(target: ExplorerTarget, network: ExplorerNetwork = 'mainnet'): string {\n const base =\n network === 'mainnet'\n ? 'https://mempool.space'\n : `https://mempool.space/${network}`;\n if ('address' in target) return `${base}/address/${target.address}`;\n if ('tx' in target) return `${base}/tx/${target.tx}`;\n return `${base}/block/${target.block}`;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/format/use-spot-price.ts","../src/format/index.ts"],"names":[],"mappings":"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","file":"format.mjs","sourcesContent":["/**\n * useSpotPrice — live BTC/USD spot rate for display surfaces.\n *\n * Pulls a JSON shape `{ btc_usd: number, fetched_at?: string | null }`\n * from a consumer-hosted endpoint (default `/api/price/btc-usd`),\n * refreshing every 60 seconds on a setInterval. The initial paint\n * reads `REFERENCE_BTC_USD` so the first frame is never NaN.\n *\n * Consumer contract (read this when wiring):\n *\n * The hook makes a relative fetch to `endpoint`. Every consumer that\n * mounts this MUST host that route locally — typically a Next.js\n * pages-router API route that proxies mempool.space's spot feed and\n * caches in-process. The canonical implementation lives at\n * `oc-vault-web/src/pages/api/price/btc-usd.ts` (and the mirror in\n * `oc-me-web`). Both also host the matching server-side fetch +\n * cache in `src/lib/price/feed.ts` — that part stays per-consumer\n * because it's Node-only code that doesn't belong in a UI package.\n *\n * If you don't have the route yet, pass an explicit endpoint\n * (e.g. `useSpotPrice({ endpoint: 'https://other-site.ochk.io/api/price/btc-usd' })`)\n * pointing at a sibling site's endpoint — they're CORS-open.\n *\n * Returns the rich `{ btcUsd, fetchedAt }` shape rather than a bare\n * number so callers that want a provenance caption can render\n * `asOf(fetchedAt)` without a second hook.\n */\n\n'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { REFERENCE_BTC_USD } from './index';\n\nexport interface SpotPrice {\n /** BTC/USD rate. Falls back to `REFERENCE_BTC_USD` until the first\n * fetch lands so the initial paint never reads NaN. */\n btcUsd: number;\n /** ISO timestamp the rate was fetched upstream. `null` until the\n * first successful fetch. Pair with `asOf()` for \"as of HH:MM\". */\n fetchedAt: string | null;\n}\n\nexport interface UseSpotPriceOptions {\n /** Override the endpoint path. Defaults to `/api/price/btc-usd` —\n * the family-canonical proxy route. Useful when consuming a sibling\n * site's endpoint cross-origin, or when self-hosting the proxy at\n * a non-default path. */\n endpoint?: string;\n /** Override the refresh cadence in milliseconds. Defaults to 60_000,\n * matching the upstream mempool.space cache TTL. Lower than ~10s\n * is wasted bandwidth — the underlying feed doesn't refresh faster. */\n intervalMs?: number;\n}\n\ninterface SpotPriceResponse {\n btc_usd?: number;\n fetched_at?: string | null;\n}\n\n/**\n * Hook · returns the latest BTC/USD spot rate from `endpoint`.\n *\n * Behaviour:\n * - First mount: synchronous return of `REFERENCE_BTC_USD` (no NaN),\n * then an async fetch updates state.\n * - Every `intervalMs` thereafter, a background fetch keeps the rate\n * fresh while the tab is alive.\n * - Network blips keep the last-good value — no flash to fallback.\n * - Unmount cancels both the in-flight read (`cancelled` flag) and\n * the interval timer.\n */\nexport function useSpotPrice(opts: UseSpotPriceOptions = {}): SpotPrice {\n const { endpoint = '/api/price/btc-usd', intervalMs = 60_000 } = opts;\n const [state, setState] = useState<SpotPrice>({\n btcUsd: REFERENCE_BTC_USD,\n fetchedAt: null,\n });\n\n useEffect(() => {\n let cancelled = false;\n async function pull() {\n try {\n const res = await fetch(endpoint);\n if (!res.ok) return;\n const json = (await res.json()) as SpotPriceResponse;\n if (cancelled) return;\n if (typeof json.btc_usd === 'number' && json.btc_usd > 0) {\n setState({\n btcUsd: Math.round(json.btc_usd),\n fetchedAt: json.fetched_at ?? null,\n });\n }\n } catch {\n // Network blip · keep the last good value, don't flash a\n // fallback. The next poll will recover.\n }\n }\n void pull();\n const t = setInterval(pull, intervalMs);\n return () => {\n cancelled = true;\n clearInterval(t);\n };\n }, [endpoint, intervalMs]);\n\n return state;\n}\n","/**\n * @orangecheck/design/format — Bitcoin/money display + formatting utilities.\n *\n * Presentational only. Lifted from the canonical implementations across the\n * family (oc-me-web's utils, oc-vault-web's billing) and standardized so every\n * site formats sats, prices, addresses, and times identically. NOTE: crypto\n * (BIP-322 signing, bech32 validation) deliberately does NOT live here — that\n * belongs in a bitcoin/crypto library, not the design system.\n *\n * Includes the `useSpotPrice` React hook for live BTC/USD display. The hook\n * fetches a consumer-hosted endpoint — see `use-spot-price.ts` for the\n * one-route-per-consumer contract.\n */\n\nexport { useSpotPrice, type SpotPrice, type UseSpotPriceOptions } from './use-spot-price';\n\n/** Reference BTC/USD rate for sats↔USD display. Override per call when a live\n * rate is available; this is a display affordance, not a price oracle. */\nexport const REFERENCE_BTC_USD = 95_000;\n\n/** Format a satoshi integer as a thousands-separated string (\"12,408\"). */\nexport function formatSats(sats: number): string {\n return new Intl.NumberFormat('en-US').format(Math.round(sats));\n}\n\n/** Compact sats for buttons/chips: \"7k sats\", \"1.5k sats\", \"999 sats\". */\nexport function formatSatsCompact(sats: number): string {\n const n = Math.round(sats);\n if (n < 1000) return `${n} sats`;\n const k = n / 1000;\n return `${Number.isInteger(k) ? k : k.toFixed(1)}k sats`;\n}\n\n/** Convert sats to a USD currency string using the reference (or given) rate. */\nexport function satsToUsd(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n const usd = (sats / 100_000_000) * btcUsd;\n if (usd > 0 && usd < 0.01) return '< $0.01';\n // Whole dollars once >= $100, cents below. min must never exceed max.\n const fractionDigits = usd >= 100 ? 0 : 2;\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: fractionDigits,\n maximumFractionDigits: fractionDigits,\n }).format(usd);\n}\n\n/** Convert a USD amount to sats using the reference (or given) rate. */\nexport function usdToSats(usd: number, btcUsd: number = REFERENCE_BTC_USD): number {\n return Math.round((usd / btcUsd) * 100_000_000);\n}\n\n/** Both denominations as one label: \"7,000 sats · $6.65\". */\nexport function priceBoth(sats: number, btcUsd: number = REFERENCE_BTC_USD): string {\n return `${formatSats(sats)} sats · ${satsToUsd(sats, btcUsd)}`;\n}\n\n/**\n * \"as of HH:MM\" provenance footnote. Returns `null` for nullish or\n * unparseable input so callers can render nothing\n * (`{x && <span>{x}</span>}`) rather than gate on a magic empty\n * string. 24-hour, locale-aware. Pairs with `useSpotPrice().fetchedAt`\n * to caption a live-rate display.\n */\nexport function asOf(iso: string | null | undefined): string | null {\n if (!iso) return null;\n const t = new Date(iso);\n if (Number.isNaN(t.getTime())) return null;\n return `as of ${t.toLocaleTimeString(undefined, {\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n })}`;\n}\n\n/** Compact relative time — \"now\", \"3m ago\", \"2h ago\", \"4d ago\", or a date. */\nexport function relativeTime(when: string | number | Date): string {\n const t = when instanceof Date ? when.getTime() : new Date(when).getTime();\n if (Number.isNaN(t)) return '';\n const diff = Date.now() - t;\n const m = Math.floor(diff / 60_000);\n if (m < 1) return 'now';\n if (m < 60) return `${m}m ago`;\n const h = Math.floor(m / 60);\n if (h < 24) return `${h}h ago`;\n const d = Math.floor(h / 24);\n if (d < 30) return `${d}d ago`;\n const mo = Math.floor(d / 30);\n if (mo < 12) return `${mo}mo ago`;\n return `${Math.floor(mo / 12)}y ago`;\n}\n\n/**\n * Truncate a Bitcoin/Lightning address (or any long id) for display:\n * \"bc1qxy…wxyz\". Defaults match the family's most common rule (8 head / 4 tail).\n */\nexport function shortenAddress(value: string, head = 8, tail = 4): string {\n if (!value) return '';\n if (value.length <= head + tail + 1) return value;\n return `${value.slice(0, head)}…${value.slice(-tail)}`;\n}\n\nexport type ExplorerNetwork = 'mainnet' | 'testnet' | 'signet';\nexport type ExplorerTarget =\n | { address: string }\n | { tx: string }\n | { block: string | number };\n\n/** A mempool.space explorer URL for an address, tx, or block. */\nexport function explorerUrl(target: ExplorerTarget, network: ExplorerNetwork = 'mainnet'): string {\n const base =\n network === 'mainnet'\n ? 'https://mempool.space'\n : `https://mempool.space/${network}`;\n if ('address' in target) return `${base}/address/${target.address}`;\n if ('tx' in target) return `${base}/tx/${target.tx}`;\n return `${base}/block/${target.block}`;\n}\n"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ export { DEFAULT_OC_THEME, OC_DEFAULT_ACCENT, OC_SKIN_COOKIE, OC_THEMES, OcAppea
|
|
|
2
2
|
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertTitle, AlertWithAction, AlertWithActionProps, AlertWithCountdown, AlertWithCountdownProps, Badge, Bar, BarProps, BitcoinAddress, BitcoinAddressProps, Button, Card, CardProps, Checkbox, ConfirmHost, ConfirmOptions, CopyButton, CopyButtonProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, ErrorBoundary, Input, Label, Modal, ModalProps, Pagination, PaginationProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PromptHost, PromptOptions, QrCode, QrCodeProps, RadioGroup, RadioGroupItem, SatsAmount, SatsAmountProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Skeleton, SkeletonProps, Sparkline, SparklineProps, StatBlock, StatBlockProps, StatusPill, StatusPillProps, StatusPillSpec, StatusPillVariant, StatusTone, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ThemeToggle, ThemeToggleLink, Toaster, Working, WorkingPanel, WorkingProps, badgeVariants, buttonVariants, confirm, makeStatusPill, prompt } from './primitives.mjs';
|
|
3
3
|
export { Callout, CalloutProps, CalloutTone, DataRow, DataRowProps, DefinitionItem, DefinitionList, DefinitionListProps, EmptyState, EmptyStateCta, EmptyStateProps, HelpHint, HelpHintProps, SectionHeader, SectionHeaderProps, StatCard, StatCardProps, StatGrid, StatGridProps, StatItem, StatTile } from './composites.mjs';
|
|
4
4
|
export { AppShell, AppShellProps, EcosystemSlug, EcosystemSwitcher, EcosystemSwitcherProps, FAMILY_PROPERTIES, FamilyCategory, FamilyProperty, LayoutSubHeader, LayoutSubHeaderProps, LayoutSubHeaderTag, OcAccountMenu, OcAccountMenuBuildInfo, OcAccountMenuItem, OcAccountMenuProps, OcAccountMenuSession, OcAccountMenuView, OcAccountMenuViewProps, OcDashboardHub, OcDashboardHubProps, OcDashboardShell, OcDashboardShellProps, OcDashboardTool, OcLogoDropdown, OcLogoDropdownProps, OcPrimaryNav, OcPrimaryNavLink, OcPrimaryNavProps, SITE_STATE_LABEL, SiteState, findFamilyProperty } from './chrome.mjs';
|
|
5
|
-
export { ExplorerNetwork, ExplorerTarget, REFERENCE_BTC_USD, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats } from './format.mjs';
|
|
5
|
+
export { ExplorerNetwork, ExplorerTarget, REFERENCE_BTC_USD, SpotPrice, UseSpotPriceOptions, asOf, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats, useSpotPrice } from './format.mjs';
|
|
6
6
|
import 'clsx';
|
|
7
7
|
import 'react/jsx-runtime';
|
|
8
8
|
import 'react';
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { DEFAULT_OC_THEME, OC_DEFAULT_ACCENT, OC_SKIN_COOKIE, OC_THEMES, OcAppea
|
|
|
2
2
|
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertTitle, AlertWithAction, AlertWithActionProps, AlertWithCountdown, AlertWithCountdownProps, Badge, Bar, BarProps, BitcoinAddress, BitcoinAddressProps, Button, Card, CardProps, Checkbox, ConfirmHost, ConfirmOptions, CopyButton, CopyButtonProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, ErrorBoundary, Input, Label, Modal, ModalProps, Pagination, PaginationProps, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, PromptHost, PromptOptions, QrCode, QrCodeProps, RadioGroup, RadioGroupItem, SatsAmount, SatsAmountProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Skeleton, SkeletonProps, Sparkline, SparklineProps, StatBlock, StatBlockProps, StatusPill, StatusPillProps, StatusPillSpec, StatusPillVariant, StatusTone, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, ThemeToggle, ThemeToggleLink, Toaster, Working, WorkingPanel, WorkingProps, badgeVariants, buttonVariants, confirm, makeStatusPill, prompt } from './primitives.js';
|
|
3
3
|
export { Callout, CalloutProps, CalloutTone, DataRow, DataRowProps, DefinitionItem, DefinitionList, DefinitionListProps, EmptyState, EmptyStateCta, EmptyStateProps, HelpHint, HelpHintProps, SectionHeader, SectionHeaderProps, StatCard, StatCardProps, StatGrid, StatGridProps, StatItem, StatTile } from './composites.js';
|
|
4
4
|
export { AppShell, AppShellProps, EcosystemSlug, EcosystemSwitcher, EcosystemSwitcherProps, FAMILY_PROPERTIES, FamilyCategory, FamilyProperty, LayoutSubHeader, LayoutSubHeaderProps, LayoutSubHeaderTag, OcAccountMenu, OcAccountMenuBuildInfo, OcAccountMenuItem, OcAccountMenuProps, OcAccountMenuSession, OcAccountMenuView, OcAccountMenuViewProps, OcDashboardHub, OcDashboardHubProps, OcDashboardShell, OcDashboardShellProps, OcDashboardTool, OcLogoDropdown, OcLogoDropdownProps, OcPrimaryNav, OcPrimaryNavLink, OcPrimaryNavProps, SITE_STATE_LABEL, SiteState, findFamilyProperty } from './chrome.js';
|
|
5
|
-
export { ExplorerNetwork, ExplorerTarget, REFERENCE_BTC_USD, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats } from './format.js';
|
|
5
|
+
export { ExplorerNetwork, ExplorerTarget, REFERENCE_BTC_USD, SpotPrice, UseSpotPriceOptions, asOf, explorerUrl, formatSats, formatSatsCompact, priceBoth, relativeTime, satsToUsd, shortenAddress, usdToSats, useSpotPrice } from './format.js';
|
|
6
6
|
import 'clsx';
|
|
7
7
|
import 'react/jsx-runtime';
|
|
8
8
|
import 'react';
|
package/dist/index.js
CHANGED
|
@@ -1939,6 +1939,38 @@ function TabsContent({ className, ...props }) {
|
|
|
1939
1939
|
}
|
|
1940
1940
|
);
|
|
1941
1941
|
}
|
|
1942
|
+
function useSpotPrice(opts = {}) {
|
|
1943
|
+
const { endpoint = "/api/price/btc-usd", intervalMs = 6e4 } = opts;
|
|
1944
|
+
const [state, setState] = React2.useState({
|
|
1945
|
+
btcUsd: REFERENCE_BTC_USD,
|
|
1946
|
+
fetchedAt: null
|
|
1947
|
+
});
|
|
1948
|
+
React2.useEffect(() => {
|
|
1949
|
+
let cancelled = false;
|
|
1950
|
+
async function pull() {
|
|
1951
|
+
try {
|
|
1952
|
+
const res = await fetch(endpoint);
|
|
1953
|
+
if (!res.ok) return;
|
|
1954
|
+
const json = await res.json();
|
|
1955
|
+
if (cancelled) return;
|
|
1956
|
+
if (typeof json.btc_usd === "number" && json.btc_usd > 0) {
|
|
1957
|
+
setState({
|
|
1958
|
+
btcUsd: Math.round(json.btc_usd),
|
|
1959
|
+
fetchedAt: json.fetched_at ?? null
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
} catch {
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
void pull();
|
|
1966
|
+
const t = setInterval(pull, intervalMs);
|
|
1967
|
+
return () => {
|
|
1968
|
+
cancelled = true;
|
|
1969
|
+
clearInterval(t);
|
|
1970
|
+
};
|
|
1971
|
+
}, [endpoint, intervalMs]);
|
|
1972
|
+
return state;
|
|
1973
|
+
}
|
|
1942
1974
|
|
|
1943
1975
|
// src/format/index.ts
|
|
1944
1976
|
var REFERENCE_BTC_USD = 95e3;
|
|
@@ -1968,6 +2000,16 @@ function usdToSats(usd, btcUsd = REFERENCE_BTC_USD) {
|
|
|
1968
2000
|
function priceBoth(sats, btcUsd = REFERENCE_BTC_USD) {
|
|
1969
2001
|
return `${formatSats(sats)} sats \xB7 ${satsToUsd(sats, btcUsd)}`;
|
|
1970
2002
|
}
|
|
2003
|
+
function asOf(iso) {
|
|
2004
|
+
if (!iso) return null;
|
|
2005
|
+
const t = new Date(iso);
|
|
2006
|
+
if (Number.isNaN(t.getTime())) return null;
|
|
2007
|
+
return `as of ${t.toLocaleTimeString(void 0, {
|
|
2008
|
+
hour: "2-digit",
|
|
2009
|
+
minute: "2-digit",
|
|
2010
|
+
hour12: false
|
|
2011
|
+
})}`;
|
|
2012
|
+
}
|
|
1971
2013
|
function relativeTime(when) {
|
|
1972
2014
|
const t = when instanceof Date ? when.getTime() : new Date(when).getTime();
|
|
1973
2015
|
if (Number.isNaN(t)) return "";
|
|
@@ -4030,6 +4072,7 @@ exports.Toaster = Toaster;
|
|
|
4030
4072
|
exports.Working = Working;
|
|
4031
4073
|
exports.WorkingPanel = WorkingPanel;
|
|
4032
4074
|
exports.accentFor = accentFor;
|
|
4075
|
+
exports.asOf = asOf;
|
|
4033
4076
|
exports.badgeVariants = badgeVariants;
|
|
4034
4077
|
exports.buttonVariants = buttonVariants;
|
|
4035
4078
|
exports.cn = cn;
|
|
@@ -4049,5 +4092,6 @@ exports.satsToUsd = satsToUsd;
|
|
|
4049
4092
|
exports.shortenAddress = shortenAddress;
|
|
4050
4093
|
exports.usdToSats = usdToSats;
|
|
4051
4094
|
exports.useOcSkin = useOcSkin;
|
|
4095
|
+
exports.useSpotPrice = useSpotPrice;
|
|
4052
4096
|
//# sourceMappingURL=index.js.map
|
|
4053
4097
|
//# sourceMappingURL=index.js.map
|