WIPP
This commit is contained in:
parent
b4d8f3cd4c
commit
340b39d47d
9 changed files with 189 additions and 72 deletions
38
lib/bill-math.js
Normal file
38
lib/bill-math.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
const uuid = require('uuid')
|
||||
|
||||
// Custom algorith for two cassettes. For three or more denominations, we'll need
|
||||
// to rethink this. Greedy algorithm fails to find *any* solution in some cases.
|
||||
// Dynamic programming may be too inefficient for large amounts.
|
||||
//
|
||||
// We can either require canononical denominations for 3+, or try to expand
|
||||
// this algorithm.
|
||||
exports.makeChange = function makeChange (cartridges, amount) {
|
||||
// Note: Everything here is converted to primitive numbers,
|
||||
// since they're all integers, well within JS number range,
|
||||
// and this is way more efficient in a tight loop.
|
||||
|
||||
console.log('DEBUG777: %j', cartridges)
|
||||
const small = cartridges[0]
|
||||
const large = cartridges[1]
|
||||
|
||||
const largeDenom = large.denomination
|
||||
const smallDenom = small.denomination
|
||||
const largeBills = Math.min(large.count, Math.floor(amount / largeDenom))
|
||||
const amountNum = amount.toNumber()
|
||||
|
||||
for (let i = largeBills; i >= 0; i--) {
|
||||
const remainder = amountNum - largeDenom * i
|
||||
|
||||
if (remainder % smallDenom !== 0) continue
|
||||
|
||||
const smallCount = remainder / smallDenom
|
||||
if (smallCount > small.count) continue
|
||||
|
||||
return [
|
||||
{count: smallCount, denomination: small.denomination, id: uuid.v4()},
|
||||
{count: i, denomination: largeDenom, id: uuid.v4()}
|
||||
]
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
|
@ -68,6 +68,8 @@ function toObj (row) {
|
|||
newObj[objKey] = row[key]
|
||||
})
|
||||
|
||||
newObj.direction = 'cashIn'
|
||||
|
||||
return newObj
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,25 +2,11 @@ const _ = require('lodash/fp')
|
|||
const pgp = require('pg-promise')()
|
||||
const db = require('./db')
|
||||
const BN = require('./bn')
|
||||
const billMath = require('./bill-math')
|
||||
|
||||
module.exports = {post}
|
||||
|
||||
// id | uuid | not null
|
||||
// device_id | text | not null
|
||||
// to_address | text | not null
|
||||
// crypto_atoms | bigint | not null
|
||||
// crypto_code | text | not null
|
||||
// fiat | numeric(14,5) | not null
|
||||
// currency_code | text | not null
|
||||
// tx_hash | text |
|
||||
// status | status_stage | not null default 'notSeen'::status_stage
|
||||
// dispensed | boolean | not null default false
|
||||
// notified | boolean | not null default false
|
||||
// redeem | boolean | not null default false
|
||||
// phone | text |
|
||||
// error | text |
|
||||
// created | timestamp with time zone | not null default now()
|
||||
// confirmation_time | timestamp with time zone |
|
||||
const mapValuesWithKey = _.mapValues.convert({cap: false})
|
||||
|
||||
const UPDATEABLE_FIELDS = ['txHash', 'status', 'dispensed', 'notified', 'redeem',
|
||||
'phone', 'error', 'confirmationTime']
|
||||
|
|
@ -33,9 +19,13 @@ function post (tx, pi) {
|
|||
function transaction (t) {
|
||||
const sql = 'select * from cash_out_txs where id=$1'
|
||||
|
||||
console.log('DEBUG888: %j', tx)
|
||||
console.log('DEBUG988: %j', tx)
|
||||
return t.oneOrNone(sql, [tx.id])
|
||||
.then(row => upsert(row, tx))
|
||||
.then(toObj)
|
||||
.then(oldTx => {
|
||||
return preProcess(oldTx, tx, pi)
|
||||
.then(preProcessedTx => upsert(oldTx, preProcessedTx))
|
||||
})
|
||||
}
|
||||
|
||||
transaction.txMode = tmSRD
|
||||
|
|
@ -86,12 +76,12 @@ function toObj (row) {
|
|||
newObj[objKey] = row[key]
|
||||
})
|
||||
|
||||
newObj.direction = 'cashOut'
|
||||
|
||||
return newObj
|
||||
}
|
||||
|
||||
function upsert (row, tx) {
|
||||
const oldTx = toObj(row)
|
||||
|
||||
function upsert (oldTx, tx) {
|
||||
// insert bills
|
||||
|
||||
if (!oldTx) {
|
||||
|
|
@ -103,10 +93,40 @@ function upsert (row, tx) {
|
|||
.then(newTx => [oldTx, newTx])
|
||||
}
|
||||
|
||||
function mapDispense (tx) {
|
||||
const bills = tx.bills
|
||||
|
||||
if (_.isEmpty(bills)) return tx
|
||||
|
||||
const extra = {
|
||||
dispensed1: bills[0].actualDispense,
|
||||
dispensed2: bills[1].actualDispense,
|
||||
rejected1: bills[0].rejected,
|
||||
rejected2: bills[1].rejected,
|
||||
denomination1: bills[0].denomination,
|
||||
denomination2: bills[1].denomination,
|
||||
dispenseTime: 'NOW()^'
|
||||
}
|
||||
|
||||
return _.assign(tx, extra)
|
||||
}
|
||||
|
||||
function toDb (tx) {
|
||||
const mapper = (v, k) => {
|
||||
if (k === 'fiat' || k === 'cryptoAtoms') return v.toNumber()
|
||||
return v
|
||||
}
|
||||
|
||||
const massager = _.flow(mapValuesWithKey(mapper), mapDispense, _.omit(['direction', 'bills']), _.mapKeys(_.snakeCase))
|
||||
return massager(tx)
|
||||
}
|
||||
|
||||
function insert (tx) {
|
||||
const dbTx = _.mapKeys(_.snakeCase, _.omit(['direction', 'bills'], tx))
|
||||
const dbTx = toDb(tx)
|
||||
|
||||
const sql = pgp.helpers.insert(dbTx, null, 'cash_out_txs') + ' returning *'
|
||||
console.log('DEBUG901: %s', sql)
|
||||
console.log('DEBUG902: %j', dbTx)
|
||||
return db.one(sql)
|
||||
.then(toObj)
|
||||
}
|
||||
|
|
@ -114,17 +134,36 @@ function insert (tx) {
|
|||
function update (tx, changes) {
|
||||
if (_.isEmpty(changes)) return Promise.resolve(tx)
|
||||
|
||||
const dbChanges = _.mapKeys(_.snakeCase, _.omit(['direction', 'bills'], changes))
|
||||
const dbChanges = toDb(tx)
|
||||
console.log('DEBUG893: %j', dbChanges)
|
||||
const sql = pgp.helpers.update(dbChanges, null, 'cash_out_txs') +
|
||||
pgp.as.format(' where id=$1', [tx.id]) + ' returning *'
|
||||
pgp.as.format(' where id=$1', [tx.id])
|
||||
|
||||
return db.one(sql)
|
||||
.then(toObj)
|
||||
const newTx = _.merge(tx, changes)
|
||||
|
||||
return db.none(sql)
|
||||
.then(() => newTx)
|
||||
}
|
||||
|
||||
function preProcess (tx, newTx, pi) {
|
||||
if (!tx) {
|
||||
console.log('DEBUG910')
|
||||
return pi.newAddress(newTx)
|
||||
.then(_.set('toAddress', _, newTx))
|
||||
}
|
||||
|
||||
return Promise.resolve(newTx)
|
||||
}
|
||||
|
||||
function postProcess (txVector, pi) {
|
||||
const [oldTx, newTx] = txVector
|
||||
const [, newTx] = txVector
|
||||
|
||||
return Promise.resolve({})
|
||||
if (newTx.dispensed && !newTx.bills) {
|
||||
return pi.buildCartridges()
|
||||
.then(cartridges => {
|
||||
return _.set('bills', billMath.makeChange(cartridges.cartridges, newTx.fiat), newTx)
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.resolve(newTx)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,8 +72,14 @@ function plugins (settings, deviceId) {
|
|||
return balances
|
||||
}
|
||||
|
||||
function buildCartridges (cartridges, virtualCartridges, rec) {
|
||||
return {
|
||||
function buildCartridges () {
|
||||
const config = configManager.machineScoped(deviceId, settings.config)
|
||||
const cartridges = [ config.topCashOutDenomination,
|
||||
config.bottomCashOutDenomination ]
|
||||
const virtualCartridges = [config.virtualCashOutDenomination]
|
||||
|
||||
return dbm.cartridgeCounts(deviceId)
|
||||
.then(rec => ({
|
||||
cartridges: [
|
||||
{
|
||||
denomination: parseInt(cartridges[0], 10),
|
||||
|
|
@ -85,7 +91,7 @@ function plugins (settings, deviceId) {
|
|||
}
|
||||
],
|
||||
virtualCartridges
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
function fetchCurrentConfigVersion () {
|
||||
|
|
@ -102,9 +108,6 @@ function plugins (settings, deviceId) {
|
|||
const config = configManager.machineScoped(deviceId, settings.config)
|
||||
const fiatCode = config.fiatCurrency
|
||||
const cryptoCodes = config.cryptoCurrencies
|
||||
const cartridges = [ config.topCashOutDenomination,
|
||||
config.bottomCashOutDenomination ]
|
||||
const virtualCartridges = [config.virtualCashOutDenomination]
|
||||
|
||||
const tickerPromises = cryptoCodes.map(c => ticker.getRates(settings, fiatCode, c))
|
||||
const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c))
|
||||
|
|
@ -112,20 +115,20 @@ function plugins (settings, deviceId) {
|
|||
const currentConfigVersionPromise = fetchCurrentConfigVersion()
|
||||
|
||||
const promises = [
|
||||
dbm.cartridgeCounts(deviceId),
|
||||
buildCartridges(),
|
||||
pingPromise,
|
||||
currentConfigVersionPromise
|
||||
].concat(tickerPromises, balancePromises)
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(arr => {
|
||||
const cartridgeCounts = arr[0]
|
||||
const cartridges = arr[0]
|
||||
const currentConfigVersion = arr[2]
|
||||
const tickers = arr.slice(3, cryptoCodes.length + 3)
|
||||
const balances = arr.slice(cryptoCodes.length + 3)
|
||||
|
||||
return {
|
||||
cartridges: buildCartridges(cartridges, virtualCartridges, cartridgeCounts),
|
||||
cartridges,
|
||||
rates: buildRates(tickers),
|
||||
balances: buildBalances(balances),
|
||||
currentConfigVersion
|
||||
|
|
@ -174,29 +177,13 @@ function plugins (settings, deviceId) {
|
|||
return dbm.machineEvent(event)
|
||||
}
|
||||
|
||||
function cashOut (tx) {
|
||||
function newAddress (tx) {
|
||||
const cryptoCode = tx.cryptoCode
|
||||
|
||||
const serialPromise = wallet.supportsHD
|
||||
? dbm.nextCashOutSerialHD(tx.id, cryptoCode)
|
||||
: Promise.resolve()
|
||||
|
||||
return serialPromise
|
||||
.then(serialNumber => {
|
||||
const info = {
|
||||
label: 'TX ' + Date.now(),
|
||||
account: 'deposit',
|
||||
serialNumber
|
||||
}
|
||||
|
||||
return wallet.newAddress(settings, cryptoCode, info)
|
||||
.then(address => {
|
||||
const newTx = _.set('toAddress', address, tx)
|
||||
|
||||
return dbm.addInitialIncoming(deviceId, newTx, address)
|
||||
.then(() => address)
|
||||
})
|
||||
})
|
||||
const info = {
|
||||
label: 'TX ' + Date.now(),
|
||||
account: 'deposit'
|
||||
}
|
||||
return wallet.newAddress(settings, cryptoCode, info)
|
||||
}
|
||||
|
||||
function dispenseAck (tx) {
|
||||
|
|
@ -486,7 +473,7 @@ function plugins (settings, deviceId) {
|
|||
pollQueries,
|
||||
trade,
|
||||
sendCoins,
|
||||
cashOut,
|
||||
newAddress,
|
||||
dispenseAck,
|
||||
getPhoneCode,
|
||||
executeTrades,
|
||||
|
|
@ -498,7 +485,8 @@ function plugins (settings, deviceId) {
|
|||
sweepLiveHD,
|
||||
sweepOldHD,
|
||||
sendMessage,
|
||||
checkBalances
|
||||
checkBalances,
|
||||
buildCartridges
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,12 +71,15 @@ function fetchPhoneTx (phone) {
|
|||
}
|
||||
|
||||
function fetchStatusTx (txId, status) {
|
||||
console.log('DEBUG444')
|
||||
const sql = 'select * from cash_out_txs where id=$1'
|
||||
|
||||
return db.oneOrNone(sql, [txId, status])
|
||||
return db.oneOrNone(sql, [txId])
|
||||
.then(toObj)
|
||||
.then(tx => {
|
||||
console.log('DEBUG445')
|
||||
if (!tx) throw httpError('No transaction', 404)
|
||||
console.log('DEBUG446')
|
||||
if (tx.status === status) throw httpError('Not Modified', 304)
|
||||
return tx
|
||||
})
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ function poll (req, res, next) {
|
|||
|
||||
pids[deviceId] = {pid, ts: Date.now()}
|
||||
|
||||
pi.pollQueries(deviceTime, req.query)
|
||||
return pi.pollQueries(deviceTime, req.query)
|
||||
.then(results => {
|
||||
const cartridges = results.cartridges
|
||||
|
||||
|
|
@ -73,15 +73,32 @@ function poll (req, res, next) {
|
|||
response.idVerificationLimit = config.idVerificationLimit
|
||||
}
|
||||
|
||||
console.log('DEBUG22: %j', response)
|
||||
return res.json(response)
|
||||
})
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
function getTx (req, res, next) {
|
||||
if (req.query.phone) return helpers.fetchPhoneTx(req.query.phone)
|
||||
if (req.query.status) return helpers.fetchStatusTx(req.query.status)
|
||||
throw httpError('Not Found', 404)
|
||||
console.log('DEBUG333: %s', req.query.status)
|
||||
if (req.query.status) {
|
||||
return helpers.fetchStatusTx(req.params.id, req.query.status)
|
||||
.then(r => res.json(r))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
console.log('DEBUG334')
|
||||
return next(httpError('Not Found', 404))
|
||||
}
|
||||
|
||||
function getPhoneTx (req, res, next) {
|
||||
if (req.query.phone) {
|
||||
return helpers.fetchPhoneTx(req.query.phone)
|
||||
.then(r => res.json(r))
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
return next(httpError('Not Found', 404))
|
||||
}
|
||||
|
||||
function postTx (req, res, next) {
|
||||
|
|
@ -153,7 +170,7 @@ function phoneCode (req, res, next) {
|
|||
}
|
||||
|
||||
function errorHandler (err, req, res, next) {
|
||||
const statusCode = err.name === 'HttpError'
|
||||
const statusCode = err.name === 'HTTPError'
|
||||
? err.code || 500
|
||||
: 500
|
||||
|
||||
|
|
@ -244,7 +261,8 @@ app.post('/verify_transaction', verifyTx)
|
|||
|
||||
app.post('/phone_code', phoneCode)
|
||||
app.post('/tx', postTx)
|
||||
app.get('/tx', getTx)
|
||||
app.get('/tx/:id', getTx)
|
||||
app.get('/tx', getPhoneTx)
|
||||
|
||||
app.use(errorHandler)
|
||||
app.use((req, res) => res.status(404).json({err: 'No such route'}))
|
||||
|
|
|
|||
14
lib/tx.js
14
lib/tx.js
|
|
@ -1,10 +1,18 @@
|
|||
const _ = require('lodash/fp')
|
||||
const BN = require('./bn')
|
||||
const CashInTx = require('./cash-in-tx')
|
||||
const CashOutTx = require('./cash-out-tx')
|
||||
|
||||
function post (tx, pi) {
|
||||
if (tx.direction === 'cashIn') return CashInTx.post(tx, pi)
|
||||
if (tx.direction === 'cashOut') throw new Error('not implemented')
|
||||
const mtx = massage(tx)
|
||||
if (mtx.direction === 'cashIn') return CashInTx.post(mtx, pi)
|
||||
if (mtx.direction === 'cashOut') return CashOutTx.post(mtx, pi)
|
||||
|
||||
return Promise.reject(new Error('No such tx direction: %s', tx.direction))
|
||||
return Promise.reject(new Error('No such tx direction: ' + mtx.direction))
|
||||
}
|
||||
|
||||
function massage (tx) {
|
||||
return _.assign(tx, {cryptoAtoms: BN(tx.cryptoAtoms), fiat: BN(tx.fiat)})
|
||||
}
|
||||
|
||||
module.exports = {post}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ var db = require('./db')
|
|||
exports.up = function (next) {
|
||||
var sql = [
|
||||
'alter table cash_in_txs add column send boolean not null default false',
|
||||
'alter table cash_in_txs rename currency_code to fiat_code'
|
||||
'alter table cash_in_txs rename currency_code to fiat_code',
|
||||
'alter table cash_out_txs rename currency_code to fiat_code'
|
||||
]
|
||||
db.multi(sql, next)
|
||||
}
|
||||
|
|
|
|||
20
migrations/023-add-dispenses-to-cash-out.js
Normal file
20
migrations/023-add-dispenses-to-cash-out.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
var db = require('./db')
|
||||
|
||||
exports.up = function (next) {
|
||||
var sql = [
|
||||
'alter table cash_out_txs add column dispensed_1 integer',
|
||||
'alter table cash_out_txs add column dispensed_2 integer',
|
||||
'alter table cash_out_txs add column rejected_1 integer',
|
||||
'alter table cash_out_txs add column rejected_2 integer',
|
||||
'alter table cash_out_txs add column denomination_1 integer',
|
||||
'alter table cash_out_txs add column denomination_2 integer',
|
||||
'alter table cash_out_txs add column dispense_error text',
|
||||
'alter table cash_out_txs add column dispense_time timestamptz',
|
||||
'drop table dispenses'
|
||||
]
|
||||
db.multi(sql, next)
|
||||
}
|
||||
|
||||
exports.down = function (next) {
|
||||
next()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue