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 ccxt = require('./plugins/exchange/ccxt')
|
||||
const mockExchange = require('./plugins/exchange/mock-exchange')
|
||||
const accounts = require('./new-admin/config/accounts')
|
||||
|
||||
function lookupExchange (settings, cryptoCode) {
|
||||
const exchange = configManager.getWalletSettings(cryptoCode, settings.config).exchange
|
||||
|
|
@ -45,8 +49,26 @@ function active (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 = {
|
||||
fetchExchange,
|
||||
buy,
|
||||
sell,
|
||||
active
|
||||
active,
|
||||
getMarkets
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ const funding = require('./funding.resolver')
|
|||
const log = require('./log.resolver')
|
||||
const loyalty = require('./loyalty.resolver')
|
||||
const machine = require('./machine.resolver')
|
||||
const market = require('./market.resolver')
|
||||
const notification = require('./notification.resolver')
|
||||
const pairing = require('./pairing.resolver')
|
||||
const rates = require('./rates.resolver')
|
||||
|
|
@ -35,6 +36,7 @@ const resolvers = [
|
|||
log,
|
||||
loyalty,
|
||||
machine,
|
||||
market,
|
||||
notification,
|
||||
pairing,
|
||||
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 loyalty = require('./loyalty.type')
|
||||
const machine = require('./machine.type')
|
||||
const market = require('./market.type')
|
||||
const notification = require('./notification.type')
|
||||
const pairing = require('./pairing.type')
|
||||
const rates = require('./rates.type')
|
||||
|
|
@ -35,6 +36,7 @@ const types = [
|
|||
log,
|
||||
loyalty,
|
||||
machine,
|
||||
market,
|
||||
notification,
|
||||
pairing,
|
||||
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,25 +475,28 @@ function plugins (settings, deviceId) {
|
|||
|
||||
function buyAndSell (rec, doBuy, tx) {
|
||||
const cryptoCode = rec.cryptoCode
|
||||
const fiatCode = rec.fiatCode
|
||||
const cryptoAtoms = doBuy ? commissionMath.fiatToCrypto(tx, rec, deviceId, settings.config) : rec.cryptoAtoms.negated()
|
||||
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 market = [fiatCode, cryptoCode].join('')
|
||||
const market = [fiatCode, cryptoCode].join('')
|
||||
|
||||
if (!exchange.active(settings, cryptoCode)) return
|
||||
if (!exchange.active(settings, cryptoCode)) return
|
||||
|
||||
const direction = doBuy ? 'cashIn' : 'cashOut'
|
||||
const internalTxId = tx ? tx.id : rec.id
|
||||
logger.debug('[%s] Pushing trade: %d', market, cryptoAtoms)
|
||||
if (!tradesQueues[market]) tradesQueues[market] = []
|
||||
tradesQueues[market].push({
|
||||
direction,
|
||||
internalTxId,
|
||||
fiatCode,
|
||||
cryptoAtoms,
|
||||
cryptoCode,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
const direction = doBuy ? 'cashIn' : 'cashOut'
|
||||
const internalTxId = tx ? tx.id : rec.id
|
||||
logger.debug('[%s] Pushing trade: %d', market, cryptoAtoms)
|
||||
if (!tradesQueues[market]) tradesQueues[market] = []
|
||||
tradesQueues[market].push({
|
||||
direction,
|
||||
internalTxId,
|
||||
fiatCode,
|
||||
cryptoAtoms,
|
||||
cryptoCode,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function consolidateTrades (cryptoCode, fiatCode) {
|
||||
|
|
@ -550,19 +553,22 @@ function plugins (settings, deviceId) {
|
|||
const deviceIds = devices.map(device => device.deviceId)
|
||||
const lists = deviceIds.map(deviceId => {
|
||||
const localeConfig = configManager.getLocale(deviceId, settings.config)
|
||||
const fiatCode = localeConfig.fiatCurrency
|
||||
const cryptoCodes = localeConfig.cryptoCurrencies
|
||||
|
||||
return cryptoCodes.map(cryptoCode => ({
|
||||
fiatCode,
|
||||
cryptoCode
|
||||
return Promise.all(cryptoCodes.map(cryptoCode => {
|
||||
return exchange.fetchExchange(settings, cryptoCode)
|
||||
.then(exchange => ({
|
||||
fiatCode: exchange.account.currencyMarket,
|
||||
cryptoCode
|
||||
}))
|
||||
}))
|
||||
})
|
||||
|
||||
const tradesPromises = _.uniq(_.flatten(lists))
|
||||
.map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode))
|
||||
|
||||
return Promise.all(tradesPromises)
|
||||
|
||||
return Promise.all(lists)
|
||||
})
|
||||
.then(lists => {
|
||||
return Promise.all(_.uniq(_.flatten(lists))
|
||||
.map(r => executeTradesForMarket(settings, r.fiatCode, r.cryptoCode)))
|
||||
})
|
||||
.catch(logger.error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,11 +33,8 @@ function buildMarket (fiatCode, cryptoCode, serviceName) {
|
|||
if (!_.includes(cryptoCode, ALL[serviceName].CRYPTO)) {
|
||||
throw new Error('Unsupported crypto: ' + cryptoCode)
|
||||
}
|
||||
const fiatSupported = ALL[serviceName].FIAT
|
||||
if (fiatSupported !== 'ALL_CURRENCIES' && !_.includes(fiatCode, fiatSupported)) {
|
||||
logger.info('Building a market for an unsupported fiat. Defaulting to EUR market')
|
||||
return cryptoCode + '/' + 'EUR'
|
||||
}
|
||||
|
||||
if (_.isNil(fiatCode)) throw new Error('Market pair building failed: Missing fiat code')
|
||||
return cryptoCode + '/' + fiatCode
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ const ORDER_TYPE = ORDER_TYPES.MARKET
|
|||
const { BTC, BCH, XMR, ETH, LTC, ZEC, LN } = COINS
|
||||
const CRYPTO = [BTC, ETH, LTC, ZEC, BCH, XMR, LN]
|
||||
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 mapper = {
|
||||
|
|
@ -17,4 +18,4 @@ const loadConfig = (account) => {
|
|||
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 CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, USDT, USDT_TRON, LN]
|
||||
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 mapper = {
|
||||
|
|
@ -17,4 +18,4 @@ const loadConfig = (account) => {
|
|||
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 CRYPTO = [BTC, ETH, LTC, BCH, USDT, LN]
|
||||
const FIAT = ['USD', 'EUR']
|
||||
const DEFAULT_FIAT_MARKET = 'EUR'
|
||||
const AMOUNT_PRECISION = 8
|
||||
const REQUIRED_CONFIG_FIELDS = ['key', 'secret']
|
||||
|
||||
|
|
@ -18,4 +19,4 @@ const loadConfig = (account) => {
|
|||
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 CRYPTO = [BTC, ETH, LTC, BCH, USDT, LN]
|
||||
const FIAT = ['USD', 'EUR']
|
||||
const DEFAULT_FIAT_MARKET = 'EUR'
|
||||
const AMOUNT_PRECISION = 8
|
||||
const REQUIRED_CONFIG_FIELDS = ['key', 'secret', 'clientId']
|
||||
const REQUIRED_CONFIG_FIELDS = ['key', 'secret', 'clientId', 'currencyMarket']
|
||||
|
||||
const loadConfig = (account) => {
|
||||
const mapper = {
|
||||
|
|
@ -19,4 +20,4 @@ const loadConfig = (account) => {
|
|||
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 _ = require('lodash/fp')
|
||||
const ccxt = require('ccxt')
|
||||
const mem = require('mem')
|
||||
|
||||
const { buildMarket, ALL, isConfigValid } = require('../common/ccxt')
|
||||
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_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
|
||||
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 amount = coinUtils.toUnit(cryptoAtoms, cryptoCode).toFixed(precision)
|
||||
const accountOptions = _.isFunction(loadOptions) ? loadOptions(account) : {}
|
||||
|
|
@ -50,4 +55,36 @@ function calculatePrice (side, amount, orderBook) {
|
|||
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 CRYPTO = [BTC, ETH, LTC, DASH, BCH, USDT, TRX, USDT_TRON, LN]
|
||||
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 mapper = {
|
||||
|
|
@ -17,4 +18,4 @@ const loadConfig = (account) => {
|
|||
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 CRYPTO = [BTC, ETH, USDT, LN]
|
||||
const FIAT = ['USD']
|
||||
const DEFAULT_FIAT_MARKET = 'USD'
|
||||
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 mapper = {
|
||||
|
|
@ -21,4 +22,4 @@ const loadConfig = (account) => {
|
|||
}
|
||||
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 CRYPTO = [BTC, ETH, LTC, DASH, ZEC, BCH, XMR, USDT, TRX, USDT_TRON, LN]
|
||||
const FIAT = ['USD', 'EUR']
|
||||
const DEFAULT_FIAT_MARKET = 'EUR'
|
||||
const AMOUNT_PRECISION = 6
|
||||
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey']
|
||||
const REQUIRED_CONFIG_FIELDS = ['apiKey', 'privateKey', 'currencyMarket']
|
||||
const USER_REF = 'userref'
|
||||
|
||||
const loadConfig = (account) => {
|
||||
|
|
@ -26,4 +27,4 @@ const loadConfig = (account) => {
|
|||
|
||||
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 sort from 'match-sorter'
|
||||
import * as R from 'ramda'
|
||||
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'
|
||||
|
||||
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 FormRenderer from './FormRenderer'
|
||||
import schemas from './schemas'
|
||||
import _schemas from './schemas'
|
||||
|
||||
const GET_INFO = gql`
|
||||
query getData {
|
||||
|
|
@ -21,6 +21,12 @@ const GET_INFO = gql`
|
|||
}
|
||||
`
|
||||
|
||||
const GET_MARKETS = gql`
|
||||
query getMarkets {
|
||||
getMarkets
|
||||
}
|
||||
`
|
||||
|
||||
const SAVE_ACCOUNT = gql`
|
||||
mutation Save($accounts: JSONObject) {
|
||||
saveAccounts(accounts: $accounts)
|
||||
|
|
@ -40,12 +46,17 @@ const useStyles = makeStyles(styles)
|
|||
const Services = () => {
|
||||
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, {
|
||||
onCompleted: () => setEditingSchema(null),
|
||||
refetchQueries: ['getData']
|
||||
})
|
||||
|
||||
const markets = marketsData?.getMarkets
|
||||
|
||||
const schemas = _schemas(markets)
|
||||
|
||||
const classes = useStyles()
|
||||
|
||||
const accounts = data?.accounts ?? {}
|
||||
|
|
@ -101,40 +112,44 @@ const Services = () => {
|
|||
const getValidationSchema = ({ code, getValidationSchema }) =>
|
||||
getValidationSchema(accounts[code])
|
||||
|
||||
const loading = marketsLoading || configLoading
|
||||
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<TitleSection title="Third-Party services" />
|
||||
<Grid container spacing={4}>
|
||||
{R.values(schemas).map(schema => (
|
||||
<Grid item key={schema.code}>
|
||||
<SingleRowTable
|
||||
editMessage={'Configure ' + schema.title}
|
||||
title={schema.title}
|
||||
onEdit={() => setEditingSchema(schema)}
|
||||
items={getItems(schema.code, schema.elements)}
|
||||
!loading && (
|
||||
<div className={classes.wrapper}>
|
||||
<TitleSection title="Third-Party services" />
|
||||
<Grid container spacing={4}>
|
||||
{R.values(schemas).map(schema => (
|
||||
<Grid item key={schema.code}>
|
||||
<SingleRowTable
|
||||
editMessage={'Configure ' + schema.title}
|
||||
title={schema.title}
|
||||
onEdit={() => setEditingSchema(schema)}
|
||||
items={getItems(schema.code, schema.elements)}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
{editingSchema && (
|
||||
<Modal
|
||||
title={`Edit ${editingSchema.name}`}
|
||||
width={525}
|
||||
handleClose={() => setEditingSchema(null)}
|
||||
open={true}>
|
||||
<FormRenderer
|
||||
save={it =>
|
||||
saveAccount({
|
||||
variables: { accounts: { [editingSchema.code]: it } }
|
||||
})
|
||||
}
|
||||
elements={getElements(editingSchema)}
|
||||
validationSchema={getValidationSchema(editingSchema)}
|
||||
value={getAccounts(editingSchema)}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
{editingSchema && (
|
||||
<Modal
|
||||
title={`Edit ${editingSchema.name}`}
|
||||
width={525}
|
||||
handleClose={() => setEditingSchema(null)}
|
||||
open={true}>
|
||||
<FormRenderer
|
||||
save={it =>
|
||||
saveAccount({
|
||||
variables: { accounts: { [editingSchema.code]: it } }
|
||||
})
|
||||
}
|
||||
elements={getElements(editingSchema)}
|
||||
validationSchema={getValidationSchema(editingSchema)}
|
||||
value={getAccounts(editingSchema)}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,36 +1,57 @@
|
|||
import * as Yup from 'yup'
|
||||
|
||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
||||
import {
|
||||
SecretInput,
|
||||
TextInput,
|
||||
Autocomplete
|
||||
} from 'src/components/inputs/formik'
|
||||
|
||||
import { secretTest } from './helper'
|
||||
import { secretTest, buildCurrencyOptions } from './helper'
|
||||
|
||||
export default {
|
||||
code: 'binance',
|
||||
name: 'Binance',
|
||||
title: 'Binance (Exchange)',
|
||||
elements: [
|
||||
{
|
||||
code: 'apiKey',
|
||||
display: 'API key',
|
||||
component: TextInputFormik,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'privateKey',
|
||||
display: 'Private key',
|
||||
component: SecretInputFormik
|
||||
const schema = markets => {
|
||||
return {
|
||||
code: 'binance',
|
||||
name: 'Binance',
|
||||
title: 'Binance (Exchange)',
|
||||
elements: [
|
||||
{
|
||||
code: 'apiKey',
|
||||
display: 'API key',
|
||||
component: TextInput,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'privateKey',
|
||||
display: 'Private key',
|
||||
component: SecretInput
|
||||
},
|
||||
{
|
||||
code: 'currencyMarket',
|
||||
display: 'Currency market',
|
||||
component: Autocomplete,
|
||||
inputProps: {
|
||||
options: buildCurrencyOptions(markets),
|
||||
labelProp: 'display',
|
||||
valueProp: 'code'
|
||||
},
|
||||
face: true
|
||||
}
|
||||
],
|
||||
getValidationSchema: account => {
|
||||
return Yup.object().shape({
|
||||
apiKey: Yup.string('The API key must be a string')
|
||||
.max(100, 'The API key is too long')
|
||||
.required('The API key is required'),
|
||||
privateKey: Yup.string('The private key must be a string')
|
||||
.max(100, 'The private key is too long')
|
||||
.test(secretTest(account?.privateKey, 'private key')),
|
||||
currencyMarket: Yup.string(
|
||||
'The currency market must be a string'
|
||||
).required('The currency market is required')
|
||||
})
|
||||
}
|
||||
],
|
||||
getValidationSchema: account => {
|
||||
return Yup.object().shape({
|
||||
apiKey: Yup.string('The API key must be a string')
|
||||
.max(100, 'The API key is too long')
|
||||
.required('The API key is required'),
|
||||
privateKey: Yup.string('The private key must be a string')
|
||||
.max(100, 'The private key is too long')
|
||||
.test(secretTest(account?.privateKey, 'private key'))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default schema
|
||||
|
|
|
|||
|
|
@ -1,36 +1,57 @@
|
|||
import * as Yup from 'yup'
|
||||
|
||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
||||
import {
|
||||
SecretInput,
|
||||
TextInput,
|
||||
Autocomplete
|
||||
} from 'src/components/inputs/formik'
|
||||
|
||||
import { secretTest } from './helper'
|
||||
import { secretTest, buildCurrencyOptions } from './helper'
|
||||
|
||||
export default {
|
||||
code: 'binanceus',
|
||||
name: 'Binance.us',
|
||||
title: 'Binance.us (Exchange)',
|
||||
elements: [
|
||||
{
|
||||
code: 'apiKey',
|
||||
display: 'API key',
|
||||
component: TextInputFormik,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'privateKey',
|
||||
display: 'Private key',
|
||||
component: SecretInputFormik
|
||||
const schema = markets => {
|
||||
return {
|
||||
code: 'binanceus',
|
||||
name: 'Binance.us',
|
||||
title: 'Binance.us (Exchange)',
|
||||
elements: [
|
||||
{
|
||||
code: 'apiKey',
|
||||
display: 'API key',
|
||||
component: TextInput,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'privateKey',
|
||||
display: 'Private key',
|
||||
component: SecretInput
|
||||
},
|
||||
{
|
||||
code: 'currencyMarket',
|
||||
display: 'Currency market',
|
||||
component: Autocomplete,
|
||||
inputProps: {
|
||||
options: buildCurrencyOptions(markets),
|
||||
labelProp: 'display',
|
||||
valueProp: 'code'
|
||||
},
|
||||
face: true
|
||||
}
|
||||
],
|
||||
getValidationSchema: account => {
|
||||
return Yup.object().shape({
|
||||
apiKey: Yup.string('The API key must be a string')
|
||||
.max(100, 'The API key is too long')
|
||||
.required('The API key is required'),
|
||||
privateKey: Yup.string('The private key must be a string')
|
||||
.max(100, 'The private key is too long')
|
||||
.test(secretTest(account?.privateKey, 'private key')),
|
||||
currencyMarket: Yup.string(
|
||||
'The currency market must be a string'
|
||||
).required('The currency market is required')
|
||||
})
|
||||
}
|
||||
],
|
||||
getValidationSchema: account => {
|
||||
return Yup.object().shape({
|
||||
apiKey: Yup.string('The API key must be a string')
|
||||
.max(100, 'The API key is too long')
|
||||
.required('The API key is required'),
|
||||
privateKey: Yup.string('The private key must be a string')
|
||||
.max(100, 'The private key is too long')
|
||||
.test(secretTest(account?.privateKey, 'private key'))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default schema
|
||||
|
|
|
|||
|
|
@ -1,36 +1,57 @@
|
|||
import * as Yup from 'yup'
|
||||
|
||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
||||
import {
|
||||
SecretInput,
|
||||
TextInput,
|
||||
Autocomplete
|
||||
} from 'src/components/inputs/formik'
|
||||
|
||||
import { secretTest } from './helper'
|
||||
import { secretTest, buildCurrencyOptions } from './helper'
|
||||
|
||||
export default {
|
||||
code: 'bitfinex',
|
||||
name: 'Bitfinex',
|
||||
title: 'Bitfinex (Exchange)',
|
||||
elements: [
|
||||
{
|
||||
code: 'key',
|
||||
display: 'API Key',
|
||||
component: TextInputFormik,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'secret',
|
||||
display: 'API Secret',
|
||||
component: SecretInputFormik
|
||||
const schema = markets => {
|
||||
return {
|
||||
code: 'bitfinex',
|
||||
name: 'Bitfinex',
|
||||
title: 'Bitfinex (Exchange)',
|
||||
elements: [
|
||||
{
|
||||
code: 'key',
|
||||
display: 'API key',
|
||||
component: TextInput,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'secret',
|
||||
display: 'API secret',
|
||||
component: SecretInput
|
||||
},
|
||||
{
|
||||
code: 'currencyMarket',
|
||||
display: 'Currency Market',
|
||||
component: Autocomplete,
|
||||
inputProps: {
|
||||
options: buildCurrencyOptions(markets),
|
||||
labelProp: 'display',
|
||||
valueProp: 'code'
|
||||
},
|
||||
face: true
|
||||
}
|
||||
],
|
||||
getValidationSchema: account => {
|
||||
return Yup.object().shape({
|
||||
key: Yup.string('The API key must be a string')
|
||||
.max(100, 'The API key is too long')
|
||||
.required('The API key is required'),
|
||||
secret: Yup.string('The API secret must be a string')
|
||||
.max(100, 'The API secret is too long')
|
||||
.test(secretTest(account?.secret, 'API secret')),
|
||||
currencyMarket: Yup.string(
|
||||
'The currency market must be a string'
|
||||
).required('The currency market is required')
|
||||
})
|
||||
}
|
||||
],
|
||||
getValidationSchema: account => {
|
||||
return Yup.object().shape({
|
||||
key: Yup.string('The API key must be a string')
|
||||
.max(100, 'The API key is too long')
|
||||
.required('The API key is required'),
|
||||
secret: Yup.string('The API secret must be a string')
|
||||
.max(100, 'The API secret is too long')
|
||||
.test(secretTest(account?.secret, 'API secret'))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default schema
|
||||
|
|
|
|||
|
|
@ -1,46 +1,67 @@
|
|||
import * as Yup from 'yup'
|
||||
|
||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
||||
import {
|
||||
SecretInput,
|
||||
TextInput,
|
||||
Autocomplete
|
||||
} from 'src/components/inputs/formik'
|
||||
|
||||
import { secretTest } from './helper'
|
||||
import { secretTest, buildCurrencyOptions } from './helper'
|
||||
|
||||
export default {
|
||||
code: 'bitstamp',
|
||||
name: 'Bitstamp',
|
||||
title: 'Bitstamp (Exchange)',
|
||||
elements: [
|
||||
{
|
||||
code: 'clientId',
|
||||
display: 'Client ID',
|
||||
component: TextInputFormik,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'key',
|
||||
display: 'API key',
|
||||
component: TextInputFormik,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'secret',
|
||||
display: 'API secret',
|
||||
component: SecretInputFormik
|
||||
const schema = markets => {
|
||||
return {
|
||||
code: 'bitstamp',
|
||||
name: 'Bitstamp',
|
||||
title: 'Bitstamp (Exchange)',
|
||||
elements: [
|
||||
{
|
||||
code: 'clientId',
|
||||
display: 'Client ID',
|
||||
component: TextInput,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'key',
|
||||
display: 'API key',
|
||||
component: TextInput,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'secret',
|
||||
display: 'API secret',
|
||||
component: SecretInput
|
||||
},
|
||||
{
|
||||
code: 'currencyMarket',
|
||||
display: 'Currency market',
|
||||
component: Autocomplete,
|
||||
inputProps: {
|
||||
options: buildCurrencyOptions(markets),
|
||||
labelProp: 'display',
|
||||
valueProp: 'code'
|
||||
},
|
||||
face: true
|
||||
}
|
||||
],
|
||||
getValidationSchema: account => {
|
||||
return Yup.object().shape({
|
||||
clientId: Yup.string('The client ID must be a string')
|
||||
.max(100, 'The client ID is too long')
|
||||
.required('The client ID is required'),
|
||||
key: Yup.string('The API key must be a string')
|
||||
.max(100, 'The API key is too long')
|
||||
.required('The API key is required'),
|
||||
secret: Yup.string('The API secret must be a string')
|
||||
.max(100, 'The API secret is too long')
|
||||
.test(secretTest(account?.secret, 'API secret')),
|
||||
currencyMarket: Yup.string(
|
||||
'The currency market must be a string'
|
||||
).required('The currency market is required')
|
||||
})
|
||||
}
|
||||
],
|
||||
getValidationSchema: account => {
|
||||
return Yup.object().shape({
|
||||
clientId: Yup.string('The client ID must be a string')
|
||||
.max(100, 'The client ID is too long')
|
||||
.required('The client ID is required'),
|
||||
key: Yup.string('The API key must be a string')
|
||||
.max(100, 'The API key is too long')
|
||||
.required('The API key is required'),
|
||||
secret: Yup.string('The API secret must be a string')
|
||||
.max(100, 'The API secret is too long')
|
||||
.test(secretTest(account?.secret, 'API secret'))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default schema
|
||||
|
|
|
|||
|
|
@ -1,46 +1,67 @@
|
|||
import * as Yup from 'yup'
|
||||
|
||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
||||
import {
|
||||
SecretInput,
|
||||
TextInput,
|
||||
Autocomplete
|
||||
} from 'src/components/inputs/formik'
|
||||
|
||||
import { secretTest } from './helper'
|
||||
import { secretTest, buildCurrencyOptions } from './helper'
|
||||
|
||||
export default {
|
||||
code: 'cex',
|
||||
name: 'CEX.IO',
|
||||
title: 'CEX.IO (Exchange)',
|
||||
elements: [
|
||||
{
|
||||
code: 'apiKey',
|
||||
display: 'API key',
|
||||
component: TextInputFormik,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'uid',
|
||||
display: 'User ID',
|
||||
component: TextInputFormik,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'privateKey',
|
||||
display: 'Private key',
|
||||
component: SecretInputFormik
|
||||
const schema = markets => {
|
||||
return {
|
||||
code: 'cex',
|
||||
name: 'CEX.IO',
|
||||
title: 'CEX.IO (Exchange)',
|
||||
elements: [
|
||||
{
|
||||
code: 'apiKey',
|
||||
display: 'API key',
|
||||
component: TextInput,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'uid',
|
||||
display: 'User ID',
|
||||
component: TextInput,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'privateKey',
|
||||
display: 'Private key',
|
||||
component: SecretInput
|
||||
},
|
||||
{
|
||||
code: 'currencyMarket',
|
||||
display: 'Currency Market',
|
||||
component: Autocomplete,
|
||||
inputProps: {
|
||||
options: buildCurrencyOptions(markets),
|
||||
labelProp: 'display',
|
||||
valueProp: 'code'
|
||||
},
|
||||
face: true
|
||||
}
|
||||
],
|
||||
getValidationSchema: account => {
|
||||
return Yup.object().shape({
|
||||
apiKey: Yup.string('The API key must be a string')
|
||||
.max(100, 'The API key is too long')
|
||||
.required('The API key is required'),
|
||||
uid: Yup.string('The User ID must be a string')
|
||||
.max(100, 'The User ID is too long')
|
||||
.required('The User ID is required'),
|
||||
privateKey: Yup.string('The private key must be a string')
|
||||
.max(100, 'The private key is too long')
|
||||
.test(secretTest(account?.privateKey, 'private key')),
|
||||
currencyMarket: Yup.string(
|
||||
'The currency market must be a string'
|
||||
).required('The currency market is required')
|
||||
})
|
||||
}
|
||||
],
|
||||
getValidationSchema: account => {
|
||||
return Yup.object().shape({
|
||||
apiKey: Yup.string('The API key must be a string')
|
||||
.max(100, 'The API key is too long')
|
||||
.required('The API key is required'),
|
||||
uid: Yup.string('The User ID must be a string')
|
||||
.max(100, 'The User ID is too long')
|
||||
.required('The User ID is required'),
|
||||
privateKey: Yup.string('The private key must be a string')
|
||||
.max(100, 'The private key is too long')
|
||||
.test(secretTest(account?.privateKey, 'private key'))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default schema
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
import { ALL_CRYPTOS } from '@lamassu/coins'
|
||||
import * as R from 'ramda'
|
||||
|
||||
const WARNING_LEVELS = {
|
||||
CLEAN: 'clean',
|
||||
PARTIAL: 'partial',
|
||||
IMPORTANT: 'important'
|
||||
}
|
||||
|
||||
const secretTest = (secret, message) => ({
|
||||
name: 'secret-test',
|
||||
message: message ? `The ${message} is invalid` : 'Invalid field',
|
||||
|
|
@ -21,4 +28,35 @@ const leadingZerosTest = (value, context) => {
|
|||
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 binanceus from './binanceus'
|
||||
import bitfinex from './bitfinex'
|
||||
import _binance from './binance'
|
||||
import _binanceus from './binanceus'
|
||||
import _bitfinex from './bitfinex'
|
||||
import bitgo from './bitgo'
|
||||
import bitstamp from './bitstamp'
|
||||
import _bitstamp from './bitstamp'
|
||||
import blockcypher from './blockcypher'
|
||||
import cex from './cex'
|
||||
import _cex from './cex'
|
||||
import elliptic from './elliptic'
|
||||
import galoy from './galoy'
|
||||
import inforu from './inforu'
|
||||
import infura from './infura'
|
||||
import itbit from './itbit'
|
||||
import kraken from './kraken'
|
||||
import _itbit from './itbit'
|
||||
import _kraken from './kraken'
|
||||
import mailgun from './mailgun'
|
||||
import scorechain from './scorechain'
|
||||
import sumsub from './sumsub'
|
||||
|
|
@ -19,25 +19,37 @@ import trongrid from './trongrid'
|
|||
import twilio from './twilio'
|
||||
import vonage from './vonage'
|
||||
|
||||
export default {
|
||||
[bitgo.code]: bitgo,
|
||||
[galoy.code]: galoy,
|
||||
[bitstamp.code]: bitstamp,
|
||||
[blockcypher.code]: blockcypher,
|
||||
[elliptic.code]: elliptic,
|
||||
[inforu.code]: inforu,
|
||||
[infura.code]: infura,
|
||||
[itbit.code]: itbit,
|
||||
[kraken.code]: kraken,
|
||||
[mailgun.code]: mailgun,
|
||||
[telnyx.code]: telnyx,
|
||||
[vonage.code]: vonage,
|
||||
[twilio.code]: twilio,
|
||||
[binanceus.code]: binanceus,
|
||||
[cex.code]: cex,
|
||||
[scorechain.code]: scorechain,
|
||||
[trongrid.code]: trongrid,
|
||||
[binance.code]: binance,
|
||||
[bitfinex.code]: bitfinex,
|
||||
[sumsub.code]: sumsub
|
||||
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,
|
||||
[galoy.code]: galoy,
|
||||
[bitstamp.code]: bitstamp,
|
||||
[blockcypher.code]: blockcypher,
|
||||
[elliptic.code]: elliptic,
|
||||
[inforu.code]: inforu,
|
||||
[infura.code]: infura,
|
||||
[itbit.code]: itbit,
|
||||
[kraken.code]: kraken,
|
||||
[mailgun.code]: mailgun,
|
||||
[telnyx.code]: telnyx,
|
||||
[vonage.code]: vonage,
|
||||
[twilio.code]: twilio,
|
||||
[binanceus.code]: binanceus,
|
||||
[cex.code]: cex,
|
||||
[scorechain.code]: scorechain,
|
||||
[trongrid.code]: trongrid,
|
||||
[binance.code]: binance,
|
||||
[bitfinex.code]: bitfinex,
|
||||
[sumsub.code]: sumsub
|
||||
}
|
||||
}
|
||||
|
||||
export default schemas
|
||||
|
|
|
|||
|
|
@ -1,54 +1,75 @@
|
|||
import * as Yup from 'yup'
|
||||
|
||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
||||
import {
|
||||
SecretInput,
|
||||
TextInput,
|
||||
Autocomplete
|
||||
} from 'src/components/inputs/formik'
|
||||
|
||||
import { secretTest } from './helper'
|
||||
import { buildCurrencyOptions, secretTest } from './helper'
|
||||
|
||||
export default {
|
||||
code: 'itbit',
|
||||
name: 'itBit',
|
||||
title: 'itBit (Exchange)',
|
||||
elements: [
|
||||
{
|
||||
code: 'userId',
|
||||
display: 'User ID',
|
||||
component: TextInputFormik,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'walletId',
|
||||
display: 'Wallet ID',
|
||||
component: TextInputFormik,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'clientKey',
|
||||
display: 'Client key',
|
||||
component: TextInputFormik
|
||||
},
|
||||
{
|
||||
code: 'clientSecret',
|
||||
display: 'Client secret',
|
||||
component: SecretInputFormik
|
||||
const schema = markets => {
|
||||
return {
|
||||
code: 'itbit',
|
||||
name: 'itBit',
|
||||
title: 'itBit (Exchange)',
|
||||
elements: [
|
||||
{
|
||||
code: 'userId',
|
||||
display: 'User ID',
|
||||
component: TextInput,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'walletId',
|
||||
display: 'Wallet ID',
|
||||
component: TextInput,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'clientKey',
|
||||
display: 'Client key',
|
||||
component: TextInput
|
||||
},
|
||||
{
|
||||
code: 'clientSecret',
|
||||
display: 'Client secret',
|
||||
component: SecretInput
|
||||
},
|
||||
{
|
||||
code: 'currencyMarket',
|
||||
display: 'Currency market',
|
||||
component: Autocomplete,
|
||||
inputProps: {
|
||||
options: buildCurrencyOptions(markets),
|
||||
labelProp: 'display',
|
||||
valueProp: 'code'
|
||||
},
|
||||
face: true
|
||||
}
|
||||
],
|
||||
getValidationSchema: account => {
|
||||
return Yup.object().shape({
|
||||
userId: Yup.string('The user ID must be a string')
|
||||
.max(100, 'The user ID is too long')
|
||||
.required('The user ID is required'),
|
||||
walletId: Yup.string('The wallet ID must be a string')
|
||||
.max(100, 'The wallet ID is too long')
|
||||
.required('The wallet ID is required'),
|
||||
clientKey: Yup.string('The client key must be a string')
|
||||
.max(100, 'The client key is too long')
|
||||
.required('The client key is required'),
|
||||
clientSecret: Yup.string('The client secret must be a string')
|
||||
.max(100, 'The client secret is too long')
|
||||
.test(secretTest(account?.clientSecret, 'client secret')),
|
||||
currencyMarket: Yup.string(
|
||||
'The currency market must be a string'
|
||||
).required('The currency market is required')
|
||||
})
|
||||
}
|
||||
],
|
||||
getValidationSchema: account => {
|
||||
return Yup.object().shape({
|
||||
userId: Yup.string('The user ID must be a string')
|
||||
.max(100, 'The user ID is too long')
|
||||
.required('The user ID is required'),
|
||||
walletId: Yup.string('The wallet ID must be a string')
|
||||
.max(100, 'The wallet ID is too long')
|
||||
.required('The wallet ID is required'),
|
||||
clientKey: Yup.string('The client key must be a string')
|
||||
.max(100, 'The client key is too long')
|
||||
.required('The client key is required'),
|
||||
clientSecret: Yup.string('The client secret must be a string')
|
||||
.max(100, 'The client secret is too long')
|
||||
.test(secretTest(account?.clientSecret, 'client secret'))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default schema
|
||||
|
|
|
|||
|
|
@ -1,36 +1,57 @@
|
|||
import * as Yup from 'yup'
|
||||
|
||||
import SecretInputFormik from 'src/components/inputs/formik/SecretInput'
|
||||
import TextInputFormik from 'src/components/inputs/formik/TextInput'
|
||||
import {
|
||||
SecretInput,
|
||||
TextInput,
|
||||
Autocomplete
|
||||
} from 'src/components/inputs/formik'
|
||||
|
||||
import { secretTest } from './helper'
|
||||
import { secretTest, buildCurrencyOptions } from './helper'
|
||||
|
||||
export default {
|
||||
code: 'kraken',
|
||||
name: 'Kraken',
|
||||
title: 'Kraken (Exchange)',
|
||||
elements: [
|
||||
{
|
||||
code: 'apiKey',
|
||||
display: 'API key',
|
||||
component: TextInputFormik,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'privateKey',
|
||||
display: 'Private key',
|
||||
component: SecretInputFormik
|
||||
const schema = markets => {
|
||||
return {
|
||||
code: 'kraken',
|
||||
name: 'Kraken',
|
||||
title: 'Kraken (Exchange)',
|
||||
elements: [
|
||||
{
|
||||
code: 'apiKey',
|
||||
display: 'API key',
|
||||
component: TextInput,
|
||||
face: true,
|
||||
long: true
|
||||
},
|
||||
{
|
||||
code: 'privateKey',
|
||||
display: 'Private key',
|
||||
component: SecretInput
|
||||
},
|
||||
{
|
||||
code: 'currencyMarket',
|
||||
display: 'Currency market',
|
||||
component: Autocomplete,
|
||||
inputProps: {
|
||||
options: buildCurrencyOptions(markets),
|
||||
labelProp: 'display',
|
||||
valueProp: 'code'
|
||||
},
|
||||
face: true
|
||||
}
|
||||
],
|
||||
getValidationSchema: account => {
|
||||
return Yup.object().shape({
|
||||
apiKey: Yup.string('The API key must be a string')
|
||||
.max(100, 'The API key is too long')
|
||||
.required('The API key is required'),
|
||||
privateKey: Yup.string('The private key must be a string')
|
||||
.max(100, 'The private key is too long')
|
||||
.test(secretTest(account?.privateKey, 'private key')),
|
||||
currencyMarket: Yup.string(
|
||||
'The currency market must be a string'
|
||||
).required('The currency market is required')
|
||||
})
|
||||
}
|
||||
],
|
||||
getValidationSchema: account => {
|
||||
return Yup.object().shape({
|
||||
apiKey: Yup.string('The API key must be a string')
|
||||
.max(100, 'The API key is too long')
|
||||
.required('The API key is required'),
|
||||
privateKey: Yup.string('The private key must be a string')
|
||||
.max(100, 'The private key is too long')
|
||||
.test(secretTest(account?.privateKey, 'private key'))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default schema
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ const mistyRose = '#ffeceb'
|
|||
const pumpkin = '#ff7311'
|
||||
const linen = '#fbf3ec'
|
||||
|
||||
// Warning
|
||||
const orangeYellow = '#ffcc00'
|
||||
|
||||
// Color Variables
|
||||
const primaryColor = zodiac
|
||||
|
||||
|
|
@ -136,6 +139,7 @@ export {
|
|||
java,
|
||||
neon,
|
||||
linen,
|
||||
orangeYellow,
|
||||
// named colors
|
||||
primaryColor,
|
||||
secondaryColor,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue