
浏览器插件钱包是Web3用户最常用的入口,MetaMask的成功证明了这一形态的价值。本文将带你从零构建一个支持多链的插件钱包,涵盖项目架构、密钥管理、Provider注入、交易签名与多链适配等核心环节,走通从开发到加载测试的完整流程。

插件钱包基于Chrome Extension Manifest V3构建,核心由三部分组成:
UI层(Popup):用户界面,负责账户展示、交易确认、网络切换。
Background Service Worker:后台逻辑,管理密钥、签名、网络请求。MV3中Service Worker会被休眠,需用chrome.storage持久化状态。
Content Script + Injected Provider:向网页注入window.ethereum对象,实现EIP-1193接口,让DApp能与钱包通信。
项目结构:
text
wallet-extension/ ├── manifest.json ├── src/ │ ├── background/ # service worker │ ├── popup/ # React UI │ ├── content/ # content script │ ├── inject/ # window.ethereum 注入 │ └── core/ # 密钥、签名、多链逻辑
manifest.json关键配置:
json
{
"manifest_version": 3,
"name": "MultiChain Wallet",
"permissions": ["storage", "activeTab"],
"background": { "service_worker": "background.js" },
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_start"
}],
"web_accessible_resources": [{
"resources": ["inject.js"],
"matches": ["<all_urls>"]
}]}使用ethers.js或@scure/bip39生成助记词并派生账户:
javascript
import { HDNodeWallet, Mnemonic } from "ethers";// 生成12位助记词const mnemonic = Mnemonic.fromEntropy(randomBytes(16));// 按BIP-44派生:m/44'/60'/0'/0/0const wallet = HDNodeWallet.fromMnemonic(mnemonic, "m/44'/60'/0'/0/0");助记词绝不明文存储。用PBKDF2或scrypt从用户密码派生密钥,AES-GCM加密后存入chrome.storage.local。解密仅在用户解锁后的会话内进行,且私钥应尽量缩短驻留内存的时间。
Content Script在页面上下文中注入window.ethereum。由于MV3的隔离世界限制,需通过DOM事件或window.postMessage在injected script与content script间通信。
Injected Provider实现核心接口:
javascript
window.ethereum = {
isMetaMask: false,
request: async ({ method, params }) => {
return new Promise((resolve, reject) => {
const id = Date.now();
const handler = (e) => {
if (e.data.id !== id) return;
window.removeEventListener("message", handler);
e.data.error ? reject(e.data.error) : resolve(e.data.result);
};
window.addEventListener("message", handler);
window.postMessage({ target: "wallet", id, method, params }, "*");
});
},
on: (event, cb) => { /* 监听 accountsChanged、chainChanged */ },};Content Script接收消息后转发给Background,Background完成签名或查询后回传结果。
当DApp调用eth_sendTransaction时,Background需:
解析交易参数,校验from是否为已授权账户;
估算Gas、获取nonce与链ID;
向Popup发送待确认请求,展示交易详情(收款地址、金额、Gas费);
用户确认后,用私钥签名并广播。
javascript
import { Transaction } from "ethers";const tx = Transaction.from({
to, value, data, nonce, gasLimit, maxFeePerGas, chainId,});const signed = await wallet.signTransaction(tx);await provider.broadcastTransaction(signed);关键安全点:签名前务必让用户核对地址与金额;对无限授权等高风险操作做二次确认;禁止静默签名。
多链支持的核心是网络配置与签名逻辑的抽象。
网络管理:维护链ID到RPC、浏览器、符号的映射:
javascript
const CHAINS = {
1: { name: "Ethereum", rpc: "...", symbol: "ETH" },
137: { name: "Polygon", rpc: "...", symbol: "MATIC" },
42161: { name: "Arbitrum", rpc: "...", symbol: "ETH" },
56: { name: "BSC", rpc: "...", symbol: "BNB" },};实现wallet_switchEthereumChain与wallet_addEthereumChain,响应DApp的网络切换请求,并派发chainChanged事件。
EVM兼容链可共用同一套签名逻辑,仅切换Provider与chainId。若要支持Solana等非EVM链,则需抽象出Signer接口,为不同链实现独立的派生路径与交易序列化逻辑。务实做法是先做好EVM多链,再按需扩展。
账户派生:不同链可能使用不同派生路径(如Solana用m/44'/501'/0'/0'),应在账户层记录每条链对应的派生索引与地址。
本地构建后,在Chrome打开chrome://extensions,开启开发者模式,选择"加载已解压的扩展程序",指向dist/目录。测试要点:
与Uniswap等DApp连接,验证eth_requestAccounts;
发起交易,确认弹窗展示与签名广播;
切换网络,验证chainChanged事件触发;
锁定/解锁钱包,确认状态持久化正确。
建议先在Sepolia等测试网全流程验证,再考虑主网。
专注WEB3开发、区块链技术落地、数字钱包与交易所定制开发,深耕区块链底层技术与Web3生态构建,提供公链/联盟链部署、智能合约开发、多链钱包搭建、中心化/去中心化交易所定制等一站式技术解决方案

