5. Web3.js SDK 使用说明

chainmaker-web3-js 是专为长安链(ChainMaker)区块链网络设计的 JavaScript SDK,定位为 Web3 应用开发的核心工具库。它提供类似 ethers.js 的 API 体验,方便开发者在浏览器或前端环境中安全、便捷地与 ChainMaker 区块链进行交互。

5.1. 主要功能

  • 交易签名:支持 RSA/ECDSA/国密算法,目前所有链上操作均需签名认证,保障安全性。

  • 交易发送:可便捷发起链上交易、合约调用、合约部署等操作。

  • 交易订阅:支持监听链上区块事件,实现实时响应。

  • 链信息查询:获取链配置信息、区块详情、交易详情等。

  • 合约交互:提供合约部署(含 EVM 运行时)、调用、参数编码等完整能力。

  • ERC20 便捷转账:内置 invokeErc20Transfer,自动完成标准 transfer(address,uint256) 的 ABI 编码。

  • EIP-712 结构化签名:支持 signTypedData / verifyTypedData,可与链上验签合约配合完成签名校验。

  • 硬件钱包:内置 LedgerSignerTrezorSigner,支持 Ledger / Trezor 硬件设备签名,且可在设备屏幕展示可读的交易字段。

  • 多端支持:兼容浏览器环境,NodeJS 服务端环境,支持代理配置,适配多种前端场景。

5.2. 前置依赖

由于 chainmaker-web3-js 是面向长安链,因此对链会有一些要求,目前需要满足以下前置条件:

  1. ChainMaker 区块链网络,版本>=2.3.8,原因是 HTTP 代理及链订阅问题在该版本得到了修复,之前版本在使用上存在问题,如没开放跨域,WebSocket订阅。

  2. HTTP协议使用Web3JS,链需开启HTTP API服务, 在 chainmaker.yml 配置文件中开启 HTTP API 服务:

    # restful api gateway
    gateway:
    # enable restful api
    enabled: true
    

    参考文档见这里

  3. 如想使用 HTTPS 协议,需要自己使用 Nginx/Caddy 等反向代理工具进行 TLS 终端代理,本身链节点的 gRPC TLS 还是处于关闭状态。

5.3. 快速对比 (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

5.4. 使用

如果是npm方式使用,node推荐为v22及以上版本。这里无明确版本要求,但建议使用最新的LTS版本。

5.4.1. 介绍

  • web3.providers.Web3Provider

  • web3.Wallet

npm install @chainmaker/web3js

注意:同时支持CJS/ESM两种模块化规范。

5.4.2. API 使用

5.4.3. 获取链配置信息

// 创建钱包实例
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);

5.4.4. 获取交易信息

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);

5.4.5. 获取区块信息

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);

5.4.6. 部署合约

ContractFactory 的构造签名为 new web3.ContractFactory(abi, bytecode, provider),通过 deploy() 传入长安链特有的部署参数(合约名、版本、运行时类型等)。

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: {}
});

5.4.6.1. 部署 EVM 合约

长安链支持 EVM 运行时,将 runtimeType 指定为 'EVM' 即可部署 Solidity 编译产物:

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 编码,再通过 invokeContractdata 参数(去掉 0x 前缀)提交:

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
});

5.4.7. 调用合约

const contract = new web3.Contract(contractAddress, [], wallet);
const tx = await contract.someMethod(/* args */);

5.4.8. ERC20 便捷转账

invokeErc20Transfer 封装了标准 ERC20 transfer(address,uint256) 的 ABI 编码,无需手动构造 calldata。当签名器为硬件钱包(Ledger/Trezor)时,设备屏幕会展示 From / To / Amount / Max Fees / Network 等可读字段。

await provider.invokeErc20Transfer(
  'erc20_contract_name',
  {
    to: '0xRecipientAddress...',
    amount: '1000000000000000000', // 按合约精度传入的整数金额(字符串)
    // from 可选,默认取签名器地址
  },
  {
    // 以下字段主要用于硬件钱包设备端展示
    maxFees: '0.01',
    network: 'ChainMaker'
  }
);

5.4.9. 订阅

目前仅支持区块订阅。

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);

5.4.10. K1 曲线支持

当前ChainMaker K1曲线下的私钥是以hex字符串形式存储的,因此可以直接传入私钥字符串创建 Wallet 实例。

const wallet2 = new Wallet('62edf6740bc60d75763c9b980a9df2da2916b1f9c34149e5582ba566e98fbb64');

5.5. 证书 Cert 模式

证书模式下与 PK 相比,需要增加如下配置,组织 ID/证书文本内容/认证类型

const wallet = new web3.Wallet(privateKey, null, {
  certificate: '',
  orgId: 'wx-org1.chainmaker.org',
  authType: 'PermissionedWithCert'
});

5.6. 结合钱包插件使用

chainmaker-web3-js 支持两种使用方式:

5.6.1. 1. 直接使用 Wallet(内部签名器)

当直接使用 Wallet 传入私钥时,会自动处理签名逻辑:

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  // 内部签名器
  }
});

5.6.2. 2. 结合钱包插件使用(外部签名器)

如果想结合浏览器扩展钱包或其他钱包插件使用,可以通过实现一个符合 web3.Signer 接口的适配器类来实现。这与 ethers.js 的 Web3Provider 模式完全一致。

5.6.2.1. 快速示例

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({ /* ... */ });

5.7. 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 摘要(用于自定义签名/验签流程)。

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);修改任意字段都会改变签名结果,可配合链上验签合约完成防篡改校验。

5.8. 硬件钱包(Ledger / Trezor)

SDK 内置了针对长安链的 Ledger、Trezor 硬件钱包签名器,二者都实现了统一的 Signer 接口,可以像普通 Wallet 一样传给 provider。除普通交易签名外,还支持在设备屏幕上展示可读的 ERC20 转账字段。

5.8.1. Ledger

LedgerSigner 接收一个 @ledgerhq/hw-transport 的 transport 实例(由使用方根据环境自行创建,如 WebUSB / WebHID)。

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 发起交易 / 调用合约

5.8.2. Trezor

TrezorSigner 推荐使用静态工厂 createWebUsb() 创建(内部会初始化 WebUSB transport)。

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();

5.8.3. 硬件钱包 + ERC20 可读展示

配合 provider.invokeErc20Transfer,硬件钱包设备端会展示可读的转账信息(收款地址、金额、最大手续费、网络),便于用户在设备上核对后确认:

// provider 使用硬件钱包 signer
await provider.invokeErc20Transfer(
  'erc20_contract_name',
  { to: '0xRecipient...', amount: '1000000000000000000' },
  { maxFees: '0.01', network: 'ChainMaker' }
);

5.9. 认证模式说明

长安链支持三种认证模式,通过 ChainMakerAuthType 表示,Wallet 可通过静态工厂便捷创建对应模式的实例:

模式 说明 便捷创建
PermissionedWithKey 许可链,公钥(PK)身份 Wallet.createPermissionedWithKey(key, orgId)
PermissionedWithCert 许可链,证书身份 Wallet.createPermissionedWithCert(key, certificate, orgId)
Public 公开链身份 Wallet.createPublic(key)
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);

5.10. 请求代理

代理配置字段为 proxyHost。这里的代理,仅仅为了保证节点IP还配置为真实的,JS通过转发请求地址来访问链节点,使用上也可以直接配置代理地址,nodeIp指向代理地址,proxyHost不配置。

举几个例子

  1. 长安链节点为grpc,未开启HTTP,这里可以搭建一个gRPC-Web的代理,nodeIp还是指向链节点的grpc地址,代理配置为proxyHost,浏览器端即可正常发起请求,nodeIp还可以真实的指向链节点的地址。

  2. 长安链节点开启了HTTP服务,nodeIp指向HTTP地址,但本身网络无法直连,这时也可以通过配置proxyHost来走HTTP代理。

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'
  }
});