китаев.tech

Провайдер vs Сайнер — в чём разница

Чем Provider отличается от Signer в ethers.js: кто читает Ethereum, кто подписывает сообщения и кто отправляет транзакции.

Провайдер vs Сайнер

Provider читает Ethereum. Signer подписывает действия от имени аккаунта.

Если нужно узнать баланс, блок или состояние контракта — нужен provider. Если нужно отправить транзакцию или подписать сообщение — нужен signer.


Главное отличие

ОбъектЧто умеетПриватный ключ
Providerчитать сеть и отправлять RPC-запросыНе нужен
Signerподписывать сообщения и транзакцииНужен

Provider не доказывает личность пользователя. Он просто подключается к RPC-ноду.

Signer связан с аккаунтом. Он может создать цифровую подпись, которую Ethereum проверит перед изменением State.


Читаем через provider, подписываем через signer

typescript
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("https://ethereum.publicnode.com");

// Тестовый ключ из документации Hardhat. Не используйте его для реальных средств.
const wallet = new ethers.Wallet(
  "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
  provider,
);

const vitalikAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";

const blockNumber = await provider.getBlockNumber();
const vitalikBalance = await provider.getBalance(vitalikAddress);
const walletBalance = await provider.getBalance(wallet.address);

const message = "Я управляю этим адресом";
const signature = await wallet.signMessage(message);

const txRequest = {
  to: vitalikAddress,
  value: ethers.parseEther("0.001"),
  nonce: await provider.getTransactionCount(wallet.address),
  gasLimit: 21_000n,
  maxFeePerGas: ethers.parseUnits("20", "gwei"),
  maxPriorityFeePerGas: ethers.parseUnits("2", "gwei"),
  chainId: 1,
  type: 2,
};

const signedTx = await wallet.signTransaction(txRequest);

console.log("Latest block:", blockNumber);
console.log("Vitalik balance:", ethers.formatEther(vitalikBalance), "ETH");
console.log("Wallet address:", wallet.address);
console.log("Wallet balance:", ethers.formatEther(walletBalance), "ETH");
console.log("Message signature:", signature);
console.log("Signed transaction:", signedTx);

Этот код ничего не отправляет в сеть. Provider читает данные, а signer локально создаёт подпись сообщения и raw transaction.


Как это работает

new ethers.JsonRpcProvider() — создаёт подключение к Ethereum RPC.

provider.getBlockNumber() — читает номер последнего блока. Для этого не нужен приватный ключ.

new ethers.Wallet(privateKey, provider) — создаёт signer и подключает его к provider.

wallet.signMessage(message) — подписывает текст приватным ключом. Это не транзакция и gas не тратится.

wallet.signTransaction(txRequest) — подписывает транзакцию и возвращает raw transaction. State всё ещё не изменился.

Чтобы реально отправить подписанную транзакцию, нужен отдельный вызов provider.broadcastTransaction(signedTx).


Provider нужен даже signer

Signer умеет подписывать, но сам по себе не знает состояние сети.

Чтобы подготовить транзакцию, ему часто нужны данные из provider:

text
nonce текущего аккаунта
chainId сети
актуальная цена gas
баланс отправителя

Поэтому в ethers часто используют связку:

typescript
const provider = new ethers.JsonRpcProvider("https://ethereum.publicnode.com");
const signer = new ethers.Wallet(PRIVATE_KEY, provider);

Signer подписывает, provider доставляет запросы в сеть.


Read и write в контрактах

Один и тот же контракт ведёт себя по-разному в зависимости от третьего аргумента:

typescript
const readOnlyContract = new ethers.Contract(address, abi, provider);
const writableContract = new ethers.Contract(address, abi, signer);

readOnlyContract.balanceOf(user) сделает бесплатный eth_call.

writableContract.transfer(to, amount) создаст транзакцию, которую signer должен подписать и отправить.


Частые ошибки

Пытаться отправить транзакцию через provider → provider не владеет приватным ключом и не может подписать действие.

Использовать signer для простого чтения → для getBalance(), getBlockNumber() и view функций достаточно provider.

Путать подпись сообщения и транзакциюsignMessage() не меняет State и не стоит gas. Транзакция меняет State только после отправки и включения в блок.


Что дальше

Материалы китаev.tech публикуются в образовательных целях и не являются инвестиционной рекомендацией. Примеры кода и описания протоколов — для обучения; использование в продакшне на ваш собственный риск. Дисклеймер и политика конфиденциальности.