@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/src/icons.tsx ADDED
@@ -0,0 +1,228 @@
1
+ import React from "react";
2
+
3
+ interface IconProps {
4
+ /** Icon size in pixels */
5
+ size?: number;
6
+ /** Stroke/fill color */
7
+ color?: string;
8
+ /** Additional CSS class */
9
+ className?: string;
10
+ /** Additional inline styles */
11
+ style?: React.CSSProperties;
12
+ }
13
+
14
+ /**
15
+ * Chevron down icon for dropdowns
16
+ */
17
+ export function ChevronDownIcon({
18
+ size = 16,
19
+ color = "currentColor",
20
+ className,
21
+ style,
22
+ }: IconProps) {
23
+ return (
24
+ <svg
25
+ width={size}
26
+ height={size}
27
+ viewBox="0 0 24 24"
28
+ fill="none"
29
+ stroke={color}
30
+ className={className}
31
+ style={style}
32
+ aria-hidden="true"
33
+ >
34
+ <path
35
+ strokeLinecap="round"
36
+ strokeLinejoin="round"
37
+ strokeWidth={2}
38
+ d="M19 9l-7 7-7-7"
39
+ />
40
+ </svg>
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Swap/exchange icon for chain swap button
46
+ */
47
+ export function SwapIcon({
48
+ size = 20,
49
+ color = "currentColor",
50
+ className,
51
+ style,
52
+ }: IconProps) {
53
+ return (
54
+ <svg
55
+ width={size}
56
+ height={size}
57
+ viewBox="0 0 24 24"
58
+ fill="none"
59
+ stroke={color}
60
+ className={className}
61
+ style={style}
62
+ aria-hidden="true"
63
+ >
64
+ <path
65
+ strokeLinecap="round"
66
+ strokeLinejoin="round"
67
+ strokeWidth={2}
68
+ d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"
69
+ />
70
+ </svg>
71
+ );
72
+ }
73
+
74
+ /**
75
+ * Loading spinner icon
76
+ */
77
+ export function SpinnerIcon({
78
+ size = 20,
79
+ color = "currentColor",
80
+ className,
81
+ style,
82
+ }: IconProps) {
83
+ return (
84
+ <svg
85
+ width={size}
86
+ height={size}
87
+ viewBox="0 0 24 24"
88
+ fill="none"
89
+ stroke={color}
90
+ className={className}
91
+ style={{
92
+ animation: "cc-spin 1s linear infinite",
93
+ ...style,
94
+ }}
95
+ aria-hidden="true"
96
+ >
97
+ <style>{`@keyframes cc-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }`}</style>
98
+ <circle
99
+ cx="12"
100
+ cy="12"
101
+ r="10"
102
+ strokeWidth={2}
103
+ strokeDasharray="32"
104
+ strokeLinecap="round"
105
+ />
106
+ </svg>
107
+ );
108
+ }
109
+
110
+ /**
111
+ * Check/success icon
112
+ */
113
+ export function CheckIcon({
114
+ size = 20,
115
+ color = "currentColor",
116
+ className,
117
+ style,
118
+ }: IconProps) {
119
+ return (
120
+ <svg
121
+ width={size}
122
+ height={size}
123
+ viewBox="0 0 24 24"
124
+ fill="none"
125
+ stroke={color}
126
+ className={className}
127
+ style={style}
128
+ aria-hidden="true"
129
+ >
130
+ <path
131
+ strokeLinecap="round"
132
+ strokeLinejoin="round"
133
+ strokeWidth={2}
134
+ d="M5 13l4 4L19 7"
135
+ />
136
+ </svg>
137
+ );
138
+ }
139
+
140
+ /**
141
+ * Error/X icon
142
+ */
143
+ export function ErrorIcon({
144
+ size = 20,
145
+ color = "currentColor",
146
+ className,
147
+ style,
148
+ }: IconProps) {
149
+ return (
150
+ <svg
151
+ width={size}
152
+ height={size}
153
+ viewBox="0 0 24 24"
154
+ fill="none"
155
+ stroke={color}
156
+ className={className}
157
+ style={style}
158
+ aria-hidden="true"
159
+ >
160
+ <path
161
+ strokeLinecap="round"
162
+ strokeLinejoin="round"
163
+ strokeWidth={2}
164
+ d="M6 18L18 6M6 6l12 12"
165
+ />
166
+ </svg>
167
+ );
168
+ }
169
+
170
+ /**
171
+ * External link icon
172
+ */
173
+ export function ExternalLinkIcon({
174
+ size = 16,
175
+ color = "currentColor",
176
+ className,
177
+ style,
178
+ }: IconProps) {
179
+ return (
180
+ <svg
181
+ width={size}
182
+ height={size}
183
+ viewBox="0 0 24 24"
184
+ fill="none"
185
+ stroke={color}
186
+ className={className}
187
+ style={style}
188
+ aria-hidden="true"
189
+ >
190
+ <path
191
+ strokeLinecap="round"
192
+ strokeLinejoin="round"
193
+ strokeWidth={2}
194
+ d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
195
+ />
196
+ </svg>
197
+ );
198
+ }
199
+
200
+ /**
201
+ * Wallet icon
202
+ */
203
+ export function WalletIcon({
204
+ size = 20,
205
+ color = "currentColor",
206
+ className,
207
+ style,
208
+ }: IconProps) {
209
+ return (
210
+ <svg
211
+ width={size}
212
+ height={size}
213
+ viewBox="0 0 24 24"
214
+ fill="none"
215
+ stroke={color}
216
+ className={className}
217
+ style={style}
218
+ aria-hidden="true"
219
+ >
220
+ <path
221
+ strokeLinecap="round"
222
+ strokeLinejoin="round"
223
+ strokeWidth={2}
224
+ d="M3 10h18M3 6h18a2 2 0 012 2v10a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2zm14 6h.01"
225
+ />
226
+ </svg>
227
+ );
228
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,111 @@
1
+ // Main widget export
2
+ export { BridgeWidget } from "./BridgeWidget";
3
+
4
+ // Types
5
+ export type {
6
+ BridgeWidgetProps,
7
+ BridgeWidgetTheme,
8
+ BridgeChainConfig,
9
+ BridgeEstimate,
10
+ BridgeResult,
11
+ } from "./types";
12
+
13
+ // Hooks (for advanced usage)
14
+ export {
15
+ useUSDCBalance,
16
+ useAllUSDCBalances,
17
+ useUSDCAllowance,
18
+ /**
19
+ * @deprecated Use `useBridgeQuote` instead
20
+ */
21
+ useBridgeEstimate,
22
+ /**
23
+ * @deprecated Use `formatNumber` from utils instead
24
+ */
25
+ useFormatNumber,
26
+ } from "./hooks";
27
+
28
+ // Bridge hook
29
+ export { useBridge, useBridgeQuote, getChainName } from "./useBridge";
30
+ export type { BridgeParams, BridgeState, BridgeEvent, UseBridgeResult, BridgeQuote } from "./useBridge";
31
+
32
+ // Utilities
33
+ export {
34
+ formatNumber,
35
+ getErrorMessage,
36
+ parseUSDCAmount,
37
+ isValidPositiveAmount,
38
+ validateAmountInput,
39
+ validateChainConfig,
40
+ validateChainConfigs,
41
+ MAX_USDC_AMOUNT,
42
+ MIN_USDC_AMOUNT,
43
+ } from "./utils";
44
+ export type { ChainConfigValidationResult } from "./utils";
45
+
46
+ // Constants
47
+ export {
48
+ USDC_DECIMALS,
49
+ USDC_BRAND_COLOR,
50
+ USDC_ADDRESSES,
51
+ TOKEN_MESSENGER_V1_ADDRESSES,
52
+ TOKEN_MESSENGER_V2_ADDRESS,
53
+ TOKEN_MESSENGER_ADDRESSES,
54
+ CHAIN_ICONS,
55
+ DEFAULT_LOCALE,
56
+ } from "./constants";
57
+
58
+ // Chain configs and helpers
59
+ export {
60
+ createChainConfig,
61
+ DEFAULT_CHAIN_CONFIGS,
62
+ // Custom chains
63
+ unichain,
64
+ hyperEvm,
65
+ plume,
66
+ monad,
67
+ codex,
68
+ // Re-exported viem chains
69
+ mainnet,
70
+ arbitrum,
71
+ avalanche,
72
+ base,
73
+ optimism,
74
+ polygon,
75
+ linea,
76
+ sei,
77
+ worldchain,
78
+ ink,
79
+ sonic,
80
+ xdc,
81
+ // Testnet support
82
+ createTestnetChainConfig,
83
+ TESTNET_CHAIN_CONFIGS,
84
+ sepolia,
85
+ arbitrumSepolia,
86
+ avalancheFuji,
87
+ baseSepolia,
88
+ optimismSepolia,
89
+ polygonAmoy,
90
+ } from "./chains";
91
+
92
+ // Theme utilities
93
+ export {
94
+ defaultTheme,
95
+ mergeTheme,
96
+ themePresets,
97
+ THEME_COLORS,
98
+ THEME_SIZING,
99
+ THEME_FONTS,
100
+ } from "./theme";
101
+
102
+ // Icons (for custom implementations)
103
+ export {
104
+ ChevronDownIcon,
105
+ SwapIcon,
106
+ SpinnerIcon,
107
+ CheckIcon,
108
+ ErrorIcon,
109
+ ExternalLinkIcon,
110
+ WalletIcon,
111
+ } from "./icons";
package/src/theme.ts ADDED
@@ -0,0 +1,131 @@
1
+ import type { BridgeWidgetTheme } from "./types";
2
+
3
+ /**
4
+ * Default theme color palette
5
+ */
6
+ export const THEME_COLORS = {
7
+ /** Primary accent - Indigo */
8
+ primary: "#6366f1",
9
+ /** Secondary accent - Purple */
10
+ secondary: "#a855f7",
11
+ /** Success state - Green */
12
+ success: "#22c55e",
13
+ /** Error state - Red */
14
+ error: "#ef4444",
15
+ /** Text color - White */
16
+ text: "#ffffff",
17
+ /** Muted text - Semi-transparent white */
18
+ mutedText: "rgba(255, 255, 255, 0.54)",
19
+ /** Border color - Semi-transparent white */
20
+ border: "rgba(255, 255, 255, 0.06)",
21
+ /** Background - Dark with transparency */
22
+ background: "rgba(15, 15, 25, 0.8)",
23
+ /** Card background - Darker with transparency */
24
+ cardBackground: "rgba(15, 15, 25, 0.6)",
25
+ /** Dropdown background - Near opaque dark */
26
+ dropdownBackground: "rgba(20, 20, 35, 0.98)",
27
+ /** Input background - Transparent black */
28
+ inputBackground: "rgba(0, 0, 0, 0.3)",
29
+ /** Hover state - Semi-transparent white */
30
+ hover: "rgba(255, 255, 255, 0.05)",
31
+ } as const;
32
+
33
+ /**
34
+ * Default theme spacing and sizing
35
+ */
36
+ export const THEME_SIZING = {
37
+ /** Default border radius in pixels */
38
+ borderRadius: 12,
39
+ /** Widget max width */
40
+ maxWidth: "480px",
41
+ /** Standard padding */
42
+ padding: "16px",
43
+ /** Gap between elements */
44
+ gap: "12px",
45
+ /** Small gap */
46
+ smallGap: "8px",
47
+ /** Dropdown max height */
48
+ dropdownMaxHeight: "300px",
49
+ } as const;
50
+
51
+ /**
52
+ * Default font settings
53
+ */
54
+ export const THEME_FONTS = {
55
+ /** Primary font family */
56
+ family: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
57
+ /** Font sizes */
58
+ sizes: {
59
+ xs: "10px",
60
+ sm: "12px",
61
+ base: "14px",
62
+ lg: "18px",
63
+ },
64
+ /** Font weights */
65
+ weights: {
66
+ normal: 400,
67
+ medium: 500,
68
+ semibold: 600,
69
+ bold: 700,
70
+ },
71
+ } as const;
72
+
73
+ /**
74
+ * Default complete theme configuration
75
+ */
76
+ export const defaultTheme: Required<BridgeWidgetTheme> = {
77
+ primaryColor: THEME_COLORS.primary,
78
+ secondaryColor: THEME_COLORS.secondary,
79
+ backgroundColor: THEME_COLORS.background,
80
+ cardBackgroundColor: THEME_COLORS.cardBackground,
81
+ textColor: THEME_COLORS.text,
82
+ mutedTextColor: THEME_COLORS.mutedText,
83
+ borderColor: THEME_COLORS.border,
84
+ successColor: THEME_COLORS.success,
85
+ errorColor: THEME_COLORS.error,
86
+ hoverColor: THEME_COLORS.hover,
87
+ borderRadius: THEME_SIZING.borderRadius,
88
+ fontFamily: THEME_FONTS.family,
89
+ };
90
+
91
+ /**
92
+ * Merge user theme overrides with defaults
93
+ */
94
+ export function mergeTheme(
95
+ theme?: BridgeWidgetTheme
96
+ ): Required<BridgeWidgetTheme> {
97
+ return { ...defaultTheme, ...theme };
98
+ }
99
+
100
+ /**
101
+ * Pre-built theme presets
102
+ */
103
+ export const themePresets = {
104
+ /** Default dark theme */
105
+ dark: defaultTheme,
106
+
107
+ /** Light theme variant */
108
+ light: {
109
+ ...defaultTheme,
110
+ backgroundColor: "rgba(255, 255, 255, 0.95)",
111
+ cardBackgroundColor: "rgba(245, 245, 245, 0.9)",
112
+ textColor: "#1a1a2e",
113
+ mutedTextColor: "rgba(0, 0, 0, 0.54)",
114
+ borderColor: "rgba(0, 0, 0, 0.1)",
115
+ hoverColor: "rgba(0, 0, 0, 0.05)",
116
+ } as Required<BridgeWidgetTheme>,
117
+
118
+ /** Blue accent theme */
119
+ blue: {
120
+ ...defaultTheme,
121
+ primaryColor: "#3b82f6",
122
+ secondaryColor: "#06b6d4",
123
+ } as Required<BridgeWidgetTheme>,
124
+
125
+ /** Green accent theme */
126
+ green: {
127
+ ...defaultTheme,
128
+ primaryColor: "#10b981",
129
+ secondaryColor: "#34d399",
130
+ } as Required<BridgeWidgetTheme>,
131
+ } as const;
package/src/types.ts ADDED
@@ -0,0 +1,160 @@
1
+ import type { Chain } from "viem";
2
+
3
+ /**
4
+ * Theme configuration for the Bridge Widget.
5
+ * All color values should be valid CSS color strings (hex, rgb, rgba, etc.).
6
+ *
7
+ * @example
8
+ * const customTheme: BridgeWidgetTheme = {
9
+ * primaryColor: "#3b82f6",
10
+ * backgroundColor: "rgba(0, 0, 0, 0.9)",
11
+ * borderRadius: 16,
12
+ * };
13
+ */
14
+ export interface BridgeWidgetTheme {
15
+ /** Primary accent color used for buttons and highlights (hex) */
16
+ primaryColor?: string;
17
+ /** Secondary accent color used for gradients (hex) */
18
+ secondaryColor?: string;
19
+ /** Main background color of the widget (hex or rgba) */
20
+ backgroundColor?: string;
21
+ /** Background color for cards and nested elements (hex or rgba) */
22
+ cardBackgroundColor?: string;
23
+ /** Primary text color (hex) */
24
+ textColor?: string;
25
+ /** Secondary/muted text color for labels (hex or rgba) */
26
+ mutedTextColor?: string;
27
+ /** Border color for inputs and containers (hex or rgba) */
28
+ borderColor?: string;
29
+ /** Color for success states and messages (hex) */
30
+ successColor?: string;
31
+ /** Color for error states and messages (hex) */
32
+ errorColor?: string;
33
+ /** Color for hover states (hex or rgba) */
34
+ hoverColor?: string;
35
+ /** Border radius in pixels for rounded corners */
36
+ borderRadius?: number;
37
+ /** Font family for all text in the widget */
38
+ fontFamily?: string;
39
+ }
40
+
41
+ /**
42
+ * Configuration for a supported blockchain in the bridge.
43
+ * Contains chain metadata, USDC contract address, and CCTP TokenMessenger address.
44
+ *
45
+ * @example
46
+ * const ethereumConfig: BridgeChainConfig = {
47
+ * chain: mainnet,
48
+ * usdcAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
49
+ * tokenMessengerAddress: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
50
+ * iconUrl: "https://icons.llamao.fi/icons/chains/rsz_ethereum.jpg",
51
+ * };
52
+ */
53
+ export interface BridgeChainConfig {
54
+ /** Chain definition from viem containing id, name, and network details */
55
+ chain: Chain;
56
+ /** USDC token contract address on this chain (checksummed) */
57
+ usdcAddress: `0x${string}`;
58
+ /** Circle TokenMessenger contract address for CCTP transfers */
59
+ tokenMessengerAddress?: `0x${string}`;
60
+ /** URL for the chain's icon/logo image (optional, falls back to initial) */
61
+ iconUrl?: string;
62
+ }
63
+
64
+ /**
65
+ * Props for the BridgeWidget component.
66
+ *
67
+ * @example
68
+ * <BridgeWidget
69
+ * defaultSourceChainId={1}
70
+ * defaultDestinationChainId={8453}
71
+ * onBridgeSuccess={({ txHash }) => console.log('Success:', txHash)}
72
+ * onBridgeError={(error) => console.error('Error:', error)}
73
+ * theme={{ primaryColor: "#3b82f6" }}
74
+ * />
75
+ */
76
+ export interface BridgeWidgetProps {
77
+ /**
78
+ * Array of supported chains with their USDC addresses.
79
+ * Defaults to all CCTP-supported chains from DEFAULT_CHAIN_CONFIGS.
80
+ */
81
+ chains?: BridgeChainConfig[];
82
+ /** Chain ID to pre-select as the source chain */
83
+ defaultSourceChainId?: number;
84
+ /** Chain ID to pre-select as the destination chain */
85
+ defaultDestinationChainId?: number;
86
+ /**
87
+ * Callback fired when a bridge transaction is initiated.
88
+ * Called before approval (if needed) or bridge execution begins.
89
+ */
90
+ onBridgeStart?: (params: {
91
+ /** Source chain ID */
92
+ sourceChainId: number;
93
+ /** Destination chain ID */
94
+ destChainId: number;
95
+ /** Amount being bridged (as string) */
96
+ amount: string;
97
+ /** Transaction hash (only present if tx already submitted) */
98
+ txHash?: `0x${string}`;
99
+ }) => void;
100
+ /**
101
+ * Callback fired when a bridge transaction completes successfully.
102
+ * The txHash can be used to link to a block explorer.
103
+ */
104
+ onBridgeSuccess?: (params: {
105
+ /** Source chain ID */
106
+ sourceChainId: number;
107
+ /** Destination chain ID */
108
+ destChainId: number;
109
+ /** Amount bridged (as string) */
110
+ amount: string;
111
+ /** Transaction hash of the successful bridge */
112
+ txHash: `0x${string}`;
113
+ }) => void;
114
+ /**
115
+ * Callback fired when a bridge transaction fails.
116
+ * The error object contains details about what went wrong.
117
+ */
118
+ onBridgeError?: (error: Error) => void;
119
+ /**
120
+ * Callback fired when the user clicks "Connect Wallet".
121
+ * If not provided, the widget will attempt to use wagmi's connectors.
122
+ */
123
+ onConnectWallet?: () => void;
124
+ /** Custom theme overrides to customize the widget appearance */
125
+ theme?: BridgeWidgetTheme;
126
+ /** Custom CSS class name to apply to the widget container */
127
+ className?: string;
128
+ /** Custom inline styles to apply to the widget container */
129
+ style?: React.CSSProperties;
130
+ }
131
+
132
+ /**
133
+ * Estimated costs and timing for a bridge transfer.
134
+ * Used by the deprecated useBridgeEstimate hook.
135
+ *
136
+ * @deprecated Use BridgeQuote from useBridge.ts instead
137
+ */
138
+ export interface BridgeEstimate {
139
+ /** Estimated gas fee (as formatted string) */
140
+ gasFee: string;
141
+ /** Protocol bridge fee (as formatted string) */
142
+ bridgeFee: string;
143
+ /** Total estimated fee including gas and protocol (as formatted string) */
144
+ totalFee: string;
145
+ /** Estimated time for the bridge to complete (e.g., "~15-20 minutes") */
146
+ estimatedTime: string;
147
+ }
148
+
149
+ /**
150
+ * Result state for a bridge operation.
151
+ * Tracks the current state and any associated transaction hash or error.
152
+ */
153
+ export interface BridgeResult {
154
+ /** Current state of the bridge operation */
155
+ state: "idle" | "pending" | "success" | "error";
156
+ /** Transaction hash if a transaction has been submitted */
157
+ txHash?: `0x${string}`;
158
+ /** Error message if the operation failed */
159
+ error?: string;
160
+ }