@hongming-wang/usdc-bridge-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.mjs ADDED
@@ -0,0 +1,2295 @@
1
+ // src/BridgeWidget.tsx
2
+ import { useState as useState3, useEffect as useEffect3, useCallback as useCallback3, useRef as useRef2, useId, useMemo as useMemo2 } from "react";
3
+ import {
4
+ useAccount as useAccount3,
5
+ useChainId,
6
+ useSwitchChain,
7
+ useWaitForTransactionReceipt as useWaitForTransactionReceipt2,
8
+ useConnect
9
+ } from "wagmi";
10
+
11
+ // src/hooks.ts
12
+ import { useState as useState2, useCallback as useCallback2, useEffect as useEffect2, useMemo } from "react";
13
+ import {
14
+ useAccount as useAccount2,
15
+ useReadContract,
16
+ useReadContracts,
17
+ useWriteContract,
18
+ useWaitForTransactionReceipt
19
+ } from "wagmi";
20
+ import { formatUnits, parseUnits as parseUnits2, erc20Abi } from "viem";
21
+
22
+ // src/constants.ts
23
+ var USDC_DECIMALS = 6;
24
+ var USDC_BRAND_COLOR = "#2775ca";
25
+ var MAX_USDC_AMOUNT = "100000000000";
26
+ var MIN_USDC_AMOUNT = "0.000001";
27
+ var DEFAULT_LOCALE = "en-US";
28
+ var USDC_ADDRESSES = {
29
+ // Original chains
30
+ 1: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
31
+ // Ethereum
32
+ 42161: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
33
+ // Arbitrum
34
+ 43114: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
35
+ // Avalanche
36
+ 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
37
+ // Base
38
+ 10: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
39
+ // Optimism
40
+ 137: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
41
+ // Polygon
42
+ 59144: "0x176211869cA2b568f2A7D4EE941E073a821EE1ff",
43
+ // Linea
44
+ // New CCTP V2 chains
45
+ 130: "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
46
+ // Unichain
47
+ 146: "0x29219dd400f2Bf60E5a23d13Be72B486D4038894",
48
+ // Sonic
49
+ 480: "0x79A02482A880bCE3F13e09Da970dC34db4CD24d1",
50
+ // World Chain
51
+ 10200: "0x754704Bc059F8C67012fEd69BC8A327a5aafb603",
52
+ // Monad
53
+ 1329: "0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392",
54
+ // Sei
55
+ 50: "0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1",
56
+ // XDC
57
+ 999: "0xb88339CB7199b77E23DB6E890353E22632Ba630f",
58
+ // HyperEVM
59
+ 57073: "0x2D270e6886d130D724215A266106e6832161EAEd",
60
+ // Ink
61
+ 98866: "0x222365EF19F7947e5484218551B56bb3965Aa7aF",
62
+ // Plume
63
+ 81224: "0xd996633a415985DBd7D6D12f4A4343E31f5037cf"
64
+ // Codex
65
+ };
66
+ var TOKEN_MESSENGER_V1_ADDRESSES = {
67
+ 1: "0xBd3fa81B58Ba92a82136038B25aDec7066af3155",
68
+ // Ethereum
69
+ 42161: "0x19330d10D9Cc8751218eaf51E8885D058642E08A",
70
+ // Arbitrum
71
+ 43114: "0x6B25532e1060CE10cc3B0A99e5683b91BFDe6982",
72
+ // Avalanche
73
+ 8453: "0x1682Ae6375C4E4A97e4B583BC394c861A46D8962",
74
+ // Base
75
+ 10: "0x2B4069517957735bE00ceE0fadAE88a26365528f",
76
+ // Optimism
77
+ 137: "0x9daF8c91AEFAE50b9c0E69629D3F6Ca40cA3B3FE"
78
+ // Polygon
79
+ };
80
+ var TOKEN_MESSENGER_V2_ADDRESS = "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d";
81
+ var TOKEN_MESSENGER_ADDRESSES = {
82
+ // All CCTP V2 supported chains use the same address
83
+ 1: TOKEN_MESSENGER_V2_ADDRESS,
84
+ // Ethereum
85
+ 42161: TOKEN_MESSENGER_V2_ADDRESS,
86
+ // Arbitrum
87
+ 43114: TOKEN_MESSENGER_V2_ADDRESS,
88
+ // Avalanche
89
+ 8453: TOKEN_MESSENGER_V2_ADDRESS,
90
+ // Base
91
+ 10: TOKEN_MESSENGER_V2_ADDRESS,
92
+ // Optimism
93
+ 137: TOKEN_MESSENGER_V2_ADDRESS,
94
+ // Polygon
95
+ 59144: TOKEN_MESSENGER_V2_ADDRESS,
96
+ // Linea
97
+ 130: TOKEN_MESSENGER_V2_ADDRESS,
98
+ // Unichain
99
+ 146: TOKEN_MESSENGER_V2_ADDRESS,
100
+ // Sonic
101
+ 480: TOKEN_MESSENGER_V2_ADDRESS,
102
+ // World Chain
103
+ 10200: TOKEN_MESSENGER_V2_ADDRESS,
104
+ // Monad
105
+ 1329: TOKEN_MESSENGER_V2_ADDRESS,
106
+ // Sei
107
+ 50: TOKEN_MESSENGER_V2_ADDRESS,
108
+ // XDC
109
+ 999: TOKEN_MESSENGER_V2_ADDRESS,
110
+ // HyperEVM
111
+ 57073: TOKEN_MESSENGER_V2_ADDRESS,
112
+ // Ink
113
+ 98866: TOKEN_MESSENGER_V2_ADDRESS,
114
+ // Plume
115
+ 81224: TOKEN_MESSENGER_V2_ADDRESS
116
+ // Codex
117
+ };
118
+ var CHAIN_ICONS = {
119
+ 1: "https://icons.llamao.fi/icons/chains/rsz_ethereum.jpg",
120
+ // Ethereum
121
+ 42161: "https://icons.llamao.fi/icons/chains/rsz_arbitrum.jpg",
122
+ // Arbitrum
123
+ 43114: "https://icons.llamao.fi/icons/chains/rsz_avalanche.jpg",
124
+ // Avalanche
125
+ 8453: "https://icons.llamao.fi/icons/chains/rsz_base.jpg",
126
+ // Base
127
+ 10: "https://icons.llamao.fi/icons/chains/rsz_optimism.jpg",
128
+ // Optimism
129
+ 137: "https://icons.llamao.fi/icons/chains/rsz_polygon.jpg",
130
+ // Polygon
131
+ 59144: "https://icons.llamao.fi/icons/chains/rsz_linea.jpg",
132
+ // Linea
133
+ 130: "https://icons.llamao.fi/icons/chains/rsz_unichain.jpg",
134
+ // Unichain
135
+ 146: "https://icons.llamao.fi/icons/chains/rsz_sonic.jpg",
136
+ // Sonic
137
+ 480: "https://icons.llamao.fi/icons/chains/rsz_world-chain.jpg",
138
+ // World Chain
139
+ 10200: "https://icons.llamao.fi/icons/chains/rsz_monad.jpg",
140
+ // Monad
141
+ 1329: "https://icons.llamao.fi/icons/chains/rsz_sei.jpg",
142
+ // Sei
143
+ 50: "https://icons.llamao.fi/icons/chains/rsz_xdc.jpg",
144
+ // XDC
145
+ 999: "https://icons.llamao.fi/icons/chains/rsz_hyperevm.jpg",
146
+ // HyperEVM
147
+ 57073: "https://icons.llamao.fi/icons/chains/rsz_ink.jpg",
148
+ // Ink
149
+ 98866: "https://icons.llamao.fi/icons/chains/rsz_plume.jpg",
150
+ // Plume
151
+ 81224: "https://raw.githubusercontent.com/0xa3k5/web3icons/main/packages/core/src/svgs/networks/branded/codex.svg"
152
+ // Codex
153
+ };
154
+
155
+ // src/utils.ts
156
+ import { parseUnits, isAddress } from "viem";
157
+ function formatNumber(value, decimals = 2, locale = DEFAULT_LOCALE) {
158
+ const num = typeof value === "string" ? parseFloat(value) : value;
159
+ if (isNaN(num)) return "0";
160
+ return num.toLocaleString(locale, {
161
+ minimumFractionDigits: decimals,
162
+ maximumFractionDigits: decimals
163
+ });
164
+ }
165
+ function validateAmountInput(value) {
166
+ if (value === "") {
167
+ return { isValid: true, sanitized: "" };
168
+ }
169
+ if (/[eE]/.test(value)) {
170
+ return { isValid: false, sanitized: "", error: "Scientific notation not allowed" };
171
+ }
172
+ if (value.includes("-")) {
173
+ return { isValid: false, sanitized: "", error: "Negative values not allowed" };
174
+ }
175
+ if (value === "." || value === "0.") {
176
+ return { isValid: true, sanitized: value };
177
+ }
178
+ if (!/^[0-9]*\.?[0-9]*$/.test(value)) {
179
+ return { isValid: false, sanitized: "", error: "Invalid characters" };
180
+ }
181
+ const parts = value.split(".");
182
+ if (parts.length === 2 && parts[1].length > USDC_DECIMALS) {
183
+ return {
184
+ isValid: false,
185
+ sanitized: `${parts[0]}.${parts[1].slice(0, USDC_DECIMALS)}`,
186
+ error: `Maximum ${USDC_DECIMALS} decimal places`
187
+ };
188
+ }
189
+ const num = parseFloat(value);
190
+ if (!isNaN(num) && num > parseFloat(MAX_USDC_AMOUNT)) {
191
+ return { isValid: false, sanitized: value, error: "Amount exceeds maximum" };
192
+ }
193
+ let sanitized = value;
194
+ if (sanitized.length > 1 && sanitized.startsWith("0") && sanitized[1] !== ".") {
195
+ sanitized = sanitized.replace(/^0+/, "") || "0";
196
+ }
197
+ return { isValid: true, sanitized };
198
+ }
199
+ function parseUSDCAmount(amount) {
200
+ try {
201
+ if (!amount || parseFloat(amount) < 0) return null;
202
+ return parseUnits(amount, USDC_DECIMALS);
203
+ } catch {
204
+ return null;
205
+ }
206
+ }
207
+ function isValidPositiveAmount(amount) {
208
+ if (!amount) return false;
209
+ const num = parseFloat(amount);
210
+ return !isNaN(num) && num > 0;
211
+ }
212
+ function getErrorMessage(error) {
213
+ if (error instanceof Error) {
214
+ return error.message;
215
+ }
216
+ if (typeof error === "string") {
217
+ return error;
218
+ }
219
+ if (error !== null && typeof error === "object" && "message" in error && typeof error.message === "string") {
220
+ return error.message;
221
+ }
222
+ return "An unknown error occurred";
223
+ }
224
+ function validateChainConfig(config) {
225
+ const errors = [];
226
+ if (!config.chain) {
227
+ errors.push("Chain object is required");
228
+ return { isValid: false, errors };
229
+ }
230
+ if (typeof config.chain.id !== "number" || config.chain.id <= 0) {
231
+ errors.push(`Invalid chain ID: ${config.chain.id}`);
232
+ }
233
+ if (!config.chain.name || typeof config.chain.name !== "string") {
234
+ errors.push("Chain name is required");
235
+ }
236
+ if (!config.usdcAddress) {
237
+ errors.push(`USDC address is required for chain ${config.chain.name || config.chain.id}`);
238
+ } else if (!isAddress(config.usdcAddress)) {
239
+ errors.push(`Invalid USDC address for chain ${config.chain.name}: ${config.usdcAddress}`);
240
+ }
241
+ if (config.tokenMessengerAddress && !isAddress(config.tokenMessengerAddress)) {
242
+ errors.push(
243
+ `Invalid TokenMessenger address for chain ${config.chain.name}: ${config.tokenMessengerAddress}`
244
+ );
245
+ }
246
+ return {
247
+ isValid: errors.length === 0,
248
+ errors
249
+ };
250
+ }
251
+ function validateChainConfigs(configs) {
252
+ const allErrors = [];
253
+ if (!Array.isArray(configs)) {
254
+ return { isValid: false, errors: ["Chain configs must be an array"] };
255
+ }
256
+ if (configs.length === 0) {
257
+ return { isValid: false, errors: ["At least one chain configuration is required"] };
258
+ }
259
+ if (configs.length < 2) {
260
+ return { isValid: false, errors: ["At least two chains are required for bridging"] };
261
+ }
262
+ const chainIds = /* @__PURE__ */ new Set();
263
+ for (const config of configs) {
264
+ if (config.chain?.id) {
265
+ if (chainIds.has(config.chain.id)) {
266
+ allErrors.push(`Duplicate chain ID: ${config.chain.id}`);
267
+ }
268
+ chainIds.add(config.chain.id);
269
+ }
270
+ const result = validateChainConfig(config);
271
+ if (!result.isValid) {
272
+ allErrors.push(...result.errors);
273
+ }
274
+ }
275
+ return {
276
+ isValid: allErrors.length === 0,
277
+ errors: allErrors
278
+ };
279
+ }
280
+
281
+ // src/useBridge.ts
282
+ import { useState, useCallback, useEffect, useRef } from "react";
283
+ import { useAccount } from "wagmi";
284
+ import { BridgeKit, BridgeChain } from "@circle-fin/bridge-kit";
285
+ import { createViemAdapterFromProvider } from "@circle-fin/adapter-viem-v2";
286
+ var MAX_EVENTS = 100;
287
+ var CHAIN_ID_TO_BRIDGE_CHAIN = {
288
+ 1: BridgeChain.Ethereum,
289
+ 42161: BridgeChain.Arbitrum,
290
+ 43114: BridgeChain.Avalanche,
291
+ 8453: BridgeChain.Base,
292
+ 10: BridgeChain.Optimism,
293
+ 137: BridgeChain.Polygon,
294
+ 59144: BridgeChain.Linea,
295
+ 130: BridgeChain.Unichain,
296
+ 146: BridgeChain.Sonic,
297
+ 480: BridgeChain.World_Chain,
298
+ // Note: Monad (10200) not yet supported in Circle Bridge Kit
299
+ 1329: BridgeChain.Sei,
300
+ 50: BridgeChain.XDC,
301
+ 999: BridgeChain.HyperEVM,
302
+ 57073: BridgeChain.Ink,
303
+ 98866: BridgeChain.Plume,
304
+ 81224: BridgeChain.Codex
305
+ };
306
+ function isBridgeEventWithTxHash(event) {
307
+ return typeof event === "object" && event !== null && ("values" in event ? typeof event.values === "object" : true);
308
+ }
309
+ function extractTxHash(event) {
310
+ if (isBridgeEventWithTxHash(event) && event.values?.txHash) {
311
+ const hash = event.values.txHash;
312
+ if (typeof hash === "string" && hash.startsWith("0x")) {
313
+ return hash;
314
+ }
315
+ }
316
+ return void 0;
317
+ }
318
+ function isEIP1193Provider(provider) {
319
+ return typeof provider === "object" && provider !== null && "request" in provider && typeof provider.request === "function";
320
+ }
321
+ function getBridgeChain(chainId) {
322
+ return CHAIN_ID_TO_BRIDGE_CHAIN[chainId];
323
+ }
324
+ function getChainName(chainId) {
325
+ const bridgeChain = CHAIN_ID_TO_BRIDGE_CHAIN[chainId];
326
+ return bridgeChain || `Chain_${chainId}`;
327
+ }
328
+ function useBridge() {
329
+ const { connector, isConnected } = useAccount();
330
+ const [state, setState] = useState({
331
+ status: "idle",
332
+ events: []
333
+ });
334
+ const isMountedRef = useRef(true);
335
+ const currentBridgeRef = useRef(null);
336
+ useEffect(() => {
337
+ isMountedRef.current = true;
338
+ return () => {
339
+ isMountedRef.current = false;
340
+ if (currentBridgeRef.current) {
341
+ currentBridgeRef.current.aborted = true;
342
+ }
343
+ };
344
+ }, []);
345
+ const addEvent = useCallback((type, data) => {
346
+ if (!isMountedRef.current) return;
347
+ setState((prev) => {
348
+ const newEvents = [...prev.events, { type, timestamp: Date.now(), data }];
349
+ if (newEvents.length > MAX_EVENTS) {
350
+ newEvents.splice(0, newEvents.length - MAX_EVENTS);
351
+ }
352
+ return { ...prev, events: newEvents };
353
+ });
354
+ }, []);
355
+ const reset = useCallback(() => {
356
+ if (currentBridgeRef.current) {
357
+ currentBridgeRef.current.aborted = true;
358
+ }
359
+ currentBridgeRef.current = null;
360
+ setState({ status: "idle", events: [] });
361
+ }, []);
362
+ const bridge = useCallback(
363
+ async (params) => {
364
+ const { sourceChainConfig, destChainConfig, amount, recipientAddress } = params;
365
+ if (currentBridgeRef.current) {
366
+ currentBridgeRef.current.aborted = true;
367
+ }
368
+ const operation = { aborted: false, kit: null };
369
+ currentBridgeRef.current = operation;
370
+ if (!isConnected || !connector) {
371
+ setState({
372
+ status: "error",
373
+ error: new Error("Wallet not connected"),
374
+ events: []
375
+ });
376
+ return;
377
+ }
378
+ setState({ status: "loading", events: [] });
379
+ addEvent("start", { amount, sourceChain: sourceChainConfig.chain.id, destChain: destChainConfig.chain.id });
380
+ let cleanupListeners = null;
381
+ try {
382
+ const provider = await connector.getProvider();
383
+ if (!provider) {
384
+ throw new Error("Could not get wallet provider from connector");
385
+ }
386
+ if (!isEIP1193Provider(provider)) {
387
+ throw new Error("Wallet provider is not EIP-1193 compatible");
388
+ }
389
+ const adapter = await createViemAdapterFromProvider({
390
+ provider
391
+ });
392
+ if (operation.aborted) {
393
+ return;
394
+ }
395
+ const kit = new BridgeKit();
396
+ operation.kit = kit;
397
+ const handleApprove = (event) => {
398
+ if (operation.aborted || !isMountedRef.current) return;
399
+ addEvent("approve", event);
400
+ setState((prev) => ({
401
+ ...prev,
402
+ status: "approving",
403
+ txHash: extractTxHash(event)
404
+ }));
405
+ };
406
+ const handleBurn = (event) => {
407
+ if (operation.aborted || !isMountedRef.current) return;
408
+ addEvent("burn", event);
409
+ setState((prev) => ({
410
+ ...prev,
411
+ status: "burning",
412
+ txHash: extractTxHash(event)
413
+ }));
414
+ };
415
+ const handleFetchAttestation = (event) => {
416
+ if (operation.aborted || !isMountedRef.current) return;
417
+ addEvent("fetchAttestation", event);
418
+ setState((prev) => ({
419
+ ...prev,
420
+ status: "fetching-attestation"
421
+ }));
422
+ };
423
+ const handleMint = (event) => {
424
+ if (operation.aborted || !isMountedRef.current) return;
425
+ addEvent("mint", event);
426
+ setState((prev) => ({
427
+ ...prev,
428
+ status: "minting",
429
+ txHash: extractTxHash(event)
430
+ }));
431
+ };
432
+ cleanupListeners = () => {
433
+ try {
434
+ kit.off("approve", handleApprove);
435
+ kit.off("burn", handleBurn);
436
+ kit.off("fetchAttestation", handleFetchAttestation);
437
+ kit.off("mint", handleMint);
438
+ } catch {
439
+ }
440
+ };
441
+ kit.on("approve", handleApprove);
442
+ kit.on("burn", handleBurn);
443
+ kit.on("fetchAttestation", handleFetchAttestation);
444
+ kit.on("mint", handleMint);
445
+ const sourceBridgeChain = getBridgeChain(sourceChainConfig.chain.id);
446
+ const destBridgeChain = getBridgeChain(destChainConfig.chain.id);
447
+ if (!sourceBridgeChain) {
448
+ throw new Error(`Unsupported source chain: ${sourceChainConfig.chain.name} (${sourceChainConfig.chain.id})`);
449
+ }
450
+ if (!destBridgeChain) {
451
+ throw new Error(`Unsupported destination chain: ${destChainConfig.chain.name} (${destChainConfig.chain.id})`);
452
+ }
453
+ const result = await kit.bridge({
454
+ from: { adapter, chain: sourceBridgeChain },
455
+ to: recipientAddress ? { adapter, chain: destBridgeChain, recipientAddress } : { adapter, chain: destBridgeChain },
456
+ amount
457
+ });
458
+ cleanupListeners();
459
+ if (operation.aborted || !isMountedRef.current) {
460
+ return;
461
+ }
462
+ addEvent("complete", result);
463
+ setState((prev) => ({
464
+ ...prev,
465
+ status: "success",
466
+ txHash: extractTxHash(result)
467
+ }));
468
+ } catch (error) {
469
+ if (cleanupListeners) {
470
+ cleanupListeners();
471
+ }
472
+ if (operation.aborted || !isMountedRef.current) {
473
+ return;
474
+ }
475
+ addEvent("error", error);
476
+ setState((prev) => ({
477
+ ...prev,
478
+ status: "error",
479
+ error: error instanceof Error ? error : new Error("Bridge transfer failed")
480
+ }));
481
+ throw error;
482
+ }
483
+ },
484
+ [isConnected, connector, addEvent]
485
+ );
486
+ return { bridge, state, reset };
487
+ }
488
+ function useBridgeQuote(sourceChainId, destChainId, amount) {
489
+ const [quote, setQuote] = useState(null);
490
+ const [isLoading, setIsLoading] = useState(false);
491
+ const [error, setError] = useState(null);
492
+ useEffect(() => {
493
+ if (!sourceChainId || !destChainId || !amount || parseFloat(amount) <= 0) {
494
+ setQuote(null);
495
+ setError(null);
496
+ return;
497
+ }
498
+ const fetchQuote = async () => {
499
+ setIsLoading(true);
500
+ setError(null);
501
+ try {
502
+ const sourceBridgeChain = getBridgeChain(sourceChainId);
503
+ const destBridgeChain = getBridgeChain(destChainId);
504
+ if (!sourceBridgeChain || !destBridgeChain) {
505
+ setQuote({
506
+ estimatedGasFee: "Estimated by wallet",
507
+ bridgeFee: "0.00",
508
+ totalFee: "Gas only",
509
+ estimatedTime: "~15-20 minutes"
510
+ });
511
+ return;
512
+ }
513
+ setQuote({
514
+ estimatedGasFee: "Estimated by wallet",
515
+ bridgeFee: "0-14 bps (FAST) / 0 (SLOW)",
516
+ totalFee: "Gas + protocol fee",
517
+ estimatedTime: "~15-20 minutes"
518
+ });
519
+ } catch (err) {
520
+ setError(err instanceof Error ? err : new Error("Failed to get quote"));
521
+ setQuote(null);
522
+ } finally {
523
+ setIsLoading(false);
524
+ }
525
+ };
526
+ let isActive = true;
527
+ const debounceTimer = setTimeout(() => {
528
+ if (isActive) {
529
+ fetchQuote();
530
+ }
531
+ }, 500);
532
+ return () => {
533
+ isActive = false;
534
+ clearTimeout(debounceTimer);
535
+ };
536
+ }, [sourceChainId, destChainId, amount]);
537
+ return { quote, isLoading, error };
538
+ }
539
+
540
+ // src/hooks.ts
541
+ function useUSDCBalance(chainConfig) {
542
+ const { address } = useAccount2();
543
+ const {
544
+ data: balance,
545
+ isLoading,
546
+ refetch
547
+ } = useReadContract({
548
+ address: chainConfig?.usdcAddress,
549
+ abi: erc20Abi,
550
+ functionName: "balanceOf",
551
+ args: address ? [address] : void 0,
552
+ query: {
553
+ enabled: !!address && !!chainConfig?.usdcAddress
554
+ }
555
+ });
556
+ return {
557
+ balance: balance ?? 0n,
558
+ balanceFormatted: balance ? formatUnits(balance, USDC_DECIMALS) : "0",
559
+ isLoading,
560
+ refetch
561
+ };
562
+ }
563
+ function useAllUSDCBalances(chainConfigs) {
564
+ const { address } = useAccount2();
565
+ const contracts = useMemo(() => {
566
+ if (!address) return [];
567
+ return chainConfigs.filter((config) => config.usdcAddress).map((config) => ({
568
+ address: config.usdcAddress,
569
+ abi: erc20Abi,
570
+ functionName: "balanceOf",
571
+ args: [address],
572
+ chainId: config.chain.id
573
+ }));
574
+ }, [address, chainConfigs]);
575
+ const {
576
+ data: results,
577
+ isLoading,
578
+ refetch
579
+ } = useReadContracts({
580
+ contracts,
581
+ query: {
582
+ enabled: !!address && contracts.length > 0
583
+ }
584
+ });
585
+ const balances = useMemo(() => {
586
+ const balanceMap = {};
587
+ if (!results) return balanceMap;
588
+ chainConfigs.forEach((config, index) => {
589
+ const result = results[index];
590
+ if (result?.status === "success" && typeof result.result === "bigint") {
591
+ balanceMap[config.chain.id] = {
592
+ balance: result.result,
593
+ formatted: formatUnits(result.result, USDC_DECIMALS)
594
+ };
595
+ } else {
596
+ balanceMap[config.chain.id] = {
597
+ balance: 0n,
598
+ formatted: "0"
599
+ };
600
+ }
601
+ });
602
+ return balanceMap;
603
+ }, [results, chainConfigs]);
604
+ return {
605
+ balances,
606
+ isLoading,
607
+ refetch
608
+ };
609
+ }
610
+ function useUSDCAllowance(chainConfig, spenderAddress) {
611
+ const { address } = useAccount2();
612
+ const effectiveSpender = spenderAddress || chainConfig?.tokenMessengerAddress;
613
+ const {
614
+ data: allowance,
615
+ isLoading,
616
+ refetch
617
+ } = useReadContract({
618
+ address: chainConfig?.usdcAddress,
619
+ abi: erc20Abi,
620
+ functionName: "allowance",
621
+ args: address && effectiveSpender ? [address, effectiveSpender] : void 0,
622
+ query: {
623
+ enabled: !!address && !!chainConfig?.usdcAddress && !!effectiveSpender
624
+ }
625
+ });
626
+ const { writeContractAsync, isPending: isApproving } = useWriteContract();
627
+ const [approvalTxHash, setApprovalTxHash] = useState2();
628
+ const [approvalError, setApprovalError] = useState2(null);
629
+ const { isLoading: isConfirming, isSuccess: isApprovalConfirmed } = useWaitForTransactionReceipt({
630
+ hash: approvalTxHash
631
+ });
632
+ const approve = useCallback2(
633
+ async (amount) => {
634
+ if (!chainConfig?.usdcAddress || !effectiveSpender) {
635
+ throw new Error("Missing chain config or spender address");
636
+ }
637
+ setApprovalError(null);
638
+ try {
639
+ const amountBigInt = parseUnits2(amount, USDC_DECIMALS);
640
+ const hash = await writeContractAsync({
641
+ address: chainConfig.usdcAddress,
642
+ abi: erc20Abi,
643
+ functionName: "approve",
644
+ args: [effectiveSpender, amountBigInt]
645
+ });
646
+ setApprovalTxHash(hash);
647
+ return hash;
648
+ } catch (error) {
649
+ const err = error instanceof Error ? error : new Error("Approval failed");
650
+ setApprovalError(err);
651
+ throw err;
652
+ }
653
+ },
654
+ [chainConfig?.usdcAddress, effectiveSpender, writeContractAsync]
655
+ );
656
+ useEffect2(() => {
657
+ if (isApprovalConfirmed) {
658
+ refetch();
659
+ }
660
+ }, [isApprovalConfirmed, refetch]);
661
+ const needsApproval = useCallback2(
662
+ (amount) => {
663
+ if (!amount || !allowance) return false;
664
+ const parsedAmount = parseFloat(amount);
665
+ if (isNaN(parsedAmount) || parsedAmount <= 0) return false;
666
+ try {
667
+ const amountBigInt = parseUnits2(amount, USDC_DECIMALS);
668
+ return allowance < amountBigInt;
669
+ } catch {
670
+ return false;
671
+ }
672
+ },
673
+ [allowance]
674
+ );
675
+ return {
676
+ allowance: allowance ?? 0n,
677
+ allowanceFormatted: allowance ? formatUnits(allowance, USDC_DECIMALS) : "0",
678
+ isLoading,
679
+ isApproving: isApproving || isConfirming,
680
+ approve,
681
+ needsApproval,
682
+ refetch,
683
+ approvalError
684
+ };
685
+ }
686
+ function useBridgeEstimate(sourceChainId, destChainId, amount) {
687
+ useEffect2(() => {
688
+ console.warn(
689
+ "[DEPRECATED] useBridgeEstimate is deprecated and will be removed in a future version. Use useBridgeQuote from './useBridge' instead."
690
+ );
691
+ }, []);
692
+ const [estimate, setEstimate] = useState2(null);
693
+ const [isLoading, setIsLoading] = useState2(false);
694
+ const [error, setError] = useState2(null);
695
+ const fetchEstimate = useCallback2(async () => {
696
+ if (!amount || parseFloat(amount) <= 0 || !sourceChainId || !destChainId) {
697
+ setEstimate(null);
698
+ setError(null);
699
+ return;
700
+ }
701
+ setIsLoading(true);
702
+ setError(null);
703
+ try {
704
+ const sourceBridgeChain = getBridgeChain(sourceChainId);
705
+ const destBridgeChain = getBridgeChain(destChainId);
706
+ if (!sourceBridgeChain || !destBridgeChain) {
707
+ setEstimate({
708
+ gasFee: "Estimated by wallet",
709
+ bridgeFee: "0.00",
710
+ totalFee: "Gas only",
711
+ estimatedTime: "~15-20 minutes"
712
+ });
713
+ setIsLoading(false);
714
+ return;
715
+ }
716
+ setEstimate({
717
+ gasFee: "Estimated by wallet",
718
+ bridgeFee: "0-14 bps (FAST) / 0 (SLOW)",
719
+ totalFee: "Gas + protocol fee",
720
+ estimatedTime: "~15-20 minutes"
721
+ });
722
+ } catch (err) {
723
+ const error2 = err instanceof Error ? err : new Error("Failed to estimate bridge cost");
724
+ setError(error2);
725
+ setEstimate(null);
726
+ } finally {
727
+ setIsLoading(false);
728
+ }
729
+ }, [sourceChainId, destChainId, amount]);
730
+ useEffect2(() => {
731
+ let isActive = true;
732
+ const debounceTimer = setTimeout(() => {
733
+ if (isActive) {
734
+ fetchEstimate();
735
+ }
736
+ }, 500);
737
+ return () => {
738
+ isActive = false;
739
+ clearTimeout(debounceTimer);
740
+ };
741
+ }, [fetchEstimate]);
742
+ return { estimate, isLoading, error };
743
+ }
744
+ function useFormatNumber() {
745
+ useEffect2(() => {
746
+ console.warn(
747
+ "[DEPRECATED] useFormatNumber is deprecated and will be removed in a future version. Use the formatNumber utility function from './utils' directly instead."
748
+ );
749
+ }, []);
750
+ return useCallback2(
751
+ (value, decimals = 2) => {
752
+ return formatNumber(value, decimals);
753
+ },
754
+ []
755
+ );
756
+ }
757
+
758
+ // src/chains.ts
759
+ import { defineChain } from "viem";
760
+ import {
761
+ mainnet,
762
+ arbitrum,
763
+ avalanche,
764
+ base,
765
+ optimism,
766
+ polygon,
767
+ linea,
768
+ sei,
769
+ worldchain,
770
+ ink,
771
+ sonic,
772
+ xdc,
773
+ sepolia,
774
+ arbitrumSepolia,
775
+ avalancheFuji,
776
+ baseSepolia,
777
+ optimismSepolia,
778
+ polygonAmoy
779
+ } from "viem/chains";
780
+ var unichain = defineChain({
781
+ id: 130,
782
+ name: "Unichain",
783
+ nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
784
+ rpcUrls: {
785
+ default: { http: ["https://mainnet.unichain.org"] }
786
+ },
787
+ blockExplorers: {
788
+ default: { name: "Uniscan", url: "https://uniscan.xyz" }
789
+ }
790
+ });
791
+ var hyperEvm = defineChain({
792
+ id: 999,
793
+ name: "HyperEVM",
794
+ nativeCurrency: { name: "HYPE", symbol: "HYPE", decimals: 18 },
795
+ rpcUrls: {
796
+ default: { http: ["https://rpc.hyperliquid.xyz/evm"] }
797
+ },
798
+ blockExplorers: {
799
+ default: { name: "Hyperscan", url: "https://hyperscan.xyz" }
800
+ }
801
+ });
802
+ var plume = defineChain({
803
+ id: 98866,
804
+ name: "Plume",
805
+ nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
806
+ rpcUrls: {
807
+ default: { http: ["https://rpc.plume.org"] }
808
+ },
809
+ blockExplorers: {
810
+ default: { name: "Plume Explorer", url: "https://explorer.plume.org" }
811
+ }
812
+ });
813
+ var monad = defineChain({
814
+ id: 10200,
815
+ name: "Monad",
816
+ nativeCurrency: { name: "MON", symbol: "MON", decimals: 18 },
817
+ rpcUrls: {
818
+ default: { http: ["https://rpc.monad.xyz"] }
819
+ },
820
+ blockExplorers: {
821
+ default: { name: "Monad Explorer", url: "https://explorer.monad.xyz" }
822
+ }
823
+ });
824
+ var codex = defineChain({
825
+ id: 81224,
826
+ name: "Codex",
827
+ nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
828
+ rpcUrls: {
829
+ default: { http: ["https://rpc.codex.storage"] }
830
+ },
831
+ blockExplorers: {
832
+ default: { name: "Codex Explorer", url: "https://explorer.codex.storage" }
833
+ }
834
+ });
835
+ function createChainConfig(chain, options) {
836
+ return {
837
+ chain,
838
+ usdcAddress: options?.usdcAddress || USDC_ADDRESSES[chain.id],
839
+ tokenMessengerAddress: options?.tokenMessengerAddress || TOKEN_MESSENGER_ADDRESSES[chain.id],
840
+ iconUrl: options?.iconUrl || CHAIN_ICONS[chain.id]
841
+ };
842
+ }
843
+ var DEFAULT_CHAIN_CONFIGS = [
844
+ createChainConfig(mainnet),
845
+ createChainConfig(arbitrum),
846
+ createChainConfig(base),
847
+ createChainConfig(optimism),
848
+ createChainConfig(polygon),
849
+ createChainConfig(avalanche),
850
+ createChainConfig(linea),
851
+ createChainConfig(sonic),
852
+ createChainConfig(worldchain),
853
+ createChainConfig(sei),
854
+ createChainConfig(xdc),
855
+ createChainConfig(ink),
856
+ createChainConfig(unichain),
857
+ createChainConfig(hyperEvm),
858
+ createChainConfig(plume),
859
+ createChainConfig(codex)
860
+ ];
861
+ var TESTNET_USDC_ADDRESSES = {
862
+ 11155111: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
863
+ // Sepolia
864
+ 421614: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d",
865
+ // Arbitrum Sepolia
866
+ 43113: "0x5425890298aed601595a70AB815c96711a31Bc65",
867
+ // Avalanche Fuji
868
+ 84532: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
869
+ // Base Sepolia
870
+ 11155420: "0x5fd84259d66Cd46123540766Be93DFE6D43130D7",
871
+ // Optimism Sepolia
872
+ 80002: "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582"
873
+ // Polygon Amoy
874
+ };
875
+ var TESTNET_TOKEN_MESSENGER_V2_ADDRESS = "0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5";
876
+ function createTestnetChainConfig(chain, options) {
877
+ return {
878
+ chain,
879
+ usdcAddress: options?.usdcAddress || TESTNET_USDC_ADDRESSES[chain.id],
880
+ tokenMessengerAddress: options?.tokenMessengerAddress || TESTNET_TOKEN_MESSENGER_V2_ADDRESS,
881
+ iconUrl: options?.iconUrl || CHAIN_ICONS[chain.id]
882
+ };
883
+ }
884
+ var TESTNET_CHAIN_CONFIGS = [
885
+ createTestnetChainConfig(sepolia),
886
+ createTestnetChainConfig(arbitrumSepolia),
887
+ createTestnetChainConfig(avalancheFuji),
888
+ createTestnetChainConfig(baseSepolia),
889
+ createTestnetChainConfig(optimismSepolia),
890
+ createTestnetChainConfig(polygonAmoy)
891
+ ];
892
+
893
+ // src/theme.ts
894
+ var THEME_COLORS = {
895
+ /** Primary accent - Indigo */
896
+ primary: "#6366f1",
897
+ /** Secondary accent - Purple */
898
+ secondary: "#a855f7",
899
+ /** Success state - Green */
900
+ success: "#22c55e",
901
+ /** Error state - Red */
902
+ error: "#ef4444",
903
+ /** Text color - White */
904
+ text: "#ffffff",
905
+ /** Muted text - Semi-transparent white */
906
+ mutedText: "rgba(255, 255, 255, 0.54)",
907
+ /** Border color - Semi-transparent white */
908
+ border: "rgba(255, 255, 255, 0.06)",
909
+ /** Background - Dark with transparency */
910
+ background: "rgba(15, 15, 25, 0.8)",
911
+ /** Card background - Darker with transparency */
912
+ cardBackground: "rgba(15, 15, 25, 0.6)",
913
+ /** Dropdown background - Near opaque dark */
914
+ dropdownBackground: "rgba(20, 20, 35, 0.98)",
915
+ /** Input background - Transparent black */
916
+ inputBackground: "rgba(0, 0, 0, 0.3)",
917
+ /** Hover state - Semi-transparent white */
918
+ hover: "rgba(255, 255, 255, 0.05)"
919
+ };
920
+ var THEME_SIZING = {
921
+ /** Default border radius in pixels */
922
+ borderRadius: 12,
923
+ /** Widget max width */
924
+ maxWidth: "480px",
925
+ /** Standard padding */
926
+ padding: "16px",
927
+ /** Gap between elements */
928
+ gap: "12px",
929
+ /** Small gap */
930
+ smallGap: "8px",
931
+ /** Dropdown max height */
932
+ dropdownMaxHeight: "300px"
933
+ };
934
+ var THEME_FONTS = {
935
+ /** Primary font family */
936
+ family: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
937
+ /** Font sizes */
938
+ sizes: {
939
+ xs: "10px",
940
+ sm: "12px",
941
+ base: "14px",
942
+ lg: "18px"
943
+ },
944
+ /** Font weights */
945
+ weights: {
946
+ normal: 400,
947
+ medium: 500,
948
+ semibold: 600,
949
+ bold: 700
950
+ }
951
+ };
952
+ var defaultTheme = {
953
+ primaryColor: THEME_COLORS.primary,
954
+ secondaryColor: THEME_COLORS.secondary,
955
+ backgroundColor: THEME_COLORS.background,
956
+ cardBackgroundColor: THEME_COLORS.cardBackground,
957
+ textColor: THEME_COLORS.text,
958
+ mutedTextColor: THEME_COLORS.mutedText,
959
+ borderColor: THEME_COLORS.border,
960
+ successColor: THEME_COLORS.success,
961
+ errorColor: THEME_COLORS.error,
962
+ hoverColor: THEME_COLORS.hover,
963
+ borderRadius: THEME_SIZING.borderRadius,
964
+ fontFamily: THEME_FONTS.family
965
+ };
966
+ function mergeTheme(theme) {
967
+ return { ...defaultTheme, ...theme };
968
+ }
969
+ var themePresets = {
970
+ /** Default dark theme */
971
+ dark: defaultTheme,
972
+ /** Light theme variant */
973
+ light: {
974
+ ...defaultTheme,
975
+ backgroundColor: "rgba(255, 255, 255, 0.95)",
976
+ cardBackgroundColor: "rgba(245, 245, 245, 0.9)",
977
+ textColor: "#1a1a2e",
978
+ mutedTextColor: "rgba(0, 0, 0, 0.54)",
979
+ borderColor: "rgba(0, 0, 0, 0.1)",
980
+ hoverColor: "rgba(0, 0, 0, 0.05)"
981
+ },
982
+ /** Blue accent theme */
983
+ blue: {
984
+ ...defaultTheme,
985
+ primaryColor: "#3b82f6",
986
+ secondaryColor: "#06b6d4"
987
+ },
988
+ /** Green accent theme */
989
+ green: {
990
+ ...defaultTheme,
991
+ primaryColor: "#10b981",
992
+ secondaryColor: "#34d399"
993
+ }
994
+ };
995
+
996
+ // src/icons.tsx
997
+ import { jsx, jsxs } from "react/jsx-runtime";
998
+ function ChevronDownIcon({
999
+ size = 16,
1000
+ color = "currentColor",
1001
+ className,
1002
+ style
1003
+ }) {
1004
+ return /* @__PURE__ */ jsx(
1005
+ "svg",
1006
+ {
1007
+ width: size,
1008
+ height: size,
1009
+ viewBox: "0 0 24 24",
1010
+ fill: "none",
1011
+ stroke: color,
1012
+ className,
1013
+ style,
1014
+ "aria-hidden": "true",
1015
+ children: /* @__PURE__ */ jsx(
1016
+ "path",
1017
+ {
1018
+ strokeLinecap: "round",
1019
+ strokeLinejoin: "round",
1020
+ strokeWidth: 2,
1021
+ d: "M19 9l-7 7-7-7"
1022
+ }
1023
+ )
1024
+ }
1025
+ );
1026
+ }
1027
+ function SwapIcon({
1028
+ size = 20,
1029
+ color = "currentColor",
1030
+ className,
1031
+ style
1032
+ }) {
1033
+ return /* @__PURE__ */ jsx(
1034
+ "svg",
1035
+ {
1036
+ width: size,
1037
+ height: size,
1038
+ viewBox: "0 0 24 24",
1039
+ fill: "none",
1040
+ stroke: color,
1041
+ className,
1042
+ style,
1043
+ "aria-hidden": "true",
1044
+ children: /* @__PURE__ */ jsx(
1045
+ "path",
1046
+ {
1047
+ strokeLinecap: "round",
1048
+ strokeLinejoin: "round",
1049
+ strokeWidth: 2,
1050
+ d: "M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"
1051
+ }
1052
+ )
1053
+ }
1054
+ );
1055
+ }
1056
+ function SpinnerIcon({
1057
+ size = 20,
1058
+ color = "currentColor",
1059
+ className,
1060
+ style
1061
+ }) {
1062
+ return /* @__PURE__ */ jsxs(
1063
+ "svg",
1064
+ {
1065
+ width: size,
1066
+ height: size,
1067
+ viewBox: "0 0 24 24",
1068
+ fill: "none",
1069
+ stroke: color,
1070
+ className,
1071
+ style: {
1072
+ animation: "cc-spin 1s linear infinite",
1073
+ ...style
1074
+ },
1075
+ "aria-hidden": "true",
1076
+ children: [
1077
+ /* @__PURE__ */ jsx("style", { children: `@keyframes cc-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }` }),
1078
+ /* @__PURE__ */ jsx(
1079
+ "circle",
1080
+ {
1081
+ cx: "12",
1082
+ cy: "12",
1083
+ r: "10",
1084
+ strokeWidth: 2,
1085
+ strokeDasharray: "32",
1086
+ strokeLinecap: "round"
1087
+ }
1088
+ )
1089
+ ]
1090
+ }
1091
+ );
1092
+ }
1093
+ function CheckIcon({
1094
+ size = 20,
1095
+ color = "currentColor",
1096
+ className,
1097
+ style
1098
+ }) {
1099
+ return /* @__PURE__ */ jsx(
1100
+ "svg",
1101
+ {
1102
+ width: size,
1103
+ height: size,
1104
+ viewBox: "0 0 24 24",
1105
+ fill: "none",
1106
+ stroke: color,
1107
+ className,
1108
+ style,
1109
+ "aria-hidden": "true",
1110
+ children: /* @__PURE__ */ jsx(
1111
+ "path",
1112
+ {
1113
+ strokeLinecap: "round",
1114
+ strokeLinejoin: "round",
1115
+ strokeWidth: 2,
1116
+ d: "M5 13l4 4L19 7"
1117
+ }
1118
+ )
1119
+ }
1120
+ );
1121
+ }
1122
+ function ErrorIcon({
1123
+ size = 20,
1124
+ color = "currentColor",
1125
+ className,
1126
+ style
1127
+ }) {
1128
+ return /* @__PURE__ */ jsx(
1129
+ "svg",
1130
+ {
1131
+ width: size,
1132
+ height: size,
1133
+ viewBox: "0 0 24 24",
1134
+ fill: "none",
1135
+ stroke: color,
1136
+ className,
1137
+ style,
1138
+ "aria-hidden": "true",
1139
+ children: /* @__PURE__ */ jsx(
1140
+ "path",
1141
+ {
1142
+ strokeLinecap: "round",
1143
+ strokeLinejoin: "round",
1144
+ strokeWidth: 2,
1145
+ d: "M6 18L18 6M6 6l12 12"
1146
+ }
1147
+ )
1148
+ }
1149
+ );
1150
+ }
1151
+ function ExternalLinkIcon({
1152
+ size = 16,
1153
+ color = "currentColor",
1154
+ className,
1155
+ style
1156
+ }) {
1157
+ return /* @__PURE__ */ jsx(
1158
+ "svg",
1159
+ {
1160
+ width: size,
1161
+ height: size,
1162
+ viewBox: "0 0 24 24",
1163
+ fill: "none",
1164
+ stroke: color,
1165
+ className,
1166
+ style,
1167
+ "aria-hidden": "true",
1168
+ children: /* @__PURE__ */ jsx(
1169
+ "path",
1170
+ {
1171
+ strokeLinecap: "round",
1172
+ strokeLinejoin: "round",
1173
+ strokeWidth: 2,
1174
+ d: "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
1175
+ }
1176
+ )
1177
+ }
1178
+ );
1179
+ }
1180
+ function WalletIcon({
1181
+ size = 20,
1182
+ color = "currentColor",
1183
+ className,
1184
+ style
1185
+ }) {
1186
+ return /* @__PURE__ */ jsx(
1187
+ "svg",
1188
+ {
1189
+ width: size,
1190
+ height: size,
1191
+ viewBox: "0 0 24 24",
1192
+ fill: "none",
1193
+ stroke: color,
1194
+ className,
1195
+ style,
1196
+ "aria-hidden": "true",
1197
+ children: /* @__PURE__ */ jsx(
1198
+ "path",
1199
+ {
1200
+ strokeLinecap: "round",
1201
+ strokeLinejoin: "round",
1202
+ strokeWidth: 2,
1203
+ d: "M3 10h18M3 6h18a2 2 0 012 2v10a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2zm14 6h.01"
1204
+ }
1205
+ )
1206
+ }
1207
+ );
1208
+ }
1209
+
1210
+ // src/BridgeWidget.tsx
1211
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1212
+ function ChainIcon({
1213
+ chainConfig,
1214
+ theme,
1215
+ size = 24
1216
+ }) {
1217
+ const [hasError, setHasError] = useState3(false);
1218
+ if (!chainConfig.iconUrl || hasError) {
1219
+ return /* @__PURE__ */ jsx2(
1220
+ "div",
1221
+ {
1222
+ style: {
1223
+ width: `${size}px`,
1224
+ height: `${size}px`,
1225
+ borderRadius: "50%",
1226
+ display: "flex",
1227
+ alignItems: "center",
1228
+ justifyContent: "center",
1229
+ fontSize: `${size * 0.5}px`,
1230
+ fontWeight: "bold",
1231
+ color: theme.textColor,
1232
+ background: `linear-gradient(135deg, ${theme.primaryColor}, ${theme.secondaryColor})`
1233
+ },
1234
+ "aria-hidden": "true",
1235
+ children: chainConfig.chain.name.charAt(0)
1236
+ }
1237
+ );
1238
+ }
1239
+ return /* @__PURE__ */ jsx2(
1240
+ "img",
1241
+ {
1242
+ src: chainConfig.iconUrl,
1243
+ alt: "",
1244
+ "aria-hidden": "true",
1245
+ style: { width: `${size}px`, height: `${size}px`, borderRadius: "50%" },
1246
+ onError: () => setHasError(true)
1247
+ }
1248
+ );
1249
+ }
1250
+ function BalanceSpinner({ size = 12 }) {
1251
+ return /* @__PURE__ */ jsxs2(
1252
+ "svg",
1253
+ {
1254
+ width: size,
1255
+ height: size,
1256
+ viewBox: "0 0 24 24",
1257
+ fill: "none",
1258
+ stroke: "currentColor",
1259
+ strokeWidth: "2",
1260
+ style: {
1261
+ animation: "cc-balance-spin 1s linear infinite",
1262
+ opacity: 0.6
1263
+ },
1264
+ "aria-hidden": "true",
1265
+ children: [
1266
+ /* @__PURE__ */ jsx2("style", { children: `@keyframes cc-balance-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }` }),
1267
+ /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "10", strokeOpacity: "0.25" }),
1268
+ /* @__PURE__ */ jsx2("path", { d: "M12 2a10 10 0 0 1 10 10", strokeLinecap: "round" })
1269
+ ]
1270
+ }
1271
+ );
1272
+ }
1273
+ function ChainSelector({
1274
+ label,
1275
+ chains,
1276
+ selectedChain,
1277
+ onSelect,
1278
+ excludeChainId,
1279
+ theme,
1280
+ id,
1281
+ balances,
1282
+ isLoadingBalances,
1283
+ disabled
1284
+ }) {
1285
+ const [isOpen, setIsOpen] = useState3(false);
1286
+ const [focusedIndex, setFocusedIndex] = useState3(-1);
1287
+ const [typeAhead, setTypeAhead] = useState3("");
1288
+ const typeAheadTimeoutRef = useRef2(null);
1289
+ const buttonRef = useRef2(null);
1290
+ const listRef = useRef2(null);
1291
+ const availableChains = chains.filter(
1292
+ (c) => c.chain.id !== excludeChainId
1293
+ );
1294
+ useEffect3(() => {
1295
+ return () => {
1296
+ if (typeAheadTimeoutRef.current) {
1297
+ clearTimeout(typeAheadTimeoutRef.current);
1298
+ }
1299
+ };
1300
+ }, []);
1301
+ const handleButtonKeyDown = useCallback3(
1302
+ (e) => {
1303
+ if (e.key === "Escape") {
1304
+ setIsOpen(false);
1305
+ buttonRef.current?.focus();
1306
+ } else if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
1307
+ if (!isOpen) {
1308
+ e.preventDefault();
1309
+ setIsOpen(true);
1310
+ setFocusedIndex(0);
1311
+ }
1312
+ }
1313
+ },
1314
+ [isOpen]
1315
+ );
1316
+ const handleListKeyDown = useCallback3(
1317
+ (e) => {
1318
+ if (e.key === "Escape") {
1319
+ setIsOpen(false);
1320
+ setTypeAhead("");
1321
+ buttonRef.current?.focus();
1322
+ } else if (e.key === "ArrowDown") {
1323
+ e.preventDefault();
1324
+ setFocusedIndex(
1325
+ (prev) => prev < availableChains.length - 1 ? prev + 1 : 0
1326
+ );
1327
+ } else if (e.key === "ArrowUp") {
1328
+ e.preventDefault();
1329
+ setFocusedIndex(
1330
+ (prev) => prev > 0 ? prev - 1 : availableChains.length - 1
1331
+ );
1332
+ } else if (e.key === "Enter" || e.key === " ") {
1333
+ e.preventDefault();
1334
+ if (focusedIndex >= 0 && focusedIndex < availableChains.length) {
1335
+ onSelect(availableChains[focusedIndex]);
1336
+ setIsOpen(false);
1337
+ setTypeAhead("");
1338
+ buttonRef.current?.focus();
1339
+ }
1340
+ } else if (e.key === "Home") {
1341
+ e.preventDefault();
1342
+ setFocusedIndex(0);
1343
+ } else if (e.key === "End") {
1344
+ e.preventDefault();
1345
+ setFocusedIndex(availableChains.length - 1);
1346
+ } else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
1347
+ e.preventDefault();
1348
+ const newTypeAhead = typeAhead + e.key.toLowerCase();
1349
+ setTypeAhead(newTypeAhead);
1350
+ if (typeAheadTimeoutRef.current) {
1351
+ clearTimeout(typeAheadTimeoutRef.current);
1352
+ }
1353
+ typeAheadTimeoutRef.current = setTimeout(() => {
1354
+ setTypeAhead("");
1355
+ }, 1e3);
1356
+ const matchIndex = availableChains.findIndex(
1357
+ (chain) => chain.chain.name.toLowerCase().startsWith(newTypeAhead)
1358
+ );
1359
+ if (matchIndex !== -1) {
1360
+ setFocusedIndex(matchIndex);
1361
+ }
1362
+ }
1363
+ },
1364
+ [availableChains, focusedIndex, onSelect, typeAhead]
1365
+ );
1366
+ useEffect3(() => {
1367
+ if (isOpen && listRef.current) {
1368
+ listRef.current.focus();
1369
+ }
1370
+ }, [isOpen]);
1371
+ useEffect3(() => {
1372
+ if (!isOpen) return;
1373
+ const handleGlobalKeyDown = (e) => {
1374
+ if (e.key === "Escape") {
1375
+ setIsOpen(false);
1376
+ setTypeAhead("");
1377
+ buttonRef.current?.focus();
1378
+ }
1379
+ };
1380
+ document.addEventListener("keydown", handleGlobalKeyDown);
1381
+ return () => document.removeEventListener("keydown", handleGlobalKeyDown);
1382
+ }, [isOpen]);
1383
+ useEffect3(() => {
1384
+ if (!isOpen) {
1385
+ setTypeAhead("");
1386
+ if (typeAheadTimeoutRef.current) {
1387
+ clearTimeout(typeAheadTimeoutRef.current);
1388
+ typeAheadTimeoutRef.current = null;
1389
+ }
1390
+ }
1391
+ }, [isOpen]);
1392
+ const buttonId = `${id}-button`;
1393
+ const listboxId = `${id}-listbox`;
1394
+ const selectedBalance = balances?.[selectedChain.chain.id];
1395
+ return /* @__PURE__ */ jsxs2("div", { style: { position: "relative", flex: 1 }, children: [
1396
+ /* @__PURE__ */ jsx2(
1397
+ "label",
1398
+ {
1399
+ id: `${id}-label`,
1400
+ htmlFor: buttonId,
1401
+ style: {
1402
+ display: "block",
1403
+ fontSize: "10px",
1404
+ color: theme.mutedTextColor,
1405
+ textTransform: "uppercase",
1406
+ letterSpacing: "0.05em",
1407
+ fontWeight: 500,
1408
+ marginBottom: "4px"
1409
+ },
1410
+ children: label
1411
+ }
1412
+ ),
1413
+ /* @__PURE__ */ jsxs2(
1414
+ "button",
1415
+ {
1416
+ ref: buttonRef,
1417
+ id: buttonId,
1418
+ onClick: () => !disabled && setIsOpen(!isOpen),
1419
+ onKeyDown: disabled ? void 0 : handleButtonKeyDown,
1420
+ disabled,
1421
+ "aria-haspopup": "listbox",
1422
+ "aria-expanded": isOpen,
1423
+ "aria-labelledby": `${id}-label`,
1424
+ "aria-controls": isOpen ? listboxId : void 0,
1425
+ "aria-disabled": disabled,
1426
+ style: {
1427
+ width: "100%",
1428
+ display: "flex",
1429
+ alignItems: "center",
1430
+ justifyContent: "space-between",
1431
+ padding: "10px 12px",
1432
+ borderRadius: `${theme.borderRadius}px`,
1433
+ background: theme.cardBackgroundColor,
1434
+ border: `1px solid ${theme.borderColor}`,
1435
+ cursor: disabled ? "not-allowed" : "pointer",
1436
+ opacity: disabled ? 0.6 : 1,
1437
+ transition: "all 0.2s"
1438
+ },
1439
+ children: [
1440
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
1441
+ /* @__PURE__ */ jsx2(ChainIcon, { chainConfig: selectedChain, theme }),
1442
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-start" }, children: [
1443
+ /* @__PURE__ */ jsx2(
1444
+ "span",
1445
+ {
1446
+ style: {
1447
+ fontSize: "14px",
1448
+ fontWeight: 500,
1449
+ color: theme.textColor
1450
+ },
1451
+ children: selectedChain.chain.name
1452
+ }
1453
+ ),
1454
+ isLoadingBalances ? /* @__PURE__ */ jsxs2(
1455
+ "span",
1456
+ {
1457
+ style: {
1458
+ fontSize: "10px",
1459
+ color: theme.mutedTextColor,
1460
+ display: "flex",
1461
+ alignItems: "center",
1462
+ gap: "4px"
1463
+ },
1464
+ children: [
1465
+ /* @__PURE__ */ jsx2(BalanceSpinner, { size: 10 }),
1466
+ " Loading..."
1467
+ ]
1468
+ }
1469
+ ) : selectedBalance ? /* @__PURE__ */ jsxs2(
1470
+ "span",
1471
+ {
1472
+ style: {
1473
+ fontSize: "10px",
1474
+ color: theme.mutedTextColor
1475
+ },
1476
+ children: [
1477
+ formatNumber(selectedBalance.formatted, 2),
1478
+ " USDC"
1479
+ ]
1480
+ }
1481
+ ) : null
1482
+ ] })
1483
+ ] }),
1484
+ /* @__PURE__ */ jsx2(
1485
+ ChevronDownIcon,
1486
+ {
1487
+ size: 16,
1488
+ color: theme.mutedTextColor,
1489
+ style: {
1490
+ transition: "transform 0.2s",
1491
+ transform: isOpen ? "rotate(180deg)" : "rotate(0deg)"
1492
+ }
1493
+ }
1494
+ )
1495
+ ]
1496
+ }
1497
+ ),
1498
+ isOpen && /* @__PURE__ */ jsxs2(Fragment, { children: [
1499
+ /* @__PURE__ */ jsx2(
1500
+ "div",
1501
+ {
1502
+ style: {
1503
+ position: "fixed",
1504
+ inset: 0,
1505
+ zIndex: 10
1506
+ },
1507
+ onClick: () => setIsOpen(false),
1508
+ "aria-hidden": "true"
1509
+ }
1510
+ ),
1511
+ /* @__PURE__ */ jsx2(
1512
+ "ul",
1513
+ {
1514
+ ref: listRef,
1515
+ id: listboxId,
1516
+ role: "listbox",
1517
+ "aria-labelledby": `${id}-label`,
1518
+ "aria-activedescendant": focusedIndex >= 0 ? `${id}-option-${availableChains[focusedIndex]?.chain.id}` : void 0,
1519
+ tabIndex: 0,
1520
+ onKeyDown: handleListKeyDown,
1521
+ style: {
1522
+ position: "absolute",
1523
+ zIndex: 20,
1524
+ width: "100%",
1525
+ marginTop: "8px",
1526
+ borderRadius: `${theme.borderRadius}px`,
1527
+ boxShadow: "0 10px 40px rgba(0,0,0,0.3)",
1528
+ background: theme.cardBackgroundColor,
1529
+ backdropFilter: "blur(10px)",
1530
+ border: `1px solid ${theme.borderColor}`,
1531
+ maxHeight: "300px",
1532
+ overflowY: "auto",
1533
+ overflowX: "hidden",
1534
+ padding: 0,
1535
+ margin: 0,
1536
+ listStyle: "none",
1537
+ outline: "none"
1538
+ },
1539
+ children: availableChains.map((chainConfig, index) => {
1540
+ const chainBalance = balances?.[chainConfig.chain.id];
1541
+ const isFocused = index === focusedIndex;
1542
+ const isSelected = chainConfig.chain.id === selectedChain.chain.id;
1543
+ return /* @__PURE__ */ jsxs2(
1544
+ "li",
1545
+ {
1546
+ id: `${id}-option-${chainConfig.chain.id}`,
1547
+ role: "option",
1548
+ "aria-selected": isSelected,
1549
+ onClick: () => {
1550
+ onSelect(chainConfig);
1551
+ setIsOpen(false);
1552
+ buttonRef.current?.focus();
1553
+ },
1554
+ style: {
1555
+ width: "100%",
1556
+ display: "flex",
1557
+ alignItems: "center",
1558
+ gap: "8px",
1559
+ padding: "10px 12px",
1560
+ background: isFocused ? theme.hoverColor : "transparent",
1561
+ border: "none",
1562
+ cursor: "pointer",
1563
+ transition: "background 0.2s"
1564
+ },
1565
+ onMouseEnter: () => setFocusedIndex(index),
1566
+ children: [
1567
+ /* @__PURE__ */ jsx2(ChainIcon, { chainConfig, theme }),
1568
+ /* @__PURE__ */ jsxs2("div", { style: { flex: 1, display: "flex", flexDirection: "column" }, children: [
1569
+ /* @__PURE__ */ jsx2(
1570
+ "span",
1571
+ {
1572
+ style: {
1573
+ fontSize: "14px",
1574
+ color: isSelected ? theme.textColor : theme.mutedTextColor,
1575
+ fontWeight: isSelected ? 500 : 400
1576
+ },
1577
+ children: chainConfig.chain.name
1578
+ }
1579
+ ),
1580
+ isLoadingBalances ? /* @__PURE__ */ jsx2(
1581
+ "span",
1582
+ {
1583
+ style: {
1584
+ fontSize: "10px",
1585
+ color: theme.mutedTextColor,
1586
+ display: "flex",
1587
+ alignItems: "center",
1588
+ gap: "4px"
1589
+ },
1590
+ children: /* @__PURE__ */ jsx2(BalanceSpinner, { size: 10 })
1591
+ }
1592
+ ) : chainBalance ? /* @__PURE__ */ jsxs2(
1593
+ "span",
1594
+ {
1595
+ style: {
1596
+ fontSize: "10px",
1597
+ color: parseFloat(chainBalance.formatted) > 0 ? theme.successColor : theme.mutedTextColor
1598
+ },
1599
+ children: [
1600
+ formatNumber(chainBalance.formatted, 2),
1601
+ " USDC"
1602
+ ]
1603
+ }
1604
+ ) : /* @__PURE__ */ jsx2(
1605
+ "span",
1606
+ {
1607
+ style: {
1608
+ fontSize: "10px",
1609
+ color: theme.mutedTextColor
1610
+ },
1611
+ children: "0.00 USDC"
1612
+ }
1613
+ )
1614
+ ] })
1615
+ ]
1616
+ },
1617
+ chainConfig.chain.id
1618
+ );
1619
+ })
1620
+ }
1621
+ )
1622
+ ] })
1623
+ ] });
1624
+ }
1625
+ function SwapButton({
1626
+ onClick,
1627
+ theme,
1628
+ disabled
1629
+ }) {
1630
+ const [isHovered, setIsHovered] = useState3(false);
1631
+ return /* @__PURE__ */ jsx2(
1632
+ "button",
1633
+ {
1634
+ onClick,
1635
+ disabled,
1636
+ "aria-label": "Swap source and destination chains",
1637
+ style: {
1638
+ padding: "8px",
1639
+ borderRadius: `${theme.borderRadius}px`,
1640
+ background: `${theme.primaryColor}15`,
1641
+ border: `1px solid ${theme.primaryColor}40`,
1642
+ cursor: disabled ? "not-allowed" : "pointer",
1643
+ opacity: disabled ? 0.5 : 1,
1644
+ transition: "all 0.2s",
1645
+ display: "flex",
1646
+ alignItems: "center",
1647
+ justifyContent: "center",
1648
+ alignSelf: "flex-end",
1649
+ marginBottom: "4px",
1650
+ transform: isHovered && !disabled ? "scale(1.1)" : "scale(1)"
1651
+ },
1652
+ onMouseEnter: () => setIsHovered(true),
1653
+ onMouseLeave: () => setIsHovered(false),
1654
+ children: /* @__PURE__ */ jsx2(SwapIcon, { size: 20, color: theme.primaryColor })
1655
+ }
1656
+ );
1657
+ }
1658
+ function AmountInput({
1659
+ value,
1660
+ onChange,
1661
+ balance,
1662
+ onMaxClick,
1663
+ theme,
1664
+ id,
1665
+ disabled
1666
+ }) {
1667
+ const inputId = `${id}-input`;
1668
+ const labelId = `${id}-label`;
1669
+ const handleInputChange = useCallback3(
1670
+ (e) => {
1671
+ if (disabled) return;
1672
+ const result = validateAmountInput(e.target.value);
1673
+ if (result.isValid) {
1674
+ onChange(result.sanitized);
1675
+ } else if (result.sanitized) {
1676
+ onChange(result.sanitized);
1677
+ }
1678
+ },
1679
+ [onChange, disabled]
1680
+ );
1681
+ const handleKeyDown = useCallback3((e) => {
1682
+ if (e.key === "e" || e.key === "E" || e.key === "+" || e.key === "-") {
1683
+ e.preventDefault();
1684
+ }
1685
+ }, []);
1686
+ return /* @__PURE__ */ jsxs2("div", { children: [
1687
+ /* @__PURE__ */ jsxs2(
1688
+ "div",
1689
+ {
1690
+ style: {
1691
+ display: "flex",
1692
+ justifyContent: "space-between",
1693
+ marginBottom: "4px"
1694
+ },
1695
+ children: [
1696
+ /* @__PURE__ */ jsx2(
1697
+ "label",
1698
+ {
1699
+ id: labelId,
1700
+ htmlFor: inputId,
1701
+ style: {
1702
+ fontSize: "10px",
1703
+ color: theme.mutedTextColor,
1704
+ textTransform: "uppercase",
1705
+ letterSpacing: "0.05em",
1706
+ fontWeight: 500
1707
+ },
1708
+ children: "Amount"
1709
+ }
1710
+ ),
1711
+ /* @__PURE__ */ jsxs2(
1712
+ "span",
1713
+ {
1714
+ style: { fontSize: "10px", color: theme.mutedTextColor },
1715
+ "aria-live": "polite",
1716
+ children: [
1717
+ "Balance:",
1718
+ " ",
1719
+ /* @__PURE__ */ jsxs2("span", { style: { color: theme.textColor }, children: [
1720
+ formatNumber(balance),
1721
+ " USDC"
1722
+ ] })
1723
+ ]
1724
+ }
1725
+ )
1726
+ ]
1727
+ }
1728
+ ),
1729
+ /* @__PURE__ */ jsxs2(
1730
+ "div",
1731
+ {
1732
+ style: {
1733
+ display: "flex",
1734
+ alignItems: "center",
1735
+ borderRadius: `${theme.borderRadius}px`,
1736
+ overflow: "hidden",
1737
+ background: theme.cardBackgroundColor,
1738
+ border: `1px solid ${theme.borderColor}`,
1739
+ opacity: disabled ? 0.6 : 1
1740
+ },
1741
+ children: [
1742
+ /* @__PURE__ */ jsx2(
1743
+ "input",
1744
+ {
1745
+ id: inputId,
1746
+ type: "text",
1747
+ inputMode: "decimal",
1748
+ pattern: "[0-9]*\\.?[0-9]*",
1749
+ value,
1750
+ onChange: handleInputChange,
1751
+ onKeyDown: handleKeyDown,
1752
+ placeholder: "0.00",
1753
+ disabled,
1754
+ "aria-labelledby": labelId,
1755
+ "aria-describedby": `${id}-currency`,
1756
+ "aria-disabled": disabled,
1757
+ style: {
1758
+ flex: 1,
1759
+ background: "transparent",
1760
+ border: "none",
1761
+ padding: "12px",
1762
+ fontSize: "18px",
1763
+ color: theme.textColor,
1764
+ fontWeight: 500,
1765
+ outline: "none",
1766
+ minWidth: 0,
1767
+ fontFamily: theme.fontFamily,
1768
+ cursor: disabled ? "not-allowed" : "text"
1769
+ }
1770
+ }
1771
+ ),
1772
+ /* @__PURE__ */ jsxs2(
1773
+ "div",
1774
+ {
1775
+ style: {
1776
+ display: "flex",
1777
+ alignItems: "center",
1778
+ gap: "8px",
1779
+ paddingRight: "12px"
1780
+ },
1781
+ children: [
1782
+ /* @__PURE__ */ jsx2(
1783
+ "button",
1784
+ {
1785
+ onClick: onMaxClick,
1786
+ disabled,
1787
+ "aria-label": "Set maximum amount",
1788
+ style: {
1789
+ padding: "4px 8px",
1790
+ fontSize: "10px",
1791
+ fontWeight: 600,
1792
+ borderRadius: "4px",
1793
+ background: `${theme.primaryColor}20`,
1794
+ color: theme.primaryColor,
1795
+ border: "none",
1796
+ cursor: disabled ? "not-allowed" : "pointer",
1797
+ opacity: disabled ? 0.5 : 1
1798
+ },
1799
+ children: "MAX"
1800
+ }
1801
+ ),
1802
+ /* @__PURE__ */ jsxs2(
1803
+ "div",
1804
+ {
1805
+ id: `${id}-currency`,
1806
+ style: { display: "flex", alignItems: "center", gap: "4px" },
1807
+ children: [
1808
+ /* @__PURE__ */ jsx2(
1809
+ "div",
1810
+ {
1811
+ style: {
1812
+ width: "20px",
1813
+ height: "20px",
1814
+ borderRadius: "50%",
1815
+ background: USDC_BRAND_COLOR,
1816
+ display: "flex",
1817
+ alignItems: "center",
1818
+ justifyContent: "center"
1819
+ },
1820
+ "aria-hidden": "true",
1821
+ children: /* @__PURE__ */ jsx2(
1822
+ "span",
1823
+ {
1824
+ style: {
1825
+ fontSize: "10px",
1826
+ fontWeight: "bold",
1827
+ color: "#fff"
1828
+ },
1829
+ children: "$"
1830
+ }
1831
+ )
1832
+ }
1833
+ ),
1834
+ /* @__PURE__ */ jsx2(
1835
+ "span",
1836
+ {
1837
+ style: {
1838
+ fontSize: "14px",
1839
+ fontWeight: 500,
1840
+ color: theme.textColor
1841
+ },
1842
+ children: "USDC"
1843
+ }
1844
+ )
1845
+ ]
1846
+ }
1847
+ )
1848
+ ]
1849
+ }
1850
+ )
1851
+ ]
1852
+ }
1853
+ )
1854
+ ] });
1855
+ }
1856
+ function BridgeWidget({
1857
+ chains = DEFAULT_CHAIN_CONFIGS,
1858
+ defaultSourceChainId,
1859
+ defaultDestinationChainId,
1860
+ onBridgeStart,
1861
+ onBridgeSuccess,
1862
+ onBridgeError,
1863
+ onConnectWallet,
1864
+ theme: themeOverrides,
1865
+ className,
1866
+ style
1867
+ }) {
1868
+ const theme = mergeTheme(themeOverrides);
1869
+ const { address, isConnected } = useAccount3();
1870
+ const currentChainId = useChainId();
1871
+ const { switchChainAsync } = useSwitchChain();
1872
+ const { connect, connectors } = useConnect();
1873
+ const [configError, setConfigError] = useState3(null);
1874
+ useEffect3(() => {
1875
+ const validation = validateChainConfigs(chains);
1876
+ if (!validation.isValid) {
1877
+ const errorMsg = validation.errors.join("; ");
1878
+ console.error("[BridgeWidget] Invalid chain configuration:", errorMsg);
1879
+ setConfigError(errorMsg);
1880
+ } else {
1881
+ setConfigError(null);
1882
+ }
1883
+ }, [chains]);
1884
+ const baseId = useId();
1885
+ const sourceChainId = `${baseId}-source`;
1886
+ const destChainId = `${baseId}-dest`;
1887
+ const amountId = `${baseId}-amount`;
1888
+ const getChainConfig = useCallback3(
1889
+ (chainId) => {
1890
+ if (!chainId) return chains[0];
1891
+ return chains.find((c) => c.chain.id === chainId) || chains[0];
1892
+ },
1893
+ [chains]
1894
+ );
1895
+ const [sourceChainConfig, setSourceChainConfig] = useState3(
1896
+ () => getChainConfig(defaultSourceChainId)
1897
+ );
1898
+ const [destChainConfig, setDestChainConfig] = useState3(
1899
+ () => getChainConfig(defaultDestinationChainId) || chains.find((c) => c.chain.id !== sourceChainConfig.chain.id) || chains[1] || chains[0]
1900
+ );
1901
+ const [amount, setAmount] = useState3("");
1902
+ const [txHash, setTxHash] = useState3();
1903
+ const [error, setError] = useState3(null);
1904
+ const { balances: allBalances, isLoading: isLoadingAllBalances, refetch: refetchAllBalances } = useAllUSDCBalances(chains);
1905
+ const { balanceFormatted, refetch: refetchBalance } = useUSDCBalance(
1906
+ sourceChainConfig
1907
+ );
1908
+ const { needsApproval, approve, isApproving } = useUSDCAllowance(
1909
+ sourceChainConfig
1910
+ );
1911
+ const refetchBalances = useCallback3(() => {
1912
+ refetchBalance();
1913
+ refetchAllBalances();
1914
+ }, [refetchBalance, refetchAllBalances]);
1915
+ const { bridge: executeBridge, state: bridgeState, reset: resetBridge } = useBridge();
1916
+ const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt2({
1917
+ hash: txHash
1918
+ });
1919
+ const isBridging = bridgeState.status === "loading" || bridgeState.status === "approving" || bridgeState.status === "burning" || bridgeState.status === "fetching-attestation" || bridgeState.status === "minting";
1920
+ const isOperationPending = isBridging || isConfirming || isApproving;
1921
+ const onBridgeSuccessRef = useRef2(onBridgeSuccess);
1922
+ onBridgeSuccessRef.current = onBridgeSuccess;
1923
+ const onBridgeErrorRef = useRef2(onBridgeError);
1924
+ onBridgeErrorRef.current = onBridgeError;
1925
+ const needsChainSwitch = isConnected && currentChainId !== sourceChainConfig.chain.id;
1926
+ const handleSwapChains = useCallback3(() => {
1927
+ setSourceChainConfig((prev) => {
1928
+ setDestChainConfig(prev);
1929
+ return destChainConfig;
1930
+ });
1931
+ }, [destChainConfig]);
1932
+ const handleMaxClick = useCallback3(() => {
1933
+ setAmount(balanceFormatted);
1934
+ }, [balanceFormatted]);
1935
+ const handleSwitchChain = useCallback3(async () => {
1936
+ try {
1937
+ await switchChainAsync({ chainId: sourceChainConfig.chain.id });
1938
+ } catch (err) {
1939
+ setError(getErrorMessage(err));
1940
+ }
1941
+ }, [switchChainAsync, sourceChainConfig.chain.id]);
1942
+ const handleBridge = useCallback3(async () => {
1943
+ if (!address || !amount || parseFloat(amount) <= 0) return;
1944
+ setError(null);
1945
+ resetBridge();
1946
+ try {
1947
+ onBridgeStart?.({
1948
+ sourceChainId: sourceChainConfig.chain.id,
1949
+ destChainId: destChainConfig.chain.id,
1950
+ amount
1951
+ });
1952
+ if (needsApproval(amount)) {
1953
+ pendingBridgeRef.current = {
1954
+ amount,
1955
+ sourceChainConfig,
1956
+ destChainConfig
1957
+ };
1958
+ const approveTx = await approve(amount);
1959
+ setTxHash(approveTx);
1960
+ } else {
1961
+ await executeBridge({
1962
+ sourceChainConfig,
1963
+ destChainConfig,
1964
+ amount
1965
+ });
1966
+ }
1967
+ } catch (err) {
1968
+ const errorMessage = getErrorMessage(err);
1969
+ setError(errorMessage);
1970
+ onBridgeErrorRef.current?.(
1971
+ err instanceof Error ? err : new Error(errorMessage)
1972
+ );
1973
+ }
1974
+ }, [
1975
+ address,
1976
+ amount,
1977
+ needsApproval,
1978
+ approve,
1979
+ executeBridge,
1980
+ resetBridge,
1981
+ onBridgeStart,
1982
+ sourceChainConfig,
1983
+ destChainConfig
1984
+ ]);
1985
+ const pendingBridgeRef = useRef2(null);
1986
+ useEffect3(() => {
1987
+ if (isSuccess && txHash && pendingBridgeRef.current) {
1988
+ const { amount: pendingAmount, sourceChainConfig: pendingSource, destChainConfig: pendingDest } = pendingBridgeRef.current;
1989
+ pendingBridgeRef.current = null;
1990
+ setTxHash(void 0);
1991
+ void executeBridge({
1992
+ sourceChainConfig: pendingSource,
1993
+ destChainConfig: pendingDest,
1994
+ amount: pendingAmount
1995
+ }).catch((err) => {
1996
+ setError(getErrorMessage(err));
1997
+ });
1998
+ }
1999
+ }, [isSuccess, txHash, executeBridge]);
2000
+ useEffect3(() => {
2001
+ if (bridgeState.status === "success") {
2002
+ const currentAmount = amount;
2003
+ const currentSourceChainId = sourceChainConfig.chain.id;
2004
+ const currentDestChainId = destChainConfig.chain.id;
2005
+ const currentTxHash = bridgeState.txHash;
2006
+ setAmount("");
2007
+ refetchBalances();
2008
+ if (currentTxHash) {
2009
+ onBridgeSuccessRef.current?.({
2010
+ sourceChainId: currentSourceChainId,
2011
+ destChainId: currentDestChainId,
2012
+ amount: currentAmount,
2013
+ txHash: currentTxHash
2014
+ });
2015
+ }
2016
+ } else if (bridgeState.status === "error" && bridgeState.error) {
2017
+ setError(bridgeState.error.message);
2018
+ }
2019
+ }, [
2020
+ bridgeState.status,
2021
+ bridgeState.txHash,
2022
+ bridgeState.error,
2023
+ refetchBalances,
2024
+ amount,
2025
+ sourceChainConfig.chain.id,
2026
+ destChainConfig.chain.id
2027
+ ]);
2028
+ const isButtonDisabled = !isConnected || needsChainSwitch || !amount || parseFloat(amount) <= 0 || parseFloat(amount) > parseFloat(balanceFormatted) || isConfirming || isApproving || isBridging;
2029
+ const isButtonActuallyDisabled = isButtonDisabled && !needsChainSwitch && isConnected;
2030
+ const getButtonText = useCallback3(() => {
2031
+ if (!isConnected) return "Connect Wallet";
2032
+ if (needsChainSwitch) return `Switch to ${sourceChainConfig.chain.name}`;
2033
+ if (bridgeState.status === "loading") return "Preparing Bridge...";
2034
+ if (bridgeState.status === "approving") return "Approving...";
2035
+ if (bridgeState.status === "burning") return "Burning USDC...";
2036
+ if (bridgeState.status === "fetching-attestation") return "Fetching Attestation...";
2037
+ if (bridgeState.status === "minting") return "Minting on Destination...";
2038
+ if (isConfirming || isApproving) {
2039
+ return "Approving...";
2040
+ }
2041
+ if (!amount || parseFloat(amount) <= 0) return "Enter Amount";
2042
+ if (parseFloat(amount) > parseFloat(balanceFormatted)) {
2043
+ return "Insufficient Balance";
2044
+ }
2045
+ if (needsApproval(amount)) return "Approve & Bridge USDC";
2046
+ return "Bridge USDC";
2047
+ }, [
2048
+ isConnected,
2049
+ needsChainSwitch,
2050
+ sourceChainConfig.chain.name,
2051
+ bridgeState.status,
2052
+ isConfirming,
2053
+ isApproving,
2054
+ amount,
2055
+ balanceFormatted,
2056
+ needsApproval
2057
+ ]);
2058
+ const handleButtonClick = useCallback3(() => {
2059
+ if (!isConnected) {
2060
+ if (onConnectWallet) {
2061
+ onConnectWallet();
2062
+ } else if (connectors.length > 0) {
2063
+ const injectedConnector = connectors.find(
2064
+ (c) => c.type === "injected"
2065
+ );
2066
+ connect({ connector: injectedConnector || connectors[0] });
2067
+ }
2068
+ return;
2069
+ }
2070
+ if (needsChainSwitch) {
2071
+ handleSwitchChain();
2072
+ return;
2073
+ }
2074
+ handleBridge();
2075
+ }, [
2076
+ isConnected,
2077
+ onConnectWallet,
2078
+ connectors,
2079
+ connect,
2080
+ needsChainSwitch,
2081
+ handleSwitchChain,
2082
+ handleBridge
2083
+ ]);
2084
+ const buttonStyles = useMemo2(
2085
+ () => ({
2086
+ width: "100%",
2087
+ padding: "14px",
2088
+ borderRadius: `${theme.borderRadius}px`,
2089
+ fontSize: "14px",
2090
+ fontWeight: 600,
2091
+ border: "none",
2092
+ cursor: isButtonActuallyDisabled ? "not-allowed" : "pointer",
2093
+ transition: "all 0.2s",
2094
+ color: isButtonActuallyDisabled ? theme.mutedTextColor : theme.textColor,
2095
+ background: isButtonActuallyDisabled ? "rgba(255,255,255,0.1)" : `linear-gradient(135deg, ${theme.primaryColor} 0%, ${theme.secondaryColor} 100%)`,
2096
+ boxShadow: isButtonActuallyDisabled ? "none" : `0 4px 14px ${theme.primaryColor}60, inset 0 1px 0 rgba(255,255,255,0.2)`
2097
+ }),
2098
+ [
2099
+ theme.borderRadius,
2100
+ theme.mutedTextColor,
2101
+ theme.textColor,
2102
+ theme.primaryColor,
2103
+ theme.secondaryColor,
2104
+ isButtonActuallyDisabled
2105
+ ]
2106
+ );
2107
+ return /* @__PURE__ */ jsxs2(
2108
+ "div",
2109
+ {
2110
+ className,
2111
+ role: "region",
2112
+ "aria-label": "USDC Bridge Widget",
2113
+ style: {
2114
+ fontFamily: theme.fontFamily,
2115
+ maxWidth: "480px",
2116
+ width: "100%",
2117
+ borderRadius: `${theme.borderRadius}px`,
2118
+ padding: "16px",
2119
+ background: theme.backgroundColor,
2120
+ border: `1px solid ${theme.borderColor}`,
2121
+ boxShadow: "0 4px 24px rgba(0,0,0,0.3)",
2122
+ ...style
2123
+ },
2124
+ children: [
2125
+ /* @__PURE__ */ jsxs2(
2126
+ "div",
2127
+ {
2128
+ style: {
2129
+ display: "flex",
2130
+ alignItems: "flex-end",
2131
+ gap: "12px",
2132
+ marginBottom: "16px"
2133
+ },
2134
+ children: [
2135
+ /* @__PURE__ */ jsx2(
2136
+ ChainSelector,
2137
+ {
2138
+ id: sourceChainId,
2139
+ label: "From",
2140
+ chains,
2141
+ selectedChain: sourceChainConfig,
2142
+ onSelect: setSourceChainConfig,
2143
+ excludeChainId: destChainConfig.chain.id,
2144
+ theme,
2145
+ balances: allBalances,
2146
+ isLoadingBalances: isLoadingAllBalances,
2147
+ disabled: isOperationPending
2148
+ }
2149
+ ),
2150
+ /* @__PURE__ */ jsx2(SwapButton, { onClick: handleSwapChains, theme, disabled: isOperationPending }),
2151
+ /* @__PURE__ */ jsx2(
2152
+ ChainSelector,
2153
+ {
2154
+ id: destChainId,
2155
+ label: "To",
2156
+ chains,
2157
+ selectedChain: destChainConfig,
2158
+ onSelect: setDestChainConfig,
2159
+ excludeChainId: sourceChainConfig.chain.id,
2160
+ theme,
2161
+ balances: allBalances,
2162
+ isLoadingBalances: isLoadingAllBalances,
2163
+ disabled: isOperationPending
2164
+ }
2165
+ )
2166
+ ]
2167
+ }
2168
+ ),
2169
+ /* @__PURE__ */ jsx2("div", { style: { marginBottom: "16px" }, children: /* @__PURE__ */ jsx2(
2170
+ AmountInput,
2171
+ {
2172
+ id: amountId,
2173
+ value: amount,
2174
+ onChange: setAmount,
2175
+ balance: balanceFormatted,
2176
+ onMaxClick: handleMaxClick,
2177
+ theme,
2178
+ disabled: isOperationPending
2179
+ }
2180
+ ) }),
2181
+ configError && /* @__PURE__ */ jsxs2(
2182
+ "div",
2183
+ {
2184
+ role: "alert",
2185
+ style: {
2186
+ fontSize: "12px",
2187
+ color: theme.errorColor,
2188
+ background: `${theme.errorColor}15`,
2189
+ padding: "8px 12px",
2190
+ borderRadius: `${theme.borderRadius}px`,
2191
+ marginBottom: "16px"
2192
+ },
2193
+ children: [
2194
+ "Configuration Error: ",
2195
+ configError
2196
+ ]
2197
+ }
2198
+ ),
2199
+ error && !configError && /* @__PURE__ */ jsx2(
2200
+ "div",
2201
+ {
2202
+ role: "alert",
2203
+ style: {
2204
+ fontSize: "12px",
2205
+ color: theme.errorColor,
2206
+ background: `${theme.errorColor}15`,
2207
+ padding: "8px 12px",
2208
+ borderRadius: `${theme.borderRadius}px`,
2209
+ marginBottom: "16px"
2210
+ },
2211
+ children: error
2212
+ }
2213
+ ),
2214
+ /* @__PURE__ */ jsx2(
2215
+ "button",
2216
+ {
2217
+ onClick: handleButtonClick,
2218
+ disabled: isButtonActuallyDisabled,
2219
+ "aria-busy": isConfirming || isApproving || isBridging,
2220
+ style: buttonStyles,
2221
+ children: getButtonText()
2222
+ }
2223
+ )
2224
+ ]
2225
+ }
2226
+ );
2227
+ }
2228
+ export {
2229
+ BridgeWidget,
2230
+ CHAIN_ICONS,
2231
+ CheckIcon,
2232
+ ChevronDownIcon,
2233
+ DEFAULT_CHAIN_CONFIGS,
2234
+ DEFAULT_LOCALE,
2235
+ ErrorIcon,
2236
+ ExternalLinkIcon,
2237
+ MAX_USDC_AMOUNT,
2238
+ MIN_USDC_AMOUNT,
2239
+ SpinnerIcon,
2240
+ SwapIcon,
2241
+ TESTNET_CHAIN_CONFIGS,
2242
+ THEME_COLORS,
2243
+ THEME_FONTS,
2244
+ THEME_SIZING,
2245
+ TOKEN_MESSENGER_ADDRESSES,
2246
+ TOKEN_MESSENGER_V1_ADDRESSES,
2247
+ TOKEN_MESSENGER_V2_ADDRESS,
2248
+ USDC_ADDRESSES,
2249
+ USDC_BRAND_COLOR,
2250
+ USDC_DECIMALS,
2251
+ WalletIcon,
2252
+ arbitrum,
2253
+ arbitrumSepolia,
2254
+ avalanche,
2255
+ avalancheFuji,
2256
+ base,
2257
+ baseSepolia,
2258
+ codex,
2259
+ createChainConfig,
2260
+ createTestnetChainConfig,
2261
+ defaultTheme,
2262
+ formatNumber,
2263
+ getChainName,
2264
+ getErrorMessage,
2265
+ hyperEvm,
2266
+ ink,
2267
+ isValidPositiveAmount,
2268
+ linea,
2269
+ mainnet,
2270
+ mergeTheme,
2271
+ monad,
2272
+ optimism,
2273
+ optimismSepolia,
2274
+ parseUSDCAmount,
2275
+ plume,
2276
+ polygon,
2277
+ polygonAmoy,
2278
+ sei,
2279
+ sepolia,
2280
+ sonic,
2281
+ themePresets,
2282
+ unichain,
2283
+ useAllUSDCBalances,
2284
+ useBridge,
2285
+ useBridgeEstimate,
2286
+ useBridgeQuote,
2287
+ useFormatNumber,
2288
+ useUSDCAllowance,
2289
+ useUSDCBalance,
2290
+ validateAmountInput,
2291
+ validateChainConfig,
2292
+ validateChainConfigs,
2293
+ worldchain,
2294
+ xdc
2295
+ };