@hongming-wang/usdc-bridge-widget
v0.3.4
Published
A reusable USDC cross-chain bridge widget powered by Circle CCTP
Downloads
70
Maintainers
Readme
USDC Bridge Widget
A reusable React widget for cross-chain USDC transfers powered by Circle's CCTP (Cross-Chain Transfer Protocol).
Features
- Cross-chain USDC transfers with native token minting
- No bridge fees (only gas costs)
- Customizable theme and styling
- Built-in chain switching
- TypeScript support
- Works with any wagmi-compatible wallet
Installation
npm install @honeypot-finance/usdc-bridge-widget
# or
yarn add @honeypot-finance/usdc-bridge-widget
# or
pnpm add @honeypot-finance/usdc-bridge-widgetPeer Dependencies
Make sure you have these peer dependencies installed:
npm install react react-dom wagmi viem @tanstack/react-queryQuick Start
import { BridgeWidget } from "@honeypot-finance/usdc-bridge-widget";
import { WagmiProvider, createConfig, http } from "wagmi";
import { mainnet, arbitrum, base, optimism, polygon } from "viem/chains";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { injected } from "wagmi/connectors";
// Create wagmi config - IMPORTANT: Include transports for ALL chains you want to fetch balances from
const config = createConfig({
chains: [mainnet, arbitrum, base, optimism, polygon],
connectors: [injected()],
transports: {
[mainnet.id]: http(),
[arbitrum.id]: http(),
[base.id]: http(),
[optimism.id]: http(),
[polygon.id]: http(),
},
});
const queryClient = new QueryClient();
function App() {
// You need to provide onConnectWallet to handle wallet connection
const handleConnectWallet = () => {
// For RainbowKit: use openConnectModal from useConnectModal()
// For ConnectKit: use open from useModal()
// For web3modal: use open from useWeb3Modal()
};
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<BridgeWidget
defaultSourceChainId={1}
defaultDestinationChainId={8453}
onConnectWallet={handleConnectWallet}
onBridgeSuccess={({ txHash, amount }) => {
console.log(`Bridged ${amount} USDC: ${txHash}`);
}}
/>
</QueryClientProvider>
</WagmiProvider>
);
}RainbowKit Integration
import { useConnectModal } from '@rainbow-me/rainbowkit';
import { useAccount } from 'wagmi';
function App() {
const { openConnectModal } = useConnectModal();
const { isConnected } = useAccount();
// Prevent opening modal if already connected or reconnecting
const handleConnectWallet = () => {
if (!isConnected && openConnectModal) {
openConnectModal();
}
};
return (
<BridgeWidget
onConnectWallet={handleConnectWallet}
// ... other props
/>
);
}Important Integration Notes
Wagmi Config Must Match Widget Chains
The widget fetches USDC balances for all chains passed via the chains prop. Your wagmi config must include transports for every chain you want to display:
import { createConfig, http } from 'wagmi';
import { mainnet, base, arbitrum } from 'viem/chains';
import { createChainConfig } from '@honeypot-finance/usdc-bridge-widget';
// ✅ Good: Wagmi config matches widget chains
const config = createConfig({
chains: [mainnet, base, arbitrum],
transports: {
[mainnet.id]: http(),
[base.id]: http(),
[arbitrum.id]: http(),
},
});
const widgetChains = [
createChainConfig(mainnet),
createChainConfig(base),
createChainConfig(arbitrum),
];
<BridgeWidget chains={widgetChains} />If a chain is in the widget but not in your wagmi config, balance fetching will fail for that chain.
Handling Wallet Reconnection
The widget handles reconnecting states automatically, showing "Connecting..." during wagmi's auto-reconnect. Your onConnectWallet callback only needs a simple isConnected check since the widget handles reconnecting states internally:
const { isConnected } = useAccount();
const handleConnectWallet = () => {
if (!isConnected) {
openConnectModal?.();
}
};Props
BridgeWidgetProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| chains | BridgeChainConfig[] | All CCTP chains | Array of supported chains |
| defaultSourceChainId | number | First chain | Default source chain ID |
| defaultDestinationChainId | number | Second chain | Default destination chain ID |
| onBridgeStart | function | - | Called when bridge starts |
| onBridgeSuccess | function | - | Called on successful bridge |
| onBridgeError | function | - | Called on bridge error |
| onConnectWallet | function | - | Recommended. Called when "Connect Wallet" clicked. Required for wallet connection to work. |
| theme | BridgeWidgetTheme | Default theme | Custom theme overrides |
| borderless | boolean | false | Remove borders/shadows for seamless integration |
| enablePersistence | boolean | true | Persist bridge state to localStorage for recovery |
| onPendingBridgeDetected | function | - | Called when incomplete bridges are found on load |
| onRecoveryComplete | function | - | Called when a recovered bridge completes |
| onRecoveryError | function | - | Called when bridge recovery fails |
| className | string | - | Custom CSS class |
| style | CSSProperties | - | Custom inline styles |
BridgeChainConfig
interface BridgeChainConfig {
chain: Chain; // viem Chain object
usdcAddress: `0x${string}`; // USDC contract address
tokenMessengerAddress?: `0x${string}`; // Circle TokenMessenger address
iconUrl?: string; // Optional chain icon URL
}BridgeWidgetTheme
interface BridgeWidgetTheme {
primaryColor?: string; // Default: "#6366f1"
secondaryColor?: string; // Default: "#a855f7"
backgroundColor?: string; // Default: "rgba(15, 15, 25, 0.8)"
cardBackgroundColor?: string;
textColor?: string; // Default: "#ffffff"
mutedTextColor?: string;
borderColor?: string;
successColor?: string; // Default: "#22c55e"
errorColor?: string; // Default: "#ef4444"
hoverColor?: string; // Default: "rgba(255, 255, 255, 0.05)"
borderRadius?: number; // Default: 12
fontFamily?: string;
}Custom Chain Configuration
import { BridgeWidget, createChainConfig, USDC_ADDRESSES } from "@honeypot-finance/usdc-bridge-widget";
import { mainnet, arbitrum, base } from "viem/chains";
import { defineChain } from "viem";
// Use helper function
const chains = [
createChainConfig(mainnet, {
iconUrl: "https://example.com/eth-icon.png",
}),
createChainConfig(arbitrum),
createChainConfig(base),
];
// Or define manually
const customChain = defineChain({
id: 12345,
name: "Custom Chain",
// ... other chain config
});
const customChainConfig = {
chain: customChain,
usdcAddress: "0x..." as `0x${string}`,
tokenMessengerAddress: "0x..." as `0x${string}`,
iconUrl: "https://example.com/custom-icon.png",
};
<BridgeWidget chains={[...chains, customChainConfig]} />Custom Theme
<BridgeWidget
chains={DEFAULT_CHAIN_CONFIGS}
theme={{
primaryColor: "#00ff00",
secondaryColor: "#00cc00",
backgroundColor: "#1a1a2e",
borderRadius: 16,
fontFamily: "Inter, sans-serif",
}}
/>Borderless Mode
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:
<BridgeWidget borderless />
{/* Or combine with custom styling */}
<div className="my-custom-container">
<BridgeWidget
borderless
style={{ padding: 0 }}
/>
</div>Callbacks
<BridgeWidget
chains={DEFAULT_CHAIN_CONFIGS}
onBridgeStart={({ sourceChainId, destChainId, amount }) => {
console.log(`Starting bridge: ${amount} USDC from ${sourceChainId} to ${destChainId}`);
}}
onBridgeSuccess={({ sourceChainId, destChainId, amount, txHash }) => {
console.log(`Bridge successful! TX: ${txHash}`);
}}
onBridgeError={(error) => {
console.error("Bridge failed:", error.message);
}}
onConnectWallet={() => {
// Open your wallet modal
openWalletModal();
}}
/>Bridge Recovery
The widget automatically persists bridge state to localStorage. If a user closes the tab during bridging (e.g., during the ~15 min attestation wait), a recovery banner appears on next visit allowing them to resume.
<BridgeWidget
enablePersistence={true} // default
onPendingBridgeDetected={(bridges) => {
console.log(`Found ${bridges.length} incomplete bridge(s)`);
}}
onRecoveryComplete={({ sourceChainId, destChainId, amount, txHash }) => {
console.log(`Recovery complete: ${amount} USDC, tx: ${txHash}`);
}}
onRecoveryError={(error) => {
console.error("Recovery failed:", error.message);
}}
/>Advanced consumers can use the recovery hook and storage utilities directly:
import { useRecovery, loadPendingBridges } from "@honeypot-finance/usdc-bridge-widget";Using Individual Hooks
The widget exports its internal hooks for advanced usage:
import {
useUSDCBalance,
useAllUSDCBalances,
useUSDCAllowance,
useBridge,
useBridgeQuote,
useRecovery,
} from "@honeypot-finance/usdc-bridge-widget";
function CustomComponent() {
const chainConfig = { chain: mainnet, usdcAddress: "0x..." };
// Balance hooks
const { balance, balanceFormatted } = useUSDCBalance(chainConfig);
const { balances, isLoading } = useAllUSDCBalances([chainConfig]);
// Allowance hook
const { needsApproval, approve, isApproving } = useUSDCAllowance(chainConfig);
// Bridge execution hook
const { bridge, state, reset } = useBridge();
// state.status: "idle" | "loading" | "approving" | "burning" | "fetching-attestation" | "minting" | "success" | "error"
// Quote hook (returns static CCTP estimates)
const { quote, isLoading: quoteLoading } = useBridgeQuote(1, 8453, "100");
// Build your own UI
}Deprecated Hooks
The following hooks are deprecated and will be removed in a future version:
useBridgeEstimate- UseuseBridgeQuoteinsteaduseFormatNumber- Use theformatNumberutility function directly
Supported Chains
The widget includes pre-configured USDC and TokenMessenger addresses for all CCTP V2 EVM chains:
| Chain | Chain ID | |-------|----------| | Ethereum | 1 | | Arbitrum | 42161 | | Base | 8453 | | Optimism | 10 | | Polygon | 137 | | Avalanche | 43114 | | Linea | 59144 | | Unichain | 130 | | Sonic | 146 | | World Chain | 480 | | Monad | 10200 | | Sei | 1329 | | XDC | 50 | | HyperEVM | 999 | | Ink | 57073 | | Plume | 98866 | | Codex | 81224 |
See Circle CCTP Docs for the latest supported chains
Circle Bridge Kit Integration
For full bridge functionality, install Circle's Bridge Kit:
npm install @circle-fin/bridge-kit @circle-fin/adapter-viem-v2Then integrate with the widget's callbacks to execute actual transfers.
License
MIT
