SuperAPI AI Directory

dapp-common-skill

Coding & development

View repository

Updated 10-15 20:06

About this skill

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
  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

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

function shortenAddress(address: string, chars: number = 4): string {
  if (!address) return '';
  return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`;
}

Chain Configs

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
  'solana':      { id: 'solana',      family: 'solana',  name: 'Solana',    symbol: 'SOL',  decimals: 9,  rpcUrl: 'https://api.mainnet-beta.solana.com', explorerUrl: 'https://solscan.io',             explorerTxPath: '/tx/',   explorerName: 'Solscan',      isTestnet: false },
  'solana-devnet': { id: 'solana-devnet', family: 'solana', name: 'Solana Devnet', symbol: 'SOL', decimals: 9, rpcUrl: 'https://api.devnet.solana.com',  explorerUrl: 'https://explorer.solana.com', explorerTxPath: '/tx/',   explorerName: 'Solana Explorer (Devnet)', isTestnet: true },

  // Bitcoin
  'bitcoin':     { id: 'bitcoin',     family: 'bitcoin', name: 'Bitcoin',   symbol: 'BTC',  decimals: 8,  rpcUrl: '',                                    explorerUrl: 'https://mempool.space',          explorerTxPath: '/tx/',   explorerName: 'Mempool',      isTestnet: false },

  // TON
  'ton':         { id: 'ton',         family: 'ton',     name: 'TON',       symbol: 'TON',  decimals: 9,  rpcUrl: 'https://toncenter.com/api/v2',        explorerUrl: 'https://tonviewer.com',          explorerTxPath: '/transaction/', explorerName: 'TonViewer', isTestnet: false },

  // Aptos
  'aptos':       { id: 'aptos',       family: 'aptos',   name: 'Aptos',     symbol: 'APT',  decimals: 8,  rpcUrl: 'https://fullnode.mainnet.aptoslabs.com/v1', explorerUrl: 'https://explorer.aptoslabs.com', explorerTxPath: '/txn/',  explorerName: 'Aptos Explorer', isTestnet: false },

  // Tron
  'tron':        { id: 'tron',        family: 'tron',    name: 'Tron',      symbol: 'TRX',  decimals: 6,  rpcUrl: 'https://api.trongrid.io',             explorerUrl: 'https://tronscan.org',           explorerTxPath: '/#/transaction/', explorerName: 'TronScan', isTestnet: false },

  // Sui
  'sui':         { id: 'sui',         family: 'sui',     name: 'Sui',       symbol: 'SUI',  decimals: 9,  rpcUrl: 'https://fullnode.mainnet.sui.io',     explorerUrl: 'https://suiscan.xyz',            explorerTxPath: '/tx/',   explorerName: 'SuiScan',      isTestnet: false },

  // Cosmos
  'cosmos':      { id: 'cosmos',      family: 'cosmos',  name: 'Cosmos Hub', symbol: 'ATOM', decimals: 6, rpcUrl: 'https://rpc.cosmos.network',          explorerUrl: 'https://www.mintscan.io/cosmos', explorerTxPath: '/tx/',   explorerName: 'Mintscan',     isTestnet: false },
};

function getExplorerTxUrl(chainId: string, txHash: string): string {
  const chain = CHAINS[chainId];
  return chain ? `${chain.explorerUrl}${chain.explorerTxPath}${txHash}` : `https://etherscan.io/tx/${txHash}`;
}

function getChainsByFamily(family: ChainFamily): ChainConfig[] {
  return Object.values(CHAINS).filter(c => c.family === family);
}

Chain Adapter Interface

For multi-chain DApps, all chain-specific logic goes behind a common interface:

interface ChainAdapter {
  family: ChainFamily;
  connect(): Promise<string>;                                   // → address
  disconnect(): void;
  getBalance(address: string): Promise<string>;                 // → human-readable
  transfer(to: string, amount: string): Promise<string>;        // → tx hash or signature
  signMessage(message: string): Promise<string>;                // → signature
  getExplorerTxUrl(txHash: string): string;
  formatAddress(address: string): string;                       // → shortened display
  getSupportedSignTypes(): string[];                            // e.g. ['personal_sign', 'eth_signTypedData_v4']
}

Each chain implements this interface differently (see reference files for per-chain implementations). The UI components use this interface — they never call chain-specific APIs directly.

// Usage in components — chain-agnostic
const adapter = useActiveChainAdapter();  // returns the adapter for current chain
const balance = await adapter.getBalance(address);
const txHash = await adapter.transfer(to, amount);

Error Messages

function getUserFriendlyError(error: any): string {
  const code = error?.code;
  const msg = (error?.message || '').toLowerCase();

  if (code === 4001 || /reject|cancel|denied|user/i.test(msg))
    return 'You rejected the request. Try again when ready.';
  if (code === 4100)
    return 'Not authorized. Please connect your wallet first.';
  if (code === 4902)
    return 'This network is not available in your wallet. Please add it manually.';
  if (code === -32002)
    return 'A wallet request is already pending. Please check your wallet.';
  if (code === -32603 || msg.includes('internal'))
    return 'Something went wrong. Please try again.';
  if (msg.includes('insufficient funds'))
    return 'Insufficient balance for this transaction.';
  if (msg.includes('nonce'))
    return 'Transaction conflict. Please reset your wallet nonce or wait.';

  return error?.message || 'An unexpected error occurred.';
}

Examples

Example 1: EVM Transfer DApp

User says: "Build a DApp where users can send ETH to an address"

Actions:

  1. Chains: EVM (Ethereum). Single-chain.
  2. Read layout.md + wallet-connection.md + transfer.md + transaction-lifecycle.md
  3. Create EVM transfer form with: recipient input, amount input, balance display, gas estimate
  4. Show transaction status: signing → pending → confirmed with Etherscan link
  5. Handle errors (insufficient funds, user rejection)

Example 2: Solana Transfer DApp

User says: "Build a DApp to send SOL"

Actions:

  1. Chains: Solana. Single-chain.
  2. Read layout.md + wallet-connection.md + transfer.md + transaction-lifecycle.md
  3. Implement Solana adapter: connect via window.solana.connect(), transfer via @solana/web3.js SystemProgram.transfer, sign via signAndSendTransaction
  4. Show amounts in SOL (9 decimals), link to Solscan

Example 3: Multi-Chain Dashboard (EVM + Solana + Bitcoin)

User says: "Build a dashboard that shows balances across Ethereum, Solana, and Bitcoin"

Actions:

  1. Chains: EVM + Solana + Bitcoin. Multi-chain (3 families).
  2. Read layout.md + wallet-connection.md + chain-switching.md
  3. Create chain-family tabs (Ethereum | Solana | Bitcoin), not just an EVM dropdown
  4. Implement 3 chain adapters behind ChainAdapter interface
  5. Display balance per chain with chain-appropriate formatting and explorers

Example 4: Sign-In with Ethereum (SIWE)

User says: "Add wallet-based login to my app"

Actions:

  1. Chains: EVM. Single-chain.
  2. Read wallet-connection.md + signing.md
  3. Implement SIWE flow: connect → sign structured message → verify server-side
  4. Provide clear signing preview showing domain, nonce, expiry

Example 5: Bitcoin + TON DApp

User says: "Build a DApp for Bitcoin and TON transfers"

Actions:

  1. Chains: Bitcoin + TON. Multi-chain (2 non-EVM families).
  2. Read ALL references
  3. Implement Bitcoin adapter (UniSat API: sendBitcoin(to, sats)) and TON adapter (ton_sendTransaction)
  4. Chain-family tabs: Bitcoin | TON. Each with appropriate transfer form, address format, and explorer links
  5. Bitcoin amounts in BTC (8 decimals, sats for the API), TON amounts in TON (9 decimals, nanotons for the API)

Example 6: Full-Featured Multi-Chain DApp

User says: "Build a DApp with Bitget Wallet"

Actions:

  1. Ask which chains — do NOT assume EVM only
  2. Read ALL references from this skill
  3. Read wallet-specific skill (e.g. Bitget Wallet Developer Skill) for provider APIs
  4. Implement chain adapters for each requested chain family
  5. Build complete DApp with: connection, chain-family switching, transfers, signing, tx tracking
  6. Every chain gets proper: address format, amount decimals, explorer links, sign types

Resources