@jim4565/dapp-wrapper-react
v1.4.1
Published
React components and hooks for @jim4565/dapp-wrapper with persistent wallet connection
Maintainers
Readme
@jim4565/dapp-wrapper-react
🚀 React components and hooks for @jim4565/dapp-wrapper with persistent wallet connection
Built with industry standards following wagmi/RainbowKit patterns and MetaMask-level persistence.
🎯 What This Library Does
This library extends your existing @jim4565/dapp-wrapper with:
✅ Persistent Wallet Connection - Auto-reconnect after page refresh without popups
✅ React Integration - Hooks, context, and components for seamless React usage
✅ Cross-Page Persistence - Maintain connection across routes and page refreshes
✅ 3-Tier Reconnection Strategy - Silent → Session → Full Connect (industry standard)
✅ Your Familiar API - Keep using contract.playGame(true).withValue("0.1") syntax
✅ Real-time Balance - Automatic balance updates with 5-strategy fallback system
Key Benefit: Your existing contract interaction code works exactly the same, but now with React hooks and persistent connection!
🚀 Installation & Setup
1. Install the Package
npm install @jim4565/dapp-wrapper-react @jim4565/dapp-wrapper react react-dom2. Critical Setup - Provider Configuration
⚠️ IMPORTANT: The provider MUST wrap your entire app to enable persistent connection across all pages.
Next.js Setup (pages/_app.tsx or app/layout.tsx)
// pages/_app.tsx (Pages Router)
import { IncentivWalletProvider } from '@jim4565/dapp-wrapper-react';
import type { AppProps } from 'next/app';
// Your existing contract ABIs
const gameABI = [
{
"inputs": [{"internalType": "bool", "name": "_isActive", "type": "bool"}],
"name": "playGame",
"outputs": [],
"stateMutability": "payable",
"type": "function"
}
// ... rest of your ABI
];
const contracts = [
{ name: "Game", address: "0x1234567890...", abi: gameABI },
{ name: "Lottery", address: "0x0987654321...", abi: lotteryABI }
];
export default function App({ Component, pageProps }: AppProps) {
return (
<IncentivWalletProvider
config={{
contracts, // Register your contracts
autoConnect: true, // Auto-reconnect on app start
reconnectOnMount: true, // Try reconnection when mounted
pollingInterval: 30000 // Balance polling (30s)
}}
enableLogging={process.env.NODE_ENV === 'development'}
>
{/* Your entire app - ALL pages will have persistent wallet connection */}
<Component {...pageProps} />
</IncentivWalletProvider>
);
}// app/layout.tsx (App Router)
'use client';
import { IncentivWalletProvider } from '@jim4565/dapp-wrapper-react';
const contracts = [
{ name: "Game", address: "0x1234567890...", abi: gameABI }
];
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<IncentivWalletProvider
config={{
contracts,
autoConnect: true,
reconnectOnMount: true,
pollingInterval: 30000
}}
enableLogging={process.env.NODE_ENV === 'development'}
>
{/* All pages and components will have persistent wallet connection */}
{children}
</IncentivWalletProvider>
</body>
</html>
);
}React Router Setup (src/App.tsx)
// src/App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { IncentivWalletProvider } from '@jim4565/dapp-wrapper-react';
import { HomePage } from './pages/HomePage';
import { GamePage } from './pages/GamePage';
import { LeaderboardPage } from './pages/LeaderboardPage';
const contracts = [
{ name: "Game", address: "0x1234567890...", abi: gameABI }
];
function App() {
return (
<IncentivWalletProvider
config={{
contracts,
autoConnect: true,
reconnectOnMount: true,
pollingInterval: 30000
}}
enableLogging={process.env.NODE_ENV === 'development'}
>
<BrowserRouter>
{/* All routes will have persistent wallet connection */}
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/game" element={<GamePage />} />
<Route path="/leaderboard" element={<LeaderboardPage />} />
</Routes>
</BrowserRouter>
</IncentivWalletProvider>
);
}
export default App;📱 Basic Usage
Wallet Connection Hook
import { useIncentivWallet } from '@jim4565/dapp-wrapper-react';
function WalletInfo() {
const {
address, // Current wallet address
isConnected, // Connection status
isConnecting, // Loading state
balance, // Current balance in ETH
isBalanceLoading, // Balance loading state
refreshBalance, // Manual balance refresh
connect, // Connect function
disconnect, // Disconnect function
error // Any connection errors
} = useIncentivWallet();
if (!isConnected) {
return (
<button onClick={connect} disabled={isConnecting}>
{isConnecting ? 'Connecting...' : 'Connect Wallet'}
</button>
);
}
return (
<div>
<p>Address: {address}</p>
<p>Balance: {isBalanceLoading ? 'Loading...' : `${balance} ETH`}</p>
<button onClick={disconnect}>Disconnect</button>
<button onClick={refreshBalance}>Refresh Balance</button>
</div>
);
}Contract Interaction
The beauty: Your existing contract interaction code works exactly the same!
import { useContract, useIncentivWallet } from '@jim4565/dapp-wrapper-react';
import { useState } from 'react';
function GameComponent() {
const { isConnected } = useIncentivWallet();
const { contract: gameContract, isLoading, error } = useContract("Game");
const [isPlaying, setIsPlaying] = useState(false);
const playGame = async () => {
if (!gameContract || !isConnected) return;
try {
setIsPlaying(true);
// Your exact @jim4565/dapp-wrapper syntax - no changes needed!
await gameContract.playGame(true).withValue("0.1");
alert('Game played successfully!');
} catch (error) {
console.error('Game failed:', error);
alert('Game failed: ' + error.message);
} finally {
setIsPlaying(false);
}
};
if (!isConnected) {
return <p>Connect your wallet to play</p>;
}
if (isLoading) {
return <p>Loading contract...</p>;
}
if (error) {
return <p>Error loading contract: {error.message}</p>;
}
return (
<button onClick={playGame} disabled={isPlaying}>
{isPlaying ? 'Playing...' : 'Play Game (0.1 ETH)'}
</button>
);
}Ready-to-Use Components
import { ConnectButton, WalletStatus } from '@jim4565/dapp-wrapper-react';
function Navigation() {
return (
<nav>
{/* Ready-to-use wallet components */}
<WalletStatus showBalance={true} />
<ConnectButton />
</nav>
);
}⚠️ Critical Navigation Rule
ALWAYS use framework routing components to preserve wallet connection:
// ✅ CORRECT - Preserves wallet connection
import Link from 'next/link'; // Next.js
// import { Link } from 'react-router-dom'; // React Router
function Navigation() {
return (
<nav>
<Link href="/game">Game</Link> {/* ✅ Client-side routing */}
<Link href="/leaderboard">Leaderboard</Link> {/* ✅ Wallet stays connected */}
</nav>
);
}
// ❌ WRONG - Forces page reload and wallet reconnection
function BadNavigation() {
return (
<nav>
<a href="/game">Game</a> {/* ❌ Page reload */}
<a href="/leaderboard">Leaderboard</a> {/* ❌ Wallet must reconnect */}
</nav>
);
}🔧 API Reference
Main Hooks
useIncentivWallet()
Main wallet hook providing connection state and actions.
const {
// Connection State
address, // string | undefined
isConnected, // boolean
isConnecting, // boolean
error, // Error | null
// Balance State
balance, // string | undefined - current balance in ETH
isBalanceLoading, // boolean - balance loading state
refreshBalance, // () => Promise<void> - manual balance refresh
// Actions
connect, // () => Promise<void>
disconnect, // () => void
reconnect // () => Promise<void>
} = useIncentivWallet();useContract(contractName)
Access registered smart contracts.
const {
contract, // Contract instance with your methods
isLoading, // boolean
error // Error | null
} = useContract("GameContract");
// Use your contract exactly as before
await contract.playGame(true).withValue("0.1");Components
<ConnectButton />
Professional wallet connection button.
<ConnectButton
showAddress={true}
showBalance={false}
size="medium"
variant="primary"
/><WalletStatus />
Comprehensive wallet status display.
<WalletStatus
showBalance={true}
balanceDecimals={4}
showFullAddress={false}
size="medium"
/>🏗️ How Persistence Works
3-Tier Reconnection Strategy
The library implements a professional reconnection strategy:
- Tier 1: Silent Check - Check existing session without popups
- Tier 2: Session Restore - Restore previous session silently
- Tier 3: Full Connect - Show connection popup only if user connected before
This ensures:
- ✅ No unwanted popups for first-time users
- ✅ Seamless reconnection for returning users
- ✅ Fallback strategies if silent methods fail
What Gets Persisted
- ✅ Wallet address - Securely stored in localStorage
- ✅ Connection status - Whether user was connected
- ✅ Ever connected flag - Prevents popups for new users
- ✅ Data expiration - Auto-clears old data (30 days)
🔍 Troubleshooting
Provider Not Found Error
// ❌ Wrong: Component used outside provider
function App() {
return (
<div>
<MyWalletComponent /> {/* Error: hook called outside provider */}
<IncentivWalletProvider>
<OtherComponents />
</IncentivWalletProvider>
</div>
);
}
// ✅ Correct: All components inside provider
function App() {
return (
<IncentivWalletProvider>
<div>
<MyWalletComponent /> {/* Works: inside provider */}
<OtherComponents />
</div>
</IncentivWalletProvider>
);
}Wallet Disconnects When Navigating
// ❌ Problem: Using <a> tags forces page reload
<a href="/game">Game</a> // Page reload → wallet disconnect
// ✅ Solution: Use framework routing components
<Link href="/game">Game</Link> // Client-side → wallet preservedAuto-Reconnect Not Working
// Debug what's stored:
import { PersistenceService } from '@jim4565/dapp-wrapper-react';
const { address, wasConnected, hasEverConnected } = PersistenceService.loadWalletData();
console.log('Stored data:', { address, wasConnected, hasEverConnected });
// For auto-reconnect to work, you need:
// ✅ address: "0x..." (valid address)
// ✅ wasConnected: true (was connected last session)
// ✅ hasEverConnected: true (user has connected before)Debug Mode
Enable logging to see what's happening:
<IncentivWalletProvider
config={config}
enableLogging={true} // ✅ Enable debug logs
>🎯 Migration from Raw @jim4565/dapp-wrapper
// ❌ Before: Raw wrapper usage
import { IncentivWrapper } from '@jim4565/dapp-wrapper';
const wrapper = new IncentivWrapper();
await wrapper.connect();
wrapper.registerContract("Game", address, abi);
const contract = wrapper.getContract("Game");
await contract.playGame(true).withValue("0.1");// ✅ After: React wrapper (no changes to contract calls!)
import { IncentivWalletProvider, useContract } from '@jim4565/dapp-wrapper-react';
// 1. Wrap your app
<IncentivWalletProvider config={{ contracts: [...] }}>
<App />
</IncentivWalletProvider>
// 2. Use hooks in components
function GameComponent() {
const { contract } = useContract("Game");
// Same syntax! No changes needed!
await contract.playGame(true).withValue("0.1");
}Benefits of migrating:
- ✅ Persistent connection across pages
- ✅ Automatic reconnection
- ✅ React hooks and components
- ✅ TypeScript improvements
- ✅ Your contract code stays exactly the same!
📄 License
MIT License - see LICENSE file for details.
Built with ❤️ for the Incentiv ecosystem
Making Web3 development as easy as Web2
