feat: add script to sweep all ETH balance to a new wallet
This commit is contained in:
parent
c94d767f0b
commit
c16e0ea776
4 changed files with 145 additions and 11 deletions
130
bin/lamassu-eth-sweep-to-new-wallet
Normal file
130
bin/lamassu-eth-sweep-to-new-wallet
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
#!/usr/bin/env node
|
||||
const hdkey = require('ethereumjs-wallet/hdkey')
|
||||
const hkdf = require('futoin-hkdf')
|
||||
const crypto = require('crypto')
|
||||
const path = require('path')
|
||||
const pify = require('pify')
|
||||
const fs = pify(require('fs'))
|
||||
const _ = require('lodash/fp')
|
||||
const { BigNumber } = require('bignumber.js')
|
||||
|
||||
const mnemonicHelpers = require('../lib/mnemonic-helpers')
|
||||
const options = require('../lib/options')
|
||||
const settingsLoader = require('../lib/new-settings-loader')
|
||||
const wallet = require('../lib/wallet')
|
||||
const BN = require('../lib/bn')
|
||||
|
||||
const defaultPrefixPath = "m/44'/60'/1'/0'"
|
||||
const paymentPrefixPath = "m/44'/60'/0'/0'"
|
||||
|
||||
function writeNewMnemonic (mnemonic) {
|
||||
return fs.writeFile(options.mnemonicPath, mnemonic)
|
||||
}
|
||||
|
||||
function backupMnemonic () {
|
||||
const folderPath = path.dirname(options.mnemonicPath)
|
||||
const fileName = path.resolve(folderPath, `mnemonic-${Date.now()}.txt`)
|
||||
return fs.copyFile(options.mnemonicPath, fileName)
|
||||
.then(() => fileName)
|
||||
}
|
||||
|
||||
function computeSeed (seed) {
|
||||
const masterSeed = mnemonicHelpers.toEntropyBuffer(seed)
|
||||
return hkdf(masterSeed, 32, { salt: 'lamassu-server-salt', info: 'wallet-seed' })
|
||||
}
|
||||
|
||||
function generateRandomSeed () {
|
||||
const seed = crypto
|
||||
.randomBytes(32)
|
||||
.toString('hex')
|
||||
|
||||
return Buffer.from(seed, 'hex')
|
||||
}
|
||||
|
||||
function generateNewMnemonic (newSeed) {
|
||||
return mnemonicHelpers.fromSeed(newSeed)
|
||||
}
|
||||
|
||||
function defaultWallet (seed) {
|
||||
return defaultHdNode(seed).deriveChild(0).getWallet()
|
||||
}
|
||||
|
||||
function defaultAddress (seed) {
|
||||
return defaultWallet(seed).getChecksumAddressString()
|
||||
}
|
||||
|
||||
function defaultHdNode (seed) {
|
||||
const key = hdkey.fromMasterSeed(seed)
|
||||
return key.derivePath(defaultPrefixPath)
|
||||
}
|
||||
|
||||
function getAllBalance () {
|
||||
return settingsLoader.loadLatest()
|
||||
.then(settings => wallet.balance(settings, 'ETH'))
|
||||
.then(r => r.balance)
|
||||
}
|
||||
|
||||
function isInfuraRunning (settings) {
|
||||
const isInfuraSelected = settings.config.wallets_ETH_wallet === 'infura'
|
||||
const isInfuraConfigured =
|
||||
!_.isNil(settings.accounts.infura)
|
||||
&& !_.isNil(settings.accounts.infura.apiKey)
|
||||
&& !_.isNil(settings.accounts.infura.apiSecret)
|
||||
&& !_.isNil(settings.accounts.infura.endpoint)
|
||||
|
||||
return isInfuraSelected && isInfuraConfigured
|
||||
}
|
||||
|
||||
function isGethRunning (settings) {
|
||||
return wallet.checkBlockchainStatus(settings, 'ETH')
|
||||
.then(res => res === 'ready')
|
||||
}
|
||||
|
||||
const seed = generateRandomSeed()
|
||||
const mnemonic = generateNewMnemonic(seed)
|
||||
const mnemonicSeed = computeSeed(mnemonic)
|
||||
const newAddress = defaultAddress(mnemonicSeed)
|
||||
|
||||
settingsLoader.loadLatest()
|
||||
.then(settings => Promise.all([isInfuraRunning(settings), isGethRunning(settings), settings]))
|
||||
.then(([infuraIsRunning, gethIsRunning, settings]) => {
|
||||
if (!infuraIsRunning && !gethIsRunning) {
|
||||
console.log('Neither geth nor Infura are running, so the script cannot be executed.')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
return Promise.all([getAllBalance(), settings])
|
||||
})
|
||||
.then(([balance, settings]) => {
|
||||
const tx = {
|
||||
cryptoCode: 'ETH',
|
||||
toAddress: newAddress,
|
||||
cryptoAtoms: BN(balance.times(0.99999).toFixed(0, BigNumber.ROUND_DOWN))
|
||||
}
|
||||
|
||||
const opts = {
|
||||
chainId: 1,
|
||||
nonce: 0,
|
||||
includesFee: true
|
||||
}
|
||||
|
||||
return wallet.sendCoins(settings, tx, opts)
|
||||
})
|
||||
.then(resTx => {
|
||||
console.log('Successfully moved funds from the old wallet to the new one.')
|
||||
console.log('Information about the transaction', resTx)
|
||||
return backupMnemonic()
|
||||
})
|
||||
.then(fileName => {
|
||||
console.log(`Successfully backed up the old mnemonic, new location is ${fileName}`)
|
||||
return writeNewMnemonic(mnemonic)
|
||||
})
|
||||
.then(() => {
|
||||
console.log('New mnemonic stored successfully! All your funds (minus the transaction fee) should be available in the next few minutes.')
|
||||
console.log('Process finished successfully!')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
|
|
@ -158,7 +158,8 @@ function run () {
|
|||
const validateAnswers = async (answers) => {
|
||||
if (_.size(answers) > 2) return { message: `Please insert a maximum of two coins to install.`, isValid: false }
|
||||
return getBlockchainSyncStatus(cryptos)
|
||||
.then(blockchainStatuses => {
|
||||
.then(_blockchainStatuses => {
|
||||
const blockchainStatuses = _.filter(it => it !== 'disconnected', _blockchainStatuses)
|
||||
const result = _.reduce((acc, value) => ({ ...acc, [value]: _.isNil(acc[value]) ? 1 : acc[value] + 1 }), {}, _.values(blockchainStatuses))
|
||||
if (_.size(answers) + result.syncing > 2) {
|
||||
return { message: `Installing these coins would pass the 2 parallel blockchain synchronization limit. Please try again with fewer coins or try again later.`, isValid: false }
|
||||
|
|
|
|||
|
|
@ -50,9 +50,10 @@ function isStrictAddress (cryptoCode, toAddress, settings, operatorId) {
|
|||
return cryptoCode === 'ETH' && util.isValidChecksumAddress(toAddress)
|
||||
}
|
||||
|
||||
function sendCoins (account, tx, settings, operatorId) {
|
||||
function sendCoins (account, tx, settings, operatorId, feeMultiplier, _opts) {
|
||||
const { toAddress, cryptoAtoms, cryptoCode } = tx
|
||||
return generateTx(toAddress, defaultWallet(account), cryptoAtoms, false, cryptoCode)
|
||||
const opts = { ..._opts, includesFee: _.defaultTo(false, _opts?.includesFee) }
|
||||
return generateTx(toAddress, defaultWallet(account), cryptoAtoms, cryptoCode, opts)
|
||||
.then(pify(web3.eth.sendRawTransaction))
|
||||
.then(txid => {
|
||||
return pify(web3.eth.getTransaction)(txid)
|
||||
|
|
@ -95,7 +96,7 @@ function _balance (includePending, address, cryptoCode) {
|
|||
.then(balance => balance ? BN(balance) : BN(0))
|
||||
}
|
||||
|
||||
function generateTx (_toAddress, wallet, amount, includesFee, cryptoCode) {
|
||||
function generateTx (_toAddress, wallet, amount, cryptoCode, opts) {
|
||||
const fromAddress = '0x' + wallet.getAddress().toString('hex')
|
||||
|
||||
const isErc20Token = coins.utils.isErc20Token(cryptoCode)
|
||||
|
|
@ -125,18 +126,18 @@ function generateTx (_toAddress, wallet, amount, includesFee, cryptoCode) {
|
|||
.then(([gas, gasPrice, txCount]) => [
|
||||
BN(gas),
|
||||
BN(gasPrice),
|
||||
_.max([0, txCount, lastUsedNonces[fromAddress] + 1])
|
||||
_.max([1, txCount, lastUsedNonces[fromAddress] + 1])
|
||||
])
|
||||
.then(([gas, gasPrice, txCount]) => {
|
||||
lastUsedNonces[fromAddress] = txCount
|
||||
|
||||
const toSend = includesFee
|
||||
const toSend = opts.includesFee
|
||||
? amount.minus(gasPrice.times(gas))
|
||||
: amount
|
||||
|
||||
const rawTx = {
|
||||
chainId: 1,
|
||||
nonce: txCount,
|
||||
chainId: _.defaultTo(1, opts?.chainId),
|
||||
nonce: _.defaultTo(txCount, opts?.nonce),
|
||||
gasPrice: hex(gasPrice),
|
||||
gasLimit: hex(gas),
|
||||
to: toAddress,
|
||||
|
|
@ -172,8 +173,9 @@ function sweep (account, cryptoCode, hdIndex, settings, operatorId) {
|
|||
return confirmedBalance(fromAddress, cryptoCode)
|
||||
.then(r => {
|
||||
if (r.eq(0)) return
|
||||
const opts = { includesFee: true }
|
||||
|
||||
return generateTx(defaultAddress(account), wallet, r, true, cryptoCode)
|
||||
return generateTx(defaultAddress(account), wallet, r, cryptoCode, opts)
|
||||
.then(signedTx => pify(web3.eth.sendRawTransaction)(signedTx))
|
||||
})
|
||||
}
|
||||
|
|
@ -237,4 +239,5 @@ function checkBlockchainStatus (cryptoCode) {
|
|||
.then(() => connect(`http://localhost:${coins.utils.getCryptoCurrency(cryptoCode).defaultPort}`))
|
||||
.then(() => web3.eth.syncing)
|
||||
.then(res => res === false ? 'ready' : 'syncing')
|
||||
.catch(() => 'disconnected')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,11 +59,11 @@ function _balance (settings, cryptoCode) {
|
|||
})
|
||||
}
|
||||
|
||||
function sendCoins (settings, tx) {
|
||||
function sendCoins (settings, tx, opts) {
|
||||
return fetchWallet(settings, tx.cryptoCode)
|
||||
.then(r => {
|
||||
const feeMultiplier = new BN(configManager.getWalletSettings(tx.cryptoCode, settings.config).feeMultiplier)
|
||||
return r.wallet.sendCoins(r.account, tx, settings, r.operatorId, feeMultiplier)
|
||||
return r.wallet.sendCoins(r.account, tx, settings, r.operatorId, feeMultiplier, opts)
|
||||
.then(res => {
|
||||
mem.clear(module.exports.balance)
|
||||
return res
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue