---
name: web3-integration
description: "สกิลสำหรับ การเชื่อมต่อ Web3 ใช้เมื่อต้องการแนวทาง ขั้นตอน Template หรือคำแนะนำเชิงปฏิบัติสำหรับงาน Blockchain, Web3, Smart Contract, DeFi, Wallet และระบบกระจายศูนย์ ในหัวข้อนี้"
allowed-tools: Read, Write, Edit
version: "1.0"
---

# การเชื่อมต่อ Web3

> Pattern การเชื่อม frontend กับ blockchain สำหรับ dApp development

---

## Stack แนะนำ

| ส่วน | เครื่องมือ | หมายเหตุ |
|------|-----------|---------|
| Web3 Library | **viem** + **wagmi** (React) | modern, type-safe |
| หรือ | **ethers.js v6** | battle-tested, ทำงานได้ทุก framework |
| Wallet | **WalletConnect v2** + **MetaMask** | รองรับ mobile + desktop |
| RPC | **Alchemy** หรือ **Infura** | ใช้ key จาก env variable |
| Network Config | **hardhat** / **foundry** | สำหรับ local dev |

---

## Wallet Connection (wagmi + viem)

```typescript
// wagmi config
import { createConfig, http } from 'wagmi';
import { mainnet, polygon, arbitrum } from 'wagmi/chains';
import { injected, walletConnect } from 'wagmi/connectors';

export const config = createConfig({
    chains: [mainnet, polygon, arbitrum],
    connectors: [
        injected(),
        walletConnect({ projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID! }),
    ],
    transports: {
        [mainnet.id]: http(process.env.NEXT_PUBLIC_ALCHEMY_URL),
        [polygon.id]: http(),
        [arbitrum.id]: http(),
    },
});

// React hook
import { useAccount, useConnect, useDisconnect } from 'wagmi';

function WalletButton() {
    const { address, isConnected } = useAccount();
    const { connect, connectors } = useConnect();
    const { disconnect } = useDisconnect();

    if (isConnected) {
        return (
            <button onClick={() => disconnect()}>
                Disconnect ({address?.slice(0, 6)}...{address?.slice(-4)})
            </button>
        );
    }
    return (
        <button onClick={() => connect({ connector: connectors[0] })}>
            Connect Wallet
        </button>
    );
}
```

---

## Contract Read (ethers.js v6)

```typescript
import { ethers } from 'ethers';
import ERC20_ABI from './abis/ERC20.json';

const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const contract = new ethers.Contract(TOKEN_ADDRESS, ERC20_ABI, provider);

// อ่านข้อมูลจาก contract (ไม่เสีย gas)
async function getTokenInfo(userAddress: string) {
    const [name, symbol, decimals, balance] = await Promise.all([
        contract.name(),
        contract.symbol(),
        contract.decimals(),
        contract.balanceOf(userAddress),
    ]);

    return {
        name,
        symbol,
        decimals,
        balance: ethers.formatUnits(balance, decimals),
    };
}
```

---

## Contract Write (Transaction)

```typescript
// ต้องใช้ signer (ผู้ใช้ sign transaction)
async function approveAndDeposit(amount: string) {
    const provider = new ethers.BrowserProvider(window.ethereum);
    const signer = await provider.getSigner();

    const tokenContract = new ethers.Contract(TOKEN_ADDRESS, ERC20_ABI, signer);
    const vaultContract = new ethers.Contract(VAULT_ADDRESS, VAULT_ABI, signer);

    const amountWei = ethers.parseEther(amount);

    // Step 1: Approve
    const approveTx = await tokenContract.approve(VAULT_ADDRESS, amountWei);
    await approveTx.wait(); // รอ confirm

    // Step 2: Deposit
    const depositTx = await vaultContract.deposit(amountWei);
    const receipt = await depositTx.wait();

    console.log(`Deposited in block ${receipt.blockNumber}`);
    return receipt;
}
```

---

## Error Handling

```typescript
async function safeTransaction(fn: () => Promise<any>) {
    try {
        return await fn();
    } catch (err: any) {
        // User rejected
        if (err.code === 'ACTION_REJECTED' || err.code === 4001) {
            throw new Error('ผู้ใช้ปฏิเสธการทำธุรกรรม');
        }
        // Contract revert
        if (err.code === 'CALL_EXCEPTION') {
            const reason = err.reason || err.data?.message || 'Contract reverted';
            throw new Error(`Contract error: ${reason}`);
        }
        // Insufficient funds
        if (err.code === 'INSUFFICIENT_FUNDS') {
            throw new Error('ETH ไม่เพียงพอสำหรับค่า gas');
        }
        throw err;
    }
}
```

---

## Event Listening

```typescript
// Listen to contract events แบบ real-time
const filter = contract.filters.Transfer(null, userAddress); // รับ event Transfer มาถึง user

contract.on(filter, (from, to, amount, event) => {
    console.log(`รับ ${ethers.formatEther(amount)} tokens จาก ${from}`);
    // อัปเดต UI
});

// Query historical events
async function getTransferHistory(fromBlock: number) {
    const events = await contract.queryFilter(filter, fromBlock, 'latest');
    return events.map(e => ({
        from: e.args.from,
        to: e.args.to,
        amount: ethers.formatEther(e.args.value),
        blockNumber: e.blockNumber,
        txHash: e.transactionHash,
    }));
}

// Cleanup เมื่อ component unmount
contract.removeAllListeners();
```

---

## Multi-Chain Support

```typescript
const CHAIN_CONFIG = {
    1:     { name: 'Ethereum',  rpc: process.env.ETH_RPC,  explorer: 'https://etherscan.io' },
    137:   { name: 'Polygon',   rpc: process.env.POLY_RPC,  explorer: 'https://polygonscan.com' },
    42161: { name: 'Arbitrum',  rpc: process.env.ARB_RPC,   explorer: 'https://arbiscan.io' },
    8453:  { name: 'Base',      rpc: process.env.BASE_RPC,  explorer: 'https://basescan.org' },
} as const;

async function switchNetwork(chainId: number) {
    await window.ethereum.request({
        method: 'wallet_switchEthereumChain',
        params: [{ chainId: `0x${chainId.toString(16)}` }],
    });
}
```

---

## Security สำหรับ Frontend

| ข้อควรระวัง | วิธีป้องกัน |
|-----------|-----------|
| RPC key ถูก expose | ใช้ restricted API key + allowlist domain |
| Signature phishing | แสดง human-readable message ก่อน sign เสมอ |
| Address spoofing | แสดง ENS name + checksum address |
| Fake approval | แสดง exact amount ที่ approve ไม่ใช่ unlimited |
| XSS → wallet drain | sanitize input, CSP headers |
