算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
blockchain-web3【设计与创意】Use this skill when asked about web3 frontend development, ethers.js, viem, wagmi, web3.js, wallet integration (MetaMask, Phantom, WalletConnect), dApp architecture, RPC providers (Alchemy, Infura), and TypeScript blockchain SDKs. Language: TypeScript. Covers reading blockchain state, sending transactions, wallet connection flows, contract interactio…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Guide web3 frontend and dApp development covering library selection, wallet integration, contract interaction, transaction management, event handling, and account abstraction. Focuses on TypeScript-based frontend patterns.
"web3", "ethers.js", "viem", "wagmi", "web3.js", "metamask", "phantom wallet", "walletconnect", "dapp", "decentralized app", "react blockchain", "nextjs web3", "ethers contract", "contract interaction", "read contract", "write contract", "send transaction", "sign message", "eip-1193", "eip-4337", "account abstraction", "multicall", "rpc provider", "infura", "alchemy", "blockchain frontend", "typechain", "wagmi hooks", "viem client", "user operation", "sign typed data"
Web3 frontend architecture: library selection, provider setup, wallet connection flow, contract interaction patterns, event handling, and transaction management.
4000 tokens
Web3 frontend stack:
├── Modern TypeScript dApp?
│ ├── React + TypeScript → viem + wagmi (default)
│ │ ├── viem: lightweight, tree-shakeable, type-safe
│ │ ├── wagmi: React hooks, auto-refetch, multicall
│ │ └── ConnectKit/RainbowKit: wallet UI components
│ └── Vanilla JS/Node → viem (no React dependency)
├── Legacy / broad compatibility?
│ ├── ethers.js v6 — stable, well-documented
│ ├── TypeChain for typed contracts
│ └── More boilerplate than viem
├── Solana dApp?
│ ├── @solana/web3.js v2
│ ├── @solana/wallet-adapter-react
│ └── Anchor TS client for Anchor programs
└── Cosmos dApp?
├── @cosmjs/stargate
└── Cosmos Kit (React)
Wallet integration:
├── Desktop browser?
│ ├── MetaMask (EVM) — most users
│ ├── Phantom (Solana + EVM)
│ ├── Rabby (EVM, superior UX)
│ └── EIP-6963 multi-injected provider discovery
├── Mobile?
│ ├── WalletConnect v2 — universal mobile bridge
│ └── MetaMask SDK — native mobile support
├── Smart accounts (ERC-4337)?
│ ├── Privy, Dynamic, Web3Auth — social login + embedded wallets
│ └── ZeroDev, Biconomy, StackUp — ERC-4337 bundlers
└── Hardware wallet?
├── Ledger — Ledger Connect
└── Trezor — Trezor Suite
import { createConfig, http } from 'wagmi'
import { mainnet, polygon, arbitrum } from 'wagmi/chains'
import { metaMask, walletConnect } from 'wagmi/connectors'
export const config = createConfig({
chains: [mainnet, polygon, arbitrum],
connectors: [
metaMask(),
walletConnect({ projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID! }),
],
transports: {
[mainnet.id]: http(
`https://eth-mainnet.g.alchemy.com/v2/${process.env.NEXT_PUBLIC_ALCHEMY_API_KEY}`,
{ batch: true } // Enable multicall batching
),
[polygon.id]: http(),
[arbitrum.id]: http(),
},
})
import { useReadContract } from 'wagmi'
import { abi } from './token-abi'
import { formatUnits } from 'viem'
// React hook — auto-refetches on account/chain change
function TokenBalance({ tokenAddress, userAddress }: Props) {
const { data: balance, isLoading, isError } = useReadContract({
address: tokenAddress as `0x${string}`,
abi,
functionName: 'balanceOf',
args: [userAddress],
})
if (isLoading) return <div>Loading...</div>
if (isError) return <div>Error loading balance</div>
return <div>{formatUnits(balance ?? 0n, 18)} Tokens</div>
}
import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
import { parseEther } from 'viem'
function TransferForm() {
const { writeContract, data: hash, isPending, error } = useWriteContract()
const { isLoading: isConfirming, isSuccess: isConfirmed } =
useWaitForTransactionReceipt({ hash })
async function handleTransfer(amount: string) {
writeContract({
address: '0x...',
abi,
functionName: 'transfer',
args: ['0x...recipient', parseEther(amount)],
})
}
if (isPending) return <div>Check wallet...</div>
if (isConfirming) return <div>Confirming transaction...</div>
if (isConfirmed) return <div>Transfer complete!</div>
return (
<div>
<input type="text" placeholder="Amount" />
<button onClick={() => handleTransfer('10')}>Send</button>
{error && <div>Error: {error.message}</div>}
</div>
)
}
import { useSignTypedData } from 'wagmi'
import { domain, types } from './eip712-types'
function SignOrder() {
const { signTypedData, data: signature, isPending } = useSignTypedData()
const handleSign = () => {
signTypedData({
domain: {
name: 'Exchange',
version: '1',
chainId: 1,
verifyingContract: '0x...',
},
types: {
Order: [
{ name: 'maker', type: 'address' },
{ name: 'taker', type: 'address' },
{ name: 'amount', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
},
primaryType: 'Order',
message: {
maker: '0x...',
taker: '0x...',
amount: 1000n,
nonce: 1n,
deadline: 1700000000n,
},
})
}
return <button onClick={handleSign}>Sign Order</button>
}
import { useMulticall } from 'wagmi'
// Batch multiple read calls into single RPC request
function useTokenBalances(tokens: `0x${string}`[], user: `0x${string}`) {
const { data } = useMulticall({
contracts: tokens.map(token => ({
address: token,
abi: erc20Abi,
functionName: 'balanceOf',
args: [user],
})),
})
return data?.map((result) =>
result.status === 'success' ? formatUnits(result.result, 18) : '0'
)
}
import { useWatchContractEvent } from 'wagmi'
function TransferMonitor() {
useWatchContractEvent({
address: '0x...',
abi,
eventName: 'Transfer',
onLogs(logs) {
logs.forEach(log => {
console.log(`Transfer: ${log.args.from} → ${log.args.to}: ${log.args.value}`)
// Update UI in real-time
})
},
})
return <div>Monitoring transfers...</div>
}
import { useSendTransaction, useWaitForTransactionReceipt } from 'wagmi'
import { parseEther } from 'viem'
function SendTransaction() {
const {
sendTransaction,
data: hash,
isPending,
error: sendError,
} = useSendTransaction()
const {
isLoading: isConfirming,
isSuccess: isConfirmed,
error: receiptError,
} = useWaitForTransactionReceipt({ hash })
async function handleSend(amount: string) {
try {
sendTransaction({
to: '0x...',
value: parseEther(amount),
})
} catch (e) {
// User rejected in wallet
if ((e as any)?.code === 'ACTION_REJECTED' || (e as any)?.code === 4001) {
console.log('User rejected transaction')
return
}
// Insufficient funds
if ((e as any)?.message?.includes('insufficient funds')) {
console.log('Insufficient balance')
return
}
console.error('Transaction error:', e)
}
}
// Transaction states
if (isPending) return <div>Please confirm in wallet...</div>
if (isConfirming) return <div>Waiting for confirmation...</div>
if (isConfirmed) return <div>Transaction confirmed! Hash: {hash}</div>
return (
<div>
<button onClick={() => handleSend('0.1')}>Send 0.1 ETH</button>
{sendError && <div>Error: {sendError.message}</div>}
</div>
)
}
import { createPublicClient, createWalletClient, http } from 'viem'
import { mainnet } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
// Public client (reads)
const publicClient = createPublicClient({
chain: mainnet,
transport: http('https://eth-mainnet.g.alchemy.com/v2/...'),
})
// Wallet client (writes)
const account = privateKeyToAccount('0x...')
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(),
})
// Read example
const balance = await publicClient.readContract({
address: '0x...',
abi,
functionName: 'balanceOf',
args: ['0x...'],
})
// Write example
const hash = await walletClient.writeContract({
address: '0x...',
abi,
functionName: 'transfer',
args: ['0x...', 1000n],
})
const receipt = await publicClient.waitForTransactionReceipt({ hash })
import { createSmartAccountClient } from 'permissionless'
import { signerToSimpleSmartAccount } from 'permissionless/accounts'
// UserOperation replaces regular eth_sendTransaction
// 1. User signs UserOperation off-chain
// 2. Bundler submits to EntryPoint contract
// 3. EntryPoint verifies signature and pays gas (via paymaster)
async function sendUserOp() {
const smartAccount = await signerToSimpleSmartAccount(client, {
entryPoint: ENTRYPOINT_ADDRESS_V07,
signer: walletClient,
factoryAddress: SIMPLE_ACCOUNT_FACTORY,
})
const smartClient = createSmartAccountClient({
account: smartAccount,
client: publicClient,
bundlerUrl: 'https://bundler.example.com',
paymasterUrl: 'https://paymaster.example.com', // Optional: sponsored gas
})
const txHash = await smartClient.sendTransaction({
to: '0x...',
data: encodeFunctionData({ abi, functionName: 'mint', args: [] }),
})
}
// ERC-4337 supports session keys for automated transactions
// 1. Deploy smart account
// 2. Approve session key with specific permissions
// 3. Session key signs UserOps without main key
// Session key permissions:
interface SessionKeyPermission {
target: string // Allowed contract
functionSelector: string // Allowed function (bytes4)
valueLimit: bigint // Max ETH value
expiry: number // Unix timestamp
}
// Use cases:
// - Gaming: auto-approve in-game transactions
// - DeFi: scheduled DCA without manual signing each time
// - Subscriptions: recurring payments
type Web3Error = {
code: number | string
message: string
data?: string
}
const ErrorHandler = {
// User rejected in wallet
USER_REJECTED: (e: Web3Error) => e.code === 4001 || e.code === 'ACTION_REJECTED',
// Insufficient funds
INSUFFICIENT_FUNDS: (e: Web3Error) => e.message.includes('insufficient funds'),
// Gas estimation failed
GAS_ESTIMATION_FAILED: (e: Web3Error) => e.message.includes('gas required exceeds'),
// Network error
NETWORK_ERROR: (e: Web3Error) => e.message.includes('network error') || e.code === 'NETWORK_ERROR',
// Rate limited
RATE_LIMITED: (e: Web3Error) => e.message.includes('rate limit') || e.message.includes('429'),
// Nonce too low
NONCE_TOO_LOW: (e: Web3Error) => e.message.includes('nonce too low'),
// Contract revert
CONTRACT_REVERT: (e: Web3Error) => e.message.includes('revert') || e.data !== undefined,
}
// Usage
function handleError(error: unknown) {
const e = error as Web3Error
if (ErrorHandler.USER_REJECTED(e)) {
return { type: 'warning', message: 'Transaction cancelled' }
}
if (ErrorHandler.INSUFFICIENT_FUNDS(e)) {
return { type: 'error', message: 'Insufficient balance for gas' }
}
if (ErrorHandler.RATE_LIMITED(e)) {
return { type: 'error', message: 'Too many requests, please wait' }
}
return { type: 'error', message: 'Transaction failed: ' + e.message }
}
Web3 Frontend Architecture
├── Wallet connection?
│ ├── Multi-wallet → Web3Modal / RainbowKit (wagmi-based)
│ ├── Single wallet → ConnectKit / Privy (embedded wallet)
│ └── Account abstraction → ZeroDev / Biconomy (ERC-4337)
├── Data fetching?
│ ├── Real-time → wagmi hooks + useQuery (TanStack Query)
│ ├── Indexed → The Graph subgraph (GraphQL)
│ └── Custom → ethers.js direct RPC calls
├── Transaction management?
│ ├── Simple → wagmi useSendTransaction
│ ├── Complex → viem + custom state machine
│ └── Gasless → Biconomy / OpenGSN (sponsored txs)
└── Chain selection?
├── Multi-chain → wagmi chain switching + EIP-6963
├── L2-only → Specific L2 (Optimism, Arbitrum)
└── L1 + L2 fallback → Primary L2 with L1 fallback
Decision criteria: Evaluate target user (retail vs power), transaction complexity, data freshness needs, and wallet diversity.
// blockchain-web3/hooks/useTokenBalance.ts
import { useReadContract, useAccount } from 'wagmi';
import { erc20ABI } from 'wagmi-generate';
export function useTokenBalance(tokenAddress: `0x${string}`) {
const { address } = useAccount();
return useReadContract({
address: tokenAddress,
abi: erc20ABI,
functionName: 'balanceOf',
args: [address!],
query: {
enabled: !!address,
refetchInterval: 10000,
},
});
}
// blockchain-web3/hooks/useTransaction.ts
type TxState = 'idle' | 'approving' | 'pending' | 'confirmed' | 'failed';
interface TransactionState {
status: TxState;
txHash?: `0x${string}`;
error?: string;
}
export function useTransaction() {
const [state, setState] = useState<TransactionState>({ status: 'idle' });
const send = useCallback(async (fn: () => Promise<`0x${string}`>) => {
setState({ status: 'pending' });
try {
const txHash = await fn();
setState({ status: 'pending', txHash });
const receipt = await waitForTransaction({ hash: txHash });
setState({ status: receipt.status === 'success' ? 'confirmed' : 'failed', txHash });
} catch (err) {
setState({ status: 'failed', error: (err as Error).message });
}
}, []);
return { state, send };
}
estimateGas with 20% buffer; handle out-of-gas errors with user notification.| Anti-Pattern | Consequence | Solution |
|---|---|---|
| Direct RPC calls for every read | Rate limiting, slow UI | Use subgraph + wagmi caching |
| No loading states | Poor UX during tx | Show tx progress with step indicators |
| Hardcoded gas limits | Failed txs on network congestion | Dynamic gas estimation with buffer |
| Ignoring chain switching | App breaks on wrong chain | Handle chainChanged event; prompt switch |
| No tx simulation | Failed txs waste user gas | Simulate txs before sending (eth_call) |
eth_call before sending; warn user on failure.blockchain → blockchain-web3