← back to blog

Setup guide: How to set up Mantle and Linea wallets at scale

Setup guide: How to set up Mantle and Linea wallets at scale

Managing one or two wallets on Mantle or Linea is trivial. Managing 50, 200, or 1000 wallets, each needing funding, network configuration, and activity logging, is a different problem entirely. Most guides stop at “add the network to MetaMask.” that is not useful if you are running an operation.

This guide is for operators who already understand the basics of EVM wallets and want a repeatable, scriptable system for generating and managing wallets across both Mantle (chain ID 5000, gas token MNT) and Linea (chain ID 59144, gas token ETH). i have run similar setups out of Singapore, primarily on a VPS, and this is the stack that has worked reliably for me.

By the end of this, you will have a working wallet generation script, both networks configured and tested, a proxy-to-wallet mapping, and an understanding of what breaks when you scale past 100 wallets.

what you need

  • Node.js 18+ installed on your machine or VPS (Ubuntu 22.04 works cleanly)
  • ethers.js v6 , the wallet generation library we will use
  • Python 3.10+ optional, if you prefer Python tooling
  • A funded source wallet , you need MNT on Mantle and ETH on Linea to distribute gas to child wallets
  • Residential or mobile proxies , one proxy per wallet cluster at minimum; datacenter proxies get flagged on Linea bridge and some dApps
  • A spreadsheet or SQLite database to track wallet addresses, private keys (encrypted), and proxy assignments
  • Estimated costs: proxy plan from around $3-8/GB depending on provider; MNT for gas is cheap (fractions of a cent per tx as of May 2026); Linea ETH costs more, budget ~$0.01-0.05 per transaction depending on L1 congestion
  • Optionally: antidetect browser profiles if you are interacting with dApps manually at scale

step by step

step 1: set up your working environment

On a fresh Ubuntu 22.04 VPS or your local machine:

# install Node.js 18 via nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install 18
nvm use 18

# create a project folder
mkdir wallet-factory && cd wallet-factory
npm init -y
npm install ethers@6 dotenv

Store your source wallet private key in a .env file, never hardcoded in scripts:

echo "SOURCE_PRIVATE_KEY=0xyourprivatekeyhere" > .env
echo ".env" >> .gitignore

if it breaks: if nvm command is not found after install, close and reopen your terminal or run source ~/.nvm/nvm.sh manually.

step 2: generate wallets in bulk

Create generate.js. this script generates N wallets and writes them to a JSON file. Adapt N to your target count.

import { ethers } from "ethers";
import fs from "fs";

const COUNT = 100; // change to your target
const wallets = [];

for (let i = 0; i < COUNT; i++) {
  const wallet = ethers.Wallet.createRandom();
  wallets.push({
    index: i,
    address: wallet.address,
    privateKey: wallet.privateKey,
    mnemonic: wallet.mnemonic.phrase,
  });
}

fs.writeFileSync("wallets.json", JSON.stringify(wallets, null, 2));
console.log(`Generated ${COUNT} wallets -> wallets.json`);
node generate.js

security note: wallets.json contains raw private keys. encrypt it immediately or move it to an encrypted volume. do not commit it anywhere.

if it breaks: if you get an ES module error, add "type": "module" to your package.json.

step 3: configure Mantle and Linea network connections

Mantle and Linea both use the standard EVM JSON-RPC interface. the official RPC endpoints per Mantle’s network documentation and Linea’s developer docs are:

  • Mantle: https://rpc.mantle.xyz (chain ID 5000)
  • Linea: https://rpc.linea.build (chain ID 59144)

Create config.js:

export const NETWORKS = {
  mantle: {
    name: "Mantle",
    rpc: "https://rpc.mantle.xyz",
    chainId: 5000,
    symbol: "MNT",
    explorer: "https://explorer.mantle.xyz",
  },
  linea: {
    name: "Linea",
    rpc: "https://rpc.linea.build",
    chainId: 59144,
    symbol: "ETH",
    explorer: "https://lineascan.build",
  },
};

if it breaks: public RPCs get rate-limited under load. for scale operations, use a dedicated RPC node via Alchemy, QuickNode, or Ankr. Alchemy supports both Mantle and Linea.

step 4: verify wallet connectivity on both networks

Before funding anything, confirm your generated wallets can connect and read balances:

import { ethers } from "ethers";
import { NETWORKS } from "./config.js";
import wallets from "./wallets.json" assert { type: "json" };

async function checkBalances(networkKey) {
  const net = NETWORKS[networkKey];
  const provider = new ethers.JsonRpcProvider(net.rpc);

  for (const w of wallets.slice(0, 5)) { // test first 5
    const balance = await provider.getBalance(w.address);
    console.log(`[${net.name}] ${w.address}: ${ethers.formatEther(balance)} ${net.symbol}`);
  }
}

await checkBalances("mantle");
await checkBalances("linea");

Expected output: all balances return 0.0 for fresh wallets. if you get connection timeouts, the RPC is rate-limiting you. slow down or switch to a paid endpoint.

if it breaks: assert { type: "json" } import syntax requires Node 18+. if you are on an older version, use fs.readFileSync and JSON.parse instead.

step 5: distribute gas to child wallets

You need a small amount of MNT on Mantle and ETH on Linea in each wallet to pay for transactions. write a distributor that sends from your source wallet in batches:

import { ethers } from "ethers";
import "dotenv/config";
import { NETWORKS } from "./config.js";
import wallets from "./wallets.json" assert { type: "json" };

async function distribute(networkKey, amountEther) {
  const net = NETWORKS[networkKey];
  const provider = new ethers.JsonRpcProvider(net.rpc);
  const source = new ethers.Wallet(process.env.SOURCE_PRIVATE_KEY, provider);

  console.log(`Distributing ${amountEther} ${net.symbol} on ${net.name}...`);

  for (const w of wallets) {
    const tx = await source.sendTransaction({
      to: w.address,
      value: ethers.parseEther(amountEther),
    });
    console.log(`Sent to ${w.address}: ${tx.hash}`);
    await tx.wait();
    await new Promise(r => setTimeout(r, 500)); // 500ms delay between sends
  }
}

// adjust amounts based on expected activity
await distribute("mantle", "0.05");  // 0.05 MNT per wallet
await distribute("linea", "0.001"); // 0.001 ETH per wallet

if it breaks: if the source wallet runs out of gas mid-run, the script will throw. add a balance check before the loop and verify your source wallet has enough for all distributions plus its own gas.

step 6: assign proxies to wallet clusters

At scale, running all wallets through the same IP triggers Sybil detection on most protocols. the general approach: group wallets into clusters of 3-10 and assign one residential proxy per cluster.

Store your proxy-to-cluster mapping in a file:

[
  {
    "proxyId": "proxy_001",
    "proxyUrl": "http://user:[email protected]:port",
    "walletIndices": [0, 1, 2, 3, 4]
  },
  {
    "proxyId": "proxy_002",
    "proxyUrl": "http://user:[email protected]:port2",
    "walletIndices": [5, 6, 7, 8, 9]
  }
]

To use proxies with ethers.js, you need an HTTP agent. install https-proxy-agent:

npm install https-proxy-agent
import { HttpsProxyAgent } from "https-proxy-agent";
import { ethers, FetchRequest } from "ethers";

function getProviderWithProxy(rpcUrl, proxyUrl) {
  const agent = new HttpsProxyAgent(proxyUrl);
  const fetchRequest = new FetchRequest(rpcUrl);
  fetchRequest.getUrlFunc = FetchRequest.createGetUrlFunc({ agent });
  return new ethers.JsonRpcProvider(fetchRequest);
}

For manual dApp interactions, pair each wallet cluster with a browser profile. the multiaccountops.com blog has detailed guides on browser profile management for multi-account operations.

if it breaks: some proxy providers block Web3 traffic by default. test your proxy against the RPC endpoint with a basic curl first before wiring it into scripts.

step 7: run a test batch and validate on-chain

Before running the full set, run 5-10 wallets through your intended activity flow on both chains. check:

  • transactions appear in the respective explorers (explorer.mantle.xyz and lineascan.build)
  • no wallets are sharing the same nonce due to race conditions in concurrent sends
  • gas estimates are within expected range
# check a wallet's tx history on Linea via API
curl "https://api.lineascan.build/api?module=account&action=txlist&address=YOUR_ADDRESS&apikey=YourApiKeyToken"

if it breaks: nonce collisions happen when you fire transactions too fast from the same wallet. add nonce management or use a queue that tracks pending transactions per address.

step 8: log everything

At scale, debugging without logs is painful. write each transaction hash, wallet index, network, and timestamp to a SQLite database or at minimum a CSV. you will need this when investigating why a specific wallet was not counted by a protocol’s snapshot.

import fs from "fs";

function logTx(walletIndex, network, txHash, action) {
  const row = `${new Date().toISOString()},${walletIndex},${network},${txHash},${action}\n`;
  fs.appendFileSync("activity_log.csv", row);
}

if it breaks: concurrent writes to a single file from multiple async processes will corrupt it. use a proper queue or write per-wallet log files instead.

common pitfalls

1. using the same IP across all wallets. this is the fastest way to get your wallets grouped together in a Sybil analysis. even basic clustering by IP will flag you. assign proxies before you start any on-chain activity.

2. generating wallets with weak randomness. do not use Math.random() or timestamp-seeded generators for private keys. ethers.js Wallet.createRandom() uses crypto.getRandomValues() under the hood, which is cryptographically secure. anything else is not.

3. funding all wallets from one address in sequence. a single source wallet funding 500 addresses in a straight line is a clear on-chain pattern. use intermediary wallets, split funding across multiple sources, and add time variation between sends.

4. skipping the test batch. running 1000 wallets through a flow you have not validated on 10 wallets first is how you lose gas money to a bug. always test small first.

5. not backing up wallets before deleting the VPS. i have seen operators lose entire wallet sets because they terminated a VPS without extracting wallets.json. encrypt and store your wallet file in at least two locations before you touch the infrastructure.

scaling this

10x (under 100 wallets): the scripts above handle this fine. run sequentially with 500ms delays. one VPS, one proxy pool.

100x (100-1000 wallets): you need concurrency controls. use a job queue like Bull or BullMQ to manage wallet tasks, cap concurrency at 10-20 simultaneous workers, and implement exponential backoff on RPC errors. switch to a paid RPC provider. Linea’s public RPC is not designed for 1000 concurrent wallets.

1000x (1000+ wallets): this becomes an infrastructure problem. distribute workers across multiple VPS instances in different regions. shard your wallet set by region. use a dedicated database (PostgreSQL) instead of flat files. your proxy spend becomes significant , negotiate bulk rates or use a proxy API with dynamic IP rotation. at this scale, the operational bottleneck is usually gas distribution and proxy management, not the scripts themselves.

where to go next

Written by Xavier Fok

disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-05-19.

need infra for this today?