Skip to content

Instantly share code, notes, and snippets.

@kmjones1979
Created June 12, 2025 22:29
Show Gist options
  • Save kmjones1979/a5ef7e6fce15302c53ab08d7fde03d90 to your computer and use it in GitHub Desktop.
Save kmjones1979/a5ef7e6fce15302c53ab08d7fde03d90 to your computer and use it in GitHub Desktop.
example sweeper for deployer and burner wallet accounts
import * as dotenv from "dotenv";
dotenv.config();
import { ethers, Wallet } from "ethers";
import { config } from "hardhat";
import password from "@inquirer/password";
import { input, confirm, select } from "@inquirer/prompts";
interface AccountInfo {
address: string;
wallet: Wallet;
source: string;
}
async function main() {
const accounts: AccountInfo[] = [];
// Get destination address
const destinationAddress = await input({
message: "Enter the destination address to sweep funds to:",
validate: (input: string) => {
if (!ethers.isAddress(input)) {
return "Please enter a valid Ethereum address";
}
return true;
},
});
console.log(`🎯 Destination: ${destinationAddress}\n`);
// Add encrypted deployer account if available
const encryptedKey = process.env.DEPLOYER_PRIVATE_KEY_ENCRYPTED;
if (encryptedKey) {
const pass = await password({ message: "Enter password for encrypted deployer account (or press Enter to skip):" });
if (pass) {
try {
const wallet = (await Wallet.fromEncryptedJson(encryptedKey, pass)) as Wallet;
accounts.push({
address: wallet.address,
wallet,
source: "Encrypted Deployer Account",
});
console.log(`βœ… Added encrypted account: ${wallet.address}`);
} catch (e) {
console.log("❌ Failed to decrypt private key. Skipping encrypted account.");
}
}
}
// Allow adding additional accounts via private key
while (true) {
const addAnother = await confirm({
message: "Do you want to add another account by private key?",
default: false,
});
if (!addAnother) break;
const privateKey = await password({ message: "Enter the private key (0x...):" });
try {
const wallet = new ethers.Wallet(privateKey);
// Check if already added
if (accounts.some(acc => acc.address.toLowerCase() === wallet.address.toLowerCase())) {
console.log("⚠️ This account is already added. Skipping.");
continue;
}
accounts.push({
address: wallet.address,
wallet,
source: "Manual Private Key",
});
console.log(`βœ… Added account: ${wallet.address}`);
} catch (e) {
console.log("❌ Invalid private key format. Please try again.");
}
}
if (accounts.length === 0) {
console.log("🚫 No accounts added. Exiting.");
return;
}
console.log(`\nπŸ“‹ Accounts to sweep (${accounts.length}):`);
accounts.forEach((acc, i) => {
console.log(` ${i + 1}. ${acc.address} (${acc.source})`);
});
// Check balances and prepare sweep transactions
const allSweepTransactions = [];
const availableNetworks = config.networks;
for (const account of accounts) {
console.log(`\nπŸ” Checking balances for ${account.address}:`);
for (const networkName in availableNetworks) {
try {
const network = availableNetworks[networkName];
if (!("url" in network) || networkName === "hardhat" || networkName === "localhost") continue;
const provider = new ethers.JsonRpcProvider(network.url);
await provider._detectNetwork();
const balance = await provider.getBalance(account.address);
if (balance > 0n) {
// Get current gas data
const feeData = await provider.getFeeData();
const gasLimit = 21000n;
// Calculate gas cost using EIP-1559 if available, otherwise legacy
let baseGasCost: bigint;
if (feeData.maxFeePerGas) {
baseGasCost = feeData.maxFeePerGas * gasLimit;
} else {
baseGasCost = (feeData.gasPrice || 0n) * gasLimit;
}
// Add 25% buffer to gas cost
const gasCost = baseGasCost + (baseGasCost * 25n) / 100n;
const amountToSend = balance - gasCost;
if (amountToSend > 0n) {
console.log(` πŸ’° ${networkName}: ${ethers.formatEther(balance)} ETH available`);
console.log(` β›½ Gas cost: ${ethers.formatEther(gasCost)} ETH`);
console.log(` πŸ“€ Will send: ${ethers.formatEther(amountToSend)} ETH`);
allSweepTransactions.push({
account,
networkName,
provider,
balance,
amountToSend,
gasLimit,
});
} else {
console.log(` ⚠️ ${networkName}: Balance too low to cover gas costs`);
}
} else {
console.log(` πŸ’Έ ${networkName}: No balance`);
}
} catch (e) {
console.log(` ❌ Can't connect to ${networkName}`);
}
}
}
if (allSweepTransactions.length === 0) {
console.log("\n🀷 No funds available to sweep on any network from any account.");
return;
}
// Show summary
const totalETH = allSweepTransactions.reduce((sum, tx) => sum + tx.amountToSend, 0n);
const uniqueNetworks = [...new Set(allSweepTransactions.map(tx => tx.networkName))].length;
console.log(`\nπŸ“Š Summary:`);
console.log(` Accounts with funds: ${accounts.length}`);
console.log(` Networks with funds: ${uniqueNetworks}`);
console.log(` Total transactions: ${allSweepTransactions.length}`);
console.log(` Total ETH to sweep: ${ethers.formatEther(totalETH)}`);
const shouldProceed = await confirm({
message: "Do you want to proceed with sweeping all these funds?",
default: false,
});
if (!shouldProceed) {
console.log("❌ Sweep cancelled.");
return;
}
// Execute sweep transactions
console.log("\nπŸš€ Starting sweep transactions...\n");
for (const tx of allSweepTransactions) {
try {
const walletConnected = tx.account.wallet.connect(tx.provider);
// Prepare transaction with proper gas settings
const transaction: any = {
to: destinationAddress,
value: tx.amountToSend,
gasLimit: tx.gasLimit,
};
// Use EIP-1559 if supported, otherwise use legacy gas price
const feeData = await tx.provider.getFeeData();
if (feeData.maxFeePerGas && feeData.maxPriorityFeePerGas) {
transaction.maxFeePerGas = feeData.maxFeePerGas;
transaction.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
} else {
transaction.gasPrice = feeData.gasPrice;
}
console.log(`πŸ”„ ${tx.account.address} β†’ ${tx.networkName}`);
const txResponse = await walletConnected.sendTransaction(transaction);
console.log(` πŸ“‹ Transaction hash: ${txResponse.hash}`);
// Wait for confirmation
console.log(` ⏳ Waiting for confirmation...`);
const receipt = await txResponse.wait();
if (receipt?.status === 1) {
console.log(` βœ… Success! ${ethers.formatEther(tx.amountToSend)} ETH swept`);
} else {
console.log(` ❌ Transaction failed`);
}
} catch (error) {
console.log(` ❌ Error: ${error}`);
}
console.log("");
}
console.log("πŸŽ‰ Multi-account sweep operation completed!");
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
});
import * as dotenv from "dotenv";
dotenv.config();
import { ethers, Wallet } from "ethers";
import { config } from "hardhat";
import password from "@inquirer/password";
import { input, confirm } from "@inquirer/prompts";
async function main() {
const encryptedKey = process.env.DEPLOYER_PRIVATE_KEY_ENCRYPTED;
if (!encryptedKey) {
console.log("🚫️ You don't have a deployer account. Run `yarn generate` or `yarn account:import` first");
return;
}
// Get destination address
const destinationAddress = await input({
message: "Enter the destination address to sweep funds to:",
validate: (input: string) => {
if (!ethers.isAddress(input)) {
return "Please enter a valid Ethereum address";
}
return true;
},
});
// Get password to decrypt private key
const pass = await password({ message: "Enter your password to decrypt the private key:" });
let wallet: Wallet;
try {
wallet = (await Wallet.fromEncryptedJson(encryptedKey, pass)) as Wallet;
} catch (e) {
console.log("❌ Failed to decrypt private key. Wrong password?");
return;
}
const sourceAddress = wallet.address;
console.log(`\nπŸ”„ Sweeping funds from: ${sourceAddress}`);
console.log(`🎯 To destination: ${destinationAddress}\n`);
// Check balances and prepare sweep transactions
const availableNetworks = config.networks;
const sweepTransactions = [];
for (const networkName in availableNetworks) {
try {
const network = availableNetworks[networkName];
if (!("url" in network) || networkName === "hardhat" || networkName === "localhost") continue;
const provider = new ethers.JsonRpcProvider(network.url);
await provider._detectNetwork();
const balance = await provider.getBalance(sourceAddress);
if (balance > 0n) {
// Get current gas data
const feeData = await provider.getFeeData();
const gasLimit = 21000n; // Standard gas limit for ETH transfer
// Calculate gas cost using EIP-1559 if available, otherwise legacy
let baseGasCost: bigint;
if (feeData.maxFeePerGas) {
baseGasCost = feeData.maxFeePerGas * gasLimit;
} else {
baseGasCost = (feeData.gasPrice || 0n) * gasLimit;
}
// Add 25% buffer to gas cost to account for price fluctuations
const gasCost = baseGasCost + (baseGasCost * 25n) / 100n;
// Calculate amount to send (balance minus gas cost with buffer)
const amountToSend = balance - gasCost;
if (amountToSend > 0n) {
console.log(`πŸ’° ${networkName}: ${ethers.formatEther(balance)} ETH available`);
console.log(` β›½ Gas cost: ${ethers.formatEther(gasCost)} ETH`);
console.log(` πŸ“€ Will send: ${ethers.formatEther(amountToSend)} ETH`);
sweepTransactions.push({
networkName,
provider,
balance,
amountToSend,
gasLimit,
});
} else {
console.log(`⚠️ ${networkName}: Balance too low to cover gas costs`);
}
} else {
console.log(`πŸ’Έ ${networkName}: No balance to sweep`);
}
} catch (e) {
console.log(`❌ Can't connect to network ${networkName}`);
}
}
if (sweepTransactions.length === 0) {
console.log("\n🀷 No funds available to sweep on any network.");
return;
}
// Confirm before proceeding
const totalETH = sweepTransactions.reduce((sum, tx) => sum + tx.amountToSend, 0n);
console.log(`\nπŸ“Š Summary:`);
console.log(` Networks with funds: ${sweepTransactions.length}`);
console.log(` Total ETH to sweep: ${ethers.formatEther(totalETH)}`);
const shouldProceed = await confirm({
message: "Do you want to proceed with sweeping these funds?",
default: false,
});
if (!shouldProceed) {
console.log("❌ Sweep cancelled.");
return;
}
// Execute sweep transactions
console.log("\nπŸš€ Starting sweep transactions...\n");
for (const tx of sweepTransactions) {
try {
const walletConnected = wallet.connect(tx.provider);
// Prepare transaction with proper gas settings
const transaction: any = {
to: destinationAddress,
value: tx.amountToSend,
gasLimit: tx.gasLimit,
};
// Use EIP-1559 if supported, otherwise use legacy gas price
const feeData = await tx.provider.getFeeData();
if (feeData.maxFeePerGas && feeData.maxPriorityFeePerGas) {
transaction.maxFeePerGas = feeData.maxFeePerGas;
transaction.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
} else {
transaction.gasPrice = feeData.gasPrice;
}
console.log(`πŸ”„ Sending transaction on ${tx.networkName}...`);
const txResponse = await walletConnected.sendTransaction(transaction);
console.log(` πŸ“‹ Transaction hash: ${txResponse.hash}`);
// Wait for confirmation
console.log(` ⏳ Waiting for confirmation...`);
const receipt = await txResponse.wait();
if (receipt?.status === 1) {
console.log(` βœ… Success! ${ethers.formatEther(tx.amountToSend)} ETH swept from ${tx.networkName}`);
} else {
console.log(` ❌ Transaction failed on ${tx.networkName}`);
}
} catch (error) {
console.log(` ❌ Error sweeping funds from ${tx.networkName}:`, error);
}
console.log(""); // Empty line for spacing
}
console.log("πŸŽ‰ Sweep operation completed!");
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment