Scholarpeak

Signers & Wallets

Learn how to send transactions and sign messages with Ethers.js.

What is a Signer?

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.

Getting a Signer from MetaMask

The most common way to get a signer in browser-based dApps is through MetaMask.

typescript
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;
}

Sending Transactions

Use a signer to send transactions to the blockchain.

typescript
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);
  }
}

Signing Messages

Sign messages for authentication or verification without sending transactions.

typescript
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();
}

Creating Wallets (Advanced)

You can create wallets programmatically using private keys or seed phrases. Only do this in secure environments!

typescript
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.

Advanced Transaction Options

typescript
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
  ));
}