Add transaction versioning, tx cancellation (#79)
This commit is contained in:
parent
4a97535dec
commit
500edcf279
15 changed files with 2223 additions and 1468 deletions
|
|
@ -150,6 +150,24 @@ app.get('/api/transactions', (req, res, next) => {
|
||||||
.catch(next)
|
.catch(next)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.get('/api/transaction/:id', (req, res, next) => {
|
||||||
|
return transactions.single(req.params.id)
|
||||||
|
.then(r => {
|
||||||
|
if (!r) return res.status(404).send({Error: 'Not found'})
|
||||||
|
return res.send(r)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app.patch('/api/transaction/:id', (req, res, next) => {
|
||||||
|
if (!req.query.cancel) return res.status(400).send({Error: 'Requires cancel'})
|
||||||
|
|
||||||
|
return transactions.cancel(req.params.id)
|
||||||
|
.then(r => {
|
||||||
|
return res.send(r)
|
||||||
|
})
|
||||||
|
.catch(() => res.status(404).send({Error: 'Not found'}))
|
||||||
|
})
|
||||||
|
|
||||||
app.use((err, req, res, next) => {
|
app.use((err, req, res, next) => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
|
||||||
|
|
@ -223,7 +241,6 @@ function establishSocket (ws, token) {
|
||||||
if (!success) return ws.close(1008, 'Authentication error')
|
if (!success) return ws.close(1008, 'Authentication error')
|
||||||
|
|
||||||
const listener = data => {
|
const listener = data => {
|
||||||
console.log('DEBUG200: %j', data)
|
|
||||||
ws.send(JSON.stringify(data))
|
ws.send(JSON.stringify(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,8 +256,6 @@ function establishSocket (ws, token) {
|
||||||
}, REAUTHENTICATE_INTERVAL)
|
}, REAUTHENTICATE_INTERVAL)
|
||||||
|
|
||||||
socketEmitter.on('message', listener)
|
socketEmitter.on('message', listener)
|
||||||
|
|
||||||
console.log('DEBUG120: %j', token)
|
|
||||||
ws.send('Testing123')
|
ws.send('Testing123')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ const _ = require('lodash/fp')
|
||||||
|
|
||||||
const db = require('../db')
|
const db = require('../db')
|
||||||
const machineLoader = require('../machine-loader')
|
const machineLoader = require('../machine-loader')
|
||||||
|
const tx = require('../tx')
|
||||||
|
const cashInTx = require('../cash-in-tx')
|
||||||
|
|
||||||
const NUM_RESULTS = 20
|
const NUM_RESULTS = 20
|
||||||
|
|
||||||
|
|
@ -18,21 +20,49 @@ function addNames (txs) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function batch () {
|
|
||||||
const camelize = _.mapKeys(_.camelCase)
|
const camelize = _.mapKeys(_.camelCase)
|
||||||
|
|
||||||
|
function batch () {
|
||||||
const packager = _.flow(_.flatten, _.orderBy(_.property('created'), ['desc']),
|
const packager = _.flow(_.flatten, _.orderBy(_.property('created'), ['desc']),
|
||||||
_.take(NUM_RESULTS), _.map(camelize), addNames)
|
_.take(NUM_RESULTS), _.map(camelize), addNames)
|
||||||
|
|
||||||
const cashInSql = `select 'cashIn' as tx_class, cash_in_txs.*
|
const cashInSql = `select 'cashIn' as tx_class, cash_in_txs.*,
|
||||||
|
((not send_confirmed) and (created <= now() - interval $1)) as expired
|
||||||
from cash_in_txs
|
from cash_in_txs
|
||||||
order by created desc limit $1`
|
order by created desc limit $2`
|
||||||
|
|
||||||
const cashOutSql = `select 'cashOut' as tx_class, cash_out_txs.*
|
const cashOutSql = `select 'cashOut' as tx_class, cash_out_txs.*
|
||||||
from cash_out_txs
|
from cash_out_txs
|
||||||
order by created desc limit $1`
|
order by created desc limit $1`
|
||||||
|
|
||||||
return Promise.all([db.any(cashInSql, [NUM_RESULTS]), db.any(cashOutSql, [NUM_RESULTS])])
|
return Promise.all([db.any(cashInSql, [cashInTx.PENDING_INTERVAL, NUM_RESULTS]), db.any(cashOutSql, [NUM_RESULTS])])
|
||||||
.then(packager)
|
.then(packager)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {batch}
|
function single (txId) {
|
||||||
|
const packager = _.flow(_.compact, _.map(camelize), addNames)
|
||||||
|
|
||||||
|
const cashInSql = `select 'cashIn' as tx_class,
|
||||||
|
((not send_confirmed) and (created <= now() - interval $1)) as expired,
|
||||||
|
cash_in_txs.*
|
||||||
|
from cash_in_txs
|
||||||
|
where id=$2`
|
||||||
|
|
||||||
|
const cashOutSql = `select 'cashOut' as tx_class, cash_out_txs.*
|
||||||
|
from cash_out_txs
|
||||||
|
where id=$1`
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
db.oneOrNone(cashInSql, [cashInTx.PENDING_INTERVAL, txId]),
|
||||||
|
db.oneOrNone(cashOutSql, [txId])
|
||||||
|
])
|
||||||
|
.then(packager)
|
||||||
|
.then(_.head)
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel (txId) {
|
||||||
|
return tx.cancel(txId)
|
||||||
|
.then(() => single(txId))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {batch, single, cancel}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,15 @@ const db = require('./db')
|
||||||
const BN = require('./bn')
|
const BN = require('./bn')
|
||||||
const plugins = require('./plugins')
|
const plugins = require('./plugins')
|
||||||
const logger = require('./logger')
|
const logger = require('./logger')
|
||||||
const pp = require('./pp')
|
const T = require('./time')
|
||||||
|
const E = require('./error')
|
||||||
|
|
||||||
module.exports = {post, monitorPending}
|
const PENDING_INTERVAL = '60 minutes'
|
||||||
|
const PENDING_INTERVAL_MS = 60 * T.minutes
|
||||||
const PENDING_INTERVAL = '1 day'
|
|
||||||
const MAX_PENDING = 10
|
const MAX_PENDING = 10
|
||||||
|
|
||||||
|
module.exports = {post, monitorPending, cancel, PENDING_INTERVAL}
|
||||||
|
|
||||||
function atomic (machineTx, pi) {
|
function atomic (machineTx, pi) {
|
||||||
const TransactionMode = pgp.txMode.TransactionMode
|
const TransactionMode = pgp.txMode.TransactionMode
|
||||||
const isolationLevel = pgp.txMode.isolationLevel
|
const isolationLevel = pgp.txMode.isolationLevel
|
||||||
|
|
@ -22,6 +24,8 @@ function atomic (machineTx, pi) {
|
||||||
|
|
||||||
return t.oneOrNone(sql, [machineTx.id])
|
return t.oneOrNone(sql, [machineTx.id])
|
||||||
.then(row => {
|
.then(row => {
|
||||||
|
if (row && row.tx_version >= machineTx.txVersion) throw new E.StaleTxError('Stale tx')
|
||||||
|
|
||||||
return t.any(sql2, [machineTx.id])
|
return t.any(sql2, [machineTx.id])
|
||||||
.then(billRows => {
|
.then(billRows => {
|
||||||
const dbTx = toObj(row)
|
const dbTx = toObj(row)
|
||||||
|
|
@ -29,10 +33,8 @@ function atomic (machineTx, pi) {
|
||||||
return preProcess(dbTx, machineTx, pi)
|
return preProcess(dbTx, machineTx, pi)
|
||||||
.then(preProcessedTx => upsert(dbTx, preProcessedTx))
|
.then(preProcessedTx => upsert(dbTx, preProcessedTx))
|
||||||
.then(r => {
|
.then(r => {
|
||||||
pp('DEBUG701.5')(r)
|
|
||||||
return insertNewBills(billRows, machineTx)
|
return insertNewBills(billRows, machineTx)
|
||||||
.then(newBills => _.set('newBills', newBills, r))
|
.then(newBills => _.set('newBills', newBills, r))
|
||||||
.then(pp('DEBUG702'))
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -44,7 +46,6 @@ function atomic (machineTx, pi) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function post (machineTx, pi) {
|
function post (machineTx, pi) {
|
||||||
console.log('DEBUG700: %j', machineTx)
|
|
||||||
return db.tx(atomic(machineTx, pi))
|
return db.tx(atomic(machineTx, pi))
|
||||||
.then(r => {
|
.then(r => {
|
||||||
const updatedTx = r.tx
|
const updatedTx = r.tx
|
||||||
|
|
@ -67,11 +68,11 @@ function isMonotonic (oldField, newField, fieldKey) {
|
||||||
if (oldField.isBigNumber) return oldField.lte(newField)
|
if (oldField.isBigNumber) return oldField.lte(newField)
|
||||||
if (_.isNumber(oldField)) return oldField <= newField
|
if (_.isNumber(oldField)) return oldField <= newField
|
||||||
|
|
||||||
throw new Error(`Unexpected value: ${oldField}`)
|
throw new Error(`Unexpected value [${fieldKey}]: ${oldField}, ${newField}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureRatchet (oldField, newField, fieldKey) {
|
function ensureRatchet (oldField, newField, fieldKey) {
|
||||||
const monotonic = ['cryptoAtoms', 'fiat', 'send', 'sendConfirmed', 'operatorCompleted', 'timedout']
|
const monotonic = ['cryptoAtoms', 'fiat', 'cashInFeeCrypto', 'send', 'sendConfirmed', 'operatorCompleted', 'timedout', 'txVersion']
|
||||||
const free = ['sendPending', 'error', 'errorCode']
|
const free = ['sendPending', 'error', 'errorCode']
|
||||||
|
|
||||||
if (_.isNil(oldField)) return true
|
if (_.isNil(oldField)) return true
|
||||||
|
|
@ -121,16 +122,11 @@ function toObj (row) {
|
||||||
|
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
const objKey = _.camelCase(key)
|
const objKey = _.camelCase(key)
|
||||||
if (key === 'crypto_atoms' || key === 'fiat' || key === 'cash_in_fee') {
|
if (_.includes(key, ['crypto_atoms', 'fiat', 'cash_in_fee', 'cash_in_fee_crypto'])) {
|
||||||
newObj[objKey] = BN(row[key])
|
newObj[objKey] = BN(row[key])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'device_time') {
|
|
||||||
newObj[objKey] = parseInt(row[key], 10)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
newObj[objKey] = row[key]
|
newObj[objKey] = row[key]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -200,14 +196,13 @@ function update (tx, changes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerTrades (pi, newBills) {
|
function registerTrades (pi, newBills) {
|
||||||
console.log('DEBUG600: %j', newBills)
|
|
||||||
_.forEach(bill => pi.buy(bill), newBills)
|
_.forEach(bill => pi.buy(bill), newBills)
|
||||||
}
|
}
|
||||||
|
|
||||||
function logAction (rec, tx) {
|
function logAction (rec, tx) {
|
||||||
const action = {
|
const action = {
|
||||||
tx_id: tx.id,
|
tx_id: tx.id,
|
||||||
action: rec.sendConfirmed ? 'sendCoins' : 'sendCoinsError',
|
action: rec.action || (rec.sendConfirmed ? 'sendCoins' : 'sendCoinsError'),
|
||||||
error: rec.error,
|
error: rec.error,
|
||||||
error_code: rec.errorCode,
|
error_code: rec.errorCode,
|
||||||
tx_hash: rec.txHash
|
tx_hash: rec.txHash
|
||||||
|
|
@ -219,13 +214,22 @@ function logAction (rec, tx) {
|
||||||
.then(_.constant(rec))
|
.then(_.constant(rec))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function logActionById (action, _rec, txId) {
|
||||||
|
const rec = _.assign(_rec, {action, tx_id: txId})
|
||||||
|
const sql = pgp.helpers.insert(rec, null, 'cash_in_actions')
|
||||||
|
|
||||||
|
return db.none(sql)
|
||||||
|
}
|
||||||
|
|
||||||
function isClearToSend (oldTx, newTx) {
|
function isClearToSend (oldTx, newTx) {
|
||||||
|
const now = Date.now()
|
||||||
|
|
||||||
return newTx.send &&
|
return newTx.send &&
|
||||||
(!oldTx || (!oldTx.sendPending && !oldTx.sendConfirmed))
|
(!oldTx || (!oldTx.sendPending && !oldTx.sendConfirmed)) &&
|
||||||
|
(newTx.created > now - PENDING_INTERVAL_MS)
|
||||||
}
|
}
|
||||||
|
|
||||||
function postProcess (r, pi) {
|
function postProcess (r, pi) {
|
||||||
console.log('DEBUG701: %j', r)
|
|
||||||
registerTrades(pi, r.newBills)
|
registerTrades(pi, r.newBills)
|
||||||
|
|
||||||
if (isClearToSend(r.dbTx, r.tx)) {
|
if (isClearToSend(r.dbTx, r.tx)) {
|
||||||
|
|
@ -303,3 +307,22 @@ function monitorPending (settings) {
|
||||||
.then(rows => Promise.all(_.map(processPending, rows)))
|
.then(rows => Promise.all(_.map(processPending, rows)))
|
||||||
.catch(logger.error)
|
.catch(logger.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cancel (txId) {
|
||||||
|
const updateRec = {
|
||||||
|
error: 'Operator cancel',
|
||||||
|
error_code: 'operatorCancel',
|
||||||
|
operator_completed: true
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
return pgp.helpers.update(updateRec, null, 'cash_in_txs') +
|
||||||
|
pgp.as.format(' where id=$1', [txId])
|
||||||
|
})
|
||||||
|
.then(sql => db.result(sql, false))
|
||||||
|
.then(res => {
|
||||||
|
if (res.rowCount !== 1) throw new Error('No such tx-id')
|
||||||
|
})
|
||||||
|
.then(() => logActionById('operatorCompleted', {}, txId))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,4 @@ function register (errorName) {
|
||||||
register('BadNumberError')
|
register('BadNumberError')
|
||||||
register('NoDataError')
|
register('NoDataError')
|
||||||
register('InsufficientFundsError')
|
register('InsufficientFundsError')
|
||||||
|
register('StaleTxError')
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ function fetchExchange (settings, cryptoCode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function buy (settings, cryptoAtoms, fiatCode, cryptoCode) {
|
function buy (settings, cryptoAtoms, fiatCode, cryptoCode) {
|
||||||
console.log('DEBUG600')
|
|
||||||
return fetchExchange(settings, cryptoCode)
|
return fetchExchange(settings, cryptoCode)
|
||||||
.then(r => r.exchange.buy(r.account, cryptoAtoms, fiatCode, cryptoCode))
|
.then(r => r.exchange.buy(r.account, cryptoAtoms, fiatCode, cryptoCode))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ function plugins (settings, deviceId) {
|
||||||
cryptoCode,
|
cryptoCode,
|
||||||
display: cryptoRec.display,
|
display: cryptoRec.display,
|
||||||
minimumTx: BN.max(minimumTx, cashInFee),
|
minimumTx: BN.max(minimumTx, cashInFee),
|
||||||
cashInFee: cashInFee,
|
cashInFee,
|
||||||
cryptoNetwork
|
cryptoNetwork
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -216,8 +216,6 @@ function plugins (settings, deviceId) {
|
||||||
const testNets = arr.slice(2 * cryptoCodesCount + 3)
|
const testNets = arr.slice(2 * cryptoCodesCount + 3)
|
||||||
const coinParams = _.zip(cryptoCodes, testNets)
|
const coinParams = _.zip(cryptoCodes, testNets)
|
||||||
|
|
||||||
console.log('DEBUG200: %j', cryptoCodes)
|
|
||||||
console.log('DEBUG201: %j', coinParams)
|
|
||||||
return {
|
return {
|
||||||
cassettes,
|
cassettes,
|
||||||
rates: buildRates(tickers),
|
rates: buildRates(tickers),
|
||||||
|
|
@ -351,7 +349,6 @@ function plugins (settings, deviceId) {
|
||||||
|
|
||||||
const market = [fiatCode, cryptoCode].join('')
|
const market = [fiatCode, cryptoCode].join('')
|
||||||
|
|
||||||
console.log('DEBUG505')
|
|
||||||
if (!exchange.active(settings, cryptoCode)) return
|
if (!exchange.active(settings, cryptoCode)) return
|
||||||
|
|
||||||
logger.debug('[%s] Pushing trade: %d', market, cryptoAtoms)
|
logger.debug('[%s] Pushing trade: %d', market, cryptoAtoms)
|
||||||
|
|
@ -368,7 +365,6 @@ function plugins (settings, deviceId) {
|
||||||
const market = [fiatCode, cryptoCode].join('')
|
const market = [fiatCode, cryptoCode].join('')
|
||||||
|
|
||||||
const marketTradesQueues = tradesQueues[market]
|
const marketTradesQueues = tradesQueues[market]
|
||||||
console.log('DEBUG504: %j', marketTradesQueues)
|
|
||||||
if (!marketTradesQueues || marketTradesQueues.length === 0) return null
|
if (!marketTradesQueues || marketTradesQueues.length === 0) return null
|
||||||
|
|
||||||
logger.debug('[%s] tradesQueues size: %d', market, marketTradesQueues.length)
|
logger.debug('[%s] tradesQueues size: %d', market, marketTradesQueues.length)
|
||||||
|
|
@ -409,7 +405,6 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeTrades () {
|
function executeTrades () {
|
||||||
console.log('DEBUG500')
|
|
||||||
return machineLoader.getMachines()
|
return machineLoader.getMachines()
|
||||||
.then(devices => {
|
.then(devices => {
|
||||||
const deviceIds = devices.map(device => device.deviceId)
|
const deviceIds = devices.map(device => device.deviceId)
|
||||||
|
|
@ -430,7 +425,6 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeTradesForMarket (settings, fiatCode, cryptoCode) {
|
function executeTradesForMarket (settings, fiatCode, cryptoCode) {
|
||||||
console.log('DEBUG501: %s, %j', cryptoCode, exchange.active(settings, cryptoCode))
|
|
||||||
if (!exchange.active(settings, cryptoCode)) return
|
if (!exchange.active(settings, cryptoCode)) return
|
||||||
|
|
||||||
const market = [fiatCode, cryptoCode].join('')
|
const market = [fiatCode, cryptoCode].join('')
|
||||||
|
|
@ -438,8 +432,6 @@ function plugins (settings, deviceId) {
|
||||||
|
|
||||||
if (tradeEntry === null || tradeEntry.cryptoAtoms.eq(0)) return
|
if (tradeEntry === null || tradeEntry.cryptoAtoms.eq(0)) return
|
||||||
|
|
||||||
console.log('DEBUG502')
|
|
||||||
|
|
||||||
return executeTradeForType(tradeEntry)
|
return executeTradeForType(tradeEntry)
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
tradesQueues[market].push(tradeEntry)
|
tradesQueues[market].push(tradeEntry)
|
||||||
|
|
@ -457,8 +449,6 @@ function plugins (settings, deviceId) {
|
||||||
const tradeEntry = expand(_tradeEntry)
|
const tradeEntry = expand(_tradeEntry)
|
||||||
const execute = tradeEntry.type === 'buy' ? exchange.buy : exchange.sell
|
const execute = tradeEntry.type === 'buy' ? exchange.buy : exchange.sell
|
||||||
|
|
||||||
console.log('DEBUG503')
|
|
||||||
|
|
||||||
return execute(settings, tradeEntry.cryptoAtoms, tradeEntry.fiatCode, tradeEntry.cryptoCode)
|
return execute(settings, tradeEntry.cryptoAtoms, tradeEntry.fiatCode, tradeEntry.cryptoCode)
|
||||||
.then(() => recordTrade(tradeEntry))
|
.then(() => recordTrade(tradeEntry))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ function trade (account, type, cryptoAtoms, fiatCode, cryptoCode) {
|
||||||
kraken.api('AddOrder', orderInfo, (error, response) => {
|
kraken.api('AddOrder', orderInfo, (error, response) => {
|
||||||
if (error) return reject(error)
|
if (error) return reject(error)
|
||||||
|
|
||||||
console.log('DEBUG900: %j', response)
|
|
||||||
return resolve()
|
return resolve()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ const plugins = require('./plugins')
|
||||||
const helpers = require('./route-helpers')
|
const helpers = require('./route-helpers')
|
||||||
const poller = require('./poller')
|
const poller = require('./poller')
|
||||||
const Tx = require('./tx')
|
const Tx = require('./tx')
|
||||||
|
const E = require('./error')
|
||||||
|
|
||||||
const argv = require('minimist')(process.argv.slice(2))
|
const argv = require('minimist')(process.argv.slice(2))
|
||||||
|
|
||||||
|
|
@ -108,6 +109,10 @@ function postTx (req, res, next) {
|
||||||
|
|
||||||
return res.json(tx)
|
return res.json(tx)
|
||||||
})
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (err instanceof E.StaleTxError) return res.status(404).json({})
|
||||||
|
throw err
|
||||||
|
})
|
||||||
.catch(next)
|
.catch(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,7 +275,11 @@ app.get('/tx/:id', getTx)
|
||||||
app.get('/tx', getPhoneTx)
|
app.get('/tx', getPhoneTx)
|
||||||
|
|
||||||
app.use(errorHandler)
|
app.use(errorHandler)
|
||||||
app.use((req, res) => res.status(404).json({error: 'No such route'}))
|
app.use((req, res) => {
|
||||||
|
console.log('DEBUG98')
|
||||||
|
console.log(req.route)
|
||||||
|
res.status(404).json({error: 'No such route'})
|
||||||
|
})
|
||||||
|
|
||||||
localApp.get('/pid', (req, res) => {
|
localApp.get('/pid', (req, res) => {
|
||||||
const deviceId = req.query.device_id
|
const deviceId = req.query.device_id
|
||||||
|
|
|
||||||
25
lib/tx.js
25
lib/tx.js
|
|
@ -23,9 +23,13 @@ function massage (tx) {
|
||||||
const transformDates = r => mapValuesWithKey(transformDate, r)
|
const transformDates = r => mapValuesWithKey(transformDate, r)
|
||||||
|
|
||||||
const mapBN = r => {
|
const mapBN = r => {
|
||||||
const update = r.direction === 'cashIn'
|
const update = {
|
||||||
? {cryptoAtoms: BN(r.cryptoAtoms), fiat: BN(r.fiat), cashInFee: BN(r.cashInFee)}
|
cryptoAtoms: BN(r.cryptoAtoms),
|
||||||
: {cryptoAtoms: BN(r.cryptoAtoms), fiat: BN(r.fiat)}
|
fiat: BN(r.fiat),
|
||||||
|
cashInFee: BN(r.cashInFee),
|
||||||
|
cashInFeeCrypto: BN(r.cashInFeeCrypto),
|
||||||
|
minimumTx: BN(r.minimumTx)
|
||||||
|
}
|
||||||
|
|
||||||
return _.assign(r, update)
|
return _.assign(r, update)
|
||||||
}
|
}
|
||||||
|
|
@ -35,4 +39,17 @@ function massage (tx) {
|
||||||
return mapper(tx)
|
return mapper(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {post}
|
function cancel (txId) {
|
||||||
|
const promises = [
|
||||||
|
CashInTx.cancel(txId).then(() => true).catch(() => false),
|
||||||
|
CashOutTx.cancel(txId).then(() => true).catch(() => false)
|
||||||
|
]
|
||||||
|
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then(r => {
|
||||||
|
if (_.some(r)) return
|
||||||
|
throw new Error('No such transaction')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {post, cancel}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ var anonymous = require('../lib/constants').anonymousCustomer
|
||||||
|
|
||||||
exports.up = function (next) {
|
exports.up = function (next) {
|
||||||
const sql =
|
const sql =
|
||||||
[
|
[`create table customers (
|
||||||
`create table customers (
|
|
||||||
id uuid PRIMARY KEY,
|
id uuid PRIMARY KEY,
|
||||||
phone text unique,
|
phone text unique,
|
||||||
phone_at timestamptz,
|
phone_at timestamptz,
|
||||||
|
|
@ -23,12 +22,12 @@ exports.up = function(next) {
|
||||||
created timestamptz NOT NULL DEFAULT now() )`,
|
created timestamptz NOT NULL DEFAULT now() )`,
|
||||||
`insert into customers (id, name) VALUES ( '${anonymous.uuid}','${anonymous.name}' )`,
|
`insert into customers (id, name) VALUES ( '${anonymous.uuid}','${anonymous.name}' )`,
|
||||||
`alter table cash_in_txs add column customer_id uuid references customers (id) DEFAULT '${anonymous.uuid}'`,
|
`alter table cash_in_txs add column customer_id uuid references customers (id) DEFAULT '${anonymous.uuid}'`,
|
||||||
`alter table cash_out_txs add column customer_id uuid references customers (id) DEFAULT '${anonymous.uuid}'`,
|
`alter table cash_out_txs add column customer_id uuid references customers (id) DEFAULT '${anonymous.uuid}'`
|
||||||
]
|
]
|
||||||
|
|
||||||
db.multi(sql, next)
|
db.multi(sql, next)
|
||||||
};
|
}
|
||||||
|
|
||||||
exports.down = function (next) {
|
exports.down = function (next) {
|
||||||
next();
|
next()
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ var db = require('./db')
|
||||||
|
|
||||||
exports.up = function (next) {
|
exports.up = function (next) {
|
||||||
const sql =
|
const sql =
|
||||||
[`create type compliance_types as enum ('manual', 'sanctions', 'sanctions_override') `,
|
[ "create type compliance_types as enum ('manual', 'sanctions', 'sanctions_override')",
|
||||||
`create table compliance_authorizations (
|
`create table compliance_authorizations (
|
||||||
id uuid PRIMARY KEY,
|
id uuid PRIMARY KEY,
|
||||||
customer_id uuid REFERENCES customers (id),
|
customer_id uuid REFERENCES customers (id),
|
||||||
|
|
@ -10,10 +10,9 @@ exports.up = function(next) {
|
||||||
authorized_at timestamptz NOT NULL,
|
authorized_at timestamptz NOT NULL,
|
||||||
authorized_by text REFERENCES user_tokens (token) )` ]
|
authorized_by text REFERENCES user_tokens (token) )` ]
|
||||||
|
|
||||||
|
|
||||||
db.multi(sql, next)
|
db.multi(sql, next)
|
||||||
};
|
}
|
||||||
|
|
||||||
exports.down = function (next) {
|
exports.down = function (next) {
|
||||||
next();
|
next()
|
||||||
};
|
}
|
||||||
|
|
|
||||||
14
migrations/1503907708756-drop-device-time.js
Normal file
14
migrations/1503907708756-drop-device-time.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
var db = require('./db')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
const sql = [
|
||||||
|
'alter table cash_in_txs drop column device_time',
|
||||||
|
'alter table cash_out_txs drop column device_time'
|
||||||
|
]
|
||||||
|
|
||||||
|
db.multi(sql, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
14
migrations/1503945570220-add-tx-version.js
Normal file
14
migrations/1503945570220-add-tx-version.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
var db = require('./db')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
const sql = [
|
||||||
|
'alter table cash_in_txs add column tx_version integer not null',
|
||||||
|
'alter table cash_out_txs add column tx_version integer not null'
|
||||||
|
]
|
||||||
|
|
||||||
|
db.multi(sql, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
3134
public/elm.js
3134
public/elm.js
File diff suppressed because it is too large
Load diff
|
|
@ -204,6 +204,12 @@ p {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lamassuAdminTxTable a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #5f5f56;
|
||||||
|
border-bottom: 1px solid #37e8d7;
|
||||||
|
}
|
||||||
|
|
||||||
.lamassuAdminTxTable .lamassuAdminNumberColumn {
|
.lamassuAdminTxTable .lamassuAdminNumberColumn {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 10em;
|
width: 10em;
|
||||||
|
|
@ -215,6 +221,10 @@ p {
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lamassuAdminTxTable .lamassuAdminTxCancelled {
|
||||||
|
background-color: #efd1d2;
|
||||||
|
}
|
||||||
|
|
||||||
.lamassuAdminTxTable tbody {
|
.lamassuAdminTxTable tbody {
|
||||||
font-family: Inconsolata, monospace;
|
font-family: Inconsolata, monospace;
|
||||||
color: #5f5f56;
|
color: #5f5f56;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue