← All Docs

ethers.js Adapter - Use PolicyLayer with ethers.js

ethers.js Adapter

Use PolicyLayer with ethers.js, the most popular Ethereum library.

Overview

Best for: Most developers, production applications, maximum compatibility Chains: All EVM-compatible blockchains Versions: Supports both ethers v5 and v6 Bundle size: ~300kB

Installation

npm install @policylayer/sdk ethers

Peer dependencies: ethers.js v5 or v6

Basic Setup

1. Import the adapter

import { PolicyWallet, createEthersAdapter } from '@policylayer/sdk';
import { ethers } from 'ethers';

2. Create the adapter

const privateKey = process.env.PRIVATE_KEY; // Your wallet private key
const rpcUrl = process.env.RPC_URL; // e.g., https://eth-mainnet.g.alchemy.com/v2/YOUR-KEY

const adapter = await createEthersAdapter(privateKey, rpcUrl);

3. Wrap with PolicyWallet

const wallet = new PolicyWallet(adapter, {
  apiKey: process.env.POLICYLAYER_API_KEY,
});

4. Send transactions

// Native ETH transfer
await wallet.send({
  chain: 'ethereum',
  asset: 'eth',
  to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1',
  amount: '1000000000000000000', // 1 ETH in wei
});

// ERC-20 token transfer (use contract address as asset)
await wallet.send({
  chain: 'ethereum',
  asset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC contract address
  to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1',
  amount: '1000000', // 1 USDC (6 decimals)
});

Configuration

Environment Variables

# Required
PRIVATE_KEY=0x... # Your wallet private key (with 0x prefix)
RPC_URL=https://... # Your RPC endpoint
POLICYLAYER_API_KEY=sk_... # Your PolicyLayer API key

# Optional
CHAIN_ID=1 # Ethereum mainnet (auto-detected if not specified)

RPC Providers

Recommended providers:

Example RPC URLs:

// Ethereum Mainnet
const rpcUrl = 'https://eth-mainnet.g.alchemy.com/v2/YOUR-KEY';

// Ethereum Sepolia (testnet)
const rpcUrl = 'https://eth-sepolia.g.alchemy.com/v2/YOUR-KEY';

// Base Mainnet
const rpcUrl = 'https://mainnet.base.org';

// Base Sepolia
const rpcUrl = 'https://sepolia.base.org';

// Polygon Mainnet
const rpcUrl = 'https://polygon-rpc.com';

Complete Example

import { PolicyWallet, createEthersAdapter } from '@policylayer/sdk';
import * as dotenv from 'dotenv';

dotenv.config();

async function main() {
  // 1. Create ethers.js adapter
  const adapter = await createEthersAdapter(
    process.env.PRIVATE_KEY!,
    process.env.RPC_URL!
  );

  // 2. Create PolicyWallet
  const wallet = new PolicyWallet(adapter, {
    apiKey: process.env.POLICYLAYER_API_KEY!,
  });

  try {
    // 3. Send ETH with policy enforcement
    const result = await wallet.send({
      chain: 'ethereum',
      asset: 'eth',
      to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1',
      amount: '1000000000000000000', // 1 ETH
    });

    console.log('✅ Transaction sent:', result.hash);
    console.log('⛽ Gas fee:', result.fee);
  } catch (error) {
    if (error.code === 'POLICY_DECISION_DENY') {
      console.log('❌ Policy denied:', error.message);
      console.log('   Remaining daily:', error.details?.counters?.remainingDaily);
    } else {
      throw error;
    }
  }
}

main();

Advanced Usage

ERC-20 Token Transfers

// USDC transfer (6 decimals)
await wallet.send({
  chain: 'ethereum',
  asset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
  to: recipientAddress,
  amount: '1000000', // 1 USDC
});

// DAI transfer (18 decimals)
await wallet.send({
  chain: 'ethereum',
  asset: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI on Ethereum
  to: recipientAddress,
  amount: '1000000000000000000', // 1 DAI
});

Multiple Chains

// Ethereum mainnet wallet
const ethAdapter = await createEthersAdapter(
  privateKey,
  'https://eth-mainnet.g.alchemy.com/v2/YOUR-KEY'
);
const ethWallet = new PolicyWallet(ethAdapter, { apiKey: 'key-1' });

// Base mainnet wallet
const baseAdapter = await createEthersAdapter(
  privateKey,
  'https://mainnet.base.org'
);
const baseWallet = new PolicyWallet(baseAdapter, { apiKey: 'key-2' });

// Send on different chains
await ethWallet.send({ chain: 'ethereum', asset: 'eth', to: '0x...', amount: '1000000' });
await baseWallet.send({ chain: 'base', asset: 'eth', to: '0x...', amount: '1000000' });

Error Handling

import { PolicyError } from '@policylayer/sdk';

try {
  await wallet.send({ chain: 'ethereum', asset: 'eth', to: merchant, amount: '1000000' });
} catch (error) {
  if (error instanceof PolicyError) {
    // Policy denied transaction
    console.log('Policy blocked transaction:');
    console.log('- Code:', error.code);  // e.g., 'POLICY_DECISION_DENY'
    console.log('- Message:', error.message);
    console.log('- Remaining daily:', error.details?.counters?.remainingDaily);
  } else if (error.code === 'INSUFFICIENT_FUNDS') {
    // Not enough balance
    console.log('Insufficient funds');
  } else {
    // Other error (network, gas, etc.)
    console.error('Transaction failed:', error.message);
  }
}

Gas Estimation

The adapter handles gas estimation automatically, but you can customise:

// Adapter uses ethers.js gas estimation by default
const result = await wallet.send({
  to: '0x...',
  amount: '1000000',
  // Gas estimated automatically
});

console.log('Gas fee paid:', result.fee); // in ETH

Balance Checks

// Get wallet balance
const balance = await adapter.getBalance();
console.log('Wallet balance:', balance, 'wei');

// Convert to human-readable
import { ethers } from 'ethers';
const balanceEth = ethers.formatEther(balance);
console.log('Balance:', balanceEth, 'ETH');

Network Configuration

Ethereum Networks

NetworkChain IDRPC URL
Mainnet1https://eth-mainnet.g.alchemy.com/v2/YOUR-KEY
Sepolia11155111https://eth-sepolia.g.alchemy.com/v2/YOUR-KEY

Base Networks

NetworkChain IDRPC URL
Base Mainnet8453https://mainnet.base.org
Base Sepolia84532https://sepolia.base.org

Other EVM Networks

ethers.js works with any EVM chain. Just provide the correct RPC URL:

Troubleshooting

”Invalid private key” error

Problem: Private key format is incorrect

Solution: Ensure private key includes 0x prefix:

// ✅ Correct
const privateKey = '0xabc123...';

// ❌ Wrong
const privateKey = 'abc123...';

“Insufficient funds” error

Problem: Wallet doesn’t have enough ETH for transaction + gas

Solution: Fund your wallet with ETH for gas fees:

// Check balance first
const balance = await adapter.getBalance();
console.log('Balance (wei):', balance);

// Ensure balance > amount + gas fees

“Network mismatch” error

Problem: RPC URL doesn’t match expected chain

Solution: Verify RPC URL matches your intended network:

// Check what chain you're connected to
const chainId = await adapter.getChainId();
console.log('Connected to chain:', chainId);
// 1 = Ethereum, 8453 = Base, etc.

“Rate limit exceeded” error

Problem: Free RPC tier has rate limits

Solution: Use a paid RPC provider (Alchemy, Infura) or implement retries:

// Upgrade to paid tier, or add retry logic
const adapter = await createEthersAdapter(privateKey, rpcUrl);

Best Practises

1. Use environment variables for secrets

// ✅ Good
const privateKey = process.env.PRIVATE_KEY;

// ❌ Never hardcode
const privateKey = '0xabc123...'; // DON'T DO THIS

2. Use testnet for development

// Development: Use Sepolia testnet
const rpcUrl = 'https://eth-sepolia.g.alchemy.com/v2/YOUR-KEY';

// Production: Use mainnet
const rpcUrl = 'https://eth-mainnet.g.alchemy.com/v2/YOUR-KEY';

3. Handle policy violations gracefully

try {
  await wallet.send({ chain: 'ethereum', asset: 'eth', to, amount });
} catch (error) {
  if (error.code === 'POLICY_DECISION_DENY') {
    // Log and notify, don't crash
    await notifyAdmin(`Policy blocked: ${error.message}`);
  }
}

4. Monitor gas fees

const result = await wallet.send({ to, amount });
console.log('Gas fee:', result.fee); // Monitor costs

Next Steps

API Reference

createEthersAdapter()

function createEthersAdapter(
  privateKey: string,
  rpcUrl: string
): Promise<WalletAdapter>

Parameters:

  • privateKey - Wallet private key (with 0x prefix)
  • rpcUrl - Ethereum RPC endpoint URL

Returns: Promise resolving to WalletAdapter

Example:

const adapter = await createEthersAdapter(
  '0xabc123...',
  'https://eth-mainnet.g.alchemy.com/v2/YOUR-KEY'
);