diff --git a/lamassu-schema.json b/lamassu-schema.json index 5ad0d845..4bda4d34 100644 --- a/lamassu-schema.json +++ b/lamassu-schema.json @@ -70,7 +70,8 @@ "fields": [ "ticker", "wallet", - "exchange" + "exchange", + "zeroConf" ] }, { @@ -176,7 +177,7 @@ "displayBottom": "Limit", "fieldType": "integer", "fieldClass": "fiat", - "cryptoScope": "both", + "cryptoScope": "global", "machineScope": "both", "enabledIf": [ "cashOutEnabled" @@ -213,6 +214,14 @@ "fieldValidation": [], "default": "no-exchange" }, + { + "code": "zeroConf", + "displayBottom": "Zero Conf", + "fieldType": "account", + "fieldClass": "zeroConf", + "fieldValidation": [], + "default": "all-zero-conf" + }, { "code": "fiatCurrency", "displayBottom": "Fiat Currency", diff --git a/lib/admin/config.js b/lib/admin/config.js index 2332d321..1fa27c65 100644 --- a/lib/admin/config.js +++ b/lib/admin/config.js @@ -153,7 +153,10 @@ function fetchData () { {code: 'mock-sms', display: 'Mock SMS', class: 'sms'}, {code: 'mock-id-verify', display: 'Mock ID verifier', class: 'idVerifier'}, {code: 'twilio', display: 'Twilio', class: 'sms'}, - {code: 'mailjet', display: 'Mailjet', class: 'email'} + {code: 'mailjet', display: 'Mailjet', class: 'email'}, + {code: 'all-zero-conf', display: 'All pass', class: 'zeroConf'}, + {code: 'blockcypher', display: 'Blockcypher', class: 'zeroConf'}, + {code: 'mock-zero-conf', display: 'Mock 0-conf', class: 'zeroConf'} ], machines: machineList.map(machine => ({machine: machine.deviceId, display: machine.name})) })) diff --git a/lib/plugin-helper.js b/lib/plugin-helper.js index d4ce91b8..d9c9b6f5 100644 --- a/lib/plugin-helper.js +++ b/lib/plugin-helper.js @@ -8,7 +8,8 @@ const pluginCodes = { EXCHANGE: 'exchange', WALLET: 'wallet', SMS: 'sms', - EMAIL: 'email' + EMAIL: 'email', + ZERO_CONF: 'zero-conf' } module.exports = _.assign({load, loadSchemas}, pluginCodes) diff --git a/lib/plugins.js b/lib/plugins.js index 40f6753d..06ec353f 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -231,7 +231,7 @@ function plugins (settings, deviceId) { } function getStatus (tx) { - return wallet.getStatus(settings, tx.toAddress, tx.cryptoAtoms, tx.cryptoCode) + return wallet.getStatus(settings, tx.toAddress, tx.cryptoAtoms, tx.cryptoCode, tx.fiat, deviceId) } function newAddress (tx) { @@ -471,6 +471,8 @@ function plugins (settings, deviceId) { const config = configManager.machineScoped(_deviceId, settings.config) const cryptoCodes = config.cryptoCurrencies const fiatCode = config.fiatCurrency + + console.log('DEBUG103: %j', cryptoCodes) const fiatBalancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c)) return Promise.all(fiatBalancePromises) diff --git a/lib/plugins/zero-conf/blockcypher/blockcypher.js b/lib/plugins/zero-conf/blockcypher/blockcypher.js new file mode 100644 index 00000000..21e71f52 --- /dev/null +++ b/lib/plugins/zero-conf/blockcypher/blockcypher.js @@ -0,0 +1,28 @@ +const qs = require('querystring') +const axios = require('axios') +const _ = require('lodash/fp') + +module.exports = {authorize} + +function authorize (account, toAddress, cryptoAtoms, cryptoCode) { + return Promise.resolve() + .then(() => { + if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode) + + const query = qs.stringify({ + token: account.token, + includeConfidence: true, + confidence: account.confidence + }) + + const url = `https://api.blockcypher.com/v1/btc/main/addrs/${toAddress}?${query}` + + return axios.get(url) + .then(r => { + const data = r.data + const authorizedValue = _.sumBy('value', data.txrefs) + + return cryptoAtoms.lte(authorizedValue) + }) + }) +} diff --git a/lib/plugins/zero-conf/mock-zero-conf/mock-zero-conf.js b/lib/plugins/zero-conf/mock-zero-conf/mock-zero-conf.js new file mode 100644 index 00000000..4427162c --- /dev/null +++ b/lib/plugins/zero-conf/mock-zero-conf/mock-zero-conf.js @@ -0,0 +1,11 @@ +module.exports = {authorize} + +function authorize (account, toAddress, cryptoAtoms, cryptoCode) { + return Promise.resolve() + .then(() => { + if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode) + + const authorizedValue = 1e5 * 10 + return cryptoAtoms.lte(authorizedValue) + }) +} diff --git a/lib/wallet.js b/lib/wallet.js index 6d0f73ae..08cb2faf 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -26,6 +26,7 @@ function computeSeed (masterSeed) { } function fetchWallet (settings, cryptoCode) { + console.log('DEBUG102') return fs.readFile(options.seedPath, 'utf8') .then(hex => { const masterSeed = Buffer.from(hex.trim(), 'hex') @@ -66,11 +67,39 @@ function newAddress (settings, info) { .then(r => r.wallet.newAddress(r.account, info)) } -function getStatus (settings, toAddress, cryptoAtoms, cryptoCode) { +function getWalletStatus (settings, toAddress, cryptoAtoms, cryptoCode) { return fetchWallet(settings, cryptoCode) .then(r => r.wallet.getStatus(r.account, toAddress, cryptoAtoms, cryptoCode)) } +function authorizeZeroConf (settings, toAddress, cryptoAtoms, cryptoCode, fiat, machineId) { + const cryptoConfig = configManager.cryptoScoped(cryptoCode, settings.config) + const machineConfig = configManager.machineScoped(machineId, settings.config) + const plugin = cryptoConfig.zeroConf + const zeroConfLimit = machineConfig.zeroConfLimit + + if (fiat.gt(zeroConfLimit)) return Promise.resolve(false) + if (cryptoCode !== 'BTC' || plugin === 'all-zero-conf') return Promise.resolve(true) + + const zeroConf = ph.load(ph.ZERO_CONF, plugin) + const account = settings.accounts[plugin] + + return zeroConf.authorize(account, toAddress, cryptoAtoms, cryptoCode) +} + +function getStatus (settings, toAddress, cryptoAtoms, cryptoCode, fiat, machineId) { + const promises = [ + getWalletStatus(settings, toAddress, cryptoAtoms, cryptoCode), + authorizeZeroConf(settings, toAddress, cryptoAtoms, cryptoCode, fiat, machineId) + ] + + return Promise.all(promises) + .then(([status, isAuthorized]) => { + if (status === 'authorized') return isAuthorized ? 'authorized' : 'published' + return status + }) +} + function sweep (settings, cryptoCode, hdIndex) { return fetchWallet(settings, cryptoCode) .then(r => r.wallet.sweep(r.account, cryptoCode, hdIndex)) diff --git a/migrations/034-add_cash_out_error_code.js b/migrations/034-add_cash_out_error_code.js index 939238c6..b8a446b0 100644 --- a/migrations/034-add_cash_out_error_code.js +++ b/migrations/034-add_cash_out_error_code.js @@ -1,8 +1,12 @@ +var db = require('./db') -exports.up = function(next){ - next(); -}; +exports.up = function (next) { + var sql = [ + 'alter table cash_out_txs add column error_code text' + ] + db.multi(sql, next) +} -exports.down = function(next){ - next(); -}; +exports.down = function (next) { + next() +} diff --git a/public/elm.js b/public/elm.js index e0108007..7556a1f1 100644 --- a/public/elm.js +++ b/public/elm.js @@ -34010,19 +34010,28 @@ var _user$project$NavBar$view = F2( ctor: '::', _0: { ctor: '_Tuple3', - _0: 'Twilio', - _1: _user$project$CoreTypes$AccountRoute('twilio'), + _0: 'Blockcypher', + _1: _user$project$CoreTypes$AccountRoute('blockcypher'), _2: true }, _1: { ctor: '::', _0: { ctor: '_Tuple3', - _0: 'Mailjet', - _1: _user$project$CoreTypes$AccountRoute('mailjet'), + _0: 'Twilio', + _1: _user$project$CoreTypes$AccountRoute('twilio'), _2: true }, - _1: {ctor: '[]'} + _1: { + ctor: '::', + _0: { + ctor: '_Tuple3', + _0: 'Mailjet', + _1: _user$project$CoreTypes$AccountRoute('mailjet'), + _2: true + }, + _1: {ctor: '[]'} + } } } } diff --git a/schemas/blockcypher.json b/schemas/blockcypher.json new file mode 100644 index 00000000..e56becd4 --- /dev/null +++ b/schemas/blockcypher.json @@ -0,0 +1,20 @@ +{ + "code": "blockcypher", + "display": "Blockcypher", + "fields": [ + { + "code": "token", + "display": "API token", + "fieldType": "password", + "required": true, + "value": "" + }, + { + "code": "confidenceFactor", + "display": "Confidence Factor", + "fieldType": "string", + "required": true, + "value": "" + } + ] +}