2026年最全DApp开发实战指南:从零搭建公链钱包,我踩过的坑都在这了
导读:本文从一个真实项目的视角,完整拆解DApp + 公链钱包的开发全流程。涵盖钱包连接、智能合约交互、多链适配、Gas优化等核心环节,附完整代码。无论你是Web2转型还是Web3老手,这篇万字长文都值得收藏。
一、为什么现在做DApp开发正是时候?
2025年,链上活跃地址数持续创新高,DeFi TVL重回千亿级别,NFT、GameFi、SocialFi 赛道轮番爆发。但纵观整个开发者生态,真正能打通"前端DApp → 智能合约 → 钱包交互"全链路的工程师,依然是稀缺中的稀缺。
我从2023年开始全职做DApp开发,先后在以太坊、BNB Chain、Solana、Sui 上部署过产品。这篇文章是我在实际项目中总结的方法论,**不是教程搬运,是我真金白银踩坑后沉淀下来的东西。
你将收获:
| 模块 | 内容 |
|---|---|
| 钱包连接 | MetaMask / WalletConnect / Solana钱包 多端适配 |
| 合约交互 | ethers.js / viem / web3.js 对比选型 + 实战代码 |
| 多链架构 | 一套前端如何同时支持EVM链 + 非EVM链 |
| Gas优化 | 合约层面 + 前端层面的双重优化策略 |
| 安全防护 | 常见攻击手法及防御方案(亲历过的) |
二、技术选型:2025年DApp开发的最佳组合
先上结论,再解释为什么:
前端框架: Next.js 14+ (App Router)
Web3库: viem + wagmi(EVM)/ @solana/web3.js(Solana)
钱包连接: RainbowKit / ConnectKit
合约开发: Foundry(取代Hardhat成为主流)
索引层: The Graph / Ponder
部署: Vercel + Fleek(去中心化托管)
2.1 为什么选 viem 而不是 ethers.js?
ethers.js v6 虽然更新了,但 viem 在类型安全、Bundle Size、性能上全面领先:
// viem — 类型推导天然友好
import { createPublicClient, http, parseAbi } from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({
chain: mainnet,
transport: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
})
// 读取合约 — 自动推导返回类型
const balance = await client.readContract({
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
abi: parseAbi(['function balanceOf(address) view returns (uint256)']),
functionName: 'balanceOf',
args: ['0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'],
})
console.log(balance) // bigint,类型安全
// ethers.js v6 — 对比一下
import { ethers } from 'ethers'
const provider = new ethers.JsonRpcProvider('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY')
const contract = new ethers.Contract(
'0xdAC17F958D2ee523a2206206994597C13D831ec7',
['function balanceOf(address) view returns (uint256)'],
provider
)
const balance = await contract.balanceOf('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')
看起来差不多?实际体感差异巨大:
| 对比维度 | viem + wagmi | ethers.js v6 |
|---|---|---|
| Bundle Size | ~27KB (gzip) | ~118KB (gzip) |
| TypeScript支持 | 原生泛型推导 | 需要手动标注 |
| React集成 | wagmi hooks 直接用 | 需要自己封装 |
| SSR支持 | 天然支持 | 需要额外处理 |
| 社区趋势 | GitHub star 增速更快 | 成熟但增长放缓 |
结论:新项目无脑选 viem + wagmi。存量项目迁移不急,ethers v6 能用。
2.2 合约开发为什么推荐 Foundry?
如果你还在用 Hardhat,我建议你花一周时间学 Foundry,然后你就回不去了:
// Counter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Counter {
uint256 public number;
event NumberChanged(uint256 indexed newNumber, address indexed caller);
function setNumber(uint256 newNumber) external {
number = newNumber;
emit NumberChanged(newNumber, msg.sender);
}
function increment() external {
number++;
emit NumberChanged(number, msg.sender);
}
}
Foundry的核心优势:
速度:Rust编写,测试执行比Hardhat快10-100倍
Fuzz测试:内置模糊测试,一行代码多出几百个测试用例
Solidity写测试:不需要切换到JavaScript上下文
forge script:部署脚本也是Solidity,和合约无缝衔接
三、钱包连接:全场景适配方案
这是DApp开发的第一道门槛。用户连不上钱包,后面一切都白搭。
3.1 架构设计
┌─────────────────────────────────────────────┐
│ DApp 前端 │
│ ┌─────────┐ ┌──────────┐ ┌─────────────┐ │
│ │RainbowKit│ │ wagmi │ │ viem client │ │
│ └────┬─────┘ └────┬─────┘ └──────┬──────┘ │
│ │ │ │ │
│ ┌────▼──────────────▼───────────────▼────┐ │
│ │ 统一钱包状态管理层 │ │
│ └────┬──────────────┬───────────────┬────┘ │
│ │ │ │ │
│ ┌────▼────┐ ┌─────▼────┐ ┌─────▼────┐ │
│ │ MetaMask│ │WalletCon-│ │ Coinbase │ │
│ │ Injected│ │ nect │ │ Wallet │ │
│ └─────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────┘
3.2 完整接入代码
Step 1:安装依赖
npm install @rainbow-me/rainbowkit wagmi viem @tanstack/react-query
Step 2:配置Provider
typescript
// src/config/wagmi.ts
import { getDefaultConfig } from '@rainbow-me/rainbowkit'
import { mainnet, polygon, arbitrum, base, optimism } from 'wagmi/chains'
import { http } from 'wagmi'
export const config = getDefaultConfig({
appName: 'My DApp',
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID', // 去 cloud.walletconnect.com 申请
chains: [
mainnet,
polygon,
arbitrum,
base,
optimism,
// 可以自定义链
{
id: 56,
name: 'BNB Chain',
nativeCurrency: { name: 'BNB', symbol: 'BNB', decimals: 18 },
rpcUrls: {
default: { http: ['https://bsc-dataseed.binance.org'] },
},
blockExplorers: {
default: { name: 'BscScan', url: 'https://bscscan.com' },
},
},
],
transports: {
[mainnet.id]: http('https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY'),
[polygon.id]: http('https://polygon-mainnet.g.alchemy.com/v2/YOUR_KEY'),
[arbitrum.id]: http(),
[base.id]: http(),
[optimism.id]: http(),
56: http(),
},
})
```
```tsx
// src/app/providers.tsx
'use client'
import { RainbowKitProvider, darkTheme } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { config } from '@/config/wagmi'
import '@rainbow-me/rainbowkit/styles.css'
const queryClient = new QueryClient()
export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider
theme={darkTheme({
accentColor: '#7c3aed',
accentColorForeground: 'white',
borderRadius: 'medium',
})}
modalSize="compact"
>
{children}
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}
```
Step 3:在页面中使用**
```tsx
// src/app/page.tsx
'use client'
import { ConnectButton } from '@rainbow-me/rainbowkit'
import { useAccount, useBalance, useReadContract, useWriteContract } from 'wagmi'
import { parseEther, formatEther } from 'viem'
export default function Home() {
const { address, isConnected, chain } = useAccount()
const { data: balance } = useBalance({ address })
return (
<main className="min-h-screen bg-gray-950 text-white p-8">
<nav className="flex justify-between items-center mb-12">
<h1 className="text-2xl font-bold">My DApp</h1>
<ConnectButton />
</nav>
{isConnected && (
<div className="max-w-2xl mx-auto space-y-6">
<div className="bg-gray-900 rounded-2xl p-6 border border-gray-800">
<h2 className="text-lg text-gray-400 mb-2">钱包信息</h2>
<p className="font-mono text-sm">{address}</p>
<p className="text-2xl font-bold mt-2">
{balance?.formatted} {balance?.symbol}
</p>
<p className="text-sm text-gray-500 mt-1">
当前网络:{chain?.name}
</p>
</div>
<ContractInteraction />
</div>
)}
</main>
)
}
3.3 合约交互组件(读写分离)
// src/components/ContractInteraction.tsx
'use client'
import { useState } from 'react'
import { useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
import { parseAbi, parseEther } from 'viem'
const CONTRACT_ADDRESS = '0xYourContractAddress' as const
const ABI = parseAbi([
'function number() view returns (uint256)',
'function setNumber(uint256 newNumber)',
'function increment()',
'event NumberChanged(uint256 indexed newNumber, address indexed caller)',
])
export function ContractInteraction() {
const [newNumber, setNewNumber] = useState('')
// 读取链上数据 — 自动缓存 + 轮询
const { data: currentNumber, refetch } = useReadContract({
address: CONTRACT_ADDRESS,
abi: ABI,
functionName: 'number',
})
// 写入合约
const { writeContract, data: hash, isPending } = useWriteContract()
// 等待交易确认
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
hash,
})
const handleSetNumber = () => {
writeContract({
address: CONTRACT_ADDRESS,
abi: ABI,
functionName: 'setNumber',
args: [BigInt(newNumber)],
})
}
const handleIncrement = () => {
writeContract({
address: CONTRACT_ADDRESS,
abi: ABI,
functionName: 'increment',
})
}
return (
<div className="bg-gray-900 rounded-2xl p-6 border border-gray-800">
<h2 className="text-lg text-gray-400 mb-4">合约交互</h2>
<div className="mb-6">
<p className="text-sm text-gray-500">当前链上数字</p>
<p className="text-4xl font-bold">{currentNumber?.toString() ?? '加载中...'}</p>
<button
onClick={() => refetch()}
className="text-sm text-purple-400 hover:text-purple-300 mt-1"
>
刷新
</button>
</div>
<div className="flex gap-4 mb-4">
<input
type="number"
value={newNumber}
onChange={(e) => setNewNumber(e.target.value)}
placeholder="输入新数字"
className="flex-1 bg-gray-800 rounded-lg px-4 py-3 border border-gray-700
focus:border-purple-500 focus:outline-none"
/>
<button
onClick={handleSetNumber}
disabled={isPending || isConfirming}
className="px-6 py-3 bg-purple-600 hover:bg-purple-500 disabled:opacity-50
rounded-lg font-medium transition-colors"
>
{isPending ? '签名中...' : isConfirming ? '确认中...' : '设置数字'}
</button>
</div>
<button
onClick={handleIncrement}
disabled={isPending || isConfirming}
className="w-full py-3 bg-gray-800 hover:bg-gray-700 disabled:opacity-50
rounded-lg font-medium transition-colors border border-gray-700"
>
+1 (自增)
</button>
{isSuccess && (
<p className="text-green-400 text-sm mt-4">
交易成功!Tx: {hash?.slice(0, 10)}...
</p>
)}
</div>
)
}
4.2 多链地址推导
```typescript
// src/lib/wallet/multi-chain.ts
import { HDKey } from '@scure/bip32'
import { mnemonicToSeedSync } from '@scure/bip39'
import { wordlist } from '@scure/bip39/wordlists/english'
import { privateKeyToAccount } from 'viem/accounts'
import { Keypair } from '@solana/web3.js'
import { derivePath } from 'ed25519-hd-key'
import nacl from 'tweetnacl'
interface ChainConfig {
name: string
derivationPath: string
deriveAddress: (privateKey: Uint8Array) => string
}
const CHAINS: Record<string, ChainConfig> = {
ethereum: {
name: 'Ethereum',
derivationPath: "m/44'/60'/0'/0",
deriveAddress: (pk) => {
const account = privateKeyToAccount(`0x${Buffer.from(pk).toString('hex')}`)
return account.address
},
},
bitcoin: {
name: 'Bitcoin',
derivationPath: "m/84'/0'/0'/0", // BIP84 (Native SegWit)
deriveAddress: (pk) => {
// 实际项目中需要用 bitcoinjs-lib 推导
return 'bc1q...'
},
},
// Solana 使用 ed25519,路径不同
solana: {
name: 'Solana',
derivationPath: "m/44'/501'/0'/0'",
deriveAddress: (pk) => {
// Solana用ed25519,推导方式不同
return '...'
},
},
}
四、自建轻量级钱包(进阶)
如果项目需要内置钱包体验(类似Telegram Bot或嵌入式场景),可以集成 Privy 或 Dynamic,也可以自建:
4.1 基于助记词的HD钱包核心逻辑
// src/lib/wallet/hd-wallet.ts
import { generateMnemonic, mnemonicToSeedSync } from '@scure/bip39'
import { wordlist } from '@scure/bip39/wordlists/english'
import { HDKey } from '@scure/bip32'
import { privateKeyToAccount } from 'viem/accounts'
const DERIVATION_PATH = "m/44'/60'/0'/0" // 以太坊标准路径
export class HDWallet {
private seed: Uint8Array
private hdKey: HDKey
constructor(mnemonic?: string) {
const phrase = mnemonic ?? generateMnemonic(wordlist, 128) // 12词
this.seed = mnemonicToSeedSync(phrase)
this.hdKey = HDKey.fromMasterSeed(this.seed)
}
// 获取指定索引的账户
getAccount(index: number = 0) {
const derived = this.hdKey.derive(`${DERIVATION_PATH}/${index}`)
if (!derived.privateKey) throw new Error('Failed to derive key')
const account = privateKeyToAccount(
`0x${Buffer.from(derived.privateKey).toString('hex')}`
)
return account
}
// 批量生成地址(用于地址簿展示)
getAddresses(count: number = 5) {
return Array.from({ length: count }, (_, i) => {
const account = this.getAccount(i)
return {
index: i,
address: account.address,
path: `${DERIVATION_PATH}/${i}`,
}
})
}
}
// 使用示例
const wallet = new HDWallet()
console.log('Mnemonic:', wallet['seed']) // 实际项目中需要安全导出助记词
const addresses = wallet.getAddresses(5)
console.log(addresses)
// [
// { index: 0, address: '0x...', path: "m/44'/60'/0'/0/0" },
// { index: 1, address: '0x...', path: "m/44'/60'/0'/0/1" },
// ...
// ]
五、Gas优化实战:省到就是赚到
5.1 合约层面优化
```solidity
// ❌ 低效写法
contract Ineffient {
uint256 public price;
uint256 public supply;
address public owner;
function buy() external {
require(msg.value >= price, "Not enough ETH");
supply++;
owner.transfer(msg.value); // transfer 会 revert 但不友好
}
}
// ✅ 高效写法
contract Efficient {
// 使用紧凑存储:slot packing
// price (uint128) + supply (uint128) = 1个storage slot
uint128 public price;
uint128 public supply;
address public owner; // 160 bits, 单独一个slot
// 使用 custom errors 代替 require strings(省gas)
error InsufficientPayment(uint256 required, uint256 provided);
// 使用 unchecked 做安全范围内的算术
function buy() external {
if (msg.value < price) {
revert InsufficientPayment(price, msg.value);
}
unchecked { supply++; } // overflow极不可能
// 使用 call 代替 transfer(2300 gas限制问题)
(bool success, ) = owner.call{value: msg.value}("");
require(success, "Transfer failed");
}
// 使用 immutable 存储不变的值(部署时写入,读取免费)
constructor(uint256 _price) {
price = uint128(_price);
owner = msg.sender;
}
}
```
5.2 前端层面优化
```typescript
// src/lib/gas/optimizer.ts
import { estimateGas, getGasPrice } from 'viem/actions'
import type { PublicClient, WalletClient } from 'viem'
interface GasStrategy {
maxFeePerGas: bigint
maxPriorityFeePerGas: bigint
gasLimit: bigint
}
export async function estimateOptimalGas(
publicClient: PublicClient,
txParams: Parameters<typeof estimateGas>[1]
): Promise<GasStrategy> {
// 并行获取 gas 估算和 fee data
const [gasLimit, feeHistory, block] = await Promise.all([
estimateGas(publicClient, txParams),
publicClient.getFeeHistory({
blockCount: 20,
rewardPercentiles: [25, 50, 75],
}),
publicClient.getBlock({ blockTag: 'latest' }),
])
// EIP-1559 fee 估算
const baseFee = block.baseFeePerGas ?? 0n
// 取最近20个块的中位优先费
const priorityFees = feeHistory.reward
?.map(r => r[1]) // 50th percentile
.filter((f): f is bigint => f !== null) ?? []
const medianPriorityFee = priorityFees.length > 0
? priorityFees.sort((a, b) => Number(a - b))[Math.floor(priorityFees.length / 2)]
: 1500000000n // 1.5 gwei fallback
// 添加20% buffer避免stuck
const bufferedGasLimit = (gasLimit * 120n) / 100n
return {
maxFeePerGas: baseFee * 2n + medianPriorityFee,
maxPriorityFeePerGas: medianPriorityFee,
gasLimit: bufferedGasLimit,
}
}
// 使用:发送交易前先估算
// const gas = await estimateOptimalGas(publicClient, { to, value, data })
// await walletClient.sendTransaction({ ...txParams, ...gas })
```
5.3 批量操作优化
```solidity
// 批量调用合约 — 一次交易完成多笔操作
// 使用 Multicall3(大多数链已经内置部署)
// 前端批量读取
import { multicall } from 'viem/actions'
async function batchRead(publicClient: PublicClient) {
const results = await multicall(publicClient, {
contracts: [
{
address: TOKEN_A,
abi: erc20Abi,
functionName: 'balanceOf',
args: [userAddress],
},
{
address: TOKEN_B,
abi: erc20Abi,
functionName: 'balanceOf',
args: [userAddress],
},
{
address: TOKEN_A,
abi: erc20Abi,
functionName: 'allowance',
args: [userAddress, DEX_ADDRESS],
},
],
})
// 一次 RPC 调用拿到所有数据
return {
balanceA: results[0].result,
balanceB: results[1].result,
allowance: results[2].result,
}
}
```
---
六、安全防线:我亲身经历过的攻击
6.1 签名钓鱼
场景:用户在DApp上点了"签名",但签的是恶意授权。
```typescript
// ❌ 危险:盲目签名
const signature = await signMessage({ message: 'Sign this to login' })
// 用户不知道自己签了什么
// ✅ 安全:EIP-712 结构化签名(用户可以看到清晰的字段)
const domain = {
name: 'My DApp',
version: '1',
chainId: 1,
verifyingContract: '0xYourContract' as const,
}
const types = {
Login: [
{ name: 'address', type: 'address' },
{ name: 'nonce', type: 'uint256' },
{ name: 'expiry', type: 'uint256' },
],
}
const message = {
address: userAddress,
nonce: 1n,
expiry: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1小时过期
}
const signature = await signTypedData({ domain, types, primaryType: 'Login', message })
```
6.2 前端常见的安全检查清单
```typescript
// src/lib/security/checks.ts
// 1. 交易前验证目标地址
function validateContractAddress(address: string): boolean {
// 检查是否为已知恶意地址(维护黑名单)
const BLACKLIST = new Set([
'0xKnownScam1...',
'0xKnownScam2...',
])
if (BLACKLIST.has(address.toLowerCase())) return false
// 检查是否为合约地址(避免发到EOA丢失资金)
return true // 实际需要 eth_getCode 检查
}
// 2. 金额合理性检查
function validateAmount(amount: bigint, balance: bigint): string | null {
if (amount <= 0n) return '金额必须大于0'
if (amount > balance) return '余额不足'
if (amount > balance * 95n / 100n) return '警告:即将转出全部余额'
return null
}
// 3. 防止重入攻击的前端防线
class TransactionGuard {
private pending = new Set<string>()
async execute(key: string, txFn: () => Promise<any>) {
if (this.pending.has(key)) {
throw new Error('交易正在处理中,请勿重复提交')
}
this.pending.add(key)
try {
return await txFn()
} finally {
this.pending.delete(key)
}
}
}
```
---
七、生产级项目结构
my-dapp/
├── contracts/ # Foundry项目
│ ├── src/
│ │ ├── Counter.sol
│ │ ├── Token.sol
│ │ └── interfaces/
│ ├── test/
│ ├── script/ # 部署脚本
│ │ └── Deploy.s.sol
│ └── foundry.toml
├── src/ # Next.js 前端
│ ├── app/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── providers.tsx
│ ├── components/
│ │ ├── wallet/
│ │ │ ├── ConnectButton.tsx
│ │ │ └── NetworkSwitch.tsx
│ │ ├── contract/
│ │ │ ├── ReadContract.tsx
│ │ │ └── WriteContract.tsx
│ │ └── ui/
│ ├── config/
│ │ ├── wagmi.ts
│ │ ├── chains.ts # 链配置
│ │ └── contracts.ts # 合约地址 + ABI
│ ├── hooks/
│ │ ├── useContractRead.ts
│ │ ├── useContractWrite.ts
│ │ └── useTransaction.ts
│ ├── lib/
│ │ ├── gas/
│ │ ├── security/
│ │ └── wallet/
│ └── styles/
├── subgraph/ # The Graph 索引
│ ├── schema.graphql
│ ├── src/
│ └── subgraph.yaml
├── package.json
└── README.md
合约地址集中管理
typescript
// src/config/contracts.ts
import { mainnet, polygon, arbitrum, base } from 'wagmi/chains'
type ContractInfo = {
address: `0x${string}`
chainId: number
deployedBlock?: bigint
}
//每个链上的合约地址
export const CONTRACTS = {
Counter: {
[mainnet.id]: {
address: '0x1234...5678',
chainId: mainnet.id,
deployedBlock: 19000000n,
},
[polygon.id]: {
address: '0xabcd...efgh',
chainId: polygon.id,
deployedBlock: 52000000n,
},
},
Token: {
[mainnet.id]: {
address: '0x9876...5432',
chainId: mainnet.id,
},
},
} as const satisfies Record<string, Record<number, ContractInfo>>
ABI 集中导出
export { abi as CounterABI } from './abis/Counter.json'
export { abi as TokenABI } from './abis/Token.json'
八、部署与监控
8.1 合约验证
bash
# Foundry 一键验证
forge verify-contract \
--chain-id 1 \
--etherscan-api-key $ETHERSCAN_API_KEY \
0xYourContractAddress \
src/Counter.sol:Counter
8.2 前端事件监听
```typescript
// src/hooks/useContractEvents.ts
import { useWatchContractEvent } from 'wagmi'
import { parseAbi } from 'viem'
export function useNumberChangedEvents() {
useWatchContractEvent({
address: CONTRACT_ADDRESS,
abi: parseAbi(['event NumberChanged(uint256 indexed newNumber, address indexed caller)']),
eventName: 'NumberChanged',
onLogs(logs) {
logs.forEach((log) => {
const { newNumber, caller } = log.args
console.log(`数字变更为 ${newNumber},操作者: ${caller}`)
// 可以触发通知、更新缓存等
// queryClient.invalidateQueries({ queryKey: ['number'] })
})
},
})
九、常见踩坑记录(血泪总结)
| 坑 | 问题描述 | 解决方案 |
|---|---|---|
| WalletConnect断连 | 移动端切后台再回来,连接丢失 | wagmi自动重连 + reconnectOnMount: true |
| Gas估算失败 | estimateGas在某些RPC上不稳定 |
手动设gasLimit上限 + fallback机制 |
| 交易pending卡死 | 用户nonce冲突或gas过低 | 实现nonce管理 + 加速/取消交易功能 |
| 多链ABI不同步 | 合约升级后ABI变了 | 用版本化ABI管理 + 合约验证后的ABI自动拉取 |
| 移动端钱包兼容 | 部分手机浏览器不支持注入 | WalletConnect作为兜底方案 |
| 链切换后状态残留 | 用户切链后旧数据还在 | useAccount 监听 chainId 变化触发清理 |
十、总结与建议
2025年做DApp开发,技术栈已经相当成熟,不再是摸着石头过河的阶段。但真正拉开差距的,是以下三点:
1. 体验打磨— 钱包连接、交易确认、错误提示,每个环节都要做到丝滑。用户不会因为你是Web3就容忍卡顿和报错。
2. 安全意识 — 不要等被攻击了才想起安全。从第一行代码开始就要有防护思维,签名验证、输入校验、重入防护一个都不能少。
3. 多链思维 — 不要只盯着一条链。viem + wagmi的抽象层已经做得很好,前期多花一天做多链适配,后期省一个月的迁移成本。
如果这篇文章对你有帮助,欢迎点赞 + 收藏 + 关注。
后续我会持续更新:
《智能合约安全审计实战:从发现到修复》
《用The Graph搭建DApp索引层:从Schema到Subgraph部署》
《Solana DApp开发完全指南:Anchor框架从入门到生产》
关注不迷路,我们下篇见。
版权声明:本文为原创内容,首发于CSDN,转载请注明出处。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)