@hongming-wang/usdc-bridge-widget 0.1.1 → 0.2.1
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 +44 -6
- package/dist/index.d.mts +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +98 -74
- package/dist/index.mjs +99 -76
- package/package.json +10 -10
- package/src/BridgeWidget.tsx +137 -83
- package/src/__tests__/BridgeWidget.test.tsx +29 -0
- package/src/__tests__/hooks.test.ts +20 -1
- package/src/hooks.ts +12 -3
- package/src/types.ts +13 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ import { mainnet, arbitrum, base, optimism, polygon } from "viem/chains";
|
|
|
38
38
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
39
39
|
import { injected } from "wagmi/connectors";
|
|
40
40
|
|
|
41
|
-
// Create wagmi config
|
|
41
|
+
// Create wagmi config - IMPORTANT: Include transports for ALL chains you want to fetch balances from
|
|
42
42
|
const config = createConfig({
|
|
43
43
|
chains: [mainnet, arbitrum, base, optimism, polygon],
|
|
44
44
|
connectors: [injected()],
|
|
@@ -54,16 +54,20 @@ const config = createConfig({
|
|
|
54
54
|
const queryClient = new QueryClient();
|
|
55
55
|
|
|
56
56
|
function App() {
|
|
57
|
+
// You need to provide onConnectWallet to handle wallet connection
|
|
58
|
+
const handleConnectWallet = () => {
|
|
59
|
+
// For RainbowKit: use openConnectModal from useConnectModal()
|
|
60
|
+
// For ConnectKit: use open from useModal()
|
|
61
|
+
// For web3modal: use open from useWeb3Modal()
|
|
62
|
+
};
|
|
63
|
+
|
|
57
64
|
return (
|
|
58
65
|
<WagmiProvider config={config}>
|
|
59
66
|
<QueryClientProvider client={queryClient}>
|
|
60
|
-
{/* Minimal - uses all 17 CCTP chains by default */}
|
|
61
|
-
<BridgeWidget />
|
|
62
|
-
|
|
63
|
-
{/* Or with options */}
|
|
64
67
|
<BridgeWidget
|
|
65
68
|
defaultSourceChainId={1}
|
|
66
69
|
defaultDestinationChainId={8453}
|
|
70
|
+
onConnectWallet={handleConnectWallet}
|
|
67
71
|
onBridgeSuccess={({ txHash, amount }) => {
|
|
68
72
|
console.log(`Bridged ${amount} USDC: ${txHash}`);
|
|
69
73
|
}}
|
|
@@ -74,6 +78,23 @@ function App() {
|
|
|
74
78
|
}
|
|
75
79
|
```
|
|
76
80
|
|
|
81
|
+
### RainbowKit Integration
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { useConnectModal } from '@rainbow-me/rainbowkit';
|
|
85
|
+
|
|
86
|
+
function App() {
|
|
87
|
+
const { openConnectModal } = useConnectModal();
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<BridgeWidget
|
|
91
|
+
onConnectWallet={openConnectModal}
|
|
92
|
+
// ... other props
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
77
98
|
## Props
|
|
78
99
|
|
|
79
100
|
### BridgeWidgetProps
|
|
@@ -86,8 +107,9 @@ function App() {
|
|
|
86
107
|
| `onBridgeStart` | `function` | - | Called when bridge starts |
|
|
87
108
|
| `onBridgeSuccess` | `function` | - | Called on successful bridge |
|
|
88
109
|
| `onBridgeError` | `function` | - | Called on bridge error |
|
|
89
|
-
| `onConnectWallet` | `function` | - | Called when "Connect Wallet" clicked |
|
|
110
|
+
| `onConnectWallet` | `function` | - | **Recommended.** Called when "Connect Wallet" clicked. Required for wallet connection to work. |
|
|
90
111
|
| `theme` | `BridgeWidgetTheme` | Default theme | Custom theme overrides |
|
|
112
|
+
| `borderless` | `boolean` | `false` | Remove borders/shadows for seamless integration |
|
|
91
113
|
| `className` | `string` | - | Custom CSS class |
|
|
92
114
|
| `style` | `CSSProperties` | - | Custom inline styles |
|
|
93
115
|
|
|
@@ -169,6 +191,22 @@ const customChainConfig = {
|
|
|
169
191
|
/>
|
|
170
192
|
```
|
|
171
193
|
|
|
194
|
+
## Borderless Mode
|
|
195
|
+
|
|
196
|
+
Use the `borderless` prop for seamless integration into your existing UI. This removes all borders, shadows, and backgrounds from the widget container and its child components:
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
<BridgeWidget borderless />
|
|
200
|
+
|
|
201
|
+
{/* Or combine with custom styling */}
|
|
202
|
+
<div className="my-custom-container">
|
|
203
|
+
<BridgeWidget
|
|
204
|
+
borderless
|
|
205
|
+
style={{ padding: 0 }}
|
|
206
|
+
/>
|
|
207
|
+
</div>
|
|
208
|
+
```
|
|
209
|
+
|
|
172
210
|
## Callbacks
|
|
173
211
|
|
|
174
212
|
```tsx
|
package/dist/index.d.mts
CHANGED
|
@@ -121,11 +121,23 @@ interface BridgeWidgetProps {
|
|
|
121
121
|
onBridgeError?: (error: Error) => void;
|
|
122
122
|
/**
|
|
123
123
|
* Callback fired when the user clicks "Connect Wallet".
|
|
124
|
-
*
|
|
124
|
+
* Recommended for wallet connection to work. The widget does not auto-connect.
|
|
125
|
+
* If not provided, a warning will be logged in development mode.
|
|
126
|
+
*
|
|
127
|
+
* For RainbowKit: use `openConnectModal` from `useConnectModal()`
|
|
128
|
+
* For ConnectKit: use `open` from `useModal()`
|
|
129
|
+
* For web3modal: use `open` from `useWeb3Modal()`
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* // RainbowKit
|
|
133
|
+
* const { openConnectModal } = useConnectModal();
|
|
134
|
+
* <BridgeWidget onConnectWallet={openConnectModal} />
|
|
125
135
|
*/
|
|
126
136
|
onConnectWallet?: () => void;
|
|
127
137
|
/** Custom theme overrides to customize the widget appearance */
|
|
128
138
|
theme?: BridgeWidgetTheme;
|
|
139
|
+
/** Remove all borders from the widget for seamless integration */
|
|
140
|
+
borderless?: boolean;
|
|
129
141
|
/** Custom CSS class name to apply to the widget container */
|
|
130
142
|
className?: string;
|
|
131
143
|
/** Custom inline styles to apply to the widget container */
|
|
@@ -160,7 +172,7 @@ interface BridgeResult {
|
|
|
160
172
|
error?: string;
|
|
161
173
|
}
|
|
162
174
|
|
|
163
|
-
declare function BridgeWidget({ chains, defaultSourceChainId, defaultDestinationChainId, onBridgeStart, onBridgeSuccess, onBridgeError, onConnectWallet, theme: themeOverrides, className, style, }: BridgeWidgetProps): react_jsx_runtime.JSX.Element;
|
|
175
|
+
declare function BridgeWidget({ chains, defaultSourceChainId, defaultDestinationChainId, onBridgeStart, onBridgeSuccess, onBridgeError, onConnectWallet, theme: themeOverrides, borderless, className, style, }: BridgeWidgetProps): react_jsx_runtime.JSX.Element;
|
|
164
176
|
|
|
165
177
|
/**
|
|
166
178
|
* Hook to get USDC balance for a specific chain
|
package/dist/index.d.ts
CHANGED
|
@@ -121,11 +121,23 @@ interface BridgeWidgetProps {
|
|
|
121
121
|
onBridgeError?: (error: Error) => void;
|
|
122
122
|
/**
|
|
123
123
|
* Callback fired when the user clicks "Connect Wallet".
|
|
124
|
-
*
|
|
124
|
+
* Recommended for wallet connection to work. The widget does not auto-connect.
|
|
125
|
+
* If not provided, a warning will be logged in development mode.
|
|
126
|
+
*
|
|
127
|
+
* For RainbowKit: use `openConnectModal` from `useConnectModal()`
|
|
128
|
+
* For ConnectKit: use `open` from `useModal()`
|
|
129
|
+
* For web3modal: use `open` from `useWeb3Modal()`
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* // RainbowKit
|
|
133
|
+
* const { openConnectModal } = useConnectModal();
|
|
134
|
+
* <BridgeWidget onConnectWallet={openConnectModal} />
|
|
125
135
|
*/
|
|
126
136
|
onConnectWallet?: () => void;
|
|
127
137
|
/** Custom theme overrides to customize the widget appearance */
|
|
128
138
|
theme?: BridgeWidgetTheme;
|
|
139
|
+
/** Remove all borders from the widget for seamless integration */
|
|
140
|
+
borderless?: boolean;
|
|
129
141
|
/** Custom CSS class name to apply to the widget container */
|
|
130
142
|
className?: string;
|
|
131
143
|
/** Custom inline styles to apply to the widget container */
|
|
@@ -160,7 +172,7 @@ interface BridgeResult {
|
|
|
160
172
|
error?: string;
|
|
161
173
|
}
|
|
162
174
|
|
|
163
|
-
declare function BridgeWidget({ chains, defaultSourceChainId, defaultDestinationChainId, onBridgeStart, onBridgeSuccess, onBridgeError, onConnectWallet, theme: themeOverrides, className, style, }: BridgeWidgetProps): react_jsx_runtime.JSX.Element;
|
|
175
|
+
declare function BridgeWidget({ chains, defaultSourceChainId, defaultDestinationChainId, onBridgeStart, onBridgeSuccess, onBridgeError, onConnectWallet, theme: themeOverrides, borderless, className, style, }: BridgeWidgetProps): react_jsx_runtime.JSX.Element;
|
|
164
176
|
|
|
165
177
|
/**
|
|
166
178
|
* Hook to get USDC balance for a specific chain
|
package/dist/index.js
CHANGED
|
@@ -621,7 +621,7 @@ function useUSDCBalance(chainConfig) {
|
|
|
621
621
|
const { address } = (0, import_wagmi2.useAccount)();
|
|
622
622
|
const {
|
|
623
623
|
data: balance,
|
|
624
|
-
isLoading,
|
|
624
|
+
isLoading: queryLoading,
|
|
625
625
|
refetch
|
|
626
626
|
} = (0, import_wagmi2.useReadContract)({
|
|
627
627
|
address: chainConfig?.usdcAddress,
|
|
@@ -632,6 +632,7 @@ function useUSDCBalance(chainConfig) {
|
|
|
632
632
|
enabled: !!address && !!chainConfig?.usdcAddress
|
|
633
633
|
}
|
|
634
634
|
});
|
|
635
|
+
const isLoading = !!address && queryLoading;
|
|
635
636
|
return {
|
|
636
637
|
balance: balance ?? 0n,
|
|
637
638
|
balanceFormatted: balance ? (0, import_viem2.formatUnits)(balance, USDC_DECIMALS) : "0",
|
|
@@ -653,7 +654,7 @@ function useAllUSDCBalances(chainConfigs) {
|
|
|
653
654
|
}, [address, chainConfigs]);
|
|
654
655
|
const {
|
|
655
656
|
data: results,
|
|
656
|
-
isLoading,
|
|
657
|
+
isLoading: queryLoading,
|
|
657
658
|
refetch
|
|
658
659
|
} = (0, import_wagmi2.useReadContracts)({
|
|
659
660
|
contracts,
|
|
@@ -661,6 +662,7 @@ function useAllUSDCBalances(chainConfigs) {
|
|
|
661
662
|
enabled: !!address && contracts.length > 0
|
|
662
663
|
}
|
|
663
664
|
});
|
|
665
|
+
const isLoading = !!address && queryLoading;
|
|
664
666
|
const balances = (0, import_react2.useMemo)(() => {
|
|
665
667
|
const balanceMap = {};
|
|
666
668
|
if (!results) return balanceMap;
|
|
@@ -691,7 +693,7 @@ function useUSDCAllowance(chainConfig, spenderAddress) {
|
|
|
691
693
|
const effectiveSpender = spenderAddress || chainConfig?.tokenMessengerAddress;
|
|
692
694
|
const {
|
|
693
695
|
data: allowance,
|
|
694
|
-
isLoading,
|
|
696
|
+
isLoading: queryLoading,
|
|
695
697
|
refetch
|
|
696
698
|
} = (0, import_wagmi2.useReadContract)({
|
|
697
699
|
address: chainConfig?.usdcAddress,
|
|
@@ -702,6 +704,7 @@ function useUSDCAllowance(chainConfig, spenderAddress) {
|
|
|
702
704
|
enabled: !!address && !!chainConfig?.usdcAddress && !!effectiveSpender
|
|
703
705
|
}
|
|
704
706
|
});
|
|
707
|
+
const isLoading = !!address && queryLoading;
|
|
705
708
|
const { writeContractAsync, isPending: isApproving } = (0, import_wagmi2.useWriteContract)();
|
|
706
709
|
const [approvalTxHash, setApprovalTxHash] = (0, import_react2.useState)();
|
|
707
710
|
const [approvalError, setApprovalError] = (0, import_react2.useState)(null);
|
|
@@ -1269,12 +1272,40 @@ function WalletIcon({
|
|
|
1269
1272
|
|
|
1270
1273
|
// src/BridgeWidget.tsx
|
|
1271
1274
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1275
|
+
var TYPE_AHEAD_RESET_MS = 1e3;
|
|
1276
|
+
var DROPDOWN_MAX_HEIGHT = 300;
|
|
1277
|
+
var BOX_SHADOW_COLOR = "rgba(0,0,0,0.3)";
|
|
1278
|
+
var DISABLED_BUTTON_BACKGROUND = "rgba(255,255,255,0.1)";
|
|
1279
|
+
function getBorderlessStyles(borderless, theme, options) {
|
|
1280
|
+
const bgColor = options?.useBackgroundColor ? theme.backgroundColor : theme.cardBackgroundColor;
|
|
1281
|
+
return {
|
|
1282
|
+
borderRadius: borderless ? 0 : `${theme.borderRadius}px`,
|
|
1283
|
+
background: borderless ? "transparent" : bgColor,
|
|
1284
|
+
border: borderless ? "none" : `1px solid ${theme.borderColor}`,
|
|
1285
|
+
...options?.includeBoxShadow && {
|
|
1286
|
+
boxShadow: borderless ? "none" : `0 4px 24px ${BOX_SHADOW_COLOR}`
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
var SPINNER_KEYFRAMES = `@keyframes cc-balance-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }`;
|
|
1291
|
+
var KEYFRAMES_ATTR = "data-cc-spinner-keyframes";
|
|
1292
|
+
function injectSpinnerKeyframes() {
|
|
1293
|
+
if (typeof document === "undefined") return;
|
|
1294
|
+
if (document.querySelector(`style[${KEYFRAMES_ATTR}]`)) return;
|
|
1295
|
+
const style = document.createElement("style");
|
|
1296
|
+
style.setAttribute(KEYFRAMES_ATTR, "true");
|
|
1297
|
+
style.textContent = SPINNER_KEYFRAMES;
|
|
1298
|
+
document.head.appendChild(style);
|
|
1299
|
+
}
|
|
1272
1300
|
function ChainIcon({
|
|
1273
1301
|
chainConfig,
|
|
1274
1302
|
theme,
|
|
1275
1303
|
size = 24
|
|
1276
1304
|
}) {
|
|
1277
1305
|
const [hasError, setHasError] = (0, import_react3.useState)(false);
|
|
1306
|
+
(0, import_react3.useEffect)(() => {
|
|
1307
|
+
setHasError(false);
|
|
1308
|
+
}, [chainConfig.iconUrl]);
|
|
1278
1309
|
if (!chainConfig.iconUrl || hasError) {
|
|
1279
1310
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1280
1311
|
"div",
|
|
@@ -1308,6 +1339,9 @@ function ChainIcon({
|
|
|
1308
1339
|
);
|
|
1309
1340
|
}
|
|
1310
1341
|
function BalanceSpinner({ size = 12 }) {
|
|
1342
|
+
(0, import_react3.useEffect)(() => {
|
|
1343
|
+
injectSpinnerKeyframes();
|
|
1344
|
+
}, []);
|
|
1311
1345
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1312
1346
|
"svg",
|
|
1313
1347
|
{
|
|
@@ -1323,7 +1357,6 @@ function BalanceSpinner({ size = 12 }) {
|
|
|
1323
1357
|
},
|
|
1324
1358
|
"aria-hidden": "true",
|
|
1325
1359
|
children: [
|
|
1326
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `@keyframes cc-balance-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }` }),
|
|
1327
1360
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "10", strokeOpacity: "0.25" }),
|
|
1328
1361
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 2a10 10 0 0 1 10 10", strokeLinecap: "round" })
|
|
1329
1362
|
]
|
|
@@ -1340,7 +1373,8 @@ function ChainSelector({
|
|
|
1340
1373
|
id,
|
|
1341
1374
|
balances,
|
|
1342
1375
|
isLoadingBalances,
|
|
1343
|
-
disabled
|
|
1376
|
+
disabled,
|
|
1377
|
+
borderless
|
|
1344
1378
|
}) {
|
|
1345
1379
|
const [isOpen, setIsOpen] = (0, import_react3.useState)(false);
|
|
1346
1380
|
const [focusedIndex, setFocusedIndex] = (0, import_react3.useState)(-1);
|
|
@@ -1348,8 +1382,9 @@ function ChainSelector({
|
|
|
1348
1382
|
const typeAheadTimeoutRef = (0, import_react3.useRef)(null);
|
|
1349
1383
|
const buttonRef = (0, import_react3.useRef)(null);
|
|
1350
1384
|
const listRef = (0, import_react3.useRef)(null);
|
|
1351
|
-
const availableChains =
|
|
1352
|
-
(c) => c.chain.id !== excludeChainId
|
|
1385
|
+
const availableChains = (0, import_react3.useMemo)(
|
|
1386
|
+
() => chains.filter((c) => c.chain.id !== excludeChainId),
|
|
1387
|
+
[chains, excludeChainId]
|
|
1353
1388
|
);
|
|
1354
1389
|
(0, import_react3.useEffect)(() => {
|
|
1355
1390
|
return () => {
|
|
@@ -1412,7 +1447,7 @@ function ChainSelector({
|
|
|
1412
1447
|
}
|
|
1413
1448
|
typeAheadTimeoutRef.current = setTimeout(() => {
|
|
1414
1449
|
setTypeAhead("");
|
|
1415
|
-
},
|
|
1450
|
+
}, TYPE_AHEAD_RESET_MS);
|
|
1416
1451
|
const matchIndex = availableChains.findIndex(
|
|
1417
1452
|
(chain) => chain.chain.name.toLowerCase().startsWith(newTypeAhead)
|
|
1418
1453
|
);
|
|
@@ -1489,9 +1524,7 @@ function ChainSelector({
|
|
|
1489
1524
|
alignItems: "center",
|
|
1490
1525
|
justifyContent: "space-between",
|
|
1491
1526
|
padding: "10px 12px",
|
|
1492
|
-
|
|
1493
|
-
background: theme.cardBackgroundColor,
|
|
1494
|
-
border: `1px solid ${theme.borderColor}`,
|
|
1527
|
+
...getBorderlessStyles(borderless, theme),
|
|
1495
1528
|
cursor: disabled ? "not-allowed" : "pointer",
|
|
1496
1529
|
opacity: disabled ? 0.6 : 1,
|
|
1497
1530
|
transition: "all 0.2s"
|
|
@@ -1526,7 +1559,7 @@ function ChainSelector({
|
|
|
1526
1559
|
" Loading..."
|
|
1527
1560
|
]
|
|
1528
1561
|
}
|
|
1529
|
-
) : selectedBalance ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1562
|
+
) : balances && selectedBalance ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1530
1563
|
"span",
|
|
1531
1564
|
{
|
|
1532
1565
|
style: {
|
|
@@ -1584,11 +1617,11 @@ function ChainSelector({
|
|
|
1584
1617
|
width: "100%",
|
|
1585
1618
|
marginTop: "8px",
|
|
1586
1619
|
borderRadius: `${theme.borderRadius}px`,
|
|
1587
|
-
boxShadow:
|
|
1620
|
+
boxShadow: `0 10px 40px ${BOX_SHADOW_COLOR}`,
|
|
1588
1621
|
background: theme.cardBackgroundColor,
|
|
1589
1622
|
backdropFilter: "blur(10px)",
|
|
1590
1623
|
border: `1px solid ${theme.borderColor}`,
|
|
1591
|
-
maxHeight:
|
|
1624
|
+
maxHeight: `${DROPDOWN_MAX_HEIGHT}px`,
|
|
1592
1625
|
overflowY: "auto",
|
|
1593
1626
|
overflowX: "hidden",
|
|
1594
1627
|
padding: 0,
|
|
@@ -1600,6 +1633,7 @@ function ChainSelector({
|
|
|
1600
1633
|
const chainBalance = balances?.[chainConfig.chain.id];
|
|
1601
1634
|
const isFocused = index === focusedIndex;
|
|
1602
1635
|
const isSelected = chainConfig.chain.id === selectedChain.chain.id;
|
|
1636
|
+
const hasPositiveBalance = chainBalance ? parseFloat(chainBalance.formatted) > 0 : false;
|
|
1603
1637
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1604
1638
|
"li",
|
|
1605
1639
|
{
|
|
@@ -1649,28 +1683,19 @@ function ChainSelector({
|
|
|
1649
1683
|
},
|
|
1650
1684
|
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BalanceSpinner, { size: 10 })
|
|
1651
1685
|
}
|
|
1652
|
-
) : chainBalance ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1686
|
+
) : balances && chainBalance ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1653
1687
|
"span",
|
|
1654
1688
|
{
|
|
1655
1689
|
style: {
|
|
1656
1690
|
fontSize: "10px",
|
|
1657
|
-
color:
|
|
1691
|
+
color: hasPositiveBalance ? theme.successColor : theme.mutedTextColor
|
|
1658
1692
|
},
|
|
1659
1693
|
children: [
|
|
1660
1694
|
formatNumber(chainBalance.formatted, 2),
|
|
1661
1695
|
" USDC"
|
|
1662
1696
|
]
|
|
1663
1697
|
}
|
|
1664
|
-
) :
|
|
1665
|
-
"span",
|
|
1666
|
-
{
|
|
1667
|
-
style: {
|
|
1668
|
-
fontSize: "10px",
|
|
1669
|
-
color: theme.mutedTextColor
|
|
1670
|
-
},
|
|
1671
|
-
children: "0.00 USDC"
|
|
1672
|
-
}
|
|
1673
|
-
)
|
|
1698
|
+
) : null
|
|
1674
1699
|
] })
|
|
1675
1700
|
]
|
|
1676
1701
|
},
|
|
@@ -1722,7 +1747,9 @@ function AmountInput({
|
|
|
1722
1747
|
onMaxClick,
|
|
1723
1748
|
theme,
|
|
1724
1749
|
id,
|
|
1725
|
-
disabled
|
|
1750
|
+
disabled,
|
|
1751
|
+
showBalance = true,
|
|
1752
|
+
borderless
|
|
1726
1753
|
}) {
|
|
1727
1754
|
const inputId = `${id}-input`;
|
|
1728
1755
|
const labelId = `${id}-label`;
|
|
@@ -1768,7 +1795,7 @@ function AmountInput({
|
|
|
1768
1795
|
children: "Amount"
|
|
1769
1796
|
}
|
|
1770
1797
|
),
|
|
1771
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1798
|
+
showBalance && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1772
1799
|
"span",
|
|
1773
1800
|
{
|
|
1774
1801
|
style: { fontSize: "10px", color: theme.mutedTextColor },
|
|
@@ -1792,10 +1819,8 @@ function AmountInput({
|
|
|
1792
1819
|
style: {
|
|
1793
1820
|
display: "flex",
|
|
1794
1821
|
alignItems: "center",
|
|
1795
|
-
borderRadius: `${theme.borderRadius}px`,
|
|
1796
1822
|
overflow: "hidden",
|
|
1797
|
-
|
|
1798
|
-
border: `1px solid ${theme.borderColor}`,
|
|
1823
|
+
...getBorderlessStyles(borderless, theme),
|
|
1799
1824
|
opacity: disabled ? 0.6 : 1
|
|
1800
1825
|
},
|
|
1801
1826
|
children: [
|
|
@@ -1922,6 +1947,7 @@ function BridgeWidget({
|
|
|
1922
1947
|
onBridgeError,
|
|
1923
1948
|
onConnectWallet,
|
|
1924
1949
|
theme: themeOverrides,
|
|
1950
|
+
borderless = false,
|
|
1925
1951
|
className,
|
|
1926
1952
|
style
|
|
1927
1953
|
}) {
|
|
@@ -1929,13 +1955,11 @@ function BridgeWidget({
|
|
|
1929
1955
|
const { address, isConnected } = (0, import_wagmi3.useAccount)();
|
|
1930
1956
|
const currentChainId = (0, import_wagmi3.useChainId)();
|
|
1931
1957
|
const { switchChainAsync } = (0, import_wagmi3.useSwitchChain)();
|
|
1932
|
-
const { connect, connectors } = (0, import_wagmi3.useConnect)();
|
|
1933
1958
|
const [configError, setConfigError] = (0, import_react3.useState)(null);
|
|
1934
1959
|
(0, import_react3.useEffect)(() => {
|
|
1935
1960
|
const validation = validateChainConfigs(chains);
|
|
1936
1961
|
if (!validation.isValid) {
|
|
1937
1962
|
const errorMsg = validation.errors.join("; ");
|
|
1938
|
-
console.error("[BridgeWidget] Invalid chain configuration:", errorMsg);
|
|
1939
1963
|
setConfigError(errorMsg);
|
|
1940
1964
|
} else {
|
|
1941
1965
|
setConfigError(null);
|
|
@@ -1962,22 +1986,19 @@ function BridgeWidget({
|
|
|
1962
1986
|
const [txHash, setTxHash] = (0, import_react3.useState)();
|
|
1963
1987
|
const [error, setError] = (0, import_react3.useState)(null);
|
|
1964
1988
|
const { balances: allBalances, isLoading: isLoadingAllBalances, refetch: refetchAllBalances } = useAllUSDCBalances(chains);
|
|
1965
|
-
const
|
|
1966
|
-
sourceChainConfig
|
|
1967
|
-
);
|
|
1989
|
+
const balanceFormatted = (0, import_react3.useMemo)(() => {
|
|
1990
|
+
return allBalances[sourceChainConfig.chain.id]?.formatted ?? "0";
|
|
1991
|
+
}, [allBalances, sourceChainConfig.chain.id]);
|
|
1992
|
+
const parsedBalance = (0, import_react3.useMemo)(() => parseFloat(balanceFormatted), [balanceFormatted]);
|
|
1993
|
+
const parsedAmount = (0, import_react3.useMemo)(() => parseFloat(amount) || 0, [amount]);
|
|
1968
1994
|
const { needsApproval, approve, isApproving } = useUSDCAllowance(
|
|
1969
1995
|
sourceChainConfig
|
|
1970
1996
|
);
|
|
1971
|
-
const refetchBalances = (0, import_react3.useCallback)(() => {
|
|
1972
|
-
refetchBalance();
|
|
1973
|
-
refetchAllBalances();
|
|
1974
|
-
}, [refetchBalance, refetchAllBalances]);
|
|
1975
1997
|
(0, import_react3.useEffect)(() => {
|
|
1976
1998
|
if (address) {
|
|
1977
1999
|
refetchAllBalances();
|
|
1978
|
-
refetchBalance();
|
|
1979
2000
|
}
|
|
1980
|
-
}, [address, refetchAllBalances
|
|
2001
|
+
}, [address, refetchAllBalances]);
|
|
1981
2002
|
const { bridge: executeBridge, state: bridgeState, reset: resetBridge } = useBridge();
|
|
1982
2003
|
const { isLoading: isConfirming, isSuccess } = (0, import_wagmi3.useWaitForTransactionReceipt)({
|
|
1983
2004
|
hash: txHash
|
|
@@ -1990,11 +2011,11 @@ function BridgeWidget({
|
|
|
1990
2011
|
onBridgeErrorRef.current = onBridgeError;
|
|
1991
2012
|
const needsChainSwitch = isConnected && currentChainId !== sourceChainConfig.chain.id;
|
|
1992
2013
|
const handleSwapChains = (0, import_react3.useCallback)(() => {
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
}, [destChainConfig]);
|
|
2014
|
+
const newSource = destChainConfig;
|
|
2015
|
+
const newDest = sourceChainConfig;
|
|
2016
|
+
setSourceChainConfig(newSource);
|
|
2017
|
+
setDestChainConfig(newDest);
|
|
2018
|
+
}, [destChainConfig, sourceChainConfig]);
|
|
1998
2019
|
const handleMaxClick = (0, import_react3.useCallback)(() => {
|
|
1999
2020
|
setAmount(balanceFormatted);
|
|
2000
2021
|
}, [balanceFormatted]);
|
|
@@ -2006,7 +2027,7 @@ function BridgeWidget({
|
|
|
2006
2027
|
}
|
|
2007
2028
|
}, [switchChainAsync, sourceChainConfig.chain.id]);
|
|
2008
2029
|
const handleBridge = (0, import_react3.useCallback)(async () => {
|
|
2009
|
-
if (!address || !amount ||
|
|
2030
|
+
if (!address || !amount || parsedAmount <= 0) return;
|
|
2010
2031
|
setError(null);
|
|
2011
2032
|
resetBridge();
|
|
2012
2033
|
try {
|
|
@@ -2040,6 +2061,7 @@ function BridgeWidget({
|
|
|
2040
2061
|
}, [
|
|
2041
2062
|
address,
|
|
2042
2063
|
amount,
|
|
2064
|
+
parsedAmount,
|
|
2043
2065
|
needsApproval,
|
|
2044
2066
|
approve,
|
|
2045
2067
|
executeBridge,
|
|
@@ -2070,7 +2092,7 @@ function BridgeWidget({
|
|
|
2070
2092
|
const currentDestChainId = destChainConfig.chain.id;
|
|
2071
2093
|
const currentTxHash = bridgeState.txHash;
|
|
2072
2094
|
setAmount("");
|
|
2073
|
-
|
|
2095
|
+
refetchAllBalances();
|
|
2074
2096
|
if (currentTxHash) {
|
|
2075
2097
|
onBridgeSuccessRef.current?.({
|
|
2076
2098
|
sourceChainId: currentSourceChainId,
|
|
@@ -2086,14 +2108,14 @@ function BridgeWidget({
|
|
|
2086
2108
|
bridgeState.status,
|
|
2087
2109
|
bridgeState.txHash,
|
|
2088
2110
|
bridgeState.error,
|
|
2089
|
-
|
|
2111
|
+
refetchAllBalances,
|
|
2090
2112
|
amount,
|
|
2091
2113
|
sourceChainConfig.chain.id,
|
|
2092
2114
|
destChainConfig.chain.id
|
|
2093
2115
|
]);
|
|
2094
|
-
const isButtonDisabled = !isConnected || needsChainSwitch || !amount ||
|
|
2116
|
+
const isButtonDisabled = !isConnected || needsChainSwitch || !amount || parsedAmount <= 0 || parsedAmount > parsedBalance || isConfirming || isApproving || isBridging;
|
|
2095
2117
|
const isButtonActuallyDisabled = isButtonDisabled && !needsChainSwitch && isConnected;
|
|
2096
|
-
const
|
|
2118
|
+
const buttonText = (0, import_react3.useMemo)(() => {
|
|
2097
2119
|
if (!isConnected) return "Connect Wallet";
|
|
2098
2120
|
if (needsChainSwitch) return `Switch to ${sourceChainConfig.chain.name}`;
|
|
2099
2121
|
if (bridgeState.status === "loading") return "Preparing Bridge...";
|
|
@@ -2104,8 +2126,8 @@ function BridgeWidget({
|
|
|
2104
2126
|
if (isConfirming || isApproving) {
|
|
2105
2127
|
return "Approving...";
|
|
2106
2128
|
}
|
|
2107
|
-
if (!amount ||
|
|
2108
|
-
if (
|
|
2129
|
+
if (!amount || parsedAmount <= 0) return "Enter Amount";
|
|
2130
|
+
if (parsedAmount > parsedBalance) {
|
|
2109
2131
|
return "Insufficient Balance";
|
|
2110
2132
|
}
|
|
2111
2133
|
if (needsApproval(amount)) return "Approve & Bridge USDC";
|
|
@@ -2118,18 +2140,18 @@ function BridgeWidget({
|
|
|
2118
2140
|
isConfirming,
|
|
2119
2141
|
isApproving,
|
|
2120
2142
|
amount,
|
|
2121
|
-
|
|
2143
|
+
parsedAmount,
|
|
2144
|
+
parsedBalance,
|
|
2122
2145
|
needsApproval
|
|
2123
2146
|
]);
|
|
2124
2147
|
const handleButtonClick = (0, import_react3.useCallback)(() => {
|
|
2125
2148
|
if (!isConnected) {
|
|
2126
2149
|
if (onConnectWallet) {
|
|
2127
2150
|
onConnectWallet();
|
|
2128
|
-
} else
|
|
2129
|
-
|
|
2130
|
-
|
|
2151
|
+
} else {
|
|
2152
|
+
console.warn(
|
|
2153
|
+
"[BridgeWidget] onConnectWallet prop is not provided. Please provide onConnectWallet to handle wallet connection (e.g., openConnectModal from RainbowKit)."
|
|
2131
2154
|
);
|
|
2132
|
-
connect({ connector: injectedConnector || connectors[0] });
|
|
2133
2155
|
}
|
|
2134
2156
|
return;
|
|
2135
2157
|
}
|
|
@@ -2141,8 +2163,6 @@ function BridgeWidget({
|
|
|
2141
2163
|
}, [
|
|
2142
2164
|
isConnected,
|
|
2143
2165
|
onConnectWallet,
|
|
2144
|
-
connectors,
|
|
2145
|
-
connect,
|
|
2146
2166
|
needsChainSwitch,
|
|
2147
2167
|
handleSwitchChain,
|
|
2148
2168
|
handleBridge
|
|
@@ -2158,7 +2178,7 @@ function BridgeWidget({
|
|
|
2158
2178
|
cursor: isButtonActuallyDisabled ? "not-allowed" : "pointer",
|
|
2159
2179
|
transition: "all 0.2s",
|
|
2160
2180
|
color: isButtonActuallyDisabled ? theme.mutedTextColor : theme.textColor,
|
|
2161
|
-
background: isButtonActuallyDisabled ?
|
|
2181
|
+
background: isButtonActuallyDisabled ? DISABLED_BUTTON_BACKGROUND : `linear-gradient(135deg, ${theme.primaryColor} 0%, ${theme.secondaryColor} 100%)`,
|
|
2162
2182
|
boxShadow: isButtonActuallyDisabled ? "none" : `0 4px 14px ${theme.primaryColor}60, inset 0 1px 0 rgba(255,255,255,0.2)`
|
|
2163
2183
|
}),
|
|
2164
2184
|
[
|
|
@@ -2180,11 +2200,11 @@ function BridgeWidget({
|
|
|
2180
2200
|
fontFamily: theme.fontFamily,
|
|
2181
2201
|
maxWidth: "480px",
|
|
2182
2202
|
width: "100%",
|
|
2183
|
-
borderRadius: `${theme.borderRadius}px`,
|
|
2184
2203
|
padding: "16px",
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2204
|
+
...getBorderlessStyles(borderless, theme, {
|
|
2205
|
+
includeBoxShadow: true,
|
|
2206
|
+
useBackgroundColor: true
|
|
2207
|
+
}),
|
|
2188
2208
|
...style
|
|
2189
2209
|
},
|
|
2190
2210
|
children: [
|
|
@@ -2208,9 +2228,10 @@ function BridgeWidget({
|
|
|
2208
2228
|
onSelect: setSourceChainConfig,
|
|
2209
2229
|
excludeChainId: destChainConfig.chain.id,
|
|
2210
2230
|
theme,
|
|
2211
|
-
balances: allBalances,
|
|
2212
|
-
isLoadingBalances: isLoadingAllBalances,
|
|
2213
|
-
disabled: isOperationPending
|
|
2231
|
+
balances: isConnected ? allBalances : void 0,
|
|
2232
|
+
isLoadingBalances: isConnected && isLoadingAllBalances,
|
|
2233
|
+
disabled: isOperationPending,
|
|
2234
|
+
borderless
|
|
2214
2235
|
}
|
|
2215
2236
|
),
|
|
2216
2237
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SwapButton, { onClick: handleSwapChains, theme, disabled: isOperationPending }),
|
|
@@ -2224,9 +2245,10 @@ function BridgeWidget({
|
|
|
2224
2245
|
onSelect: setDestChainConfig,
|
|
2225
2246
|
excludeChainId: sourceChainConfig.chain.id,
|
|
2226
2247
|
theme,
|
|
2227
|
-
balances: allBalances,
|
|
2228
|
-
isLoadingBalances: isLoadingAllBalances,
|
|
2229
|
-
disabled: isOperationPending
|
|
2248
|
+
balances: isConnected ? allBalances : void 0,
|
|
2249
|
+
isLoadingBalances: isConnected && isLoadingAllBalances,
|
|
2250
|
+
disabled: isOperationPending,
|
|
2251
|
+
borderless
|
|
2230
2252
|
}
|
|
2231
2253
|
)
|
|
2232
2254
|
]
|
|
@@ -2241,7 +2263,9 @@ function BridgeWidget({
|
|
|
2241
2263
|
balance: balanceFormatted,
|
|
2242
2264
|
onMaxClick: handleMaxClick,
|
|
2243
2265
|
theme,
|
|
2244
|
-
disabled: isOperationPending
|
|
2266
|
+
disabled: isOperationPending,
|
|
2267
|
+
showBalance: isConnected,
|
|
2268
|
+
borderless
|
|
2245
2269
|
}
|
|
2246
2270
|
) }),
|
|
2247
2271
|
configError && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
@@ -2284,7 +2308,7 @@ function BridgeWidget({
|
|
|
2284
2308
|
disabled: isButtonActuallyDisabled,
|
|
2285
2309
|
"aria-busy": isConfirming || isApproving || isBridging,
|
|
2286
2310
|
style: buttonStyles,
|
|
2287
|
-
children:
|
|
2311
|
+
children: buttonText
|
|
2288
2312
|
}
|
|
2289
2313
|
)
|
|
2290
2314
|
]
|