Wagmi dApp Example
Introduction
The CrossFi dApp Example is a minimal Next.js application built with wagmi v2 and viem v2 that demonstrates core EVM interactions on CrossFi Chain. This example serves as a practical starting point for developers who want to build decentralized applications on the CrossFi EVM-compatible network.
The application demonstrates:
- Wallet connection: Connect MetaMask to CrossFi Testnet and Mainnet
- Account information: Display connected wallet address and XFI balance
- Native transfers: Send XFI tokens to any address
- Message signing: Sign arbitrary messages using
signMessage
GitHub repository: https://github.com/crossfichain/dapp-example
When to Use This Example
This example is useful for:
- Quick start: Get a working dApp running in minutes to understand CrossFi EVM integration
- RPC validation: Test CrossFi Testnet/Mainnet RPC endpoints and network connectivity
- Integration foundation: Use as a template for building more complex dApps
- Learning wagmi: Understand how to use wagmi hooks with CrossFi Chain
- Development testing: Quick prototype for testing wallet interactions and transactions
Prerequisites
Before running this example, ensure you have:
- Node.js 18+ installed on your system
- yarn package manager (or npm/pnpm)
- MetaMask browser extension (or another Web3-compatible wallet)
- Test XFI tokens on CrossFi Testnet (for testing transactions)
To get test XFI tokens, you can:
- Use the CrossFi Testnet faucet (check the official CrossFi resources)
- Request tokens from the community
Installation and Running
Clone the repository and install dependencies:
git clone https://github.com/crossfichain/dapp-example.git
cd dapp-example
yarn
Start the development server:
yarn dev
The application will be available at http://localhost:3000.
Optional commands:
yarn build # Build for production
yarn start # Run production build
Project Overview
Tech Stack
- Next.js 16 - React framework with App Router
- React 19 - UI library
- TypeScript - Type-safe development
- wagmi v2 - React hooks for Ethereum
- viem v2 - TypeScript Ethereum library
- Material-UI (MUI) - Component library for UI
- notistack - Notification system
Project Structure
app/
components/
Header.tsx # Wallet connection and status
AccountWidget.tsx # Display address and balance
NetworkSwitcher.tsx # Switch between Testnet/Mainnet
SendTxWidget.tsx # Send XFI transactions
SignMessageWidget.tsx # Sign messages
connector/
index.tsx # wagmi configuration
lib/
theme.ts # MUI theme configuration
layout.tsx # App layout with providers
page.tsx # Main page component
Component Roles
- Header: Manages wallet connection/disconnection using MetaMask connector and displays connection status
- AccountWidget: Displays the connected wallet address and XFI balance using wagmi hooks
- NetworkSwitcher: Allows users to switch between CrossFi Testnet (chain ID 4157) and Mainnet (chain ID 4158)
- SendTxWidget: Handles native XFI token transfers with transaction tracking
- SignMessageWidget: Demonstrates message signing functionality
Network Configuration
The application supports both CrossFi Testnet and Mainnet. Network configuration is defined in app/connector/index.tsx:
export const CHAIN_CONFIG = {
testnet: {
chanId: 4157,
name: 'CrossFi Testnet',
rpcUrls: 'https://rpc.testnet.ms',
nativeCurrency: {
name: 'XFI',
symbol: 'XFI',
decimals: 18,
},
},
mainnet: {
chanId: 4158,
name: 'CrossFi Mainnet',
rpcUrls: 'https://rpc.mainnet.ms',
nativeCurrency: {
name: 'XFI',
symbol: 'XFI',
decimals: 18,
},
},
};
export const wagmiConfig = createConfig({
chains: [
{
id: CHAIN_CONFIG.testnet.chanId,
name: CHAIN_CONFIG.testnet.name,
rpcUrls: {
default: { http: [CHAIN_CONFIG.testnet.rpcUrls] },
},
nativeCurrency: CHAIN_CONFIG.testnet.nativeCurrency,
},
{
id: CHAIN_CONFIG.mainnet.chanId,
name: CHAIN_CONFIG.mainnet.name,
rpcUrls: {
default: { http: [CHAIN_CONFIG.mainnet.rpcUrls] },
},
nativeCurrency: CHAIN_CONFIG.mainnet.nativeCurrency,
},
],
connectors: [metaMask()],
ssr: true,
transports: {
[CHAIN_CONFIG.testnet.chanId]: http(CHAIN_CONFIG.testnet.rpcUrls),
[CHAIN_CONFIG.mainnet.chanId]: http(CHAIN_CONFIG.mainnet.rpcUrls),
},
});
Key Configuration Points
- Chain IDs: Testnet uses
4157, Mainnet uses4158 - RPC URLs: Direct HTTP connections to CrossFi RPC endpoints
- Native Currency: XFI with 18 decimals on both networks
- Connector: MetaMask is configured as the wallet connector
- SSR Support: Server-side rendering is enabled for Next.js
Block explorer URLs are not currently configured in this example. You can add them as an optional improvement if official CrossFi explorers are available.
Connecting a Wallet
The Header component manages wallet connection using wagmi's useConnect, useAccount, and useDisconnect hooks:
import { useAccount, useConnect, useDisconnect } from 'wagmi';
import { metaMask } from 'wagmi/connectors';
export function Header() {
const { address, isConnected, chain } = useAccount();
const { connect, isPending } = useConnect();
const { disconnect } = useDisconnect();
return (
// UI renders based on connection state
{!isConnected ? (
<Button onClick={() => connect({ connector: metaMask() })} disabled={isPending}>
{isPending ? 'Connecting...' : 'Connect Wallet'}
</Button>
) : (
<Box>
<Typography>{address?.slice(0, 6)}...{address?.slice(-4)}</Typography>
<Typography>{chain?.name}</Typography>
<Button onClick={() => disconnect()}>Disconnect</Button>
</Box>
)}
);
}
Connection Flow
- User clicks "Connect Wallet" button
- MetaMask popup appears requesting permission
- User approves the connection
- UI updates to show:
- Shortened wallet address (e.g.,
0x1234...5678) - Current network name
- "Disconnect" button
- Shortened wallet address (e.g.,
- While connecting, button shows "Connecting..." state
Network Switcher
The NetworkSwitcher component allows users to switch between CrossFi Testnet and Mainnet:
import { useSwitchChain } from 'wagmi';
export function NetworkSwitcher() {
const { chain } = useAccount();
const { switchChain } = useSwitchChain();
return (
<TextField
select
value={chain?.id || ''}
onChange={e => switchChain({ chanId: Number(e.target.value) })}
>
{Object.values(CHAIN_CONFIG).map(net => (
<MenuItem key={net.chanId} value={net.chanId}>
{net.name}
</MenuItem>
))}
</TextField>
);
}
The useSwitchChain hook triggers a MetaMask network switch request when the user selects a different network from the dropdown.
Viewing Account and Balance
The AccountWidget component displays the connected wallet address and XFI balance:
import { useAccount, useBalance } from 'wagmi';
export function AccountWidget() {
const { address, isConnecting } = useAccount();
const { data, isLoading } = useBalance({ address });
return (
<Card>
<CardContent>
<Typography variant="h6">Address</Typography>
{isConnecting ? <Typography>Loading...</Typography> : <Typography>{address}</Typography>}
<Typography variant="h6">Balance</Typography>
{isLoading ? (
<Typography>Loading...</Typography>
) : (
<Typography>{data ? `${data.formatted} ${data.symbol}` : 'No Data'}</Typography>
)}
</CardContent>
</Card>
);
}
How It Works
useAccount: Provides the currently connected wallet addressuseBalance: Fetches the XFI balance for the connected address from the RPC endpoint- Automatic updates: The balance refreshes automatically when the network or account changes
- Formatted display: Balance is formatted with proper decimals and displays the currency symbol (XFI)
Sending XFI Transactions
The SendTxWidget component demonstrates how to send native XFI token transfers:
import { useSendTransaction, useWaitForTransactionReceipt } from 'wagmi';
import { isAddress, parseEther } from 'viem';
export function SendTxWidget() {
const { sendTransaction, data, isPending, isError, error } = useSendTransaction();
const { data: receipt, isLoading } = useWaitForTransactionReceipt({ hash: data });
const [recipient, setRecipient] = useState('');
const [amount, setAmount] = useState('');
const handleSend = async () => {
if (!isAddress(recipient)) {
enqueueSnackbar('Please fill correct recipient', { variant: 'warning' });
return;
}
sendTransaction({
to: recipient as Address,
value: parseEther(amount),
});
enqueueSnackbar('Transaction sent', { variant: 'success' });
};
// Display transaction receipt when available
{receipt && (
<>
<Typography>Transaction hash: {receipt.transactionHash}</Typography>
<Typography>Block: {receipt.blockNumber}</Typography>
</>
)}
}
Transaction Flow
- User Input: User enters recipient address and amount in XFI
- Validation: Address is validated using
isAddress()from viem - Amount Conversion: Amount is converted from XFI to wei using
parseEther() - Send Transaction:
sendTransaction()is called with recipient and value - MetaMask Confirmation: User confirms the transaction in MetaMask
- Wait for Receipt:
useWaitForTransactionReceiptmonitors transaction status - Display Result: Transaction hash and block number are displayed when mined
Key Features
- Address validation: Ensures recipient address is valid before sending
- Amount parsing: Automatically converts human-readable amounts to wei
- Transaction tracking: Shows transaction hash immediately and block number after mining
- Error handling: Displays error notifications if transaction fails
- Loading states: Shows loading indicators during transaction processing
This example demonstrates native XFI transfers only. It does not include ERC-20 token transfers or smart contract interactions. Those would require additional contract ABI and different wagmi hooks.
Signing a Message
The SignMessageWidget component demonstrates message signing using the wallet's private key:
import { useSignMessage } from 'wagmi';
export function SignMessageWidget() {
const [message, setMessage] = useState('');
const [signature, setSignature] = useState('');
const { signMessageAsync, isPending } = useSignMessage();
const handleSign = async () => {
try {
const sig = await signMessageAsync({ message });
setSignature(sig);
} catch (error) {
console.error(error);
}
};
return (
<Card>
<CardContent>
<Typography variant="h6">Sign Message</Typography>
<TextField
label="Message"
value={message}
onChange={e => setMessage(e.target.value)}
/>
<Button onClick={handleSign} disabled={isPending}>
{isPending ? 'Signing...' : 'Sign'}
</Button>
{signature && (
<Box>
<Typography variant="body2">Signature:</Typography>
<Typography variant="caption">{signature}</Typography>
</Box>
)}
</CardContent>
</Card>
);
}
How Message Signing Works
- User enters an arbitrary text message
- User clicks "Sign" button
- MetaMask popup asks user to sign the message
- User approves the signing request
- Signature is displayed in the UI (a hex string starting with
0x)
Current Limitations
This example demonstrates signMessage only (personal sign). The following are not included in this example:
- EIP-712 typed data signing (
signTypedData) - structured data signing - Signature verification - verifying signatures on-chain or off-chain
- Message recovery - recovering the signer address from a signature
These additional signing features can be implemented in separate examples or added to this project as enhancements.
Running the Example
Follow these steps to run the dApp locally:
-
Clone the repository:
git clone https://github.com/crossfichain/dapp-example.git
cd dapp-example -
Install dependencies:
yarn -
Start development server:
yarn dev -
Open in browser: Navigate to
http://localhost:3000 -
Configure MetaMask:
- If CrossFi Testnet is not in your MetaMask, the dApp will prompt you to add it
- Alternatively, you can add the network manually using the network configuration guide
-
Get test XFI:
- Obtain test XFI tokens from the CrossFi Testnet faucet
- Check official CrossFi channels for faucet links
-
Test the features:
- Click "Connect Wallet" and approve MetaMask connection
- View your address and balance in the Account Widget
- Switch between Testnet and Mainnet using the Network Switcher
- Send a small amount of XFI to another address using the Send Transaction widget
- Sign a test message using the Sign Message widget
Limitations and Next Steps
What This Example Includes
✅ MetaMask connector via wagmi
✅ Native XFI transfers (send/receive XFI tokens)
✅ Message signing (signMessage)
✅ Network switching between Testnet and Mainnet
✅ Balance display and account information
What Is Not Included
The following features are not implemented in this example and could be added in future iterations:
❌ Web3Modal - multi-wallet connection UI
❌ WalletConnect - mobile wallet support
❌ EIP-712 typed data - signTypedData for structured signatures
❌ Signature verification - on-chain or off-chain verification
❌ Smart contract interactions - reading from or writing to deployed contracts
❌ ERC-20 token transfers - sending tokens other than native XFI
❌ Gas estimation - calculating transaction costs
❌ Block explorer integration - links to view transactions on explorer
Possible Enhancements
Future versions or separate guides could include:
- Support for multiple wallet connectors (WalletConnect, Coinbase Wallet, etc.)
- Smart contract deployment and interaction examples
- ERC-20, ERC-721, and ERC-1155 token operations
- Integration with CrossFi-specific features (if any)
- Production-ready error handling and user feedback
- Transaction history and receipt display
- Gas price optimization
Conclusion
The CrossFi dApp Example provides a solid foundation for building decentralized applications on CrossFi EVM. It demonstrates the essential interactions needed for most dApps: wallet connection, balance checking, transactions, and message signing.
Use this example as a starting point and extend it with additional features as needed for your specific use case. For more advanced topics, explore other sections of the CrossFi documentation.
Resources: