From 3f9c139f83069e990eb550d7dbb19cade57584df Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sat, 26 May 2018 14:09:47 +0300 Subject: [PATCH] Support Strike for Lightning Network --- dev/strike.js | 20 ++++++++++ lamassu-schema.json | 13 +++++++ lib/admin/config.js | 4 +- lib/layer2.js | 40 ++++++++++++++++++++ lib/plugin-helper.js | 3 +- lib/plugins/layer2/strike/strike.js | 58 +++++++++++++++++++++++++++++ lib/poller.js | 6 +-- lib/routes.js | 2 +- lib/wallet.js | 11 ++++++ public/elm.js | 15 ++++++-- schemas/strike.json | 14 +++++++ 11 files changed, 175 insertions(+), 11 deletions(-) create mode 100644 dev/strike.js create mode 100644 lib/layer2.js create mode 100644 lib/plugins/layer2/strike/strike.js create mode 100644 schemas/strike.json diff --git a/dev/strike.js b/dev/strike.js new file mode 100644 index 00000000..7d099697 --- /dev/null +++ b/dev/strike.js @@ -0,0 +1,20 @@ +const strike = require('../lib/plugins/wallet/strike/strike') +const BN = require('../lib/bn') + +const account = {token: 'xxx'} + +strike.newAddress(account, {cryptoCode: 'BTC', cryptoAtoms: BN(10000)}) + .then(r => { + console.log(r) + + const toAddress = r + const requested = null + const cryptoCode = 'BTC' + + setInterval(() => { + strike.getStatus(account, toAddress, requested, cryptoCode) + .then(console.log) + .catch(r => console.log(r.message)) + }, 2000) + }) + .catch(console.log) diff --git a/lamassu-schema.json b/lamassu-schema.json index f3a6f5a5..aca8f64c 100644 --- a/lamassu-schema.json +++ b/lamassu-schema.json @@ -100,6 +100,7 @@ "fields": [ "ticker", "wallet", + "layer2", "exchange", "zeroConf" ] @@ -499,6 +500,18 @@ } ] }, + { + "code": "layer2", + "displayBottom": "Layer 2", + "fieldType": "account", + "fieldClass": "layer2", + "fieldValidation": [ + { + "code": "required" + } + ], + "default": "no-layer2" + }, { "code": "exchange", "displayBottom": "Exchange", diff --git a/lib/admin/config.js b/lib/admin/config.js index c6e189bb..8ad3c999 100644 --- a/lib/admin/config.js +++ b/lib/admin/config.js @@ -180,7 +180,9 @@ function fetchData () { {code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']}, {code: 'mock-ticker', display: 'Mock ticker', class: 'ticker', cryptos: ALL_CRYPTOS}, {code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']}, - {code: 'lnd', display: 'Lightning Network', class: 'wallet', cryptos: ['BTC']}, + {code: 'no-layer2', display: 'No Layer 2', class: 'layer2', cryptos: ALL_CRYPTOS}, + {code: 'strike', display: 'Strike (LN)', class: 'layer2', cryptos: ['BTC']}, + {code: 'lnd', display: 'Lightning Network', class: 'layer2', 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']}, diff --git a/lib/layer2.js b/lib/layer2.js new file mode 100644 index 00000000..ccef42bc --- /dev/null +++ b/lib/layer2.js @@ -0,0 +1,40 @@ +const configManager = require('./config-manager') +const ph = require('./plugin-helper') + +function fetch (settings, cryptoCode) { + const plugin = configManager.cryptoScoped(cryptoCode, settings.config).layer2 + const layer2 = ph.load(ph.LAYER2, plugin) + const account = settings.accounts[plugin] + + return Promise.resolve({layer2, account}) +} + +function newAddress (settings, info) { + return fetch(settings, info.cryptoCode) + .then(r => r.layer2.newAddress(r.account, info)) +} + +function getStatus (settings, tx) { + return fetch(settings, tx.cryptoCode) + .then(r => r.layer2.getStatus(r.account, tx.toAddress, tx.cryptoAtoms, tx.cryptoCode)) +} + +function cryptoNetwork (settings, cryptoCode) { + const plugin = configManager.cryptoScoped(cryptoCode, settings.config).layer2 + const layer2 = ph.load(ph.LAYER2, plugin) + const account = settings.accounts[plugin] + + if (!layer2.cryptoNetwork) return Promise.resolve(false) + return layer2.cryptoNetwork(account, cryptoCode) +} + +function isLayer2Address (address) { + return address.split(':').length >= 2 +} + +module.exports = { + isLayer2Address, + newAddress, + getStatus, + cryptoNetwork +} diff --git a/lib/plugin-helper.js b/lib/plugin-helper.js index 15003260..103e5ff1 100644 --- a/lib/plugin-helper.js +++ b/lib/plugin-helper.js @@ -7,6 +7,7 @@ const pluginCodes = { TICKER: 'ticker', EXCHANGE: 'exchange', WALLET: 'wallet', + LAYER2: 'layer2', SMS: 'sms', EMAIL: 'email', ZERO_CONF: 'zero-conf' @@ -21,7 +22,7 @@ function load (type, pluginCode) { if (!pluginCode) throw new Error(`No plugin defined for ${type}`) - if (pluginCode.search(/[a-z0-9\-]/) === -1) { + if (pluginCode.search(/[a-z0-9-]/) === -1) { throw new Error(`Unallowed plugin name: ${pluginCode}`) } diff --git a/lib/plugins/layer2/strike/strike.js b/lib/plugins/layer2/strike/strike.js new file mode 100644 index 00000000..4ef18cb0 --- /dev/null +++ b/lib/plugins/layer2/strike/strike.js @@ -0,0 +1,58 @@ +const axios = require('axios') +const _ = require('lodash/fp') + +module.exports = { + newAddress, + getStatus, + cryptoNetwork +} + +function cryptoNetwork (account, cryptoCode) { + return Promise.resolve('test') +} + +function checkCryptoCode (cryptoCode) { + if (cryptoCode !== 'BTC') return Promise.reject(new Error('Unsupported crypto: ' + cryptoCode)) + return Promise.resolve() +} + +function getCharge (account, chargeId) { + return axios({ + method: 'get', + url: `https://api.strike.acinq.co/api/v1/charges/${chargeId}`, + auth: {username: account.token, password: ''} + }).then(_.get('data')) +} + +function createCharge (account, info) { + const data = { + amount: info.cryptoAtoms.toNumber(), + currency: 'btc', + description: 'Lamassu cryptomat cash-out' + } + + return axios({ + method: 'post', + url: 'https://api.strike.acinq.co/api/v1/charges', + auth: {username: account.token, password: ''}, + data + }).then(_.get('data')) +} + +function newAddress (account, info) { + return checkCryptoCode(info.cryptoCode) + .then(() => createCharge(account, info)) + .then(_.tap(console.log)) + .then(r => `strike:${r.id}:${r.payment_hash}:${r.payment_request}`) +} + +function getStatus (account, toAddress, requested, cryptoCode) { + return checkCryptoCode(cryptoCode) + .then(() => { + const parts = _.split(':', toAddress) + const chargeId = parts[1] + + return getCharge(account, chargeId) + .then(r => ({status: r.paid ? 'confirmed' : 'notSeen'})) + }) +} diff --git a/lib/poller.js b/lib/poller.js index f9893a1a..de7699c9 100644 --- a/lib/poller.js +++ b/lib/poller.js @@ -40,15 +40,11 @@ function updateAndLoadSanctions () { .then(() => logger.info('Sanctions database updated.')) } -const pp = require('./pp') function updateCoinAtmRadar () { const config = settings().config return pi().getRates() - .then(rates => { - pp('DEBUG200')(rates) - return coinAtmRadar.update({rates, config}) - }) + .then(rates => coinAtmRadar.update({rates, config})) } function start (__settings) { diff --git a/lib/routes.js b/lib/routes.js index fbb60302..619c2783 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -31,7 +31,7 @@ const reboots = {} const devMode = argv.dev || options.http function checkHasLightning (settings) { - return configManager.cryptoScoped('BTC', settings.config).wallet === 'lnd' + return configManager.cryptoScoped('BTC', settings.config).layer2 !== 'no-layer2' } function poll (req, res, next) { diff --git a/lib/wallet.js b/lib/wallet.js index 41205719..140a34b3 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -8,6 +8,7 @@ const fs = pify(require('fs')) const options = require('./options') const ph = require('./plugin-helper') +const layer2 = require('./layer2') const FETCH_INTERVAL = 5000 const INSUFFICIENT_FUNDS_CODE = 570 @@ -39,6 +40,12 @@ function fetchWallet (settings, cryptoCode) { }) } +function isLayer2 (tx) { + return _.isNil(tx.isLightning) + ? layer2.isLayer2Address(tx.toAddress) + : tx.isLightning +} + const lastBalance = {} function _balance (settings, cryptoCode) { @@ -74,6 +81,8 @@ function sendCoins (settings, toAddress, cryptoAtoms, cryptoCode) { } function newAddress (settings, info) { + if (isLayer2(info)) return layer2.newAddress(settings, info) + return fetchWallet(settings, info.cryptoCode) .then(r => r.wallet.newAddress(r.account, info)) } @@ -89,6 +98,8 @@ function newFunding (settings, cryptoCode, address) { } function getWalletStatus (settings, tx) { + if (isLayer2(tx)) return layer2.getStatus(settings, tx) + return fetchWallet(settings, tx.cryptoCode) .then(r => r.wallet.getStatus(r.account, tx.toAddress, tx.cryptoAtoms, tx.cryptoCode)) } diff --git a/public/elm.js b/public/elm.js index 8bfa22a8..ac096579 100644 --- a/public/elm.js +++ b/public/elm.js @@ -37222,11 +37222,20 @@ var _user$project$NavBar$view = F2( ctor: '::', _0: { ctor: '_Tuple3', - _0: 'Twilio', - _1: _user$project$CoreTypes$AccountRoute('twilio'), + _0: 'Strike', + _1: _user$project$CoreTypes$AccountRoute('strike'), _2: true }, - _1: {ctor: '[]'} + _1: { + ctor: '::', + _0: { + ctor: '_Tuple3', + _0: 'Twilio', + _1: _user$project$CoreTypes$AccountRoute('twilio'), + _2: true + }, + _1: {ctor: '[]'} + } } } } diff --git a/schemas/strike.json b/schemas/strike.json new file mode 100644 index 00000000..4052fe1f --- /dev/null +++ b/schemas/strike.json @@ -0,0 +1,14 @@ +{ + "code": "strike", + "display": "Strike", + "fields": [ + { + "code": "token", + "display": "API Token", + "fieldType": "password", + "secret": true, + "required": true, + "value": "" + } + ] +}