From 6833f5ec5706ede41e256ba4fbe816ed73c30884 Mon Sep 17 00:00:00 2001 From: sdgoh Date: Mon, 9 Nov 2020 10:08:31 +0800 Subject: [PATCH 01/17] refactor common variables across routes --- .dockerignore | 5 ++++- .env.example | 3 +++ certs/readme.md | 2 +- src/routes/balancer.route.js | 12 +++++++----- src/routes/eth.route.js | 10 +++++++--- src/services/balancer.js | 12 +++++++----- src/services/config.js | 21 ++++++++++++++++++++ src/services/eth.js | 37 ++++++++++++++++++++++++++++-------- test/command.md | 3 +++ 9 files changed, 82 insertions(+), 23 deletions(-) create mode 100644 src/services/config.js create mode 100644 test/command.md diff --git a/.dockerignore b/.dockerignore index b2196be..cd75aae 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,4 +6,7 @@ npm-debug.log *.env *.env.* # except the example .env.example -!.env.example \ No newline at end of file +!.env.example + +# Gateway API files +*.pem diff --git a/.env.example b/.env.example index 07f4d81..6cdac29 100644 --- a/.env.example +++ b/.env.example @@ -38,3 +38,6 @@ EXCHANGE_PROXY={exchange_proxy} # cert CERT_PATH={full_path_to_certs_folder} CERT_PASSPHRASE={passphrase} + +# Gas Limit +GAS_LIMIT=750000 \ No newline at end of file diff --git a/certs/readme.md b/certs/readme.md index 4fa6b9d..8d9f12b 100644 --- a/certs/readme.md +++ b/certs/readme.md @@ -1 +1 @@ -certs dir \ No newline at end of file +certs dir for local testing only \ No newline at end of file diff --git a/src/routes/balancer.route.js b/src/routes/balancer.route.js index 8880861..5b101aa 100644 --- a/src/routes/balancer.route.js +++ b/src/routes/balancer.route.js @@ -3,15 +3,16 @@ import { ethers } from 'ethers'; import express from 'express'; import { getParamData, latency, reportConnectionError, statusMessages } from '../services/utils'; +import { getConfig } from '../services/config'; + import Balancer from '../services/balancer'; -import Ethereum from '../services/eth'; -require('dotenv').config() +// require('dotenv').config() const debug = require('debug')('router') const router = express.Router() -const balancer = new Balancer(process.env.BALANCER_NETWORK) -const eth = new Ethereum(process.env.BALANCER_NETWORK) +const envConfig = getConfig() +const balancer = new Balancer(envConfig.balancer.BALANCER_NETWORK) const denomMultiplier = 1e18 const swapMoreThanMaxPriceError = 'Swap price exceeds maxPrice' @@ -36,7 +37,8 @@ router.post('/', async (req, res) => { network: balancer.network, provider: balancer.provider.connection.url, exchangeProxy: balancer.exchangeProxy, - subgraphUrl: process.env.REACT_APP_SUBGRAPH_URL, + subgraphUrl: envConfig.balancer.REACT_APP_SUBGRAPH_URL, + gasLimit: envConfig.balancer.GAS_LIMIT, connection: true, timestamp: Date.now(), }) diff --git a/src/routes/eth.route.js b/src/routes/eth.route.js index 4208e13..67216d6 100644 --- a/src/routes/eth.route.js +++ b/src/routes/eth.route.js @@ -3,12 +3,14 @@ import { ethers } from 'ethers'; import express from 'express'; import { getParamData, latency, reportConnectionError, statusMessages } from '../services/utils'; +import { getConfig } from '../services/config'; import Ethereum from '../services/eth'; import Balancer from '../services/balancer'; const router = express.Router() -const eth = new Ethereum(process.env.BALANCER_NETWORK) -const balancer = new Balancer(process.env.BALANCER_NETWORK) +const envConfig = getConfig() +const eth = new Ethereum(envConfig.balancer.BALANCER_NETWORK) +const balancer = new Balancer(envConfig.balancer.BALANCER_NETWORK) const seperator = ',' const debug = require('debug')('router') @@ -24,9 +26,10 @@ router.post('/balances', async (req, res) => { const initTime = Date.now() const paramData = getParamData(req.body) const privateKey = paramData.privateKey - let wallet + let wallet, currentGasPrice try { wallet = new ethers.Wallet(privateKey, eth.provider) + currentGasPrice = await eth.getCurrentGasPrice() } catch (err) { let reason err.reason ? reason = err.reason : reason = 'Error getting wallet' @@ -53,6 +56,7 @@ router.post('/balances', async (req, res) => { network: eth.network, timestamp: initTime, latency: latency(initTime, Date.now()), + currentGasPrice: currentGasPrice, balances: balances }) }) diff --git a/src/services/balancer.js b/src/services/balancer.js index 0698e3a..63e5e78 100644 --- a/src/services/balancer.js +++ b/src/services/balancer.js @@ -5,19 +5,21 @@ const BigNumber = require('bignumber.js') const ethers = require('ethers') const proxyArtifact = require('../static/ExchangeProxy.json') const debug = require('debug')('router') +const config = require('../services/config') // constants +const ENV_CONFIG = config.getConfig() const MAX_UINT = ethers.constants.MaxUint256; const MULTI = '0xeefba1e63905ef1d7acba5a8513c70307c1ce441'; -const GAS_LIMIT = 1200000 export default class Balancer { constructor (network = 'kovan') { // network defaults to kovan - const providerUrl = process.env.ETHEREUM_RPC_URL - this.network = process.env.BALANCER_NETWORK + const providerUrl = ENV_CONFIG.ethereum.ETHEREUM_RPC_URL + this.network = ENV_CONFIG.balancer.BALANCER_NETWORK this.provider = new ethers.providers.JsonRpcProvider(providerUrl) - this.exchangeProxy = process.env.EXCHANGE_PROXY + this.exchangeProxy = ENV_CONFIG.balancer.EXCHANGE_PROXY + this.gasLimit = ENV_CONFIG.balancer.GAS_LIMIT if (network === 'kovan') { // this.erc20Tokens = JSON.parse(fs.readFileSync('src/static/erc20_tokens_kovan.json')) @@ -129,7 +131,7 @@ export default class Balancer { 0, { gasPrice: gasPrice * 1e9, - gasLimit: GAS_LIMIT + gasLimit: this.gasLimit } ) debug(`Tx Hash: ${tx.hash}`); diff --git a/src/services/config.js b/src/services/config.js new file mode 100644 index 0000000..1275dca --- /dev/null +++ b/src/services/config.js @@ -0,0 +1,21 @@ +require('dotenv').config() + +export const getConfig = () => { + const env = { + terra: { + TERRA_LCD_URL: process.env.TERRA_LCD_URL, + TERRA_CHAIN: process.env.TERRA_LCD_URL, + }, + balancer: { + BALANCER_NETWORK: process.env.BALANCER_NETWORK, + REACT_APP_SUBGRAPH_URL: process.env.REACT_APP_SUBGRAPH_URL, + EXCHANGE_PROXY: process.env.EXCHANGE_PROXY, + GAS_PRICE: parseInt(process.env.GAS_PRICE), + GAS_LIMIT: parseInt(process.env.GAS_LIMIT) || 1200000 + }, + ethereum: { + ETHEREUM_RPC_URL: process.env.ETHEREUM_RPC_URL, + } + } + return env +} diff --git a/src/services/eth.js b/src/services/eth.js index 4788792..caac63f 100644 --- a/src/services/eth.js +++ b/src/services/eth.js @@ -2,13 +2,19 @@ require('dotenv').config() const fs = require('fs'); const ethers = require('ethers') const abi = require('../static/abi') +const debug = require('debug')('router') +const config = require('../services/config') + +// constants +const ENV_CONFIG = config.getConfig() export default class Ethereum { constructor (network = 'kovan') { // network defaults to kovan - const providerUrl = process.env.ETHEREUM_RPC_URL - this.network = process.env.BALANCER_NETWORK + const providerUrl = ENV_CONFIG.ethereum.ETHEREUM_RPC_URL + this.network = ENV_CONFIG.balancer.BALANCER_NETWORK this.provider = new ethers.providers.JsonRpcProvider(providerUrl) + this.gasLimit = ENV_CONFIG.balancer.GAS_LIMIT if (network === 'kovan') { // for kovan testing only @@ -62,8 +68,7 @@ export default class Ethereum { } // approve a spender to transfer tokens from a wallet address - async approveERC20 (wallet, spender, tokenAddress, amount, gasPrice = process.env.GAS_PRICE) { - const GAS_LIMIT = 100000 + async approveERC20 (wallet, spender, tokenAddress, amount, gasPrice = ENV_CONFIG.balancer.GAS_PRICE, gasLimit = this.gasLimit) { try { // instantiate a contract and pass in wallet, which act on behalf of that signer const contract = new ethers.Contract(tokenAddress, abi.ERC20Abi, wallet) @@ -71,7 +76,7 @@ export default class Ethereum { spender, amount, { gasPrice: gasPrice * 1e9, - gasLimit: GAS_LIMIT + gasLimit: gasLimit } ) } catch (err) { @@ -81,15 +86,31 @@ export default class Ethereum { } } - async deposit (wallet, tokenAddress, amount, gasPrice = process.env.GAS_PRICE) { - const GAS_LIMIT = 100000 + // get current Gas + async getCurrentGasPrice () { + try { + this.provider.getGasPrice().then(function (gas) { + // gasPrice is a BigNumber; convert it to a decimal string + const gasPrice = gas.toString(); + debug('gas obj', gas) + debug('Current gas price: ', gasPrice / 1e9) + return gasPrice + }) + } catch (err) { + let reason + err.reason ? reason = err.reason : reason = 'error gas lookup' + return reason + } + } + + async deposit (wallet, tokenAddress, amount, gasPrice = ENV_CONFIG.balancer.GAS_PRICE, gasLimit = this.gasLimit) { // deposit ETH to a contract address try { const contract = new ethers.Contract(tokenAddress, abi.KovanWETHAbi, wallet) return await contract.deposit( { value: amount, gasPrice: gasPrice * 1e9, - gasLimit: GAS_LIMIT + gasLimit: gasLimit } ) } catch (err) { diff --git a/test/command.md b/test/command.md new file mode 100644 index 0000000..7ddb10c --- /dev/null +++ b/test/command.md @@ -0,0 +1,3 @@ + +# test endpoint +curl --insecure --key /home/dev/bots/hbot_files/hummingbot_certs/client_key.pem --cert /home/dev/bots/hbot_files/hummingbot_certs/client_cert.pem https://localhost:5000/api From b34e57005deeb89bb9ccb6c76b87aa6191477a87 Mon Sep 17 00:00:00 2001 From: sdgoh Date: Mon, 9 Nov 2020 23:49:23 +0800 Subject: [PATCH 02/17] Update input param and readme --- README.md | 3 +-- setup.md | 2 +- src/routes/eth.route.js | 3 +-- src/services/balancer.js | 2 +- src/services/eth.js | 7 ++++--- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8b49cff..01aa595 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,7 @@ We created hummingbot to promote **decentralized market-making**: enabling membe ### Install Hummingbot - [Quickstart guide](https://docs.hummingbot.io/quickstart/) -- [All installation options](https://docs.hummingbot.io/installation/) -- [Installation scripts](./installation/) +- [All installation options](https://docs.hummingbot.io/installation/overview/) ### Get support - Chat with our support team on [Discord](https://discord.hummingbot.io) diff --git a/setup.md b/setup.md index a7d189e..3340dce 100644 --- a/setup.md +++ b/setup.md @@ -9,7 +9,7 @@ This can be used as a common API server to handle transactions that requires cus ## Development Requirements - NodeJS - - Tested on Node v10.22.0 + - Tested on Node v10.22.1 - https://docs.npmjs.com/downloading-and-installing-node-js-and-npm ```bash diff --git a/src/routes/eth.route.js b/src/routes/eth.route.js index 67216d6..6722016 100644 --- a/src/routes/eth.route.js +++ b/src/routes/eth.route.js @@ -26,10 +26,9 @@ router.post('/balances', async (req, res) => { const initTime = Date.now() const paramData = getParamData(req.body) const privateKey = paramData.privateKey - let wallet, currentGasPrice + let wallet try { wallet = new ethers.Wallet(privateKey, eth.provider) - currentGasPrice = await eth.getCurrentGasPrice() } catch (err) { let reason err.reason ? reason = err.reason : reason = 'Error getting wallet' diff --git a/src/services/balancer.js b/src/services/balancer.js index 63e5e78..561b8a2 100644 --- a/src/services/balancer.js +++ b/src/services/balancer.js @@ -16,8 +16,8 @@ export default class Balancer { constructor (network = 'kovan') { // network defaults to kovan const providerUrl = ENV_CONFIG.ethereum.ETHEREUM_RPC_URL - this.network = ENV_CONFIG.balancer.BALANCER_NETWORK this.provider = new ethers.providers.JsonRpcProvider(providerUrl) + this.network = ENV_CONFIG.balancer.BALANCER_NETWORK this.exchangeProxy = ENV_CONFIG.balancer.EXCHANGE_PROXY this.gasLimit = ENV_CONFIG.balancer.GAS_LIMIT diff --git a/src/services/eth.js b/src/services/eth.js index caac63f..fe9b29c 100644 --- a/src/services/eth.js +++ b/src/services/eth.js @@ -12,9 +12,10 @@ export default class Ethereum { constructor (network = 'kovan') { // network defaults to kovan const providerUrl = ENV_CONFIG.ethereum.ETHEREUM_RPC_URL - this.network = ENV_CONFIG.balancer.BALANCER_NETWORK this.provider = new ethers.providers.JsonRpcProvider(providerUrl) + this.network = ENV_CONFIG.balancer.BALANCER_NETWORK this.gasLimit = ENV_CONFIG.balancer.GAS_LIMIT + this.approvalGasLimit = ENV_CONFIG.balancer.APPROVAL_GAS_LIMIT if (network === 'kovan') { // for kovan testing only @@ -68,7 +69,7 @@ export default class Ethereum { } // approve a spender to transfer tokens from a wallet address - async approveERC20 (wallet, spender, tokenAddress, amount, gasPrice = ENV_CONFIG.balancer.GAS_PRICE, gasLimit = this.gasLimit) { + async approveERC20 (wallet, spender, tokenAddress, amount, gasPrice = this.gasPrice, gasLimit = this.approvalGasLimit) { try { // instantiate a contract and pass in wallet, which act on behalf of that signer const contract = new ethers.Contract(tokenAddress, abi.ERC20Abi, wallet) @@ -103,7 +104,7 @@ export default class Ethereum { } } - async deposit (wallet, tokenAddress, amount, gasPrice = ENV_CONFIG.balancer.GAS_PRICE, gasLimit = this.gasLimit) { + async deposit (wallet, tokenAddress, amount, gasPrice = this.gasPrice, gasLimit = this.approvalGasLimit) { // deposit ETH to a contract address try { const contract = new ethers.Contract(tokenAddress, abi.KovanWETHAbi, wallet) From 519dcab01c47820ee8d7f88595286ea098f19afd Mon Sep 17 00:00:00 2001 From: sdgoh Date: Tue, 10 Nov 2020 00:11:32 +0800 Subject: [PATCH 03/17] (fix) Remove undefined current gas price param --- src/routes/eth.route.js | 1 - src/services/eth.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/routes/eth.route.js b/src/routes/eth.route.js index 6722016..d3200f9 100644 --- a/src/routes/eth.route.js +++ b/src/routes/eth.route.js @@ -55,7 +55,6 @@ router.post('/balances', async (req, res) => { network: eth.network, timestamp: initTime, latency: latency(initTime, Date.now()), - currentGasPrice: currentGasPrice, balances: balances }) }) diff --git a/src/services/eth.js b/src/services/eth.js index fe9b29c..0cafdd3 100644 --- a/src/services/eth.js +++ b/src/services/eth.js @@ -93,8 +93,6 @@ export default class Ethereum { this.provider.getGasPrice().then(function (gas) { // gasPrice is a BigNumber; convert it to a decimal string const gasPrice = gas.toString(); - debug('gas obj', gas) - debug('Current gas price: ', gasPrice / 1e9) return gasPrice }) } catch (err) { From ce04a694095b43b72da025c10015a6e0e439161d Mon Sep 17 00:00:00 2001 From: sdgoh Date: Wed, 11 Nov 2020 08:40:55 +0800 Subject: [PATCH 04/17] (feat) Refactor routes to use middleware peer connection check --- src/app.js | 22 +++++++++------------- src/routes/balancer.route.js | 11 ----------- src/routes/index.route.js | 12 +----------- src/routes/terra.route.js | 11 ----------- src/services/access.js | 21 +++++++++++++++++++++ src/services/config.js | 3 ++- 6 files changed, 33 insertions(+), 47 deletions(-) create mode 100644 src/services/access.js diff --git a/src/app.js b/src/app.js index c65bc44..e1ac9a4 100644 --- a/src/app.js +++ b/src/app.js @@ -3,6 +3,7 @@ import bodyParser from 'body-parser' import express from 'express' import helmet from 'helmet' import { statusMessages } from './services/utils'; +import { validateAccess } from './services/access'; import { IpFilter } from 'express-ipfilter' // Routes @@ -34,22 +35,17 @@ if (ipWhitelist) { app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); +app.use(validateAccess) + // mount all routes to this path -app.use('/api', apiRoutes); -app.use('/eth', ethRoutes); -// app.use('/celo', celoRoutes); -app.use('/terra', terraRoutes); -app.use('/balancer', balancerRoutes); +app.use('/api', validateAccess, apiRoutes); +app.use('/eth', validateAccess, ethRoutes); +// app.use('/celo', validateAccess, celoRoutes); +app.use('/terra', validateAccess, terraRoutes); +app.use('/balancer', validateAccess, balancerRoutes); app.get('/', (req, res, next) => { - const cert = req.connection.getPeerCertificate() - if (req.client.authorized) { - next() - } else if (cert.subject) { - res.status(403).send({ error: statusMessages.ssl_cert_invalid }) - } else { - res.status(401).send({ error: statusMessages.ssl_cert_required }) - } + res.send('ok') }) /** diff --git a/src/routes/balancer.route.js b/src/routes/balancer.route.js index 5b101aa..a2faabf 100644 --- a/src/routes/balancer.route.js +++ b/src/routes/balancer.route.js @@ -18,17 +18,6 @@ const denomMultiplier = 1e18 const swapMoreThanMaxPriceError = 'Swap price exceeds maxPrice' const swapLessThanMaxPriceError = 'Swap price lower than maxPrice' -router.use((req, res, next) => { - const cert = req.connection.getPeerCertificate() - if (req.client.authorized) { - next() - } else if (cert.subject) { - res.status(403).send({ error: statusMessages.ssl_cert_invalid }) - } else { - res.status(401).send({ error: statusMessages.ssl_cert_required }) - } -}) - router.post('/', async (req, res) => { /* POST / diff --git a/src/routes/index.route.js b/src/routes/index.route.js index d69541d..ac1cc57 100644 --- a/src/routes/index.route.js +++ b/src/routes/index.route.js @@ -4,20 +4,10 @@ const express = require('express'); const router = express.Router(); -router.use((req, res, next) => { - const cert = req.connection.getPeerCertificate() - if (req.client.authorized) { - next() - } else if (cert.subject) { - res.status(403).send({ error: statusMessages.ssl_cert_invalid }) - } else { - res.status(401).send({ error: statusMessages.ssl_cert_required }) - } -}) - router.get('/', (req, res) => { res.status(200).json({ app: process.env.APPNAME, + image: process.env.IMAGE, status: 'ok', }); }) diff --git a/src/routes/terra.route.js b/src/routes/terra.route.js index b29edaa..1831848 100644 --- a/src/routes/terra.route.js +++ b/src/routes/terra.route.js @@ -59,17 +59,6 @@ const connect = () => { return terra } -router.use((req, res, next) => { - const cert = req.connection.getPeerCertificate() - if (req.client.authorized) { - next() - } else if (cert.subject) { - res.status(403).send({ error: statusMessages.ssl_cert_invalid }) - } else { - res.status(401).send({ error: statusMessages.ssl_cert_required }) - } -}) - router.get('/', async (req, res) => { /* GET / diff --git a/src/services/access.js b/src/services/access.js new file mode 100644 index 0000000..e35ff81 --- /dev/null +++ b/src/services/access.js @@ -0,0 +1,21 @@ +/* + middleware for validating mutual authentication access +*/ + +import { statusMessages } from './utils'; + +const debug = require('debug')('router') + +export const validateAccess = (req, res, next) => { + const cert = req.connection.getPeerCertificate() + if (req.client.authorized) { + debug('Access granted') + next() + } else if (cert.subject) { + console.log('Error!', statusMessages.ssl_cert_invalid) + res.status(403).send({ error: statusMessages.ssl_cert_invalid }) + } else { + console.log('Error!', statusMessages.ssl_cert_required) + res.status(401).send({ error: statusMessages.ssl_cert_required }) + } +} diff --git a/src/services/config.js b/src/services/config.js index 1275dca..16543ad 100644 --- a/src/services/config.js +++ b/src/services/config.js @@ -11,7 +11,8 @@ export const getConfig = () => { REACT_APP_SUBGRAPH_URL: process.env.REACT_APP_SUBGRAPH_URL, EXCHANGE_PROXY: process.env.EXCHANGE_PROXY, GAS_PRICE: parseInt(process.env.GAS_PRICE), - GAS_LIMIT: parseInt(process.env.GAS_LIMIT) || 1200000 + GAS_LIMIT: parseInt(process.env.GAS_LIMIT) || 1200000, + APPROVAL_GAS_LIMIT: parseInt(process.env.APPROVAL_GAS_LIMIT) || 100000 }, ethereum: { ETHEREUM_RPC_URL: process.env.ETHEREUM_RPC_URL, From 11375842ea085339cf64ebe7394fddb03db76fe3 Mon Sep 17 00:00:00 2001 From: sdgoh Date: Fri, 13 Nov 2020 13:02:24 +0800 Subject: [PATCH 05/17] Update global config setting; Refactor Terra endpoints --- .env.example | 2 +- src/index.js | 5 +- src/routes/balancer.route.js | 8 +- src/routes/eth.route.js | 4 +- src/routes/terra.route.js | 363 +++++++++++++++++------------------ src/services/balancer.js | 9 +- src/services/config.js | 18 +- src/services/eth.js | 8 +- src/services/terra.js | 90 +++++++++ src/services/utils.js | 6 + 10 files changed, 308 insertions(+), 205 deletions(-) create mode 100644 src/services/terra.js diff --git a/.env.example b/.env.example index 6cdac29..958a19a 100644 --- a/.env.example +++ b/.env.example @@ -14,7 +14,7 @@ IP_WHITELIST= # Terra TERRA_LCD_URL=https://tequila-lcd.terra.dev -TERRA_CHAIN=tequila-0004 +TERRA_NETWORK=tequila-0004 # Balancer # - network: mainnet, kovan, etc diff --git a/src/index.js b/src/index.js index 3ded6f3..e628b92 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,7 @@ const env = process.env.NODE_ENV const port = process.env.PORT const certPassphrase = process.env.CERT_PASSPHRASE const balancerNetwork = process.env.BALANCER_NETWORK +const terraNetwork = process.env.TERRA_NETWORK let certPath = process.env.CERT_PATH if ((typeof certPath === 'undefined' && certPath == null) || certPath === '') { @@ -79,4 +80,6 @@ server.listen(port) server.on('error', onError) server.on('listening', onListening) -console.log('server: gateway-api | port:', port, '| balancer-network:', balancerNetwork); +console.log('server: gateway-api | port:', port) +console.log(' - balancer-network:', balancerNetwork) +console.log(' - terra-network:', terraNetwork) diff --git a/src/routes/balancer.route.js b/src/routes/balancer.route.js index a2faabf..c06d45d 100644 --- a/src/routes/balancer.route.js +++ b/src/routes/balancer.route.js @@ -11,8 +11,8 @@ import Balancer from '../services/balancer'; const debug = require('debug')('router') const router = express.Router() -const envConfig = getConfig() -const balancer = new Balancer(envConfig.balancer.BALANCER_NETWORK) +const ENV_CONFIG = getConfig() +const balancer = new Balancer(ENV_CONFIG.BALANCER.NETWORK) const denomMultiplier = 1e18 const swapMoreThanMaxPriceError = 'Swap price exceeds maxPrice' @@ -26,8 +26,8 @@ router.post('/', async (req, res) => { network: balancer.network, provider: balancer.provider.connection.url, exchangeProxy: balancer.exchangeProxy, - subgraphUrl: envConfig.balancer.REACT_APP_SUBGRAPH_URL, - gasLimit: envConfig.balancer.GAS_LIMIT, + subgraphUrl: balancer.subgraphUrl, + gasLimit: balancer.gasLimit, connection: true, timestamp: Date.now(), }) diff --git a/src/routes/eth.route.js b/src/routes/eth.route.js index d3200f9..f5a745b 100644 --- a/src/routes/eth.route.js +++ b/src/routes/eth.route.js @@ -9,8 +9,8 @@ import Balancer from '../services/balancer'; const router = express.Router() const envConfig = getConfig() -const eth = new Ethereum(envConfig.balancer.BALANCER_NETWORK) -const balancer = new Balancer(envConfig.balancer.BALANCER_NETWORK) +const eth = new Ethereum(envConfig.BALANCER.NETWORK) +const balancer = new Balancer(envConfig.BALANCER.NETWORK) const seperator = ',' const debug = require('debug')('router') diff --git a/src/routes/terra.route.js b/src/routes/terra.route.js index 1831848..d39878f 100644 --- a/src/routes/terra.route.js +++ b/src/routes/terra.route.js @@ -2,269 +2,268 @@ import express from 'express' import BigNumber from 'bignumber.js' -import { LCDClient, MnemonicKey, Coin, MsgSwap } from '@terra-money/terra.js' -import { getParamData, getSymbols, latency, reportConnectionError, statusMessages } from '../services/utils'; +import { MnemonicKey, Coin, MsgSwap } from '@terra-money/terra.js' +import { getParamData, latency, reportConnectionError, statusMessages } from '../services/utils'; +import { getConfig } from '../services/config'; -const router = express.Router(); -const debug = require('debug')('router') - -const TerraTokens = { - LUNA: { denom: 'uluna' }, - UST: { denom: 'uusd' }, - KRT: { denom: 'ukrw' }, - SDT: { denom: 'usdr' }, - MNT: { denom: 'umnt' }, -} +import Terra from '../services/terra'; -const getTerraSymbol = (denom) => { - let symbol - Object.keys(TerraTokens).forEach((item) => { - if (TerraTokens[item].denom === denom) { - symbol = item - } - }) - return symbol -} +const debug = require('debug')('router') +const router = express.Router(); +const ENV_CONFIG = getConfig() +const terra = new Terra(ENV_CONFIG.TERRA) +// constants +const network = terra.lcd.config.chainID const denomUnitMultiplier = BigNumber('1e+6') -const getTxAttributes = (attributes) => { - let attrib = {} - console.log(attributes) - attributes.forEach((item) => { - console.log(item) - attrib[item.key] = item.value - }) - return attrib -} - -// load environment config -const network = 'terra' -const lcdUrl = process.env.TERRA_LCD_URL; -const chain = process.env.TERRA_CHAIN; - -/** - * Connect to network - */ -const connect = () => { - const terra = new LCDClient({ - URL: lcdUrl, - chainID: chain, - }) - - terra.market.parameters().catch(() => { - throw new Error('Connection error') - }) - - return terra -} - -router.get('/', async (req, res) => { +router.post('/', async (req, res) => { /* - GET / + POST / */ - const terra = connect() - - const marketParams = await terra.market.parameters().catch((err) => { - reportConnectionError(res, err) - }) - res.status(200).json({ network: network, - chain: chain, + lcdUrl: terra.lcd.config.URL, + gasPrices: terra.lcd.config.gasPrices, + gasAdjustment: terra.lcd.config.gasAdjustment, connection: true, - timestamp: Date.now(), - market_params: marketParams + timestamp: Date.now() }) }) -router.get('/price', async (req, res) => { +router.post('/balances', async (req, res) => { /* - GET /price?trading_pair=LUNA-UST&trade_type=sell&amount=1.2345 + POST: + address:{{address}} */ const initTime = Date.now() - const keyFormat = ['trading_pair', 'trade_type', 'amount'] - - const paramData = getParamData(req.query, keyFormat) - const tradingPair = paramData.trading_pair - const requestAmount = paramData.amount - const amount = parseFloat(requestAmount) * denomUnitMultiplier - debug('params', req.params) - debug('paramData', paramData) - const terra = connect() - const exchangeRates = await terra.oracle.exchangeRates().catch((err) => { - reportConnectionError(res, err) - }); + const paramData = getParamData(req.body) + const address = paramData.address + debug(paramData) - const symbols = getSymbols(tradingPair) - const symbolsKeys = Object.keys(symbols) - let price + let balances = {} - if (symbolsKeys.includes('LUNA')) { - let targetSymbol - if (symbolsKeys.includes('UST')) { - targetSymbol = TerraTokens.UST.denom - } else if (symbolsKeys.includes('KRT')) { - targetSymbol = TerraTokens.KRT.denom - } else if (symbolsKeys.includes('SDT')) { - targetSymbol = TerraTokens.SDT.denom + try { + await terra.lcd.bank.balance(address).then(bal => { + bal.toArray().forEach(async (x) => { + const item = x.toData() + const denom = item.denom + const amount = item.amount / denomUnitMultiplier + const symbol = terra.tokens[denom].symbol + balances[symbol] = amount + }) + }) + res.status(200).json({ + network: network, + timestamp: initTime, + latency: latency(initTime, Date.now()), + balances: balances, + }) + } catch (err) { + let message + let reason + err.reason ? reason = err.reason : reason = statusMessages.operation_error + const isAxiosError = err.isAxiosError + if (isAxiosError) { + reason = err.response.status + message = err.response.statusText + } else { + message = err } - price = exchangeRates.get(targetSymbol) * amount - } else { - // get the current swap rate - const baseDenom = TerraTokens[symbols.base].denom - const quoteDenom = TerraTokens[symbols.quote].denom - - const offerCoin = new Coin(baseDenom, amount); - await terra.market.swapRate(offerCoin, quoteDenom).then(swapCoin => { - price = Number(swapCoin.amount) / denomUnitMultiplier - }).catch((err) => { - reportConnectionError(res, err) + res.status(500).json({ + error: reason, + message: message }) } - - const result = Object.assign(paramData, { - price: price, - timestamp: initTime, - latency: latency(initTime, Date.now()) - }) - res.status(200).json(result) }) -router.get('/balance', async (req, res) => { +router.post('/price', async (req, res) => { /* - GET: /balance?address=0x87A4...b120 + POST: + x-www-form-urlencoded: { + "base":"UST" + "quote":"KRT" + "amount":1 + } */ - const keyFormat = ['address'] - const paramData = getParamData(req.query, keyFormat) - const address = paramData.address - debug(paramData) + const initTime = Date.now() - const terra = connect() + const paramData = getParamData(req.body) + const baseToken = paramData.base + const quoteToken = paramData.quote + const amount = parseFloat(paramData.amount) + debug('paramData', paramData) - let balance = {} - let txSuccess, message + const symbols = [baseToken, quoteToken] + let exchangeRate, price try { - await terra.bank.balance(address).then(bal => { - bal.toArray().forEach((x) => { - const item = x.toData() - const denom = item.denom - const amount = item.amount / denomUnitMultiplier - const symbol = getTerraSymbol(denom) - balance[symbol] = amount + if (symbols.includes('LUNA')) { + const target = baseToken !== 'LUNA' ? baseToken : quoteToken + const denom = terra.getTokenDenom(target) + await terra.getExchangeRates(denom).then((rate) => { + price = exchangeRate * amount + }).catch((err) => { + reportConnectionError(res, err) }) - }) + } else { + // get the current swap rate + const offerDenom = terra.getTokenDenom(baseToken) + const swapDenom = terra.getTokenDenom(quoteToken) + + if ((typeof offerDenom === 'undefined' && offerDenom == null) || (typeof swapDenom === 'undefined' && swapDenom == null)) { + res.status(500).json({ + error: statusMessages.invalid_token_symbol, + message: { + base: baseToken, + quote: quoteToken + } + }) + return + } + + console.log('offerDenom, swapDenom', offerDenom, swapDenom) + + const offerCoin = new Coin(offerDenom, amount * denomUnitMultiplier); + await terra.lcd.market.swapRate(offerCoin, swapDenom).then(swapCoin => { + price = parseFloat(swapCoin.amount) / denomUnitMultiplier + debug('price', price) + }).catch((err) => { + reportConnectionError(res, err) + }) + } + debug('price', price) + + res.status(200).json( + { + network: network, + timestamp: initTime, + latency: latency(initTime, Date.now()), + base: baseToken, + quote: quoteToken, + amount: amount, + exchangeRate: exchangeRate, + price: price + } + ) } catch (err) { - txSuccess = false + let message + let reason + err.reason ? reason = err.reason : reason = statusMessages.operation_error const isAxiosError = err.isAxiosError if (isAxiosError) { - const status = err.response.status - const statusText = err.response.statusText - message = { error: statusText, status: status, data: err.response.data } + reason = err.response.status + message = err.response.statusText } else { - message = err.status + message = err } + res.status(500).json({ + error: reason, + message: message + }) } - - res.status(200).json({ - success: txSuccess, - address: address, - balance: balance, - timestamp: Date.now(), - message: message - }) }) router.post('/trade', async (req, res) => { /* POST: /trade data: { - "trading_pair":SDT-KRT - "trade_type": "buy" - "amount": "1.01" - "address": "0x...123" - "secret": "mysupersecret" + "base":"UST" + "quote":"KRT" + "trade_type":"buy" or "sell" + "amount":1 + "seeds": "mysupersecret" } */ - const keyFormat = ['trading_pair', 'trade_type', 'amount', 'address', 'secret'] - const paramData = getParamData(req.body, keyFormat) - const tradeType = paramData.tradeType - const secret = paramData.secret - debug(paramData) + const initTime = Date.now() + + const paramData = getParamData(req.body) + const baseToken = paramData.base + const quoteToken = paramData.quote + const tradeType = paramData.trade_type + const amount = parseFloat(paramData.amount) + const seeds = paramData.seeds + // debug(paramData) - const terra = connect() const mk = new MnemonicKey({ - mnemonic: secret, + mnemonic: seeds, }); - const wallet = terra.wallet(mk); + const wallet = terra.lcd.wallet(mk); const address = wallet.key.accAddress + debug(address) // get the current swap rate - const symbols = getSymbols(paramData.trading_pair) - debug('symbols', symbols) - const baseDenom = TerraTokens[symbols.base].denom - const quoteDenom = TerraTokens[symbols.quote].denom + const baseDenom = terra.getTokenDenom(baseToken) + const quoteDenom = terra.getTokenDenom(quoteToken) - let offerDenom, swapDenom, swapAmount - swapAmount = paramData.amount * denomUnitMultiplier + let offerDenom, swapDenom if (tradeType === 'sell') { offerDenom = baseDenom swapDenom = quoteDenom } else { + // get equivalent of amount in return offerDenom = quoteDenom swapDenom = baseDenom } - const offerCoin = new Coin(offerDenom, swapAmount); - debug('base', offerDenom, 'quote', swapDenom) + const swapAmount = amount * denomUnitMultiplier + const offerCoin = new Coin(offerDenom, swapAmount) // Create and Sign Transaction const swap = new MsgSwap(address, offerCoin, swapDenom); - const memo = 'tx: 0802...1520' - - let txSuccess, txAttributes, message + const testnetMemo = 'tx: 0xhb034' + const memo = network.toLowerCase().includes('columbus') ? '' : testnetMemo + let txAttributes try { const tx = await wallet.createAndSignTx({ msgs: [swap], memo: memo - }).then(tx => terra.tx.broadcast(tx)).then(result => { + }).then(tx => terra.lcd.tx.broadcast(tx)).then(result => { debug(`TX hash: ${result.txhash}`); - txSuccess = true const txHash = result.txhash const events = JSON.parse(result.raw_log)[0].events - console.log(events) const swap = events.find(obj => { return obj.type === 'swap' }) - txAttributes = getTxAttributes(swap.attributes) - - message = { - txHash: txHash - } + txAttributes = terra.getTxAttributes(swap.attributes) + const buyCoin = Coin.fromString(txAttributes.swap_coin).toDecCoin() + const sellCoin = Coin.fromString(txAttributes.offer) + // const feeCoin = Coin.fromString(txAttributes.swap_fee) + + res.status(200).json( + { + network: network, + timestamp: initTime, + latency: latency(initTime, Date.now()), + base: baseToken, + quote: quoteToken, + tradeType: tradeType, + amount: amount, + buy: buyCoin.amount / denomUnitMultiplier, + sell: sellCoin.amount / denomUnitMultiplier, + // fee: feeCoin.amount / denomUnitMultiplier, + txHash: txHash + } + ) }) } catch (err) { - txSuccess = false + let message + let reason + err.reason ? reason = err.reason : reason = statusMessages.operation_error const isAxiosError = err.isAxiosError if (isAxiosError) { - const status = err.response.status - const statusText = err.response.statusText - message = { error: statusText, status: status, data: err.response.data } + reason = err.response.status + message = err.response.statusText } else { - message = err.status + message = err } + res.status(500).json({ + error: reason, + message: message + }) } - - res.status(200).json({ - success: txSuccess, - timestamp: Date.now(), - buy: txAttributes.swap_coin, - sell: txAttributes.offer, - fee: txAttributes.swap_fee, - message: message - }) }) module.exports = router; diff --git a/src/services/balancer.js b/src/services/balancer.js index 561b8a2..7d88571 100644 --- a/src/services/balancer.js +++ b/src/services/balancer.js @@ -15,11 +15,12 @@ const MULTI = '0xeefba1e63905ef1d7acba5a8513c70307c1ce441'; export default class Balancer { constructor (network = 'kovan') { // network defaults to kovan - const providerUrl = ENV_CONFIG.ethereum.ETHEREUM_RPC_URL + const providerUrl = ENV_CONFIG.ETHEREUM.RPC_URL this.provider = new ethers.providers.JsonRpcProvider(providerUrl) - this.network = ENV_CONFIG.balancer.BALANCER_NETWORK - this.exchangeProxy = ENV_CONFIG.balancer.EXCHANGE_PROXY - this.gasLimit = ENV_CONFIG.balancer.GAS_LIMIT + this.network = ENV_CONFIG.BALANCER.NETWORK + this.subgraphUrl = ENV_CONFIG.BALANCER.REACT_APP_SUBGRAPH_URL + this.exchangeProxy = ENV_CONFIG.BALANCER.EXCHANGE_PROXY + this.gasLimit = ENV_CONFIG.BALANCER.GAS_LIMIT if (network === 'kovan') { // this.erc20Tokens = JSON.parse(fs.readFileSync('src/static/erc20_tokens_kovan.json')) diff --git a/src/services/config.js b/src/services/config.js index 16543ad..32cbc90 100644 --- a/src/services/config.js +++ b/src/services/config.js @@ -2,20 +2,24 @@ require('dotenv').config() export const getConfig = () => { const env = { - terra: { - TERRA_LCD_URL: process.env.TERRA_LCD_URL, - TERRA_CHAIN: process.env.TERRA_LCD_URL, + TERRA: { + LCD_URL: process.env.TERRA_LCD_URL, + NETWORK: process.env.TERRA_NETWORK, }, - balancer: { - BALANCER_NETWORK: process.env.BALANCER_NETWORK, + BALANCER: { + NETWORK: process.env.BALANCER_NETWORK, REACT_APP_SUBGRAPH_URL: process.env.REACT_APP_SUBGRAPH_URL, EXCHANGE_PROXY: process.env.EXCHANGE_PROXY, GAS_PRICE: parseInt(process.env.GAS_PRICE), GAS_LIMIT: parseInt(process.env.GAS_LIMIT) || 1200000, APPROVAL_GAS_LIMIT: parseInt(process.env.APPROVAL_GAS_LIMIT) || 100000 }, - ethereum: { - ETHEREUM_RPC_URL: process.env.ETHEREUM_RPC_URL, + ETHEREUM: { + RPC_URL: process.env.ETHEREUM_RPC_URL, + }, + uniswap: { + }, + celo: { } } return env diff --git a/src/services/eth.js b/src/services/eth.js index 0cafdd3..cd40e45 100644 --- a/src/services/eth.js +++ b/src/services/eth.js @@ -11,11 +11,11 @@ const ENV_CONFIG = config.getConfig() export default class Ethereum { constructor (network = 'kovan') { // network defaults to kovan - const providerUrl = ENV_CONFIG.ethereum.ETHEREUM_RPC_URL + const providerUrl = ENV_CONFIG.ETHEREUM.RPC_URL this.provider = new ethers.providers.JsonRpcProvider(providerUrl) - this.network = ENV_CONFIG.balancer.BALANCER_NETWORK - this.gasLimit = ENV_CONFIG.balancer.GAS_LIMIT - this.approvalGasLimit = ENV_CONFIG.balancer.APPROVAL_GAS_LIMIT + this.network = ENV_CONFIG.BALANCER.NETWORK + this.gasLimit = ENV_CONFIG.BALANCER.GAS_LIMIT + this.approvalGasLimit = ENV_CONFIG.BALANCER.APPROVAL_GAS_LIMIT if (network === 'kovan') { // for kovan testing only diff --git a/src/services/terra.js b/src/services/terra.js new file mode 100644 index 0000000..d1c3b40 --- /dev/null +++ b/src/services/terra.js @@ -0,0 +1,90 @@ +import { LCDClient } from '@terra-money/terra.js' +import { reportConnectionError, statusMessages } from '../services/utils'; + +require('dotenv').config() +const debug = require('debug')('router') +const config = require('../services/config') + +// constants +const ENV_CONFIG = config.getConfig() +const TERRA_TOKENS = { + uluna: { symbol: 'LUNA' }, + uusd: { symbol: 'UST' }, + ukrw: { symbol: 'KRT' }, + usdr: { symbol: 'SDT' }, + umnt: { symbol: 'MNT' }, +} + +export default class Terra { + constructor () { + const lcdUrl = ENV_CONFIG.TERRA.LCD_URL; + const network = ENV_CONFIG.TERRA.NETWORK; + + this.tokens = TERRA_TOKENS + + try { + this.lcd = new LCDClient({ + URL: lcdUrl, + chainID: network, + }) + + this.lcd.market.parameters().catch(() => { + throw new Error('Connection error') + }) + } catch (err) { + throw Error(`Connection failed: ${network}`) + } + } + + // get Token Denom + getTokenDenom (symbol) { + try { + let denom + Object.keys(TERRA_TOKENS).forEach((item) => { + if (TERRA_TOKENS[item].symbol === symbol) { + denom = item + } + }) + return denom + } catch (err) { + let reason + console.log(reason) + err.reason ? reason = err.reason : reason = 'error Terra Denom lookup' + return reason + } + } + + // get Token Symbol + getTokenSymbol (denom) { + try { + const symbol = TERRA_TOKENS[denom].symbol + return symbol + } catch (err) { + let reason + console.log(reason) + err.reason ? reason = err.reason : reason = 'error Terra Denom lookup' + return reason + } + } + + getTxAttributes (attributes) { + let attrib = {} + attributes.forEach((item) => { + attrib[item.key] = item.value + }) + return attrib + } + + async getExchangeRates (denom) { + try { + const exchangeRates = await this.lcd.oracle.exchangeRates() + debug('exchangeRates', exchangeRates) + return exchangeRates.get(denom) + } catch (err) { + let reason + console.log(reason) + err.reason ? reason = err.reason : reason = 'error Terra exchange rate lookup' + return reason + } + } +} diff --git a/src/services/utils.js b/src/services/utils.js index 4095936..58883de 100644 --- a/src/services/utils.js +++ b/src/services/utils.js @@ -8,6 +8,7 @@ export const statusMessages = { ssl_cert_invalid: 'Invalid SSL Certificate', operation_error: 'Operation Error', no_pool_available: 'No Pool Available', + invalid_token_symbol: 'Invalid Token Symbol', } export const latency = (startTime, endTime) => parseFloat((endTime - startTime) / 1000) @@ -45,6 +46,11 @@ export const getParamData = (data, format = null) => { return dataObject } +export const splitParamData = (param, separator = ',') => { + const dataArray = param.split(separator) + return dataArray +} + export const getSymbols = (tradingPair) => { const symbols = tradingPair.split('-') const baseQuotePair = { From 463440580936cb38f6973387f2283ac619672304 Mon Sep 17 00:00:00 2001 From: sdgoh Date: Fri, 13 Nov 2020 14:02:31 +0800 Subject: [PATCH 06/17] Update secret param, Add postman collection & environment --- .gitignore | 1 - src/routes/terra.route.js | 6 +- .../Gateway-Terra.postman_collection.json | 323 ++++++++++++++++++ test/postman/terra.postman_environment.json | 29 ++ 4 files changed, 355 insertions(+), 4 deletions(-) create mode 100644 test/postman/Gateway-Terra.postman_collection.json create mode 100644 test/postman/terra.postman_environment.json diff --git a/.gitignore b/.gitignore index ff3f6d6..8a4f589 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,6 @@ npm-debug.log dist/ # cert -certs/ *.pem *.srl *.key diff --git a/src/routes/terra.route.js b/src/routes/terra.route.js index d39878f..e085c0a 100644 --- a/src/routes/terra.route.js +++ b/src/routes/terra.route.js @@ -173,7 +173,7 @@ router.post('/trade', async (req, res) => { "quote":"KRT" "trade_type":"buy" or "sell" "amount":1 - "seeds": "mysupersecret" + "secret": "mysupersecret" } */ const initTime = Date.now() @@ -183,11 +183,11 @@ router.post('/trade', async (req, res) => { const quoteToken = paramData.quote const tradeType = paramData.trade_type const amount = parseFloat(paramData.amount) - const seeds = paramData.seeds + const secret = paramData.secret // debug(paramData) const mk = new MnemonicKey({ - mnemonic: seeds, + mnemonic: secret, }); const wallet = terra.lcd.wallet(mk); const address = wallet.key.accAddress diff --git a/test/postman/Gateway-Terra.postman_collection.json b/test/postman/Gateway-Terra.postman_collection.json new file mode 100644 index 0000000..9436113 --- /dev/null +++ b/test/postman/Gateway-Terra.postman_collection.json @@ -0,0 +1,323 @@ +{ + "info": { + "_postman_id": "3cca9e73-0e1f-4e4f-8973-87c985a43219", + "name": "Gateway-Terra", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "terra", + "item": [ + { + "name": "terra/balances", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "address", + "value": "{{address}}", + "type": "text" + } + ], + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "https://localhost:{{port}}/terra/balances", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "{{port}}", + "path": [ + "terra", + "balances" + ] + } + }, + "response": [] + }, + { + "name": "terra/price", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "base", + "value": "SDT", + "type": "text" + }, + { + "key": "quote", + "value": "KRT", + "type": "text" + }, + { + "key": "amount", + "value": "3", + "type": "text" + } + ] + }, + "url": { + "raw": "https://localhost:{{port}}/terra/price", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "{{port}}", + "path": [ + "terra", + "price" + ] + } + }, + "response": [ + { + "name": "{network}/quote", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:5000/{{network}}/quote/trading_pair/{{celo-cusd}}/amount/1", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "{{network}}", + "quote", + "trading_pair", + "{{celo-cusd}}", + "amount", + "1" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Security-Policy", + "value": "default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests" + }, + { + "key": "X-DNS-Prefetch-Control", + "value": "off" + }, + { + "key": "Expect-CT", + "value": "max-age=0" + }, + { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "key": "Strict-Transport-Security", + "value": "max-age=15552000; includeSubDomains" + }, + { + "key": "X-Download-Options", + "value": "noopen" + }, + { + "key": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "key": "X-Permitted-Cross-Domain-Policies", + "value": "none" + }, + { + "key": "Referrer-Policy", + "value": "no-referrer" + }, + { + "key": "X-XSS-Protection", + "value": "0" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "97" + }, + { + "key": "ETag", + "value": "W/\"61-Wemp9YmP9g/CsUFMa7Y5zK6SoLQ\"" + }, + { + "key": "Date", + "value": "Wed, 23 Sep 2020 18:07:26 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + } + ], + "cookie": [], + "body": "{\n \"timestamp\": 1600884444051,\n \"latency\": 2.542,\n \"trading_pair\": \"CELO-CUSD\",\n \"price\": 2.5435604641582747\n}" + } + ] + }, + { + "name": "terra/trade", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "base", + "value": "SDT", + "type": "text" + }, + { + "key": "quote", + "value": "KRT", + "type": "text" + }, + { + "key": "trade_type", + "value": "buy", + "type": "text" + }, + { + "key": "amount", + "value": "3", + "type": "text" + }, + { + "key": "secret", + "value": "{{secret}}", + "type": "text" + } + ] + }, + "url": { + "raw": "https://localhost:{{port}}/terra/trade", + "protocol": "https", + "host": [ + "localhost" + ], + "port": "{{port}}", + "path": [ + "terra", + "trade" + ] + } + }, + "response": [ + { + "name": "{network}/quote", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:5000/{{network}}/quote/trading_pair/{{celo-cusd}}/amount/1", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "5000", + "path": [ + "{{network}}", + "quote", + "trading_pair", + "{{celo-cusd}}", + "amount", + "1" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Security-Policy", + "value": "default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests" + }, + { + "key": "X-DNS-Prefetch-Control", + "value": "off" + }, + { + "key": "Expect-CT", + "value": "max-age=0" + }, + { + "key": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "key": "Strict-Transport-Security", + "value": "max-age=15552000; includeSubDomains" + }, + { + "key": "X-Download-Options", + "value": "noopen" + }, + { + "key": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "key": "X-Permitted-Cross-Domain-Policies", + "value": "none" + }, + { + "key": "Referrer-Policy", + "value": "no-referrer" + }, + { + "key": "X-XSS-Protection", + "value": "0" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "97" + }, + { + "key": "ETag", + "value": "W/\"61-Wemp9YmP9g/CsUFMa7Y5zK6SoLQ\"" + }, + { + "key": "Date", + "value": "Wed, 23 Sep 2020 18:07:26 GMT" + }, + { + "key": "Connection", + "value": "keep-alive" + } + ], + "cookie": [], + "body": "{\n \"timestamp\": 1600884444051,\n \"latency\": 2.542,\n \"trading_pair\": \"CELO-CUSD\",\n \"price\": 2.5435604641582747\n}" + } + ] + } + ], + "protocolProfileBehavior": {} + } + ], + "protocolProfileBehavior": {} +} \ No newline at end of file diff --git a/test/postman/terra.postman_environment.json b/test/postman/terra.postman_environment.json new file mode 100644 index 0000000..735927a --- /dev/null +++ b/test/postman/terra.postman_environment.json @@ -0,0 +1,29 @@ +{ + "id": "aebe7d1f-0e85-4441-8679-2ddc38d74350", + "name": "terra", + "values": [ + { + "key": "protocol", + "value": "terra", + "enabled": true + }, + { + "key": "port", + "value": "5000", + "enabled": true + }, + { + "key": "address", + "value": "myaddresss", + "enabled": true + }, + { + "key": "secret", + "value": "mysupersecret", + "enabled": true + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2020-11-13T06:00:14.142Z", + "_postman_exported_using": "Postman/7.35.0" +} \ No newline at end of file From f3d508ab7515d0b391f8cc995c2c388ca9229f8f Mon Sep 17 00:00:00 2001 From: sdgoh Date: Sat, 14 Nov 2020 18:46:49 +0800 Subject: [PATCH 07/17] Return token swap info in price & trade. Refactor route functions --- src/routes/terra.route.js | 137 ++++++++--------------------- src/services/terra.js | 178 +++++++++++++++++++++++++++++++++++--- 2 files changed, 203 insertions(+), 112 deletions(-) diff --git a/src/routes/terra.route.js b/src/routes/terra.route.js index e085c0a..580a906 100644 --- a/src/routes/terra.route.js +++ b/src/routes/terra.route.js @@ -1,12 +1,12 @@ 'use strict' import express from 'express' -import BigNumber from 'bignumber.js' -import { MnemonicKey, Coin, MsgSwap } from '@terra-money/terra.js' +import { MnemonicKey, Coin, MsgSwap, isTxError } from '@terra-money/terra.js' import { getParamData, latency, reportConnectionError, statusMessages } from '../services/utils'; import { getConfig } from '../services/config'; import Terra from '../services/terra'; +import e from 'express'; const debug = require('debug')('router') const router = express.Router(); @@ -15,7 +15,7 @@ const terra = new Terra(ENV_CONFIG.TERRA) // constants const network = terra.lcd.config.chainID -const denomUnitMultiplier = BigNumber('1e+6') +const denomUnitMultiplier = terra.denomUnitMultiplier router.post('/', async (req, res) => { /* @@ -84,6 +84,7 @@ router.post('/price', async (req, res) => { x-www-form-urlencoded: { "base":"UST" "quote":"KRT" + "trade_type":"buy" or "sell" "amount":1 } */ @@ -92,48 +93,21 @@ router.post('/price', async (req, res) => { const paramData = getParamData(req.body) const baseToken = paramData.base const quoteToken = paramData.quote + const tradeType = paramData.trade_type const amount = parseFloat(paramData.amount) debug('paramData', paramData) const symbols = [baseToken, quoteToken] - let exchangeRate, price + let exchangeRate try { - if (symbols.includes('LUNA')) { - const target = baseToken !== 'LUNA' ? baseToken : quoteToken - const denom = terra.getTokenDenom(target) - await terra.getExchangeRates(denom).then((rate) => { - price = exchangeRate * amount - }).catch((err) => { - reportConnectionError(res, err) - }) - } else { - // get the current swap rate - const offerDenom = terra.getTokenDenom(baseToken) - const swapDenom = terra.getTokenDenom(quoteToken) - - if ((typeof offerDenom === 'undefined' && offerDenom == null) || (typeof swapDenom === 'undefined' && swapDenom == null)) { - res.status(500).json({ - error: statusMessages.invalid_token_symbol, - message: { - base: baseToken, - quote: quoteToken - } - }) - return - } - - console.log('offerDenom, swapDenom', offerDenom, swapDenom) + await terra.getSwapRate(baseToken, quoteToken, amount, tradeType).then((rate) => { + exchangeRate = rate + }).catch((err) => { + reportConnectionError(res, err) + }) - const offerCoin = new Coin(offerDenom, amount * denomUnitMultiplier); - await terra.lcd.market.swapRate(offerCoin, swapDenom).then(swapCoin => { - price = parseFloat(swapCoin.amount) / denomUnitMultiplier - debug('price', price) - }).catch((err) => { - reportConnectionError(res, err) - }) - } - debug('price', price) + debug('exchangeRate', exchangeRate) res.status(200).json( { @@ -143,8 +117,11 @@ router.post('/price', async (req, res) => { base: baseToken, quote: quoteToken, amount: amount, - exchangeRate: exchangeRate, - price: price + tradeType: tradeType, + swapIn: exchangeRate.swapIn, + swapOut: exchangeRate.swapOut, + tobinTax: terra.tobinTax, + minSpread: terra.minSpread } ) } catch (err) { @@ -184,70 +161,30 @@ router.post('/trade', async (req, res) => { const tradeType = paramData.trade_type const amount = parseFloat(paramData.amount) const secret = paramData.secret - // debug(paramData) - const mk = new MnemonicKey({ - mnemonic: secret, - }); - const wallet = terra.lcd.wallet(mk); - const address = wallet.key.accAddress - debug(address) - - // get the current swap rate - const baseDenom = terra.getTokenDenom(baseToken) - const quoteDenom = terra.getTokenDenom(quoteToken) - - let offerDenom, swapDenom - if (tradeType === 'sell') { - offerDenom = baseDenom - swapDenom = quoteDenom - } else { - // get equivalent of amount in return - offerDenom = quoteDenom - swapDenom = baseDenom - } - - const swapAmount = amount * denomUnitMultiplier - const offerCoin = new Coin(offerDenom, swapAmount) - - // Create and Sign Transaction - const swap = new MsgSwap(address, offerCoin, swapDenom); - const testnetMemo = 'tx: 0xhb034' - const memo = network.toLowerCase().includes('columbus') ? '' : testnetMemo - let txAttributes + let tokenSwaps try { - const tx = await wallet.createAndSignTx({ - msgs: [swap], - memo: memo - }).then(tx => terra.lcd.tx.broadcast(tx)).then(result => { - debug(`TX hash: ${result.txhash}`); - const txHash = result.txhash - const events = JSON.parse(result.raw_log)[0].events - const swap = events.find(obj => { - return obj.type === 'swap' - }) - txAttributes = terra.getTxAttributes(swap.attributes) - const buyCoin = Coin.fromString(txAttributes.swap_coin).toDecCoin() - const sellCoin = Coin.fromString(txAttributes.offer) - // const feeCoin = Coin.fromString(txAttributes.swap_fee) - - res.status(200).json( - { - network: network, - timestamp: initTime, - latency: latency(initTime, Date.now()), - base: baseToken, - quote: quoteToken, - tradeType: tradeType, - amount: amount, - buy: buyCoin.amount / denomUnitMultiplier, - sell: sellCoin.amount / denomUnitMultiplier, - // fee: feeCoin.amount / denomUnitMultiplier, - txHash: txHash - } - ) + await terra.swapTokens(baseToken, quoteToken, amount, tradeType, secret).then((swap) => { + tokenSwaps = swap + }).catch((err) => { + reportConnectionError(res, err) }) + + const swapResult = { + network: network, + timestamp: initTime, + latency: latency(initTime, Date.now()), + base: baseToken, + tradeType: tradeType, + quote: quoteToken, + amount: amount + } + debug('tokenSwaps', tokenSwaps) + Object.assign(swapResult, tokenSwaps); + res.status(200).json( + swapResult + ) } catch (err) { let message let reason diff --git a/src/services/terra.js b/src/services/terra.js index d1c3b40..14dbf8d 100644 --- a/src/services/terra.js +++ b/src/services/terra.js @@ -1,5 +1,5 @@ -import { LCDClient } from '@terra-money/terra.js' -import { reportConnectionError, statusMessages } from '../services/utils'; +import { LCDClient, Coin, MsgSwap, MnemonicKey, isTxError } from '@terra-money/terra.js' +import BigNumber from 'bignumber.js' require('dotenv').config() const debug = require('debug')('router') @@ -14,25 +14,44 @@ const TERRA_TOKENS = { usdr: { symbol: 'SDT' }, umnt: { symbol: 'MNT' }, } +const DENOM_UNIT = BigNumber('1e+6') +const TOBIN_TAX = 0.25 +const MIN_SPREAD = 2.0 +const TESTNET_MEMO = '' export default class Terra { constructor () { - const lcdUrl = ENV_CONFIG.TERRA.LCD_URL; - const network = ENV_CONFIG.TERRA.NETWORK; - + this.lcdUrl = ENV_CONFIG.TERRA.LCD_URL; + this.network = ENV_CONFIG.TERRA.NETWORK; this.tokens = TERRA_TOKENS + this.denomUnitMultiplier = DENOM_UNIT + this.tobinTax = TOBIN_TAX + this.minSpread = MIN_SPREAD try { - this.lcd = new LCDClient({ - URL: lcdUrl, - chainID: network, - }) + this.lcd = this.connect() this.lcd.market.parameters().catch(() => { throw new Error('Connection error') }) } catch (err) { - throw Error(`Connection failed: ${network}`) + throw Error(`Connection failed: ${this.network}`) + } + } + + // connect Terra LCD + connect () { + try { + const lcd = new LCDClient({ + URL: this.lcdUrl, + chainID: this.network, + }) + return lcd + } catch (err) { + let reason + console.log(reason) + err.reason ? reason = err.reason : reason = 'error Terra LCD connect' + return reason } } @@ -75,10 +94,9 @@ export default class Terra { return attrib } - async getExchangeRates (denom) { + async getExchangeRate (denom) { try { const exchangeRates = await this.lcd.oracle.exchangeRates() - debug('exchangeRates', exchangeRates) return exchangeRates.get(denom) } catch (err) { let reason @@ -87,4 +105,140 @@ export default class Terra { return reason } } + + // get Terra Swap Rate + async getSwapRate (baseToken, quoteToken, amount, tradeType) { + try { + let exchangeRate + let swaps = {} + const offerDenom = this.getTokenDenom(baseToken) + const swapDenom = this.getTokenDenom(quoteToken) + + const offerCoin = new Coin(offerDenom, amount * DENOM_UNIT); + await this.lcd.market.swapRate(offerCoin, swapDenom).then(swapCoin => { + exchangeRate = { + amount: parseFloat(swapCoin.amount) / DENOM_UNIT, + token: quoteToken + } + }) + + if (tradeType.toLowerCase() === 'buy') { + swaps.swapIn = { + amount: amount, + token: baseToken + } + swaps.swapOut = exchangeRate + } else { + // sell + swaps.swapIn = exchangeRate + swaps.swapOut = { + amount: amount, + token: baseToken + } + } + return swaps + } catch (err) { + debug() + let reason + console.log(reason) + err.reason ? reason = err.reason : reason = 'error swap rate lookup' + return reason + } + } + + // Swap tokens + async swapTokens (baseToken, quoteToken, amount, tradeType, secret) { + try { + // connect to lcd + const lcd = this.connect() + + const mk = new MnemonicKey({ + mnemonic: secret, + }); + let wallet + try { + wallet = lcd.wallet(mk); + } catch (err) { + throw Error('Wallet access error') + } + + const address = wallet.key.accAddress + + // get the current swap rate + const baseDenom = this.getTokenDenom(baseToken) + const quoteDenom = this.getTokenDenom(quoteToken) + + let offerDenom, swapDenom + let swaps + let txAttributes + let tokenSwap = {} + + if (tradeType.toLowerCase() === 'sell') { + offerDenom = baseDenom + swapDenom = quoteDenom + } else { + offerDenom = quoteDenom + swapDenom = baseDenom + } + + await this.getSwapRate(baseToken, quoteToken, amount, tradeType, secret).then((rate) => { + swaps = rate + }) + + const offerAmount = swaps.swapOut.amount * DENOM_UNIT + const offerCoin = new Coin(offerDenom, offerAmount) + + // Create and Sign Transaction + const msgSwap = new MsgSwap(address, offerCoin, swapDenom); + const memo = lcd.config.chainID.toLowerCase().includes('columbus') ? '' : TESTNET_MEMO + + const tx = await wallet.createAndSignTx({ + msgs: [msgSwap], + memo: memo + }) + + await lcd.tx.broadcast(tx).then((txResult) => { + const swapSucces = !isTxError(txResult) + if (swapSucces) { + tokenSwap.txSuccess = swapSucces + } else { + tokenSwap.txSuccess = !swapSucces + throw new Error(`encountered an error while running the transaction: ${txResult.code} ${txResult.codespace}`); + } + + // check for events from the first message + // debug('event first message', txResult.logs[0].eventsByType.store_code) + + const txHash = txResult.txhash + const events = JSON.parse(txResult.raw_log)[0].events + const swap = events.find(obj => { + return obj.type === 'swap' + }) + txAttributes = this.getTxAttributes(swap.attributes) + const offer = Coin.fromString(txAttributes.offer) + const ask = Coin.fromString(txAttributes.swap_coin) + const fee = Coin.fromString(txAttributes.swap_fee) + + tokenSwap.swapIn = { + amount: parseFloat(ask.amount) / DENOM_UNIT, + token: TERRA_TOKENS[ask.denom].symbol + } + tokenSwap.swapOut = { + amount: parseFloat(offer.amount) / DENOM_UNIT, + token: TERRA_TOKENS[offer.denom].symbol + } + tokenSwap.fee = { + amount: parseFloat(fee.amount) / DENOM_UNIT, + token: TERRA_TOKENS[fee.denom].symbol + } + tokenSwap.txHash = txHash + }) + return tokenSwap + } catch (err) { + let reason + console.log(reason) + err.reason ? reason = err.reason : reason = 'error Terra tokens swap' + return { txSuccess: false, message: reason } + } + } } From bf51849072617de6ee7bf449b24c99bf09eeff4b Mon Sep 17 00:00:00 2001 From: sdgoh Date: Sat, 14 Nov 2020 19:01:45 +0800 Subject: [PATCH 08/17] Add trade_type to price endpoint param --- test/postman/Gateway-Terra.postman_collection.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/postman/Gateway-Terra.postman_collection.json b/test/postman/Gateway-Terra.postman_collection.json index 9436113..40866b8 100644 --- a/test/postman/Gateway-Terra.postman_collection.json +++ b/test/postman/Gateway-Terra.postman_collection.json @@ -61,6 +61,11 @@ "value": "KRT", "type": "text" }, + { + "key": "trade_type", + "value": "buy", + "type": "text" + }, { "key": "amount", "value": "3", From a43ef33ce1d3842e7aa89e41d6dd782a03dba77c Mon Sep 17 00:00:00 2001 From: sdgoh Date: Sun, 15 Nov 2020 23:13:33 +0800 Subject: [PATCH 09/17] (fix) price look up error. --- src/routes/terra.route.js | 23 ++++-- src/services/terra.js | 170 ++++++++++++++++++++++++++------------ 2 files changed, 136 insertions(+), 57 deletions(-) diff --git a/src/routes/terra.route.js b/src/routes/terra.route.js index 580a906..04a5951 100644 --- a/src/routes/terra.route.js +++ b/src/routes/terra.route.js @@ -118,10 +118,18 @@ router.post('/price', async (req, res) => { quote: quoteToken, amount: amount, tradeType: tradeType, - swapIn: exchangeRate.swapIn, - swapOut: exchangeRate.swapOut, - tobinTax: terra.tobinTax, - minSpread: terra.minSpread + price: { + amount: exchangeRate.price.amount, + token: exchangeRate.price.token + }, + cost: { + amount: exchangeRate.cost.amount, + token: exchangeRate.cost.token + } + // swapIn: exchangeRate.swapIn, + // swapOut: exchangeRate.swapOut, + // tobinTax: terra.tobinTax, + // minSpread: terra.minSpread } ) } catch (err) { @@ -160,12 +168,14 @@ router.post('/trade', async (req, res) => { const quoteToken = paramData.quote const tradeType = paramData.trade_type const amount = parseFloat(paramData.amount) + const gasPrice = parseFloat(paramData.gas_price) || null + const gasAdjustment = paramData.gas_adjustment || null const secret = paramData.secret let tokenSwaps try { - await terra.swapTokens(baseToken, quoteToken, amount, tradeType, secret).then((swap) => { + await terra.swapTokens(baseToken, quoteToken, amount, tradeType, gasPrice, gasAdjustment, secret).then((swap) => { tokenSwaps = swap }).catch((err) => { reportConnectionError(res, err) @@ -178,7 +188,8 @@ router.post('/trade', async (req, res) => { base: baseToken, tradeType: tradeType, quote: quoteToken, - amount: amount + amount: amount, + // gasPrice: gasPrice } debug('tokenSwaps', tokenSwaps) Object.assign(swapResult, tokenSwaps); diff --git a/src/services/terra.js b/src/services/terra.js index 14dbf8d..e9630f0 100644 --- a/src/services/terra.js +++ b/src/services/terra.js @@ -15,9 +15,10 @@ const TERRA_TOKENS = { umnt: { symbol: 'MNT' }, } const DENOM_UNIT = BigNumber('1e+6') -const TOBIN_TAX = 0.25 -const MIN_SPREAD = 2.0 -const TESTNET_MEMO = '' +const TOBIN_TAX = 0.25 // a Tobin Tax (set at 0.25%) for spot-converting Terra<>Terra swaps +const MIN_SPREAD = 2.0 // a minimum spread (set at 2%) for Terra<>Luna swaps +const GAS_ADJUSTMENT = 1.5 +const TESTNET_MEMO = '0xhb34' export default class Terra { constructor () { @@ -85,7 +86,7 @@ export default class Terra { return reason } } - + getTxAttributes (attributes) { let attrib = {} attributes.forEach((item) => { @@ -94,6 +95,18 @@ export default class Terra { return attrib } + async getEstimateFee (tx) { + try { + const fee = await this.lcd.tx.estimateFee(tx) + return fee + } catch (err) { + let reason + console.log(reason) + err.reason ? reason = err.reason : reason = 'error Terra estimate fee lookup' + return reason + } + } + async getExchangeRate (denom) { try { const exchangeRates = await this.lcd.oracle.exchangeRates() @@ -109,36 +122,48 @@ export default class Terra { // get Terra Swap Rate async getSwapRate (baseToken, quoteToken, amount, tradeType) { try { - let exchangeRate + let exchangeRate, offerDenom, swapDenom, cost let swaps = {} - const offerDenom = this.getTokenDenom(baseToken) - const swapDenom = this.getTokenDenom(quoteToken) - - const offerCoin = new Coin(offerDenom, amount * DENOM_UNIT); - await this.lcd.market.swapRate(offerCoin, swapDenom).then(swapCoin => { - exchangeRate = { - amount: parseFloat(swapCoin.amount) / DENOM_UNIT, - token: quoteToken - } - }) - if (tradeType.toLowerCase() === 'buy') { - swaps.swapIn = { - amount: amount, - token: baseToken - } - swaps.swapOut = exchangeRate + if (tradeType.toLowerCase() === 'sell') { + // sell base + offerDenom = this.getTokenDenom(baseToken) + swapDenom = this.getTokenDenom(quoteToken) + + const offerCoin = new Coin(offerDenom, amount * DENOM_UNIT); + await this.lcd.market.swapRate(offerCoin, swapDenom).then(swapCoin => { + exchangeRate = { + amount: (swapCoin.amount / DENOM_UNIT) / amount, + token: quoteToken + } + cost = { + amount: amount * exchangeRate.amount, + token: quoteToken + } + }) } else { - // sell - swaps.swapIn = exchangeRate - swaps.swapOut = { - amount: amount, - token: baseToken - } + // buy base + offerDenom = this.getTokenDenom(quoteToken) + swapDenom = this.getTokenDenom(baseToken) + + const offerCoin = new Coin(offerDenom, 1 * DENOM_UNIT); + await this.lcd.market.swapRate(offerCoin, swapDenom).then(swapCoin => { + exchangeRate = { + amount: (amount / swapCoin.amount * DENOM_UNIT) / amount, // adjusted amount + token: quoteToken + } + cost = { + amount: amount * exchangeRate.amount, + token: quoteToken + } + }) } + + swaps.price = exchangeRate + swaps.cost = cost + debug('swaps', swaps) return swaps } catch (err) { - debug() let reason console.log(reason) err.reason ? reason = err.reason : reason = 'error swap rate lookup' @@ -147,7 +172,8 @@ export default class Terra { } // Swap tokens - async swapTokens (baseToken, quoteToken, amount, tradeType, secret) { + async swapTokens (baseToken, quoteToken, amount, tradeType, gasPrice, gasAdjustment, secret) { + let swapResult try { // connect to lcd const lcd = this.connect() @@ -169,8 +195,7 @@ export default class Terra { const quoteDenom = this.getTokenDenom(quoteToken) let offerDenom, swapDenom - let swaps - let txAttributes + let swaps, txAttributes let tokenSwap = {} if (tradeType.toLowerCase() === 'sell') { @@ -185,30 +210,56 @@ export default class Terra { swaps = rate }) - const offerAmount = swaps.swapOut.amount * DENOM_UNIT - const offerCoin = new Coin(offerDenom, offerAmount) + const offerAmount = parseInt(swaps.cost.amount * DENOM_UNIT) + const offerCoin = new Coin(offerDenom, offerAmount) + debug('offerCoin', offerCoin, offerAmount) // Create and Sign Transaction const msgSwap = new MsgSwap(address, offerCoin, swapDenom); const memo = lcd.config.chainID.toLowerCase().includes('columbus') ? '' : TESTNET_MEMO - const tx = await wallet.createAndSignTx({ - msgs: [msgSwap], - memo: memo - }) + debug('msgSwap', msgSwap) + let txOptions + if (gasPrice !== null && gasPrice !== null) { // ignore gasAdjustment when gasPrice is not set + txOptions = { + msgs: [msgSwap], + gasPrices: { [offerDenom]: gasPrice }, + gasAdjustment: gasAdjustment || GAS_ADJUSTMENT, + memo: memo + } + } else { + txOptions = { + msgs: [msgSwap], + memo: memo + } + } + + // const fee = await this.getEstimateFee(tx) + // const estimatedGas = fee.gas + // const estimatedFee = fee.amount._coins[offerDenom] + // const estimatedFeeDenom = estimatedFee.denom + // const estimatedFeeAmount = estimatedFee.amount * DENOM_UNIT + // debug('estimatedGas/estimatedFeeDenom/estimatedFeeAmount', estimatedGas, estimatedFee) + // debug(estimatedFeeDenom, estimatedFeeAmount) + + // debug('offerDenom', offerDenom, 'gasPrice', gasPrice, 'gasAdjustment', gasAdjustment, 'options', txOptions) + + await wallet.createAndSignTx(txOptions).then(tx => lcd.tx.broadcast(tx)).then((txResult) => { + swapResult = txResult + // debug('txResult', txResult) - await lcd.tx.broadcast(tx).then((txResult) => { - const swapSucces = !isTxError(txResult) - if (swapSucces) { - tokenSwap.txSuccess = swapSucces + const swapSuccess = !isTxError(txResult) + if (swapSuccess) { + tokenSwap.txSuccess = swapSuccess } else { - tokenSwap.txSuccess = !swapSucces + tokenSwap.txSuccess = !swapSuccess throw new Error(`encountered an error while running the transaction: ${txResult.code} ${txResult.codespace}`); } // check for events from the first message // debug('event first message', txResult.logs[0].eventsByType.store_code) + // debug('swapSuccess', swapSuccess) const txHash = txResult.txhash const events = JSON.parse(txResult.raw_log)[0].events const swap = events.find(obj => { @@ -219,25 +270,42 @@ export default class Terra { const ask = Coin.fromString(txAttributes.swap_coin) const fee = Coin.fromString(txAttributes.swap_fee) - tokenSwap.swapIn = { - amount: parseFloat(ask.amount) / DENOM_UNIT, - token: TERRA_TOKENS[ask.denom].symbol - } - tokenSwap.swapOut = { - amount: parseFloat(offer.amount) / DENOM_UNIT, - token: TERRA_TOKENS[offer.denom].symbol + debug('txAttributes', txAttributes) + + if (tradeType.toLowerCase() === 'sell') { + tokenSwap.expectedOut = { + amount: parseFloat(ask.amount) / DENOM_UNIT, + token: TERRA_TOKENS[ask.denom].symbol + } + tokenSwap.price = { + amount: parseFloat(offer.amount) / DENOM_UNIT, + token: TERRA_TOKENS[offer.denom].symbol + } + } else { + tokenSwap.expectedIn = { + amount: parseFloat(ask.amount) / DENOM_UNIT, + token: TERRA_TOKENS[ask.denom].symbol + } + tokenSwap.price = { + amount: parseFloat(offer.amount) / DENOM_UNIT, + token: TERRA_TOKENS[offer.denom].symbol + } } tokenSwap.fee = { amount: parseFloat(fee.amount) / DENOM_UNIT, token: TERRA_TOKENS[fee.denom].symbol } + // tokenSwap.estimatedFee = { + // amount: estimatedFeeAmount, + // token: + // }, tokenSwap.txHash = txHash }) return tokenSwap } catch (err) { let reason - console.log(reason) - err.reason ? reason = err.reason : reason = 'error Terra tokens swap' + console.log(reason, typeof result) + err.reason ? reason = err.reason : reason = swapResult return { txSuccess: false, message: reason } } } From 2f7eaefd6e41e80781d189b4b44da92574b44975 Mon Sep 17 00:00:00 2001 From: sdgoh Date: Sun, 15 Nov 2020 23:20:40 +0800 Subject: [PATCH 10/17] (fix) Remove token info from price lookup --- src/routes/terra.route.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/routes/terra.route.js b/src/routes/terra.route.js index 04a5951..a6184b0 100644 --- a/src/routes/terra.route.js +++ b/src/routes/terra.route.js @@ -118,14 +118,8 @@ router.post('/price', async (req, res) => { quote: quoteToken, amount: amount, tradeType: tradeType, - price: { - amount: exchangeRate.price.amount, - token: exchangeRate.price.token - }, - cost: { - amount: exchangeRate.cost.amount, - token: exchangeRate.cost.token - } + price: exchangeRate.price.amount, + cost: exchangeRate.cost.amount // swapIn: exchangeRate.swapIn, // swapOut: exchangeRate.swapOut, // tobinTax: terra.tobinTax, From 8fcc13d444c4df0aa18d6470c1348c2c2d1b4bee Mon Sep 17 00:00:00 2001 From: sdgoh Date: Mon, 16 Nov 2020 01:50:01 +0800 Subject: [PATCH 11/17] (fix) incorrect sell trade offer amount --- src/routes/terra.route.js | 8 ++------ src/services/terra.js | 38 +++++++++++++++----------------------- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/src/routes/terra.route.js b/src/routes/terra.route.js index a6184b0..62285e1 100644 --- a/src/routes/terra.route.js +++ b/src/routes/terra.route.js @@ -107,7 +107,7 @@ router.post('/price', async (req, res) => { reportConnectionError(res, err) }) - debug('exchangeRate', exchangeRate) + // debug('exchangeRate', exchangeRate) res.status(200).json( { @@ -120,10 +120,6 @@ router.post('/price', async (req, res) => { tradeType: tradeType, price: exchangeRate.price.amount, cost: exchangeRate.cost.amount - // swapIn: exchangeRate.swapIn, - // swapOut: exchangeRate.swapOut, - // tobinTax: terra.tobinTax, - // minSpread: terra.minSpread } ) } catch (err) { @@ -185,7 +181,7 @@ router.post('/trade', async (req, res) => { amount: amount, // gasPrice: gasPrice } - debug('tokenSwaps', tokenSwaps) + // debug('tokenSwaps', tokenSwaps) Object.assign(swapResult, tokenSwaps); res.status(200).json( swapResult diff --git a/src/services/terra.js b/src/services/terra.js index e9630f0..6b313a0 100644 --- a/src/services/terra.js +++ b/src/services/terra.js @@ -122,7 +122,7 @@ export default class Terra { // get Terra Swap Rate async getSwapRate (baseToken, quoteToken, amount, tradeType) { try { - let exchangeRate, offerDenom, swapDenom, cost + let exchangeRate, offerDenom, swapDenom, cost, offer let swaps = {} if (tradeType.toLowerCase() === 'sell') { @@ -132,6 +132,7 @@ export default class Terra { const offerCoin = new Coin(offerDenom, amount * DENOM_UNIT); await this.lcd.market.swapRate(offerCoin, swapDenom).then(swapCoin => { + offer = { amount: amount } exchangeRate = { amount: (swapCoin.amount / DENOM_UNIT) / amount, token: quoteToken @@ -156,12 +157,14 @@ export default class Terra { amount: amount * exchangeRate.amount, token: quoteToken } + offer = { amount: cost.amount } }) } + swaps.offer = offer swaps.price = exchangeRate swaps.cost = cost - debug('swaps', swaps) + // debug('swaps', swaps) return swaps } catch (err) { let reason @@ -210,7 +213,7 @@ export default class Terra { swaps = rate }) - const offerAmount = parseInt(swaps.cost.amount * DENOM_UNIT) + const offerAmount = parseInt(swaps.offer.amount * DENOM_UNIT) const offerCoin = new Coin(offerDenom, offerAmount) debug('offerCoin', offerCoin, offerAmount) @@ -218,7 +221,7 @@ export default class Terra { const msgSwap = new MsgSwap(address, offerCoin, swapDenom); const memo = lcd.config.chainID.toLowerCase().includes('columbus') ? '' : TESTNET_MEMO - debug('msgSwap', msgSwap) + // debug('msgSwap', msgSwap) let txOptions if (gasPrice !== null && gasPrice !== null) { // ignore gasAdjustment when gasPrice is not set txOptions = { @@ -270,26 +273,15 @@ export default class Terra { const ask = Coin.fromString(txAttributes.swap_coin) const fee = Coin.fromString(txAttributes.swap_fee) - debug('txAttributes', txAttributes) + // debug('txAttributes', txAttributes) - if (tradeType.toLowerCase() === 'sell') { - tokenSwap.expectedOut = { - amount: parseFloat(ask.amount) / DENOM_UNIT, - token: TERRA_TOKENS[ask.denom].symbol - } - tokenSwap.price = { - amount: parseFloat(offer.amount) / DENOM_UNIT, - token: TERRA_TOKENS[offer.denom].symbol - } - } else { - tokenSwap.expectedIn = { - amount: parseFloat(ask.amount) / DENOM_UNIT, - token: TERRA_TOKENS[ask.denom].symbol - } - tokenSwap.price = { - amount: parseFloat(offer.amount) / DENOM_UNIT, - token: TERRA_TOKENS[offer.denom].symbol - } + tokenSwap.expectedIn = { + amount: parseFloat(offer.amount) / DENOM_UNIT, + token: TERRA_TOKENS[offer.denom].symbol + } + tokenSwap.expectedOut = { + amount: parseFloat(ask.amount) / DENOM_UNIT, + token: TERRA_TOKENS[ask.denom].symbol } tokenSwap.fee = { amount: parseFloat(fee.amount) / DENOM_UNIT, From 9037859dfae4e6444e35ae5b43880e6ebe30f8c8 Mon Sep 17 00:00:00 2001 From: sdgoh Date: Sat, 21 Nov 2020 00:36:19 +0800 Subject: [PATCH 12/17] Add gas price preset, return tx fee in price --- .env.example | 8 +++- src/routes/terra.route.js | 5 +-- src/services/config.js | 7 ++- src/services/terra.js | 92 ++++++++++++++++++++++----------------- 4 files changed, 67 insertions(+), 45 deletions(-) diff --git a/.env.example b/.env.example index 958a19a..0790df5 100644 --- a/.env.example +++ b/.env.example @@ -13,8 +13,12 @@ IP_WHITELIST= # Celo # Terra -TERRA_LCD_URL=https://tequila-lcd.terra.dev -TERRA_NETWORK=tequila-0004 +# - mainnet: https://lcd.terra.dev +# - mainnet chain: columbus-4 +# - testnet: https://tequila-lcd.terra.dev +# - testnet chain: tequila-0004 +TERRA_LCD_URL={testnet_lcd_url} +TERRA_NETWORK={testnet_chain_id} # Balancer # - network: mainnet, kovan, etc diff --git a/src/routes/terra.route.js b/src/routes/terra.route.js index 62285e1..5f73ba1 100644 --- a/src/routes/terra.route.js +++ b/src/routes/terra.route.js @@ -1,12 +1,10 @@ 'use strict' import express from 'express' -import { MnemonicKey, Coin, MsgSwap, isTxError } from '@terra-money/terra.js' import { getParamData, latency, reportConnectionError, statusMessages } from '../services/utils'; import { getConfig } from '../services/config'; import Terra from '../services/terra'; -import e from 'express'; const debug = require('debug')('router') const router = express.Router(); @@ -119,7 +117,8 @@ router.post('/price', async (req, res) => { amount: amount, tradeType: tradeType, price: exchangeRate.price.amount, - cost: exchangeRate.cost.amount + cost: exchangeRate.cost.amount, + txFee: exchangeRate.txFee.amount, } ) } catch (err) { diff --git a/src/services/config.js b/src/services/config.js index 32cbc90..e7ffe2e 100644 --- a/src/services/config.js +++ b/src/services/config.js @@ -1,10 +1,13 @@ require('dotenv').config() +const defaultMemo = '' + export const getConfig = () => { const env = { TERRA: { LCD_URL: process.env.TERRA_LCD_URL, NETWORK: process.env.TERRA_NETWORK, + MEMO: process.env.TERRA_MEMO || defaultMemo }, BALANCER: { NETWORK: process.env.BALANCER_NETWORK, @@ -12,12 +15,14 @@ export const getConfig = () => { EXCHANGE_PROXY: process.env.EXCHANGE_PROXY, GAS_PRICE: parseInt(process.env.GAS_PRICE), GAS_LIMIT: parseInt(process.env.GAS_LIMIT) || 1200000, - APPROVAL_GAS_LIMIT: parseInt(process.env.APPROVAL_GAS_LIMIT) || 100000 + APPROVAL_GAS_LIMIT: parseInt(process.env.APPROVAL_GAS_LIMIT) || 100000, + MEMO: process.env.BALANCER_MEMO || defaultMemo }, ETHEREUM: { RPC_URL: process.env.ETHEREUM_RPC_URL, }, uniswap: { + }, celo: { } diff --git a/src/services/terra.js b/src/services/terra.js index 6b313a0..4733add 100644 --- a/src/services/terra.js +++ b/src/services/terra.js @@ -1,9 +1,10 @@ -import { LCDClient, Coin, MsgSwap, MnemonicKey, isTxError } from '@terra-money/terra.js' +import { LCDClient, Coin, MsgSwap, StdTx, StdFee, Dec, MnemonicKey, isTxError, Coins } from '@terra-money/terra.js' import BigNumber from 'bignumber.js' require('dotenv').config() const debug = require('debug')('router') const config = require('../services/config') +const dummyAccount = require('../static/terraTestAccount') // constants const ENV_CONFIG = config.getConfig() @@ -15,10 +16,11 @@ const TERRA_TOKENS = { umnt: { symbol: 'MNT' }, } const DENOM_UNIT = BigNumber('1e+6') -const TOBIN_TAX = 0.25 // a Tobin Tax (set at 0.25%) for spot-converting Terra<>Terra swaps -const MIN_SPREAD = 2.0 // a minimum spread (set at 2%) for Terra<>Luna swaps +const TOBIN_TAX = 0.0025 // a Tobin Tax (set at 0.25%) for spot-converting Terra<>Terra swaps +const MIN_SPREAD = 0.02 // a minimum spread (set at 2%) for Terra<>Luna swaps +const GAS_PRICE = 0.02 const GAS_ADJUSTMENT = 1.5 -const TESTNET_MEMO = '0xhb34' +const MEMO = ENV_CONFIG.TERRA.MEMO export default class Terra { constructor () { @@ -35,6 +37,9 @@ export default class Terra { this.lcd.market.parameters().catch(() => { throw new Error('Connection error') }) + // set gas & fee + this.lcd.config.gasAdjustment = GAS_ADJUSTMENT + this.lcd.config.gasPrice = GAS_PRICE } catch (err) { throw Error(`Connection failed: ${this.network}`) } @@ -47,6 +52,8 @@ export default class Terra { URL: this.lcdUrl, chainID: this.network, }) + lcd.config.gasAdjustment = GAS_ADJUSTMENT + lcd.config.gasPrice = GAS_PRICE return lcd } catch (err) { let reason @@ -119,10 +126,30 @@ export default class Terra { } } + async getTxFee () { + try { + const lunaFee = GAS_PRICE * GAS_ADJUSTMENT + let feeList = { uluna: lunaFee } + await this.lcd.oracle.exchangeRates().then(rates => { + Object.keys(rates._coins).forEach(key => { + feeList[key] = rates._coins[key].amount * lunaFee + }) + }) + debug('lunaFee', lunaFee, feeList) + + return feeList + } catch (err) { + let reason + console.log(reason) + err.reason ? reason = err.reason : reason = 'error Terra exchange rate lookup' + return reason + } + } + // get Terra Swap Rate async getSwapRate (baseToken, quoteToken, amount, tradeType) { try { - let exchangeRate, offerDenom, swapDenom, cost, offer + let exchangeRate, offerCoin, offerDenom, swapDenom, cost, costAmount, offer let swaps = {} if (tradeType.toLowerCase() === 'sell') { @@ -130,15 +157,16 @@ export default class Terra { offerDenom = this.getTokenDenom(baseToken) swapDenom = this.getTokenDenom(quoteToken) - const offerCoin = new Coin(offerDenom, amount * DENOM_UNIT); + offerCoin = new Coin(offerDenom, amount * DENOM_UNIT); await this.lcd.market.swapRate(offerCoin, swapDenom).then(swapCoin => { offer = { amount: amount } exchangeRate = { amount: (swapCoin.amount / DENOM_UNIT) / amount, token: quoteToken } + costAmount = amount * exchangeRate.amount cost = { - amount: amount * exchangeRate.amount, + amount: costAmount, token: quoteToken } }) @@ -147,24 +175,32 @@ export default class Terra { offerDenom = this.getTokenDenom(quoteToken) swapDenom = this.getTokenDenom(baseToken) - const offerCoin = new Coin(offerDenom, 1 * DENOM_UNIT); + offerCoin = new Coin(offerDenom, 1 * DENOM_UNIT); await this.lcd.market.swapRate(offerCoin, swapDenom).then(swapCoin => { exchangeRate = { - amount: (amount / swapCoin.amount * DENOM_UNIT) / amount, // adjusted amount + amount: (amount / parseInt(swapCoin.amount) * DENOM_UNIT) / amount, // adjusted amount token: quoteToken } + costAmount = amount * exchangeRate.amount cost = { - amount: amount * exchangeRate.amount, + amount: costAmount, token: quoteToken } offer = { amount: cost.amount } }) } + let txFee + await this.getTxFee().then(fee => { + // fee in quote + txFee = { amount: parseFloat(fee[this.getTokenDenom(quoteToken)]), token: quoteToken } + }) + swaps.offer = offer swaps.price = exchangeRate swaps.cost = cost - // debug('swaps', swaps) + swaps.txFee = txFee + debug('swaps', swaps) return swaps } catch (err) { let reason @@ -213,13 +249,12 @@ export default class Terra { swaps = rate }) - const offerAmount = parseInt(swaps.offer.amount * DENOM_UNIT) - + const offerAmount = parseInt((swaps.offer.amount) * DENOM_UNIT) const offerCoin = new Coin(offerDenom, offerAmount) debug('offerCoin', offerCoin, offerAmount) + // Create and Sign Transaction const msgSwap = new MsgSwap(address, offerCoin, swapDenom); - const memo = lcd.config.chainID.toLowerCase().includes('columbus') ? '' : TESTNET_MEMO // debug('msgSwap', msgSwap) let txOptions @@ -227,29 +262,18 @@ export default class Terra { txOptions = { msgs: [msgSwap], gasPrices: { [offerDenom]: gasPrice }, - gasAdjustment: gasAdjustment || GAS_ADJUSTMENT, - memo: memo + gasAdjustment: gasAdjustment, + memo: MEMO } } else { txOptions = { msgs: [msgSwap], - memo: memo + memo: MEMO } } - // const fee = await this.getEstimateFee(tx) - // const estimatedGas = fee.gas - // const estimatedFee = fee.amount._coins[offerDenom] - // const estimatedFeeDenom = estimatedFee.denom - // const estimatedFeeAmount = estimatedFee.amount * DENOM_UNIT - // debug('estimatedGas/estimatedFeeDenom/estimatedFeeAmount', estimatedGas, estimatedFee) - // debug(estimatedFeeDenom, estimatedFeeAmount) - - // debug('offerDenom', offerDenom, 'gasPrice', gasPrice, 'gasAdjustment', gasAdjustment, 'options', txOptions) - await wallet.createAndSignTx(txOptions).then(tx => lcd.tx.broadcast(tx)).then((txResult) => { swapResult = txResult - // debug('txResult', txResult) const swapSuccess = !isTxError(txResult) if (swapSuccess) { @@ -259,10 +283,6 @@ export default class Terra { throw new Error(`encountered an error while running the transaction: ${txResult.code} ${txResult.codespace}`); } - // check for events from the first message - // debug('event first message', txResult.logs[0].eventsByType.store_code) - - // debug('swapSuccess', swapSuccess) const txHash = txResult.txhash const events = JSON.parse(txResult.raw_log)[0].events const swap = events.find(obj => { @@ -273,8 +293,6 @@ export default class Terra { const ask = Coin.fromString(txAttributes.swap_coin) const fee = Coin.fromString(txAttributes.swap_fee) - // debug('txAttributes', txAttributes) - tokenSwap.expectedIn = { amount: parseFloat(offer.amount) / DENOM_UNIT, token: TERRA_TOKENS[offer.denom].symbol @@ -287,16 +305,12 @@ export default class Terra { amount: parseFloat(fee.amount) / DENOM_UNIT, token: TERRA_TOKENS[fee.denom].symbol } - // tokenSwap.estimatedFee = { - // amount: estimatedFeeAmount, - // token: - // }, tokenSwap.txHash = txHash }) return tokenSwap } catch (err) { let reason - console.log(reason, typeof result) + console.log(err) err.reason ? reason = err.reason : reason = swapResult return { txSuccess: false, message: reason } } From f2e8f7333a180389e43906816211c108bec8bd01 Mon Sep 17 00:00:00 2001 From: sdgoh Date: Sat, 21 Nov 2020 01:05:11 +0800 Subject: [PATCH 13/17] (fix) remove unused test address config --- src/services/terra.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/terra.js b/src/services/terra.js index 4733add..32ec3f2 100644 --- a/src/services/terra.js +++ b/src/services/terra.js @@ -4,7 +4,6 @@ import BigNumber from 'bignumber.js' require('dotenv').config() const debug = require('debug')('router') const config = require('../services/config') -const dummyAccount = require('../static/terraTestAccount') // constants const ENV_CONFIG = config.getConfig() From 2168cb7af12940166d416dcf8731f91f54fa85c1 Mon Sep 17 00:00:00 2001 From: Michael Feng Date: Mon, 23 Nov 2020 17:55:21 -0800 Subject: [PATCH 14/17] (feat) balancer dynamic gas calc --- src/services/balancer.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/services/balancer.js b/src/services/balancer.js index 9aa3593..cee470b 100644 --- a/src/services/balancer.js +++ b/src/services/balancer.js @@ -7,11 +7,12 @@ const debug = require('debug')('router') // constants const MULTI = '0xeefba1e63905ef1d7acba5a8513c70307c1ce441'; +const MULTI_KOVAN = ' 0x2cc8688C5f75E365aaEEb4ea8D6a480405A48D2A'; const EXCHANGE_PROXY = '0x3E66B66Fd1d0b02fDa6C811Da9E0547970DB2f21'; const EXCHANGE_PROXY_KOVAN = '0x4e67bf5bD28Dd4b570FBAFe11D0633eCbA2754Ec'; const MAX_UINT = ethers.constants.MaxUint256; const MAX_SWAPS = 4; -const GAS_BASE = 200000; +const GAS_BASE = 200688; const GAS_PER_SWAP = 100000; export default class Balancer { @@ -23,9 +24,11 @@ export default class Balancer { switch (network) { case 'mainnet': this.exchangeProxy = EXCHANGE_PROXY; + this.multiCall = MULTI; break; case 'kovan': this.exchangeProxy = EXCHANGE_PROXY_KOVAN; + this.multiCall = MULTI_KOVAN; break; default: throw Error(`Invalid network ${network}`) @@ -41,10 +44,12 @@ export default class Balancer { } console.log('Pools Retrieved.', this.network); + // Get current on-chain data about the fetched pools let poolData if (this.network === 'mainnet') { - poolData = await sor.parsePoolDataOnChain(pools.pools, tokenIn, tokenOut, MULTI, this.provider) + poolData = await sor.parsePoolDataOnChain(pools.pools, tokenIn, tokenOut, this.multiCall, this.provider) } else { + // Kovan multicall throws an ENS error poolData = await sor.parsePoolData(pools.pools, tokenIn, tokenOut) } @@ -86,10 +91,12 @@ export default class Balancer { } console.log('Pools Retrieved.', this.network); + // Get current on-chain data about the fetched pools let poolData if (this.network === 'mainnet') { - poolData = await sor.parsePoolDataOnChain(pools.pools, tokenIn, tokenOut, MULTI, this.provider) + poolData = await sor.parsePoolDataOnChain(pools.pools, tokenIn, tokenOut, this.multiCall, this.provider) } else { + // Kovan multicall throws an ENS error poolData = await sor.parsePoolData(pools.pools, tokenIn, tokenOut) } From 05aae210adcfda20e70560f43c0fda0d2887ad36 Mon Sep 17 00:00:00 2001 From: Michael Feng Date: Tue, 24 Nov 2020 16:20:58 -0800 Subject: [PATCH 15/17] (cleanup) remove debug statement --- src/routes/balancer.route.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/routes/balancer.route.js b/src/routes/balancer.route.js index cd213e8..65dcef2 100644 --- a/src/routes/balancer.route.js +++ b/src/routes/balancer.route.js @@ -47,7 +47,7 @@ router.post('/sell-price', async (req, res) => { "quote":"0x....." "base":"0x....." "amount":0.1 - "swaps": 4 (optional) + "maxSwaps":4 } */ const initTime = Date.now() @@ -105,6 +105,7 @@ router.post('/buy-price', async (req, res) => { "quote":"0x....." "base":"0x....." "amount":0.1 + "maxSwaps":4 } */ const initTime = Date.now() @@ -163,6 +164,7 @@ router.post('/sell', async (req, res) => { "amount":0.1 "minPrice":1 "gasPrice":10 + "maxSwaps":4 "privateKey":{{privateKey}} } */ @@ -214,8 +216,6 @@ router.post('/sell', async (req, res) => { gasPrice, ) - debug(txObj) - // submit response res.status(200).json({ network: balancer.network, @@ -256,6 +256,7 @@ router.post('/buy', async (req, res) => { "amount":0.1 "maxPrice":1 "gasPrice":10 + "maxSwaps":4 "privateKey":{{privateKey}} } */ From 55b2746685603bb2046e0f42e618a24d2f5dea2c Mon Sep 17 00:00:00 2001 From: sdgoh Date: Tue, 1 Dec 2020 14:34:40 +0800 Subject: [PATCH 16/17] (fix) Remove comments & unused variables --- src/routes/terra.route.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/routes/terra.route.js b/src/routes/terra.route.js index 1ce7c07..74594e9 100644 --- a/src/routes/terra.route.js +++ b/src/routes/terra.route.js @@ -91,9 +91,7 @@ router.post('/price', async (req, res) => { const quoteToken = paramData.quote const tradeType = paramData.trade_type const amount = parseFloat(paramData.amount) - debug('paramData', paramData) - const symbols = [baseToken, quoteToken] let exchangeRate try { @@ -103,8 +101,6 @@ router.post('/price', async (req, res) => { reportConnectionError(res, err) }) - // debug('exchangeRate', exchangeRate) - res.status(200).json( { network: network, @@ -176,9 +172,7 @@ router.post('/trade', async (req, res) => { tradeType: tradeType, quote: quoteToken, amount: amount, - // gasPrice: gasPrice } - // debug('tokenSwaps', tokenSwaps) Object.assign(swapResult, tokenSwaps); res.status(200).json( swapResult From d87bbae858d30b00befe6e93098d2f1260c4e199 Mon Sep 17 00:00:00 2001 From: sdgoh Date: Tue, 1 Dec 2020 15:28:45 +0800 Subject: [PATCH 17/17] (feat) Add application label to dockerfile --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index cb27280..718af71 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,8 @@ FROM node:10.22.0-alpine +# Set labels +LABEL application="gateway-api" + # app directory WORKDIR /usr/src/app