Ethereum обеспечивает работу децентрализованных приложений (dApps) с помощью smart contracts и программируемого blockchain. Web3.js предоставляет JavaScript-интерфейс для взаимодействия с узлами Ethereum. В этом руководстве рассматривается создание blockchain-приложений с точки зрения senior-разработчика.
Почему разработка под Ethereum
Ethereum предлагает уникальные возможности:
- Smart Contracts: самовыполняющийся код в blockchain
- Децентрализация: отсутствие единой точки отказа или контроля
- Прозрачность: все транзакции публично проверяемы
- Программируемые деньги: создание финансовых приложений без посредников
- NFT и токены: создание цифровых активов
Настройка среды разработки
Установка зависимостей
# Среда Node.js
npm init -y
npm install web3 ethers dotenv
Провайдеры инфраструктуры
Используйте провайдеров вроде Infura или Alchemy вместо запуска собственного узла:
// .env
INFURA_API_KEY=your_api_key_here
PRIVATE_KEY_1=your_private_key_here
PRIVATE_KEY_2=second_private_key_here
Локальная разработка с Ganache
Ganache предоставляет локальный blockchain Ethereum для тестирования:
# Установить Ganache CLI
npm install -g ganache
# Запустить локальный blockchain
ganache --port 8545
Основы Web3.js
Подключение к сети
import { Web3 } from 'web3';
import 'dotenv/config';
// Разработка vs. Production
const isProduction = false;
const web3 = new Web3({
provider: `https://${!isProduction ? 'sepolia' : 'mainnet'}.infura.io/v3/${process.env.INFURA_API_KEY}`,
config: {
defaultTransactionType: "0x2", // Транзакции EIP-1559
},
});
// Проверить подключение
const blockNumber = await web3.eth.getBlockNumber();
console.log('Current block:', blockNumber);
Управление аккаунтами
// Создать новый аккаунт
const newAccount = web3.eth.accounts.create();
console.log('Address:', newAccount.address);
console.log('Private Key:', newAccount.privateKey);
// Загрузить существующий аккаунт из private key
const account = web3.eth.accounts.privateKeyToAccount(process.env.PRIVATE_KEY_1);
console.log('Loaded address:', account.address);
// Получить баланс
const balance = await web3.eth.getBalance(account.address);
console.log('Balance:', web3.utils.fromWei(balance, 'ether'), 'ETH');
Выполнение транзакций
Отправка ETH
import { Web3 } from 'web3';
const isProduction = false;
const web3 = new Web3({
provider: `https://${!isProduction ? 'sepolia' : 'mainnet'}.infura.io/v3/${process.env.INFURA_API_KEY}`,
config: {
defaultTransactionType: "0x2",
},
});
// Инициализировать аккаунты
const sender = web3.eth.accounts.privateKeyToAccount(process.env.PRIVATE_KEY_1);
const receiver = web3.eth.accounts.privateKeyToAccount(process.env.PRIVATE_KEY_2);
async function sendTransaction() {
// Вывести ссылки на аккаунты для проверки
console.log(`Sender: https://${isProduction ? '' : 'sepolia.'}etherscan.io/address/${sender.address}`);
console.log(`Receiver: https://${isProduction ? '' : 'sepolia.'}etherscan.io/address/${receiver.address}`);
// Проверить баланс
const balance = await web3.eth.getBalance(sender.address);
console.log(`Sender balance: ${web3.utils.fromWei(balance, 'ether')} ETH`);
// Получить количество транзакций (nonce)
const txCount = await web3.eth.getTransactionCount(sender.address);
// Сформировать транзакцию
const txObject = {
nonce: web3.utils.toHex(txCount),
to: receiver.address,
value: web3.utils.toWei('0.001', 'ether'),
maxPriorityFeePerGas: 10_000,
maxFeePerGas: 10_000_000_000, // Максимально допустимая стоимость транзакции
};
// Подписать транзакцию
const signedTx = await sender.signTransaction(txObject);
// Отправить транзакцию
console.log(`TX: https://${isProduction ? '' : 'sepolia.'}etherscan.io/tx/${signedTx.transactionHash}`);
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
console.log('Transaction confirmed in block:', receipt.blockNumber);
// Проверить новый баланс
const newBalance = await web3.eth.getBalance(receiver.address);
console.log(`Receiver balance: ${web3.utils.fromWei(newBalance, 'ether')} ETH`);
}
sendTransaction();
Взаимодействие со smart contract
Компиляция smart contract
С использованием компилятора Solidity:
npm install solc
Простой контракт SimpleStorage.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleStorage {
uint256 private storedValue;
event ValueChanged(uint256 newValue);
function set(uint256 value) public {
storedValue = value;
emit ValueChanged(value);
}
function get() public view returns (uint256) {
return storedValue;
}
}
Деплой контракта
import { Web3 } from 'web3';
import fs from 'fs';
import solc from 'solc';
const web3 = new Web3('http://localhost:8545'); // Ganache
// Скомпилировать контракт
const source = fs.readFileSync('SimpleStorage.sol', 'utf8');
const input = {
language: 'Solidity',
sources: {
'SimpleStorage.sol': { content: source },
},
settings: {
outputSelection: {
'*': { '*': ['abi', 'evm.bytecode'] },
},
},
};
const compiled = JSON.parse(solc.compile(JSON.stringify(input)));
const contract = compiled.contracts['SimpleStorage.sol']['SimpleStorage'];
const abi = contract.abi;
const bytecode = contract.evm.bytecode.object;
async function deploy() {
const accounts = await web3.eth.getAccounts();
const SimpleStorage = new web3.eth.Contract(abi);
const deployed = await SimpleStorage
.deploy({ data: bytecode })
.send({
from: accounts[0],
gas: 1500000,
});
console.log('Contract deployed at:', deployed.options.address);
return deployed.options.address;
}
deploy();
Взаимодействие с контрактом
const contractAddress = '0x...'; // Адрес деплоя
const contract = new web3.eth.Contract(abi, contractAddress);
// Прочитать значение (бесплатно, без gas)
async function getValue() {
const value = await contract.methods.get().call();
console.log('Stored value:', value);
}
// Записать значение (требует gas)
async function setValue(newValue) {
const accounts = await web3.eth.getAccounts();
const receipt = await contract.methods
.set(newValue)
.send({
from: accounts[0],
gas: 100000,
});
console.log('Transaction hash:', receipt.transactionHash);
}
// Слушать events
contract.events.ValueChanged({
fromBlock: 'latest',
})
.on('data', (event) => {
console.log('Value changed to:', event.returnValues.newValue);
})
.on('error', console.error);
Использование альтернативы Ethers.js
Ethers.js — ещё одна популярная библиотека с более чистым API:
import { ethers } from 'ethers';
// Подключиться к провайдеру
const provider = new ethers.JsonRpcProvider(
`https://sepolia.infura.io/v3/${process.env.INFURA_API_KEY}`
);
// Создать кошелёк
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
// Отправить транзакцию
async function sendEther() {
const tx = await wallet.sendTransaction({
to: '0xRecipientAddress',
value: ethers.parseEther('0.01'),
});
console.log('TX Hash:', tx.hash);
const receipt = await tx.wait();
console.log('Confirmed in block:', receipt.blockNumber);
}
// Взаимодействие с контрактом
const contract = new ethers.Contract(contractAddress, abi, wallet);
await contract.set(42);
const value = await contract.get();
Взаимодействие с токеном ERC-20
const tokenABI = [
'function balanceOf(address owner) view returns (uint256)',
'function transfer(address to, uint256 amount) returns (bool)',
'function decimals() view returns (uint8)',
'function symbol() view returns (string)',
];
const tokenContract = new web3.eth.Contract(tokenABI, tokenAddress);
// Получить информацию о токене
const symbol = await tokenContract.methods.symbol().call();
const decimals = await tokenContract.methods.decimals().call();
const balance = await tokenContract.methods.balanceOf(walletAddress).call();
console.log(`Balance: ${balance / 10 ** decimals} ${symbol}`);
// Перевести токены
const amount = BigInt(100) * BigInt(10 ** decimals); // 100 токенов
await tokenContract.methods
.transfer(recipientAddress, amount.toString())
.send({ from: senderAddress, gas: 100000 });
Среда тестирования
Использование тестовой сети Sepolia
- Получите тестовый ETH из faucet: https://www.alchemy.com/faucets/ethereum-sepolia
- Переключитесь на testnet в вашем коде:
const isProduction = process.env.NODE_ENV === 'production';
const network = isProduction ? 'mainnet' : 'sepolia';
const provider = `https://${network}.infura.io/v3/${process.env.INFURA_API_KEY}`;
Hardhat для разработки
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npx hardhat init
Настройте hardhat.config.js:
require("@nomicfoundation/hardhat-toolbox");
module.exports = {
solidity: "0.8.19",
networks: {
sepolia: {
url: `https://sepolia.infura.io/v3/${process.env.INFURA_API_KEY}`,
accounts: [process.env.PRIVATE_KEY],
},
},
};
Запуск тестов:
npx hardhat test
npx hardhat run scripts/deploy.js --network sepolia
Полезные инструменты разработки
- Remix IDE: браузерный редактор Solidity — https://remix.ethereum.org
- Etherscan: block explorer — https://etherscan.io
- Ganache: локальный blockchain — https://github.com/trufflesuite/ganache-ui
- Infura: инфраструктура узлов — https://www.infura.io
- Alchemy: расширенный API — https://www.alchemy.com
Соображения по безопасности
- Никогда не раскрывайте private keys: используйте переменные окружения
- Валидируйте все входные данные: smart contracts нельзя изменить после деплоя
- Тщательно тестируйте: ошибки стоят реальных денег
- Используйте проверенные (audited) контракты: не изобретайте велосипед для токенов
- Понимайте gas: оптимизируйте, чтобы экономить деньги пользователей
- Обрабатывайте сбои: транзакции могут завершаться неудачей по многим причинам
Ключевые выводы
- Сначала используйте testnet: всегда тестируйте в Sepolia перед mainnet
- Понимайте gas: каждая операция стоит денег
- Events для обновления UI: слушайте events контракта для обновлений в реальном времени
- BigInt для сумм: в Ethereum используется 18 знаков после запятой, используйте BigInt
- Безопасное управление ключами: для production используйте аппаратные кошельки
- Проверяйте в Etherscan: публикуйте исходный код для прозрачности
Разработка под blockchain требует внимательного отношения к безопасности и тестированию — ошибки необратимы и потенциально дорого обходятся. Начинайте с testnet и небольших сумм, пока не будете уверены.