Merge pull request #1763 from RafaelTaranto/backport/market-currency-selector
LAM-551 backport: market currency selector
This commit is contained in:
commit
b2a28d4fa9
28 changed files with 725 additions and 350 deletions
|
|
@ -1,6 +1,10 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
const { ALL_CRYPTOS } = require('@lamassu/coins')
|
||||||
|
|
||||||
const configManager = require('./new-config-manager')
|
const configManager = require('./new-config-manager')
|
||||||
const ccxt = require('./plugins/exchange/ccxt')
|
const ccxt = require('./plugins/exchange/ccxt')
|
||||||
const mockExchange = require('./plugins/exchange/mock-exchange')
|
const mockExchange = require('./plugins/exchange/mock-exchange')
|
||||||
|
const accounts = require('./new-admin/config/accounts')
|
||||||
|
|
||||||
function lookupExchange (settings, cryptoCode) {
|
function lookupExchange (settings, cryptoCode) {
|
||||||
const exchange = configManager.getWalletSettings(cryptoCode, settings.config).exchange
|
const exchange = configManager.getWalletSettings(cryptoCode, settings.config).exchange
|
||||||
|
|
@ -45,8 +49,26 @@ function active (settings, cryptoCode) {
|
||||||
return !!lookupExchange(settings, cryptoCode)
|
return !!lookupExchange(settings, cryptoCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMarkets () {
|
||||||
|
const filterExchanges = _.filter(it => it.class === 'exchange')
|
||||||
|
const availableExchanges = _.map(it => it.code, filterExchanges(accounts.ACCOUNT_LIST))
|
||||||
|
|
||||||
|
return _.reduce(
|
||||||
|
(acc, value) =>
|
||||||
|
Promise.all([acc, ccxt.getMarkets(value, ALL_CRYPTOS)])
|
||||||
|
.then(([a, markets]) => Promise.resolve({
|
||||||
|
...a,
|
||||||
|
[value]: markets
|
||||||
|
})),
|
||||||
|
Promise.resolve({}),
|
||||||
|
availableExchanges
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
fetchExchange,
|
||||||
buy,
|
buy,
|
||||||
sell,
|
sell,
|
||||||
active
|
active,
|
||||||
|
getMarkets
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ const funding = require('./funding.resolver')
|
||||||
const log = require('./log.resolver')
|
const log = require('./log.resolver')
|
||||||
const loyalty = require('./loyalty.resolver')
|
const loyalty = require('./loyalty.resolver')
|
||||||
const machine = require('./machine.resolver')
|
const machine = require('./machine.resolver')
|
||||||
|
const market = require('./market.resolver')
|
||||||
const notification = require('./notification.resolver')
|
const notification = require('./notification.resolver')
|
||||||
const pairing = require('./pairing.resolver')
|
const pairing = require('./pairing.resolver')
|
||||||
const rates = require('./rates.resolver')
|
const rates = require('./rates.resolver')
|
||||||
|
|
@ -35,6 +36,7 @@ const resolvers = [
|
||||||
log,
|
log,
|
||||||
loyalty,
|
loyalty,
|
||||||
machine,
|
machine,
|
||||||
|
market,
|
||||||
notification,
|
notification,
|
||||||
pairing,
|
pairing,
|
||||||
rates,
|
rates,
|
||||||
|
|
|
||||||
9
lib/new-admin/graphql/resolvers/market.resolver.js
Normal file
9
lib/new-admin/graphql/resolvers/market.resolver.js
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
const exchange = require('../../../exchange')
|
||||||
|
|
||||||
|
const resolvers = {
|
||||||
|
Query: {
|
||||||
|
getMarkets: () => exchange.getMarkets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = resolvers
|
||||||
|
|
@ -11,6 +11,7 @@ const funding = require('./funding.type')
|
||||||
const log = require('./log.type')
|
const log = require('./log.type')
|
||||||
const loyalty = require('./loyalty.type')
|
const loyalty = require('./loyalty.type')
|
||||||
const machine = require('./machine.type')
|
const machine = require('./machine.type')
|
||||||
|
const market = require('./market.type')
|
||||||
const notification = require('./notification.type')
|
const notification = require('./notification.type')
|
||||||
const pairing = require('./pairing.type')
|
const pairing = require('./pairing.type')
|
||||||
const rates = require('./rates.type')
|
const rates = require('./rates.type')
|
||||||
|
|
@ -35,6 +36,7 @@ const types = [
|
||||||
log,
|
log,
|
||||||
loyalty,
|
loyalty,
|
||||||
machine,
|
machine,
|
||||||
|
market,
|
||||||
notification,
|
notification,
|
||||||
pairing,
|
pairing,
|
||||||
rates,
|
rates,
|
||||||
|
|
|
||||||
9
lib/new-admin/graphql/types/market.type.js
Normal file
9
lib/new-admin/graphql/types/market.type.js
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
const { gql } = require('apollo-server-express')
|
||||||
|
|
||||||
|
const typeDef = gql`
|
||||||
|
type Query {
|
||||||
|
getMarkets: JSONObject @auth
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
module.exports = typeDef
|
||||||
|
|
@ -475,7 +475,9 @@ function plugins (settings, deviceId) {
|
||||||
|
|
||||||
function buyAndSell (rec, doBuy, tx) {
|
function buyAndSell (rec, doBuy, tx) {
|
||||||
const cryptoCode = rec.cryptoCode
|
const cryptoCode = rec.cryptoCode
|
||||||
const fiatCode = rec.fiatCode
|
return exchange.fetchExchange(settings, cryptoCode)
|
||||||
|
.then(_exchange => {
|
||||||
|
const fiatCode = _exchange.account.currencyMarket
|
||||||
const cryptoAtoms = doBuy ? commissionMath.fiatToCrypto(tx, rec, deviceId, settings.config) : rec.cryptoAtoms.negated()
|
const cryptoAtoms = doBuy ? commissionMath.fiatToCrypto(tx, rec, deviceId, settings.config) : rec.cryptoAtoms.negated()
|
||||||
|
|
||||||
const market = [fiatCode, cryptoCode].join('')
|
const market = [fiatCode, cryptoCode].join('')
|
||||||
|
|
@ -494,6 +496,7 @@ function plugins (settings, deviceId) {
|
||||||
cryptoCode,
|
cryptoCode,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function consolidateTrades (cryptoCode, fiatCode) {
|
function consolidateTrades (cryptoCode, fiatCode) {
|
||||||
|
|
@ -550,19 +553,22 @@ function plugins (settings, deviceId) {
|
||||||
const deviceIds = devices.map(device => device.deviceId)
|
const deviceIds = devices.map(device => device.deviceId)
|
||||||
const lists = deviceIds.map(deviceId => {
|
const lists = deviceIds.map(deviceId => {
|
||||||
const localeConfig = configManager.getLocale(deviceId, settings.config)
|
const localeConfig = configManager.getLocale(deviceId, settings.config)
|
||||||
const fiatCode = localeConfig.fiatCurrency
|
|
||||||
const cryptoCodes = localeConfig.cryptoCurrencies
|
const cryptoCodes = localeConfig.cryptoCurrencies
|
||||||
|
|
||||||
return cryptoCodes.map(cryptoCode => ({
|
return Promise.all(cryptoCodes.map(cryptoCode => {
|
||||||
fiatCode,
|
return exchange.fetchExchange(settings, cryptoCode)
|
||||||
|
.then(exchange => ({
|
||||||
|
fiatCode: exchange.account.currencyMarket,
|
||||||
cryptoCode
|
cryptoCode
|
||||||
}))
|
}))
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
const tradesPromises = _.uniq(_.flatten(lists))
|
return Promise.all(lists)
|
||||||
.map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode))
|
})
|
||||||
|
.then(lists => {
|
||||||
return Promise.all(tradesPromises)
|
return Promise.all(_.uniq(_.flatten(lists))
|
||||||
|
.map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode)))
|
||||||
})
|
})
|
||||||
.catch(logger.error)
|
.catch(logger.error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,8 @@ function buildMarket (fiatCode, cryptoCode, serviceName) {
|
||||||
if (!_.includes(cryptoCode, ALL[serviceName].CRYPTO)) {
|
if (!_.includes(cryptoCode, ALL[serviceName].CRYPTO)) {
|
||||||
throw new Error('Unsupported crypto: ' + cryptoCode)
|
throw new Error('Unsupported crypto: ' + cryptoCode)
|
||||||
}
|
}
|
||||||
const fiatSupported = ALL[serviceName].FIAT
|
|
||||||
if (fiatSupported !== 'ALL_CURRENCIES' && !_.includes(fiatCode, fiatSupported)) {
|
if (_.isNil(fiatCode)) throw new Error('Market pair building failed: Missing fiat code')
|
||||||
logger.info('Building a market for an unsupported fiat. Defaulting to EUR market')
|
|
||||||
return cryptoCode + '/' + 'EUR'
|
|
||||||
}
|
|
||||||
return cryptoCode + '/' + fiatCode
|
return cryptoCode + '/' + fiatCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||||
const { BTC, BCH, XMR, ETH, LTC, ZEC, LN } = COINS
|
const { BTC, BCH, XMR, ETH, LTC, ZEC, LN } = COINS
|
||||||
const CRYPTO = [BTC, ETH, LTC, ZEC, BCH, XMR, LN]
|
const CRYPTO = [BTC, ETH, LTC, ZEC, BCH, XMR, LN]
|
||||||
const FIAT = ['USD', 'EUR']
|
const FIAT = ['USD', 'EUR']
|
||||||
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
|
const DEFAULT_FIAT_MARKET = 'EUR'
|
||||||
|
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey', 'currencyMarket']
|
||||||
|
|
||||||
const loadConfig = (account) => {
|
const loadConfig = (account) => {
|
||||||
const mapper = {
|
const mapper = {
|
||||||
|
|
@ -17,4 +18,4 @@ const loadConfig = (account) => {
|
||||||
return { ...mapped, timeout: 3000 }
|
return { ...mapped, timeout: 3000 }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
|
module.exports = { loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||||
const { BTC, BCH, DASH, ETH, LTC, ZEC, USDT, USDT_TRON, LN } = COINS
|
const { BTC, BCH, DASH, ETH, LTC, ZEC, USDT, USDT_TRON, LN } = COINS
|
||||||
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, USDT, USDT_TRON, LN]
|
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, USDT, USDT_TRON, LN]
|
||||||
const FIAT = ['USD']
|
const FIAT = ['USD']
|
||||||
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
|
const DEFAULT_FIAT_MARKET = 'USD'
|
||||||
|
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey', 'currencyMarket']
|
||||||
|
|
||||||
const loadConfig = (account) => {
|
const loadConfig = (account) => {
|
||||||
const mapper = {
|
const mapper = {
|
||||||
|
|
@ -17,4 +18,4 @@ const loadConfig = (account) => {
|
||||||
return { ...mapped, timeout: 3000 }
|
return { ...mapped, timeout: 3000 }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
|
module.exports = { loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||||
const { BTC, ETH, LTC, BCH, USDT, LN } = COINS
|
const { BTC, ETH, LTC, BCH, USDT, LN } = COINS
|
||||||
const CRYPTO = [BTC, ETH, LTC, BCH, USDT, LN]
|
const CRYPTO = [BTC, ETH, LTC, BCH, USDT, LN]
|
||||||
const FIAT = ['USD', 'EUR']
|
const FIAT = ['USD', 'EUR']
|
||||||
|
const DEFAULT_FIAT_MARKET = 'EUR'
|
||||||
const AMOUNT_PRECISION = 8
|
const AMOUNT_PRECISION = 8
|
||||||
const REQUIRED_CONFIG_FIELDS = ['key', 'secret']
|
const REQUIRED_CONFIG_FIELDS = ['key', 'secret']
|
||||||
|
|
||||||
|
|
@ -18,4 +19,4 @@ const loadConfig = (account) => {
|
||||||
return { ...mapped, timeout: 3000 }
|
return { ...mapped, timeout: 3000 }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, DEFAULT_FIAT_MARKET, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||||
const { BTC, ETH, LTC, BCH, USDT, LN } = COINS
|
const { BTC, ETH, LTC, BCH, USDT, LN } = COINS
|
||||||
const CRYPTO = [BTC, ETH, LTC, BCH, USDT, LN]
|
const CRYPTO = [BTC, ETH, LTC, BCH, USDT, LN]
|
||||||
const FIAT = ['USD', 'EUR']
|
const FIAT = ['USD', 'EUR']
|
||||||
|
const DEFAULT_FIAT_MARKET = 'EUR'
|
||||||
const AMOUNT_PRECISION = 8
|
const AMOUNT_PRECISION = 8
|
||||||
const REQUIRED_CONFIG_FIELDS = ['key', 'secret', 'clientId']
|
const REQUIRED_CONFIG_FIELDS = ['key', 'secret', 'clientId', 'currencyMarket']
|
||||||
|
|
||||||
const loadConfig = (account) => {
|
const loadConfig = (account) => {
|
||||||
const mapper = {
|
const mapper = {
|
||||||
|
|
@ -19,4 +20,4 @@ const loadConfig = (account) => {
|
||||||
return { ...mapped, timeout: 3000 }
|
return { ...mapped, timeout: 3000 }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
module.exports = { loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
const { utils: coinUtils } = require('@lamassu/coins')
|
const { utils: coinUtils } = require('@lamassu/coins')
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const ccxt = require('ccxt')
|
const ccxt = require('ccxt')
|
||||||
|
const mem = require('mem')
|
||||||
|
|
||||||
const { buildMarket, ALL, isConfigValid } = require('../common/ccxt')
|
const { buildMarket, ALL, isConfigValid } = require('../common/ccxt')
|
||||||
const { ORDER_TYPES } = require('./consts')
|
const { ORDER_TYPES } = require('./consts')
|
||||||
|
const logger = require('../../logger')
|
||||||
|
const { currencies } = require('../../new-admin/config')
|
||||||
|
const T = require('../../time')
|
||||||
|
|
||||||
const DEFAULT_PRICE_PRECISION = 2
|
const DEFAULT_PRICE_PRECISION = 2
|
||||||
const DEFAULT_AMOUNT_PRECISION = 8
|
const DEFAULT_AMOUNT_PRECISION = 8
|
||||||
|
|
@ -18,7 +22,8 @@ function trade (side, account, tradeEntry, exchangeName) {
|
||||||
const { USER_REF, loadOptions, loadConfig = _.noop, REQUIRED_CONFIG_FIELDS, ORDER_TYPE, AMOUNT_PRECISION } = exchangeConfig
|
const { USER_REF, loadOptions, loadConfig = _.noop, REQUIRED_CONFIG_FIELDS, ORDER_TYPE, AMOUNT_PRECISION } = exchangeConfig
|
||||||
if (!isConfigValid(account, REQUIRED_CONFIG_FIELDS)) throw Error('Invalid config')
|
if (!isConfigValid(account, REQUIRED_CONFIG_FIELDS)) throw Error('Invalid config')
|
||||||
|
|
||||||
const symbol = buildMarket(fiatCode, cryptoCode, exchangeName)
|
const selectedFiatMarket = account.currencyMarket
|
||||||
|
const symbol = buildMarket(selectedFiatMarket, cryptoCode, exchangeName)
|
||||||
const precision = _.defaultTo(DEFAULT_AMOUNT_PRECISION, AMOUNT_PRECISION)
|
const precision = _.defaultTo(DEFAULT_AMOUNT_PRECISION, AMOUNT_PRECISION)
|
||||||
const amount = coinUtils.toUnit(cryptoAtoms, cryptoCode).toFixed(precision)
|
const amount = coinUtils.toUnit(cryptoAtoms, cryptoCode).toFixed(precision)
|
||||||
const accountOptions = _.isFunction(loadOptions) ? loadOptions(account) : {}
|
const accountOptions = _.isFunction(loadOptions) ? loadOptions(account) : {}
|
||||||
|
|
@ -50,4 +55,36 @@ function calculatePrice (side, amount, orderBook) {
|
||||||
throw new Error('Insufficient market depth')
|
throw new Error('Insufficient market depth')
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { trade }
|
function _getMarkets (exchangeName, availableCryptos) {
|
||||||
|
try {
|
||||||
|
const exchange = new ccxt[exchangeName]()
|
||||||
|
const cryptosToQuoteAgainst = ['USDT']
|
||||||
|
const currencyCodes = _.concat(_.map(it => it.code, currencies), cryptosToQuoteAgainst)
|
||||||
|
|
||||||
|
return exchange.fetchMarkets()
|
||||||
|
.then(_.filter(it => (it.type === 'spot' || it.spot)))
|
||||||
|
.then(res =>
|
||||||
|
_.reduce((acc, value) => {
|
||||||
|
if (_.includes(value.base, availableCryptos) && _.includes(value.quote, currencyCodes)) {
|
||||||
|
if (value.quote === value.base) return acc
|
||||||
|
|
||||||
|
if (_.isNil(acc[value.quote])) {
|
||||||
|
return { ...acc, [value.quote]: [value.base] }
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[value.quote].push(value.base)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {}, res)
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug(`No CCXT exchange found for ${exchangeName}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMarkets = mem(_getMarkets, {
|
||||||
|
maxAge: T.week,
|
||||||
|
cacheKey: (exchangeName, availableCryptos) => exchangeName
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = { trade, getMarkets }
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||||
const { BTC, BCH, DASH, ETH, LTC, USDT, TRX, USDT_TRON, LN } = COINS
|
const { BTC, BCH, DASH, ETH, LTC, USDT, TRX, USDT_TRON, LN } = COINS
|
||||||
const CRYPTO = [BTC, ETH, LTC, DASH, BCH, USDT, TRX, USDT_TRON, LN]
|
const CRYPTO = [BTC, ETH, LTC, DASH, BCH, USDT, TRX, USDT_TRON, LN]
|
||||||
const FIAT = ['USD', 'EUR']
|
const FIAT = ['USD', 'EUR']
|
||||||
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
|
const DEFAULT_FIAT_MARKET = 'EUR'
|
||||||
|
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey', 'currencyMarket']
|
||||||
|
|
||||||
const loadConfig = (account) => {
|
const loadConfig = (account) => {
|
||||||
const mapper = {
|
const mapper = {
|
||||||
|
|
@ -17,4 +18,4 @@ const loadConfig = (account) => {
|
||||||
return { ...mapped, timeout: 3000 }
|
return { ...mapped, timeout: 3000 }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
|
module.exports = { loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE }
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ const ORDER_TYPE = ORDER_TYPES.LIMIT
|
||||||
const { BTC, ETH, USDT, LN } = COINS
|
const { BTC, ETH, USDT, LN } = COINS
|
||||||
const CRYPTO = [BTC, ETH, USDT, LN]
|
const CRYPTO = [BTC, ETH, USDT, LN]
|
||||||
const FIAT = ['USD']
|
const FIAT = ['USD']
|
||||||
|
const DEFAULT_FIAT_MARKET = 'USD'
|
||||||
const AMOUNT_PRECISION = 4
|
const AMOUNT_PRECISION = 4
|
||||||
const REQUIRED_CONFIG_FIELDS = ['clientKey', 'clientSecret', 'userId', 'walletId']
|
const REQUIRED_CONFIG_FIELDS = ['clientKey', 'clientSecret', 'userId', 'walletId', 'currencyMarket']
|
||||||
|
|
||||||
const loadConfig = (account) => {
|
const loadConfig = (account) => {
|
||||||
const mapper = {
|
const mapper = {
|
||||||
|
|
@ -21,4 +22,4 @@ const loadConfig = (account) => {
|
||||||
}
|
}
|
||||||
const loadOptions = ({ walletId }) => ({ walletId })
|
const loadOptions = ({ walletId }) => ({ walletId })
|
||||||
|
|
||||||
module.exports = { loadOptions, loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
module.exports = { loadOptions, loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
|
||||||
const { BTC, BCH, DASH, ETH, LTC, ZEC, XMR, USDT, TRX, USDT_TRON, LN } = COINS
|
const { BTC, BCH, DASH, ETH, LTC, ZEC, XMR, USDT, TRX, USDT_TRON, LN } = COINS
|
||||||
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, XMR, USDT, TRX, USDT_TRON, LN]
|
const CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, XMR, USDT, TRX, USDT_TRON, LN]
|
||||||
const FIAT = ['USD', 'EUR']
|
const FIAT = ['USD', 'EUR']
|
||||||
|
const DEFAULT_FIAT_MARKET = 'EUR'
|
||||||
const AMOUNT_PRECISION = 6
|
const AMOUNT_PRECISION = 6
|
||||||
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
|
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey', 'currencyMarket']
|
||||||
const USER_REF = 'userref'
|
const USER_REF = 'userref'
|
||||||
|
|
||||||
const loadConfig = (account) => {
|
const loadConfig = (account) => {
|
||||||
|
|
@ -26,4 +27,4 @@ const loadConfig = (account) => {
|
||||||
|
|
||||||
const loadOptions = () => ({ expiretm: '+60' })
|
const loadOptions = () => ({ expiretm: '+60' })
|
||||||
|
|
||||||
module.exports = { USER_REF, loadOptions, loadConfig, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
module.exports = { USER_REF, loadOptions, loadConfig, DEFAULT_FIAT_MARKET, REQUIRED_CONFIG_FIELDS, CRYPTO, FIAT, ORDER_TYPE, AMOUNT_PRECISION }
|
||||||
|
|
|
||||||
30
migrations/1732874039534-market-currency.js
Normal file
30
migrations/1732874039534-market-currency.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
const { loadLatest, saveAccounts } = require('../lib/new-settings-loader')
|
||||||
|
const { ACCOUNT_LIST } = require('../lib/new-admin/config/accounts')
|
||||||
|
const { ALL } = require('../lib/plugins/common/ccxt')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
return loadLatest()
|
||||||
|
.then(({ accounts }) => {
|
||||||
|
const allExchanges = _.map(it => it.code)(_.filter(it => it.class === 'exchange', ACCOUNT_LIST))
|
||||||
|
const configuredExchanges = _.intersection(allExchanges, _.keys(accounts))
|
||||||
|
|
||||||
|
const newAccounts = _.reduce(
|
||||||
|
(acc, value) => {
|
||||||
|
if (!_.isNil(accounts[value].currencyMarket)) return acc
|
||||||
|
if (_.includes('EUR', ALL[value].FIAT)) return { ...acc, [value]: { currencyMarket: 'EUR' } }
|
||||||
|
return { ...acc, [value]: { currencyMarket: ALL[value].DEFAULT_FIAT_CURRENCY } }
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
configuredExchanges
|
||||||
|
)
|
||||||
|
|
||||||
|
return saveAccounts(newAccounts)
|
||||||
|
})
|
||||||
|
.then(next)
|
||||||
|
.catch(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
|
import { Box } from '@material-ui/core'
|
||||||
import MAutocomplete from '@material-ui/lab/Autocomplete'
|
import MAutocomplete from '@material-ui/lab/Autocomplete'
|
||||||
import sort from 'match-sorter'
|
import sort from 'match-sorter'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
|
import { HoverableTooltip } from 'src/components/Tooltip'
|
||||||
|
import { P } from 'src/components/typography'
|
||||||
|
import { errorColor, orangeYellow, spring4 } from 'src/styling/variables'
|
||||||
|
|
||||||
import TextInput from './TextInput'
|
import TextInput from './TextInput'
|
||||||
|
|
||||||
const Autocomplete = ({
|
const Autocomplete = ({
|
||||||
|
|
@ -95,6 +100,39 @@ const Autocomplete = ({
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
renderOption={props => {
|
||||||
|
if (!props.warning && !props.warningMessage)
|
||||||
|
return R.path([labelProp])(props)
|
||||||
|
|
||||||
|
const warningColors = {
|
||||||
|
clean: spring4,
|
||||||
|
partial: orangeYellow,
|
||||||
|
important: errorColor
|
||||||
|
}
|
||||||
|
|
||||||
|
const hoverableElement = (
|
||||||
|
<Box
|
||||||
|
width={18}
|
||||||
|
height={18}
|
||||||
|
borderRadius={6}
|
||||||
|
bgcolor={warningColors[props.warning]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
width="100%"
|
||||||
|
display="flex"
|
||||||
|
flexDirection="row"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center">
|
||||||
|
<Box>{R.path([labelProp])(props)}</Box>
|
||||||
|
<HoverableTooltip parentElements={hoverableElement} width={250}>
|
||||||
|
<P>{props.warningMessage}</P>
|
||||||
|
</HoverableTooltip>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import SingleRowTable from 'src/components/single-row-table/SingleRowTable'
|
||||||
import { formatLong } from 'src/utils/string'
|
import { formatLong } from 'src/utils/string'
|
||||||
|
|
||||||
import FormRenderer from './FormRenderer'
|
import FormRenderer from './FormRenderer'
|
||||||
import schemas from './schemas'
|
import _schemas from './schemas'
|
||||||
|
|
||||||
const GET_INFO = gql`
|
const GET_INFO = gql`
|
||||||
query getData {
|
query getData {
|
||||||
|
|
@ -21,6 +21,12 @@ const GET_INFO = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const GET_MARKETS = gql`
|
||||||
|
query getMarkets {
|
||||||
|
getMarkets
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
const SAVE_ACCOUNT = gql`
|
const SAVE_ACCOUNT = gql`
|
||||||
mutation Save($accounts: JSONObject) {
|
mutation Save($accounts: JSONObject) {
|
||||||
saveAccounts(accounts: $accounts)
|
saveAccounts(accounts: $accounts)
|
||||||
|
|
@ -40,12 +46,17 @@ const useStyles = makeStyles(styles)
|
||||||
const Services = () => {
|
const Services = () => {
|
||||||
const [editingSchema, setEditingSchema] = useState(null)
|
const [editingSchema, setEditingSchema] = useState(null)
|
||||||
|
|
||||||
const { data } = useQuery(GET_INFO)
|
const { data, loading: configLoading } = useQuery(GET_INFO)
|
||||||
|
const { data: marketsData, loading: marketsLoading } = useQuery(GET_MARKETS)
|
||||||
const [saveAccount] = useMutation(SAVE_ACCOUNT, {
|
const [saveAccount] = useMutation(SAVE_ACCOUNT, {
|
||||||
onCompleted: () => setEditingSchema(null),
|
onCompleted: () => setEditingSchema(null),
|
||||||
refetchQueries: ['getData']
|
refetchQueries: ['getData']
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const markets = marketsData?.getMarkets
|
||||||
|
|
||||||
|
const schemas = _schemas(markets)
|
||||||
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const accounts = data?.accounts ?? {}
|
const accounts = data?.accounts ?? {}
|
||||||
|
|
@ -101,7 +112,10 @@ const Services = () => {
|
||||||
const getValidationSchema = ({ code, getValidationSchema }) =>
|
const getValidationSchema = ({ code, getValidationSchema }) =>
|
||||||
getValidationSchema(accounts[code])
|
getValidationSchema(accounts[code])
|
||||||
|
|
||||||
|
const loading = marketsLoading || configLoading
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
!loading && (
|
||||||
<div className={classes.wrapper}>
|
<div className={classes.wrapper}>
|
||||||
<TitleSection title="Third-Party services" />
|
<TitleSection title="Third-Party services" />
|
||||||
<Grid container spacing={4}>
|
<Grid container spacing={4}>
|
||||||
|
|
@ -136,6 +150,7 @@ const Services = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Services
|
export default Services
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
import {
|
||||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
SecretInput,
|
||||||
|
TextInput,
|
||||||
|
Autocomplete
|
||||||
|
} from 'src/components/inputs/formik'
|
||||||
|
|
||||||
import { secretTest } from './helper'
|
import { secretTest, buildCurrencyOptions } from './helper'
|
||||||
|
|
||||||
export default {
|
const schema = markets => {
|
||||||
|
return {
|
||||||
code: 'binance',
|
code: 'binance',
|
||||||
name: 'Binance',
|
name: 'Binance',
|
||||||
title: 'Binance (Exchange)',
|
title: 'Binance (Exchange)',
|
||||||
|
|
@ -13,14 +17,25 @@ export default {
|
||||||
{
|
{
|
||||||
code: 'apiKey',
|
code: 'apiKey',
|
||||||
display: 'API key',
|
display: 'API key',
|
||||||
component: TextInputFormik,
|
component: TextInput,
|
||||||
face: true,
|
face: true,
|
||||||
long: true
|
long: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'privateKey',
|
code: 'privateKey',
|
||||||
display: 'Private key',
|
display: 'Private key',
|
||||||
component: SecretInputFormik
|
component: SecretInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'currencyMarket',
|
||||||
|
display: 'Currency market',
|
||||||
|
component: Autocomplete,
|
||||||
|
inputProps: {
|
||||||
|
options: buildCurrencyOptions(markets),
|
||||||
|
labelProp: 'display',
|
||||||
|
valueProp: 'code'
|
||||||
|
},
|
||||||
|
face: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
getValidationSchema: account => {
|
getValidationSchema: account => {
|
||||||
|
|
@ -30,7 +45,13 @@ export default {
|
||||||
.required('The API key is required'),
|
.required('The API key is required'),
|
||||||
privateKey: Yup.string('The private key must be a string')
|
privateKey: Yup.string('The private key must be a string')
|
||||||
.max(100, 'The private key is too long')
|
.max(100, 'The private key is too long')
|
||||||
.test(secretTest(account?.privateKey, 'private key'))
|
.test(secretTest(account?.privateKey, 'private key')),
|
||||||
|
currencyMarket: Yup.string(
|
||||||
|
'The currency market must be a string'
|
||||||
|
).required('The currency market is required')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default schema
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
import {
|
||||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
SecretInput,
|
||||||
|
TextInput,
|
||||||
|
Autocomplete
|
||||||
|
} from 'src/components/inputs/formik'
|
||||||
|
|
||||||
import { secretTest } from './helper'
|
import { secretTest, buildCurrencyOptions } from './helper'
|
||||||
|
|
||||||
export default {
|
const schema = markets => {
|
||||||
|
return {
|
||||||
code: 'binanceus',
|
code: 'binanceus',
|
||||||
name: 'Binance.us',
|
name: 'Binance.us',
|
||||||
title: 'Binance.us (Exchange)',
|
title: 'Binance.us (Exchange)',
|
||||||
|
|
@ -13,14 +17,25 @@ export default {
|
||||||
{
|
{
|
||||||
code: 'apiKey',
|
code: 'apiKey',
|
||||||
display: 'API key',
|
display: 'API key',
|
||||||
component: TextInputFormik,
|
component: TextInput,
|
||||||
face: true,
|
face: true,
|
||||||
long: true
|
long: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'privateKey',
|
code: 'privateKey',
|
||||||
display: 'Private key',
|
display: 'Private key',
|
||||||
component: SecretInputFormik
|
component: SecretInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'currencyMarket',
|
||||||
|
display: 'Currency market',
|
||||||
|
component: Autocomplete,
|
||||||
|
inputProps: {
|
||||||
|
options: buildCurrencyOptions(markets),
|
||||||
|
labelProp: 'display',
|
||||||
|
valueProp: 'code'
|
||||||
|
},
|
||||||
|
face: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
getValidationSchema: account => {
|
getValidationSchema: account => {
|
||||||
|
|
@ -30,7 +45,13 @@ export default {
|
||||||
.required('The API key is required'),
|
.required('The API key is required'),
|
||||||
privateKey: Yup.string('The private key must be a string')
|
privateKey: Yup.string('The private key must be a string')
|
||||||
.max(100, 'The private key is too long')
|
.max(100, 'The private key is too long')
|
||||||
.test(secretTest(account?.privateKey, 'private key'))
|
.test(secretTest(account?.privateKey, 'private key')),
|
||||||
|
currencyMarket: Yup.string(
|
||||||
|
'The currency market must be a string'
|
||||||
|
).required('The currency market is required')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default schema
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,41 @@
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
import {
|
||||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
SecretInput,
|
||||||
|
TextInput,
|
||||||
|
Autocomplete
|
||||||
|
} from 'src/components/inputs/formik'
|
||||||
|
|
||||||
import { secretTest } from './helper'
|
import { secretTest, buildCurrencyOptions } from './helper'
|
||||||
|
|
||||||
export default {
|
const schema = markets => {
|
||||||
|
return {
|
||||||
code: 'bitfinex',
|
code: 'bitfinex',
|
||||||
name: 'Bitfinex',
|
name: 'Bitfinex',
|
||||||
title: 'Bitfinex (Exchange)',
|
title: 'Bitfinex (Exchange)',
|
||||||
elements: [
|
elements: [
|
||||||
{
|
{
|
||||||
code: 'key',
|
code: 'key',
|
||||||
display: 'API Key',
|
display: 'API key',
|
||||||
component: TextInputFormik,
|
component: TextInput,
|
||||||
face: true,
|
face: true,
|
||||||
long: true
|
long: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'secret',
|
code: 'secret',
|
||||||
display: 'API Secret',
|
display: 'API secret',
|
||||||
component: SecretInputFormik
|
component: SecretInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'currencyMarket',
|
||||||
|
display: 'Currency Market',
|
||||||
|
component: Autocomplete,
|
||||||
|
inputProps: {
|
||||||
|
options: buildCurrencyOptions(markets),
|
||||||
|
labelProp: 'display',
|
||||||
|
valueProp: 'code'
|
||||||
|
},
|
||||||
|
face: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
getValidationSchema: account => {
|
getValidationSchema: account => {
|
||||||
|
|
@ -30,7 +45,13 @@ export default {
|
||||||
.required('The API key is required'),
|
.required('The API key is required'),
|
||||||
secret: Yup.string('The API secret must be a string')
|
secret: Yup.string('The API secret must be a string')
|
||||||
.max(100, 'The API secret is too long')
|
.max(100, 'The API secret is too long')
|
||||||
.test(secretTest(account?.secret, 'API secret'))
|
.test(secretTest(account?.secret, 'API secret')),
|
||||||
|
currencyMarket: Yup.string(
|
||||||
|
'The currency market must be a string'
|
||||||
|
).required('The currency market is required')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default schema
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
import {
|
||||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
SecretInput,
|
||||||
|
TextInput,
|
||||||
|
Autocomplete
|
||||||
|
} from 'src/components/inputs/formik'
|
||||||
|
|
||||||
import { secretTest } from './helper'
|
import { secretTest, buildCurrencyOptions } from './helper'
|
||||||
|
|
||||||
export default {
|
const schema = markets => {
|
||||||
|
return {
|
||||||
code: 'bitstamp',
|
code: 'bitstamp',
|
||||||
name: 'Bitstamp',
|
name: 'Bitstamp',
|
||||||
title: 'Bitstamp (Exchange)',
|
title: 'Bitstamp (Exchange)',
|
||||||
|
|
@ -13,21 +17,32 @@ export default {
|
||||||
{
|
{
|
||||||
code: 'clientId',
|
code: 'clientId',
|
||||||
display: 'Client ID',
|
display: 'Client ID',
|
||||||
component: TextInputFormik,
|
component: TextInput,
|
||||||
face: true,
|
face: true,
|
||||||
long: true
|
long: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'key',
|
code: 'key',
|
||||||
display: 'API key',
|
display: 'API key',
|
||||||
component: TextInputFormik,
|
component: TextInput,
|
||||||
face: true,
|
face: true,
|
||||||
long: true
|
long: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'secret',
|
code: 'secret',
|
||||||
display: 'API secret',
|
display: 'API secret',
|
||||||
component: SecretInputFormik
|
component: SecretInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'currencyMarket',
|
||||||
|
display: 'Currency market',
|
||||||
|
component: Autocomplete,
|
||||||
|
inputProps: {
|
||||||
|
options: buildCurrencyOptions(markets),
|
||||||
|
labelProp: 'display',
|
||||||
|
valueProp: 'code'
|
||||||
|
},
|
||||||
|
face: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
getValidationSchema: account => {
|
getValidationSchema: account => {
|
||||||
|
|
@ -40,7 +55,13 @@ export default {
|
||||||
.required('The API key is required'),
|
.required('The API key is required'),
|
||||||
secret: Yup.string('The API secret must be a string')
|
secret: Yup.string('The API secret must be a string')
|
||||||
.max(100, 'The API secret is too long')
|
.max(100, 'The API secret is too long')
|
||||||
.test(secretTest(account?.secret, 'API secret'))
|
.test(secretTest(account?.secret, 'API secret')),
|
||||||
|
currencyMarket: Yup.string(
|
||||||
|
'The currency market must be a string'
|
||||||
|
).required('The currency market is required')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default schema
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
import {
|
||||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
SecretInput,
|
||||||
|
TextInput,
|
||||||
|
Autocomplete
|
||||||
|
} from 'src/components/inputs/formik'
|
||||||
|
|
||||||
import { secretTest } from './helper'
|
import { secretTest, buildCurrencyOptions } from './helper'
|
||||||
|
|
||||||
export default {
|
const schema = markets => {
|
||||||
|
return {
|
||||||
code: 'cex',
|
code: 'cex',
|
||||||
name: 'CEX.IO',
|
name: 'CEX.IO',
|
||||||
title: 'CEX.IO (Exchange)',
|
title: 'CEX.IO (Exchange)',
|
||||||
|
|
@ -13,21 +17,32 @@ export default {
|
||||||
{
|
{
|
||||||
code: 'apiKey',
|
code: 'apiKey',
|
||||||
display: 'API key',
|
display: 'API key',
|
||||||
component: TextInputFormik,
|
component: TextInput,
|
||||||
face: true,
|
face: true,
|
||||||
long: true
|
long: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'uid',
|
code: 'uid',
|
||||||
display: 'User ID',
|
display: 'User ID',
|
||||||
component: TextInputFormik,
|
component: TextInput,
|
||||||
face: true,
|
face: true,
|
||||||
long: true
|
long: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'privateKey',
|
code: 'privateKey',
|
||||||
display: 'Private key',
|
display: 'Private key',
|
||||||
component: SecretInputFormik
|
component: SecretInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'currencyMarket',
|
||||||
|
display: 'Currency Market',
|
||||||
|
component: Autocomplete,
|
||||||
|
inputProps: {
|
||||||
|
options: buildCurrencyOptions(markets),
|
||||||
|
labelProp: 'display',
|
||||||
|
valueProp: 'code'
|
||||||
|
},
|
||||||
|
face: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
getValidationSchema: account => {
|
getValidationSchema: account => {
|
||||||
|
|
@ -40,7 +55,13 @@ export default {
|
||||||
.required('The User ID is required'),
|
.required('The User ID is required'),
|
||||||
privateKey: Yup.string('The private key must be a string')
|
privateKey: Yup.string('The private key must be a string')
|
||||||
.max(100, 'The private key is too long')
|
.max(100, 'The private key is too long')
|
||||||
.test(secretTest(account?.privateKey, 'private key'))
|
.test(secretTest(account?.privateKey, 'private key')),
|
||||||
|
currencyMarket: Yup.string(
|
||||||
|
'The currency market must be a string'
|
||||||
|
).required('The currency market is required')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default schema
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
|
import { ALL_CRYPTOS } from '@lamassu/coins'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
|
|
||||||
|
const WARNING_LEVELS = {
|
||||||
|
CLEAN: 'clean',
|
||||||
|
PARTIAL: 'partial',
|
||||||
|
IMPORTANT: 'important'
|
||||||
|
}
|
||||||
|
|
||||||
const secretTest = (secret, message) => ({
|
const secretTest = (secret, message) => ({
|
||||||
name: 'secret-test',
|
name: 'secret-test',
|
||||||
message: message ? `The ${message} is invalid` : 'Invalid field',
|
message: message ? `The ${message} is invalid` : 'Invalid field',
|
||||||
|
|
@ -21,4 +28,35 @@ const leadingZerosTest = (value, context) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export { secretTest, leadingZerosTest }
|
const buildCurrencyOptions = markets => {
|
||||||
|
return R.map(it => {
|
||||||
|
const unavailableCryptos = R.difference(ALL_CRYPTOS, markets[it])
|
||||||
|
const unavailableCryptosFiltered = R.difference(unavailableCryptos, [it]) // As the markets can have stablecoins to trade against other crypto, filter them out, as there can't be pairs such as USDT/USDT
|
||||||
|
|
||||||
|
const unavailableMarketsStr =
|
||||||
|
R.length(unavailableCryptosFiltered) > 1
|
||||||
|
? `${R.join(
|
||||||
|
', ',
|
||||||
|
R.slice(0, -1, unavailableCryptosFiltered)
|
||||||
|
)} and ${R.last(unavailableCryptosFiltered)}`
|
||||||
|
: unavailableCryptosFiltered[0]
|
||||||
|
|
||||||
|
const warningLevel = R.isEmpty(unavailableCryptosFiltered)
|
||||||
|
? WARNING_LEVELS.CLEAN
|
||||||
|
: !R.isEmpty(unavailableCryptosFiltered) &&
|
||||||
|
R.length(unavailableCryptosFiltered) < R.length(ALL_CRYPTOS)
|
||||||
|
? WARNING_LEVELS.PARTIAL
|
||||||
|
: WARNING_LEVELS.IMPORTANT
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: R.toUpper(it),
|
||||||
|
display: R.toUpper(it),
|
||||||
|
warning: warningLevel,
|
||||||
|
warningMessage: !R.isEmpty(unavailableCryptosFiltered)
|
||||||
|
? `No market pairs available for ${unavailableMarketsStr}`
|
||||||
|
: `All market pairs are available`
|
||||||
|
}
|
||||||
|
}, R.keys(markets))
|
||||||
|
}
|
||||||
|
|
||||||
|
export { secretTest, leadingZerosTest, buildCurrencyOptions }
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import binance from './binance'
|
import _binance from './binance'
|
||||||
import binanceus from './binanceus'
|
import _binanceus from './binanceus'
|
||||||
import bitfinex from './bitfinex'
|
import _bitfinex from './bitfinex'
|
||||||
import bitgo from './bitgo'
|
import bitgo from './bitgo'
|
||||||
import bitstamp from './bitstamp'
|
import _bitstamp from './bitstamp'
|
||||||
import blockcypher from './blockcypher'
|
import blockcypher from './blockcypher'
|
||||||
import cex from './cex'
|
import _cex from './cex'
|
||||||
import elliptic from './elliptic'
|
import elliptic from './elliptic'
|
||||||
import galoy from './galoy'
|
import galoy from './galoy'
|
||||||
import inforu from './inforu'
|
import inforu from './inforu'
|
||||||
import infura from './infura'
|
import infura from './infura'
|
||||||
import itbit from './itbit'
|
import _itbit from './itbit'
|
||||||
import kraken from './kraken'
|
import _kraken from './kraken'
|
||||||
import mailgun from './mailgun'
|
import mailgun from './mailgun'
|
||||||
import scorechain from './scorechain'
|
import scorechain from './scorechain'
|
||||||
import sumsub from './sumsub'
|
import sumsub from './sumsub'
|
||||||
|
|
@ -19,7 +19,16 @@ import trongrid from './trongrid'
|
||||||
import twilio from './twilio'
|
import twilio from './twilio'
|
||||||
import vonage from './vonage'
|
import vonage from './vonage'
|
||||||
|
|
||||||
export default {
|
const schemas = (markets = {}) => {
|
||||||
|
const binance = _binance(markets?.binance)
|
||||||
|
const bitfinex = _bitfinex(markets?.bitfinex)
|
||||||
|
const binanceus = _binanceus(markets?.binanceus)
|
||||||
|
const bitstamp = _bitstamp(markets?.bitstamp)
|
||||||
|
const cex = _cex(markets?.cex)
|
||||||
|
const itbit = _itbit(markets?.itbit)
|
||||||
|
const kraken = _kraken(markets?.kraken)
|
||||||
|
|
||||||
|
return {
|
||||||
[bitgo.code]: bitgo,
|
[bitgo.code]: bitgo,
|
||||||
[galoy.code]: galoy,
|
[galoy.code]: galoy,
|
||||||
[bitstamp.code]: bitstamp,
|
[bitstamp.code]: bitstamp,
|
||||||
|
|
@ -40,4 +49,7 @@ export default {
|
||||||
[binance.code]: binance,
|
[binance.code]: binance,
|
||||||
[bitfinex.code]: bitfinex,
|
[bitfinex.code]: bitfinex,
|
||||||
[sumsub.code]: sumsub
|
[sumsub.code]: sumsub
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default schemas
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
import {
|
||||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
SecretInput,
|
||||||
|
TextInput,
|
||||||
|
Autocomplete
|
||||||
|
} from 'src/components/inputs/formik'
|
||||||
|
|
||||||
import { secretTest } from './helper'
|
import { buildCurrencyOptions, secretTest } from './helper'
|
||||||
|
|
||||||
export default {
|
const schema = markets => {
|
||||||
|
return {
|
||||||
code: 'itbit',
|
code: 'itbit',
|
||||||
name: 'itBit',
|
name: 'itBit',
|
||||||
title: 'itBit (Exchange)',
|
title: 'itBit (Exchange)',
|
||||||
|
|
@ -13,26 +17,37 @@ export default {
|
||||||
{
|
{
|
||||||
code: 'userId',
|
code: 'userId',
|
||||||
display: 'User ID',
|
display: 'User ID',
|
||||||
component: TextInputFormik,
|
component: TextInput,
|
||||||
face: true,
|
face: true,
|
||||||
long: true
|
long: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'walletId',
|
code: 'walletId',
|
||||||
display: 'Wallet ID',
|
display: 'Wallet ID',
|
||||||
component: TextInputFormik,
|
component: TextInput,
|
||||||
face: true,
|
face: true,
|
||||||
long: true
|
long: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'clientKey',
|
code: 'clientKey',
|
||||||
display: 'Client key',
|
display: 'Client key',
|
||||||
component: TextInputFormik
|
component: TextInput
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'clientSecret',
|
code: 'clientSecret',
|
||||||
display: 'Client secret',
|
display: 'Client secret',
|
||||||
component: SecretInputFormik
|
component: SecretInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'currencyMarket',
|
||||||
|
display: 'Currency market',
|
||||||
|
component: Autocomplete,
|
||||||
|
inputProps: {
|
||||||
|
options: buildCurrencyOptions(markets),
|
||||||
|
labelProp: 'display',
|
||||||
|
valueProp: 'code'
|
||||||
|
},
|
||||||
|
face: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
getValidationSchema: account => {
|
getValidationSchema: account => {
|
||||||
|
|
@ -48,7 +63,13 @@ export default {
|
||||||
.required('The client key is required'),
|
.required('The client key is required'),
|
||||||
clientSecret: Yup.string('The client secret must be a string')
|
clientSecret: Yup.string('The client secret must be a string')
|
||||||
.max(100, 'The client secret is too long')
|
.max(100, 'The client secret is too long')
|
||||||
.test(secretTest(account?.clientSecret, 'client secret'))
|
.test(secretTest(account?.clientSecret, 'client secret')),
|
||||||
|
currencyMarket: Yup.string(
|
||||||
|
'The currency market must be a string'
|
||||||
|
).required('The currency market is required')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default schema
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
import {
|
||||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
SecretInput,
|
||||||
|
TextInput,
|
||||||
|
Autocomplete
|
||||||
|
} from 'src/components/inputs/formik'
|
||||||
|
|
||||||
import { secretTest } from './helper'
|
import { secretTest, buildCurrencyOptions } from './helper'
|
||||||
|
|
||||||
export default {
|
const schema = markets => {
|
||||||
|
return {
|
||||||
code: 'kraken',
|
code: 'kraken',
|
||||||
name: 'Kraken',
|
name: 'Kraken',
|
||||||
title: 'Kraken (Exchange)',
|
title: 'Kraken (Exchange)',
|
||||||
|
|
@ -13,14 +17,25 @@ export default {
|
||||||
{
|
{
|
||||||
code: 'apiKey',
|
code: 'apiKey',
|
||||||
display: 'API key',
|
display: 'API key',
|
||||||
component: TextInputFormik,
|
component: TextInput,
|
||||||
face: true,
|
face: true,
|
||||||
long: true
|
long: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'privateKey',
|
code: 'privateKey',
|
||||||
display: 'Private key',
|
display: 'Private key',
|
||||||
component: SecretInputFormik
|
component: SecretInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'currencyMarket',
|
||||||
|
display: 'Currency market',
|
||||||
|
component: Autocomplete,
|
||||||
|
inputProps: {
|
||||||
|
options: buildCurrencyOptions(markets),
|
||||||
|
labelProp: 'display',
|
||||||
|
valueProp: 'code'
|
||||||
|
},
|
||||||
|
face: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
getValidationSchema: account => {
|
getValidationSchema: account => {
|
||||||
|
|
@ -30,7 +45,13 @@ export default {
|
||||||
.required('The API key is required'),
|
.required('The API key is required'),
|
||||||
privateKey: Yup.string('The private key must be a string')
|
privateKey: Yup.string('The private key must be a string')
|
||||||
.max(100, 'The private key is too long')
|
.max(100, 'The private key is too long')
|
||||||
.test(secretTest(account?.privateKey, 'private key'))
|
.test(secretTest(account?.privateKey, 'private key')),
|
||||||
|
currencyMarket: Yup.string(
|
||||||
|
'The currency market must be a string'
|
||||||
|
).required('The currency market is required')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default schema
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ const mistyRose = '#ffeceb'
|
||||||
const pumpkin = '#ff7311'
|
const pumpkin = '#ff7311'
|
||||||
const linen = '#fbf3ec'
|
const linen = '#fbf3ec'
|
||||||
|
|
||||||
|
// Warning
|
||||||
|
const orangeYellow = '#ffcc00'
|
||||||
|
|
||||||
// Color Variables
|
// Color Variables
|
||||||
const primaryColor = zodiac
|
const primaryColor = zodiac
|
||||||
|
|
||||||
|
|
@ -136,6 +139,7 @@ export {
|
||||||
java,
|
java,
|
||||||
neon,
|
neon,
|
||||||
linen,
|
linen,
|
||||||
|
orangeYellow,
|
||||||
// named colors
|
// named colors
|
||||||
primaryColor,
|
primaryColor,
|
||||||
secondaryColor,
|
secondaryColor,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue