# Web3.js SDK 使用说明 > **chainmaker-web3-js** 是专为长安链(ChainMaker)区块链网络设计的 JavaScript SDK,定位为 Web3 应用开发的核心工具库。它提供类似 ethers.js 的 API 体验,方便开发者在浏览器或前端环境中安全、便捷地与 ChainMaker 区块链进行交互。 ## 主要功能 - **交易签名**:支持 RSA/ECDSA/国密算法,目前所有链上操作均需签名认证,保障安全性。 - **交易发送**:可便捷发起链上交易、合约调用、合约部署等操作。 - **交易订阅**:支持监听链上区块事件,实现实时响应。 - **链信息查询**:获取链配置信息、区块详情、交易详情等。 - **合约交互**:提供合约部署(含 EVM 运行时)、调用、参数编码等完整能力。 - **ERC20 便捷转账**:内置 `invokeErc20Transfer`,自动完成标准 `transfer(address,uint256)` 的 ABI 编码。 - **EIP-712 结构化签名**:支持 `signTypedData` / `verifyTypedData`,可与链上验签合约配合完成签名校验。 - **硬件钱包**:内置 `LedgerSigner`、`TrezorSigner`,支持 Ledger / Trezor 硬件设备签名,且可在设备屏幕展示可读的交易字段。 - **多端支持**:兼容浏览器环境,NodeJS 服务端环境,支持代理配置,适配多种前端场景。 ## 前置依赖 由于 chainmaker-web3-js 是面向长安链,因此对链会有一些要求,目前需要满足以下前置条件: 1. ChainMaker 区块链网络,版本`>=2.3.8`,原因是 HTTP 代理及链订阅问题在该版本得到了修复,之前版本在使用上存在问题,如没开放跨域,WebSocket订阅。 2. HTTP协议使用Web3JS,链需开启HTTP API服务, 在 chainmaker.yml 配置文件中`开启 HTTP API `服务: ```yml # restful api gateway gateway: # enable restful api enabled: true ``` 参考文档见[这里](https://docs.chainmaker.org.cn/v2.3.7/html/manage/%E9%95%BF%E5%AE%89%E9%93%BE%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86.html#chainmaker-yml) 3. 如想使用 HTTPS 协议,需要自己使用 Nginx/Caddy 等反向代理工具进行 TLS 终端代理,本身链节点的 gRPC TLS 还是处于关闭状态。 ## 快速对比 (ethers.js vs chainmaker.js) > 通过几个基本的用法,来方便用户了解 chainmaker-web3-js 与 ethers.js 的用法区别。 | 功能 | ethers.js 示例 | chainmaker-web3-js 示例 | 主要区别 | | -------------- | --------------------------------------- | ------------------------------------------------------------ | --------------------------------------- | | 获取链配置信息 | `provider.getNetwork()`
无需签名 | `provider.getNetwork()`
**需签名**,provider 需 signer/wallet | chainmaker 所有操作都需签名 | | 获取交易信息 | `provider.getTransaction(txHash)` | `provider.getTransaction(txHash)` | chainmaker 需签名,provider 配置 signer | | 获取区块信息 | `provider.getBlock(blockHash)` | `provider.getBlock(blockHash)` | chainmaker 需签名,provider 配置 signer | | 部署合约 | `factory.deploy()`
合约工厂需 wallet | `factory.deploy({ contractName, ... })`
需指定合约名、版本、运行时等 | chainmaker 需更多参数,所有操作需签名 | | 调用合约 | `contract.someMethod()` | `contract.someMethod()` | chainmaker 需签名,合约实例需 wallet | | ERC20 转账 | 手动编码或 `contract.transfer()` | `provider.invokeErc20Transfer(name, { to, amount })` | chainmaker 内置封装,硬件钱包可展示可读字段 | | 结构化签名 | `signer.signTypedData(domain, types, v)` | `wallet.signTypedData(domain, types, v)` / `verifyTypedData` | 用法一致,可配合链上验签合约 | | 硬件钱包 | `new LedgerSigner(...)` | `new web3.LedgerSigner({ transport })` / `web3.TrezorSigner` | 内置 Ledger/Trezor 的长安链 App 适配 | | gRPC 代理支持 | 不需要 | 需配置 `proxyHost`,浏览器端使用的话通过 gRPC-Web 代理,推荐走HTTP方式 | chainmaker 浏览器端使用配置代理,grpc支持,但首推HTTP | ## 使用 如果是npm方式使用,node推荐为v22及以上版本。这里无明确版本要求,但建议使用最新的LTS版本。 ### 介绍 - web3.providers.Web3Provider - web3.Wallet ```shell npm install @chainmaker/web3js ``` 注意:同时支持CJS/ESM两种模块化规范。 ### API 使用 ### 获取链配置信息 ```js // 创建钱包实例 const wallet = new web3.Wallet(privateKey); // 这种wallet叫内部signer,将会自动处理签名逻辑 // 使用带签名器的 provider const provider = web3.getDefaultProvider('36.110.223.69:11406', { chainmaker: { chainId: 'chainmaker_pk', protocol: 'http', tlsEnable: false, signer: wallet } }); // 获取网络信息 const network = await provider.getNetwork(); console.log('Chain ID:', network.chainId); console.log('链配置:', network.config); ``` ### 获取交易信息 ```typescript const provider = web3.getDefaultProvider('36.110.223.69:11406', { chainmaker: { chainId: 'chainmaker_pk', protocol: 'http', tlsEnable: false, signer: wallet } }); const transaction = await provider.getTransaction('0x1234567890abcdef...'); console.log(transaction); ``` ### 获取区块信息 ```typescript const provider = web3.getDefaultProvider('36.110.223.69:11406', { chainmaker: { chainId: 'chainmaker_pk', protocol: 'http', tlsEnable: false, signer: wallet } }); const block = await provider.getBlock('blockHash'); console.log(block); ``` ### 部署合约 `ContractFactory` 的构造签名为 `new web3.ContractFactory(abi, bytecode, provider)`,通过 `deploy()` 传入长安链特有的部署参数(合约名、版本、运行时类型等)。 ```typescript const provider = web3.getDefaultProvider('36.110.223.69:11406', { chainmaker: { chainId: 'chainmaker_pk', protocol: 'http', tlsEnable: false, signer: wallet } }); // abi 可传空数组;bytecode 为合约字节码(0x 前缀可选) const factory = new web3.ContractFactory([], bytecode, provider); const res = await factory.deploy({ contractName: 'fact_contract_name' + Date.now(), contractVersion: '1.0.0', runtimeType: 'DOCKER_GO', // 也支持 'EVM' 等运行时 params: {} }); ``` #### 部署 EVM 合约 长安链支持 EVM 运行时,将 `runtimeType` 指定为 `'EVM'` 即可部署 Solidity 编译产物: ```typescript const factory = new web3.ContractFactory([], '0x' + evmBytecodeHex, provider); const res = await factory.deploy({ contractName: 'my_evm_contract', contractVersion: '1.0.0', runtimeType: 'EVM', params: {} }); ``` 调用 EVM 合约方法时,使用 `Interface` 对入参做 ABI 编码,再通过 `invokeContract` 以 `data` 参数(去掉 `0x` 前缀)提交: ```typescript const iface = new web3.Interface(['function store(uint256 newValue, string note)']); const calldata = iface.encodeFunctionData('store', [42n, 'hello']); await provider.invokeContract('my_evm_contract', 'store', { data: calldata.slice(2) // 合约侧期望纯 hex }); ``` ### 调用合约 ```typescript const contract = new web3.Contract(contractAddress, [], wallet); const tx = await contract.someMethod(/* args */); ``` ### ERC20 便捷转账 `invokeErc20Transfer` 封装了标准 ERC20 `transfer(address,uint256)` 的 ABI 编码,无需手动构造 calldata。当签名器为硬件钱包(Ledger/Trezor)时,设备屏幕会展示 From / To / Amount / Max Fees / Network 等可读字段。 ```typescript await provider.invokeErc20Transfer( 'erc20_contract_name', { to: '0xRecipientAddress...', amount: '1000000000000000000', // 按合约精度传入的整数金额(字符串) // from 可选,默认取签名器地址 }, { // 以下字段主要用于硬件钱包设备端展示 maxFees: '0.01', network: 'ChainMaker' } ); ``` ### 订阅 > 目前仅支持区块订阅。 ```typescript async function subscribeBlock() { _currentBlockListener = (block) => { console.log(block); console.log(`新块通知: 块高=${block.getBlockHeight()}`); }; // 开始订阅 await provider.on( { event: 'block', fromBlock: Number(startBlock), toBlock: Number(endBlock), withRWSet: false, onlyHeader: true }, _currentBlockListener ); } // 如果需要关闭订阅,使用 provider.off('block', _currentBlockListener); ``` ### K1 曲线支持 > 当前ChainMaker K1曲线下的私钥是以hex字符串形式存储的,因此可以直接传入私钥字符串创建 Wallet 实例。 ```typescript const wallet2 = new Wallet('62edf6740bc60d75763c9b980a9df2da2916b1f9c34149e5582ba566e98fbb64'); ``` ## 证书 Cert 模式 > 证书模式下与 PK 相比,需要增加如下配置,组织 ID/证书文本内容/认证类型 ```javascript const wallet = new web3.Wallet(privateKey, null, { certificate: '', orgId: 'wx-org1.chainmaker.org', authType: 'PermissionedWithCert' }); ``` ## 结合钱包插件使用 chainmaker-web3-js 支持两种使用方式: ### 1. 直接使用 Wallet(内部签名器) 当直接使用 Wallet 传入私钥时,会自动处理签名逻辑: ```typescript const wallet = new web3.Wallet(privateKey); const provider = web3.getDefaultProvider('36.110.223.69:11406', { chainmaker: { chainId: 'chainmaker_pk', protocol: 'http', tlsEnable: false, signer: wallet // 内部签名器 } }); ``` ### 2. 结合钱包插件使用(外部签名器) 如果想结合浏览器扩展钱包或其他钱包插件使用,可以通过实现一个符合 `web3.Signer` 接口的适配器类来实现。这与 ethers.js 的 `Web3Provider` 模式完全一致。 #### 快速示例 ```typescript import { AbstractSigner } from '@chainmaker/web3js'; // 创建适配器类 class WalletPluginAdapter extends AbstractSigner { constructor(walletPlugin, provider) { super(provider); this.walletPlugin = walletPlugin; } async getAddress() { return await this.walletPlugin.getAddress(); } async signTransaction(tx) { const populatedTx = await this.populateTransaction(tx); return await this.walletPlugin.signTransaction(populatedTx); } async signMessage(message) { return await this.walletPlugin.signMessage(message); } async signTypedData(domain, types, value) { return await this.walletPlugin.signTypedData(domain, types, value); } connect(provider) { return new WalletPluginAdapter(this.walletPlugin, provider); } } // 使用适配器 const walletPlugin = window.chainmakerWallet; // 浏览器扩展注入的钱包对象 const provider = web3.getDefaultProvider('36.110.223.69:11406', { chainmaker: { chainId: 'chainmaker_pk', protocol: 'http', tlsEnable: false } }); const signer = new WalletPluginAdapter(walletPlugin, provider); // 像使用 ethers.js 一样使用 const address = await signer.getAddress(); const tx = await signer.sendTransaction({ /* ... */ }); ``` ## EIP-712 结构化数据签名与验签 SDK 完整实现了 EIP-712(typed data)签名标准,用法与 ethers.js 一致,可用于「链下签名、链上验证」的业务场景。 - `wallet.signTypedData(domain, types, value)`:生成符合 EIP-712 的 65 字节签名。 - `verifyTypedData(domain, types, value, signature)`:从签名恢复出签名者地址。 - `TypedDataEncoder.hash(domain, types, value)`:计算 EIP-712 摘要(用于自定义签名/验签流程)。 ```typescript import { Wallet, verifyTypedData, TypedDataEncoder } from '@chainmaker/web3js'; // 1. 定义 domain / types / message(由验签方或合约约定) const domain = { name: 'PaymentGateway', version: '1', chainId: 1, verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' }; const types = { PaymentInstruction: [ { name: 'uetr', type: 'bytes16' }, { name: 'fiatAmount', type: 'uint256' }, { name: 'currency', type: 'string' }, { name: 'nonce', type: 'uint256' } ] }; const message = { uetr: '0x00112233445566778899aabbccddeeff', fiatAmount: 100000n, currency: 'USD', nonce: 42n }; // 2. 签名 const wallet = new Wallet('0x' + privateKeyHex); const signature = await wallet.signTypedData(domain, types, message); // 3. 验签(链下恢复地址) const signer = verifyTypedData(domain, types, message, signature); console.log('签名者地址:', signer); // 4. 如需自定义流程,也可单独计算摘要 const digest = TypedDataEncoder.hash(domain, types, message); ``` > 签名对相同数据是确定性的(ECDSA + RFC6979);修改任意字段都会改变签名结果,可配合链上验签合约完成防篡改校验。 ## 硬件钱包(Ledger / Trezor) SDK 内置了针对长安链的 Ledger、Trezor 硬件钱包签名器,二者都实现了统一的 `Signer` 接口,可以像普通 `Wallet` 一样传给 provider。除普通交易签名外,还支持在设备屏幕上展示可读的 ERC20 转账字段。 ### Ledger `LedgerSigner` 接收一个 `@ledgerhq/hw-transport` 的 transport 实例(由使用方根据环境自行创建,如 WebUSB / WebHID)。 ```typescript import { LedgerSigner } from '@chainmaker/web3js'; // import TransportWebUSB from '@ledgerhq/hw-transport-webusb'; const transport = await TransportWebUSB.create(); const signer = new LedgerSigner({ transport, // path 可选,默认 "m/44'/60'/0'/0/0" // authConfig 可选,默认 PermissionedWithKey provider }); const address = await signer.getAddress(); // 之后即可像 Wallet 一样使用 signer 发起交易 / 调用合约 ``` ### Trezor `TrezorSigner` 推荐使用静态工厂 `createWebUsb()` 创建(内部会初始化 WebUSB transport)。 ```typescript import { TrezorSigner } from '@chainmaker/web3js'; const signer = await TrezorSigner.createWebUsb({ // path 可选,默认 "m/44'/60'/0'/0/0" // provider / authConfig 可选 provider, showOnTrezor: true }); const address = await signer.getAddress(); ``` ### 硬件钱包 + ERC20 可读展示 配合 `provider.invokeErc20Transfer`,硬件钱包设备端会展示可读的转账信息(收款地址、金额、最大手续费、网络),便于用户在设备上核对后确认: ```typescript // provider 使用硬件钱包 signer await provider.invokeErc20Transfer( 'erc20_contract_name', { to: '0xRecipient...', amount: '1000000000000000000' }, { maxFees: '0.01', network: 'ChainMaker' } ); ``` ## 认证模式说明 长安链支持三种认证模式,通过 `ChainMakerAuthType` 表示,Wallet 可通过静态工厂便捷创建对应模式的实例: | 模式 | 说明 | 便捷创建 | | ------------------------ | ------------------------ | --------------------------------------------------------- | | `PermissionedWithKey` | 许可链,公钥(PK)身份 | `Wallet.createPermissionedWithKey(key, orgId)` | | `PermissionedWithCert` | 许可链,证书身份 | `Wallet.createPermissionedWithCert(key, certificate, orgId)` | | `Public` | 公开链身份 | `Wallet.createPublic(key)` | ```typescript import { Wallet, ChainMakerAuthType } from '@chainmaker/web3js'; // PK 模式 const pkWallet = Wallet.createPermissionedWithKey(privateKey, 'wx-org1.chainmaker.org'); // 证书模式(等价于下文「证书 Cert 模式」的写法) const certWallet = Wallet.createPermissionedWithCert(privateKey, certificatePem, 'wx-org1.chainmaker.org'); // 公开链模式 const publicWallet = Wallet.createPublic(privateKey); ``` ## 请求代理 代理配置字段为 `proxyHost`。这里的代理,仅仅为了保证节点IP还配置为真实的,JS通过转发请求地址来访问链节点,使用上也可以直接配置代理地址,nodeIp指向代理地址,proxyHost不配置。 举几个例子 1. 长安链节点为grpc,未开启HTTP,这里可以搭建一个gRPC-Web的代理,nodeIp还是指向链节点的grpc地址,代理配置为proxyHost,浏览器端即可正常发起请求,nodeIp还可以真实的指向链节点的地址。 2. 长安链节点开启了HTTP服务,nodeIp指向HTTP地址,但本身网络无法直连,这时也可以通过配置proxyHost来走HTTP代理。 ```javascript const provider = web3.getDefaultProvider('36.110.223.69:11406', { // nodeIp: "36.110.223.23:12391", chainmaker: { chainId: 'chain1', protocol: 'grpc', tlsEnable: false, signer: wallet, proxyHost: 'https://127.0.0.1:9080' } }); ```