Learn how to send transactions and sign messages with Ethers.js.
A Signer has the ability to send transactions and sign messages. Unlike providers (read-only), signers can write to the blockchain and make state changes. A signer typically represents an account that holds the private key.
Key Point: Providers are read-only, Signers can write transactions.
The most common way to get a signer in browser-based dApps is through MetaMask.
import { ethers } from 'ethers';
async function connectMetaMask() {
// Request user to connect wallet
await window.ethereum.request({
method: 'eth_requestAccounts'
});
// Create provider from MetaMask
const provider = new ethers.BrowserProvider(
window.ethereum
);
// Get signer (the connected account)
const signer = await provider.getSigner();
// Get signer's address
const address = await signer.getAddress();
console.log('Connected:', address);
// Get signer's balance
const balance = await signer.provider.getBalance(address);
console.log('Balance:', ethers.formatEther(balance));
return signer;
}Use a signer to send transactions to the blockchain.
import { ethers } from 'ethers';
async function sendETH(signer) {
try {
// Create transaction object
const tx = {
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f42171',
value: ethers.parseEther('0.1') // Convert to Wei automatically!
};
// Send transaction
const txResponse = await signer.sendTransaction(tx);
console.log('Transaction sent:', txResponse.hash);
// Wait for transaction to be mined
const receipt = await txResponse.wait();
console.log('Transaction mined:', receipt);
} catch (error) {
console.error('Failed to send transaction:', error);
}
}Sign messages for authentication or verification without sending transactions.
import { ethers } from 'ethers';
async function signMessage(signer) {
// Message to sign
const message = 'I own this wallet';
// Sign message
const signature = await signer.signMessage(message);
console.log('Signature:', signature);
// Verify signature (anyone can do this, no transaction needed)
const recoveredAddress = ethers.verifyMessage(
message,
signature
);
console.log('Signed by:', recoveredAddress);
}
// Common use case: Login with wallet
async function loginWithWallet(signer) {
const address = await signer.getAddress();
// Sign a message with timestamp for security
const message = `Login to my app at ${new Date().toISOString()}`;
const signature = await signer.signMessage(message);
// Send to backend for verification
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ address, message, signature })
});
return response.json();
}You can create wallets programmatically using private keys or seed phrases. Only do this in secure environments!
import { ethers } from 'ethers';
// Create wallet from private key (NEVER expose in client code!)
const privateKey = '0x...'; // KEEP THIS SECRET!
const wallet = new ethers.Wallet(privateKey);
console.log('Address:', wallet.address);
// Connect to provider
const provider = new ethers.JsonRpcProvider('https://eth.llamarpc.com');
const connectedWallet = wallet.connect(provider);
// Now you can send transactions
const tx = await connectedWallet.sendTransaction({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f42171',
value: ethers.parseEther('0.1')
});
// Create wallet from mnemonic (seed phrase)
const mnemonic = 'abandon abandon abandon ... (12 or 24 words)';
const walletFromMnemonic = ethers.Wallet.fromPhrase(mnemonic);
console.log('Address:', walletFromMnemonic.address);
// Generate random wallet
const randomWallet = ethers.Wallet.createRandom();
console.log('Mnemonic:', randomWallet.mnemonic.phrase);⚠️ WARNING: Never expose private keys in client-side code! Only use in secure backend environments.
import { ethers } from 'ethers';
async function sendAdvancedTransaction(signer) {
const tx = {
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f42171',
value: ethers.parseEther('0.1'),
// Specify gas limit (auto-estimated if omitted)
gasLimit: 21000,
// Specify gas price (auto-calculated if omitted)
gasPrice: ethers.parseUnits('50', 'gwei'),
// Nonce (transaction order, auto-calculated if omitted)
nonce: 42,
// Data for contract calls (empty for simple ETH transfer)
data: '0x'
};
const txResponse = await signer.sendTransaction(tx);
// Get receipt
const receipt = await txResponse.wait();
console.log('Gas used:', receipt.gasUsed.toString());
console.log('Transaction fee:', ethers.formatEther(
receipt.gasUsed * receipt.gasPrice
));
}