Pular para o conteúdo principal

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:

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 uses 4158
  • 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 Explorers

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​

  1. User clicks "Connect Wallet" button
  2. MetaMask popup appears requesting permission
  3. User approves the connection
  4. UI updates to show:
    • Shortened wallet address (e.g., 0x1234...5678)
    • Current network name
    • "Disconnect" button
  5. 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 address
  • useBalance: 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​

  1. User Input: User enters recipient address and amount in XFI
  2. Validation: Address is validated using isAddress() from viem
  3. Amount Conversion: Amount is converted from XFI to wei using parseEther()
  4. Send Transaction: sendTransaction() is called with recipient and value
  5. MetaMask Confirmation: User confirms the transaction in MetaMask
  6. Wait for Receipt: useWaitForTransactionReceipt monitors transaction status
  7. 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
Native Transfers Only

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​

  1. User enters an arbitrary text message
  2. User clicks "Sign" button
  3. MetaMask popup asks user to sign the message
  4. User approves the signing request
  5. 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
Future Enhancements

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:

  1. Clone the repository:

    git clone https://github.com/crossfichain/dapp-example.git
    cd dapp-example
  2. Install dependencies:

    yarn
  3. Start development server:

    yarn dev
  4. Open in browser: Navigate to http://localhost:3000

  5. 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
  6. Get test XFI:

    • Obtain test XFI tokens from the CrossFi Testnet faucet
    • Check official CrossFi channels for faucet links
  7. 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: