Merge remote-tracking branch 'upstream/release-8.0' into release-8.1
This commit is contained in:
commit
9a22d7e976
7 changed files with 324 additions and 13 deletions
295
bin/lamassu-eth-sweep-to-new-wallet
Normal file
295
bin/lamassu-eth-sweep-to-new-wallet
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
#!/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 coins = require('@lamassu/coins')
|
||||||
|
const Web3 = require('web3')
|
||||||
|
const web3 = new Web3()
|
||||||
|
const Tx = require('ethereumjs-tx')
|
||||||
|
|
||||||
|
const mnemonicHelpers = require('../lib/mnemonic-helpers')
|
||||||
|
const options = require('../lib/options')
|
||||||
|
const settingsLoader = require('../lib/new-settings-loader')
|
||||||
|
const BN = require('../lib/bn')
|
||||||
|
const ph = require('../lib/plugin-helper')
|
||||||
|
const configManager = require('../lib/new-config-manager')
|
||||||
|
const walletI = require('../lib/wallet')
|
||||||
|
|
||||||
|
const LOCKFILE_PATH = '/var/lock/lamassu-eth-pending-sweep'
|
||||||
|
const defaultPrefixPath = "m/44'/60'/1'/0'"
|
||||||
|
let lastUsedNonces = {}
|
||||||
|
|
||||||
|
const hex = bigNum => '0x' + bigNum.integerValue(BN.ROUND_DOWN).toString(16)
|
||||||
|
|
||||||
|
function writeNewMnemonic (mnemonic) {
|
||||||
|
return fs.writeFile(`${options.mnemonicPath}-new-temp`, mnemonic)
|
||||||
|
.then(() => `${options.mnemonicPath}-new-temp`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function renameNewMnemonic () {
|
||||||
|
return fs.rename(`${options.mnemonicPath}-new-temp`, `${options.mnemonicPath}`)
|
||||||
|
.then(() => options.mnemonicPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 computeOperatorId (masterSeed) {
|
||||||
|
return hkdf(masterSeed, 16, { salt: 'lamassu-server-salt', info: 'operator-id' }).toString('hex')
|
||||||
|
}
|
||||||
|
|
||||||
|
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 defaultWalletAcc (account) {
|
||||||
|
return defaultHdNodeAcc(account).deriveChild(0).getWallet()
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultAddress (seed) {
|
||||||
|
return defaultWallet(seed).getChecksumAddressString()
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultHdNode (seed) {
|
||||||
|
const key = hdkey.fromMasterSeed(seed)
|
||||||
|
return key.derivePath(defaultPrefixPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultHdNodeAcc (account) {
|
||||||
|
const key = hdkey.fromMasterSeed(account.seed)
|
||||||
|
return key.derivePath(defaultPrefixPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllBalance () {
|
||||||
|
return settingsLoader.loadLatest()
|
||||||
|
.then(settings => walletI.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 walletI.checkBlockchainStatus(settings, 'ETH')
|
||||||
|
.then(res => res === 'ready')
|
||||||
|
.catch(() => false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect (url) {
|
||||||
|
if (!web3.isConnected()) {
|
||||||
|
web3.setProvider(new web3.providers.HttpProvider(url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendCoins (account, tx, settings, operatorId, feeMultiplier, _opts) {
|
||||||
|
const { toAddress, cryptoAtoms, cryptoCode } = tx
|
||||||
|
const opts = { ..._opts, includesFee: _.defaultTo(false, _opts?.includesFee) }
|
||||||
|
return generateTx(toAddress, defaultWalletAcc(account), cryptoAtoms, cryptoCode, opts)
|
||||||
|
.then(pify(web3.eth.sendRawTransaction))
|
||||||
|
.then(txid => {
|
||||||
|
return pify(web3.eth.getTransaction)(txid)
|
||||||
|
.then(tx => {
|
||||||
|
if (!tx) return { txid }
|
||||||
|
|
||||||
|
const fee = new BN(tx.gas).times(new BN(tx.gasPrice)).decimalPlaces(0)
|
||||||
|
|
||||||
|
return { txid, fee }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTx (_toAddress, wallet, amount, cryptoCode, opts) {
|
||||||
|
const fromAddress = '0x' + wallet.getAddress().toString('hex')
|
||||||
|
|
||||||
|
const isErc20Token = coins.utils.isErc20Token(cryptoCode)
|
||||||
|
const toAddress = isErc20Token ? coins.utils.getErc20Token(cryptoCode).contractAddress : _toAddress.toLowerCase()
|
||||||
|
|
||||||
|
let contract, contractData
|
||||||
|
if (isErc20Token) {
|
||||||
|
contract = web3.eth.contract(ABI.ERC20).at(toAddress)
|
||||||
|
contractData = isErc20Token && contract.transfer.getData(_toAddress.toLowerCase(), hex(toSend))
|
||||||
|
}
|
||||||
|
|
||||||
|
const txTemplate = {
|
||||||
|
from: fromAddress,
|
||||||
|
to: toAddress,
|
||||||
|
value: amount.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isErc20Token) txTemplate.data = contractData
|
||||||
|
|
||||||
|
const promises = [
|
||||||
|
pify(web3.eth.estimateGas)(txTemplate),
|
||||||
|
pify(web3.eth.getGasPrice)(),
|
||||||
|
pify(web3.eth.getTransactionCount)(fromAddress)
|
||||||
|
]
|
||||||
|
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then(([gas, gasPrice, txCount]) => [
|
||||||
|
BN(gas),
|
||||||
|
BN(gasPrice),
|
||||||
|
_.max([0, txCount, lastUsedNonces[fromAddress] + 1])
|
||||||
|
])
|
||||||
|
.then(([gas, gasPrice, txCount]) => {
|
||||||
|
lastUsedNonces[fromAddress] = txCount
|
||||||
|
|
||||||
|
const toSend = opts.includesFee
|
||||||
|
? amount.minus(gasPrice.times(gas))
|
||||||
|
: amount
|
||||||
|
|
||||||
|
const rawTx = {
|
||||||
|
chainId: _.defaultTo(1, opts?.chainId),
|
||||||
|
nonce: _.defaultTo(txCount, opts?.nonce),
|
||||||
|
gasPrice: hex(gasPrice),
|
||||||
|
gasLimit: hex(gas),
|
||||||
|
to: toAddress,
|
||||||
|
from: fromAddress,
|
||||||
|
value: isErc20Token ? hex(BN(0)) : hex(toSend)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isErc20Token) {
|
||||||
|
rawTx.data = contractData
|
||||||
|
}
|
||||||
|
|
||||||
|
const tx = new Tx(rawTx)
|
||||||
|
const privateKey = wallet.getPrivateKey()
|
||||||
|
|
||||||
|
tx.sign(privateKey)
|
||||||
|
|
||||||
|
return '0x' + tx.serialize().toString('hex')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchWallet (settings, cryptoCode) {
|
||||||
|
return fs.readFile(options.mnemonicPath, 'utf8')
|
||||||
|
.then(mnemonic => {
|
||||||
|
const computeSeed = masterSeed =>
|
||||||
|
hkdf(masterSeed, 32, { salt: 'lamassu-server-salt', info: 'wallet-seed' })
|
||||||
|
|
||||||
|
const masterSeed = mnemonicHelpers.toEntropyBuffer(mnemonic)
|
||||||
|
const plugin = configManager.getWalletSettings(cryptoCode, settings.config).wallet
|
||||||
|
const wallet = ph.load(ph.WALLET, plugin)
|
||||||
|
const rawAccount = settings.accounts[plugin]
|
||||||
|
const account = _.set('seed', computeSeed(masterSeed), rawAccount)
|
||||||
|
if (_.isFunction(wallet.run)) wallet.run(account)
|
||||||
|
const operatorId = computeOperatorId(masterSeed)
|
||||||
|
return { wallet, account, operatorId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.exists(LOCKFILE_PATH, function(exists) {
|
||||||
|
if (!exists) {
|
||||||
|
console.log('Couldn\'t find the lamassu-eth-pending-sweep lock file, exiting...')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Backing up old mnemonic...`)
|
||||||
|
return Promise.all([backupMnemonic(), infuraIsRunning, settings])
|
||||||
|
})
|
||||||
|
.then(([fileName, infuraIsRunning, settings]) => {
|
||||||
|
console.log(`Successfully backed up the old mnemonic, new location is ${fileName}`)
|
||||||
|
return Promise.all([writeNewMnemonic(mnemonic), infuraIsRunning, settings])
|
||||||
|
})
|
||||||
|
.then(([tempMnemonicFileName, infuraIsRunning, settings]) => {
|
||||||
|
console.log(`New mnemonic stored temporarily in ${tempMnemonicFileName}`)
|
||||||
|
console.log(`Starting funds transfer...`)
|
||||||
|
return Promise.all([infuraIsRunning, settings])
|
||||||
|
})
|
||||||
|
.then(([infuraIsRunning, settings]) => {
|
||||||
|
if (infuraIsRunning) {
|
||||||
|
const endpoint = _.startsWith('https://')(settings.accounts.infura.endpoint)
|
||||||
|
? settings.accounts.infura.endpoint
|
||||||
|
: `https://${settings.accounts.infura.endpoint}`
|
||||||
|
connect(endpoint)
|
||||||
|
} else {
|
||||||
|
connect(`http://localhost:${coins.utils.getCryptoCurrency('ETH').defaultPort}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all([getAllBalance(), settings, fetchWallet(settings, 'ETH')])
|
||||||
|
})
|
||||||
|
.then(([balance, settings, { account, operatorId }]) => {
|
||||||
|
const tx = {
|
||||||
|
cryptoCode: 'ETH',
|
||||||
|
toAddress: newAddress,
|
||||||
|
cryptoAtoms: BN(balance.times(0.99999).toFixed(0, BigNumber.ROUND_DOWN))
|
||||||
|
}
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
chainId: 3,
|
||||||
|
nonce: 0,
|
||||||
|
includesFee: true
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendCoins(account, tx, settings, operatorId, null, opts)
|
||||||
|
})
|
||||||
|
.then(resTx => {
|
||||||
|
console.log('Successfully moved funds from the old wallet to the new one.')
|
||||||
|
console.log('Information about the transaction', resTx)
|
||||||
|
console.log('Moving the current mnemonic to the default file...')
|
||||||
|
return renameNewMnemonic()
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('New mnemonic stored successfully! All your funds (minus the transaction fee) should be available in the next few minutes.')
|
||||||
|
return fs.rmdir(LOCKFILE_PATH)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('lamassu-eth-pending-sweep lock file successfully removed')
|
||||||
|
return fs.mkdir(`${LOCKFILE_PATH}-finished`)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('lamassu-eth-pending-sweep-finished lock file successfully created, this will automatically be deleted once the upgrade script finishes running')
|
||||||
|
console.log('Process finished successfully! You may now execute the upgrade script again')
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
|
@ -28,6 +28,13 @@ function updateCore (coinRec, isCurrentlyRunning) {
|
||||||
common.es(`rm -r /tmp/${coinRec.dir.replace('/bin', '')}`)
|
common.es(`rm -r /tmp/${coinRec.dir.replace('/bin', '')}`)
|
||||||
common.es(`rm /tmp/bitcoincash.tar.gz`)
|
common.es(`rm /tmp/bitcoincash.tar.gz`)
|
||||||
|
|
||||||
|
if (common.es(`grep "listenonion=" /mnt/blockchains/bitcoincash/bitcoincash.conf || true`)) {
|
||||||
|
common.logger.info(`listenonion already defined, skipping...`)
|
||||||
|
} else {
|
||||||
|
common.logger.info(`Setting 'listenonion=0' in config file...`)
|
||||||
|
common.es(`echo "\nlistenonion=0" >> /mnt/blockchains/bitcoincash/bitcoincash.conf`)
|
||||||
|
}
|
||||||
|
|
||||||
if (isCurrentlyRunning) {
|
if (isCurrentlyRunning) {
|
||||||
common.logger.info('Starting wallet...')
|
common.logger.info('Starting wallet...')
|
||||||
common.es(`sudo supervisorctl start bitcoincash`)
|
common.es(`sudo supervisorctl start bitcoincash`)
|
||||||
|
|
@ -47,5 +54,6 @@ prune=4000
|
||||||
daemon=0
|
daemon=0
|
||||||
bind=0.0.0.0:8335
|
bind=0.0.0.0:8335
|
||||||
rpcport=8336
|
rpcport=8336
|
||||||
|
listenonion=0
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ const BINARIES = {
|
||||||
dir: 'bitcoin-23.0/bin'
|
dir: 'bitcoin-23.0/bin'
|
||||||
},
|
},
|
||||||
ETH: {
|
ETH: {
|
||||||
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.10.17-25c9b49f.tar.gz',
|
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.10.19-23bee162.tar.gz',
|
||||||
dir: 'geth-linux-amd64-1.10.17-25c9b49f'
|
dir: 'geth-linux-amd64-1.10.19-23bee162'
|
||||||
},
|
},
|
||||||
ZEC: {
|
ZEC: {
|
||||||
url: 'https://z.cash/downloads/zcash-5.0.0-linux64-debian-bullseye.tar.gz',
|
url: 'https://z.cash/downloads/zcash-5.0.0-linux64-debian-bullseye.tar.gz',
|
||||||
|
|
@ -43,12 +43,12 @@ const BINARIES = {
|
||||||
LTC: {
|
LTC: {
|
||||||
defaultUrl: 'https://download.litecoin.org/litecoin-0.18.1/linux/litecoin-0.18.1-x86_64-linux-gnu.tar.gz',
|
defaultUrl: 'https://download.litecoin.org/litecoin-0.18.1/linux/litecoin-0.18.1-x86_64-linux-gnu.tar.gz',
|
||||||
defaultDir: 'litecoin-0.18.1/bin',
|
defaultDir: 'litecoin-0.18.1/bin',
|
||||||
url: 'https://download.litecoin.org/litecoin-0.21.2/linux/litecoin-0.21.2-x86_64-linux-gnu.tar.gz',
|
url: 'https://download.litecoin.org/litecoin-0.21.2.1/linux/litecoin-0.21.2.1-x86_64-linux-gnu.tar.gz',
|
||||||
dir: 'litecoin-0.21.2/bin'
|
dir: 'litecoin-0.21.2.1/bin'
|
||||||
},
|
},
|
||||||
BCH: {
|
BCH: {
|
||||||
url: 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v24.0.0/bitcoin-cash-node-24.0.0-x86_64-linux-gnu.tar.gz',
|
url: 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v24.1.0/bitcoin-cash-node-24.1.0-x86_64-linux-gnu.tar.gz',
|
||||||
dir: 'bitcoin-cash-node-24.0.0/bin',
|
dir: 'bitcoin-cash-node-24.1.0/bin',
|
||||||
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
|
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
|
||||||
},
|
},
|
||||||
XMR: {
|
XMR: {
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,9 @@ function run () {
|
||||||
name,
|
name,
|
||||||
value: c.code,
|
value: c.code,
|
||||||
checked,
|
checked,
|
||||||
disabled: checked && 'Installed'
|
disabled: c.cryptoCode === 'ETH'
|
||||||
|
? 'Use admin\'s Infura plugin'
|
||||||
|
: checked && 'Installed'
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
])(cryptos)
|
])(cryptos)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ const pify = require('pify')
|
||||||
const BN = require('../../../bn')
|
const BN = require('../../../bn')
|
||||||
const ABI = require('../../tokens')
|
const ABI = require('../../tokens')
|
||||||
|
|
||||||
const NAME = 'geth'
|
|
||||||
exports.SUPPORTED_MODULES = ['wallet']
|
exports.SUPPORTED_MODULES = ['wallet']
|
||||||
|
|
||||||
const paymentPrefixPath = "m/44'/60'/0'/0'"
|
const paymentPrefixPath = "m/44'/60'/0'/0'"
|
||||||
|
|
@ -20,7 +19,6 @@ const defaultPrefixPath = "m/44'/60'/1'/0'"
|
||||||
let lastUsedNonces = {}
|
let lastUsedNonces = {}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
NAME,
|
|
||||||
balance,
|
balance,
|
||||||
sendCoins,
|
sendCoins,
|
||||||
newAddress,
|
newAddress,
|
||||||
|
|
@ -49,7 +47,7 @@ function isStrictAddress (cryptoCode, toAddress, settings, operatorId) {
|
||||||
return cryptoCode === 'ETH' && util.isValidChecksumAddress(toAddress)
|
return cryptoCode === 'ETH' && util.isValidChecksumAddress(toAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendCoins (account, tx, settings, operatorId) {
|
function sendCoins (account, tx, settings, operatorId, feeMultiplier) {
|
||||||
const { toAddress, cryptoAtoms, cryptoCode } = tx
|
const { toAddress, cryptoAtoms, cryptoCode } = tx
|
||||||
return generateTx(toAddress, defaultWallet(account), cryptoAtoms, false, cryptoCode)
|
return generateTx(toAddress, defaultWallet(account), cryptoAtoms, false, cryptoCode)
|
||||||
.then(pify(web3.eth.sendSignedTransaction))
|
.then(pify(web3.eth.sendSignedTransaction))
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
const base = require('./base')
|
const base = require('./base')
|
||||||
|
|
||||||
const { utils: coinUtils } = require('@lamassu/coins')
|
const { utils: coinUtils } = require('@lamassu/coins')
|
||||||
const cryptoRec = coinUtils.getCryptoCurrency('ETH')
|
const cryptoRec = coinUtils.getCryptoCurrency('ETH')
|
||||||
const defaultPort = cryptoRec.defaultPort
|
const defaultPort = cryptoRec.defaultPort
|
||||||
|
|
||||||
base.connect(`http://localhost:${defaultPort}`)
|
const NAME = 'geth'
|
||||||
|
|
||||||
module.exports = base
|
function run (account) {
|
||||||
|
base.connect(`http://localhost:${defaultPort}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = _.merge(base, { NAME, run })
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const base = require('../geth/base')
|
const base = require('../geth/base')
|
||||||
|
|
||||||
|
const NAME = 'infura'
|
||||||
|
|
||||||
function run (account) {
|
function run (account) {
|
||||||
if (!account.endpoint) throw new Error('Need to configure API endpoint for Infura')
|
if (!account.endpoint) throw new Error('Need to configure API endpoint for Infura')
|
||||||
|
|
||||||
|
|
@ -10,4 +12,4 @@ function run (account) {
|
||||||
base.connect(endpoint)
|
base.connect(endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = _.merge(base, {run})
|
module.exports = _.merge(base, { NAME, run })
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue