
Ethers
- 5 installs
- 4 repo stars
- Updated February 25, 2026
- hairyf/blockchain-master
Interact with Ethereum using ethers.js v6: Provider/Signer/Contract separation, units, ABI, contract calls and events, and signing.
About
A reference for ethers.js v6 covering the Provider/Signer/Contract model, units/formatting, ABI, contract calls/events, and v5-to-v6 migration. A developer uses it when building dapps, wallets, or scripts against Ethereum.
- Read/write separation via Provider, Signer, and Contract
- Units, ABI, events, message signing, and v5 to v6 migration notes
Ethers by the numbers
- 5 all-time installs (skills.sh)
- Ranked #338 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
- Data as of Jul 13, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hairyf/blockchain-master --skill ethersAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5 |
|---|---|
| repo stars | ★ 4 |
| Last updated | February 25, 2026 |
| Repository | hairyf/blockchain-master ↗ |
What it does
Interact with Ethereum using ethers.js v6: Provider/Signer/Contract separation, units, ABI, contract calls and events, and signing.
Files
Skill based on ethers.js v6.16.0, generated at 2026-02-09. Official docs: https://docs.ethers.org/v6/
Ethers.js is a complete, compact library for interacting with Ethereum: dapps, wallets, and scripts. This skill covers Provider/Signer/Contract separation, units and formatting, ABI, contract calls and events, message signing, and v5→v6 migration.
Core References
| Topic | Description | Reference |
|---|---|---|
| Provider, Signer, Contract | Read/write separation, BrowserProvider, JsonRpcProvider, connecting | core-provider-signer |
| Units and Formatting | parseEther, parseUnits, formatEther, formatUnits | core-units-format |
| ABI | Human-readable ABI, Fragment, encoding, event topics/data | core-abi |
| Wallet | Wallet, HDNodeWallet, mnemonic, id() for testing | core-wallet |
| Transactions and Receipts | sendTransaction, wait, getBlockNumber, getFeeData, getTransactionCount | core-transactions |
| Data and hex utilities | getBytes, toBeHex, ZeroAddress, ZeroHash, encodeBytes32String, solidityPacked | core-utils |
| Address | getAddress, isAddress, isAddressable, checksum | core-address |
| Hashing | keccak256, id(string), use in selectors and testing | core-hashing |
Features
Contract and Signing
| Topic | Description | Reference |
|---|---|---|
| Contract | Creation, view/pure, state-changing, staticCall, events, queryFilter | features-contract |
| Signing | signMessage, verifyMessage, Signature, EIP-191, EIP-712 | features-signing |
| ENS | resolveName, getResolver, getAddress, getText, text records | features-ens |
| React Native | react-native-quick-crypto registration for performance | features-react-native |
| Provider options | getDefaultProvider, JsonRpcProvider staticNetwork, broadcastTransaction | features-providers |
| Contract deployment | ContractFactory, bytecode, deploy(), waitForDeployment, fromSolidity | features-contract-deploy |
| EIP-712 typed data | signTypedData, verifyTypedData, TypedDataEncoder | features-eip712 |
Best Practices
| Topic | Description | Reference |
|---|---|---|
| Error handling | Reverted tx, receipt.status, CALL_EXCEPTION, provider errors | best-practices-errors |
Advanced
| Topic | Description | Reference |
|---|---|---|
| Migrating v5→v6 | BigInt, Provider/Contract/Signature, utils renames | advanced-migrating-v6 |
Generation Info
- Source:
sources/ethers - Git SHA:
98c49d091eb84a9146dfba8476f18e4c3e3d1d31 - Generated: 2026-02-09
- Ethers version: 6.16.0
- Docs path: docs.wrm/
- More (3 passes): 2026-02-25 — 11 new reference files added (see SKILL.md).
Migrating from v5 to v6
High-level changes agents need when updating or writing v6 code.
BigInt instead of BigNumber
// v5
value = BigNumber.from("1000");
sum = value1.add(value2);
// v6
value = 1000n;
sum = value1 + value2;Provider / import
Web3Provider→BrowserProvider(EIP-1193).provider.sendTransaction→provider.broadcastTransaction.- All exports from root:
import { JsonRpcProvider } from "ethers"orimport { JsonRpcProvider } from "ethers/providers".
Contract
- Contract is ES6 Proxy; method names resolved at runtime.
- v5:
contract.callStatic.foo(addr),contract.estimateGas.foo(addr). - v6:
contract.foo.staticCall(addr),contract.foo.estimateGas(addr),contract.foo.populateTransaction(addr). - Ambiguous overloads: use Typed API or full signature, e.g.
contract.foo(Typed.address(addr)).
Fee data
- v5:
getGasPrice(),lastBaseFeePerGasin fee data. - v6: Use
(await provider.getFeeData()).gasPricefor legacy;maxFeePerGasfor EIP-1559 (auto-calculated). NolastBaseFeePerGas; useblock.baseFeePerGasif needed.
Signature and transaction
- Signature is a class:
Signature.from(sigBytes);sig.serializedfor bytes. - Transaction:
Transaction.from(txBytes);tx.serialized.
Utils renames
formatBytes32String/parseBytes32String→encodeBytes32String/decodeBytes32String.constants.AddressZero/HashZero→ZeroAddress/ZeroHash.hexDataSlice→dataSlice;hexZeroPad→zeroPadValue.hexlify(35)→toBeHex(35);arrayify→getBytes;hexValue→toQuantity.solidityPack→solidityPacked;solidityKeccak256→solidityPackedKeccak256.AbiCoder.defaultAbiCoder→AbiCoder.defaultAbiCoder().
Key Points
- Use native BigInt and v6 Provider/Contract/Signature APIs in new code.
- Static network:
new JsonRpcProvider(url, network, { staticNetwork: network })to skip chainId lookup.
<!-- Source references:
- sources/ethers/docs.wrm/migrating.wrm
- https://docs.ethers.org/v6/
-->
Error Handling
Transactions can revert; RPC and contract calls can fail. Check receipt status and catch structured errors for robust handling.
Reverted transactions
A reverted transaction is still included and pays gas. Check the receipt:
const tx = await signer.sendTransaction({ ... });
const receipt = await tx.wait();
if (receipt.status === 0) {
// Transaction reverted
}
// receipt.status === 1 means successContract and call failures
When a state-changing method is executed via a Signer, a revert on-chain results in the promise rejecting. Simulated calls (staticCall) also reject on revert:
try {
await contract.transfer.staticCall(to, amount);
} catch (err) {
// err.code, err.data (revert data), err.shortMessage
if (err.code === "CALL_EXCEPTION") {
// Decode revert reason if ABI includes custom errors
}
}Provider errors
Network and RPC errors (e.g. timeout, invalid response) surface as thrown errors. Use try/catch around sendTransaction, wait(), and provider calls; handle rate limits and chain switches (e.g. re-connect after user switches network).
Key Points
- receipt.status: 1 = success, 0 = reverted.
- Catch errors from sendTransaction, wait(), and contract methods; inspect code and data for CALL_EXCEPTION.
- v6 replaced the Logger class with error utility functions; see API docs for error types and helpers.
<!-- Source references:
- sources/ethers/docs.wrm/migrating.wrm (about-errors)
- sources/ethers/docs.wrm/getting-started.wrm
- https://docs.ethers.org/v6/
-->
Application Binary Interface (ABI)
ABI describes how to encode/decode calls and events. Include only the methods/events you need as Fragments.
Human-readable ABI
const abi = [
"function decimals() view returns (uint8)",
"function symbol() view returns (string)",
"function balanceOf(address a) view returns (uint)",
"function transfer(address to, uint amount)",
"event Transfer(address indexed from, address indexed to, uint amount)"
];
const contract = new ethers.Contract(address, abi, provider);Call data
- First 4 bytes: method selector (keccak256 of normalized signature).
- Params encoded and concatenated; each component padded to 32 bytes. Length ≡ 4 (mod 32).
- Revert: first 4 bytes = error selector; length ≡ 4 (mod 32).
Events
- First topic: topic hash (keccak256 of event signature). Indexed params go in topics; non-indexed in data.
- Use indexed params for filtering; data is cheaper but not filterable.
Key Points
- Use human-readable ABI (Solidity-style signatures) when typing by hand.
- Omit unneeded methods/events from the ABI.
- Contract is a meta-class: methods are derived at runtime from the ABI.
<!-- Source references:
- sources/ethers/docs.wrm/basics/abi.wrm
- sources/ethers/docs.wrm/getting-started.wrm
- https://docs.ethers.org/v6/
-->
Address Utilities
Normalize and validate Ethereum addresses. Use getAddress for checksummed form; isAddress to validate before sending or storing.
Normalize and validate
import { getAddress, isAddress, isAddressable } from "ethers";
// Normalize to EIP-55 checksum; throws if invalid
const checksummed = getAddress("0x8ba1f109551bd432803012645ac136ddd64dba72");
// Validate without resolving ENS (ENS names return false)
if (isAddress(input)) {
const addr = getAddress(input);
}
// Check if value has .address (Wallet, Contract, etc.)
if (isAddressable(signer)) {
const addr = await signer.address; // or getAddress(await signer.getAddress())
}Checksum rules
- getAddress returns EIP-55 checksummed form; accepts lowercase or mixed case.
- If input has mixed case, checksum is validated and an error is thrown on mismatch.
- To bypass checksum validation (rare), pass
.toLowerCase()first.
Key Points
- Always use getAddress() when storing or comparing addresses to get consistent checksum.
- isAddress() returns false for ENS names; resolve names with provider.resolveName() first.
- isAddressable(value) is true for Wallet, Contract, and any object with an address-like property.
<!-- Source references:
- sources/ethers/lib.commonjs/address (getAddress, isAddress, isAddressable)
- https://docs.ethers.org/v6/
-->
Hashing
Ethers exposes common hashes used in Ethereum: keccak256 for selectors and data, and a convenience for hashing strings (e.g. for testing).
keccak256
Standard 32-byte keccak256 hash; input can be hex string or bytes:
import { keccak256 } from "ethers";
const hash = keccak256("0x1234");
const selector = keccak256(ethers.toUtf8Bytes("transfer(address,uint256)")).slice(0, 10); // first 4 bytesid (string to hash)
Hashes a UTF-8 string with keccak256. Useful for deterministic test keys or labels:
import { id } from "ethers";
const hash = id("hello"); // keccak256 of UTF-8 "hello"
const testKey = id("test"); // common in docs for Wallet(ethers.id("test"), provider)Other hashes
sha256, sha512, ripemd160, computeHmac, pbkdf2 are available from the crypto layer; used internally and for React Native overrides. For most agent use cases, keccak256 and id() are the main ones.
Key Points
- keccak256(data) — data as BytesLike; returns 0x-prefixed 32-byte hex.
- id(text) — keccak256(UTF-8 string); handy for testing and stable identifiers.
- Method/error selectors are the first 4 bytes of keccak256(normalizedSignature).
<!-- Source references:
- sources/ethers/lib.commonjs/crypto/keccak.d.ts
- sources/ethers/lib.commonjs/hash/id.d.ts
- https://docs.ethers.org/v6/
-->
Provider, Signer, and Contract
Ethers separates read-only (Provider) from write operations (Signer). Use Provider to query state; use Signer when you need to send transactions or sign.
Terminology
- Provider: Read-only connection to the blockchain (query state, logs, call view methods).
- Signer: Wraps an account; can sign transactions and messages. Private key may be in memory (Wallet) or behind IPC (e.g. MetaMask).
- Contract: Deployed program on-chain. Read-only when connected to a Provider; state-changing when connected to a Signer.
Connecting
Browser (EIP-1193 / MetaMask):
import { ethers } from "ethers";
let provider, signer;
if (window.ethereum == null) {
provider = ethers.getDefaultProvider();
} else {
provider = new ethers.BrowserProvider(window.ethereum);
signer = await provider.getSigner();
}Custom RPC (node or Hardhat/Ganache):
provider = new ethers.JsonRpcProvider(url); // no url => localhost:8545
signer = await provider.getSigner();Key Points
- All write operations go through a Signer; Provider is read-only.
- Use
BrowserProvider(window.ethereum)for injected wallets (EIP-1193). - Use
JsonRpcProvider(url)for your own node or dev chain;getSigner()gives write access when accounts are available.
<!-- Source references:
- sources/ethers/docs.wrm/getting-started.wrm
- https://docs.ethers.org/v6/
-->
Transactions and Receipts
State changes require a transaction. Send via a Signer; wait for inclusion to get a receipt. Use Provider to read block and fee data.
Sending and waiting
const tx = await signer.sendTransaction({
to: "ethers.eth",
value: parseEther("1.0")
});
// Transaction is in mempool; not yet included
const receipt = await tx.wait();
// receipt.blockNumber, receipt.gasUsed, receipt.status (1 success, 0 reverted), receipt.logsQuerying state (Provider)
const blockNumber = await provider.getBlockNumber();
const balance = await provider.getBalance(addressOrEns);
const nonce = await provider.getTransactionCount(addressOrEns);
const feeData = await provider.getFeeData();
// feeData.gasPrice (legacy), feeData.maxFeePerGas, feeData.maxPriorityFeePerGasBlock and fee data
For EIP-1559, use getFeeData(); maxFeePerGas is computed from base fee + priority. For legacy chains use feeData.gasPrice. Base fee can be read from the latest block if needed: (await provider.getBlock("latest")).baseFeePerGas.
Key Points
- signer.sendTransaction(txRequest) returns a TransactionResponse; tx.wait() returns TransactionReceipt.
- Receipt.status === 1 means success; 0 means reverted (fee still paid).
- getTransactionCount returns the next nonce for an address; use when sending multiple txs.
<!-- Source references:
- sources/ethers/docs.wrm/getting-started.wrm
- sources/ethers/docs.wrm/migrating.wrm
- https://docs.ethers.org/v6/
-->
Units and Formatting
Ethereum uses integer wei internally. Use parse helpers for user input and format helpers for display.
Parsing (string → wei)
import { parseEther, parseUnits } from "ethers";
const eth = parseEther("1.0"); // 10^18 wei
const feePerGas = parseUnits("4.5", "gwei"); // gwei → weiFormatting (wei → string)
import { formatEther, formatUnits } from "ethers";
formatEther(eth); // wei → ether string
formatUnits(feePerGas, "gwei"); // wei → gwei string
formatUnits(balance, decimals); // token balance with token decimalsKey Points
- Use parse* when accepting user input (e.g. "2.56" ether).
- Use format* when displaying to users; avoid showing raw wei.
- One ether = 10^18 wei; one gwei = 10^9 wei.
<!-- Source references:
- sources/ethers/docs.wrm/getting-started.wrm
- https://docs.ethers.org/v6/
-->
Data and Hex Utilities
Ethers provides helpers for hex strings, bytes, constants, and Solidity-packed encoding. Use them when building calldata, comparing addresses, or encoding fixed-size values.
Hex and bytes
import { getBytes, toBeHex, dataSlice, zeroPadValue, toQuantity } from "ethers";
const bytes = getBytes("0x1234"); // Uint8Array from hex string
const hex = toBeHex(35); // "0x23" (quantity format)
const slice = dataSlice(hexValue, 0, 4); // first 4 bytes
const padded = zeroPadValue(value, 32); // left-pad to 32 bytesConstants
import { ZeroAddress, ZeroHash } from "ethers";
// EIP-55 checksummed "0x0000...0000" and "0x0000...0000" hash
if (addr === ethers.ZeroAddress) { }
if (txHash === ethers.ZeroHash) { }Bytes32 string encoding
Used by some contracts for fixed-length string slots:
import { encodeBytes32String, decodeBytes32String } from "ethers";
const bytes32 = encodeBytes32String("hello");
const text = decodeBytes32String(bytes32);Solidity packed encoding
Non-ABI packed encoding and hashing (e.g. for signatures or custom structs):
import { solidityPacked, solidityPackedKeccak256, solidityPackedSha256 } from "ethers";
const packed = solidityPacked(["address", "uint256"], [addr, amount]);
const hash = solidityPackedKeccak256(["string", "uint8"], ["foo", 1]);Key Points
- getBytes / toBeHex / dataSlice / zeroPadValue replace v5 arrayify, hexlify, hexDataSlice, hexZeroPad.
- ZeroAddress and ZeroHash replace ethers.constants.AddressZero and HashZero.
- Use encodeBytes32String / decodeBytes32String for 32-byte string slots; solidityPacked* for custom packed hashes.
<!-- Source references:
- sources/ethers/docs.wrm/migrating.wrm
- https://docs.ethers.org/v6/
-->
Contract Interaction
Contract is created with address, ABI, and Provider or Signer. Connect to Provider for read-only; to Signer for state-changing calls.
Create and read-only (view/pure)
const contract = new ethers.Contract("dai.tokens.ethers.eth", abi, provider);
const sym = await contract.symbol();
const balance = await contract.balanceOf("ethers.eth");State-changing (requires Signer)
const contract = new ethers.Contract("dai.tokens.ethers.eth", abi, signer);
const tx = await contract.transfer("ethers.eth", parseUnits("1.0", 18));
await tx.wait();Static call (simulate without sending)
await contract.transfer.staticCall("ethers.eth", amount);
contract.foo.estimateGas(addr);
contract.foo.populateTransaction(addr);Events
contract.on("Transfer", (from, to, amount, event) => {
console.log(formatEther(amount));
event.removeListener();
});
contract.on(contract.filters.Transfer(null, "ethers.eth"), (from, to, amount, event) => { });
const events = await contract.queryFilter(contract.filters.Transfer, -100);Key Points
- Provider → read-only; Signer → can send transactions.
- Use
.staticCall()to simulate;.estimateGas()for gas;.populateTransaction()for unsigned tx. - Event listener receives (...params, event); event has
removeListener(). UsequeryFilterfor historic logs.
<!-- Source references:
- sources/ethers/docs.wrm/getting-started.wrm
- https://docs.ethers.org/v6/
-->
EIP-712 Typed Data Signing
Structured typed data (EIP-712) is used for permit, meta-transactions, and domain-separated signatures. Signers expose signTypedData(domain, types, value); use TypedDataEncoder for hashing or encoding without a signer.
Sign and verify
import { ethers } from "ethers";
const domain = {
name: "MyApp",
version: "1",
chainId: (await provider.getNetwork()).chainId,
verifyingContract: contractAddress
};
const types = {
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" }
]
};
const value = { owner, spender, value: amount, nonce, deadline };
const sig = await signer.signTypedData(domain, types, value);
const recovered = ethers.verifyTypedData(domain, types, value, sig); // addressHash without signing
For on-chain verification or custom flows, compute the EIP-712 hash:
import { TypedDataEncoder } from "ethers";
const hash = TypedDataEncoder.hash(domain, types, value);
// Or domain hash only: TypedDataEncoder.hashDomain(domain)
// Or struct hash: TypedDataEncoder.hashStruct("Permit", types, value)Resolve ENS in typed data
If domain or value contain ENS names, resolve them before signing (e.g. with provider):
const resolved = await TypedDataEncoder.resolveNames(domain, types, value, (name) => provider.resolveName(name));
// Then sign with resolved.domain, types, resolved.valueKey Points
- signTypedData(domain, types, value) on Signer; verifyTypedData(domain, types, value, sig) returns recovered address.
- domain: name, version, chainId, verifyingContract, salt (optional).
- TypedDataEncoder.hash / hashDomain / hashStruct for hashing; resolveNames for ENS in domain/value.
<!-- Source references:
- sources/ethers/docs.wrm/cookbook/signing.wrm (EIP-712 coming soon)
- sources/ethers/lib.commonjs/hash/typed-data.d.ts
- sources/ethers/lib.commonjs/wallet/base-wallet.d.ts
- https://docs.ethers.org/v6/
-->
Provider Options and Default Provider
Beyond BrowserProvider and JsonRpcProvider, ethers offers a default provider that aggregates multiple backends, and JsonRpcProvider options for static networks and broadcasting.
Default provider
When no injected wallet is available (e.g. MetaMask not installed), use the default provider for read-only access. It uses multiple public RPC endpoints:
import { ethers } from "ethers";
if (window.ethereum == null) {
provider = ethers.getDefaultProvider();
// Read-only; no signer
} else {
provider = new ethers.BrowserProvider(window.ethereum);
signer = await provider.getSigner();
}You can pass a network (e.g. "mainnet", "sepolia") to getDefaultProvider(network).
JsonRpcProvider with static network
If the network is known and will not change, disable the automatic chainId check to avoid an extra RPC call:
import { ethers } from "ethers";
const network = ethers.Network.from("mainnet");
const provider = new ethers.JsonRpcProvider(url, network, {
staticNetwork: network
});
// Or detect network once and then treat as static
const provider = new ethers.JsonRpcProvider(url, undefined, {
staticNetwork: true
});Broadcasting a signed transaction
To send a raw signed transaction (e.g. from an offline signer), use the provider’s broadcast method:
// v6
provider.broadcastTransaction(signedTxHex);
// Replaces v5 provider.sendTransaction(signedTx)Key Points
- getDefaultProvider() gives read-only access when no EIP-1193 provider is available.
- Use JsonRpcProvider(url, network, { staticNetwork: network }) to skip chainId lookup on a fixed network.
- broadcastTransaction() is the v6 API for sending an already-signed serialized transaction.
<!-- Source references:
- sources/ethers/docs.wrm/getting-started.wrm
- sources/ethers/docs.wrm/migrating.wrm
- https://docs.ethers.org/v6/
-->
React Native Performance
React Native’s built-in crypto can be slow. Register native implementations with ethers so key derivation, hashing, and random bytes use react-native-quick-crypto.
Setup
Install the package and register implementations before using ethers:
import { ethers } from "ethers";
import crypto from "react-native-quick-crypto";
ethers.randomBytes.register((length) => {
return new Uint8Array(crypto.randomBytes(length));
});
ethers.computeHmac.register((algo, key, data) => {
return crypto.createHmac(algo, key).update(data).digest();
});
ethers.pbkdf2.register((passwd, salt, iter, keylen, algo) => {
return crypto.pbkdf2Sync(passwd, salt, iter, keylen, algo);
});
ethers.sha256.register((data) => {
return crypto.createHash("sha256").update(data).digest();
});
ethers.sha512.register((data) => {
return crypto.createHash("sha512").update(data).digest();
});When to use
Use this when building React Native apps that create wallets, sign messages, or use ethers crypto (e.g. HDNodeWallet.fromPhrase, signMessage). Registration is global; do it once at app startup.
Key Points
- Register randomBytes, computeHmac, pbkdf2, sha256, sha512 with native implementations from react-native-quick-crypto.
- Recommended for production React Native apps; may be available as a dedicated package later.
<!-- Source references:
- sources/ethers/docs.wrm/cookbook/react-native.wrm
- https://docs.ethers.org/v6/
-->
Signing Messages
Signers can sign arbitrary messages (e.g. login proofs). Use signMessage / verifyMessage for EIP-191 personal sign.
Sign and verify (EIP-191)
import { Wallet, verifyMessage } from "ethers";
const signer = new Wallet(privateKey);
const message = "sign into ethers.org?";
const sig = await signer.signMessage(message);
const recoveredAddress = verifyMessage(message, sig); // matches signer.addressSignature object
import { Signature } from "ethers";
const sig = Signature.from(rawSig);
// Use sig as struct: compact (r, yParityAndS) or expanded (v, r, s)
await contract.recoverStringFromCompact(message, sig);
await contract.recoverStringFromVRS(message, sig.v, sig.r, sig.s);Key Points
- Personal sign uses EIP-191 prefix; digest is keccak256("\x19Ethereum Signed Message:\n" + len + message).
- Use human-readable messages for user-facing auth so users can verify in MetaMask/Ledger.
- Signature.from() gives v, r, s and compact form for contracts.
<!-- Source references:
- sources/ethers/docs.wrm/getting-started.wrm
- sources/ethers/docs.wrm/cookbook/signing.wrm
- https://docs.ethers.org/v6/
-->