@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/dist/index.js ADDED
@@ -0,0 +1,559 @@
1
+ "use client";
2
+
3
+ // src/AuctionCard.tsx
4
+ import { useMemo, useState as useState2 } from "react";
5
+ import {
6
+ alignPriceToTickBoundary,
7
+ buildBidTransactions,
8
+ checkBidReadiness,
9
+ decimalToQ96,
10
+ describeBidBlocker,
11
+ getPrevTickHint,
12
+ formatQ96,
13
+ formatQ96Compact,
14
+ formatTokenAmount,
15
+ mpsToFraction
16
+ } from "@hammerock/cca-sdk";
17
+
18
+ // src/useAuction.ts
19
+ import { useCallback, useEffect, useRef, useState } from "react";
20
+ import {
21
+ createBeaconClient,
22
+ deriveStatus,
23
+ getAuctionInfo,
24
+ getAuctionState,
25
+ getTokenMetadata
26
+ } from "@hammerock/cca-sdk";
27
+ function useAuction(options) {
28
+ const { auction, rpcUrl, chainId, lensAddress, pollIntervalMs = 12e3 } = options;
29
+ const [data, setData] = useState(null);
30
+ const [error, setError] = useState(null);
31
+ const [loading, setLoading] = useState(true);
32
+ const [nonce, setNonce] = useState(0);
33
+ const staticRef = useRef({});
34
+ const endedAtRef = useRef(false);
35
+ const clientRef = useRef(null);
36
+ if (!clientRef.current) {
37
+ try {
38
+ clientRef.current = createBeaconClient({ rpcUrl, chainId, lensAddress });
39
+ } catch {
40
+ clientRef.current = null;
41
+ }
42
+ }
43
+ const refresh = useCallback(() => setNonce((value) => value + 1), []);
44
+ useEffect(() => {
45
+ const client = clientRef.current;
46
+ if (!client) {
47
+ setError(new Error(`Unsupported or non-testnet chain id ${chainId}.`));
48
+ setLoading(false);
49
+ return;
50
+ }
51
+ let cancelled = false;
52
+ async function load(isInitial) {
53
+ try {
54
+ if (isInitial) setLoading(true);
55
+ const [info, state, headBlock] = await Promise.all([
56
+ // Config is immutable, so only read it once.
57
+ staticRef.current.info ?? getAuctionInfo(client, auction),
58
+ getAuctionState(client, auction),
59
+ client.publicClient.getBlockNumber()
60
+ ]);
61
+ const [token, currency] = await Promise.all([
62
+ staticRef.current.token ?? getTokenMetadata(client, info.token),
63
+ staticRef.current.currency ?? getTokenMetadata(client, info.currency)
64
+ ]);
65
+ staticRef.current = { info, token, currency };
66
+ if (cancelled) return;
67
+ const status = deriveStatus(info, headBlock);
68
+ endedAtRef.current = status === "ended";
69
+ const blocksRemaining = status === "pending" ? info.startBlock - headBlock : status === "live" ? info.endBlock - headBlock : 0n;
70
+ setData({
71
+ info,
72
+ state,
73
+ token,
74
+ currency,
75
+ status,
76
+ headBlock,
77
+ blocksRemaining,
78
+ secondsRemaining: Number(blocksRemaining) * client.blockTimeSeconds
79
+ });
80
+ setError(null);
81
+ } catch (cause) {
82
+ if (!cancelled) setError(cause);
83
+ } finally {
84
+ if (!cancelled) setLoading(false);
85
+ }
86
+ }
87
+ void load(true);
88
+ const timer = setInterval(() => {
89
+ if (staticRef.current.info && endedAtRef.current) {
90
+ clearInterval(timer);
91
+ return;
92
+ }
93
+ void load(false);
94
+ }, pollIntervalMs);
95
+ return () => {
96
+ cancelled = true;
97
+ clearInterval(timer);
98
+ };
99
+ }, [auction, chainId, pollIntervalMs, nonce]);
100
+ return { data, error, loading, client: clientRef.current, refresh };
101
+ }
102
+
103
+ // src/wallet.ts
104
+ import { createWalletClient, custom } from "viem";
105
+ function getInjectedProvider() {
106
+ return typeof window === "undefined" ? void 0 : window.ethereum;
107
+ }
108
+ function hasInjectedWallet() {
109
+ return getInjectedProvider() !== void 0;
110
+ }
111
+ async function connectWallet(chain) {
112
+ const provider = getInjectedProvider();
113
+ if (!provider) {
114
+ throw new Error("No browser wallet found. Install MetaMask or another EIP-1193 wallet.");
115
+ }
116
+ const accounts = await provider.request({ method: "eth_requestAccounts" });
117
+ const address = accounts[0];
118
+ if (!address) throw new Error("Wallet returned no accounts.");
119
+ const currentChainId = Number(
120
+ await provider.request({ method: "eth_chainId" })
121
+ );
122
+ if (currentChainId !== chain.id) {
123
+ await switchChain(provider, chain);
124
+ }
125
+ const walletClient = createWalletClient({ account: address, chain, transport: custom(provider) });
126
+ return { address, walletClient, chainId: chain.id };
127
+ }
128
+ async function switchChain(provider, chain) {
129
+ const chainIdHex = `0x${chain.id.toString(16)}`;
130
+ try {
131
+ await provider.request({
132
+ method: "wallet_switchEthereumChain",
133
+ params: [{ chainId: chainIdHex }]
134
+ });
135
+ } catch (error) {
136
+ const code = error.code;
137
+ if (code !== 4902) throw error;
138
+ await provider.request({
139
+ method: "wallet_addEthereumChain",
140
+ params: [
141
+ {
142
+ chainId: chainIdHex,
143
+ chainName: chain.name,
144
+ nativeCurrency: chain.nativeCurrency,
145
+ rpcUrls: [chain.rpcUrls.default.http[0]],
146
+ blockExplorerUrls: chain.blockExplorers?.default?.url ? [chain.blockExplorers.default.url] : void 0
147
+ }
148
+ ]
149
+ });
150
+ }
151
+ }
152
+
153
+ // src/styles.ts
154
+ var border = "#2a2a2e";
155
+ var surface = "#141417";
156
+ var text = "#f4f4f5";
157
+ var muted = "#8b8b93";
158
+ var styles = {
159
+ card: {
160
+ boxSizing: "border-box",
161
+ width: "100%",
162
+ maxWidth: 360,
163
+ padding: 16,
164
+ borderRadius: 12,
165
+ border: `1px solid ${border}`,
166
+ background: surface,
167
+ color: text,
168
+ fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
169
+ fontSize: 14,
170
+ lineHeight: 1.45
171
+ },
172
+ headerRow: {
173
+ display: "flex",
174
+ alignItems: "flex-start",
175
+ justifyContent: "space-between",
176
+ gap: 8
177
+ },
178
+ symbol: { fontSize: 18, fontWeight: 600 },
179
+ pillRow: { display: "flex", flexWrap: "wrap", gap: 4, justifyContent: "flex-end" },
180
+ pill: {
181
+ display: "inline-block",
182
+ padding: "2px 8px",
183
+ borderRadius: 999,
184
+ fontSize: 11,
185
+ fontWeight: 500,
186
+ whiteSpace: "nowrap"
187
+ },
188
+ pillLive: { background: "rgba(16,185,129,0.15)", color: "#6ee7b7" },
189
+ pillPending: { background: "rgba(14,165,233,0.15)", color: "#7dd3fc" },
190
+ pillEnded: { background: "rgba(113,113,122,0.18)", color: "#c4c4cc" },
191
+ pillGraduated: { background: "rgba(139,92,246,0.18)", color: "#c4b5fd" },
192
+ priceBlock: { marginTop: 14 },
193
+ price: { fontSize: 22, fontWeight: 600, fontVariantNumeric: "tabular-nums", wordBreak: "break-all" },
194
+ priceUnit: { fontSize: 12, fontWeight: 400, color: muted },
195
+ grid: { margin: "14px 0 0", display: "grid", gap: 8 },
196
+ row: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 12, margin: 0 },
197
+ label: { fontSize: 11, textTransform: "uppercase", letterSpacing: "0.04em", color: muted, margin: 0 },
198
+ value: { margin: 0, fontSize: 13, textAlign: "right", fontVariantNumeric: "tabular-nums" },
199
+ form: { marginTop: 16, display: "grid", gap: 6, borderTop: `1px solid ${border}`, paddingTop: 14 },
200
+ input: {
201
+ boxSizing: "border-box",
202
+ width: "100%",
203
+ padding: "8px 10px",
204
+ borderRadius: 8,
205
+ border: `1px solid ${border}`,
206
+ background: "#0d0d0f",
207
+ color: text,
208
+ fontSize: 14,
209
+ outline: "none"
210
+ },
211
+ button: {
212
+ marginTop: 6,
213
+ padding: "10px 12px",
214
+ borderRadius: 8,
215
+ border: "none",
216
+ background: text,
217
+ color: "#0d0d0f",
218
+ fontSize: 14,
219
+ fontWeight: 600,
220
+ cursor: "pointer"
221
+ },
222
+ buttonDisabled: {
223
+ background: "#2a2a2e",
224
+ color: muted,
225
+ cursor: "not-allowed"
226
+ },
227
+ notice: {
228
+ padding: "8px 10px",
229
+ borderRadius: 8,
230
+ background: "rgba(113,113,122,0.12)",
231
+ color: muted,
232
+ fontSize: 12
233
+ },
234
+ muted: { color: muted, fontSize: 12 },
235
+ error: { color: "#fca5a5", fontSize: 12, wordBreak: "break-word" },
236
+ success: { color: "#6ee7b7", fontSize: 12, wordBreak: "break-word" },
237
+ link: { color: "#7dd3fc" },
238
+ footer: {
239
+ marginTop: 14,
240
+ paddingTop: 10,
241
+ borderTop: `1px solid ${border}`,
242
+ color: muted,
243
+ fontSize: 11,
244
+ textAlign: "center"
245
+ }
246
+ };
247
+
248
+ // src/AuctionCard.tsx
249
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
250
+ function AuctionCard(props) {
251
+ const { data, error, loading, client, refresh } = useAuction(props);
252
+ const [wallet, setWallet] = useState2(null);
253
+ const [maxPrice, setMaxPrice] = useState2("");
254
+ const [amount, setAmount] = useState2("");
255
+ const [tx, setTx] = useState2({ kind: "idle" });
256
+ const parsed = useMemo(() => {
257
+ let maxPriceQ96 = 0n;
258
+ let amountUnits = 0n;
259
+ let parseError = null;
260
+ try {
261
+ if (maxPrice.trim()) maxPriceQ96 = decimalToQ96(maxPrice.trim());
262
+ } catch {
263
+ parseError = "Maximum price must be a decimal number.";
264
+ }
265
+ if (amount.trim() && data) {
266
+ const decimals = data.currency.decimals;
267
+ if (!/^\d+(\.\d+)?$/.test(amount.trim())) {
268
+ parseError = "Bid amount must be a decimal number.";
269
+ } else {
270
+ const [whole = "0", frac = ""] = amount.trim().split(".");
271
+ const padded = (frac + "0".repeat(decimals)).slice(0, decimals);
272
+ amountUnits = BigInt(whole) * 10n ** BigInt(decimals) + BigInt(padded || "0");
273
+ }
274
+ }
275
+ return { maxPriceQ96, amountUnits, parseError };
276
+ }, [maxPrice, amount, data]);
277
+ if (loading && !data) {
278
+ return /* @__PURE__ */ jsx("div", { style: styles.card, children: /* @__PURE__ */ jsx("div", { style: styles.muted, children: "Loading auction\u2026" }) });
279
+ }
280
+ if (error && !data) {
281
+ return /* @__PURE__ */ jsxs("div", { style: styles.card, children: [
282
+ /* @__PURE__ */ jsx("div", { style: styles.error, children: "Could not load auction" }),
283
+ /* @__PURE__ */ jsx("div", { style: styles.muted, children: error.message.split("\n")[0] })
284
+ ] });
285
+ }
286
+ if (!data) return null;
287
+ const { info, state, token, currency, status } = data;
288
+ const hasCleared = state.totalCleared > 0n;
289
+ const schedulePercent = (mpsToFraction(state.checkpoint.cumulativeMps) * 100).toFixed(0);
290
+ const blocker = describeBidBlocker({
291
+ status,
292
+ amount: parsed.amountUnits,
293
+ maxPriceQ96: parsed.maxPriceQ96,
294
+ floorPriceQ96: info.floorPriceQ96,
295
+ tickSpacing: info.tickSpacing
296
+ });
297
+ const alignedPriceQ96 = parsed.maxPriceQ96 > 0n ? alignPriceToTickBoundary(parsed.maxPriceQ96, info.tickSpacing) : 0n;
298
+ const priceWasSnapped = alignedPriceQ96 > 0n && alignedPriceQ96 !== parsed.maxPriceQ96;
299
+ const bidDisabled = props.readOnly || status !== "live" || tx.kind === "working" || parsed.parseError !== null || blocker !== null;
300
+ async function onConnect() {
301
+ if (!client) return;
302
+ setTx({ kind: "working", message: "Connecting wallet\u2026" });
303
+ try {
304
+ setWallet(await connectWallet(client.chain));
305
+ setTx({ kind: "idle" });
306
+ } catch (cause) {
307
+ setTx({ kind: "error", message: cause.message });
308
+ }
309
+ }
310
+ async function onSubmitBid() {
311
+ if (!client || !wallet || !data) return;
312
+ try {
313
+ setTx({ kind: "working", message: "Checking balance\u2026" });
314
+ const readiness = await checkBidReadiness(client, {
315
+ auction: props.auction,
316
+ currency: data.info.currency,
317
+ bidder: wallet.address,
318
+ amount: parsed.amountUnits
319
+ });
320
+ if (!readiness.hasBalance) {
321
+ setTx({
322
+ kind: "error",
323
+ message: `Insufficient ${data.currency.symbol}. You hold ${formatTokenAmount(readiness.balance, data.currency)}.`
324
+ });
325
+ return;
326
+ }
327
+ const prevTickPriceQ96 = await getPrevTickHint(client, props.auction, parsed.maxPriceQ96);
328
+ const steps = buildBidTransactions({
329
+ auction: props.auction,
330
+ currency: data.info.currency,
331
+ maxPriceQ96: parsed.maxPriceQ96,
332
+ amount: parsed.amountUnits,
333
+ owner: wallet.address,
334
+ readiness,
335
+ prevTickPriceQ96,
336
+ // Snapped DOWN to a tick boundary; the contract rejects any other price.
337
+ tickSpacing: data.info.tickSpacing
338
+ });
339
+ let hash = "";
340
+ for (const [index, step] of steps.entries()) {
341
+ setTx({ kind: "working", message: `${step.label} (${index + 1}/${steps.length})\u2026` });
342
+ hash = await wallet.walletClient.sendTransaction({
343
+ account: wallet.address,
344
+ chain: client.chain,
345
+ to: step.request.to,
346
+ data: step.request.data,
347
+ value: step.request.value
348
+ });
349
+ if (index < steps.length - 1) {
350
+ await client.publicClient.waitForTransactionReceipt({ hash });
351
+ }
352
+ }
353
+ setTx({ kind: "sent", hash });
354
+ refresh();
355
+ } catch (cause) {
356
+ const message = cause.message.split("\n")[0] ?? "Transaction failed.";
357
+ setTx({ kind: "error", message });
358
+ }
359
+ }
360
+ const explorer = client?.chain.blockExplorers?.default?.url;
361
+ return /* @__PURE__ */ jsxs("div", { style: styles.card, children: [
362
+ /* @__PURE__ */ jsxs("div", { style: styles.headerRow, children: [
363
+ /* @__PURE__ */ jsxs("div", { children: [
364
+ /* @__PURE__ */ jsx("div", { style: styles.symbol, children: token.symbol }),
365
+ /* @__PURE__ */ jsx("div", { style: styles.muted, children: token.name })
366
+ ] }),
367
+ /* @__PURE__ */ jsx(StatusPill, { status, isGraduated: state.isGraduated })
368
+ ] }),
369
+ /* @__PURE__ */ jsxs("div", { style: styles.priceBlock, children: [
370
+ /* @__PURE__ */ jsx("div", { style: styles.label, children: "Clearing price" }),
371
+ /* @__PURE__ */ jsxs("div", { style: styles.price, children: [
372
+ formatQ96Compact(state.checkpoint.clearingPriceQ96),
373
+ " ",
374
+ /* @__PURE__ */ jsxs("span", { style: styles.priceUnit, children: [
375
+ currency.symbol,
376
+ " / ",
377
+ token.symbol
378
+ ] })
379
+ ] })
380
+ ] }),
381
+ /* @__PURE__ */ jsxs("dl", { style: styles.grid, children: [
382
+ /* @__PURE__ */ jsx(Row, { label: "Raised", value: formatTokenAmount(state.currencyRaised, currency) }),
383
+ /* @__PURE__ */ jsx(
384
+ Row,
385
+ {
386
+ label: "Cleared",
387
+ value: hasCleared ? `${formatTokenAmount(state.totalCleared, token)} of ${formatTokenAmount(info.totalSupply, token)}` : "Nothing cleared"
388
+ }
389
+ ),
390
+ /* @__PURE__ */ jsx(
391
+ Row,
392
+ {
393
+ label: "Supply schedule",
394
+ value: `${schedulePercent}% released`,
395
+ hint: "Schedule progress, not the amount sold."
396
+ }
397
+ ),
398
+ /* @__PURE__ */ jsx(
399
+ Row,
400
+ {
401
+ label: status === "pending" ? "Starts in" : status === "live" ? "Time left" : "Closed",
402
+ value: status === "ended" ? `block ${info.endBlock.toString()}` : `~${formatMinutes(data.secondsRemaining)} (${data.blocksRemaining.toString()} blocks)`
403
+ }
404
+ )
405
+ ] }),
406
+ props.readOnly ? null : /* @__PURE__ */ jsxs("div", { style: styles.form, children: [
407
+ status !== "live" ? (
408
+ // Say why, rather than showing a button that would revert.
409
+ /* @__PURE__ */ jsx("div", { style: styles.notice, children: status === "ended" ? "This auction has ended. Bidding is closed." : "This auction has not opened yet." })
410
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
411
+ /* @__PURE__ */ jsxs("label", { style: styles.label, htmlFor: "beacon-max-price", children: [
412
+ "Max price (",
413
+ currency.symbol,
414
+ " per ",
415
+ token.symbol,
416
+ ")"
417
+ ] }),
418
+ /* @__PURE__ */ jsx(
419
+ "input",
420
+ {
421
+ id: "beacon-max-price",
422
+ style: styles.input,
423
+ value: maxPrice,
424
+ onChange: (event) => setMaxPrice(event.target.value),
425
+ placeholder: formatQ96(info.floorPriceQ96),
426
+ inputMode: "decimal"
427
+ }
428
+ ),
429
+ priceWasSnapped ? /* @__PURE__ */ jsxs("div", { style: styles.muted, children: [
430
+ "Will bid at ",
431
+ formatQ96Compact(alignedPriceQ96),
432
+ " ",
433
+ currency.symbol,
434
+ " \u2014 prices snap down to the auction's tick spacing."
435
+ ] }) : null,
436
+ /* @__PURE__ */ jsxs("label", { style: styles.label, htmlFor: "beacon-amount", children: [
437
+ "Bid amount (",
438
+ currency.symbol,
439
+ ")"
440
+ ] }),
441
+ /* @__PURE__ */ jsx(
442
+ "input",
443
+ {
444
+ id: "beacon-amount",
445
+ style: styles.input,
446
+ value: amount,
447
+ onChange: (event) => setAmount(event.target.value),
448
+ placeholder: "0.0",
449
+ inputMode: "decimal"
450
+ }
451
+ )
452
+ ] }),
453
+ wallet === null ? /* @__PURE__ */ jsx(
454
+ "button",
455
+ {
456
+ type: "button",
457
+ style: { ...styles.button, ...status !== "live" ? styles.buttonDisabled : {} },
458
+ disabled: status !== "live" || !hasInjectedWallet(),
459
+ onClick: () => void onConnect(),
460
+ children: hasInjectedWallet() ? "Connect wallet" : "No browser wallet detected"
461
+ }
462
+ ) : /* @__PURE__ */ jsx(
463
+ "button",
464
+ {
465
+ type: "button",
466
+ style: { ...styles.button, ...bidDisabled ? styles.buttonDisabled : {} },
467
+ disabled: bidDisabled,
468
+ onClick: () => void onSubmitBid(),
469
+ title: blocker ?? void 0,
470
+ children: tx.kind === "working" ? tx.message : "Submit bid"
471
+ }
472
+ ),
473
+ wallet ? /* @__PURE__ */ jsxs("div", { style: styles.muted, children: [
474
+ "Connected ",
475
+ wallet.address.slice(0, 6),
476
+ "\u2026",
477
+ wallet.address.slice(-4)
478
+ ] }) : null,
479
+ parsed.parseError ? /* @__PURE__ */ jsx("div", { style: styles.error, children: parsed.parseError }) : null,
480
+ blocker && status === "live" && !parsed.parseError ? /* @__PURE__ */ jsx("div", { style: styles.muted, children: blocker }) : null,
481
+ tx.kind === "error" ? /* @__PURE__ */ jsx("div", { style: styles.error, children: tx.message }) : null,
482
+ tx.kind === "sent" ? /* @__PURE__ */ jsxs("div", { style: styles.success, children: [
483
+ "Bid submitted.",
484
+ " ",
485
+ explorer ? /* @__PURE__ */ jsx(
486
+ "a",
487
+ {
488
+ href: `${explorer}/tx/${tx.hash}`,
489
+ target: "_blank",
490
+ rel: "noreferrer",
491
+ style: styles.link,
492
+ children: "View transaction"
493
+ }
494
+ ) : tx.hash
495
+ ] }) : null
496
+ ] }),
497
+ /* @__PURE__ */ jsxs("div", { style: styles.footer, children: [
498
+ "Powered by ",
499
+ /* @__PURE__ */ jsx("strong", { children: "Beacon" }),
500
+ " \xB7 Uniswap CCA"
501
+ ] })
502
+ ] });
503
+ }
504
+ function Row({ label, value, hint }) {
505
+ return /* @__PURE__ */ jsxs("div", { style: styles.row, children: [
506
+ /* @__PURE__ */ jsx("dt", { style: styles.label, title: hint, children: label }),
507
+ /* @__PURE__ */ jsx("dd", { style: styles.value, children: value })
508
+ ] });
509
+ }
510
+ function StatusPill({ status, isGraduated }) {
511
+ const palette = status === "live" ? styles.pillLive : status === "pending" ? styles.pillPending : styles.pillEnded;
512
+ return /* @__PURE__ */ jsxs("div", { style: styles.pillRow, children: [
513
+ /* @__PURE__ */ jsx("span", { style: { ...styles.pill, ...palette }, children: status === "live" ? "Live" : status === "pending" ? "Upcoming" : "Ended" }),
514
+ isGraduated ? /* @__PURE__ */ jsx("span", { style: { ...styles.pill, ...styles.pillGraduated }, children: "Graduated" }) : null
515
+ ] });
516
+ }
517
+ function formatMinutes(seconds) {
518
+ if (seconds <= 0) return "0m";
519
+ const hours = Math.floor(seconds / 3600);
520
+ const minutes = Math.round(seconds % 3600 / 60);
521
+ return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
522
+ }
523
+
524
+ // src/mount.ts
525
+ import { createElement } from "react";
526
+ import { createRoot } from "react-dom/client";
527
+ var roots = /* @__PURE__ */ new WeakMap();
528
+ function mount(target, props) {
529
+ const element = typeof target === "string" ? document.querySelector(target) : target;
530
+ if (!element) {
531
+ throw new Error(`Beacon widget: no element matched ${String(target)}`);
532
+ }
533
+ let root = roots.get(element);
534
+ if (!root) {
535
+ root = createRoot(element);
536
+ roots.set(element, root);
537
+ }
538
+ let current = props;
539
+ root.render(createElement(AuctionCard, current));
540
+ return {
541
+ update(next) {
542
+ current = { ...current, ...next };
543
+ root.render(createElement(AuctionCard, current));
544
+ },
545
+ unmount() {
546
+ root.unmount();
547
+ roots.delete(element);
548
+ }
549
+ };
550
+ }
551
+ export {
552
+ AuctionCard,
553
+ connectWallet,
554
+ getInjectedProvider,
555
+ hasInjectedWallet,
556
+ mount,
557
+ useAuction
558
+ };
559
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/AuctionCard.tsx","../src/useAuction.ts","../src/wallet.ts","../src/styles.ts","../src/mount.ts"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport {\n alignPriceToTickBoundary,\n buildBidTransactions,\n checkBidReadiness,\n decimalToQ96,\n describeBidBlocker,\n getPrevTickHint,\n formatQ96,\n formatQ96Compact,\n formatTokenAmount,\n mpsToFraction,\n} from \"@hammerock/cca-sdk\";\nimport type { Address } from \"@hammerock/cca-sdk\";\nimport { useAuction } from \"./useAuction\";\nimport { connectWallet, hasInjectedWallet } from \"./wallet\";\nimport type { ConnectedWallet } from \"./wallet\";\nimport { styles } from \"./styles\";\n\nexport interface AuctionCardProps {\n /** Address of the ContinuousClearingAuction to display. */\n auction: Address;\n /** Public RPC the widget reads through. Never put a keyed URL here - the\n * widget runs in the visitor's browser. */\n rpcUrl: string;\n /** Chain id the auction lives on. Must be a supported testnet. */\n chainId: number;\n /** Optional deployed AuctionStateLens. Omit to use the eth_call override. */\n lensAddress?: Address;\n /** How often to re-read live state, in ms. */\n pollIntervalMs?: number;\n /** Hide the bid form and render read-only. */\n readOnly?: boolean;\n}\n\ntype TxState =\n | { kind: \"idle\" }\n | { kind: \"working\"; message: string }\n | { kind: \"sent\"; hash: string }\n | { kind: \"error\"; message: string };\n\nexport function AuctionCard(props: AuctionCardProps) {\n const { data, error, loading, client, refresh } = useAuction(props);\n\n const [wallet, setWallet] = useState<ConnectedWallet | null>(null);\n const [maxPrice, setMaxPrice] = useState(\"\");\n const [amount, setAmount] = useState(\"\");\n const [tx, setTx] = useState<TxState>({ kind: \"idle\" });\n\n const parsed = useMemo(() => {\n let maxPriceQ96 = 0n;\n let amountUnits = 0n;\n let parseError: string | null = null;\n\n try {\n if (maxPrice.trim()) maxPriceQ96 = decimalToQ96(maxPrice.trim());\n } catch {\n parseError = \"Maximum price must be a decimal number.\";\n }\n\n if (amount.trim() && data) {\n const decimals = data.currency.decimals;\n if (!/^\\d+(\\.\\d+)?$/.test(amount.trim())) {\n parseError = \"Bid amount must be a decimal number.\";\n } else {\n const [whole = \"0\", frac = \"\"] = amount.trim().split(\".\");\n const padded = (frac + \"0\".repeat(decimals)).slice(0, decimals);\n amountUnits = BigInt(whole) * 10n ** BigInt(decimals) + BigInt(padded || \"0\");\n }\n }\n\n return { maxPriceQ96, amountUnits, parseError };\n }, [maxPrice, amount, data]);\n\n if (loading && !data) {\n return (\n <div style={styles.card}>\n <div style={styles.muted}>Loading auction…</div>\n </div>\n );\n }\n\n if (error && !data) {\n return (\n <div style={styles.card}>\n <div style={styles.error}>Could not load auction</div>\n <div style={styles.muted}>{error.message.split(\"\\n\")[0]}</div>\n </div>\n );\n }\n\n if (!data) return null;\n\n const { info, state, token, currency, status } = data;\n const hasCleared = state.totalCleared > 0n;\n const schedulePercent = (mpsToFraction(state.checkpoint.cumulativeMps) * 100).toFixed(0);\n\n // The single source of truth for whether bidding is possible. An ended\n // auction disables the button with a stated reason rather than failing on\n // submit, which is the common way these embeds break.\n const blocker = describeBidBlocker({\n status,\n amount: parsed.amountUnits,\n maxPriceQ96: parsed.maxPriceQ96,\n floorPriceQ96: info.floorPriceQ96,\n tickSpacing: info.tickSpacing,\n });\n\n // The contract only accepts prices on a tick boundary, so show the bidder the\n // price that will actually be submitted rather than surprising them later.\n const alignedPriceQ96 =\n parsed.maxPriceQ96 > 0n\n ? alignPriceToTickBoundary(parsed.maxPriceQ96, info.tickSpacing)\n : 0n;\n const priceWasSnapped = alignedPriceQ96 > 0n && alignedPriceQ96 !== parsed.maxPriceQ96;\n\n const bidDisabled =\n props.readOnly ||\n status !== \"live\" ||\n tx.kind === \"working\" ||\n parsed.parseError !== null ||\n blocker !== null;\n\n async function onConnect() {\n if (!client) return;\n setTx({ kind: \"working\", message: \"Connecting wallet…\" });\n try {\n setWallet(await connectWallet(client.chain));\n setTx({ kind: \"idle\" });\n } catch (cause) {\n setTx({ kind: \"error\", message: (cause as Error).message });\n }\n }\n\n async function onSubmitBid() {\n if (!client || !wallet || !data) return;\n\n try {\n setTx({ kind: \"working\", message: \"Checking balance…\" });\n\n const readiness = await checkBidReadiness(client, {\n auction: props.auction,\n currency: data.info.currency,\n bidder: wallet.address,\n amount: parsed.amountUnits,\n });\n\n if (!readiness.hasBalance) {\n setTx({\n kind: \"error\",\n message: `Insufficient ${data.currency.symbol}. You hold ${formatTokenAmount(readiness.balance, data.currency)}.`,\n });\n return;\n }\n\n // ERC-20 bids go through Permit2, so there may be two approvals before\n // the bid itself. buildBidTransactions returns exactly the steps needed.\n const prevTickPriceQ96 = await getPrevTickHint(client, props.auction, parsed.maxPriceQ96);\n\n const steps = buildBidTransactions({\n auction: props.auction,\n currency: data.info.currency,\n maxPriceQ96: parsed.maxPriceQ96,\n amount: parsed.amountUnits,\n owner: wallet.address,\n readiness,\n prevTickPriceQ96,\n // Snapped DOWN to a tick boundary; the contract rejects any other price.\n tickSpacing: data.info.tickSpacing,\n });\n\n let hash = \"\" as `0x${string}`;\n for (const [index, step] of steps.entries()) {\n setTx({ kind: \"working\", message: `${step.label} (${index + 1}/${steps.length})…` });\n hash = await wallet.walletClient.sendTransaction({\n account: wallet.address,\n chain: client.chain,\n to: step.request.to,\n data: step.request.data,\n value: step.request.value,\n });\n // Approvals must land before the next step can read them.\n if (index < steps.length - 1) {\n await client.publicClient.waitForTransactionReceipt({ hash });\n }\n }\n\n setTx({ kind: \"sent\", hash });\n refresh();\n } catch (cause) {\n const message = (cause as Error).message.split(\"\\n\")[0] ?? \"Transaction failed.\";\n setTx({ kind: \"error\", message });\n }\n }\n\n const explorer = client?.chain.blockExplorers?.default?.url;\n\n return (\n <div style={styles.card}>\n <div style={styles.headerRow}>\n <div>\n <div style={styles.symbol}>{token.symbol}</div>\n <div style={styles.muted}>{token.name}</div>\n </div>\n <StatusPill status={status} isGraduated={state.isGraduated} />\n </div>\n\n <div style={styles.priceBlock}>\n <div style={styles.label}>Clearing price</div>\n <div style={styles.price}>\n {formatQ96Compact(state.checkpoint.clearingPriceQ96)}{\" \"}\n <span style={styles.priceUnit}>\n {currency.symbol} / {token.symbol}\n </span>\n </div>\n </div>\n\n <dl style={styles.grid}>\n <Row label=\"Raised\" value={formatTokenAmount(state.currencyRaised, currency)} />\n <Row\n label=\"Cleared\"\n value={\n hasCleared\n ? `${formatTokenAmount(state.totalCleared, token)} of ${formatTokenAmount(info.totalSupply, token)}`\n : \"Nothing cleared\"\n }\n />\n <Row\n label=\"Supply schedule\"\n value={`${schedulePercent}% released`}\n hint=\"Schedule progress, not the amount sold.\"\n />\n <Row\n label={status === \"pending\" ? \"Starts in\" : status === \"live\" ? \"Time left\" : \"Closed\"}\n value={\n status === \"ended\"\n ? `block ${info.endBlock.toString()}`\n : `~${formatMinutes(data.secondsRemaining)} (${data.blocksRemaining.toString()} blocks)`\n }\n />\n </dl>\n\n {props.readOnly ? null : (\n <div style={styles.form}>\n {status !== \"live\" ? (\n // Say why, rather than showing a button that would revert.\n <div style={styles.notice}>\n {status === \"ended\"\n ? \"This auction has ended. Bidding is closed.\"\n : \"This auction has not opened yet.\"}\n </div>\n ) : (\n <>\n <label style={styles.label} htmlFor=\"beacon-max-price\">\n Max price ({currency.symbol} per {token.symbol})\n </label>\n <input\n id=\"beacon-max-price\"\n style={styles.input}\n value={maxPrice}\n onChange={(event) => setMaxPrice(event.target.value)}\n placeholder={formatQ96(info.floorPriceQ96)}\n inputMode=\"decimal\"\n />\n\n {priceWasSnapped ? (\n <div style={styles.muted}>\n Will bid at {formatQ96Compact(alignedPriceQ96)} {currency.symbol} — prices snap\n down to the auction&apos;s tick spacing.\n </div>\n ) : null}\n\n <label style={styles.label} htmlFor=\"beacon-amount\">\n Bid amount ({currency.symbol})\n </label>\n <input\n id=\"beacon-amount\"\n style={styles.input}\n value={amount}\n onChange={(event) => setAmount(event.target.value)}\n placeholder=\"0.0\"\n inputMode=\"decimal\"\n />\n </>\n )}\n\n {wallet === null ? (\n <button\n type=\"button\"\n style={{ ...styles.button, ...(status !== \"live\" ? styles.buttonDisabled : {}) }}\n disabled={status !== \"live\" || !hasInjectedWallet()}\n onClick={() => void onConnect()}\n >\n {hasInjectedWallet() ? \"Connect wallet\" : \"No browser wallet detected\"}\n </button>\n ) : (\n <button\n type=\"button\"\n style={{ ...styles.button, ...(bidDisabled ? styles.buttonDisabled : {}) }}\n disabled={bidDisabled}\n onClick={() => void onSubmitBid()}\n title={blocker ?? undefined}\n >\n {tx.kind === \"working\" ? tx.message : \"Submit bid\"}\n </button>\n )}\n\n {wallet ? (\n <div style={styles.muted}>\n Connected {wallet.address.slice(0, 6)}…{wallet.address.slice(-4)}\n </div>\n ) : null}\n\n {parsed.parseError ? <div style={styles.error}>{parsed.parseError}</div> : null}\n {blocker && status === \"live\" && !parsed.parseError ? (\n <div style={styles.muted}>{blocker}</div>\n ) : null}\n\n {tx.kind === \"error\" ? <div style={styles.error}>{tx.message}</div> : null}\n {tx.kind === \"sent\" ? (\n <div style={styles.success}>\n Bid submitted.{\" \"}\n {explorer ? (\n <a\n href={`${explorer}/tx/${tx.hash}`}\n target=\"_blank\"\n rel=\"noreferrer\"\n style={styles.link}\n >\n View transaction\n </a>\n ) : (\n tx.hash\n )}\n </div>\n ) : null}\n </div>\n )}\n\n <div style={styles.footer}>\n Powered by <strong>Beacon</strong> · Uniswap CCA\n </div>\n </div>\n );\n}\n\nfunction Row({ label, value, hint }: { label: string; value: string; hint?: string }) {\n return (\n <div style={styles.row}>\n <dt style={styles.label} title={hint}>\n {label}\n </dt>\n <dd style={styles.value}>{value}</dd>\n </div>\n );\n}\n\nfunction StatusPill({ status, isGraduated }: { status: string; isGraduated: boolean }) {\n const palette =\n status === \"live\"\n ? styles.pillLive\n : status === \"pending\"\n ? styles.pillPending\n : styles.pillEnded;\n\n return (\n <div style={styles.pillRow}>\n <span style={{ ...styles.pill, ...palette }}>\n {status === \"live\" ? \"Live\" : status === \"pending\" ? \"Upcoming\" : \"Ended\"}\n </span>\n {isGraduated ? <span style={{ ...styles.pill, ...styles.pillGraduated }}>Graduated</span> : null}\n </div>\n );\n}\n\nfunction formatMinutes(seconds: number): string {\n if (seconds <= 0) return \"0m\";\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.round((seconds % 3600) / 60);\n return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n createBeaconClient,\n deriveStatus,\n getAuctionInfo,\n getAuctionState,\n getTokenMetadata,\n} from \"@hammerock/cca-sdk\";\nimport type {\n Address,\n AuctionInfo,\n AuctionState,\n AuctionStatus,\n BeaconClient,\n TokenMetadata,\n} from \"@hammerock/cca-sdk\";\n\n/**\n * Everything the card renders, read through @hammerock/cca-sdk.\n *\n * The widget runs on somebody else's website, so it reads through whatever\n * public RPC the embedder passes in. It never needs a lens deployment: the SDK\n * runs `AuctionStateLens` through an `eth_call` state override.\n */\nexport interface AuctionData {\n info: AuctionInfo;\n state: AuctionState;\n token: TokenMetadata;\n currency: TokenMetadata;\n status: AuctionStatus;\n headBlock: bigint;\n blocksRemaining: bigint;\n secondsRemaining: number;\n}\n\nexport interface UseAuctionResult {\n data: AuctionData | null;\n error: Error | null;\n loading: boolean;\n client: BeaconClient | null;\n refresh: () => void;\n}\n\nexport function useAuction(options: {\n auction: Address;\n rpcUrl: string;\n chainId: number;\n lensAddress?: Address;\n pollIntervalMs?: number;\n}): UseAuctionResult {\n const { auction, rpcUrl, chainId, lensAddress, pollIntervalMs = 12_000 } = options;\n\n const [data, setData] = useState<AuctionData | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState(true);\n const [nonce, setNonce] = useState(0);\n\n // Immutable per-auction data, cached across polls. Held in a ref rather than\n // read from `data` so the polling effect does not need `data` as a\n // dependency - which would restart the timer on every tick.\n const staticRef = useRef<{\n info?: AuctionInfo;\n token?: TokenMetadata;\n currency?: TokenMetadata;\n }>({});\n\n /** Set once the auction has closed, so polling can stop. */\n const endedAtRef = useRef(false);\n\n const clientRef = useRef<BeaconClient | null>(null);\n if (!clientRef.current) {\n try {\n clientRef.current = createBeaconClient({ rpcUrl, chainId, lensAddress });\n } catch {\n clientRef.current = null;\n }\n }\n\n const refresh = useCallback(() => setNonce((value) => value + 1), []);\n\n useEffect(() => {\n const client = clientRef.current;\n if (!client) {\n setError(new Error(`Unsupported or non-testnet chain id ${chainId}.`));\n setLoading(false);\n return;\n }\n\n let cancelled = false;\n\n async function load(isInitial: boolean) {\n try {\n if (isInitial) setLoading(true);\n\n const [info, state, headBlock] = await Promise.all([\n // Config is immutable, so only read it once.\n staticRef.current.info ?? getAuctionInfo(client!, auction),\n getAuctionState(client!, auction),\n client!.publicClient.getBlockNumber(),\n ]);\n\n const [token, currency] = await Promise.all([\n staticRef.current.token ?? getTokenMetadata(client!, info.token),\n staticRef.current.currency ?? getTokenMetadata(client!, info.currency),\n ]);\n\n staticRef.current = { info, token, currency };\n\n if (cancelled) return;\n\n const status = deriveStatus(info, headBlock);\n endedAtRef.current = status === \"ended\";\n const blocksRemaining =\n status === \"pending\"\n ? info.startBlock - headBlock\n : status === \"live\"\n ? info.endBlock - headBlock\n : 0n;\n\n setData({\n info,\n state,\n token,\n currency,\n status,\n headBlock,\n blocksRemaining,\n secondsRemaining: Number(blocksRemaining) * client!.blockTimeSeconds,\n });\n setError(null);\n } catch (cause) {\n if (!cancelled) setError(cause as Error);\n } finally {\n if (!cancelled) setLoading(false);\n }\n }\n\n void load(true);\n\n // An ended auction cannot change, so stop polling once it has closed.\n const timer = setInterval(() => {\n if (staticRef.current.info && endedAtRef.current) {\n clearInterval(timer);\n return;\n }\n void load(false);\n }, pollIntervalMs);\n\n return () => {\n cancelled = true;\n clearInterval(timer);\n };\n }, [auction, chainId, pollIntervalMs, nonce]);\n\n return { data, error, loading, client: clientRef.current, refresh };\n}\n","/**\n * Wallet access for the bid form.\n *\n * Deliberately uses the EIP-1193 provider the browser already exposes\n * (`window.ethereum`) through viem, rather than wagmi.\n *\n * Reasoning: this widget's whole point is that a project can drop it into their\n * own site. wagmi requires the host page to wrap the tree in `WagmiProvider`\n * plus a React Query client, which a plain marketing site cannot do at all, and\n * which the vanilla `<script>` mount path would have to bundle wholesale. Going\n * straight to EIP-1193 keeps the widget self-contained and works identically in\n * the React and vanilla builds. A host that already uses wagmi can pass its own\n * wallet client in instead.\n */\nimport { createWalletClient, custom } from \"viem\";\nimport type { Address, Chain, WalletClient } from \"viem\";\n\n/** The subset of EIP-1193 the widget needs. */\nexport interface Eip1193Provider {\n request(args: { method: string; params?: unknown[] }): Promise<unknown>;\n on?(event: string, handler: (...args: unknown[]) => void): void;\n removeListener?(event: string, handler: (...args: unknown[]) => void): void;\n}\n\ndeclare global {\n interface Window {\n ethereum?: Eip1193Provider;\n }\n}\n\nexport function getInjectedProvider(): Eip1193Provider | undefined {\n return typeof window === \"undefined\" ? undefined : window.ethereum;\n}\n\nexport function hasInjectedWallet(): boolean {\n return getInjectedProvider() !== undefined;\n}\n\nexport interface ConnectedWallet {\n address: Address;\n walletClient: WalletClient;\n chainId: number;\n}\n\n/** Prompt the wallet to connect, and switch chain if it is on the wrong one. */\nexport async function connectWallet(chain: Chain): Promise<ConnectedWallet> {\n const provider = getInjectedProvider();\n if (!provider) {\n throw new Error(\"No browser wallet found. Install MetaMask or another EIP-1193 wallet.\");\n }\n\n const accounts = (await provider.request({ method: \"eth_requestAccounts\" })) as Address[];\n const address = accounts[0];\n if (!address) throw new Error(\"Wallet returned no accounts.\");\n\n const currentChainId = Number(\n (await provider.request({ method: \"eth_chainId\" })) as string,\n );\n\n if (currentChainId !== chain.id) {\n await switchChain(provider, chain);\n }\n\n const walletClient = createWalletClient({ account: address, chain, transport: custom(provider) });\n return { address, walletClient, chainId: chain.id };\n}\n\nasync function switchChain(provider: Eip1193Provider, chain: Chain): Promise<void> {\n const chainIdHex = `0x${chain.id.toString(16)}`;\n try {\n await provider.request({\n method: \"wallet_switchEthereumChain\",\n params: [{ chainId: chainIdHex }],\n });\n } catch (error) {\n // 4902: the wallet does not know this chain yet, so offer to add it.\n const code = (error as { code?: number }).code;\n if (code !== 4902) throw error;\n\n await provider.request({\n method: \"wallet_addEthereumChain\",\n params: [\n {\n chainId: chainIdHex,\n chainName: chain.name,\n nativeCurrency: chain.nativeCurrency,\n rpcUrls: [chain.rpcUrls.default.http[0]],\n blockExplorerUrls: chain.blockExplorers?.default?.url\n ? [chain.blockExplorers.default.url]\n : undefined,\n },\n ],\n });\n }\n}\n","import type { CSSProperties } from \"react\";\n\n/**\n * Inline styles rather than a stylesheet.\n *\n * The widget is dropped into other people's pages. Shipping a CSS file would\n * require the host to import it, and global class names would collide with\n * theirs. Inline styles are self-contained and cannot leak either way.\n */\nconst border = \"#2a2a2e\";\nconst surface = \"#141417\";\nconst text = \"#f4f4f5\";\nconst muted = \"#8b8b93\";\n\nexport const styles = {\n card: {\n boxSizing: \"border-box\",\n width: \"100%\",\n maxWidth: 360,\n padding: 16,\n borderRadius: 12,\n border: `1px solid ${border}`,\n background: surface,\n color: text,\n fontFamily:\n \"ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif\",\n fontSize: 14,\n lineHeight: 1.45,\n },\n headerRow: {\n display: \"flex\",\n alignItems: \"flex-start\",\n justifyContent: \"space-between\",\n gap: 8,\n },\n symbol: { fontSize: 18, fontWeight: 600 },\n pillRow: { display: \"flex\", flexWrap: \"wrap\", gap: 4, justifyContent: \"flex-end\" },\n pill: {\n display: \"inline-block\",\n padding: \"2px 8px\",\n borderRadius: 999,\n fontSize: 11,\n fontWeight: 500,\n whiteSpace: \"nowrap\",\n },\n pillLive: { background: \"rgba(16,185,129,0.15)\", color: \"#6ee7b7\" },\n pillPending: { background: \"rgba(14,165,233,0.15)\", color: \"#7dd3fc\" },\n pillEnded: { background: \"rgba(113,113,122,0.18)\", color: \"#c4c4cc\" },\n pillGraduated: { background: \"rgba(139,92,246,0.18)\", color: \"#c4b5fd\" },\n\n priceBlock: { marginTop: 14 },\n price: { fontSize: 22, fontWeight: 600, fontVariantNumeric: \"tabular-nums\", wordBreak: \"break-all\" },\n priceUnit: { fontSize: 12, fontWeight: 400, color: muted },\n\n grid: { margin: \"14px 0 0\", display: \"grid\", gap: 8 },\n row: { display: \"flex\", alignItems: \"baseline\", justifyContent: \"space-between\", gap: 12, margin: 0 },\n label: { fontSize: 11, textTransform: \"uppercase\", letterSpacing: \"0.04em\", color: muted, margin: 0 },\n value: { margin: 0, fontSize: 13, textAlign: \"right\", fontVariantNumeric: \"tabular-nums\" },\n\n form: { marginTop: 16, display: \"grid\", gap: 6, borderTop: `1px solid ${border}`, paddingTop: 14 },\n input: {\n boxSizing: \"border-box\",\n width: \"100%\",\n padding: \"8px 10px\",\n borderRadius: 8,\n border: `1px solid ${border}`,\n background: \"#0d0d0f\",\n color: text,\n fontSize: 14,\n outline: \"none\",\n },\n button: {\n marginTop: 6,\n padding: \"10px 12px\",\n borderRadius: 8,\n border: \"none\",\n background: text,\n color: \"#0d0d0f\",\n fontSize: 14,\n fontWeight: 600,\n cursor: \"pointer\",\n },\n buttonDisabled: {\n background: \"#2a2a2e\",\n color: muted,\n cursor: \"not-allowed\",\n },\n notice: {\n padding: \"8px 10px\",\n borderRadius: 8,\n background: \"rgba(113,113,122,0.12)\",\n color: muted,\n fontSize: 12,\n },\n muted: { color: muted, fontSize: 12 },\n error: { color: \"#fca5a5\", fontSize: 12, wordBreak: \"break-word\" },\n success: { color: \"#6ee7b7\", fontSize: 12, wordBreak: \"break-word\" },\n link: { color: \"#7dd3fc\" },\n footer: {\n marginTop: 14,\n paddingTop: 10,\n borderTop: `1px solid ${border}`,\n color: muted,\n fontSize: 11,\n textAlign: \"center\",\n },\n} satisfies Record<string, CSSProperties>;\n","/**\n * Vanilla-JS entry point so non-React sites can embed the widget:\n *\n * <div id=\"beacon\"></div>\n * <script src=\"https://your-cdn/mount.global.js\"></script>\n * <script>\n * BeaconCCA.mount('#beacon', {\n * auction: '0x...', chainId: 11155111, rpcUrl: 'https://...'\n * });\n * </script>\n *\n * React and the SDK are bundled into this build, so the host page needs nothing.\n */\nimport { createElement } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport type { Root } from \"react-dom/client\";\nimport { AuctionCard } from \"./AuctionCard\";\nimport type { AuctionCardProps } from \"./AuctionCard\";\n\nconst roots = new WeakMap<Element, Root>();\n\nexport interface MountHandle {\n update(next: Partial<AuctionCardProps>): void;\n unmount(): void;\n}\n\nexport function mount(target: string | Element, props: AuctionCardProps): MountHandle {\n const element = typeof target === \"string\" ? document.querySelector(target) : target;\n if (!element) {\n throw new Error(`Beacon widget: no element matched ${String(target)}`);\n }\n\n // Re-mounting into the same node reuses its root, so calling mount() twice\n // updates in place instead of leaking a second React tree.\n let root = roots.get(element);\n if (!root) {\n root = createRoot(element);\n roots.set(element, root);\n }\n\n let current = props;\n root.render(createElement(AuctionCard, current));\n\n return {\n update(next) {\n current = { ...current, ...next };\n root!.render(createElement(AuctionCard, current));\n },\n unmount() {\n root!.unmount();\n roots.delete(element);\n },\n };\n}\n\nexport { AuctionCard };\nexport type { AuctionCardProps };\n"],"mappings":"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","names":["useState","useState"]}