Skip to content

Instantly share code, notes, and snippets.

@agazso
Last active July 15, 2025 13:50
Show Gist options
  • Select an option

  • Save agazso/1ff2e138c54b81be276f724ce8c2050b to your computer and use it in GitHub Desktop.

Select an option

Save agazso/1ff2e138c54b81be276f724ce8c2050b to your computer and use it in GitHub Desktop.
import {
createPublicClient,
createWalletClient,
http,
parseEther,
formatEther,
isAddress,
type Address,
type Hash,
type TransactionReceipt,
} from 'viem'
import { base, baseGoerli, localhost } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
// const CHAIN = { ...localhost, id: 31337 }
// const RPC_URL = 'http://127.0.0.1:8545'
// // Base network configuration
const CHAIN = base // Use baseGoerli for testnet
const RPC_URL = 'https://base-mainnet.g.alchemy.com/v2/2HKLEWoFlHyimU90uZK4x_p5y1nfDr6I' // Use 'https://goerli.base.org' for testnet
interface TransferMapping {
fromPrivateKey: `0x${string}`
toAddress: Address
}
class BaseTransferManager {
private publicClient
constructor() {
this.publicClient = createPublicClient({
chain: CHAIN,
transport: http(RPC_URL),
})
}
/**
* Get the balance of an address
*/
async getBalance(address: Address): Promise<bigint> {
return await this.publicClient.getBalance({ address })
}
/**
* Estimate gas for a transaction
*/
async estimateGas(fromAddress: Address, toAddress: Address, amount: bigint): Promise<bigint> {
return await this.publicClient.estimateGas({
account: fromAddress,
to: toAddress,
value: amount,
})
}
/**
* Get current gas price
*/
async getGasPrice(): Promise<bigint> {
return await this.publicClient.getGasPrice()
}
/**
* Transfer all funds from one wallet to another
*/
async transferAllFunds(fromPrivateKey: `0x${string}`, toAddress: Address): Promise<Hash> {
try {
// Create account from private key
const account = privateKeyToAccount(fromPrivateKey)
const fromAddress = account.address
console.log(`Processing transfer from ${fromAddress} to ${toAddress}`)
// Get current balance
const balance = await this.getBalance(fromAddress)
console.log(`Current balance: ${formatEther(balance)} ETH`)
if (balance === 0n) {
console.log('No funds to transfer')
throw new Error('No funds available')
}
// Create wallet client
const walletClient = createWalletClient({
account,
chain: CHAIN,
transport: http(RPC_URL),
})
// Get gas price and estimate gas
const gasPrice = await this.getGasPrice()
const estimatedGas = await this.estimateGas(fromAddress, toAddress, balance)
// Calculate gas cost
const gasCost = gasPrice * estimatedGas
// Calculate amount to send (balance minus gas cost)
const amountToSend = balance - gasCost
if (amountToSend <= 0n) {
console.log('Insufficient funds to cover gas costs')
throw new Error('Insufficient funds for gas')
}
console.log(`Amount to send: ${formatEther(amountToSend)} ETH`)
console.log(`Gas cost: ${formatEther(gasCost)} ETH`)
// Send transaction
const txHash = await walletClient.sendTransaction({
to: toAddress,
value: amountToSend,
gas: estimatedGas,
gasPrice: gasPrice,
})
console.log(`Transaction sent: ${txHash}`)
// Wait for confirmation with extended timeout for Base network
const receipt = await this.publicClient.waitForTransactionReceipt({
hash: txHash,
timeout: 300_000, // 5 minutes timeout
})
console.log(`Transaction confirmed in block: ${receipt.blockNumber}`)
return txHash
} catch (error) {
console.error(`Error transferring funds: ${error}`)
throw error
}
}
/**
* Process multiple transfers with error handling
*/
async processMultipleTransfersWithResults(
transferMappings: TransferMapping[],
): Promise<(Hash | null)[]> {
const results: (Hash | null)[] = []
const reverseMappings = transferMappings.reverse()
let first = true
for (const mapping of reverseMappings) {
try {
console.log(
`\n--- Processing transfer ${results.length + 1}/${transferMappings.length} ---`,
)
const txHash = await this.transferAllFunds(mapping.fromPrivateKey, mapping.toAddress)
results.push(txHash)
// Add delay between transactions to avoid nonce issues
await this.delay(first ? 2000 * 60 : 10_000)
first = false
} catch (error) {
console.error(`Failed to process transfer:`, error)
results.push(null) // Add null for failed transfers
// Stop when there is an error
return results
}
}
return results
}
/**
* Utility function to add delay
*/
private delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms))
}
/**
* Validate an Ethereum address
*/
isValidAddress(address: string): address is Address {
return isAddress(address)
}
/**
* Get transaction receipt
*/
async getTransactionReceipt(txHash: Hash): Promise<TransactionReceipt> {
return await this.publicClient.getTransactionReceipt({ hash: txHash })
}
/**
* Check if transaction is confirmed
*/
async isTransactionConfirmed(txHash: Hash): Promise<boolean> {
try {
const receipt = await this.getTransactionReceipt(txHash)
return receipt.status === 'success'
} catch (error) {
return false
}
}
/**
* Get current block number
*/
async getBlockNumber(): Promise<bigint> {
return await this.publicClient.getBlockNumber()
}
/**
* Wait for transaction confirmation with timeout
*/
async waitForConfirmation(txHash: Hash, timeoutMs: number = 60000): Promise<TransactionReceipt> {
return await this.publicClient.waitForTransactionReceipt({
hash: txHash,
timeout: timeoutMs,
})
}
}
// Load CSV data and create transfer mappings
function loadTransferMappingsFromCSV(): TransferMapping[] {
const { readFileSync } = require('fs')
const csvPath = 'Copy of Ghost.fun agent accounts - accounts.csv'
const csvContent = readFileSync(csvPath, 'utf-8')
const lines = csvContent.split('\n')
const transferMappings: TransferMapping[] = []
// Skip first 4 lines and process the rest
for (let i = 4; i < lines.length; i++) {
const line = lines[i].trim()
if (line) {
const columns = line.split(',')
const fromAddress = columns[1] // 2nd column (sender address)
const fromPrivateKey = columns[2] // 3rd column (private key)
const toAddress = columns[6] // 7th column (destination address)
// Skip row 3 (index 2) and rows without destination address
if (fromAddress && fromPrivateKey && toAddress &&
fromAddress.startsWith('0x') && fromPrivateKey.startsWith('0x') &&
toAddress.startsWith('0x')) {
transferMappings.push({
fromPrivateKey: fromPrivateKey as `0x${string}`,
toAddress: toAddress as Address
})
}
}
}
return transferMappings
}
async function main() {
const transferManager = new BaseTransferManager()
try {
console.log('Loading transfer mappings from CSV...')
const transferMappings = loadTransferMappingsFromCSV()
console.log(`Found ${transferMappings.length} valid transfer mappings`)
if (transferMappings.length === 0) {
console.log('No valid transfer mappings found')
return
}
console.log('Starting batch transfer process...')
// Validate all addresses first
for (const mapping of transferMappings) {
if (!transferManager.isValidAddress(mapping.toAddress)) {
throw new Error(`Invalid destination address: ${mapping.toAddress}`)
}
}
// Process all transfers with error handling
const results = await transferManager.processMultipleTransfersWithResults(transferMappings)
console.log('\n=== Transfer Results ===')
results.forEach((txHash, index) => {
if (txHash) {
console.log(`Transfer ${index + 1}: Success - ${txHash}`)
} else {
console.log(`Transfer ${index + 1}: Failed`)
}
})
} catch (error) {
console.error('Error in main process:', error)
}
}
// Uncomment to run
main().catch(console.error);
export { BaseTransferManager, TransferMapping }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment