Skip to content

Commit 89941b4

Browse files
refactor: refactor ERC20 token deployment and interaction module
Added a new config.js configuration file, including network, token and gas configuration. Added deployer.js module for contract deployment, interactor.js module for contract interaction, errorHandler.js module for error handling, and eventListener.js module for event listening. Refactored index.js and used new modules to implement contract deployment, token transfer, balance query and event listening functions.
1 parent fc864f0 commit 89941b4

File tree

6 files changed

+341
-78
lines changed

6 files changed

+341
-78
lines changed

basic/03-web3js-erc20/config.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Network Configurations
2+
const networks = {
3+
development: {
4+
provider: `https://sepolia.infura.io/v3/${process.env.INFURA_ID}`,
5+
networkId: 11155111, // Sepolia network ID
6+
confirmations: 2,
7+
timeoutBlocks: 200
8+
},
9+
moonbase: {
10+
provider: 'https://rpc.testnet.moonbeam.network',
11+
networkId: 1287,
12+
confirmations: 2,
13+
timeoutBlocks: 200
14+
}
15+
};
16+
17+
// Token Configuration
18+
const tokenConfig = {
19+
name: 'DAPPLEARNING',
20+
symbol: 'DAPP',
21+
decimals: 18,
22+
initialSupply: 10000000
23+
};
24+
25+
// Gas Configuration
26+
const gasConfig = {
27+
deploy: 8000000,
28+
transfer: 60000
29+
};
30+
31+
module.exports = {
32+
networks,
33+
tokenConfig,
34+
gasConfig
35+
};

basic/03-web3js-erc20/index.js

Lines changed: 50 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,74 @@
11
const Web3 = require('web3');
2-
const fs = require('fs');
32
const contractFile = require('./compile');
3+
const config = require('./config');
4+
const ContractDeployer = require('./modules/deployer');
5+
const ContractInteractor = require('./modules/interactor');
6+
const EventListener = require('./modules/eventListener');
7+
const { ErrorHandler } = require('./modules/errorHandler');
48

59
require('dotenv').config();
6-
const privatekey = process.env.PRIVATE_KEY;
7-
/*
8-
-- Define Provider & Variables --
9-
*/
1010

11+
// Receiver address
1112
const receiver = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
1213

13-
// Provider
14+
// Create Web3 instance
1415
const web3 = new Web3(
15-
new Web3.providers.HttpProvider(
16-
'https://sepolia.infura.io/v3/' + process.env.INFURA_ID
17-
)
16+
new Web3.providers.HttpProvider(config.networks.development.provider)
1817
);
1918

20-
//account
21-
const account = web3.eth.accounts.privateKeyToAccount(privatekey);
19+
// Create account
20+
const account = web3.eth.accounts.privateKeyToAccount(process.env.PRIVATE_KEY);
2221
const account_from = {
2322
privateKey: account.privateKey,
2423
accountaddress: account.address,
2524
};
2625

27-
// sol ---> abi + bin
26+
// Get contract bytecode and ABI
2827
const bytecode = contractFile.evm.bytecode.object;
2928
const abi = contractFile.abi;
3029

31-
/*
32-
-- Deploy Contract --
33-
*/
34-
const Trans = async () => {
35-
console.log(
36-
`Attempting to deploy from account ${account_from.accountaddress}`
37-
);
38-
web3.eth.getBlockNumber(function (error, result) {
39-
console.log(result);
40-
});
41-
// Create deploy Contract Instance
42-
const deployContract = new web3.eth.Contract(abi);
43-
44-
// method 1
45-
// Create Constructor Tx
46-
const deployTx = deployContract.deploy({
47-
data: bytecode,
48-
arguments: ['DAPPLEARNING', 'DAPP', 0, 10000000],
49-
});
30+
const main = async () => {
31+
try {
32+
// 1. Deploy contract
33+
const deployer = new ContractDeployer(web3, account_from);
34+
const contract = await deployer.deploy(abi, bytecode);
35+
36+
// 2. Create contract interaction instance
37+
const interactor = new ContractInteractor(web3, contract);
38+
39+
// 3. Transfer tokens
40+
await interactor.transfer(account_from, receiver, 100000);
41+
42+
// 4. Query receiver balance
43+
await interactor.balanceOf(receiver);
44+
45+
// 5. Create WebSocket connection for event listening
46+
const web3Socket = new Web3(
47+
`wss://sepolia.infura.io/ws/v3/${process.env.INFURA_ID}`
48+
);
49+
const socketContract = new web3Socket.eth.Contract(abi, contract.options.address);
50+
51+
// 6. Setup event listener
52+
const eventListener = new EventListener(web3Socket, socketContract);
53+
await eventListener.subscribeToTransfers();
54+
55+
// Wait for a while to receive events
56+
console.log('Waiting for events...');
57+
await new Promise(resolve => setTimeout(resolve, 10000));
58+
59+
// 7. Get historical transfer events
60+
const deployBlock = await web3.eth.getBlockNumber();
61+
await eventListener.getPastTransfers(deployBlock);
62+
63+
// 8. Cleanup event subscriptions
64+
eventListener.unsubscribeAll();
5065

51-
// Sign Transacation and Send
52-
const deployTransaction = await web3.eth.accounts.signTransaction(
53-
{
54-
data: deployTx.encodeABI(),
55-
gas: '8000000',
56-
},
57-
account_from.privateKey
58-
);
59-
60-
// Send Tx and Wait for Receipt
61-
const deployReceipt = await web3.eth.sendSignedTransaction(
62-
deployTransaction.rawTransaction
63-
);
64-
console.log(`Contract deployed at address: ${deployReceipt.contractAddress}`);
65-
66-
const erc20Contract = new web3.eth.Contract(
67-
abi,
68-
deployReceipt.contractAddress
69-
);
70-
71-
//build the Tx
72-
const transferTx = erc20Contract.methods
73-
.transfer(receiver, 100000)
74-
.encodeABI();
75-
76-
// Sign Tx with PK
77-
const transferTransaction = await web3.eth.accounts.signTransaction(
78-
{
79-
to: deployReceipt.contractAddress,
80-
data: transferTx,
81-
gas: 8000000,
82-
},
83-
account_from.privateKey
84-
);
85-
86-
// Send Tx and Wait for Receipt
87-
await web3.eth.sendSignedTransaction(
88-
transferTransaction.rawTransaction
89-
);
90-
91-
await erc20Contract.methods
92-
.balanceOf(receiver)
93-
.call()
94-
.then((result) => {
95-
console.log(`The balance of receiver is ${result}`);
96-
});
66+
} catch (error) {
67+
await ErrorHandler.handle(error, web3);
68+
}
9769
};
9870

99-
Trans()
71+
main()
10072
.then(() => process.exit(0))
10173
.catch((error) => {
10274
console.error(error);
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const Web3 = require('web3');
2+
const config = require('../config');
3+
4+
class ContractDeployer {
5+
constructor(web3, account) {
6+
this.web3 = web3;
7+
this.account = account;
8+
}
9+
10+
async deploy(abi, bytecode, args) {
11+
try {
12+
console.log(`Deploying contract from account ${this.account.accountaddress}...`);
13+
14+
// create contract instance
15+
const deployContract = new this.web3.eth.Contract(abi);
16+
17+
// create deploy transaction
18+
const deployTx = deployContract.deploy({
19+
data: bytecode,
20+
arguments: args || [config.tokenConfig.name, config.tokenConfig.symbol, config.tokenConfig.decimals, config.tokenConfig.initialSupply]
21+
});
22+
23+
// Sign transaction
24+
const deployTransaction = await this.web3.eth.accounts.signTransaction(
25+
{
26+
data: deployTx.encodeABI(),
27+
gas: config.gasConfig.deploy
28+
},
29+
this.account.privateKey
30+
);
31+
32+
// Send transaction and wait for receipt
33+
const deployReceipt = await this.web3.eth.sendSignedTransaction(
34+
deployTransaction.rawTransaction
35+
);
36+
37+
console.log(`Contract deployed to address: ${deployReceipt.contractAddress}`);
38+
39+
// return contract instance
40+
return new this.web3.eth.Contract(abi, deployReceipt.contractAddress);
41+
} catch (error) {
42+
console.error('Contract deployment failed:', error);
43+
throw error;
44+
}
45+
}
46+
}
47+
48+
module.exports = ContractDeployer;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
class Web3Error extends Error {
2+
constructor(message, code, data) {
3+
super(message);
4+
this.name = 'Web3Error';
5+
this.code = code;
6+
this.data = data;
7+
}
8+
}
9+
10+
class TransactionError extends Error {
11+
constructor(message, txHash, receipt) {
12+
super(message);
13+
this.name = 'TransactionError';
14+
this.txHash = txHash;
15+
this.receipt = receipt;
16+
}
17+
}
18+
19+
class ContractError extends Error {
20+
constructor(message, contractAddress, method) {
21+
super(message);
22+
this.name = 'ContractError';
23+
this.contractAddress = contractAddress;
24+
this.method = method;
25+
}
26+
}
27+
28+
class ErrorHandler {
29+
static async handle(error, web3) {
30+
console.error('Error type:', error.name);
31+
console.error('Error message:', error.message);
32+
33+
if (error instanceof Web3Error) {
34+
console.error('Web3 error code:', error.code);
35+
console.error('Error data:', error.data);
36+
}
37+
38+
if (error instanceof TransactionError && web3) {
39+
try {
40+
if (error.txHash) {
41+
const receipt = await web3.eth.getTransactionReceipt(error.txHash);
42+
console.error('Transaction receipt:', receipt);
43+
}
44+
} catch (e) {
45+
console.error('Failed to get transaction receipt:', e.message);
46+
}
47+
}
48+
49+
if (error instanceof ContractError) {
50+
console.error('Contract address:', error.contractAddress);
51+
console.error('Method:', error.method);
52+
}
53+
54+
// await this.reportError(error);
55+
}
56+
57+
static isOutOfGas(error) {
58+
return error.message.includes('out of gas');
59+
}
60+
61+
static isRevert(error) {
62+
return error.message.includes('revert');
63+
}
64+
65+
static isNetworkError(error) {
66+
return error.message.includes('network') || error.message.includes('connection');
67+
}
68+
}
69+
70+
module.exports = {
71+
Web3Error,
72+
TransactionError,
73+
ContractError,
74+
ErrorHandler
75+
};
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
const Web3 = require('web3');
2+
3+
class EventListener {
4+
constructor(web3, contract) {
5+
this.web3 = web3;
6+
this.contract = contract;
7+
this.eventSubscriptions = new Map();
8+
}
9+
10+
async subscribeToTransfers(options = {}) {
11+
try {
12+
console.log('Starting to listen for Transfer events...');
13+
14+
const subscription = this.contract.events.Transfer(options)
15+
.on('data', (event) => {
16+
console.log('Transfer event detected:');
17+
console.log(' From:', event.returnValues.from);
18+
console.log(' To:', event.returnValues.to);
19+
console.log(' Amount:', event.returnValues.value);
20+
})
21+
.on('error', (error) => {
22+
console.error('Transfer event listening error:', error);
23+
});
24+
25+
this.eventSubscriptions.set('Transfer', subscription);
26+
return subscription;
27+
} catch (error) {
28+
console.error('Failed to setup Transfer event listener:', error);
29+
throw error;
30+
}
31+
}
32+
33+
async getPastTransfers(fromBlock, toBlock = 'latest') {
34+
try {
35+
console.log(`Getting historical Transfer events from block ${fromBlock} to ${toBlock}...`);
36+
37+
const events = await this.contract.getPastEvents('Transfer', {
38+
fromBlock,
39+
toBlock
40+
});
41+
42+
events.forEach(event => {
43+
console.log('Historical Transfer event:');
44+
console.log(' From:', event.returnValues.from);
45+
console.log(' To:', event.returnValues.to);
46+
console.log(' Amount:', event.returnValues.value);
47+
console.log(' Block number:', event.blockNumber);
48+
});
49+
50+
return events;
51+
} catch (error) {
52+
console.error('GET Historical Transfer event failed:', error);
53+
throw error;
54+
}
55+
}
56+
57+
unsubscribeAll() {
58+
try {
59+
console.log('Cancel ALL subscription ...');
60+
this.eventSubscriptions.forEach((subscription) => {
61+
subscription.unsubscribe();
62+
});
63+
this.eventSubscriptions.clear();
64+
} catch (error) {
65+
console.error('Cancel subscription failed:', error);
66+
throw error;
67+
}
68+
}
69+
}
70+
71+
module.exports = EventListener;

0 commit comments

Comments
 (0)