@hammerock/cca-widget 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shubham Singh Shoora
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # @hammerock/cca-widget
2
+
3
+ An embeddable card that shows a live [Uniswap Continuous Clearing Auction](https://github.com/Uniswap/continuous-clearing-auction) and lets visitors bid on it from your own site.
4
+
5
+ You can use it as a **React component**, or on **any website with a `<script>` tag**, no build step needed.
6
+
7
+ > **Testnet only.** It works with Sepolia (11155111), Base Sepolia (84532) and Unichain Sepolia (1301), and rejects every other chain id.
8
+
9
+ The card shows the clearing price, amount raised, supply cleared, supply schedule progress, time remaining and graduation status. It refreshes every 12 seconds and stops polling once the auction ends.
10
+
11
+ ## React
12
+
13
+ ```bash
14
+ npm install @hammerock/cca-widget viem react react-dom
15
+ ```
16
+
17
+ ```tsx
18
+ import { AuctionCard } from "@hammerock/cca-widget";
19
+
20
+ export function Launch() {
21
+ return (
22
+ <AuctionCard
23
+ auction="0x1e40451D6ecabA8640559b7dFbe63B41dA52460d"
24
+ chainId={11155111}
25
+ rpcUrl="https://ethereum-sepolia-rpc.publicnode.com"
26
+ />
27
+ );
28
+ }
29
+ ```
30
+
31
+ The build includes `"use client"`, so you can import it straight into a **Next.js App Router** page.
32
+
33
+ ## Any website
34
+
35
+ ```html
36
+ <div id="beacon"></div>
37
+
38
+ <script src="https://cdn.jsdelivr.net/npm/@hammerock/cca-widget@0.1.0/dist/mount.global.js"></script>
39
+ <script>
40
+ const card = BeaconCCA.mount("#beacon", {
41
+ auction: "0x1e40451D6ecabA8640559b7dFbe63B41dA52460d",
42
+ chainId: 11155111,
43
+ rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com",
44
+ });
45
+
46
+ // Later, if needed:
47
+ // card.update({ auction: "0x..." });
48
+ // card.unmount();
49
+ </script>
50
+ ```
51
+
52
+ This bundle includes React, so the page needs nothing else. It is about 540 KB.
53
+
54
+ ## Props
55
+
56
+ | Prop | Type | Required | Description |
57
+ | --- | --- | --- | --- |
58
+ | `auction` | `` `0x${string}` `` | yes | The `ContinuousClearingAuction` address |
59
+ | `chainId` | `number` | yes | Must be a supported testnet |
60
+ | `rpcUrl` | `string` | yes | **A public RPC endpoint** — see below |
61
+ | `lensAddress` | `` `0x${string}` `` | no | A deployed `AuctionStateLens`. Leave it out and the widget reads state without one. |
62
+ | `pollIntervalMs` | `number` | no | Refresh interval, default `12000` |
63
+ | `readOnly` | `boolean` | no | Hide the bid form |
64
+
65
+ **Never pass an RPC URL that contains an API key.** The widget runs in your visitors' browsers, so anything in `rpcUrl` is visible to them.
66
+
67
+ ## Bidding
68
+
69
+ Visitors need a browser wallet such as MetaMask. The widget connects through the wallet's standard provider (EIP-1193), so you don't need wagmi or any provider setup. It also asks the wallet to switch to the right network.
70
+
71
+ What the widget handles for the visitor:
72
+
73
+ - **ERC-20 auctions** need approvals through Permit2 before the bid. The widget sends only the approvals that are missing, then the bid.
74
+ - **Prices are rounded down to the auction's tick spacing**, because the contract rejects any other price. The form shows the adjusted price before the visitor confirms.
75
+ - **Closed or not-yet-open auctions** show a clear message and a disabled button instead of a bid that would fail.
76
+ - It checks the visitor's balance before sending anything.
77
+
78
+ ## Using the hook directly
79
+
80
+ To build your own UI, use the data hook that powers the card:
81
+
82
+ ```tsx
83
+ import { useAuction } from "@hammerock/cca-widget";
84
+
85
+ const { data, error, loading, refresh } = useAuction({
86
+ auction: "0x1e40451D6ecabA8640559b7dFbe63B41dA52460d",
87
+ chainId: 11155111,
88
+ rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com",
89
+ });
90
+ ```
91
+
92
+ For lower-level access (discovery, Q96 math, bid construction), use [`@hammerock/cca-sdk`](https://www.npmjs.com/package/@hammerock/cca-sdk), which this widget is built on.
93
+
94
+ Source: [github.com/Shubham-Singh-Shoora/Beacon-](https://github.com/Shubham-Singh-Shoora/Beacon-).
95
+
96
+ ## License
97
+
98
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,573 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var src_exports = {};
23
+ __export(src_exports, {
24
+ AuctionCard: () => AuctionCard,
25
+ connectWallet: () => connectWallet,
26
+ getInjectedProvider: () => getInjectedProvider,
27
+ hasInjectedWallet: () => hasInjectedWallet,
28
+ mount: () => mount,
29
+ useAuction: () => useAuction
30
+ });
31
+ module.exports = __toCommonJS(src_exports);
32
+
33
+ // src/AuctionCard.tsx
34
+ var import_react2 = require("react");
35
+ var import_cca_sdk2 = require("@hammerock/cca-sdk");
36
+
37
+ // src/useAuction.ts
38
+ var import_react = require("react");
39
+ var import_cca_sdk = require("@hammerock/cca-sdk");
40
+ function useAuction(options) {
41
+ const { auction, rpcUrl, chainId, lensAddress, pollIntervalMs = 12e3 } = options;
42
+ const [data, setData] = (0, import_react.useState)(null);
43
+ const [error, setError] = (0, import_react.useState)(null);
44
+ const [loading, setLoading] = (0, import_react.useState)(true);
45
+ const [nonce, setNonce] = (0, import_react.useState)(0);
46
+ const staticRef = (0, import_react.useRef)({});
47
+ const endedAtRef = (0, import_react.useRef)(false);
48
+ const clientRef = (0, import_react.useRef)(null);
49
+ if (!clientRef.current) {
50
+ try {
51
+ clientRef.current = (0, import_cca_sdk.createBeaconClient)({ rpcUrl, chainId, lensAddress });
52
+ } catch {
53
+ clientRef.current = null;
54
+ }
55
+ }
56
+ const refresh = (0, import_react.useCallback)(() => setNonce((value) => value + 1), []);
57
+ (0, import_react.useEffect)(() => {
58
+ const client = clientRef.current;
59
+ if (!client) {
60
+ setError(new Error(`Unsupported or non-testnet chain id ${chainId}.`));
61
+ setLoading(false);
62
+ return;
63
+ }
64
+ let cancelled = false;
65
+ async function load(isInitial) {
66
+ try {
67
+ if (isInitial) setLoading(true);
68
+ const [info, state, headBlock] = await Promise.all([
69
+ // Config is immutable, so only read it once.
70
+ staticRef.current.info ?? (0, import_cca_sdk.getAuctionInfo)(client, auction),
71
+ (0, import_cca_sdk.getAuctionState)(client, auction),
72
+ client.publicClient.getBlockNumber()
73
+ ]);
74
+ const [token, currency] = await Promise.all([
75
+ staticRef.current.token ?? (0, import_cca_sdk.getTokenMetadata)(client, info.token),
76
+ staticRef.current.currency ?? (0, import_cca_sdk.getTokenMetadata)(client, info.currency)
77
+ ]);
78
+ staticRef.current = { info, token, currency };
79
+ if (cancelled) return;
80
+ const status = (0, import_cca_sdk.deriveStatus)(info, headBlock);
81
+ endedAtRef.current = status === "ended";
82
+ const blocksRemaining = status === "pending" ? info.startBlock - headBlock : status === "live" ? info.endBlock - headBlock : 0n;
83
+ setData({
84
+ info,
85
+ state,
86
+ token,
87
+ currency,
88
+ status,
89
+ headBlock,
90
+ blocksRemaining,
91
+ secondsRemaining: Number(blocksRemaining) * client.blockTimeSeconds
92
+ });
93
+ setError(null);
94
+ } catch (cause) {
95
+ if (!cancelled) setError(cause);
96
+ } finally {
97
+ if (!cancelled) setLoading(false);
98
+ }
99
+ }
100
+ void load(true);
101
+ const timer = setInterval(() => {
102
+ if (staticRef.current.info && endedAtRef.current) {
103
+ clearInterval(timer);
104
+ return;
105
+ }
106
+ void load(false);
107
+ }, pollIntervalMs);
108
+ return () => {
109
+ cancelled = true;
110
+ clearInterval(timer);
111
+ };
112
+ }, [auction, chainId, pollIntervalMs, nonce]);
113
+ return { data, error, loading, client: clientRef.current, refresh };
114
+ }
115
+
116
+ // src/wallet.ts
117
+ var import_viem = require("viem");
118
+ function getInjectedProvider() {
119
+ return typeof window === "undefined" ? void 0 : window.ethereum;
120
+ }
121
+ function hasInjectedWallet() {
122
+ return getInjectedProvider() !== void 0;
123
+ }
124
+ async function connectWallet(chain) {
125
+ const provider = getInjectedProvider();
126
+ if (!provider) {
127
+ throw new Error("No browser wallet found. Install MetaMask or another EIP-1193 wallet.");
128
+ }
129
+ const accounts = await provider.request({ method: "eth_requestAccounts" });
130
+ const address = accounts[0];
131
+ if (!address) throw new Error("Wallet returned no accounts.");
132
+ const currentChainId = Number(
133
+ await provider.request({ method: "eth_chainId" })
134
+ );
135
+ if (currentChainId !== chain.id) {
136
+ await switchChain(provider, chain);
137
+ }
138
+ const walletClient = (0, import_viem.createWalletClient)({ account: address, chain, transport: (0, import_viem.custom)(provider) });
139
+ return { address, walletClient, chainId: chain.id };
140
+ }
141
+ async function switchChain(provider, chain) {
142
+ const chainIdHex = `0x${chain.id.toString(16)}`;
143
+ try {
144
+ await provider.request({
145
+ method: "wallet_switchEthereumChain",
146
+ params: [{ chainId: chainIdHex }]
147
+ });
148
+ } catch (error) {
149
+ const code = error.code;
150
+ if (code !== 4902) throw error;
151
+ await provider.request({
152
+ method: "wallet_addEthereumChain",
153
+ params: [
154
+ {
155
+ chainId: chainIdHex,
156
+ chainName: chain.name,
157
+ nativeCurrency: chain.nativeCurrency,
158
+ rpcUrls: [chain.rpcUrls.default.http[0]],
159
+ blockExplorerUrls: chain.blockExplorers?.default?.url ? [chain.blockExplorers.default.url] : void 0
160
+ }
161
+ ]
162
+ });
163
+ }
164
+ }
165
+
166
+ // src/styles.ts
167
+ var border = "#2a2a2e";
168
+ var surface = "#141417";
169
+ var text = "#f4f4f5";
170
+ var muted = "#8b8b93";
171
+ var styles = {
172
+ card: {
173
+ boxSizing: "border-box",
174
+ width: "100%",
175
+ maxWidth: 360,
176
+ padding: 16,
177
+ borderRadius: 12,
178
+ border: `1px solid ${border}`,
179
+ background: surface,
180
+ color: text,
181
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
182
+ fontSize: 14,
183
+ lineHeight: 1.45
184
+ },
185
+ headerRow: {
186
+ display: "flex",
187
+ alignItems: "flex-start",
188
+ justifyContent: "space-between",
189
+ gap: 8
190
+ },
191
+ symbol: { fontSize: 18, fontWeight: 600 },
192
+ pillRow: { display: "flex", flexWrap: "wrap", gap: 4, justifyContent: "flex-end" },
193
+ pill: {
194
+ display: "inline-block",
195
+ padding: "2px 8px",
196
+ borderRadius: 999,
197
+ fontSize: 11,
198
+ fontWeight: 500,
199
+ whiteSpace: "nowrap"
200
+ },
201
+ pillLive: { background: "rgba(16,185,129,0.15)", color: "#6ee7b7" },
202
+ pillPending: { background: "rgba(14,165,233,0.15)", color: "#7dd3fc" },
203
+ pillEnded: { background: "rgba(113,113,122,0.18)", color: "#c4c4cc" },
204
+ pillGraduated: { background: "rgba(139,92,246,0.18)", color: "#c4b5fd" },
205
+ priceBlock: { marginTop: 14 },
206
+ price: { fontSize: 22, fontWeight: 600, fontVariantNumeric: "tabular-nums", wordBreak: "break-all" },
207
+ priceUnit: { fontSize: 12, fontWeight: 400, color: muted },
208
+ grid: { margin: "14px 0 0", display: "grid", gap: 8 },
209
+ row: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 12, margin: 0 },
210
+ label: { fontSize: 11, textTransform: "uppercase", letterSpacing: "0.04em", color: muted, margin: 0 },
211
+ value: { margin: 0, fontSize: 13, textAlign: "right", fontVariantNumeric: "tabular-nums" },
212
+ form: { marginTop: 16, display: "grid", gap: 6, borderTop: `1px solid ${border}`, paddingTop: 14 },
213
+ input: {
214
+ boxSizing: "border-box",
215
+ width: "100%",
216
+ padding: "8px 10px",
217
+ borderRadius: 8,
218
+ border: `1px solid ${border}`,
219
+ background: "#0d0d0f",
220
+ color: text,
221
+ fontSize: 14,
222
+ outline: "none"
223
+ },
224
+ button: {
225
+ marginTop: 6,
226
+ padding: "10px 12px",
227
+ borderRadius: 8,
228
+ border: "none",
229
+ background: text,
230
+ color: "#0d0d0f",
231
+ fontSize: 14,
232
+ fontWeight: 600,
233
+ cursor: "pointer"
234
+ },
235
+ buttonDisabled: {
236
+ background: "#2a2a2e",
237
+ color: muted,
238
+ cursor: "not-allowed"
239
+ },
240
+ notice: {
241
+ padding: "8px 10px",
242
+ borderRadius: 8,
243
+ background: "rgba(113,113,122,0.12)",
244
+ color: muted,
245
+ fontSize: 12
246
+ },
247
+ muted: { color: muted, fontSize: 12 },
248
+ error: { color: "#fca5a5", fontSize: 12, wordBreak: "break-word" },
249
+ success: { color: "#6ee7b7", fontSize: 12, wordBreak: "break-word" },
250
+ link: { color: "#7dd3fc" },
251
+ footer: {
252
+ marginTop: 14,
253
+ paddingTop: 10,
254
+ borderTop: `1px solid ${border}`,
255
+ color: muted,
256
+ fontSize: 11,
257
+ textAlign: "center"
258
+ }
259
+ };
260
+
261
+ // src/AuctionCard.tsx
262
+ var import_jsx_runtime = require("react/jsx-runtime");
263
+ function AuctionCard(props) {
264
+ const { data, error, loading, client, refresh } = useAuction(props);
265
+ const [wallet, setWallet] = (0, import_react2.useState)(null);
266
+ const [maxPrice, setMaxPrice] = (0, import_react2.useState)("");
267
+ const [amount, setAmount] = (0, import_react2.useState)("");
268
+ const [tx, setTx] = (0, import_react2.useState)({ kind: "idle" });
269
+ const parsed = (0, import_react2.useMemo)(() => {
270
+ let maxPriceQ96 = 0n;
271
+ let amountUnits = 0n;
272
+ let parseError = null;
273
+ try {
274
+ if (maxPrice.trim()) maxPriceQ96 = (0, import_cca_sdk2.decimalToQ96)(maxPrice.trim());
275
+ } catch {
276
+ parseError = "Maximum price must be a decimal number.";
277
+ }
278
+ if (amount.trim() && data) {
279
+ const decimals = data.currency.decimals;
280
+ if (!/^\d+(\.\d+)?$/.test(amount.trim())) {
281
+ parseError = "Bid amount must be a decimal number.";
282
+ } else {
283
+ const [whole = "0", frac = ""] = amount.trim().split(".");
284
+ const padded = (frac + "0".repeat(decimals)).slice(0, decimals);
285
+ amountUnits = BigInt(whole) * 10n ** BigInt(decimals) + BigInt(padded || "0");
286
+ }
287
+ }
288
+ return { maxPriceQ96, amountUnits, parseError };
289
+ }, [maxPrice, amount, data]);
290
+ if (loading && !data) {
291
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.card, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.muted, children: "Loading auction\u2026" }) });
292
+ }
293
+ if (error && !data) {
294
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.card, children: [
295
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.error, children: "Could not load auction" }),
296
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.muted, children: error.message.split("\n")[0] })
297
+ ] });
298
+ }
299
+ if (!data) return null;
300
+ const { info, state, token, currency, status } = data;
301
+ const hasCleared = state.totalCleared > 0n;
302
+ const schedulePercent = ((0, import_cca_sdk2.mpsToFraction)(state.checkpoint.cumulativeMps) * 100).toFixed(0);
303
+ const blocker = (0, import_cca_sdk2.describeBidBlocker)({
304
+ status,
305
+ amount: parsed.amountUnits,
306
+ maxPriceQ96: parsed.maxPriceQ96,
307
+ floorPriceQ96: info.floorPriceQ96,
308
+ tickSpacing: info.tickSpacing
309
+ });
310
+ const alignedPriceQ96 = parsed.maxPriceQ96 > 0n ? (0, import_cca_sdk2.alignPriceToTickBoundary)(parsed.maxPriceQ96, info.tickSpacing) : 0n;
311
+ const priceWasSnapped = alignedPriceQ96 > 0n && alignedPriceQ96 !== parsed.maxPriceQ96;
312
+ const bidDisabled = props.readOnly || status !== "live" || tx.kind === "working" || parsed.parseError !== null || blocker !== null;
313
+ async function onConnect() {
314
+ if (!client) return;
315
+ setTx({ kind: "working", message: "Connecting wallet\u2026" });
316
+ try {
317
+ setWallet(await connectWallet(client.chain));
318
+ setTx({ kind: "idle" });
319
+ } catch (cause) {
320
+ setTx({ kind: "error", message: cause.message });
321
+ }
322
+ }
323
+ async function onSubmitBid() {
324
+ if (!client || !wallet || !data) return;
325
+ try {
326
+ setTx({ kind: "working", message: "Checking balance\u2026" });
327
+ const readiness = await (0, import_cca_sdk2.checkBidReadiness)(client, {
328
+ auction: props.auction,
329
+ currency: data.info.currency,
330
+ bidder: wallet.address,
331
+ amount: parsed.amountUnits
332
+ });
333
+ if (!readiness.hasBalance) {
334
+ setTx({
335
+ kind: "error",
336
+ message: `Insufficient ${data.currency.symbol}. You hold ${(0, import_cca_sdk2.formatTokenAmount)(readiness.balance, data.currency)}.`
337
+ });
338
+ return;
339
+ }
340
+ const prevTickPriceQ96 = await (0, import_cca_sdk2.getPrevTickHint)(client, props.auction, parsed.maxPriceQ96);
341
+ const steps = (0, import_cca_sdk2.buildBidTransactions)({
342
+ auction: props.auction,
343
+ currency: data.info.currency,
344
+ maxPriceQ96: parsed.maxPriceQ96,
345
+ amount: parsed.amountUnits,
346
+ owner: wallet.address,
347
+ readiness,
348
+ prevTickPriceQ96,
349
+ // Snapped DOWN to a tick boundary; the contract rejects any other price.
350
+ tickSpacing: data.info.tickSpacing
351
+ });
352
+ let hash = "";
353
+ for (const [index, step] of steps.entries()) {
354
+ setTx({ kind: "working", message: `${step.label} (${index + 1}/${steps.length})\u2026` });
355
+ hash = await wallet.walletClient.sendTransaction({
356
+ account: wallet.address,
357
+ chain: client.chain,
358
+ to: step.request.to,
359
+ data: step.request.data,
360
+ value: step.request.value
361
+ });
362
+ if (index < steps.length - 1) {
363
+ await client.publicClient.waitForTransactionReceipt({ hash });
364
+ }
365
+ }
366
+ setTx({ kind: "sent", hash });
367
+ refresh();
368
+ } catch (cause) {
369
+ const message = cause.message.split("\n")[0] ?? "Transaction failed.";
370
+ setTx({ kind: "error", message });
371
+ }
372
+ }
373
+ const explorer = client?.chain.blockExplorers?.default?.url;
374
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.card, children: [
375
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.headerRow, children: [
376
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
377
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.symbol, children: token.symbol }),
378
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.muted, children: token.name })
379
+ ] }),
380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusPill, { status, isGraduated: state.isGraduated })
381
+ ] }),
382
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.priceBlock, children: [
383
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.label, children: "Clearing price" }),
384
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.price, children: [
385
+ (0, import_cca_sdk2.formatQ96Compact)(state.checkpoint.clearingPriceQ96),
386
+ " ",
387
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: styles.priceUnit, children: [
388
+ currency.symbol,
389
+ " / ",
390
+ token.symbol
391
+ ] })
392
+ ] })
393
+ ] }),
394
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", { style: styles.grid, children: [
395
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, { label: "Raised", value: (0, import_cca_sdk2.formatTokenAmount)(state.currencyRaised, currency) }),
396
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
397
+ Row,
398
+ {
399
+ label: "Cleared",
400
+ value: hasCleared ? `${(0, import_cca_sdk2.formatTokenAmount)(state.totalCleared, token)} of ${(0, import_cca_sdk2.formatTokenAmount)(info.totalSupply, token)}` : "Nothing cleared"
401
+ }
402
+ ),
403
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
404
+ Row,
405
+ {
406
+ label: "Supply schedule",
407
+ value: `${schedulePercent}% released`,
408
+ hint: "Schedule progress, not the amount sold."
409
+ }
410
+ ),
411
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
412
+ Row,
413
+ {
414
+ label: status === "pending" ? "Starts in" : status === "live" ? "Time left" : "Closed",
415
+ value: status === "ended" ? `block ${info.endBlock.toString()}` : `~${formatMinutes(data.secondsRemaining)} (${data.blocksRemaining.toString()} blocks)`
416
+ }
417
+ )
418
+ ] }),
419
+ props.readOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.form, children: [
420
+ status !== "live" ? (
421
+ // Say why, rather than showing a button that would revert.
422
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.notice, children: status === "ended" ? "This auction has ended. Bidding is closed." : "This auction has not opened yet." })
423
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
424
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: styles.label, htmlFor: "beacon-max-price", children: [
425
+ "Max price (",
426
+ currency.symbol,
427
+ " per ",
428
+ token.symbol,
429
+ ")"
430
+ ] }),
431
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
432
+ "input",
433
+ {
434
+ id: "beacon-max-price",
435
+ style: styles.input,
436
+ value: maxPrice,
437
+ onChange: (event) => setMaxPrice(event.target.value),
438
+ placeholder: (0, import_cca_sdk2.formatQ96)(info.floorPriceQ96),
439
+ inputMode: "decimal"
440
+ }
441
+ ),
442
+ priceWasSnapped ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.muted, children: [
443
+ "Will bid at ",
444
+ (0, import_cca_sdk2.formatQ96Compact)(alignedPriceQ96),
445
+ " ",
446
+ currency.symbol,
447
+ " \u2014 prices snap down to the auction's tick spacing."
448
+ ] }) : null,
449
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: styles.label, htmlFor: "beacon-amount", children: [
450
+ "Bid amount (",
451
+ currency.symbol,
452
+ ")"
453
+ ] }),
454
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
455
+ "input",
456
+ {
457
+ id: "beacon-amount",
458
+ style: styles.input,
459
+ value: amount,
460
+ onChange: (event) => setAmount(event.target.value),
461
+ placeholder: "0.0",
462
+ inputMode: "decimal"
463
+ }
464
+ )
465
+ ] }),
466
+ wallet === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
467
+ "button",
468
+ {
469
+ type: "button",
470
+ style: { ...styles.button, ...status !== "live" ? styles.buttonDisabled : {} },
471
+ disabled: status !== "live" || !hasInjectedWallet(),
472
+ onClick: () => void onConnect(),
473
+ children: hasInjectedWallet() ? "Connect wallet" : "No browser wallet detected"
474
+ }
475
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
476
+ "button",
477
+ {
478
+ type: "button",
479
+ style: { ...styles.button, ...bidDisabled ? styles.buttonDisabled : {} },
480
+ disabled: bidDisabled,
481
+ onClick: () => void onSubmitBid(),
482
+ title: blocker ?? void 0,
483
+ children: tx.kind === "working" ? tx.message : "Submit bid"
484
+ }
485
+ ),
486
+ wallet ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.muted, children: [
487
+ "Connected ",
488
+ wallet.address.slice(0, 6),
489
+ "\u2026",
490
+ wallet.address.slice(-4)
491
+ ] }) : null,
492
+ parsed.parseError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.error, children: parsed.parseError }) : null,
493
+ blocker && status === "live" && !parsed.parseError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.muted, children: blocker }) : null,
494
+ tx.kind === "error" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.error, children: tx.message }) : null,
495
+ tx.kind === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.success, children: [
496
+ "Bid submitted.",
497
+ " ",
498
+ explorer ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
499
+ "a",
500
+ {
501
+ href: `${explorer}/tx/${tx.hash}`,
502
+ target: "_blank",
503
+ rel: "noreferrer",
504
+ style: styles.link,
505
+ children: "View transaction"
506
+ }
507
+ ) : tx.hash
508
+ ] }) : null
509
+ ] }),
510
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.footer, children: [
511
+ "Powered by ",
512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Beacon" }),
513
+ " \xB7 Uniswap CCA"
514
+ ] })
515
+ ] });
516
+ }
517
+ function Row({ label, value, hint }) {
518
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.row, children: [
519
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { style: styles.label, title: hint, children: label }),
520
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { style: styles.value, children: value })
521
+ ] });
522
+ }
523
+ function StatusPill({ status, isGraduated }) {
524
+ const palette = status === "live" ? styles.pillLive : status === "pending" ? styles.pillPending : styles.pillEnded;
525
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.pillRow, children: [
526
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...styles.pill, ...palette }, children: status === "live" ? "Live" : status === "pending" ? "Upcoming" : "Ended" }),
527
+ isGraduated ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...styles.pill, ...styles.pillGraduated }, children: "Graduated" }) : null
528
+ ] });
529
+ }
530
+ function formatMinutes(seconds) {
531
+ if (seconds <= 0) return "0m";
532
+ const hours = Math.floor(seconds / 3600);
533
+ const minutes = Math.round(seconds % 3600 / 60);
534
+ return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
535
+ }
536
+
537
+ // src/mount.ts
538
+ var import_react3 = require("react");
539
+ var import_client = require("react-dom/client");
540
+ var roots = /* @__PURE__ */ new WeakMap();
541
+ function mount(target, props) {
542
+ const element = typeof target === "string" ? document.querySelector(target) : target;
543
+ if (!element) {
544
+ throw new Error(`Beacon widget: no element matched ${String(target)}`);
545
+ }
546
+ let root = roots.get(element);
547
+ if (!root) {
548
+ root = (0, import_client.createRoot)(element);
549
+ roots.set(element, root);
550
+ }
551
+ let current = props;
552
+ root.render((0, import_react3.createElement)(AuctionCard, current));
553
+ return {
554
+ update(next) {
555
+ current = { ...current, ...next };
556
+ root.render((0, import_react3.createElement)(AuctionCard, current));
557
+ },
558
+ unmount() {
559
+ root.unmount();
560
+ roots.delete(element);
561
+ }
562
+ };
563
+ }
564
+ // Annotate the CommonJS export names for ESM import in node:
565
+ 0 && (module.exports = {
566
+ AuctionCard,
567
+ connectWallet,
568
+ getInjectedProvider,
569
+ hasInjectedWallet,
570
+ mount,
571
+ useAuction
572
+ });
573
+ //# sourceMappingURL=index.cjs.map