Web3.js¶
Introduction¶
Interacting with blockchains typically requires an interface between your application and the network. Web3.js offers this interface through a comprehensive collection of libraries, facilitating seamless interaction with the nodes using HTTP or WebSocket protocols. This guide illustrates how to utilize Web3.js specifically for interactions with the Asset Hub chain.
Set Up the Project¶
To start working with Web3.js, begin by initializing your project:
Install Dependencies¶
Next, install the Web3.js library:
Set Up the Web3 Provider¶
The provider configuration is the foundation of any Web3.js application. The following example establishes a connection to the Asset Hub network. To use the example script, replace INSERT_RPC_URL
, INSERT_CHAIN_ID
, and INSERT_CHAIN_NAME
with the appropriate values. For example, for the Westend Asset Hub testnet, use these specific connection parameters:
const PROVIDER_RPC = {
rpc: 'https://westend-asset-hub-eth-rpc.polkadot.io',
chainId: 420420421,
name: 'westend-asset-hub'
};
The provider connection script should look something like this:
const { Web3 } = require('web3');
const createProvider = (rpcUrl) => {
const web3 = new Web3(rpcUrl);
return web3;
};
const PROVIDER_RPC = {
rpc: 'INSERT_RPC_URL',
chainId: 'INSERT_CHAIN_ID',
name: 'INSERT_CHAIN_NAME',
};
createProvider(PROVIDER_RPC.rpc);
For instance, to fetch the latest block number of the chain, you can use the following code snippet:
Complete script
const { Web3 } = require('web3');
const createProvider = (rpcUrl) => {
const web3 = new Web3(rpcUrl);
return web3;
};
const PROVIDER_RPC = {
rpc: 'https://westend-asset-hub-eth-rpc.polkadot.io',
chainId: 420420421,
name: 'westend-asset-hub',
};
const main = async () => {
try {
const web3 = createProvider(PROVIDER_RPC.rpc);
const latestBlock = await web3.eth.getBlockNumber();
console.log('Last block: ' + latestBlock);
} catch (error) {
console.error('Error connecting to Asset Hub: ' + error.message);
}
};
main();
Compile Contracts¶
Asset Hub requires contracts to be compiled to PolkaVM bytecode. This is achieved using the revive
compiler. Install the @parity/revive
library as a development dependency:
Here's a simple storage contract that you can use to follow the process:
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract Storage {
// Public state variable to store a number
uint256 public storedNumber;
/**
* Updates the stored number.
*
* The `public` modifier allows anyone to call this function.
*
* @param _newNumber - The new value to store.
*/
function setNumber(uint256 _newNumber) public {
storedNumber = _newNumber;
}
}
With that, you can now create a compile.js
snippet that transforms your solidity code into PolkaVM bytecode:
const { compile } = require('@parity/revive');
const { readFileSync, writeFileSync } = require('fs');
const { basename, join } = require('path');
const compileContract = async (solidityFilePath, outputDir) => {
try {
// Read the Solidity file
const source = readFileSync(solidityFilePath, 'utf8');
// Construct the input object for the compiler
const input = {
[basename(solidityFilePath)]: { content: source },
};
console.log(`Compiling contract: ${basename(solidityFilePath)}...`);
// Compile the contract
const out = await compile(input);
for (const contracts of Object.values(out.contracts)) {
for (const [name, contract] of Object.entries(contracts)) {
console.log(`Compiled contract: ${name}`);
// Write the ABI
const abiPath = join(outputDir, `${name}.json`);
writeFileSync(abiPath, JSON.stringify(contract.abi, null, 2));
console.log(`ABI saved to ${abiPath}`);
// Write the bytecode
const bytecodePath = join(outputDir, `${name}.polkavm`);
writeFileSync(
bytecodePath,
Buffer.from(contract.evm.bytecode.object, 'hex')
);
console.log(`Bytecode saved to ${bytecodePath}`);
}
}
} catch (error) {
console.error('Error compiling contracts:', error);
}
};
const solidityFilePath = './Storage.sol';
const outputDir = '.';
compileContract(solidityFilePath, outputDir);
After compilation, you'll have two key files: an ABI (.json
) file, which provides a JSON interface describing the contract's functions and how to interact with it, and a bytecode (.polkavm
) file, which contains the low-level machine code executable on PolkaVM that represents the compiled smart contract ready for blockchain deployment.
Contract Deployment¶
To deploy your compiled contract to Asset Hub using Web3.js, you'll need an account with a private key to sign the deployment transaction. The deployment process is exactly the same as for any EVM-compatible chain, involving creating a contract instance, estimating gas, and sending a deployment transaction. Here's how to deploy the contract, ensure replacing the INSERT_RPC_URL
, INSERT_PRIVATE_KEY
, and INSERT_CONTRACT_NAME
with the appropriate values:
import { readFileSync } from 'fs';
import { Web3 } from 'web3';
const getAbi = (contractName) => {
try {
return JSON.parse(readFileSync(`${contractName}.json`), 'utf8');
} catch (error) {
console.error(
`❌ Could not find ABI for contract ${contractName}:`,
error.message
);
throw error;
}
};
const getByteCode = (contractName) => {
try {
return `0x${readFileSync(`${contractName}.polkavm`).toString('hex')}`;
} catch (error) {
console.error(
`❌ Could not find bytecode for contract ${contractName}:`,
error.message
);
throw error;
}
};
export const deploy = async (config) => {
try {
// Initialize Web3 with RPC URL
const web3 = new Web3(config.rpcUrl);
// Prepare account
const account = web3.eth.accounts.privateKeyToAccount(config.privateKey);
web3.eth.accounts.wallet.add(account);
// Load abi
const abi = getAbi('Storage');
// Create contract instance
const contract = new web3.eth.Contract(abi);
// Prepare deployment
const deployTransaction = contract.deploy({
data: getByteCode('Storage'),
arguments: [], // Add constructor arguments if needed
});
// Estimate gas
const gasEstimate = await deployTransaction.estimateGas({
from: account.address,
});
// Get current gas price
const gasPrice = await web3.eth.getGasPrice();
// Send deployment transaction
const deployedContract = await deployTransaction.send({
from: account.address,
gas: gasEstimate,
gasPrice: gasPrice,
});
// Log and return contract details
console.log(`Contract deployed at: ${deployedContract.options.address}`);
return deployedContract;
} catch (error) {
console.error('Deployment failed:', error);
throw error;
}
};
// Example usage
const deploymentConfig = {
rpcUrl: 'INSERT_RPC_URL',
privateKey: 'INSERT_PRIVATE_KEY',
contractName: 'INSERT_CONTRACT_NAME',
};
deploy(deploymentConfig)
.then((contract) => console.log('Deployment successful'))
.catch((error) => console.error('Deployment error'));
Interact with the Contract¶
Once deployed, you can interact with your contract using Web3.js methods. Here's how to set a number and read it back, ensure replacing INSERT_RPC_URL
, INSERT_PRIVATE_KEY
, and INSERT_CONTRACT_ADDRESS
with the appropriate values:
import { readFileSync } from 'fs';
import { Web3 } from 'web3';
const getAbi = (contractName) => {
try {
return JSON.parse(readFileSync(`${contractName}.json`), 'utf8');
} catch (error) {
console.error(
`❌ Could not find ABI for contract ${contractName}:`,
error.message
);
throw error;
}
};
const updateStorage = async (config) => {
try {
// Initialize Web3 with RPC URL
const web3 = new Web3(config.rpcUrl);
// Prepare account
const account = web3.eth.accounts.privateKeyToAccount(config.privateKey);
web3.eth.accounts.wallet.add(account);
// Load abi
const abi = getAbi('Storage');
// Create contract instance
const contract = new web3.eth.Contract(abi, config.contractAddress);
// Get initial value
const initialValue = await contract.methods.storedNumber().call();
console.log('Current stored value:', initialValue);
// Prepare transaction
const updateTransaction = contract.methods.setNumber(1);
// Estimate gas
const gasEstimate = await updateTransaction.estimateGas({
from: account.address,
});
// Get current gas price
const gasPrice = await web3.eth.getGasPrice();
// Send update transaction
const receipt = await updateTransaction.send({
from: account.address,
gas: gasEstimate,
gasPrice: gasPrice,
});
// Log transaction details
console.log(`Transaction hash: ${receipt.transactionHash}`);
// Get updated value
const newValue = await contract.methods.storedNumber().call();
console.log('New stored value:', newValue);
return receipt;
} catch (error) {
console.error('Update failed:', error);
throw error;
}
};
// Example usage
const config = {
rpcUrl: 'INSERT_RPC_URL',
privateKey: 'INSERT_PRIVATE_KEY',
contractAddress: 'INSERT_CONTRACT_ADDRESS',
};
updateStorage(config)
.then((receipt) => console.log('Update successful'))
.catch((error) => console.error('Update error'));
Where to Go Next¶
Now that you’ve learned how to use Web3.js with Asset Hub, explore more advanced topics:
- Utilize Web3.js utilities – learn about additional Web3.js features such as signing transactions, managing wallets, and subscribing to events
- Build full-stack dApps – integrate Web3.js with different libraries and frameworks to build decentralized web applications
| Created: March 6, 2025