chore: use monorepo organization
This commit is contained in:
parent
deaf7d6ecc
commit
a687827f7e
1099 changed files with 8184 additions and 11535 deletions
9
packages/server/bin/bip39
Executable file
9
packages/server/bin/bip39
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict'
|
||||
|
||||
const mnemonicHelpers = require('../lib/mnemonic-helpers')
|
||||
|
||||
const seed = process.argv[2]
|
||||
|
||||
console.log(mnemonicHelpers.fromSeed(seed))
|
||||
17
packages/server/bin/hkdf
Executable file
17
packages/server/bin/hkdf
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict'
|
||||
|
||||
const hkdf = require('futoin-hkdf')
|
||||
|
||||
const label = process.argv[2]
|
||||
const masterSeedHex = process.argv[3].trim()
|
||||
|
||||
if (process.argv.length !== 4) {
|
||||
console.error('hdkf <label> <masterKey>')
|
||||
console.error('masterKey should be in hex encoding.')
|
||||
process.exit(3)
|
||||
}
|
||||
|
||||
const masterSeed = Buffer.from(masterSeedHex, 'hex')
|
||||
console.log(hkdf(masterSeed, 32, { salt: 'lamassu-server-salt', info: label }).toString('hex'))
|
||||
5
packages/server/bin/lamassu-admin-server
Executable file
5
packages/server/bin/lamassu-admin-server
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const adminServer = require('../lib/new-admin/admin-server')
|
||||
|
||||
adminServer.run()
|
||||
26
packages/server/bin/lamassu-admin-server-entrypoint.sh
Normal file
26
packages/server/bin/lamassu-admin-server-entrypoint.sh
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
CERT_FILES=(
|
||||
/lamassu-data/certs/{Lamassu_CA,Lamassu_OP,Lamassu_OP_Root_CA}.pem
|
||||
/lamassu-data/certs/Lamassu_OP_Root_CA.srl
|
||||
/lamassu-data/private/{Lamassu_OP,Lamassu_OP_Root_CA}.key
|
||||
)
|
||||
|
||||
if ! (( ${#CERT_FILES[@]} == $(ls "${CERT_FILES[@]}" 2>/dev/null | wc -l) )); then
|
||||
echo "Some certificates are missing. Retrying in 5 seconds"
|
||||
sleep 5
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Update certs on alpine"
|
||||
cp /lamassu-data/certs/Lamassu_CA.pem /usr/local/share/ca-certificates
|
||||
cp /lamassu-data/certs/Lamassu_OP_Root_CA.pem /usr/local/share/ca-certificates
|
||||
update-ca-certificates
|
||||
|
||||
if [ "${LAMASSU_DEV_MODE}" = "true" ]; then
|
||||
echo "Starting in dev mode"
|
||||
node /lamassu-server/bin/lamassu-admin-server --lamassuDev
|
||||
else
|
||||
node /lamassu-server/bin/lamassu-admin-server
|
||||
fi
|
||||
17
packages/server/bin/lamassu-backup-pg
Executable file
17
packages/server/bin/lamassu-backup-pg
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if [ "$(whoami)" != "root" ]; then
|
||||
echo -e "This script has to be run as \033[1mroot\033[0m user"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
DAYS_TO_KEEP=3
|
||||
DATE=$(date --utc +%F_%H-%M)
|
||||
BACKUP_DIR=/var/backups/postgresql
|
||||
BACKUP_FILE=$BACKUP_DIR/backup-$DATE.sql.gz
|
||||
|
||||
cd ~postgres
|
||||
su postgres -c "pg_dump lamassu" | gzip > $BACKUP_FILE
|
||||
cd
|
||||
find $BACKUP_DIR -maxdepth 1 -mtime +$DAYS_TO_KEEP -exec rm -f '{}' ';'
|
||||
47
packages/server/bin/lamassu-btc-bumpfee
Normal file
47
packages/server/bin/lamassu-btc-bumpfee
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const inquirer = require('inquirer')
|
||||
|
||||
const bitcoind = require('../lib/plugins/wallet/bitcoind/bitcoind')
|
||||
const BN = require('../lib/bn')
|
||||
const mempool = require('../lib/blockexplorers/mempool.space')
|
||||
|
||||
const txId = process.argv[2]
|
||||
if (!txId) {
|
||||
console.error('Please provide a BTC transaction hash as input.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const bumpTransactionFee = async (txId) => {
|
||||
const txData = await bitcoind.fetch('gettransaction', [txId, true, true])
|
||||
|
||||
const fee = new BN(txData.fee).abs().shiftedBy(8).decimalPlaces(0)
|
||||
const size = txData.decoded.vsize
|
||||
const satPerVb = fee.div(size)
|
||||
|
||||
console.log(`Current fee: ${satPerVb.toFixed(2).toString()} sat/vB`)
|
||||
|
||||
const recommendedFees = await mempool.getSatBEstimateFees()
|
||||
|
||||
console.log('Recommended fees (sat/vB):', recommendedFees)
|
||||
|
||||
const { selectedFee } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'selectedFee',
|
||||
message: 'Select a fee higher than the current one:',
|
||||
choices: Object.entries(recommendedFees)
|
||||
.filter(([_, value]) => satPerVb.lt(value))
|
||||
.map(([key, value]) => ({name: `${key}: ${value} sat/vB`, value})),
|
||||
},
|
||||
])
|
||||
|
||||
const { txid } = await bitcoind.fetch('bumpfee', [txId, {fee_rate: selectedFee}])
|
||||
|
||||
console.log(`
|
||||
Fee bumped to ${selectedFee.toFixed(2)} sat/vB
|
||||
Transaction ID: ${txid}
|
||||
`)
|
||||
}
|
||||
|
||||
bumpTransactionFee(txId)
|
||||
43
packages/server/bin/lamassu-clean-parsed-id
Normal file
43
packages/server/bin/lamassu-clean-parsed-id
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const argv = require('minimist')(process.argv.slice(2))
|
||||
const _ = require('lodash')
|
||||
const db = require('../lib/db')
|
||||
|
||||
const txId = argv.tx
|
||||
const customerId = argv.customer
|
||||
|
||||
if ((!txId && !customerId) || (txId && customerId)) {
|
||||
console.log('Usage: lamassu-clean-parsed-id [--tx <txId> | --customer <customerId>]')
|
||||
console.log('The command can only be run with EITHER --tx OR --customer, NOT BOTH')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
if (!_.isNil(txId)) {
|
||||
db.oneOrNone('SELECT * FROM (SELECT id, customer_id FROM cash_in_txs UNION SELECT id, customer_id FROM cash_out_txs) as txs WHERE txs.id = $1', [txId])
|
||||
.then(res => {
|
||||
return db.none('UPDATE customers SET id_card_data = null WHERE id = $1', [res.customer_id])
|
||||
.then(() => {
|
||||
console.log(`ID card data from customer ${res.customer_id} was cleared with success`)
|
||||
process.exit(0)
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
console.log('A transaction with that ID was not found')
|
||||
process.exit(0)
|
||||
})
|
||||
}
|
||||
|
||||
if (!_.isNil(customerId)) {
|
||||
db.none('UPDATE customers SET id_card_data = null WHERE id = $1', [customerId])
|
||||
.then(() => {
|
||||
console.log(`ID card data from customer ${customerId} was cleared with success`)
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(() => {
|
||||
console.log('A customer with that ID was not found')
|
||||
process.exit(0)
|
||||
})
|
||||
}
|
||||
11
packages/server/bin/lamassu-coinatmradar
Normal file
11
packages/server/bin/lamassu-coinatmradar
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
echo
|
||||
echo "Here is the 'External ID' of your paired machine(s), for use under the 'ATM / Teller details' of your CoinATMRadar listing:"
|
||||
echo
|
||||
su - postgres -c "psql \"lamassu\" -Atc \"select regexp_replace(device_id, '$', ' '),regexp_replace(name, '^', ' ') from devices\""
|
||||
echo
|
||||
echo "If speaking with CoinATMRadar directly, it may be helpful to let them know your 'Operator ID':"
|
||||
echo
|
||||
$(npm root -g)/lamassu-server/bin/lamassu-operator
|
||||
echo
|
||||
7
packages/server/bin/lamassu-coins
Executable file
7
packages/server/bin/lamassu-coins
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const install = require('../lib/blockchain/install')
|
||||
|
||||
install.run()
|
||||
11
packages/server/bin/lamassu-configure-frontcamera
Executable file
11
packages/server/bin/lamassu-configure-frontcamera
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict'
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const setEnvVariable = require('../tools/set-env-var')
|
||||
|
||||
if (!process.env.FRONT_CAMERA_DIR) {
|
||||
setEnvVariable('FRONT_CAMERA_DIR', '/opt/lamassu-server/frontcamera')
|
||||
}
|
||||
7
packages/server/bin/lamassu-devices
Normal file
7
packages/server/bin/lamassu-devices
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
echo
|
||||
echo "Your list of paired machines and their Device IDs and names:"
|
||||
echo
|
||||
su - postgres -c "psql \"lamassu\" -Atc \"select device_id, name from devices\""
|
||||
echo
|
||||
67
packages/server/bin/lamassu-eth-recovery
Normal file
67
packages/server/bin/lamassu-eth-recovery
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
const hdkey = require('ethereumjs-wallet/hdkey')
|
||||
const hkdf = require('futoin-hkdf')
|
||||
const db = require('../lib/db')
|
||||
const _ = require('lodash/fp')
|
||||
const mnemonicHelpers = require('../lib/mnemonic-helpers')
|
||||
|
||||
const pify = require('pify')
|
||||
const fs = pify(require('fs'))
|
||||
const os = require('os')
|
||||
|
||||
const MNEMONIC_PATH = process.env.MNEMONIC_PATH
|
||||
|
||||
const defaultPrefixPath = "m/44'/60'/1'/0'"
|
||||
const paymentPrefixPath = "m/44'/60'/0'/0'"
|
||||
|
||||
const address = process.argv[2]
|
||||
|
||||
if (!MNEMONIC_PATH) {
|
||||
console.error(`Unable to fetch mnemonic from your account!`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!address) {
|
||||
console.log('Usage: lamassu-eth-recovery <cash-out address>')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
function run (address) {
|
||||
Promise.all([fetchMnemonic(), searchForHdIndex(address)])
|
||||
.then(([mnemonic, hdIndex]) => {
|
||||
try {
|
||||
const prefix = !_.isNil(hdIndex) ? paymentPrefixPath : defaultPrefixPath
|
||||
console.log(`Private key: `, defaultHdNode(mnemonic, prefix).deriveChild(hdIndex).getWallet().getPrivateKeyString())
|
||||
process.exit(0)
|
||||
} catch (err) {
|
||||
console.error(`Error while retrieving private key!`)
|
||||
process.exit(3)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function searchForHdIndex (address) {
|
||||
const sql = `SELECT hd_index FROM cash_out_txs WHERE to_address = $1`
|
||||
return db.oneOrNone(sql, [address])
|
||||
.then(result => _.get('hd_index', result))
|
||||
}
|
||||
|
||||
function fetchMnemonic () {
|
||||
return fs.readFile(MNEMONIC_PATH, 'utf8')
|
||||
.then(mnemonic => computeSeed(mnemonic))
|
||||
}
|
||||
|
||||
function computeSeed (seed) {
|
||||
const masterSeed = mnemonicHelpers.toEntropyBuffer(seed)
|
||||
return hkdf(masterSeed, 32, { salt: 'lamassu-server-salt', info: 'wallet-seed' })
|
||||
}
|
||||
|
||||
function defaultHdNode (masterSeed, prefix) {
|
||||
if (!masterSeed) throw new Error('No master seed!')
|
||||
const key = hdkey.fromMasterSeed(masterSeed)
|
||||
return key.derivePath(prefix)
|
||||
}
|
||||
|
||||
run(address)
|
||||
298
packages/server/bin/lamassu-eth-sweep-to-new-wallet
Normal file
298
packages/server/bin/lamassu-eth-sweep-to-new-wallet
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
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 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)
|
||||
const MNEMONIC_PATH = process.env.MNEMONIC_PATH
|
||||
|
||||
function writeNewMnemonic (mnemonic) {
|
||||
return fs.writeFile(`${MNEMONIC_PATH}-new-temp`, mnemonic)
|
||||
.then(() => `${MNEMONIC_PATH}-new-temp`)
|
||||
}
|
||||
|
||||
function renameNewMnemonic () {
|
||||
return fs.rename(`${MNEMONIC_PATH}-new-temp`, `${MNEMONIC_PATH}`)
|
||||
.then(() => MNEMONIC_PATH)
|
||||
}
|
||||
|
||||
function backupMnemonic () {
|
||||
const folderPath = path.dirname(MNEMONIC_PATH)
|
||||
const fileName = path.resolve(folderPath, `mnemonic-${Date.now()}.txt`)
|
||||
return fs.copyFile(MNEMONIC_PATH, 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(MNEMONIC_PATH, '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: 1,
|
||||
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)
|
||||
})
|
||||
25
packages/server/bin/lamassu-migrate
Executable file
25
packages/server/bin/lamassu-migrate
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env node
|
||||
const _ = require('lodash/fp')
|
||||
|
||||
require('../lib/environment-helper')
|
||||
const db = require('../lib/db')
|
||||
const migrate = require('../lib/migrate')
|
||||
|
||||
const createMigration = `CREATE TABLE IF NOT EXISTS migrations (
|
||||
id serial PRIMARY KEY,
|
||||
data json NOT NULL
|
||||
)`
|
||||
|
||||
// no need to log the migration process
|
||||
process.env.SKIP_SERVER_LOGS = true
|
||||
|
||||
db.none(createMigration)
|
||||
.then(() => migrate.run())
|
||||
.then(() => {
|
||||
console.log('DB Migration succeeded.')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('DB Migration failed: %s', err)
|
||||
process.exit(1)
|
||||
})
|
||||
9
packages/server/bin/lamassu-mnemonic
Executable file
9
packages/server/bin/lamassu-mnemonic
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs')
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const MNEMONIC_PATH = process.env.MNEMONIC_PATH
|
||||
|
||||
const mnemonic = fs.readFileSync(MNEMONIC_PATH, 'utf8').trim()
|
||||
console.log(mnemonic)
|
||||
11
packages/server/bin/lamassu-ofac-update
Executable file
11
packages/server/bin/lamassu-ofac-update
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const ofac = require('../lib/ofac/update')
|
||||
|
||||
console.log('Updating OFAC databases.')
|
||||
|
||||
ofac.update()
|
||||
.then(() => console.log('Success.'))
|
||||
.catch(console.log)
|
||||
14
packages/server/bin/lamassu-operator
Normal file
14
packages/server/bin/lamassu-operator
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs')
|
||||
const hkdf = require('futoin-hkdf')
|
||||
|
||||
require('../lib/environment-helper')
|
||||
const mnemonicHelpers = require('../lib/mnemonic-helpers')
|
||||
|
||||
const MNEMONIC_PATH = process.env.MNEMONIC_PATH
|
||||
|
||||
const mnemonic = fs.readFileSync(MNEMONIC_PATH, 'utf8').trim()
|
||||
const masterSeed = mnemonicHelpers.toEntropyBuffer(mnemonic)
|
||||
|
||||
console.log(hkdf(masterSeed, 16, { salt: 'lamassu-server-salt', info: 'operator-id' }).toString('hex'))
|
||||
51
packages/server/bin/lamassu-register
Executable file
51
packages/server/bin/lamassu-register
Executable file
|
|
@ -0,0 +1,51 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
const userManagement = require('../lib/new-admin/graphql/modules/userManagement')
|
||||
const authErrors = require('../lib/new-admin/graphql/errors')
|
||||
|
||||
const name = process.argv[2]
|
||||
const role = process.argv[3]
|
||||
const domain = process.env.HOSTNAME
|
||||
|
||||
if (!domain) {
|
||||
console.error('No hostname configured in the environment')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!name || !role) {
|
||||
console.log('Usage: lamassu-register <email> <role>')
|
||||
console.log('<role> must be \'user\' or \'superuser\'')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
const emailRegex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
|
||||
if (!emailRegex.test(name)) {
|
||||
console.log('Usage: <email> must be in an email format')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
if (role !== 'user' && role !== 'superuser') {
|
||||
console.log('Usage: <role> must be \'user\' or \'superuser\'')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
userManagement.createRegisterToken(name, role).then(token => {
|
||||
if (domain === 'localhost' && process.env.NODE_ENV !== 'production') {
|
||||
console.log(`https://${domain}:3001/register?t=${token.token}`)
|
||||
} else {
|
||||
console.log(`https://${domain}/register?t=${token.token}`)
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
}).catch(err => {
|
||||
|
||||
if (err instanceof authErrors.UserAlreadyExistsError){
|
||||
console.log(`A user with email ${name} already exists!`)
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
console.log('Error: %s', err)
|
||||
process.exit(3)
|
||||
})
|
||||
90
packages/server/bin/lamassu-send-coins
Executable file
90
packages/server/bin/lamassu-send-coins
Executable file
|
|
@ -0,0 +1,90 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const settingsLoader = require('../lib/new-settings-loader')
|
||||
const configManager = require('../lib/new-config-manager')
|
||||
const wallet = require('../lib/wallet')
|
||||
const { utils: coinUtils } = require('@lamassu/coins')
|
||||
const BN = require('../lib/bn')
|
||||
const inquirer = require('inquirer')
|
||||
const ticker = require('../lib/ticker')
|
||||
|
||||
const [toAddress, cryptoValue, cryptoCode] = process.argv.slice(2)
|
||||
|
||||
function computeCrypto (cryptoCode, value) {
|
||||
try {
|
||||
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
|
||||
const unitScale = cryptoRec.unitScale
|
||||
|
||||
return new BN(value).shiftedBy(unitScale)
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
if (!toAddress || !cryptoValue || !cryptoCode) {
|
||||
console.log('Usage: lamassu-send-coins <address> <amount> <coin>')
|
||||
console.log('Example: lamassu-send-coins 3FUv7vKaP11idnsUKyQ2pxdWxCDMyr5HKJ 0.009 BTC')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const cryptoAtoms = computeCrypto(cryptoCode, cryptoValue)
|
||||
|
||||
if (!cryptoAtoms) {
|
||||
console.log(`Unsupported coin: ${cryptoCode}.\n`)
|
||||
console.log('Usage: lamassu-send-coins <address> <amount> <coin>')
|
||||
console.log('Example: lamassu-send-coins 3FUv7vKaP11idnsUKyQ2pxdWxCDMyr5HKJ 0.009 BTC')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
console.log('Loading ticker...')
|
||||
|
||||
settingsLoader.loadLatest()
|
||||
.then(settings => {
|
||||
const fiatCode = configManager.getGlobalLocale(settings.config).fiatCurrency
|
||||
|
||||
return wallet.isStrictAddress(settings, cryptoCode, toAddress)
|
||||
.then(isValid => {
|
||||
if (!isValid) {
|
||||
console.log(`Invalid ${cryptoCode} address: ${toAddress}.`)
|
||||
console.log('Please check your command.\n')
|
||||
console.log('Usage: lamassu-send-coins <address> <amount> <coin>')
|
||||
console.log('Example: lamassu-send-coins 3FUv7vKaP11idnsUKyQ2pxdWxCDMyr5HKJ 0.009 BTC')
|
||||
process.exit(3)
|
||||
}
|
||||
})
|
||||
.then(() => ticker.getRates(settings, fiatCode, cryptoCode))
|
||||
.then(rates => {
|
||||
const fiatAmount = rates.rates.ask.times(cryptoValue).toFixed(2)
|
||||
|
||||
const questions = [
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: `Are you sure you want to send ${cryptoValue} ${cryptoCode} (${fiatAmount} ${fiatCode}) to the address ${toAddress}?`,
|
||||
default: false
|
||||
}
|
||||
]
|
||||
|
||||
console.log('\nPlease look over this transaction carefully!')
|
||||
|
||||
return inquirer.prompt(questions)
|
||||
.then(answers => {
|
||||
if (!answers.confirm) {
|
||||
console.log('Transaction cancelled.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
console.log('Sending...')
|
||||
return wallet.sendCoins(settings, { toAddress, cryptoAtoms, cryptoCode })
|
||||
.then(() => {
|
||||
console.log('Success.')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(e => console.log(e.message))
|
||||
})
|
||||
.catch(e => console.log(e.message))
|
||||
})
|
||||
.catch(e => console.log(e.message))
|
||||
})
|
||||
12
packages/server/bin/lamassu-server
Executable file
12
packages/server/bin/lamassu-server
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const app = require('../lib/app')
|
||||
|
||||
process.on('unhandledRejection', err => {
|
||||
console.log('Unhandled rejection')
|
||||
console.dir(err)
|
||||
console.log(err.stack)
|
||||
})
|
||||
|
||||
app.run()
|
||||
.catch(console.log)
|
||||
25
packages/server/bin/lamassu-server-entrypoint.sh
Normal file
25
packages/server/bin/lamassu-server-entrypoint.sh
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env bash
|
||||
CERT_FILES=(
|
||||
/lamassu-data/certs/{Lamassu_CA,Lamassu_OP,Lamassu_OP_Root_CA}.pem
|
||||
/lamassu-data/certs/Lamassu_OP_Root_CA.srl
|
||||
/lamassu-data/private/{Lamassu_OP,Lamassu_OP_Root_CA}.key
|
||||
)
|
||||
|
||||
echo "Checking for Lamassu certificates..."
|
||||
|
||||
if ! (( ${#CERT_FILES[@]} == $(ls "${CERT_FILES[@]}" 2>/dev/null | wc -l) )); then
|
||||
echo "Some certificates are missing. Building them..."
|
||||
bash /lamassu-server/tools/build-docker-certs.sh
|
||||
fi
|
||||
|
||||
echo "Upcate certs on alpine"
|
||||
cp /lamassu-data/certs/Lamassu_CA.pem /usr/local/share/ca-certificates
|
||||
cp /lamassu-data/certs/Lamassu_OP_Root_CA.pem /usr/local/share/ca-certificates
|
||||
update-ca-certificates
|
||||
|
||||
echo "Executing migrations..."
|
||||
node /lamassu-server/bin/lamassu-migrate
|
||||
|
||||
echo "Starting server..."
|
||||
node /lamassu-server/bin/lamassu-server
|
||||
|
||||
53
packages/server/bin/lamassu-trx-recovery
Normal file
53
packages/server/bin/lamassu-trx-recovery
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
const TronWeb = require('tronweb')
|
||||
const db = require('../lib/db')
|
||||
const _ = require('lodash/fp')
|
||||
|
||||
const pify = require('pify')
|
||||
const fs = pify(require('fs'))
|
||||
|
||||
const MNEMONIC_PATH = process.env.MNEMONIC_PATH
|
||||
|
||||
const defaultPrefixPath = "m/44'/195'/0'/0"
|
||||
const paymentPrefixPath = "m/44'/195'/1'/0"
|
||||
|
||||
const address = process.argv[2]
|
||||
|
||||
if (!MNEMONIC_PATH) {
|
||||
console.error(`Unable to fetch mnemonic from your account!`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!address) {
|
||||
console.log('Usage: lamassu-trx-recovery <cash-out address>')
|
||||
process.exit(2)
|
||||
}
|
||||
|
||||
function run (address) {
|
||||
Promise.all([fetchMnemonic(), searchForHdIndex(address)])
|
||||
.then(([mnemonic, hdIndex]) => {
|
||||
try {
|
||||
const prefix = !_.isNil(hdIndex) ? `${paymentPrefixPath}/${hdIndex}` : `${defaultPrefixPath}/0`
|
||||
const privKey = TronWeb.fromMnemonic(mnemonic.replace(/[\r\n]/gm, ' ').trim(), prefix).privateKey
|
||||
console.log(`Private key: `, privKey.slice(2))
|
||||
process.exit(0)
|
||||
} catch (err) {
|
||||
console.error(`Error while retrieving private key!`)
|
||||
process.exit(3)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function searchForHdIndex (address) {
|
||||
const sql = `SELECT hd_index FROM cash_out_txs WHERE to_address = $1`
|
||||
return db.oneOrNone(sql, [address])
|
||||
.then(result => _.get('hd_index', result))
|
||||
}
|
||||
|
||||
function fetchMnemonic () {
|
||||
return fs.readFile(MNEMONIC_PATH, 'utf8')
|
||||
}
|
||||
|
||||
run(address)
|
||||
36
packages/server/bin/lamassu-update-cassettes
Normal file
36
packages/server/bin/lamassu-update-cassettes
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const _ = require('lodash')
|
||||
const db = require('../lib/db')
|
||||
|
||||
if (process.argv.length !== 4) {
|
||||
console.log('Usage: lamassu-update-cassettes <device_id> <number_of_cassettes>')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!_.isFinite(parseInt(process.argv[3]))) {
|
||||
console.log('Error: <number_of_cassettes> is not a valid number (%s)', err)
|
||||
process.exit(3)
|
||||
}
|
||||
|
||||
if (parseInt(process.argv[3]) > 4 || parseInt(process.argv[3]) < 2) {
|
||||
console.log('Error: <number_of_cassettes> is out of range. Should be a number between 2 and 4')
|
||||
process.exit(3)
|
||||
}
|
||||
|
||||
const deviceId = process.argv[2]
|
||||
const numberOfCassettes = parseInt(process.argv[3])
|
||||
|
||||
const query = `UPDATE devices SET number_of_cassettes = $1 WHERE device_id = $2`
|
||||
|
||||
db.none(query, [numberOfCassettes, deviceId])
|
||||
.then(() => {
|
||||
console.log('Success! Device %s updated to %s cassettes', deviceId, numberOfCassettes)
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('Error: %s', err)
|
||||
process.exit(3)
|
||||
})
|
||||
36
packages/server/bin/lamassu-update-recyclers
Normal file
36
packages/server/bin/lamassu-update-recyclers
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const _ = require('lodash')
|
||||
const db = require('../lib/db')
|
||||
|
||||
if (process.argv.length !== 4) {
|
||||
console.log('Usage: lamassu-update-recyclers <device_id> <number_of_recyclers>')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!_.isFinite(parseInt(process.argv[3]))) {
|
||||
console.log('Error: <number_of_recyclers> is not a valid number (%s)', err)
|
||||
process.exit(3)
|
||||
}
|
||||
|
||||
if (parseInt(process.argv[3]) > 6 || parseInt(process.argv[3]) < 1) {
|
||||
console.log('Error: <number_of_recyclers> is out of range. Should be a number between 1 and 3')
|
||||
process.exit(3)
|
||||
}
|
||||
|
||||
const deviceId = process.argv[2]
|
||||
const numberOfRecyclers = parseInt(process.argv[3])
|
||||
|
||||
const query = `UPDATE devices SET number_of_recyclers = $1 WHERE device_id = $2`
|
||||
|
||||
db.none(query, [numberOfRecyclers, deviceId])
|
||||
.then(() => {
|
||||
console.log('Success! Device %s updated to %s recyclers', deviceId, numberOfRecyclers)
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('Error: %s', err)
|
||||
process.exit(3)
|
||||
})
|
||||
30
packages/server/bin/lamassu-update-to-mnemonic
Executable file
30
packages/server/bin/lamassu-update-to-mnemonic
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
|
||||
require('../lib/environment-helper')
|
||||
const mnemonicHelpers = require('../lib/mnemonic-helpers')
|
||||
const setEnvVariable = require('../tools/set-env-var')
|
||||
|
||||
if (!process.env.MNEMONIC_PATH && process.env.SEED_PATH) {
|
||||
const seed = fs.readFileSync(process.env.SEED_PATH, 'utf8').trim()
|
||||
const mnemonic = mnemonicHelpers.fromSeed(seed)
|
||||
|
||||
if (process.argv[2] === '--prod') {
|
||||
setEnvVariable('MNEMONIC_PATH', path.resolve('/etc', 'lamassu', 'mnemonics', 'mnemonic.txt'))
|
||||
} else {
|
||||
setEnvVariable('MNEMONIC_PATH', path.resolve(os.homedir(), '.lamassu', 'mnemonics', 'mnemonic.txt'))
|
||||
}
|
||||
|
||||
if (!fs.existsSync(path.dirname(process.env.MNEMONIC_PATH))) {
|
||||
fs.mkdirSync(path.dirname(process.env.MNEMONIC_PATH))
|
||||
}
|
||||
|
||||
if (!fs.existsSync(process.env.MNEMONIC_PATH)) {
|
||||
fs.writeFileSync(process.env.MNEMONIC_PATH, mnemonic, 'utf8')
|
||||
}
|
||||
}
|
||||
43
packages/server/bin/lamassu-update-wallet-nodes
Normal file
43
packages/server/bin/lamassu-update-wallet-nodes
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
const _ = require('lodash/fp')
|
||||
const common = require('../lib/blockchain/common')
|
||||
const { utils: coinUtils } = require('@lamassu/coins')
|
||||
|
||||
const cryptos = coinUtils.cryptoCurrencies()
|
||||
|
||||
const PLUGINS = {
|
||||
BTC: require('../lib/blockchain/bitcoin.js'),
|
||||
BCH: require('../lib/blockchain/bitcoincash.js'),
|
||||
DASH: require('../lib/blockchain/dash.js'),
|
||||
ETH: require('../lib/blockchain/ethereum.js'),
|
||||
LTC: require('../lib/blockchain/litecoin.js'),
|
||||
XMR: require('../lib/blockchain/monero.js'),
|
||||
ZEC: require('../lib/blockchain/zcash.js')
|
||||
}
|
||||
|
||||
function plugin (crypto) {
|
||||
const plugin = PLUGINS[crypto.cryptoCode]
|
||||
if (!plugin) throw new Error(`No such plugin: ${crypto.cryptoCode}`)
|
||||
return plugin
|
||||
}
|
||||
|
||||
function isWalletNodeInstalled (status) {
|
||||
// From http://supervisord.org/subprocess.html#process-states
|
||||
return _.includes(status, ['STARTING', 'RUNNING', 'STOPPED', 'BACKOFF', 'STOPPING', 'EXITED', 'FATAL'])
|
||||
}
|
||||
|
||||
function run () {
|
||||
_.forEach((crypto) => {
|
||||
if (!_.includes(crypto.cryptoCode, _.keys(PLUGINS))) return
|
||||
|
||||
const cryptoPlugin = plugin(crypto)
|
||||
const status = common.es(`sudo supervisorctl status ${crypto.code} | awk '{ print $2 }'`).trim()
|
||||
|
||||
if (!isWalletNodeInstalled(status)) return
|
||||
cryptoPlugin.updateCore(common.getBinaries(crypto.cryptoCode), _.includes(status, ['RUNNING', 'STARTING']))
|
||||
}, cryptos)
|
||||
}
|
||||
|
||||
run()
|
||||
Loading…
Add table
Add a link
Reference in a new issue