Merge remote-tracking branch 'upstream/defiant-dingirma' into dev
This commit is contained in:
commit
db3a4e4936
25 changed files with 532 additions and 460 deletions
|
|
@ -13,6 +13,8 @@ LAMASSU_CA_PATH=$PWD/Lamassu_CA.pem
|
||||||
MIGRATE_STATE_PATH=$CONFIG_DIR/.migrate
|
MIGRATE_STATE_PATH=$CONFIG_DIR/.migrate
|
||||||
POSTGRES_PASS=postgres123
|
POSTGRES_PASS=postgres123
|
||||||
OFAC_DATA_DIR=$CONFIG_DIR/ofac
|
OFAC_DATA_DIR=$CONFIG_DIR/ofac
|
||||||
|
IDPHOTOCARD_DIR=$CONFIG_DIR/idphotocard
|
||||||
|
FRONTCAMERA_DIR=$CONFIG_DIR/frontcamera
|
||||||
|
|
||||||
mkdir -p $CERT_DIR
|
mkdir -p $CERT_DIR
|
||||||
mkdir -p $CONFIG_DIR >> $LOG_FILE 2>&1
|
mkdir -p $CONFIG_DIR >> $LOG_FILE 2>&1
|
||||||
|
|
@ -109,7 +111,9 @@ cat <<EOF > $CONFIG_DIR/lamassu.json
|
||||||
"name": "cons_advanced",
|
"name": "cons_advanced",
|
||||||
"url": "https://www.treasury.gov/ofac/downloads/sanctions/1.0/cons_advanced.xml"
|
"url": "https://www.treasury.gov/ofac/downloads/sanctions/1.0/cons_advanced.xml"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"idPhotoCardDir": "$IDPHOTOCARD_DIR",
|
||||||
|
"frontCameraDir": "$FRONTCAMERA_DIR"
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|
|
||||||
2
bin/hkdf
2
bin/hkdf
|
|
@ -13,5 +13,5 @@ if (process.argv.length !== 4) {
|
||||||
process.exit(3)
|
process.exit(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
const masterSeed = new Buffer(masterSeedHex, 'hex')
|
const masterSeed = Buffer.from(masterSeedHex, 'hex')
|
||||||
console.log(hkdf(masterSeed, 32, { salt: 'lamassu-server-salt', info: label }).toString('hex'))
|
console.log(hkdf(masterSeed, 32, { salt: 'lamassu-server-salt', info: label }).toString('hex'))
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SEED="$(cat ~/seeds/seed.txt)"
|
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "Here is the 'External ID' of your paired machine(s), for use under the 'ATM / Teller details' of your CoinATMRadar listing:"
|
echo "Here is the 'External ID' of your paired machine(s), for use under the 'ATM / Teller details' of your CoinATMRadar listing:"
|
||||||
echo
|
echo
|
||||||
|
|
@ -9,5 +7,5 @@ su - postgres -c "psql \"lamassu\" -Atc \"select regexp_replace(device_id, '$',
|
||||||
echo
|
echo
|
||||||
echo "If speaking with CoinATMRadar directly, it may be helpful to let them know your 'Operator ID':"
|
echo "If speaking with CoinATMRadar directly, it may be helpful to let them know your 'Operator ID':"
|
||||||
echo
|
echo
|
||||||
$(npm root -g)/lamassu-server/bin/hkdf operator-id "$SEED" | cut -c -32
|
$(npm root -g)/lamassu-server/bin/lamassu-operator
|
||||||
echo
|
echo
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env node
|
||||||
|
|
||||||
SEED="$(cat ~/seeds/seed.txt)"
|
const fs = require('fs')
|
||||||
|
const hkdf = require('futoin-hkdf')
|
||||||
|
|
||||||
echo
|
const options = require('../lib/options')
|
||||||
echo "Your Operator ID for use with CoinATMRadar is:"
|
const mnemonicHelpers = require('../lib/mnemonic-helpers')
|
||||||
echo
|
|
||||||
/usr/bin/hkdf operator-id "$SEED" | cut -c -32
|
const mnemonic = fs.readFileSync(options.mnemonicPath, 'utf8').trim()
|
||||||
echo
|
const masterSeed = mnemonicHelpers.toEntropyBuffer(mnemonic)
|
||||||
|
|
||||||
|
console.log(hkdf(masterSeed, 16, { salt: 'lamassu-server-salt', info: 'operator-id' }).toString('hex'))
|
||||||
|
|
|
||||||
|
|
@ -262,7 +262,6 @@ view route invalidGroups =
|
||||||
, ( "itBit", AccountRoute "itbit", True )
|
, ( "itBit", AccountRoute "itbit", True )
|
||||||
, ( "Kraken", AccountRoute "kraken", True )
|
, ( "Kraken", AccountRoute "kraken", True )
|
||||||
, ( "Mailgun", AccountRoute "mailgun", True )
|
, ( "Mailgun", AccountRoute "mailgun", True )
|
||||||
, ( "QuadrigaCX", AccountRoute "quadrigacx", True )
|
|
||||||
, ( "Strike", AccountRoute "strike", True )
|
, ( "Strike", AccountRoute "strike", True )
|
||||||
, ( "Twilio", AccountRoute "twilio", True )
|
, ( "Twilio", AccountRoute "twilio", True )
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -178,8 +178,8 @@ function fetchData () {
|
||||||
{code: 'bitpay', display: 'Bitpay', class: 'ticker', cryptos: ['BTC', 'BCH']},
|
{code: 'bitpay', display: 'Bitpay', class: 'ticker', cryptos: ['BTC', 'BCH']},
|
||||||
{code: 'kraken', display: 'Kraken', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
|
{code: 'kraken', display: 'Kraken', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
|
||||||
{code: 'bitstamp', display: 'Bitstamp', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
{code: 'bitstamp', display: 'Bitstamp', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||||
{code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
{code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH', 'ZEC', 'DASH']},
|
||||||
{code: 'itbit', display: 'itBit', class: 'ticker', cryptos: ['BTC']},
|
{code: 'itbit', display: 'itBit', class: 'ticker', cryptos: ['BTC', 'ETH']},
|
||||||
{code: 'mock-ticker', display: 'Mock (Caution!)', class: 'ticker', cryptos: ALL_CRYPTOS},
|
{code: 'mock-ticker', display: 'Mock (Caution!)', class: 'ticker', cryptos: ALL_CRYPTOS},
|
||||||
{code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']},
|
{code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']},
|
||||||
{code: 'no-layer2', display: 'No Layer 2', class: 'layer2', cryptos: ALL_CRYPTOS},
|
{code: 'no-layer2', display: 'No Layer 2', class: 'layer2', cryptos: ALL_CRYPTOS},
|
||||||
|
|
@ -191,7 +191,7 @@ function fetchData () {
|
||||||
{code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']},
|
{code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']},
|
||||||
{code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC', 'ZEC', 'LTC', 'BCH', 'DASH']},
|
{code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC', 'ZEC', 'LTC', 'BCH', 'DASH']},
|
||||||
{code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
{code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
||||||
{code: 'itbit', display: 'itBit', class: 'exchange', cryptos: ['BTC']},
|
{code: 'itbit', display: 'itBit', class: 'exchange', cryptos: ['BTC', 'ETH']},
|
||||||
{code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
|
{code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']},
|
||||||
{code: 'mock-wallet', display: 'Mock (Caution!)', class: 'wallet', cryptos: ALL_CRYPTOS},
|
{code: 'mock-wallet', display: 'Mock (Caution!)', class: 'wallet', cryptos: ALL_CRYPTOS},
|
||||||
{code: 'no-exchange', display: 'No exchange', class: 'exchange', cryptos: ALL_CRYPTOS},
|
{code: 'no-exchange', display: 'No exchange', class: 'exchange', cryptos: ALL_CRYPTOS},
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ const db = require('./db')
|
||||||
|
|
||||||
function blocked (address, cryptoCode) {
|
function blocked (address, cryptoCode) {
|
||||||
const sql = `select * from blacklist where address = $1 and crypto_code = $2`
|
const sql = `select * from blacklist where address = $1 and crypto_code = $2`
|
||||||
return db.oneOrNone(sql, [
|
return db.any(sql, [
|
||||||
address,
|
address,
|
||||||
cryptoCode
|
cryptoCode
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function buildConfig () {
|
||||||
rpcpassword=${common.randomPass()}
|
rpcpassword=${common.randomPass()}
|
||||||
dbcache=500
|
dbcache=500
|
||||||
server=1
|
server=1
|
||||||
connections=40
|
maxconnections=40
|
||||||
keypool=10000
|
keypool=10000
|
||||||
prune=4000
|
prune=4000
|
||||||
daemon=0
|
daemon=0
|
||||||
|
|
|
||||||
|
|
@ -21,19 +21,19 @@ module.exports = {
|
||||||
|
|
||||||
const BINARIES = {
|
const BINARIES = {
|
||||||
BTC: {
|
BTC: {
|
||||||
url: 'https://bitcoin.org/bin/bitcoin-core-0.18.0/bitcoin-0.18.0-x86_64-linux-gnu.tar.gz',
|
url: 'https://bitcoin.org/bin/bitcoin-core-0.18.1/bitcoin-0.18.1-x86_64-linux-gnu.tar.gz',
|
||||||
dir: 'bitcoin-0.18.0/bin'
|
dir: 'bitcoin-0.18.1/bin'
|
||||||
},
|
},
|
||||||
ETH: {
|
ETH: {
|
||||||
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.1-b7b2f60f.tar.gz',
|
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.7-a718daa6.tar.gz',
|
||||||
dir: 'geth-linux-amd64-1.9.1-b7b2f60f'
|
dir: 'geth-linux-amd64-1.9.7-a718daa6'
|
||||||
},
|
},
|
||||||
ZEC: {
|
ZEC: {
|
||||||
url: 'https://z.cash/downloads/zcash-2.0.6-linux64.tar.gz',
|
url: 'https://z.cash/downloads/zcash-2.1.0-1-linux64-debian-jessie.tar.gz',
|
||||||
dir: 'zcash-2.0.6/bin'
|
dir: 'zcash-2.1.0-1/bin'
|
||||||
},
|
},
|
||||||
DASH: {
|
DASH: {
|
||||||
url: 'https://github.com/dashpay/dash/releases/download/v0.14.0.2/dashcore-0.14.0.2-x86_64-linux-gnu.tar',
|
url: 'https://github.com/dashpay/dash/releases/download/v0.14.0.3/dashcore-0.14.0.3-x86_64-linux-gnu.tar.gz',
|
||||||
dir: 'dashcore-0.14.0/bin'
|
dir: 'dashcore-0.14.0/bin'
|
||||||
},
|
},
|
||||||
LTC: {
|
LTC: {
|
||||||
|
|
@ -41,8 +41,8 @@ const BINARIES = {
|
||||||
dir: 'litecoin-0.17.1/bin'
|
dir: 'litecoin-0.17.1/bin'
|
||||||
},
|
},
|
||||||
BCH: {
|
BCH: {
|
||||||
url: 'https://download.bitcoinabc.org/0.19.10/linux/bitcoin-abc-0.19.10-x86_64-linux-gnu.tar.gz',
|
url: 'https://download.bitcoinabc.org/0.20.5/linux/bitcoin-abc-0.20.5-x86_64-linux-gnu.tar.gz',
|
||||||
dir: 'bitcoin-abc-0.19.10/bin',
|
dir: 'bitcoin-abc-0.20.5/bin',
|
||||||
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
|
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,13 @@ function post (machineTx, pi) {
|
||||||
let blacklisted = false
|
let blacklisted = false
|
||||||
let addressReuse = false
|
let addressReuse = false
|
||||||
|
|
||||||
return checkForBlacklisted(updatedTx)
|
return Promise.all([settingsLoader.loadLatest(), checkForBlacklisted(updatedTx)])
|
||||||
.then(blacklistItem => {
|
.then(([{ config }, blacklistItems]) => {
|
||||||
if (blacklistItem && blacklistItem.created_by_operator) {
|
const rejectAddressReuseActive = configManager.unscoped(config).rejectAddressReuseActive
|
||||||
blacklisted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blacklistItem && !blacklistItem.created_by_operator) {
|
if (_.some(it => it.created_by_operator === true)(blacklistItems)) {
|
||||||
|
blacklisted = true
|
||||||
|
} else if (_.some(it => it.created_by_operator === false)(blacklistItems) && rejectAddressReuseActive) {
|
||||||
addressReuse = true
|
addressReuse = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ const configManager = require('./config-manager')
|
||||||
const logger = require('./logger')
|
const logger = require('./logger')
|
||||||
const schema = require('../lamassu-schema.json')
|
const schema = require('../lamassu-schema.json')
|
||||||
|
|
||||||
|
const REMOVED_FIELDS = ['crossRefVerificationActive', 'crossRefVerificationThreshold']
|
||||||
|
|
||||||
function allScopes (cryptoScopes, machineScopes) {
|
function allScopes (cryptoScopes, machineScopes) {
|
||||||
const scopes = []
|
const scopes = []
|
||||||
cryptoScopes.forEach(c => {
|
cryptoScopes.forEach(c => {
|
||||||
|
|
@ -128,6 +130,8 @@ function ensureConstraints (config) {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
config.every(fieldInstance => {
|
config.every(fieldInstance => {
|
||||||
const fieldCode = fieldInstance.fieldLocator.code
|
const fieldCode = fieldInstance.fieldLocator.code
|
||||||
|
if (_.includes(fieldCode, REMOVED_FIELDS)) return
|
||||||
|
|
||||||
const field = pickField(fieldCode)
|
const field = pickField(fieldCode)
|
||||||
if (!field) {
|
if (!field) {
|
||||||
logger.warn('No such field: %s, %j', fieldCode, fieldInstance.fieldLocator.fieldScope)
|
logger.warn('No such field: %s, %j', fieldCode, fieldInstance.fieldLocator.fieldScope)
|
||||||
|
|
|
||||||
|
|
@ -347,7 +347,7 @@ function plugins (settings, deviceId) {
|
||||||
if (!transactionNotificationsEnabled()) return Promise.resolve()
|
if (!transactionNotificationsEnabled()) return Promise.resolve()
|
||||||
|
|
||||||
const isCashOut = tx.direction === 'cashOut'
|
const isCashOut = tx.direction === 'cashOut'
|
||||||
const zeroConf = isZeroConf(tx)
|
const zeroConf = isCashOut && isZeroConf(tx)
|
||||||
|
|
||||||
// 0-conf cash-out should only send notification on redemption
|
// 0-conf cash-out should only send notification on redemption
|
||||||
if (zeroConf && isCashOut && !rec.isRedemption && !rec.error) return Promise.resolve()
|
if (zeroConf && isCashOut && !rec.isRedemption && !rec.error) return Promise.resolve()
|
||||||
|
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
const axios = require('axios')
|
|
||||||
const crypto = require('crypto')
|
|
||||||
const _ = require('lodash/fp')
|
|
||||||
|
|
||||||
const API_ENDPOINT = 'https://api.quadrigacx.com/v2'
|
|
||||||
|
|
||||||
let counter = -1
|
|
||||||
let lastTimestamp = Date.now()
|
|
||||||
|
|
||||||
function pad (num) {
|
|
||||||
const asString = num.toString(10)
|
|
||||||
if (num < 10) return '00' + asString
|
|
||||||
if (num < 100) return '0' + asString
|
|
||||||
return asString
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateNonce () {
|
|
||||||
const timestamp = Date.now()
|
|
||||||
if (timestamp !== lastTimestamp) counter = -1
|
|
||||||
lastTimestamp = timestamp
|
|
||||||
counter = (counter + 1) % 1000
|
|
||||||
return timestamp.toString(10) + pad(counter)
|
|
||||||
}
|
|
||||||
|
|
||||||
function authRequest (config, path, data) {
|
|
||||||
if (!config.key || !config.secret || !config.clientId) {
|
|
||||||
const err = new Error('Must provide key, secret and client ID')
|
|
||||||
return Promise.reject(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data = data || {}
|
|
||||||
|
|
||||||
const nonce = generateNonce()
|
|
||||||
const msg = [nonce, config.clientId, config.key].join('')
|
|
||||||
|
|
||||||
const signature = crypto
|
|
||||||
.createHmac('sha256', Buffer.from(config.secret))
|
|
||||||
.update(msg)
|
|
||||||
.digest('hex')
|
|
||||||
.toLowerCase()
|
|
||||||
|
|
||||||
const signedData = _.merge(data, {
|
|
||||||
key: config.key,
|
|
||||||
signature,
|
|
||||||
nonce
|
|
||||||
})
|
|
||||||
|
|
||||||
return request(path, 'POST', signedData)
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildMarket (fiatCode, cryptoCode) {
|
|
||||||
if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH'])) {
|
|
||||||
throw new Error(`Unsupported crypto: ${cryptoCode}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_.includes(fiatCode, ['USD', 'CAD'])) {
|
|
||||||
throw new Error(`Unsupported fiat: ${fiatCode}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
let market = `${cryptoCode.toLowerCase()}_${fiatCode.toLowerCase()}`
|
|
||||||
|
|
||||||
if (fiatCode === 'USD' && cryptoCode !== 'BTC') {
|
|
||||||
throw new Error(`Unsupported market: ${market}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return market
|
|
||||||
}
|
|
||||||
|
|
||||||
function request (path, method, data) {
|
|
||||||
const options = {
|
|
||||||
method,
|
|
||||||
data,
|
|
||||||
url: API_ENDPOINT + path,
|
|
||||||
headers: {
|
|
||||||
'User-Agent': 'Mozilla/4.0 (compatible; Lamassu client)',
|
|
||||||
'Content-Type': 'application/json; charset=utf-8'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return axios(options)
|
|
||||||
.then(r => {
|
|
||||||
if (r.data.error) throw new Error(r.data.error.message)
|
|
||||||
return r.data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
buildMarket,
|
|
||||||
authRequest,
|
|
||||||
request
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
const common = require('../../common/quadrigacx')
|
|
||||||
const coinUtils = require('../../../coin-utils')
|
|
||||||
|
|
||||||
function buy (account, cryptoAtoms, fiatCode, cryptoCode) {
|
|
||||||
return trade('buy', account, cryptoAtoms, fiatCode, cryptoCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
function sell (account, cryptoAtoms, fiatCode, cryptoCode) {
|
|
||||||
return trade('sell', account, cryptoAtoms, fiatCode, cryptoCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
function trade (type, account, cryptoAtoms, fiatCode, cryptoCode) {
|
|
||||||
return Promise.resolve()
|
|
||||||
.then(() => {
|
|
||||||
const market = common.buildMarket(fiatCode, cryptoCode)
|
|
||||||
const options = {
|
|
||||||
book: market,
|
|
||||||
amount: coinUtils.toUnit(cryptoAtoms, cryptoCode).toFixed(8)
|
|
||||||
}
|
|
||||||
|
|
||||||
return common.authRequest(account, '/' + type, options)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
buy,
|
|
||||||
sell
|
|
||||||
}
|
|
||||||
|
|
@ -2,9 +2,9 @@ const axios = require('axios')
|
||||||
const BN = require('../../../bn')
|
const BN = require('../../../bn')
|
||||||
|
|
||||||
function ticker (account, fiatCode, cryptoCode) {
|
function ticker (account, fiatCode, cryptoCode) {
|
||||||
return axios.get('https://bitpay.com/api/rates/' + cryptoCode + '/' + fiatCode)
|
return axios.get('https://bitpay.com/rates/' + cryptoCode + '/' + fiatCode)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
const data = r.data
|
const data = r.data.data
|
||||||
const price = BN(data.rate.toString())
|
const price = BN(data.rate.toString())
|
||||||
return {
|
return {
|
||||||
rates: {
|
rates: {
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ exports.ticker = function ticker (account, fiatCode, cryptoCode) {
|
||||||
return getCurrencyRates(fiatCode, cryptoCode)
|
return getCurrencyRates(fiatCode, cryptoCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return axios.get('https://bitpay.com/api/rates')
|
return axios.get('https://bitpay.com/rates')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const fxRates = response.data
|
const fxRates = response.data.data
|
||||||
const usdRate = findCurrency(fxRates, 'USD')
|
const usdRate = findCurrency(fxRates, 'USD')
|
||||||
const fxRate = findCurrency(fxRates, fiatCode).div(usdRate)
|
const fxRate = findCurrency(fxRates, fiatCode).div(usdRate)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ function getSellPrice (obj) {
|
||||||
function ticker (account, fiatCode, cryptoCode) {
|
function ticker (account, fiatCode, cryptoCode) {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH'])) {
|
if (!_.includes(cryptoCode, ['BTC', 'ETH', 'LTC', 'BCH', 'ZEC', 'DASH'])) {
|
||||||
throw new Error('Unsupported crypto: ' + cryptoCode)
|
throw new Error('Unsupported crypto: ' + cryptoCode)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
const axios = require('axios')
|
|
||||||
const _ = require('lodash/fp')
|
|
||||||
|
|
||||||
const BN = require('../../../bn')
|
|
||||||
const common = require('../../common/quadrigacx')
|
|
||||||
|
|
||||||
exports.NAME = 'QuadrigaCX'
|
|
||||||
exports.SUPPORTED_MODULES = ['ticker']
|
|
||||||
|
|
||||||
function findCurrency (fxRates, fiatCode) {
|
|
||||||
const rates = _.find(_.matchesProperty('code', fiatCode), fxRates)
|
|
||||||
if (!rates || !rates.rate) throw new Error(`Unsupported currency: ${fiatCode}`)
|
|
||||||
return BN(rates.rate.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.ticker = function ticker (account, fiatCode, cryptoCode) {
|
|
||||||
if (fiatCode === 'USD' && cryptoCode === 'BTC' || fiatCode === 'CAD') {
|
|
||||||
return getCurrencyRates(fiatCode, cryptoCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return axios.get('https://bitpay.com/api/rates')
|
|
||||||
.then(response => {
|
|
||||||
const fxRates = response.data
|
|
||||||
const cadRate = findCurrency(fxRates, 'CAD')
|
|
||||||
const fxRate = findCurrency(fxRates, fiatCode).div(cadRate)
|
|
||||||
|
|
||||||
return getCurrencyRates('CAD', cryptoCode)
|
|
||||||
.then(res => ({
|
|
||||||
rates: {
|
|
||||||
ask: res.rates.ask.times(fxRate),
|
|
||||||
bid: res.rates.bid.times(fxRate)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCurrencyRates (fiatCode, cryptoCode) {
|
|
||||||
return Promise.resolve()
|
|
||||||
.then(() => {
|
|
||||||
const market = common.buildMarket(fiatCode, cryptoCode)
|
|
||||||
return common.request(`/ticker?book=${market}`, 'GET')
|
|
||||||
})
|
|
||||||
.then(r => ({
|
|
||||||
rates: {
|
|
||||||
ask: BN(r.ask),
|
|
||||||
bid: BN(r.bid)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
@ -54,7 +54,7 @@ function sendCoins (account, toAddress, cryptoAtoms, cryptoCode) {
|
||||||
.then(tx => {
|
.then(tx => {
|
||||||
if (!tx) return { txid }
|
if (!tx) return { txid }
|
||||||
|
|
||||||
const fee = BN(tx.gas).multipliedBy(BN(tx.gasPrice)).round()
|
const fee = BN(tx.gas).mul(BN(tx.gasPrice)).round()
|
||||||
|
|
||||||
return { txid, fee }
|
return { txid, fee }
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ function updateCoinAtmRadar () {
|
||||||
const config = settings().config
|
const config = settings().config
|
||||||
|
|
||||||
return pi().getRawRates()
|
return pi().getRawRates()
|
||||||
.then(rates => coinAtmRadar.update({ rates, config }))
|
.then(rates => coinAtmRadar.update({ rates, config }, settings()))
|
||||||
}
|
}
|
||||||
|
|
||||||
function start (__settings) {
|
function start (__settings) {
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,16 @@ const argv = require('minimist')(process.argv.slice(2))
|
||||||
|
|
||||||
const CLOCK_SKEW = 60 * 1000
|
const CLOCK_SKEW = 60 * 1000
|
||||||
const REQUEST_TTL = 3 * 60 * 1000
|
const REQUEST_TTL = 3 * 60 * 1000
|
||||||
|
const THROTTLE_LOGS_QUERY = 30 * 1000
|
||||||
|
const THROTTLE_CLOCK_SKEW = 60 * 1000
|
||||||
|
const SETTINGS_CACHE_REFRESH = 60 * 60 * 1000
|
||||||
|
|
||||||
const pids = {}
|
const pids = {}
|
||||||
const reboots = {}
|
const reboots = {}
|
||||||
const restartServicesMap = {}
|
const restartServicesMap = {}
|
||||||
|
const canGetLastSeenMap = {}
|
||||||
|
const canLogClockSkewMap = {}
|
||||||
|
const settingsCache = {}
|
||||||
|
|
||||||
const devMode = argv.dev || options.http
|
const devMode = argv.dev || options.http
|
||||||
|
|
||||||
|
|
@ -167,13 +173,6 @@ function stateChange (req, res, next) {
|
||||||
.catch(next)
|
.catch(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
function deviceEvent (req, res, next) {
|
|
||||||
const pi = plugins(req.settings, req.deviceId)
|
|
||||||
pi.logEvent(req.body)
|
|
||||||
.then(() => respond(req, res))
|
|
||||||
.catch(next)
|
|
||||||
}
|
|
||||||
|
|
||||||
function verifyUser (req, res, next) {
|
function verifyUser (req, res, next) {
|
||||||
const pi = plugins(req.settings, req.deviceId)
|
const pi = plugins(req.settings, req.deviceId)
|
||||||
pi.verifyUser(req.body)
|
pi.verifyUser(req.body)
|
||||||
|
|
@ -245,11 +244,21 @@ function updateCustomer (req, res, next) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLastSeen (req, res, next) {
|
function getLastSeen (req, res, next) {
|
||||||
return logs.getLastSeen(req.deviceId)
|
const deviceId = req.deviceId
|
||||||
|
const timestamp = Date.now()
|
||||||
|
const shouldTrigger = !canGetLastSeenMap[deviceId] ||
|
||||||
|
timestamp - canGetLastSeenMap[deviceId] >= THROTTLE_LOGS_QUERY
|
||||||
|
|
||||||
|
if (shouldTrigger) {
|
||||||
|
canGetLastSeenMap[deviceId] = timestamp
|
||||||
|
return logs.getLastSeen(deviceId)
|
||||||
.then(r => res.json(r))
|
.then(r => res.json(r))
|
||||||
.catch(next)
|
.catch(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return res.status(408).json({})
|
||||||
|
}
|
||||||
|
|
||||||
function updateLogs (req, res, next) {
|
function updateLogs (req, res, next) {
|
||||||
return logs.update(req.deviceId, req.body.logs)
|
return logs.update(req.deviceId, req.body.logs)
|
||||||
.then(status => res.json({ success: status }))
|
.then(status => res.json({ success: status }))
|
||||||
|
|
@ -310,9 +319,15 @@ function httpError (msg, code) {
|
||||||
|
|
||||||
function filterOldRequests (req, res, next) {
|
function filterOldRequests (req, res, next) {
|
||||||
const deviceTime = req.deviceTime
|
const deviceTime = req.deviceTime
|
||||||
const delta = Date.now() - Date.parse(deviceTime)
|
const deviceId = req.deviceId
|
||||||
|
const timestamp = Date.now()
|
||||||
|
const delta = timestamp - Date.parse(deviceTime)
|
||||||
|
|
||||||
if (delta > CLOCK_SKEW) {
|
const shouldTrigger = !canLogClockSkewMap[deviceId] ||
|
||||||
|
timestamp - canLogClockSkewMap[deviceId] >= THROTTLE_CLOCK_SKEW
|
||||||
|
|
||||||
|
if (delta > CLOCK_SKEW && shouldTrigger) {
|
||||||
|
canLogClockSkewMap[deviceId] = timestamp
|
||||||
logger.error('Clock skew with lamassu-machine[%s] too high [%ss], adjust lamassu-machine clock',
|
logger.error('Clock skew with lamassu-machine[%s] too high [%ss], adjust lamassu-machine clock',
|
||||||
req.deviceName, (delta / 1000).toFixed(2))
|
req.deviceName, (delta / 1000).toFixed(2))
|
||||||
}
|
}
|
||||||
|
|
@ -337,8 +352,7 @@ function authorize (req, res, next) {
|
||||||
.catch(next)
|
.catch(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
const skip = (req, res) => _.includes(req.path, ['/poll', '/state', '/logs']) &&
|
const skip = (req, res) => _.includes(req.path, ['/poll', '/state', '/logs']) && _.includes(res.statusCode, [200, 408])
|
||||||
res.statusCode === 200
|
|
||||||
|
|
||||||
const configRequiredRoutes = [
|
const configRequiredRoutes = [
|
||||||
'/poll',
|
'/poll',
|
||||||
|
|
@ -367,7 +381,6 @@ app.use(filterOldRequests)
|
||||||
app.get('/poll', poll)
|
app.get('/poll', poll)
|
||||||
app.post('/state', stateChange)
|
app.post('/state', stateChange)
|
||||||
|
|
||||||
app.post('/event', deviceEvent)
|
|
||||||
app.post('/verify_user', verifyUser)
|
app.post('/verify_user', verifyUser)
|
||||||
app.post('/verify_transaction', verifyTx)
|
app.post('/verify_transaction', verifyTx)
|
||||||
|
|
||||||
|
|
@ -416,6 +429,7 @@ localApp.post('/restartServices', (req, res) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
localApp.post('/dbChange', (req, res, next) => {
|
localApp.post('/dbChange', (req, res, next) => {
|
||||||
|
settingsCache.cache = null
|
||||||
return settingsLoader.loadLatest()
|
return settingsLoader.loadLatest()
|
||||||
.then(poller.reload)
|
.then(poller.reload)
|
||||||
.then(() => logger.info('Config reloaded'))
|
.then(() => logger.info('Config reloaded'))
|
||||||
|
|
@ -452,9 +466,23 @@ function populateSettings (req, res, next) {
|
||||||
oldVersionId = versionId
|
oldVersionId = versionId
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!versionId) {
|
// Clear cache every hour
|
||||||
|
if (Date.now() - settingsCache.timestamp > SETTINGS_CACHE_REFRESH) {
|
||||||
|
settingsCache.cache = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!versionId && settingsCache.cache) {
|
||||||
|
req.settings = settingsCache.cache
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!versionId && !settingsCache.cache) {
|
||||||
return settingsLoader.loadLatest()
|
return settingsLoader.loadLatest()
|
||||||
.then(settings => { req.settings = settings })
|
.then(settings => {
|
||||||
|
settingsCache.cache = settings
|
||||||
|
settingsCache.timestamp = Date.now()
|
||||||
|
req.settings = settings
|
||||||
|
})
|
||||||
.then(() => next())
|
.then(() => next())
|
||||||
.catch(next)
|
.catch(next)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
639
package-lock.json
generated
639
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "lamassu-server",
|
"name": "lamassu-server",
|
||||||
"description": "bitcoin atm client server protocol module",
|
"description": "bitcoin atm client server protocol module",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"version": "7.3.2",
|
"version": "7.5.0-beta.0",
|
||||||
"license": "Unlicense",
|
"license": "Unlicense",
|
||||||
"author": "Lamassu (https://lamassu.is)",
|
"author": "Lamassu (https://lamassu.is)",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
{
|
|
||||||
"code": "quadrigacx",
|
|
||||||
"display": "QuadrigaCX",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"code": "clientId",
|
|
||||||
"display": "Client ID",
|
|
||||||
"fieldType": "string",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "key",
|
|
||||||
"display": "API key",
|
|
||||||
"fieldType": "string",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "secret",
|
|
||||||
"display": "API secret",
|
|
||||||
"fieldType": "password",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,7 @@ const data = {
|
||||||
accounts: [
|
accounts: [
|
||||||
{code: 'mock-ticker', display: 'Mock ticker', class: 'ticker', cryptos: ALL_CRYPTOS},
|
{code: 'mock-ticker', display: 'Mock ticker', class: 'ticker', cryptos: ALL_CRYPTOS},
|
||||||
{code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']},
|
{code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']},
|
||||||
{code: 'quadrigacx', display: 'QuadrigaCX', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']},
|
{code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ZEC', 'ETH', 'LTC', 'BCH', 'DASH']},
|
||||||
{code: 'mock-wallet', display: 'Mock (Caution!)', class: 'wallet', cryptos: ALL_CRYPTOS}
|
{code: 'mock-wallet', display: 'Mock (Caution!)', class: 'wallet', cryptos: ALL_CRYPTOS}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue