概述
最近朋友考虑找个 web3 的远程工作,之前的 web3 项目黄了,就整理了下 web3 相关的代码分享出来希望可以帮助大家对合约交互有个大致的了解。
主要功能
-
OKX、MateMask、Phontom 等钱包的连接
-
EVM、Tron、Solana 等多链的合约交互
项目结构
bash
web3-start/
├── src/
│ ├── components/ # 前端交互组件(如钱包交互、交易面板等)
│ ├── Interaction/ # 合约交互功能页面
│ ├── SelectChain/ # 选择链页面
│ ├── SelectWallet/ # 选择钱包页面
│ ├── constants/ # 各链配置、常量定义
│ ├── contracts/ # 智能合约源码
│ ├── hook/ # 业务逻辑 hooks(如钱包连接、交易处理、余额查询等)
│ ├── lib/ # 区块链交互libs(如 EVM 交易等)
│ ├── store/ # 状态管理(钱包、余额、用户信息等)
│ ├── types/ # TypeScript 类型声明
├── public/ # 公共资源目录
├── .env # 环境变量(API Key等配置)
├── README.md # 项目说明文档
├── package.json # 依赖与脚本配置
钱包交互实现详解
项目支持多种钱包(如 MetaMask、OKX、TronLink、Sui Wallet)连接与账户管理,核心逻辑主要分布在 src/hook/useConnectWallet.ts
和相关组件中。以下将以 EVM 钱包为例,详细剖析钱包连接和链上交易的实现方式。
1. 钱包连接(以 EVM/MetaMask 为例)
连接钱包主要通过调用浏览器注入的钱包(如 window.ethereum),并请求账户授权:
typescript
const connectEvmWallet = async () => {
try {
const provider = getProvider();
// 请求用户授权并连接钱包
const { accounts } = await connectAsync({
connector: provider,
chainId: CHAIN[chainType.toUpperCase()].id,
});
await handleConnectSuccess(accounts[0]);
} catch (error) {
handleConnectError(error);
}
};
对于 OKX、Tron、Sui 等钱包,也有专门的连接方法,例如:
typescript
const connectTronWallet = async () => {
const currentProvider = window?.tronLink;
subscribeTronWallet();
window?.tron.request({ method: "eth_requestAccounts" }).then((res: any) => {
addressRef.current = currentProvider.tronWeb.defaultAddress.base58;
handleSignMessage(
currentProvider.tronWeb.defaultAddress.base58,
CHAIN.TRON.brief
);
});
};
2. 钱包事件监听
连接钱包后,需要监听如账户切换、断开连接等事件:
typescript
const subscribeSuiWallet = async () => {
const provider = window.okxwallet.sui;
provider.features["standard:events"].on("connect", () =>
setIsConnected(true)
);
provider.features["standard:events"].on(
"accountChanged",
(publicKey: any) => {
if (publicKey) {
console.log(`Switched to account ${publicKey.toBase58()}`);
}
}
);
provider.features["standard:events"].on("disconnect", () => {
// disconnect();
});
};
余额查询与链上交互
钱包连接成功后,可获取当前账户的余额,展示在前端页面上:
typescript
const fetchBalanceLogic = async (set: any, chain: any, address: string) => {
set({ isLoading: true });
let balance: any = null;
if (isEvmChain(chain.brief)) {
balance = await getEvmBalance(chain.brief, address);
set({
balance: {
origin: balance,
value: formatUnits(balance, chain.decimals),
},
});
return;
}
// 其他链(如Solana、Tron)处理方式
// ...
};
合约交互与交易流程
前端通过钱包签名发起交易,完成如转账链上操作。典型流程如下:
1. 发起交易
在 src/components/Interaction/index.tsx
中,用户输入金额后点击按钮即可发起交易:
tsx
const handleTrade = async () => {
if (!inputValue || Number(inputValue) <= 0) {
addToast({ title: "Please enter a valid amount", color: "danger" });
return;
}
// 判断余额是否足够
if (Number(inputValue) >= 1) {
addToast({ title: "Please enter a amount < 1", color: "danger" });
return;
}
try {
const voteTokenAmount = parseUnits(
inputValue,
CHAIN[chain.brief!.toUpperCase()]?.decimals ?? 18
);
// ...额外判断和授权逻辑
setLoading(true);
await handleTradeFunc({
inputValue: Number(inputValue),
address,
voteToken: {
tokenAddress: "",
chainType: chain.brief,
tokenDecimals: CHAIN[chain.brief!.toUpperCase()]?.decimals,
},
mainTokenBalance: balance.origin,
});
setShowTradeSuccess(true);
setTimeout(() => setShowTradeSuccess(false), 2000);
} catch (error: any) {
// ...错误提示
} finally {
setLoading(false);
}
};
2. EVM 链合约交互(签名 & 发送交易)
在 src/lib/okxEvm.ts
中,使用 viem
库和钱包签名实现交易发送:
typescript
const publicClient = createPublicClient({ chain, transport: http() });
const { request } = await publicClient.simulateContract({
address: params.smartContractAddress as `0x${string}`,
abi: EvmVoteAbi,
functionName: "placeVote",
args: [
[
params.voteId,
params.tokenAddress,
params.tokenAmount,
params.expireTimestamp,
params.signature,
],
],
value: parseUnits(params.tokenAmount, 0),
account: params.senderAddress as `0x${string}`,
});
// 发送交易
const walletClient = createWalletClient({
chain,
transport: custom(window?.ethereum),
});
const result = await walletClient.writeContract(request);
源码及在线体验
github 地址: github.com/jiandandkl/...
在线 demo: web3-start-sandy.vercel.app/