Embeddable React widget for USDC payments and cross-chain bridges. Built directly on CCTP V2.
Drop-in React component: same-chain sends and cross-ecosystem bridges over CCTP V2 Fast Transfer. EVM, Solana, Stellar, and Starknet wallets. Multichain ENS resolution in the box. Mid-flight recovery if a burn lands but the mint doesn't. Theme with CSS variables, go headless with useWhisk(). MIT-licensed.
Whisk is a drop-in React component for USDC payments. One React component, four ecosystems, native CCTP V2 Fast Transfers under the hood:
- Send USDC to any address on the same chain
- Bridge USDC across chains and ecosystems via CCTP V2
- Pay through any wallet: MetaMask, Phantom, Freighter, Argent X, and dozens more
- See a transparent fee breakdown before confirming
- Watch the transfer progress through every step (
approve→burn→fetchAttestation→mint)
Whisk speaks CCTP V2 natively: no third-party SDKs, no adapter layers to keep up to date. When Circle ships a new CCTP chain, we add it in one file and everyone gets it.
<WhiskProvider config={config}>
<WhiskSend onSuccess={(r) => console.log("done:", r.finalTxHash)} />
</WhiskProvider>That's it. The widget handles wallet connection, chain switching, recipient validation, fee preview, and the whole transaction lifecycle.
Building USDC payments from scratch means:
- A UI for the multi-step CCTP burn → attest → mint state machine
- Wallet connection across four ecosystems (EVM, Solana, Stellar, Starknet)
- Per-ecosystem address validation
- Fee math across burn fees, forwarder fees, and gas
- Iris polling with retry and recovery
- Manual mint into Solana, Stellar and Starknet (Circle only forwards mints into EVM chains)
- The Cairo
ByteArrayencoding for Starknet destinations - The Stellar CctpForwarder pattern for inbound Stellar mints
That's the work Whisk does once, well, and ships as a widget so you spend your time on the rest of your product.
pnpm add @usewhisk/react @tanstack/react-queryimport {
WhiskProvider,
WhiskSend,
createWhiskConfig,
evm,
} from "@usewhisk/react";
import "@usewhisk/react/styles.css";
const config = createWhiskConfig({
wallets: [
evm({
chains: ["Base_Sepolia", "Ethereum_Sepolia"],
projectId: process.env.WALLETCONNECT_PROJECT_ID!,
}),
],
chains: ["Base_Sepolia", "Ethereum_Sepolia"],
defaultDestinationChain: "Ethereum_Sepolia",
});
export default function App() {
return (
<WhiskProvider config={config}>
<WhiskSend />
</WhiskProvider>
);
}That's the entire integration.
Whisk mounts each ecosystem's provider only when its factory is
present in wallets. Every pair of ecosystems can bridge in both
directions.
import { evm, solana, stellar, starknet } from "@usewhisk/react";
const config = createWhiskConfig({
wallets: [evm(), solana(), stellar(), starknet()],
chains: [
"Base_Sepolia",
"Solana_Devnet",
"Stellar_Testnet",
"Starknet_Sepolia",
],
});<WhiskProvider config={config} theme="auto">
<WhiskSend />
</WhiskProvider>"light" (the default), "dark" and "auto" are built in, alongside a
few named palettes. See Theming.
| Capability | Notes |
|---|---|
| Send (same-chain) | Direct ERC-20 (EVM), SPL (Solana), Stellar Asset (Stellar), or Cairo transfer (Starknet). |
| Bridge (cross-chain) | CCTP V2 Fast Transfer where the source offers it, Standard otherwise. Approve → burn → attestation → mint, all automatic. |
| Cross-ecosystem | Every pair of the four ecosystems, both directions. |
| Smart routing | Whisk picks same-chain vs bridge from the source/destination chains. |
| Multi-wallet | Ecosystem-first connect modal: EVM (wagmi connectors), Solana (Wallet Standard), Stellar (Stellar Wallets Kit), Starknet (get-starknet-core). |
| Recipient resolvers | Address + ENSIP-11 multichain ENS built in; bring your own. |
| Bound quotes | Burn + forwarder + gas shown before confirm. send takes the quote the payer saw and refuses it if it expired or fees rose. |
| Fee bearer | feeBearer: "sender" (default) grosses up the burn so the recipient nets the exact amount; "receiver" deducts fees from the transfer. |
| Max button | Fills in the largest amount the wallet can send after fees, when the balance can be read. |
| Progress streaming | Live step events over the whole approve → burn → fetchAttestation → mint lifecycle. |
| Mid-flight recovery | Every bridge is saved from the moment the burn is signed (localStorage by default) and kept until dismissed. After a reload the widget lists it and finishes the mint without a second burn. |
| Network from the chain | Mainnet or testnet always follows the source chain. Mixing the two is refused with NetworkMismatchError. |
| Typed errors | RouteUnavailableError, QuoteExpiredError, QuoteChangedError, InsufficientBalanceError and more, each with a stable code. |
| Headless mode | useWhisk() hook for full UI control, or import @usewhisk/core for server flows. |
| Theming | light, dark and auto presets, --whisk-* CSS variables, optional Tailwind preset. |
| Open source | MIT. Read every line, fork it, ship your own. |
Whisk is a small monorepo. Three packages, one architecture.
whisk/
├── packages/
│ ├── cctp/ # @usewhisk/cctp: CCTP V2 protocol layer (chains, ABIs, Iris, builders)
│ ├── core/ # @usewhisk/core: framework-agnostic engine
│ └── react/ # @usewhisk/react: components, hooks, provider
├── examples/
│ ├── playground/ # Every-prop showcase + control panel
│ ├── ecommerce-checkout/ # Next.js storefront
│ ├── donate-button/ # Vite donation page
│ ├── themed-saas/ # Next.js dark dashboard
│ ├── payroll-batch/ # Vite batch payouts
│ ├── invoice-link/ # Next.js invoice links
│ ├── node-headless/ # Server-side scripts (send / payroll / refund)
│ └── vue-headless/ # Engine driving a Vue UI
└── apps/
└── docs/ # Documentation site (Fumadocs)
@usewhisk/cctp: CCTP V2 protocol layer. Chain registry (EVM + Solana + Stellar + Starknet), TokenMessengerV2 / MessageTransmitterV2 ABIs, Iris client, burn intent builders, manual mint builders. No engine logic, no UI.@usewhisk/core: the engine. Pure TypeScript, no React, no DOM. ExportscreateWhisk(), the adapter interfaces, the state machine, resolver composition, fee logic, and error classes.@usewhisk/react: components, hooks, and the provider. Wraps wagmi (EVM), Wallet Standard (Solana), Stellar Wallets Kit, andget-starknet-core. Ships<WhiskSend>(drop-in) anduseWhisk()(headless).
Deeper design docs (engine boundary, state machine, fee model, resolvers, errors) live on the documentation site.
Five reference integrations, each a themed mini-app. All run on testnet. Grab test USDC from the Circle faucet.
| Recipe | What it shows | Live demo |
|---|---|---|
| E-commerce checkout | Cart total + merchant address locked; the widget is confirm-and-pay | demo |
| Donate button | Pinned treasury + preset amounts that seed defaultAmount |
demo |
| Themed SaaS | --whisk-* overrides repaint the widget into a dark dashboard |
demo |
| Payroll batch | Widget remounts per payee, bridging treasury USDC to each one's chain | demo |
| Invoice link | A shareable URL opens a pre-filled, locked checkout | demo |
Two headless examples, node-headless for server flows and
vue-headless for a non-React UI, cover the engine-only path.
A selection of the supported chains. The full list, with domains and
Fast Transfer support, is in the
@usewhisk/cctp README.
| Ecosystem | Testnet chain | Mainnet chain |
|---|---|---|
| EVM | Ethereum_Sepolia |
Ethereum |
Base_Sepolia |
Base |
|
Arbitrum_Sepolia |
Arbitrum |
|
Optimism_Sepolia |
Optimism |
|
Polygon_Amoy_Testnet |
Polygon |
|
Avalanche_Fuji |
Avalanche |
|
Linea_Sepolia |
Linea |
|
Unichain_Sepolia |
Unichain |
|
World_Chain_Sepolia |
World_Chain |
|
Arc_Testnet |
Arc |
|
| Solana | Solana_Devnet |
Solana |
| Stellar | Stellar_Testnet |
Stellar |
| Starknet | Starknet_Sepolia |
Starknet |
Adding a chain is a new entry in packages/cctp/src/chains/. An EVM
chain also needs its viem chain in
packages/react/src/config/adapters/evm.ts.
Pick a preset on the provider: "light" (the default), "dark",
"wine", "indigo", "emerald", "monochrome", or "auto", which
follows prefers-color-scheme in CSS alone. To change some colors,
pass a preset with tokens:
<WhiskProvider
config={config}
theme={{ preset: "dark", tokens: { primary: "#6366f1", radius: "0.625rem" } }}
>
<WhiskSend />
</WhiskProvider>Everything is driven by --whisk-* CSS variables on [data-whisk].
With "light", "dark" or "auto", a rule in your own stylesheet
(loaded after styles.css) overrides any of them. The other presets set
their colors inline, so change those with tokens:
[data-whisk] {
--whisk-radius: 0.625rem;
--whisk-font: "Inter", system-ui, sans-serif;
}Tailwind users can opt into the preset:
// tailwind.config.js
import { whiskTheme } from "@usewhisk/react/tailwind";
export default {
presets: [whiskTheme()],
};- No private keys ever touch Whisk. The widget is browser-side; signing happens in the user's wallet.
- No telemetry. Nothing phones home.
- Open source under MIT. Audit every line. Fork it. Ship your own.
- Type-safe end to end. No
anycasts in the source. Every ecosystem's adapter is a discriminated union member. - Branch protection.
mainis protected; every change goes through PR review including changes from the maintainer. - CI on every PR. Format, lint, typecheck, tests, production build and a dependency audit.
- Nightly testnet matrix.
pnpm test:testnetsends real testnet USDC throughcreateWhisk()across EVM, Solana, Stellar and Starknet routes, plus a burn-then-resume scenario.
Whisk is community-maintained. Anyone can:
- File a bug or feature request: open an issue
- Improve the code: fork the repo, branch off
main, open a pull request - Improve the docs: same flow; small docs PRs are very welcome
Read CONTRIBUTING.md for development setup, project layout, PR conventions, and the security-disclosure flow.
git clone https://github.com/usewhisk/whisk.git
cd whisk
pnpm install
pnpm -r build # build all packages
pnpm -r typecheck # strict type-check all packages
pnpm -r test # run all package tests- Built directly on Circle's CCTP V2, the protocol doing the underlying settlement.
- Inspired by Uniswap Widgets, shadcn/ui for the theming approach, and wagmi for the config-driven adapter pattern.
- Not affiliated with Circle. Whisk is a community-built widget over Circle's public protocol.
