Web3.js开发指南

Web3.js开发指南

Web3.js是以太坊生态系统中最流行的JavaScript库之一,用于与以太坊节点交互。本章将介绍Web3.js的核心功能和实践应用。

环境配置

1. 安装Web3.js

1
2
3
4
5
# 使用npm安装
npm install web3

# 使用yarn安装
yarn add web3

2. 初始化Web3

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 使用MetaMask提供的provider
const web3 = new Web3(window.ethereum);

// 使用Infura
const web3 = new Web3(new Web3.providers.HttpProvider(
    'https://mainnet.infura.io/v3/YOUR-PROJECT-ID'
));

// 使用WebSocket
const web3 = new Web3(new Web3.providers.WebsocketProvider(
    'wss://mainnet.infura.io/ws/v3/YOUR-PROJECT-ID'
));

// 使用本地节点
const web3 = new Web3('http://localhost:8545');

基础功能

1. 账户操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 创建账户
const account = web3.eth.accounts.create();
console.log('地址:', account.address);
console.log('私钥:', account.privateKey);

// 导入私钥
const privateKey = '0x...';
const importedAccount = web3.eth.accounts.privateKeyToAccount(privateKey);

// 获取账户余额
async function getBalance(address) {
    const balance = await web3.eth.getBalance(address);
    return web3.utils.fromWei(balance, 'ether');
}

// 获取账户交易数量
async function getNonce(address) {
    return await web3.eth.getTransactionCount(address);
}

2. 交易处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 发送交易
async function sendTransaction(from, to, value) {
    const tx = {
        from: from,
        to: to,
        value: web3.utils.toWei(value, 'ether'),
        gas: 21000,
        gasPrice: await web3.eth.getGasPrice()
    };

    return await web3.eth.sendTransaction(tx);
}

// 签名交易
async function signTransaction(privateKey, to, value) {
    const tx = {
        to: to,
        value: web3.utils.toWei(value, 'ether'),
        gas: 21000,
        gasPrice: await web3.eth.getGasPrice(),
        nonce: await web3.eth.getTransactionCount(account.address)
    };

    const signedTx = await web3.eth.accounts.signTransaction(tx, privateKey);
    return await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
}

// 监听交易确认
function waitForConfirmation(txHash) {
    return new Promise((resolve, reject) => {
        web3.eth.getTransactionReceipt(txHash)
            .then(receipt => {
                if (receipt) {
                    resolve(receipt);
                } else {
                    setTimeout(() => waitForConfirmation(txHash), 1000);
                }
            })
            .catch(reject);
    });
}

智能合约交互

1. 合约部署

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 部署合约
async function deployContract(abi, bytecode, from, args = []) {
    const contract = new web3.eth.Contract(abi);

    const deploy = contract.deploy({
        data: bytecode,
        arguments: args
    });

    const gas = await deploy.estimateGas();

    const tx = await deploy.send({
        from: from,
        gas: gas
    });

    return tx.options.address;
}

// 部署示例
const MyContract = {
    abi: [...],
    bytecode: '0x...'
};

const contractAddress = await deployContract(
    MyContract.abi,
    MyContract.bytecode,
    account.address,
    ['Constructor Arg']
);

2. 合约方法调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 初始化合约实例
const contract = new web3.eth.Contract(abi, contractAddress);

// 调用view/pure函数
async function readContract() {
    const result = await contract.methods.getValue().call();
    console.log('Value:', result);
}

// 调用状态修改函数
async function writeContract(from) {
    const tx = await contract.methods.setValue(123).send({
        from: from,
        gas: await contract.methods.setValue(123).estimateGas()
    });
    console.log('Transaction:', tx.transactionHash);
}

// 批量调用
async function batchCall() {
    const batch = new web3.BatchRequest();

    const promises = [
        new Promise((resolve, reject) => {
            batch.add(contract.methods.getValue().call.request(null, (error, result) => {
                if (error) reject(error);
                else resolve(result);
            }));
        }),
        new Promise((resolve, reject) => {
            batch.add(contract.methods.getOwner().call.request(null, (error, result) => {
                if (error) reject(error);
                else resolve(result);
            }));
        })
    ];

    batch.execute();
    return Promise.all(promises);
}

3. 事件监听

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 监听所有事件
contract.events.allEvents()
    .on('data', event => console.log('Event:', event))
    .on('error', error => console.error('Error:', error));

// 监听特定事件
contract.events.Transfer({
    filter: {
        from: account.address
    },
    fromBlock: 'latest'
})
.on('data', event => {
    console.log('Transfer:', {
        from: event.returnValues.from,
        to: event.returnValues.to,
        value: event.returnValues.value
    });
})
.on('error', console.error);

// 获取历史事件
async function getPastEvents() {
    const events = await contract.getPastEvents('Transfer', {
        filter: {
            from: account.address
        },
        fromBlock: 0,
        toBlock: 'latest'
    });

    return events.map(event => ({
        from: event.returnValues.from,
        to: event.returnValues.to,
        value: event.returnValues.value,
        blockNumber: event.blockNumber
    }));
}

实用工具

1. 数据转换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Wei转换
const toWei = web3.utils.toWei('1', 'ether');
const fromWei = web3.utils.fromWei('1000000000000000000', 'ether');

// 十六进制转换
const toHex = web3.utils.toHex('123');
const fromHex = web3.utils.hexToNumber('0x7b');

// UTF8转换
const toUtf8 = web3.utils.toUtf8('Hello');
const fromUtf8 = web3.utils.hexToUtf8('0x48656c6c6f');

// 单位转换
const units = {
    wei: '1',
    kwei: '1000',
    mwei: '1000000',
    gwei: '1000000000',
    szabo: '1000000000000',
    finney: '1000000000000000',
    ether: '1000000000000000000'
};

2. 数据编码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ABI编码
const encoded = web3.eth.abi.encodeFunctionCall({
    name: 'transfer',
    type: 'function',
    inputs: [{
        type: 'address',
        name: 'to'
    }, {
        type: 'uint256',
        name: 'value'
    }]
}, ['0x123...', '1000000000000000000']);

// 参数编码
const params = web3.eth.abi.encodeParameters(
    ['string', 'uint256'],
    ['Hello', 123]
);

// 事件编码
const eventSignature = web3.eth.abi.encodeEventSignature(
    'Transfer(address,address,uint256)'
);

高级特性

1. 订阅

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 订阅新区块
const subscription = web3.eth.subscribe('newBlockHeaders')
    .on('data', block => {
        console.log('New block:', block.number);
    })
    .on('error', error => {
        console.error('Error:', error);
    });

// 订阅待定交易
web3.eth.subscribe('pendingTransactions')
    .on('data', txHash => {
        console.log('Pending transaction:', txHash);
    });

// 订阅日志
web3.eth.subscribe('logs', {
    address: contractAddress,
    topics: [web3.utils.sha3('Transfer(address,address,uint256)')]
})
.on('data', log => {
    console.log('Log:', log);
});

2. ENS集成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 解析ENS名称
async function resolveENS(name) {
    const address = await web3.eth.ens.getAddress(name);
    return address;
}

// 反向解析
async function reverseResolve(address) {
    const name = await web3.eth.ens.reverse(address).name();
    return name;
}

// 获取ENS属性
async function getENSAttributes(name) {
    const resolver = await web3.eth.ens.getResolver(name);
    const content = await resolver.methods.contenthash().call();
    const addr = await resolver.methods.addr().call();
    return { content, addr };
}

错误处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 错误处理示例
async function safeContractCall() {
    try {
        const result = await contract.methods.riskyFunction().call();
        return result;
    } catch (error) {
        if (error.message.includes('revert')) {
            console.error('Contract reverted:', error.reason);
        } else if (error.message.includes('gas')) {
            console.error('Out of gas');
        } else {
            console.error('Unknown error:', error);
        }
        throw error;
    }
}

// 交易错误处理
async function safeTransaction(tx) {
    try {
        const receipt = await tx.send({
            from: account.address,
            gas: await tx.estimateGas()
        });
        return receipt;
    } catch (error) {
        if (error.code === 4001) {
            console.error('Transaction rejected by user');
        } else if (error.message.includes('insufficient funds')) {
            console.error('Insufficient funds');
        } else {
            console.error('Transaction failed:', error);
        }
        throw error;
    }
}

练习题

  1. 实现一个完整的代币转账功能:
1
2
3
4
// 练习:实现ERC20代币转账
async function transferToken(tokenAddress, to, amount) {
    // 你的代码
}
  1. 创建一个事件监听器:
1
2
3
4
// 练习:实现事件监听
function setupEventListener(contract) {
    // 你的代码
}
  1. 实现批量查询余额:
1
2
3
4
// 练习:实现批量余额查询
async function getBatchBalances(addresses) {
    // 你的代码
}
  1. 创建一个交易确认检查器:
1
2
3
4
// 练习:实现交易确认检查
async function checkTransactionConfirmation(txHash) {
    // 你的代码
}
  1. 实现ENS域名解析:
1
2
3
4
// 练习:实现ENS解析
async function resolveENSName(name) {
    // 你的代码
}

参考资源

  1. Web3.js官方文档
  2. 以太坊JSON-RPC API
  3. Web3.js GitHub
  4. 以太坊开发工具
  5. ENS文档