- name
- dapp-common-skill
- description
- Common DApp development patterns and UI components. Use this skill whenever the user wants to build a DApp, create a wallet-connected web app, add token transfers, chain switching, message signing, transaction status tracking, or any blockchain-interactive frontend. Also use when the user mentions DApp layout, transfer forms, signing panels, gas estimation, chain selectors, wallet connection UI, or multi-chain architecture — even if they don't explicitly say "DApp". Use proactively for any web3 frontend work including React + Vite, Next.js, or Vue projects that interact with wallets. Pair with a wallet-specific skill (e.g. Bitget Wallet Developer Skill) for complete DApp development. Do NOT use for smart contract development, backend services, or wallet-internal implementation.
- license
- MIT
- metadata
- {"author":"Bitget Wallet","version":"1.0.0","tags":["dapp","frontend","react","web3","ux","transfer","signing","chain-switching"]}
# DApp Common Skill
Standard patterns for building production-quality DApp frontends. Multi-chain by design — supports EVM, Solana, Bitcoin, TON, Aptos, Cosmos, Tron, and Sui.
## Instructions
### Step 1: Understand What the User Wants
Before writing any code, clarify the DApp's purpose and scope.
**Must ask:**
| Question | Why | Do NOT assume |
|----------|-----|---------------|
| What does this DApp do? (swap, transfer, NFT mint, dashboard, etc.) | Determines which modules to load | Never assume "just a connect button" |
| **Which chains?** (EVM, Solana, Bitcoin, TON, Aptos, Cosmos, Tron, Sui) | Determines chain adapter(s) to implement | **Never default to EVM-only** |
| Single-chain or multi-chain? | If multi-chain, need chain-family tabs | Never assume single-chain |
| Framework preference? (React + Vite, Next.js, Vue) | Determines component syntax | Default to React + Vite if not specified |
| Need token transfers? If so, native only or token too? | Determines transfer form complexity | Never hardcode amount or skip input |
| Need message signing? What types? (login, authorization, typed data) | Determines signing UI | Never skip sign type selector |
**Do NOT assume EVM.** If the user says "build a DApp" without specifying chains, you MUST ask which chains they want. If the user specifies a wallet (e.g. "Bitget Wallet"), check which chains that wallet supports and ask the user to choose.
**If using Wagmi / RainbowKit / WalletConnect (adapter path), also ask:**
| Question | Why | Do NOT assume |
|----------|-----|---------------|
| Do you have a Reown (WalletConnect) Project ID? | Required for WalletConnect QR / mobile wallet. Not needed for EIP-6963 browser extension only. | Never hardcode `'YOUR_PROJECT_ID'` and move on |
If they don't have one, guide them:
1. Go to [dashboard.reown.com](https://dashboard.reown.com/)
2. Sign up / sign in → click **"+ Project"** → select **"AppKit"** → enter project name → **"Create"**
3. Copy the Project ID from the project page
4. Store it in `.env` as `VITE_WALLETCONNECT_PROJECT_ID` (Vite) or `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID` (Next.js)
> **Without a Project ID:** EIP-6963 injected wallet discovery (browser extensions) still works. Only WalletConnect QR code and mobile wallet connections are disabled.
### Step 2: Route to the Right References
| User Intent | Load References |
|---|---|
| "Build a DApp" (generic, EVM) | `layout.md` + `adapter-integration.md` + `ux-patterns.md` + ask what features |
| "Build a DApp" (non-EVM or multi-chain) | `layout.md` + `wallet-connection.md` + `ux-patterns.md` + ask what features |
| "Use Wagmi / RainbowKit / WalletConnect" | `adapter-integration.md` |
| "Add transfer / send tokens" | `transfer.md` + `transaction-lifecycle.md` + `gas-and-fees.md` |
| "Add ERC-20 token transfer" | `transfer.md` + `token-approval.md` + `gas-and-fees.md` |
| "Add chain switching / multi-chain" | `chain-switching.md` |
| "Add message signing / SIWE / login" | `signing.md` |
| "Show transaction status / history" | `transaction-lifecycle.md` + `ux-patterns.md` (toast) |
| "Show gas fees / fee estimate" | `gas-and-fees.md` |
| "Full-featured DApp" | All references |
> **Always load `ux-patterns.md`** for any DApp — it covers loading states, toasts, empty states, and confirmation dialogs that every DApp needs.
### Connection Architecture Decision
A production DApp should let users **choose their connection method** (browser extension, WalletConnect QR, mobile deep link, etc.), not hardcode a single method. The "Connect Wallet" button should open a modal showing all available options.
**Recommended approach by framework:**
```
React + EVM (any) → adapter-integration.md (RainbowKit — connection modal built-in)
Vue / Angular / Vanilla JS → adapter-integration.md (Web3Modal — framework-agnostic modal)
Non-EVM chains (SOL/BTC/TON) → wallet-connection.md (direct provider, no adapter ecosystem yet)
Multi-chain (EVM + non-EVM) → adapter-integration.md (EVM) + wallet-connection.md (non-EVM)
```
> **Default recommendation: Use RainbowKit or Web3Modal.** They provide a connection modal that auto-detects installed wallets (EIP-6963), shows WalletConnect QR code for mobile, and lets the user choose. Do NOT build a single-method "Connect Bitget Wallet" button for production EVM DApps — always offer multiple connection paths.
**Connection methods the modal should support:**
| Method | How It Works | When Available |
|--------|-------------|----------------|
| **Browser Extension** (EIP-6963) | Auto-detects installed wallets, user clicks one | Wallet extension installed |
| **WalletConnect QR** | Scan QR code with mobile wallet | Always (needs Project ID) |
| **Deep Link** | Opens mobile wallet app directly | Mobile browser |
| **Email / Social** | Embedded wallet via Reown AppKit | If configured |
When using adapters, Wagmi hooks replace the custom hooks from `wallet-connection.md`. But the UI components (TransferForm, SigningPanel, TxStatus, etc.) remain the same — they are adapter-agnostic.
### Step 3: Apply Standard DApp Architecture
Every DApp MUST follow this structure. Non-negotiable.
**Adapter-based DApp (Wagmi/RainbowKit — recommended for React + EVM):**
```
src/
├── config/
│ └── wagmi.ts # Wagmi config + chains + connectors + wallet list
├── components/
│ ├── TransferForm.tsx # Same as below — adapter-agnostic
│ ├── SigningPanel.tsx # Same as below — adapter-agnostic
│ ├── TxStatus.tsx # Transaction lifecycle display
│ └── Layout.tsx # Use <ConnectButton /> from RainbowKit
├── utils/
│ ├── format.ts # Address shortening, amount formatting
│ ├── chains.ts # Chain configs
│ └── errors.ts # User-friendly error messages
├── App.tsx # WagmiProvider + RainbowKitProvider
└── main.tsx
```
**Single-chain DApp (direct provider):**
```
src/
├── components/
│ ├── WalletStatus.tsx # Header: address + chain + balance
│ ├── TransferForm.tsx # Amount + recipient + token + send
│ ├── SigningPanel.tsx # Message input + type selector + sign
│ ├── TxStatus.tsx # Transaction lifecycle display
│ └── Layout.tsx # Page shell with header + content
├── hooks/
│ ├── useWalletConnection.ts # Connect / disconnect / account state
│ ├── useTransfer.ts # Build + send + track transaction
│ ├── useSigning.ts # Sign messages + verify
│ └── useBalance.ts # Native + token balance queries
├── utils/
│ ├── format.ts # Address shortening, amount formatting
│ ├── chains.ts # Chain configs (name, RPC, explorer, icon)
│ └── errors.ts # User-friendly error messages
├── App.tsx # Router + providers
└── main.tsx # Entry point
```
**Multi-chain DApp (2+ chain families):**
```
src/
├── chains/ # ← Chain adapter layer
│ ├── types.ts # ChainAdapter interface
│ ├── evm.ts # EVM adapter (ethers.js)
│ ├── solana.ts # Solana adapter (@solana/web3.js)
│ ├── bitcoin.ts # Bitcoin adapter (UniSat API)
│ ├── ton.ts # TON adapter (TonConnect)
│ └── index.ts # Registry: chain → adapter mapping
├── components/
│ ├── WalletStatus.tsx # Header: address + chain + balance
│ ├── ChainFamilySelector.tsx # EVM / Solana / Bitcoin / TON tabs
│ ├── ChainSelector.tsx # Sub-chain selector (within EVM)
│ ├── TransferForm.tsx # Uses active chain adapter
│ ├── SigningPanel.tsx # Adapts sign types per chain
│ ├── TxStatus.tsx # Transaction lifecycle display
│ └── Layout.tsx # Page shell with header + content
├── hooks/
│ ├── useWalletConnection.ts # Multi-chain connect / disconnect
│ ├── useActiveChain.ts # Current chain family + sub-chain
│ ├── useTransfer.ts # Delegates to chain adapter
│ ├── useSigning.ts # Delegates to chain adapter
│ └── useBalance.ts # Delegates to chain adapter
├── utils/
│ ├── format.ts # Address shortening, amount formatting
│ ├── chains.ts # All chain configs (EVM + non-EVM)
│ └── errors.ts # User-friendly error messages
├── App.tsx
└── main.tsx
```
### Step 4: Follow DApp UX Rules
These rules apply to ALL DApps regardless of wallet or chain. Non-negotiable.
**Inputs & Data:**
1. **Never hardcode transaction parameters** — always provide input fields for amounts, recipients, and tokens.
2. **Always show chain context** — display which chain the user is on, and provide a way to switch.
3. **Always format amounts for humans** — show ETH not wei, SOL not lamports, with proper decimal places.
4. **Accept ENS names in address fields** (EVM) — resolve `.eth` names and show the resolved address.
5. **Validate addresses per chain** — use chain-specific regex, don't accept invalid formats.
**Transaction Lifecycle:**
6. **Always show transaction status** — use a state machine: idle → signing → pending → confirmed/failed.
7. **Always link to explorer** — after any on-chain action, show a clickable link to the block explorer.
8. **Show gas fee estimate before confirmation** — never surprise users with costs. Show Slow/Standard/Fast tiers for EVM.
9. **Show pre-wallet confirmation dialog** — preview the action in the DApp BEFORE triggering the wallet popup.
10. **Use toast notifications** — persistent toasts for tx lifecycle so users see status even after navigating away.
**Token Approvals (ERC-20):**
11. **Show approval step explicitly** — "Step 1: Approve" → "Step 2: Transfer" with clear labeling.
12. **Default to exact-amount approvals** — never infinite unless user explicitly opts in.
13. **Check existing allowance** — don't force unnecessary approvals.
**Error Handling & Security:**
14. **Always handle errors with user-friendly messages** — map error codes to plain language.
15. **Show what users are signing** — display message content before asking for signature; never sign opaque data.
16. **Separate signing from broadcasting** — provide options for sign-only operations.
**Navigation & State:**
17. **Always provide cancel/back paths** — users should never be stuck.
18. **Always persist wallet connection** — reconnect on page reload.
19. **Support both mainnet and testnet** — with a clear visual indicator of which environment.
**Visual Polish:**
20. **Use skeleton loading** — show shimmering placeholders while data loads, never blank screens.
21. **Show empty states with CTAs** — "No transactions yet" with actionable guidance, not blank space.
22. **Provide copy buttons** — on addresses, signatures, and transaction hashes.
23. **Support dark mode** — respect system preference via CSS custom properties.
### Step 5: Implement Features
Load the relevant reference files and follow the patterns. Every reference includes:
- TypeScript types
- React hook implementation
- Component template
- Error handling
- Usage example
## Core Knowledge (No Reference Loading Required)
### Amount Formatting
```typescript
function formatAmount(raw: string | bigint, decimals: number = 18): string {
const value = typeof raw === 'string' ? BigInt(raw) : raw;
const divisor = BigInt(10 ** decimals);
const whole = value / divisor;
const fraction = value % divisor;
const fractionStr = fraction.toString().padStart(decimals, '0').slice(0, 6);
return `${whole}.${fractionStr}`.replace(/\.?0+$/, '') || '0';
}
function parseAmount(display: string, decimals: number = 18): bigint {
const [whole = '0', frac = ''] = display.split('.');
const padded = frac.padEnd(decimals, '0').slice(0, decimals);
return BigInt(whole + padded);
}
```
### Address Display
```typescript
function shortenAddress(address: string, chars: number = 4): string {
if (!address) return '';
return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`;
}
```
### Chain Configs
```typescript
type ChainFamily = 'evm' | 'solana' | 'bitcoin' | 'ton' | 'aptos' | 'cosmos' | 'tron' | 'sui';
interface ChainConfig {
id: string; // unique key: "ethereum", "solana-mainnet", "bitcoin"
family: ChainFamily;
chainId?: number; // EVM only
name: string;
symbol: string;
decimals: number;
rpcUrl: string;
explorerUrl: string;
explorerTxPath: string; // "/tx/" for EVM, "/transaction/" for Solana, etc.
explorerName: string;
isTestnet: boolean;
}
const CHAINS: Record<string, ChainConfig> = {
// EVM
'ethereum': { id: 'ethereum', family: 'evm', chainId: 1, name: 'Ethereum', symbol: 'ETH', decimals: 18, rpcUrl: 'https://eth.llamarpc.com', explorerUrl: 'https://etherscan.io', explorerTxPath: '/tx/', explorerName: 'Etherscan', isTestnet: false },
'bsc': { id: 'bsc', family: 'evm', chainId: 56, name: 'BNB Chain', symbol: 'BNB', decimals: 18, rpcUrl: 'https://bsc-dataseed1.binance.org', explorerUrl: 'https://bscscan.com', explorerTxPath: '/tx/', explorerName: 'BscScan', isTestnet: false },
'polygon': { id: 'polygon', family: 'evm', chainId: 137, name: 'Polygon', symbol: 'POL', decimals: 18, rpcUrl: 'https://polygon-rpc.com', explorerUrl: 'https://polygonscan.com', explorerTxPath: '/tx/', explorerName: 'PolygonScan', isTestnet: false },
'arbitrum': { id: 'arbitrum', family: 'evm', chainId: 42161, name: 'Arbitrum', symbol: 'ETH', decimals: 18, rpcUrl: 'https://arb1.arbitrum.io/rpc', explorerUrl: 'https://arbiscan.io', explorerTxPath: '/tx/', explorerName: 'Arbiscan', isTestnet: false },
'base': { id: 'base', family: 'evm', chainId: 8453, name: 'Base', symbol: 'ETH', decimals: 18, rpcUrl: 'https://mainnet.base.org', explorerUrl: 'https://basescan.org', explorerTxPath: '/tx/', explorerName: 'BaseScan', isTestnet: false },
'sepolia': { id: 'sepolia', family: 'evm', chainId: 11155111, name: 'Sepolia', symbol: 'ETH', decimals: 18, rpcUrl: 'https://rpc.sepolia.org', explorerUrl: 'https://sepolia.etherscan.io', explorerTxPath: '/tx/', explorerName: 'Etherscan (Sepolia)', isTestnet: true },
// Solana
Voir sur GitHub