diff --git a/lib/admin/config.js b/lib/admin/config.js index 2b8dc1e0..81f53f64 100644 --- a/lib/admin/config.js +++ b/lib/admin/config.js @@ -150,6 +150,7 @@ const mapLanguage = lang => { const supportedLanguages = languageRec.supported const languages = supportedLanguages.map(mapLanguage).filter(r => r) +const ALL_CRYPTOS = ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC'] function fetchData () { return machineLoader.getMachineNames() @@ -158,31 +159,35 @@ function fetchData () { cryptoCurrencies: [ {crypto: 'BTC', display: 'Bitcoin'}, {crypto: 'ETH', display: 'Ethereum'}, + {crypto: 'LTC', display: 'Litecoin'}, + {crypto: 'DASH', display: 'Dash'}, {crypto: 'ZEC', display: 'Zcash'} ], languages: languages, countries, accounts: [ {code: 'bitpay', display: 'Bitpay', class: 'ticker', cryptos: ['BTC']}, - {code: 'kraken', display: 'Kraken', class: 'ticker', cryptos: ['BTC', 'ETH', 'ZEC']}, - {code: 'bitstamp', display: 'Bitstamp', class: 'ticker', cryptos: ['BTC']}, - {code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH']}, + {code: 'kraken', display: 'Kraken', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC']}, + {code: 'bitstamp', display: 'Bitstamp', class: 'ticker', cryptos: ['BTC', 'LTC']}, + {code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC']}, {code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']}, - {code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC']}, {code: 'geth', display: 'geth', class: 'wallet', cryptos: ['ETH']}, {code: 'zcashd', display: 'zcashd', class: 'wallet', cryptos: ['ZEC']}, + {code: 'litecoind', display: 'litecoind', class: 'wallet', cryptos: ['LTC']}, + {code: 'dashd', display: 'dashd', class: 'wallet', cryptos: ['DASH']}, + {code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC']}, {code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC']}, - {code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'ZEC']}, - {code: 'mock-wallet', display: 'Mock wallet', class: 'wallet', cryptos: ['BTC', 'ETH', 'ZEC']}, - {code: 'no-exchange', display: 'No exchange', class: 'exchange', cryptos: ['BTC', 'ETH', 'ZEC']}, - {code: 'mock-exchange', display: 'Mock exchange', class: 'exchange', cryptos: ['BTC', 'ETH', 'ZEC']}, + {code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC']}, + {code: 'mock-wallet', display: 'Mock wallet', class: 'wallet', cryptos: ALL_CRYPTOS}, + {code: 'no-exchange', display: 'No exchange', class: 'exchange', cryptos: ALL_CRYPTOS}, + {code: 'mock-exchange', display: 'Mock exchange', class: 'exchange', cryptos: ALL_CRYPTOS}, {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: 'all-zero-conf', display: 'All pass', class: 'zeroConf'}, {code: 'blockcypher', display: 'Blockcypher', class: 'zeroConf', cryptos: ['BTC']}, - {code: 'mock-zero-conf', display: 'Mock 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC']} + {code: 'mock-zero-conf', display: 'Mock 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH']} ], machines: machineList.map(machine => ({machine: machine.deviceId, display: machine.name})) })) diff --git a/lib/coin-utils.js b/lib/coin-utils.js index 9102f3b6..7d2f4b63 100644 --- a/lib/coin-utils.js +++ b/lib/coin-utils.js @@ -1,26 +1,40 @@ +const _ = require('lodash/fp') + const coins = { BTC: {unitScale: 8}, ETH: {unitScale: 18}, - ZEC: {unitScale: 8} + ZEC: {unitScale: 8}, + LTC: {unitScale: 8}, + DASH: {unitScale: 8} } const cryptoDisplays = [ {cryptoCode: 'BTC', display: 'Bitcoin'}, {cryptoCode: 'ETH', display: 'Ethereum'}, - {cryptoCode: 'ZEC', display: 'Zcash'} + {cryptoCode: 'ZEC', display: 'Zcash'}, + {cryptoCode: 'LTC', display: 'Litecoin'}, + {cryptoCode: 'DASH', display: 'Dash'} ] -module.exports = {coins, cryptoDisplays, buildUrl, unitScale} +module.exports = {coins, cryptoDisplays, buildUrl, unitScale, display} function buildUrl (cryptoCode, address) { switch (cryptoCode) { case 'BTC': return `bitcoin:${address}` case 'ETH': return `ethereum:${address}` case 'ZEC': return `zcash:${address}` + case 'LTC': return `litecoin:${address}` + case 'DASH': return `dash:${address}` default: throw new Error(`Unsupported crypto: ${cryptoCode}`) } } +function display (cryptoCode) { + const rec = _.find(['cryptoCode', cryptoCode], cryptoDisplays) + if (!rec) throw new Error(`Unsupported crypto: ${cryptoCode}`) + return rec.display +} + function unitScale (cryptoCode) { const scaleRec = coins[cryptoCode] if (!scaleRec) throw new Error(`Unsupported crypto: ${cryptoCode}`) diff --git a/lib/plugins.js b/lib/plugins.js index dd0c4717..670c2859 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -176,12 +176,12 @@ function plugins (settings, deviceId) { const minimumTx = BN(config.minimumTx) const cashInFee = BN(config.cashInFee) - const coinSettings = { + return { + cryptoCode: coin, + display: coinUtils.display(coin), minimumTx: BN.max(minimumTx, cashInFee), cashInFee: cashInFee } - - return [coin, coinSettings] } function pollQueries (serialNumber, deviceTime, deviceRec) { @@ -211,7 +211,7 @@ function plugins (settings, deviceId) { cassettes, rates: buildRates(tickers), balances: buildBalances(balances), - coinSettings: _.fromPairs(_.map(mapCoinSettings, cryptoCodes)), + coins: _.map(mapCoinSettings, cryptoCodes), configVersion } }) diff --git a/lib/plugins/common/json-rpc.js b/lib/plugins/common/json-rpc.js new file mode 100644 index 00000000..bc7b705d --- /dev/null +++ b/lib/plugins/common/json-rpc.js @@ -0,0 +1,46 @@ +// JSON-RPC for bitcoind-like interfaces +const axios = require('axios') +const uuid = require('uuid') +const fs = require('fs') + +module.exports = {fetch, parseConf} + +function fetch (account, method, params) { + const data = { + method, + params, + id: uuid.v4() + } + + return axios({ + method: 'post', + auth: {username: account.username, password: account.password}, + url: `http://localhost:${account.port}`, + data + }) + .then(r => { + if (r.error) throw r.error + return r.data.result + }) + .catch(err => { + console.log(err.response.data.error) + throw err + }) +} + +function parseConf (confPath) { + const conf = fs.readFileSync(confPath) + const lines = conf.toString().split('\n', 2) + + const res = {} + for (let i = 0; i < lines.length; i++) { + const keyVal = lines[i].split('=') + + // skip when value is empty + if (!keyVal[1]) continue + + res[keyVal[0]] = keyVal[1] + } + + return res +} diff --git a/lib/plugins/common/kraken.js b/lib/plugins/common/kraken.js index 5c644fe3..98090589 100644 --- a/lib/plugins/common/kraken.js +++ b/lib/plugins/common/kraken.js @@ -16,6 +16,14 @@ const PAIRS = { ZEC: { USD: 'XZECZUSD', EUR: 'XZECZEUR' + }, + LTC: { + USD: 'XLTCZUSD', + EUR: 'XLTCZEUR' + }, + DASH: { + USD: 'DASHUSD', + EUR: 'DASHEUR' } } diff --git a/lib/plugins/wallet/bitcoind/bitcoind.js b/lib/plugins/wallet/bitcoind/bitcoind.js index 34d90f66..f54b5cca 100644 --- a/lib/plugins/wallet/bitcoind/bitcoind.js +++ b/lib/plugins/wallet/bitcoind/bitcoind.js @@ -1,52 +1,24 @@ -const path = require('path') -const os = require('os') -const RpcClient = require('bitcoind-rpc') -const fs = require('fs') -const pify = require('pify') +const jsonRpc = require('../../common/json-rpc') +const path = require('path') +const options = require('../../../options') const BN = require('../../../bn') const E = require('../../../error') -const NAME = 'Bitcoind' - +const DEFAULT_PORT = 8332 const SATOSHI_SHIFT = 8 -const configPath = path.resolve(os.homedir(), '.bitcoin', 'bitcoin.conf') -const pluginConfig = { - account: '', - bitcoindConfigurationPath: configPath +const configPath = path.resolve(options.blockchainDir, 'bitcoin.conf') +const config = jsonRpc.parseConf(configPath) + +const rpcConfig = { + username: config.rpcuser, + password: config.rpcpassword, + port: config.rpcport || DEFAULT_PORT } -function initRpc () { - const bitcoindConf = parseConf(pluginConfig.bitcoindConfigurationPath) - - const rpcConfig = { - protocol: 'http', - user: bitcoindConf.rpcuser, - pass: bitcoindConf.rpcpassword - } - - return new RpcClient(rpcConfig) -} - -/* - * initialize RpcClient - */ -function parseConf (confPath) { - const conf = fs.readFileSync(confPath) - const lines = conf.toString().split('\n') - - const res = {} - for (let i = 0; i < lines.length; i++) { - const keyVal = lines[i].split('=') - - // skip when value is empty - if (!keyVal[1]) continue - - res[keyVal[0]] = keyVal[1] - } - - return res +function fetch (method, params) { + return jsonRpc.fetch(rpcConfig, method, params) } function checkCryptoCode (cryptoCode) { @@ -54,20 +26,10 @@ function checkCryptoCode (cryptoCode) { return Promise.resolve() } -function accountBalance (account, cryptoCode, confirmations) { +function accountBalance (acount, cryptoCode, confirmations) { return checkCryptoCode(cryptoCode) - .then(() => { - const rpc = initRpc() - - return new Promise((resolve, reject) => { - rpc.getBalance(pluginConfig.account, confirmations, (err, result) => { - if (err) return reject(err) - if (result.error) reject(new Error(err)) - - return resolve(BN(result.result).shift(SATOSHI_SHIFT).round()) - }) - }) - }) + .then(() => fetch('getbalance', ['', confirmations])) + .then(r => BN(r).shift(SATOSHI_SHIFT).round()) } // We want a balance that includes all spends (0 conf) but only deposits that @@ -77,42 +39,25 @@ function balance (account, cryptoCode) { } function sendCoins (account, address, cryptoAtoms, cryptoCode) { - const rpc = initRpc() const confirmations = 1 const bitcoins = cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8) return checkCryptoCode(cryptoCode) - .then(() => { - return new Promise((resolve, reject) => { - rpc.sendFrom(pluginConfig.account, address, bitcoins, confirmations, (err, result) => { - if (err) { - if (err.code === -6) return reject(new E.InsufficientFundsError()) - return reject(err) - } - - resolve(result.result) - }) - }) + .then(() => fetch('sendfrom', [address, bitcoins, confirmations])) + .catch(err => { + if (err.code === -6) throw new E.InsufficientFundsError() + throw err }) } function newAddress (account, info) { return checkCryptoCode(info.cryptoCode) - .then(() => { - return new Promise((resolve, reject) => { - const rpc = initRpc() - rpc.getNewAddress((err, result) => { - if (err) return reject(err) - resolve(result.result) - }) - }) - }) + .then(() => fetch('getnewaddress')) } function addressBalance (address, confs) { - const rpc = initRpc() - return pify(rpc.getReceivedByAddress.bind(rpc))(address, confs) - .then(r => BN(r.result).shift(SATOSHI_SHIFT).round()) + return fetch('getreceivedbyaddress', [address, confs]) + .then(r => BN(r).shift(SATOSHI_SHIFT).round()) } function confirmedBalance (address, cryptoCode) { @@ -159,10 +104,7 @@ function newFunding (account, cryptoCode) { } module.exports = { - NAME, balance, - pendingBalance, - confirmedBalance, sendCoins, newAddress, getStatus, diff --git a/lib/plugins/wallet/dashd/dashd.js b/lib/plugins/wallet/dashd/dashd.js new file mode 100644 index 00000000..ea000d96 --- /dev/null +++ b/lib/plugins/wallet/dashd/dashd.js @@ -0,0 +1,114 @@ +const jsonRpc = require('../../common/json-rpc') + +const path = require('path') +const options = require('../../../options') + +const BN = require('../../../bn') +const E = require('../../../error') + +const DEFAULT_PORT = 9998 +const SATOSHI_SHIFT = 8 + +const configPath = path.resolve(options.blockchainDir, 'dash.conf') + +const config = jsonRpc.parseConf(configPath) + +const rpcConfig = { + username: config.rpcuser, + password: config.rpcpassword, + port: config.rpcport || DEFAULT_PORT +} + +console.log('DEBUG100: %j', rpcConfig) +function fetch (method, params) { + return jsonRpc.fetch(rpcConfig, method, params) +} + +function checkCryptoCode (cryptoCode) { + if (cryptoCode !== 'DASH') return Promise.reject(new Error('Unsupported crypto: ' + cryptoCode)) + return Promise.resolve() +} + +function accountBalance (acount, cryptoCode, confirmations) { + return checkCryptoCode(cryptoCode) + .then(() => fetch('getbalance', ['', confirmations])) + .then(r => BN(r).shift(SATOSHI_SHIFT).round()) +} + +// We want a balance that includes all spends (0 conf) but only deposits that +// have at least 1 confirmation. getbalance does this for us automatically. +function balance (account, cryptoCode) { + return accountBalance(account, cryptoCode, 1) +} + +function sendCoins (account, address, cryptoAtoms, cryptoCode) { + const coins = cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8) + + return checkCryptoCode(cryptoCode) + .then(() => fetch('sendtoaddress', [address, coins])) + .catch(err => { + if (err.code === -6) throw new E.InsufficientFundsError() + throw err + }) +} + +function newAddress (account, info) { + return checkCryptoCode(info.cryptoCode) + .then(() => fetch('getnewaddress')) +} + +function addressBalance (address, confs) { + return fetch('getreceivedbyaddress', [address, confs]) + .then(r => BN(r).shift(SATOSHI_SHIFT).round()) +} + +function confirmedBalance (address, cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => addressBalance(address, 1)) +} + +function pendingBalance (address, cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => addressBalance(address, 0)) +} + +function getStatus (account, toAddress, requested, cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => confirmedBalance(toAddress, cryptoCode)) + .then(confirmed => { + if (confirmed.gte(requested)) return {status: 'confirmed'} + + return pendingBalance(toAddress, cryptoCode) + .then(pending => { + if (pending.gte(requested)) return {status: 'authorized'} + if (pending.gt(0)) return {status: 'insufficientFunds'} + return {status: 'notSeen'} + }) + }) +} + +function newFunding (account, cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => { + const promises = [ + accountBalance(account, cryptoCode, 0), + accountBalance(account, cryptoCode, 1), + newAddress(account, {cryptoCode}) + ] + + return Promise.all(promises) + }) + .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ + fundingPendingBalance, + fundingConfirmedBalance, + fundingAddress + })) +} + +module.exports = { + balance, + sendCoins, + newAddress, + getStatus, + newFunding +} diff --git a/lib/plugins/wallet/litecoind/litecoind.js b/lib/plugins/wallet/litecoind/litecoind.js new file mode 100644 index 00000000..4ab257f8 --- /dev/null +++ b/lib/plugins/wallet/litecoind/litecoind.js @@ -0,0 +1,113 @@ +const jsonRpc = require('../../common/json-rpc') + +const path = require('path') +const options = require('../../../options') + +const BN = require('../../../bn') +const E = require('../../../error') + +const DEFAULT_PORT = 9332 +const SATOSHI_SHIFT = 8 + +const configPath = path.resolve(options.blockchainDir, 'litecoin.conf') +const config = jsonRpc.parseConf(configPath) + +const rpcConfig = { + username: config.rpcuser, + password: config.rpcpassword, + port: config.rpcport || DEFAULT_PORT +} + +function fetch (method, params) { + return jsonRpc.fetch(rpcConfig, method, params) +} + +function checkCryptoCode (cryptoCode) { + if (cryptoCode !== 'LTC') return Promise.reject(new Error('Unsupported crypto: ' + cryptoCode)) + return Promise.resolve() +} + +function accountBalance (acount, cryptoCode, confirmations) { + return checkCryptoCode(cryptoCode) + .then(() => fetch('getbalance', ['', confirmations])) + .then(r => BN(r).shift(SATOSHI_SHIFT).round()) +} + +// We want a balance that includes all spends (0 conf) but only deposits that +// have at least 1 confirmation. getbalance does this for us automatically. +function balance (account, cryptoCode) { + return accountBalance(account, cryptoCode, 1) +} + +function sendCoins (account, address, cryptoAtoms, cryptoCode) { + const confirmations = 1 + const bitcoins = cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8) + + return checkCryptoCode(cryptoCode) + .then(() => fetch('sendfrom', [address, bitcoins, confirmations])) + .catch(err => { + if (err.code === -6) throw new E.InsufficientFundsError() + throw err + }) +} + +function newAddress (account, info) { + return checkCryptoCode(info.cryptoCode) + .then(() => fetch('getnewaddress')) +} + +function addressBalance (address, confs) { + return fetch('getreceivedbyaddress', [address, confs]) + .then(r => BN(r).shift(SATOSHI_SHIFT).round()) +} + +function confirmedBalance (address, cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => addressBalance(address, 1)) +} + +function pendingBalance (address, cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => addressBalance(address, 0)) +} + +function getStatus (account, toAddress, requested, cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => confirmedBalance(toAddress, cryptoCode)) + .then(confirmed => { + if (confirmed.gte(requested)) return {status: 'confirmed'} + + return pendingBalance(toAddress, cryptoCode) + .then(pending => { + if (pending.gte(requested)) return {status: 'authorized'} + if (pending.gt(0)) return {status: 'insufficientFunds'} + return {status: 'notSeen'} + }) + }) +} + +function newFunding (account, cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => { + const promises = [ + accountBalance(account, cryptoCode, 0), + accountBalance(account, cryptoCode, 1), + newAddress(account, {cryptoCode}) + ] + + return Promise.all(promises) + }) + .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ + fundingPendingBalance, + fundingConfirmedBalance, + fundingAddress + })) +} + +module.exports = { + balance, + sendCoins, + newAddress, + getStatus, + newFunding +} diff --git a/lib/plugins/wallet/mock-wallet/mock-wallet.js b/lib/plugins/wallet/mock-wallet/mock-wallet.js index 66591705..c71fe78c 100644 --- a/lib/plugins/wallet/mock-wallet/mock-wallet.js +++ b/lib/plugins/wallet/mock-wallet/mock-wallet.js @@ -1,5 +1,6 @@ const BN = require('../../../bn') const E = require('../../../error') +const coinUtils = require('../../../coin-utils') const NAME = 'FakeWallet' @@ -10,44 +11,31 @@ const CONFIRM_TIME = 180 * SECONDS let t0 +function _balance (cryptoCode) { + const unitScale = coinUtils.unitScale(cryptoCode) + return BN(10).pow(unitScale).mul(10) +} + function balance (account, cryptoCode) { return Promise.resolve() - .then(() => { - if (cryptoCode === 'BTC') return BN(1e8 * 10) - if (cryptoCode === 'ETH') return BN(1e18 * 10) - if (cryptoCode === 'ZEC') return BN(1e8 * 10) - throw new Error('Unsupported crypto: ' + cryptoCode) - }) + .then(() => _balance(cryptoCode)) } function pendingBalance (account, cryptoCode) { - return Promise.resolve() - .then(() => { - if (cryptoCode === 'BTC') return BN(1e8 * 10.1) - if (cryptoCode === 'ETH') return BN(1e18 * 10.1) - if (cryptoCode === 'ZEC') return BN(1e8 * 10.1) - throw new Error('Unsupported crypto: ' + cryptoCode) - }) + return balance(account, cryptoCode) + .then(b => b.mul(1.1)) } function confirmedBalance (account, cryptoCode) { - return Promise.resolve() - .then(() => { - if (cryptoCode === 'BTC') return BN(1e8 * 10) - if (cryptoCode === 'ETH') return BN(1e18 * 10) - if (cryptoCode === 'ZEC') return BN(1e8 * 10) - throw new Error('Unsupported crypto: ' + cryptoCode) - }) + return balance(account, cryptoCode) } // Note: This makes it easier to test insufficient funds errors let sendCount = 100 function isInsufficient (cryptoAtoms, cryptoCode) { - if (cryptoCode === 'BTC') return cryptoAtoms.gt(1e5 * 10 * sendCount) - if (cryptoCode === 'ETH') return cryptoAtoms.gt(1e18 * 0.25 * sendCount) - if (cryptoCode === 'ZEC') return cryptoAtoms.gt(1e5 * 0.25 * sendCount) - throw new Error('Unsupported crypto: ' + cryptoCode) + const b = _balance(cryptoCode) + return cryptoAtoms.gt(b.div(1000).mul(sendCount)) } function sendCoins (account, toAddress, cryptoAtoms, cryptoCode) { diff --git a/lib/plugins/wallet/zcashd/zcashd.js b/lib/plugins/wallet/zcashd/zcashd.js new file mode 100644 index 00000000..6cc9c216 --- /dev/null +++ b/lib/plugins/wallet/zcashd/zcashd.js @@ -0,0 +1,113 @@ +const jsonRpc = require('../../common/json-rpc') + +const path = require('path') +const options = require('../../../options') + +const BN = require('../../../bn') +const E = require('../../../error') + +const DEFAULT_PORT = 8232 +const SATOSHI_SHIFT = 8 + +const configPath = path.resolve(options.blockchainDir, 'zcash.conf') +const config = jsonRpc.parseConf(configPath) + +const rpcConfig = { + username: config.rpcuser, + password: config.rpcpassword, + port: config.rpcport || DEFAULT_PORT +} + +function fetch (method, params) { + return jsonRpc.fetch(rpcConfig, method, params) +} + +function checkCryptoCode (cryptoCode) { + if (cryptoCode !== 'ZEC') return Promise.reject(new Error('Unsupported crypto: ' + cryptoCode)) + return Promise.resolve() +} + +function accountBalance (acount, cryptoCode, confirmations) { + return checkCryptoCode(cryptoCode) + .then(() => fetch('getbalance', ['', confirmations])) + .then(r => BN(r).shift(SATOSHI_SHIFT).round()) +} + +// We want a balance that includes all spends (0 conf) but only deposits that +// have at least 1 confirmation. getbalance does this for us automatically. +function balance (account, cryptoCode) { + return accountBalance(account, cryptoCode, 1) +} + +function sendCoins (account, address, cryptoAtoms, cryptoCode) { + const confirmations = 1 + const bitcoins = cryptoAtoms.shift(-SATOSHI_SHIFT).toFixed(8) + + return checkCryptoCode(cryptoCode) + .then(() => fetch('sendfrom', [address, bitcoins, confirmations])) + .catch(err => { + if (err.code === -6) throw new E.InsufficientFundsError() + throw err + }) +} + +function newAddress (account, info) { + return checkCryptoCode(info.cryptoCode) + .then(() => fetch('getnewaddress')) +} + +function addressBalance (address, confs) { + return fetch('getreceivedbyaddress', [address, confs]) + .then(r => BN(r).shift(SATOSHI_SHIFT).round()) +} + +function confirmedBalance (address, cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => addressBalance(address, 1)) +} + +function pendingBalance (address, cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => addressBalance(address, 0)) +} + +function getStatus (account, toAddress, requested, cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => confirmedBalance(toAddress, cryptoCode)) + .then(confirmed => { + if (confirmed.gte(requested)) return {status: 'confirmed'} + + return pendingBalance(toAddress, cryptoCode) + .then(pending => { + if (pending.gte(requested)) return {status: 'authorized'} + if (pending.gt(0)) return {status: 'insufficientFunds'} + return {status: 'notSeen'} + }) + }) +} + +function newFunding (account, cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => { + const promises = [ + accountBalance(account, cryptoCode, 0), + accountBalance(account, cryptoCode, 1), + newAddress(account, {cryptoCode}) + ] + + return Promise.all(promises) + }) + .then(([fundingPendingBalance, fundingConfirmedBalance, fundingAddress]) => ({ + fundingPendingBalance, + fundingConfirmedBalance, + fundingAddress + })) +} + +module.exports = { + balance, + sendCoins, + newAddress, + getStatus, + newFunding +} diff --git a/public/elm.js b/public/elm.js index bbabb758..8f83bedc 100644 --- a/public/elm.js +++ b/public/elm.js @@ -32592,6 +32592,10 @@ var _user$project$Transaction$multiplier = function (code) { return 1.0e18; case 'ZEC': return 1.0e8; + case 'DASH': + return 1.0e8; + case 'LTC': + return 1.0e8; default: return 1.0; }