Bridge USDC across Ethereum, Solana, and Stacks — Multichain Stablecoin Transfers
Hermes Bridge is a secure, production-ready multichain bridge enabling seamless USDC transfers between Ethereum, Solana, and Stacks blockchains. It provides:
- Multichain Bridge: Bridge USDC between Ethereum, Solana, and Stacks using Circle's trusted xReserve protocol and CCTP (Cross-Chain Transfer Protocol).
- Stacks Transfers: Transfer USDCx between Stacks addresses with full custody and control.
Features • How It Works • Architecture • Quick Start • Contracts • Contributing
- Table of Contents
- Core Features
- Bridge vs Transfer
- Use Cases
- Preview
- How It Works
- Architecture
- Quick Start
- Smart Contracts
- Configuration
- Project Structure
- Testing
- Deployment
- Contributing
- License
- Support
- Resources
| Feature | Description |
|---|---|
| USDC Multichain Bridge | Atomic cross-chain transfers between Ethereum, Solana, and Stacks |
| Circle xReserve Integration | Industry-standard attestation-based bridging |
| Approval-based Flow | Secure two-step process (approve, then deposit) |
| Address Encoding | Automatic Stacks address encoding for Ethereum/Solana contracts |
| Live Status Tracking | Monitor bridge status through attestation service |
| Feature | Description |
|---|---|
| Native USDCx Transfers | Direct transfers between Stacks addresses |
| Custody & Control | Full control of your USDCx tokens |
| Single-Step Transactions | Fast, simple peer-to-peer transfers |
| Balance Verification | Real-time balance checks before transfer |
| Post-Conditions | Enhanced security with transaction post-conditions |
| Feature | Description |
|---|---|
| Multi-Wallet Support | MetaMask, Coinbase Wallet, WalletConnect, Phantom, Solflare, Leather |
| Multichain Interface | Unified UI for Ethereum, Solana, and Stacks operations |
| Real-Time Tracking | Live transaction status and balance updates |
| Modern UI/UX | Beautiful, responsive interface with dark mode |
| Optimized Performance | Built with Vite for blazing fast load times |
| Secure & Reliable | Powered by Circle's trusted xReserve protocol |
Use the Bridge feature when:
- You want to move USDC between Ethereum, Solana, and Stacks
- You need to access DeFi opportunities across multiple chains
- Converting stablecoins for cross-chain operations
- Leveraging Circle's attestation infrastructure and CCTP
Bridge Characteristics:
- Requires native gas token (ETH, SOL, etc.) for source chain
- 5-30 minute attestation window
- Atomic transaction: approve + deposit in one flow
- Powered by Circle's xReserve and CCTP contracts
- Addresses are encoded for cross-chain compatibility
Use the Transfer feature when:
- You already have USDCx on Stacks
- Sending USDCx to other Stacks addresses
- Direct peer-to-peer payments on Stacks
- Requiring instant confirmation
Transfer Characteristics:
- Fast confirmation (12-15 seconds)
- Only requires STX for gas fees
- Simple recipient address input
- Native Stacks smart contract call
- Ideal for DeFi operations and payments
Ethereum Wallet (USDC) → Bridge → Stacks Wallet (USDCx)
Flow: Connect MetaMask → Approve USDC → Enter Stacks Address → Bridge
Time: ~20 minutes | Network: Ethereum Sepolia + Stacks Testnet
Solana Wallet (USDC) → Bridge → Stacks Wallet (USDCx)
Flow: Connect Phantom → Enter Amount & Stacks Address → Bridge
Time: ~20 minutes | Network: Solana Devnet + Stacks Testnet
Your Stacks Wallet (USDCx) → Transfer → Other Stacks Address (USDCx)
Flow: Connect Leather → Enter Recipient → Enter Amount → Transfer
Time: ~15 seconds | Network: Stacks Testnet only
Ethereum/Solana (USDC) → Bridge to Stacks (USDCx) → Stacks DeFi → Transfer USDCx
Flow: Complete bridge → Access Stacks DeFi → Transfer profits to recipients
Time: 20-30 minutes total | Networks: Source chain + Stacks
┌─────────────────────────────────────────────────────────────────┐
│ ⚡ HERMES BRIDGE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ From: Ethereum Sepolia │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ Amount: [1.00 ] USDC [MAX] │ │ │
│ │ │ Balance: 100.00 USDC │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ ⬇️ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ To: Stacks Testnet │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ Recipient: [ST1ABC...XYZ ] │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ [ 🚀 Bridge to Stacks ] │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ TRANSFER USDCx │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Your Balance: 10.00 USDCx │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Amount: [5.00 ] USDCx [MAX] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Recipient: [ST2XYZ...ABC ] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ [ Send USDCx ] │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
flowchart TD
A[User] -->|1. Connect Wallet| B[Ethereum Wallet]
B -->|2. Approve USDC| C[USDC Contract]
C -->|3. Allowance Set| D[xReserve Contract]
A -->|4. Enter Amount & Stacks Address| D
D -->|5. depositToRemote| E[Lock USDC]
E -->|6. Emit Event| F[Circle Attestation Service]
F -->|7. Verify & Attest| G[Stacks Attestation]
G -->|8. Call mint| H[USDCx Contract]
H -->|9. Mint USDCx| I[User receives USDCx]
style A fill:#f9f,stroke:#333,stroke-width:2px
style I fill:#9f9,stroke:#333,stroke-width:2px
style F fill:#ff9,stroke:#333,stroke-width:2px
flowchart LR
A[Sender] -->|1. Connect Leather| B[Stacks Wallet]
B -->|2. Enter Recipient & Amount| C[Transfer Form]
C -->|3. Sign Transaction| D[USDCx Contract]
D -->|4. transfer| E[Stacks Blockchain]
E -->|5. Confirm| F[Recipient receives USDCx]
style A fill:#f9f,stroke:#333,stroke-width:2px
style F fill:#9f9,stroke:#333,stroke-width:2px
gantt
title Bridge Transaction Timeline
dateFormat mm:ss
section Ethereum
Connect Wallet :a1, 00:00, 10s
Approve USDC :a2, after a1, 30s
Deposit to xReserve :a3, after a2, 30s
section Circle
Event Detection :b1, after a3, 60s
Attestation Generation :b2, after b1, 120s
section Stacks
Submit Mint TX :c1, after b2, 30s
Confirm & Finalize :c2, after c1, 60s
graph TB
subgraph "Frontend Application"
UI[React UI]
WP[Wallet Providers]
BH[Bridge Hook]
SH[Stacks Hook]
SOH[Solana Hook]
end
subgraph "Ethereum Sepolia"
USDC[USDC Token<br/>0x1c7D...7238]
XR[xReserve Contract<br/>0x0088...4442]
end
subgraph "Solana Devnet"
SUSDC[USDC Token<br/>EPjFW...s1v]
SXR[xReserve Contract<br/>CCTP...Bridge]
end
subgraph "Circle Infrastructure"
ATT[Attestation Service]
REL[Relayer]
end
subgraph "Stacks Testnet"
USDCX[USDCx Token<br/>ST1PQH...usdcx]
USDCXV1[USDCx-v1 Protocol<br/>ST1PQH...usdcx-v1]
end
UI --> WP
WP --> BH
WP --> SH
WP --> SOH
BH -->|approve| USDC
BH -->|depositToRemote| XR
SOH -->|approve| SUSDC
SOH -->|depositToRemote| SXR
XR -->|Lock USDC| USDC
SXR -->|Lock USDC| SUSDC
XR -.->|Event| ATT
SXR -.->|Event| ATT
ATT -->|Attestation| REL
REL -->|mint| USDCXV1
USDCXV1 -->|Mint| USDCX
SH -->|transfer| USDCX
style UI fill:#61DAFB,stroke:#333
style ATT fill:#FFD700,stroke:#333
style USDCX fill:#5546FF,stroke:#333
style SUSDC fill:#9945FF,stroke:#333
graph TD
subgraph "Pages"
INDEX[Index.tsx]
SOLANA[Solana.tsx]
end
subgraph "Bridge Components"
BF[BridgeForm.tsx]
TF[TransferForm.tsx]
BD[BalanceDisplay.tsx]
CWB[ConnectWalletButton.tsx]
end
subgraph "Custom Hooks"
UB[useBridge.ts]
USW[useStacksWallet.ts]
UBS[useBridgeStatus.ts]
UEW[useEthereumWallet.ts]
USWH[useSolanaWallet.ts]
end
subgraph "Libraries"
BC[bridge-config.ts]
SA[stacks-address.ts]
WC[wagmi-config.ts]
end
INDEX --> BF
INDEX --> TF
INDEX --> BD
INDEX --> CWB
SOLANA --> CWB
BF --> UB
BF --> UBS
TF --> USW
BD --> UB
BD --> USW
CWB --> UEW
SOLANA --> USWH
UB --> BC
UB --> SA
UB --> WC
USW --> SA
USWH --> BC
style INDEX fill:#61DAFB
style UB fill:#9f9
style USW fill:#9f9
sequenceDiagram
participant U as User
participant F as Frontend
participant EW as ETH Wallet
participant USDC as USDC
participant XR as xReserve
participant CA as Circle
participant SX as USDCx
U->>F: Enter amount & Stacks address
F->>EW: Request USDC approval
EW->>USDC: approve(xReserve, amount)
USDC-->>EW: Approval confirmed
EW-->>F: TX Hash
F->>EW: Request deposit
EW->>XR: depositToRemote(...)
XR->>USDC: transferFrom(user, xReserve, amount)
XR-->>EW: Deposit confirmed
EW-->>F: TX Hash
Note over CA: Attestation Service monitors events
XR--)CA: DepositToRemote event
CA->>CA: Verify & generate attestation
CA->>SX: Submit mint with attestation
SX->>SX: Mint USDCx to recipient
F->>SX: Poll for balance
SX-->>F: Balance updated
F-->>U: Bridge complete!
sequenceDiagram
participant U as User
participant F as Frontend
participant SW as Leather Wallet
participant SX as USDCx Contract
U->>F: Connect Stacks wallet
F->>SW: connect()
SW-->>F: Address & public key
U->>F: Enter recipient & amount
F->>F: Validate inputs
F->>SW: Request transfer signature
SW->>U: Approve transaction?
U->>SW: Confirm
SW->>SX: transfer(amount, sender, recipient, memo)
SX-->>SW: TX ID
SW-->>F: TX ID
F-->>U: Transfer complete!
| Requirement | Version | Description |
|---|---|---|
| Node.js | >= 18.0.0 | JavaScript runtime |
| pnpm | >= 8.0.0 | Package manager (recommended) |
| MetaMask | Latest | Ethereum wallet |
| Phantom/Solflare | Latest | Solana wallet |
| Leather | Latest | Stacks wallet |
| Token | Faucet | Network |
|---|---|---|
| Sepolia ETH | Google Cloud Faucet | Ethereum Sepolia |
| Testnet USDC | Circle Faucet | Ethereum Sepolia |
| Devnet SOL | Solana Faucet | Solana Devnet |
| Devnet USDC | Circle Faucet | Solana Devnet |
| Testnet STX | Stacks Faucet | Stacks Testnet |
For mobile browsers to connect wallets properly:
- HTTPS Required: WalletConnect requires HTTPS in production. Deploy to Vercel/Netlify for automatic HTTPS.
- WalletConnect Project: Ensure your WalletConnect project is configured with:
- Correct app URL (
https://your-domain.com) - App icon (use
/hermes-logo.svg) - Description: "Cross-chain USDC bridge between Ethereum and Stacks"
- Correct app URL (
- Supported Wallets:
- Ethereum: MetaMask, Coinbase Wallet, Trust Wallet, Rainbow
- Solana: Phantom, Solflare, Backpack
- Stacks: Leather Wallet (mobile app)
- Mobile Browsers: Works best in Safari (iOS), Chrome (Android)
Testing Mobile Wallets:
- Use your phone's browser to access the deployed app
- Scan QR codes with wallet apps
- Ensure popups are not blocked
- Try both portrait and landscape orientations
# Clone the repository
git clone https://github.com/yourusername/hermes-bridge.git
cd hermes-bridge
# Install dependencies
pnpm install# Start development server
pnpm dev
# App available at http://localhost:5173# Build for production
pnpm build
# Preview production build
pnpm preview
# Type checking
pnpm exec tsc --noEmit
# Linting
pnpm lint| Contract | Address | Explorer |
|---|---|---|
| USDC Token | 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 |
View on Etherscan |
| xReserve | 0x008888878f94C0d87defdf0B07f46B93C1934442 |
View on Etherscan |
| Contract | Address | Explorer |
|---|---|---|
| USDC Token | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
View on Solscan |
| CCTP Message Transmitter | CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd |
View on Solscan |
import { parseUnits } from 'viem';
// ABI for ERC-20 approve
const ERC20_ABI = [
{
name: "approve",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "spender", type: "address" },
{ name: "amount", type: "uint256" },
],
outputs: [{ name: "success", type: "bool" }],
},
];
// Approve xReserve to spend USDC
await walletClient.writeContract({
address: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", // USDC
abi: ERC20_ABI,
functionName: "approve",
args: [
"0x008888878f94C0d87defdf0B07f46B93C1934442", // xReserve
parseUnits("100", 6) // 100 USDC (6 decimals)
],
});import { parseUnits } from 'viem';
import { encodeStacksAddress } from './stacks-address';
// ABI for xReserve depositToRemote
const X_RESERVE_ABI = [
{
name: "depositToRemote",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "value", type: "uint256" },
{ name: "remoteDomain", type: "uint32" },
{ name: "remoteRecipient", type: "bytes32" },
{ name: "localToken", type: "address" },
{ name: "maxFee", type: "uint256" },
{ name: "hookData", type: "bytes" },
],
outputs: [],
},
];
// Bridge USDC to Stacks
const stacksRecipient = "ST1ABC...XYZ"; // Stacks testnet address
const encodedRecipient = encodeStacksAddress(stacksRecipient);
await walletClient.writeContract({
address: "0x008888878f94C0d87defdf0B07f46B93C1934442", // xReserve
abi: X_RESERVE_ABI,
functionName: "depositToRemote",
args: [
parseUnits("1.0", 6), // 1 USDC (6 decimals)
10003, // Stacks domain ID (constant)
encodedRecipient, // bytes32 encoded Stacks address
"0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", // USDC token
0n, // maxFee (0 for testnet)
"0x", // hookData (empty)
],
});import * as P from 'micro-packed';
import { createAddress } from '@stacks/transactions';
import { hex } from '@scure/base';
import { toHex, pad, type Hex } from 'viem';
// Encoder for Stacks address to bytes32
export const remoteRecipientCoder = P.wrap<string>({
encodeStream(w, value: string) {
const address = createAddress(value);
// 11 zero bytes padding
P.bytes(11).encodeStream(w, new Uint8Array(11).fill(0));
// 1 version byte
P.U8.encodeStream(w, address.version);
// 20 hash160 bytes
P.bytes(20).encodeStream(w, hex.decode(address.hash160));
},
decodeStream(r) {
P.bytes(11).decodeStream(r);
const version = P.U8.decodeStream(r);
const hash = P.bytes(20).decodeStream(r);
return addressToString({
hash160: hex.encode(hash),
version: version as AddressVersion,
type: StacksWireType.Address,
});
},
});
export function encodeStacksAddress(stacksAddress: string): Hex {
const encoded = remoteRecipientCoder.encode(stacksAddress);
return toHex(pad(encoded, { size: 32 }));
}
// Example
const encoded = encodeStacksAddress("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM");
// Returns: 0x0000000000000000000000001a...import { request } from '@stacks/connect';
import { Cl } from '@stacks/transactions';
// Transfer USDCx using @stacks/connect v8
const response = await request('stx_callContract', {
contract: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usdcx',
functionName: 'transfer',
functionArgs: [
Cl.uint(1000000n), // 1 USDCx (6 decimals)
Cl.principal('ST1ABC...sender'), // sender
Cl.principal('ST2XYZ...recipient'), // recipient
Cl.none(), // memo (optional)
],
network: 'testnet',
});
console.log('Transaction ID:', response.txid);// Fetch balance from Hiro API
const address = 'ST1ABC...XYZ';
const response = await fetch(
`https://api.testnet.hiro.so/extended/v1/address/${address}/balances`
);
const data = await response.json();
// Extract USDCx balance
const usdcxKey = 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usdcx::usdcx-token';
const balanceRaw = data.fungible_tokens?.[usdcxKey]?.balance || '0';
const balanceFormatted = (parseInt(balanceRaw) / 1_000_000).toFixed(6);
console.log(`USDCx Balance: ${balanceFormatted}`);Located in src/lib/bridge-config.ts:
export const BRIDGE_CONFIG = {
// Ethereum Sepolia contracts
X_RESERVE_CONTRACT: "0x008888878f94C0d87defdf0B07f46B93C1934442",
ETH_USDC_CONTRACT: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
// Solana Devnet contracts
SOLANA_USDC_MINT: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
SOLANA_MESSAGE_TRANSMITTER: "CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd",
// Stacks domain ID (constant for all networks)
STACKS_DOMAIN: 10003,
// RPC endpoints
ETH_RPC_URL: "https://ethereum-sepolia.publicnode.com",
SOLANA_RPC_URL: "https://api.devnet.solana.com",
// Chain IDs
ETH_CHAIN_ID: 11155111, // Sepolia
SOLANA_CHAIN_ID: 103, // Devnet
};Located in src/hooks/useStacksWallet.ts:
const USDCX_CONTRACT = {
address: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',
name: 'usdcx',
assetName: 'usdcx-token',
};hermes-bridge/
├── public/
│ ├── hermes-logo.svg # App logo
│ ├── og-image.png # Social media preview
│ └── robots.txt # SEO configuration
├── src/
│ ├── components/
│ │ ├── bridge/
│ │ │ ├── BridgeForm.tsx # Main bridge UI component
│ │ │ ├── TransferForm.tsx # USDCx transfer UI
│ │ │ ├── BalanceDisplay.tsx # Wallet balance cards
│ │ │ └── ConnectWalletButton.tsx
│ │ └── ui/ # shadcn/ui components
│ ├── hooks/
│ │ ├── useBridge.ts # Ethereum bridge logic
│ │ ├── useBridgeStatus.ts # Bridge status tracking
│ │ ├── useStacksWallet.ts # Stacks wallet (@stacks/connect)
│ │ ├── useSolanaWallet.ts # Solana wallet integration
│ │ └── useEthereumWallet.ts # Ethereum wallet hooks
│ ├── lib/
│ │ ├── bridge-config.ts # Contract addresses & config
│ │ ├── stacks-address.ts # Address encoding utilities
│ │ ├── wagmi-config.ts # Wagmi/RainbowKit setup
│ │ ├── solana-client.ts # Solana utilities
│ │ └── utils.ts # Helper functions
│ ├── pages/
│ │ ├── Index.tsx # Ethereum to Stacks bridge
│ │ ├── Solana.tsx # Solana to Stacks bridge
│ │ └── Transfer.tsx # USDCx transfer page
│ ├── App.tsx # App entry point
│ ├── main.tsx # React entry point
│ └── index.css # Global styles (Tailwind)
├── index.html # HTML template
├── package.json # Dependencies
├── vite.config.ts # Vite configuration
├── tailwind.config.ts # Tailwind configuration
└── tsconfig.json # TypeScript configuration
# Run unit tests
pnpm test
# Run tests in watch mode
pnpm test:watch- Connect Ethereum wallet (MetaMask)
- Verify USDC balance displays correctly
- Approve USDC spending for xReserve
- Execute bridge deposit with valid Stacks address
- Verify transaction confirmed on Etherscan
- Wait for Circle attestation (5-30 minutes)
- Verify USDCx received on Stacks Explorer
- Connect Solana wallet (Phantom/Solflare)
- Verify USDC and SOL balances display correctly
- Enter amount and valid Stacks recipient address
- Execute bridge deposit
- Verify transaction confirmed on Solscan
- Wait for Circle attestation (5-30 minutes)
- Verify USDCx received on Stacks Explorer
- Connect Stacks wallet (Leather)
- Verify USDCx balance displays correctly
- Enter valid recipient Stacks address
- Execute USDCx transfer
- Verify transaction on Stacks Explorer
# Install Vercel CLI
npm i -g vercel
# Deploy to preview
vercel
# Deploy to production
vercel --prod- Build the project:
pnpm build - Deploy the
dist/folder to Netlify
# Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
RUN npm install -g pnpm
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]# Build and run
docker build -t hermes-bridge .
docker run -p 8080:80 hermes-bridgeWe welcome contributions! Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
- Follow the existing code style (TypeScript strict mode)
- Write meaningful commit messages
- Add tests for new features
- Update documentation as needed
- Ensure
pnpm exec tsc --noEmitpasses - Ensure
pnpm lintpasses
This project is licensed under the MIT License - see the LICENSE file for details.
If you find Hermes Bridge helpful, consider supporting development:
Stacks Donation Address
SP2F70QJ9J57YSSZE76KC1A3X718ADXSZPG8581EP
| Resource | Link |
|---|---|
| Circle xReserve Docs | circle.com/en/xreserve |
| Stacks Documentation | docs.stacks.co |
| Solana Documentation | docs.solana.com |
| @stacks/connect | stacks.js documentation |
| @solana/wallet-adapter | solana-labs/wallet-adapter |
| Wagmi Documentation | wagmi.sh |
| RainbowKit | rainbowkit.com |
| Viem | viem.sh |
Built with ❤️ for the Stacks & Ethereum communities


