Created
June 12, 2025 22:29
-
-
Save kmjones1979/a5ef7e6fce15302c53ab08d7fde03d90 to your computer and use it in GitHub Desktop.
example sweeper for deployer and burner wallet accounts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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