前端 dApp 连 MetaMask 用 ethers.js,v5 和 v6 API 变了。写代码总卡在”provider 怎么初始化”这一步。
ethers v6 的最小骨架
<script src="https://cdn.jsdelivr.net/npm/ethers@6.7.1/dist/ethers.min.js"></script>
<script>
let provider, signer;
if (window.ethereum) {
provider = new ethers.BrowserProvider(window.ethereum);
} else {
alert("请先安装 MetaMask");
}
document.getElementById("connect").addEventListener("click", async () => {
await provider.send("eth_requestAccounts", []);
signer = await provider.getSigner();
const address = await signer.getAddress();
const balance = await provider.getBalance(address);
console.log(address, ethers.formatEther(balance), "ETH");
});
</script>
要点:
- v6 用
ethers.BrowserProvider(v5 是ethers.providers.Web3Provider) getSigner()现在返回 Promise,要awaitethers.formatEther(v6)替代了ethers.utils.formatEther(v5)
v5 vs v6 对照
| 场景 | v5 | v6 |
|---|---|---|
| 浏览器 Provider | new ethers.providers.Web3Provider(eth) | new ethers.BrowserProvider(eth) |
| RPC Provider | ethers.providers.JsonRpcProvider(url) | ethers.JsonRpcProvider(url) |
| formatEther | ethers.utils.formatEther(v) | ethers.formatEther(v) |
| parseEther | ethers.utils.parseEther(s) | ethers.parseEther(s) |
| BigNumber | ethers.BigNumber.from(x) | 原生 BigInt |
手写一个 signer(不依赖 ethers)
有些教学场景或轻量项目不想引入整个 ethers.js,直接靠 window.ethereum 也能拼出一个 signer:
async function myGetSigner() {
const accounts = await window.ethereum.request({
method: "eth_requestAccounts",
});
const address = accounts[0];
return {
address,
async getAddress() {
return address;
},
async signMessage(message) {
const hex = typeof message === "string" ? toHex(message) : message;
return window.ethereum.request({
method: "personal_sign",
params: [hex, address],
});
},
async sendTransaction(tx) {
const params = {
from: address,
to: tx.to,
value: tx.value ? "0x" + BigInt(tx.value).toString(16) : "0x0",
gas: tx.gas ? "0x" + BigInt(tx.gas).toString(16) : undefined,
data: tx.data || "0x",
};
return window.ethereum.request({
method: "eth_sendTransaction",
params: [params],
});
},
};
}
// 浏览器没有 Buffer,自己写一个字符串转 hex
function toHex(str) {
let hex = "";
for (const ch of str) {
hex += ch.charCodeAt(0).toString(16).padStart(2, "0");
}
return "0x" + hex;
}
常见踩坑
Buffer is not defined
从 Node 教程抄的 Buffer.from(...).toString('hex') 在浏览器不能用。用上面的 toHex 或 TextEncoder:
const hex = "0x" + Array.from(new TextEncoder().encode(str))
.map(b => b.toString(16).padStart(2, "0")).join("");
BigInt 序列化
v6 起金额和 nonce 都是原生 BigInt,直接 JSON.stringify 会崩:
TypeError: Do not know how to serialize a BigInt
要么手动转字符串,要么全局 patch:
BigInt.prototype.toJSON = function () {
return this.toString();
};
window.ethereum 是数组
装了多个钱包(MetaMask + Coinbase + OKX)后,window.ethereum 可能变成聚合对象,isMetaMask 判断失效。EIP-6963 出来后的做法:
window.addEventListener("eip6963:announceProvider", (event) => {
console.log(event.detail.info.name, event.detail.provider);
});
window.dispatchEvent(new Event("eip6963:requestProvider"));
一句话总结
ethers v6:new BrowserProvider(window.ethereum) + await provider.getSigner();不想引 ethers 时手写 signer 也不难,注意浏览器里没 Buffer 就行。
