@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/README.md ADDED
@@ -0,0 +1,272 @@
1
+ # USDC Bridge Widget
2
+
3
+ A reusable React widget for cross-chain USDC transfers powered by Circle's CCTP (Cross-Chain Transfer Protocol).
4
+
5
+ ## Features
6
+
7
+ - Cross-chain USDC transfers with native token minting
8
+ - No bridge fees (only gas costs)
9
+ - Customizable theme and styling
10
+ - Built-in chain switching
11
+ - TypeScript support
12
+ - Works with any wagmi-compatible wallet
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @honeypot-finance/usdc-bridge-widget
18
+ # or
19
+ yarn add @honeypot-finance/usdc-bridge-widget
20
+ # or
21
+ pnpm add @honeypot-finance/usdc-bridge-widget
22
+ ```
23
+
24
+ ### Peer Dependencies
25
+
26
+ Make sure you have these peer dependencies installed:
27
+
28
+ ```bash
29
+ npm install react react-dom wagmi viem @tanstack/react-query
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ```tsx
35
+ import { BridgeWidget } from "@honeypot-finance/usdc-bridge-widget";
36
+ import { WagmiProvider, createConfig, http } from "wagmi";
37
+ import { mainnet, arbitrum, base, optimism, polygon } from "viem/chains";
38
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
39
+ import { injected } from "wagmi/connectors";
40
+
41
+ // Create wagmi config
42
+ const config = createConfig({
43
+ chains: [mainnet, arbitrum, base, optimism, polygon],
44
+ connectors: [injected()],
45
+ transports: {
46
+ [mainnet.id]: http(),
47
+ [arbitrum.id]: http(),
48
+ [base.id]: http(),
49
+ [optimism.id]: http(),
50
+ [polygon.id]: http(),
51
+ },
52
+ });
53
+
54
+ const queryClient = new QueryClient();
55
+
56
+ function App() {
57
+ return (
58
+ <WagmiProvider config={config}>
59
+ <QueryClientProvider client={queryClient}>
60
+ {/* Minimal - uses all 17 CCTP chains by default */}
61
+ <BridgeWidget />
62
+
63
+ {/* Or with options */}
64
+ <BridgeWidget
65
+ defaultSourceChainId={1}
66
+ defaultDestinationChainId={8453}
67
+ onBridgeSuccess={({ txHash, amount }) => {
68
+ console.log(`Bridged ${amount} USDC: ${txHash}`);
69
+ }}
70
+ />
71
+ </QueryClientProvider>
72
+ </WagmiProvider>
73
+ );
74
+ }
75
+ ```
76
+
77
+ ## Props
78
+
79
+ ### BridgeWidgetProps
80
+
81
+ | Prop | Type | Default | Description |
82
+ |------|------|---------|-------------|
83
+ | `chains` | `BridgeChainConfig[]` | All CCTP chains | Array of supported chains |
84
+ | `defaultSourceChainId` | `number` | First chain | Default source chain ID |
85
+ | `defaultDestinationChainId` | `number` | Second chain | Default destination chain ID |
86
+ | `onBridgeStart` | `function` | - | Called when bridge starts |
87
+ | `onBridgeSuccess` | `function` | - | Called on successful bridge |
88
+ | `onBridgeError` | `function` | - | Called on bridge error |
89
+ | `onConnectWallet` | `function` | - | Called when "Connect Wallet" clicked |
90
+ | `theme` | `BridgeWidgetTheme` | Default theme | Custom theme overrides |
91
+ | `className` | `string` | - | Custom CSS class |
92
+ | `style` | `CSSProperties` | - | Custom inline styles |
93
+
94
+ ### BridgeChainConfig
95
+
96
+ ```ts
97
+ interface BridgeChainConfig {
98
+ chain: Chain; // viem Chain object
99
+ usdcAddress: `0x${string}`; // USDC contract address
100
+ tokenMessengerAddress?: `0x${string}`; // Circle TokenMessenger address
101
+ iconUrl?: string; // Optional chain icon URL
102
+ }
103
+ ```
104
+
105
+ ### BridgeWidgetTheme
106
+
107
+ ```ts
108
+ interface BridgeWidgetTheme {
109
+ primaryColor?: string; // Default: "#6366f1"
110
+ secondaryColor?: string; // Default: "#a855f7"
111
+ backgroundColor?: string; // Default: "rgba(15, 15, 25, 0.8)"
112
+ cardBackgroundColor?: string;
113
+ textColor?: string; // Default: "#ffffff"
114
+ mutedTextColor?: string;
115
+ borderColor?: string;
116
+ successColor?: string; // Default: "#22c55e"
117
+ errorColor?: string; // Default: "#ef4444"
118
+ hoverColor?: string; // Default: "rgba(255, 255, 255, 0.05)"
119
+ borderRadius?: number; // Default: 12
120
+ fontFamily?: string;
121
+ }
122
+ ```
123
+
124
+ ## Custom Chain Configuration
125
+
126
+ ```tsx
127
+ import { BridgeWidget, createChainConfig, USDC_ADDRESSES } from "@honeypot-finance/usdc-bridge-widget";
128
+ import { mainnet, arbitrum, base } from "viem/chains";
129
+ import { defineChain } from "viem";
130
+
131
+ // Use helper function
132
+ const chains = [
133
+ createChainConfig(mainnet, {
134
+ iconUrl: "https://example.com/eth-icon.png",
135
+ }),
136
+ createChainConfig(arbitrum),
137
+ createChainConfig(base),
138
+ ];
139
+
140
+ // Or define manually
141
+ const customChain = defineChain({
142
+ id: 12345,
143
+ name: "Custom Chain",
144
+ // ... other chain config
145
+ });
146
+
147
+ const customChainConfig = {
148
+ chain: customChain,
149
+ usdcAddress: "0x..." as `0x${string}`,
150
+ tokenMessengerAddress: "0x..." as `0x${string}`,
151
+ iconUrl: "https://example.com/custom-icon.png",
152
+ };
153
+
154
+ <BridgeWidget chains={[...chains, customChainConfig]} />
155
+ ```
156
+
157
+ ## Custom Theme
158
+
159
+ ```tsx
160
+ <BridgeWidget
161
+ chains={DEFAULT_CHAIN_CONFIGS}
162
+ theme={{
163
+ primaryColor: "#00ff00",
164
+ secondaryColor: "#00cc00",
165
+ backgroundColor: "#1a1a2e",
166
+ borderRadius: 16,
167
+ fontFamily: "Inter, sans-serif",
168
+ }}
169
+ />
170
+ ```
171
+
172
+ ## Callbacks
173
+
174
+ ```tsx
175
+ <BridgeWidget
176
+ chains={DEFAULT_CHAIN_CONFIGS}
177
+ onBridgeStart={({ sourceChainId, destChainId, amount }) => {
178
+ console.log(`Starting bridge: ${amount} USDC from ${sourceChainId} to ${destChainId}`);
179
+ }}
180
+ onBridgeSuccess={({ sourceChainId, destChainId, amount, txHash }) => {
181
+ console.log(`Bridge successful! TX: ${txHash}`);
182
+ }}
183
+ onBridgeError={(error) => {
184
+ console.error("Bridge failed:", error.message);
185
+ }}
186
+ onConnectWallet={() => {
187
+ // Open your wallet modal
188
+ openWalletModal();
189
+ }}
190
+ />
191
+ ```
192
+
193
+ ## Using Individual Hooks
194
+
195
+ The widget exports its internal hooks for advanced usage:
196
+
197
+ ```tsx
198
+ import {
199
+ useUSDCBalance,
200
+ useAllUSDCBalances,
201
+ useUSDCAllowance,
202
+ useBridge,
203
+ useBridgeQuote,
204
+ } from "@honeypot-finance/usdc-bridge-widget";
205
+
206
+ function CustomComponent() {
207
+ const chainConfig = { chain: mainnet, usdcAddress: "0x..." };
208
+
209
+ // Balance hooks
210
+ const { balance, balanceFormatted } = useUSDCBalance(chainConfig);
211
+ const { balances, isLoading } = useAllUSDCBalances([chainConfig]);
212
+
213
+ // Allowance hook
214
+ const { needsApproval, approve, isApproving } = useUSDCAllowance(chainConfig);
215
+
216
+ // Bridge execution hook
217
+ const { bridge, state, reset } = useBridge();
218
+ // state.status: "idle" | "loading" | "approving" | "burning" | "fetching-attestation" | "minting" | "success" | "error"
219
+
220
+ // Quote hook (returns static CCTP estimates)
221
+ const { quote, isLoading: quoteLoading } = useBridgeQuote(1, 8453, "100");
222
+
223
+ // Build your own UI
224
+ }
225
+ ```
226
+
227
+ ### Deprecated Hooks
228
+
229
+ The following hooks are deprecated and will be removed in a future version:
230
+
231
+ - `useBridgeEstimate` - Use `useBridgeQuote` instead
232
+ - `useFormatNumber` - Use the `formatNumber` utility function directly
233
+
234
+ ## Supported Chains
235
+
236
+ The widget includes pre-configured USDC and TokenMessenger addresses for all CCTP V2 EVM chains:
237
+
238
+ | Chain | Chain ID |
239
+ |-------|----------|
240
+ | Ethereum | 1 |
241
+ | Arbitrum | 42161 |
242
+ | Base | 8453 |
243
+ | Optimism | 10 |
244
+ | Polygon | 137 |
245
+ | Avalanche | 43114 |
246
+ | Linea | 59144 |
247
+ | Unichain | 130 |
248
+ | Sonic | 146 |
249
+ | World Chain | 480 |
250
+ | Monad | 10200 |
251
+ | Sei | 1329 |
252
+ | XDC | 50 |
253
+ | HyperEVM | 999 |
254
+ | Ink | 57073 |
255
+ | Plume | 98866 |
256
+ | Codex | 81224 |
257
+
258
+ See [Circle CCTP Docs](https://developers.circle.com/cctp/cctp-supported-blockchains) for the latest supported chains
259
+
260
+ ## Circle Bridge Kit Integration
261
+
262
+ For full bridge functionality, install Circle's Bridge Kit:
263
+
264
+ ```bash
265
+ npm install @circle-fin/bridge-kit @circle-fin/adapter-viem-v2
266
+ ```
267
+
268
+ Then integrate with the widget's callbacks to execute actual transfers.
269
+
270
+ ## License
271
+
272
+ MIT
@@ -0,0 +1,211 @@
1
+ // src/useBridge.ts
2
+ import { useState, useCallback, useEffect, useRef } from "react";
3
+ import { useConnectorClient } from "wagmi";
4
+ var CHAIN_NAME_MAP = {
5
+ 1: "Ethereum",
6
+ 42161: "Arbitrum",
7
+ 43114: "Avalanche",
8
+ 8453: "Base",
9
+ 10: "OP_Mainnet",
10
+ 137: "Polygon",
11
+ 59144: "Linea",
12
+ 130: "Unichain",
13
+ 146: "Sonic",
14
+ 480: "World_Chain",
15
+ 143: "Monad",
16
+ 1329: "Sei",
17
+ 50: "XDC",
18
+ 999: "HyperEVM",
19
+ 57073: "Ink",
20
+ 98866: "Plume",
21
+ 81224: "Codex"
22
+ };
23
+ function getChainName(chainId) {
24
+ return CHAIN_NAME_MAP[chainId] || `Chain_${chainId}`;
25
+ }
26
+ function useBridge() {
27
+ const { data: connectorClient } = useConnectorClient();
28
+ const [state, setState] = useState({
29
+ status: "idle",
30
+ events: []
31
+ });
32
+ const isMountedRef = useRef(true);
33
+ useEffect(() => {
34
+ isMountedRef.current = true;
35
+ return () => {
36
+ isMountedRef.current = false;
37
+ };
38
+ }, []);
39
+ const addEvent = useCallback((type, data) => {
40
+ if (!isMountedRef.current) return;
41
+ setState((prev) => ({
42
+ ...prev,
43
+ events: [...prev.events, { type, timestamp: Date.now(), data }]
44
+ }));
45
+ }, []);
46
+ const reset = useCallback(() => {
47
+ setState({ status: "idle", events: [] });
48
+ }, []);
49
+ const bridge = useCallback(
50
+ async (params) => {
51
+ const { sourceChainConfig, destChainConfig, amount, recipientAddress } = params;
52
+ if (!connectorClient) {
53
+ setState({
54
+ status: "error",
55
+ error: new Error("Wallet not connected"),
56
+ events: []
57
+ });
58
+ return;
59
+ }
60
+ setState({ status: "loading", events: [] });
61
+ addEvent("start", { amount, sourceChain: sourceChainConfig.chain.id, destChain: destChainConfig.chain.id });
62
+ try {
63
+ let BridgingKit;
64
+ let createAdapterFromProvider;
65
+ try {
66
+ const bridgingKitModule = await import("@circle-fin/bridging-kit");
67
+ const adapterModule = await import("@circle-fin/adapter-viem-v2");
68
+ BridgingKit = bridgingKitModule.BridgingKit;
69
+ createAdapterFromProvider = adapterModule.createAdapterFromProvider;
70
+ } catch {
71
+ throw new Error(
72
+ "Circle Bridge Kit packages not installed. Run: npm install @circle-fin/bridging-kit @circle-fin/adapter-viem-v2"
73
+ );
74
+ }
75
+ const clientWithTransport = connectorClient;
76
+ const provider = clientWithTransport?.transport?.value?.provider;
77
+ if (!provider) {
78
+ throw new Error("Could not get wallet provider from connector");
79
+ }
80
+ const adapter = await createAdapterFromProvider({ provider });
81
+ const kit = new BridgingKit();
82
+ kit.on("approve", (event) => {
83
+ addEvent("approve", event);
84
+ if (isMountedRef.current) {
85
+ setState((prev) => ({
86
+ ...prev,
87
+ status: "approving",
88
+ txHash: event.values?.txHash
89
+ }));
90
+ }
91
+ });
92
+ kit.on("burn", (event) => {
93
+ addEvent("burn", event);
94
+ if (isMountedRef.current) {
95
+ setState((prev) => ({
96
+ ...prev,
97
+ status: "burning",
98
+ txHash: event.values?.txHash
99
+ }));
100
+ }
101
+ });
102
+ kit.on("mint", (event) => {
103
+ addEvent("mint", event);
104
+ if (isMountedRef.current) {
105
+ setState((prev) => ({
106
+ ...prev,
107
+ status: "minting",
108
+ txHash: event.values?.txHash
109
+ }));
110
+ }
111
+ });
112
+ const sourceChainName = getChainName(sourceChainConfig.chain.id);
113
+ const destChainName = getChainName(destChainConfig.chain.id);
114
+ const bridgeParams = {
115
+ from: { adapter, chain: sourceChainName },
116
+ to: { adapter, chain: destChainName },
117
+ amount
118
+ };
119
+ if (recipientAddress) {
120
+ bridgeParams.recipientAddress = recipientAddress;
121
+ }
122
+ const result = await kit.bridge(bridgeParams);
123
+ addEvent("complete", result);
124
+ if (isMountedRef.current) {
125
+ setState((prev) => ({
126
+ ...prev,
127
+ status: "success",
128
+ txHash: result?.txHash
129
+ }));
130
+ }
131
+ } catch (error) {
132
+ addEvent("error", error);
133
+ if (isMountedRef.current) {
134
+ setState((prev) => ({
135
+ ...prev,
136
+ status: "error",
137
+ error: error instanceof Error ? error : new Error("Bridge transfer failed")
138
+ }));
139
+ }
140
+ throw error;
141
+ }
142
+ },
143
+ [connectorClient, addEvent]
144
+ );
145
+ return { bridge, state, reset };
146
+ }
147
+ function useBridgeQuote(sourceChainId, destChainId, amount) {
148
+ const [quote, setQuote] = useState(null);
149
+ const [isLoading, setIsLoading] = useState(false);
150
+ const [error, setError] = useState(null);
151
+ useEffect(() => {
152
+ if (!sourceChainId || !destChainId || !amount || parseFloat(amount) <= 0) {
153
+ setQuote(null);
154
+ setError(null);
155
+ return;
156
+ }
157
+ const fetchQuote = async () => {
158
+ setIsLoading(true);
159
+ setError(null);
160
+ try {
161
+ let BridgingKit;
162
+ try {
163
+ const bridgingKitModule = await import("@circle-fin/bridging-kit");
164
+ BridgingKit = bridgingKitModule.BridgingKit;
165
+ } catch {
166
+ setError(new Error("Bridge Kit not installed. Run: npm install @circle-fin/bridging-kit"));
167
+ setQuote(null);
168
+ return;
169
+ }
170
+ const kit = new BridgingKit();
171
+ const sourceChainName = getChainName(sourceChainId);
172
+ const destChainName = getChainName(destChainId);
173
+ if (typeof kit.getQuote === "function") {
174
+ const result = await kit.getQuote({
175
+ from: { chain: sourceChainName },
176
+ to: { chain: destChainName },
177
+ amount
178
+ });
179
+ setQuote({
180
+ estimatedGasFee: result.estimatedGasFee || "0.00",
181
+ bridgeFee: result.bridgeFee || "0.00",
182
+ totalFee: result.totalFee || result.estimatedGasFee || "0.00",
183
+ estimatedTime: result.estimatedTime || "~15-20 minutes",
184
+ expiresAt: result.expiresAt
185
+ });
186
+ } else {
187
+ setQuote({
188
+ estimatedGasFee: "Estimated by wallet",
189
+ bridgeFee: "0.00",
190
+ totalFee: "Gas only",
191
+ estimatedTime: "~15-20 minutes"
192
+ });
193
+ }
194
+ } catch (err) {
195
+ setError(err instanceof Error ? err : new Error("Failed to get quote"));
196
+ setQuote(null);
197
+ } finally {
198
+ setIsLoading(false);
199
+ }
200
+ };
201
+ const debounceTimer = setTimeout(fetchQuote, 500);
202
+ return () => clearTimeout(debounceTimer);
203
+ }, [sourceChainId, destChainId, amount]);
204
+ return { quote, isLoading, error };
205
+ }
206
+
207
+ export {
208
+ getChainName,
209
+ useBridge,
210
+ useBridgeQuote
211
+ };
@@ -0,0 +1,218 @@
1
+ // src/useBridge.ts
2
+ import { useState, useCallback, useEffect, useRef } from "react";
3
+ import { useConnectorClient } from "wagmi";
4
+ var CHAIN_NAME_MAP = {
5
+ 1: "Ethereum",
6
+ 42161: "Arbitrum",
7
+ 43114: "Avalanche",
8
+ 8453: "Base",
9
+ 10: "OP_Mainnet",
10
+ 137: "Polygon",
11
+ 59144: "Linea",
12
+ 130: "Unichain",
13
+ 146: "Sonic",
14
+ 480: "World_Chain",
15
+ 143: "Monad",
16
+ 1329: "Sei",
17
+ 50: "XDC",
18
+ 999: "HyperEVM",
19
+ 57073: "Ink",
20
+ 98866: "Plume",
21
+ 81224: "Codex"
22
+ };
23
+ function getChainName(chainId) {
24
+ return CHAIN_NAME_MAP[chainId] || `Chain_${chainId}`;
25
+ }
26
+ function useBridge() {
27
+ const { data: connectorClient } = useConnectorClient();
28
+ const [state, setState] = useState({
29
+ status: "idle",
30
+ events: []
31
+ });
32
+ const isMountedRef = useRef(true);
33
+ useEffect(() => {
34
+ isMountedRef.current = true;
35
+ return () => {
36
+ isMountedRef.current = false;
37
+ };
38
+ }, []);
39
+ const addEvent = useCallback((type, data) => {
40
+ if (!isMountedRef.current) return;
41
+ setState((prev) => ({
42
+ ...prev,
43
+ events: [...prev.events, { type, timestamp: Date.now(), data }]
44
+ }));
45
+ }, []);
46
+ const reset = useCallback(() => {
47
+ setState({ status: "idle", events: [] });
48
+ }, []);
49
+ const bridge = useCallback(
50
+ async (params) => {
51
+ const { sourceChainConfig, destChainConfig, amount, recipientAddress } = params;
52
+ if (!connectorClient) {
53
+ setState({
54
+ status: "error",
55
+ error: new Error("Wallet not connected"),
56
+ events: []
57
+ });
58
+ return;
59
+ }
60
+ setState({ status: "loading", events: [] });
61
+ addEvent("start", { amount, sourceChain: sourceChainConfig.chain.id, destChain: destChainConfig.chain.id });
62
+ try {
63
+ let BridgingKit;
64
+ let createAdapterFromProvider;
65
+ try {
66
+ const bridgingKitPath = ["@circle-fin", "bridging-kit"].join("/");
67
+ const adapterPath = ["@circle-fin", "adapter-viem-v2"].join("/");
68
+ const dynamicImport = Function("m", "return import(m)");
69
+ const [bridgingKitModule, adapterModule] = await Promise.all([
70
+ dynamicImport(bridgingKitPath),
71
+ dynamicImport(adapterPath)
72
+ ]);
73
+ BridgingKit = bridgingKitModule.BridgingKit;
74
+ createAdapterFromProvider = adapterModule.createAdapterFromProvider;
75
+ } catch {
76
+ throw new Error(
77
+ "Circle Bridge Kit packages not installed. Run: npm install @circle-fin/bridging-kit @circle-fin/adapter-viem-v2"
78
+ );
79
+ }
80
+ const clientWithTransport = connectorClient;
81
+ const provider = clientWithTransport?.transport?.value?.provider;
82
+ if (!provider) {
83
+ throw new Error("Could not get wallet provider from connector");
84
+ }
85
+ const adapter = await createAdapterFromProvider({ provider });
86
+ const kit = new BridgingKit();
87
+ kit.on("approve", (event) => {
88
+ addEvent("approve", event);
89
+ if (isMountedRef.current) {
90
+ setState((prev) => ({
91
+ ...prev,
92
+ status: "approving",
93
+ txHash: event.values?.txHash
94
+ }));
95
+ }
96
+ });
97
+ kit.on("burn", (event) => {
98
+ addEvent("burn", event);
99
+ if (isMountedRef.current) {
100
+ setState((prev) => ({
101
+ ...prev,
102
+ status: "burning",
103
+ txHash: event.values?.txHash
104
+ }));
105
+ }
106
+ });
107
+ kit.on("mint", (event) => {
108
+ addEvent("mint", event);
109
+ if (isMountedRef.current) {
110
+ setState((prev) => ({
111
+ ...prev,
112
+ status: "minting",
113
+ txHash: event.values?.txHash
114
+ }));
115
+ }
116
+ });
117
+ const sourceChainName = getChainName(sourceChainConfig.chain.id);
118
+ const destChainName = getChainName(destChainConfig.chain.id);
119
+ const bridgeParams = {
120
+ from: { adapter, chain: sourceChainName },
121
+ to: { adapter, chain: destChainName },
122
+ amount
123
+ };
124
+ if (recipientAddress) {
125
+ bridgeParams.recipientAddress = recipientAddress;
126
+ }
127
+ const result = await kit.bridge(bridgeParams);
128
+ addEvent("complete", result);
129
+ if (isMountedRef.current) {
130
+ setState((prev) => ({
131
+ ...prev,
132
+ status: "success",
133
+ txHash: result?.txHash
134
+ }));
135
+ }
136
+ } catch (error) {
137
+ addEvent("error", error);
138
+ if (isMountedRef.current) {
139
+ setState((prev) => ({
140
+ ...prev,
141
+ status: "error",
142
+ error: error instanceof Error ? error : new Error("Bridge transfer failed")
143
+ }));
144
+ }
145
+ throw error;
146
+ }
147
+ },
148
+ [connectorClient, addEvent]
149
+ );
150
+ return { bridge, state, reset };
151
+ }
152
+ function useBridgeQuote(sourceChainId, destChainId, amount) {
153
+ const [quote, setQuote] = useState(null);
154
+ const [isLoading, setIsLoading] = useState(false);
155
+ const [error, setError] = useState(null);
156
+ useEffect(() => {
157
+ if (!sourceChainId || !destChainId || !amount || parseFloat(amount) <= 0) {
158
+ setQuote(null);
159
+ setError(null);
160
+ return;
161
+ }
162
+ const fetchQuote = async () => {
163
+ setIsLoading(true);
164
+ setError(null);
165
+ try {
166
+ let BridgingKit;
167
+ try {
168
+ const modulePath = ["@circle-fin", "bridging-kit"].join("/");
169
+ const dynamicImport = Function("m", "return import(m)");
170
+ const bridgingKitModule = await dynamicImport(modulePath);
171
+ BridgingKit = bridgingKitModule.BridgingKit;
172
+ } catch {
173
+ setError(new Error("Bridge Kit not installed. Run: npm install @circle-fin/bridging-kit"));
174
+ setQuote(null);
175
+ return;
176
+ }
177
+ const kit = new BridgingKit();
178
+ const sourceChainName = getChainName(sourceChainId);
179
+ const destChainName = getChainName(destChainId);
180
+ if (typeof kit.getQuote === "function") {
181
+ const result = await kit.getQuote({
182
+ from: { chain: sourceChainName },
183
+ to: { chain: destChainName },
184
+ amount
185
+ });
186
+ setQuote({
187
+ estimatedGasFee: result.estimatedGasFee || "0.00",
188
+ bridgeFee: result.bridgeFee || "0.00",
189
+ totalFee: result.totalFee || result.estimatedGasFee || "0.00",
190
+ estimatedTime: result.estimatedTime || "~15-20 minutes",
191
+ expiresAt: result.expiresAt
192
+ });
193
+ } else {
194
+ setQuote({
195
+ estimatedGasFee: "Estimated by wallet",
196
+ bridgeFee: "0.00",
197
+ totalFee: "Gas only",
198
+ estimatedTime: "~15-20 minutes"
199
+ });
200
+ }
201
+ } catch (err) {
202
+ setError(err instanceof Error ? err : new Error("Failed to get quote"));
203
+ setQuote(null);
204
+ } finally {
205
+ setIsLoading(false);
206
+ }
207
+ };
208
+ const debounceTimer = setTimeout(fetchQuote, 500);
209
+ return () => clearTimeout(debounceTimer);
210
+ }, [sourceChainId, destChainId, amount]);
211
+ return { quote, isLoading, error };
212
+ }
213
+
214
+ export {
215
+ getChainName,
216
+ useBridge,
217
+ useBridgeQuote
218
+ };