-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Live balances, transactions and prices (#9)
* Adding CoinMarketCap types * CoinMarketCap class to access the quotas(prices) of each token or coin * Added type for query params for the prices endpoint * Changing return type * Changing return type * Adding price endpoint * Function to convert a new Date object into UTC epoch * Adding loki js to cache the amount of requests we have performed to the coinmarketcap api * Implementing lokijs to cache the request count and data from coinmarketcap * Removing cache system and lokijs * Adding types for metadata * Removing error handling from the coinmarketcap lib * Types for metadata response * Adding sanitization functions for metadata and quote results * Running linter * Endpoint for prices. * Reunning linter * socket setup * something proposal * better emit event name * Fixing types syntax * push new balances working * minor changes and handle disconnect * Move parsing logic to coinmarketcap.ts * Move files * Refactor api * Lint * Refactor json result * Add tests * Add error handling tests - move validatoins to api * review changes * Getting new transactions from WS explorer api (#11) * Lint * Polling function for coinmarketcap prices * Implemented pushNewPrices socket based on wallet address * Implementing tokens addresses by chainId, 30 for mainnet and 31 for testnet * Fixing tests * Adding chainId parameter for the coinmarketcap class * Lint * Fixing merge conflicts * Removing trailing comma * Extends map between contract address and coin market cap * Update list of supported tokens by coinmarketcap * Filtering out unsupported tokens * Commenting out the validatePricesRequest function which validates if the token is supported or not, since we are doing that inside the coinmarketcap function * Commenting out the validatePricesRequest function which validates if the token is supported or not, since we are doing that inside the coinmarketcap function * coinmarketcap api will fail if no ids passed therefore we added an if if there are no addresses * adding an empty json as default value for prices, in case the wallet doesnt have any token yet, the backend wont crash because of the coinmarket cap api no receiving tokens ids * Lint * Adding only supported tokens * exporting isConvertSupported function * Lint * Adding conditions to handle unsupported convert or addresses * Modifying tests to handle new responses * Adding chainId as parameter to the subscriptions * Adding RBTC as default in the subscriptions * Add caching strategy to coin market cap * Fixing test for caching * Migrate store in cache and filtering missing address * Fixing name in test and add type for channel response * Adding tests for caching * Polling to get contract call transactions (#19) * Enhancement/error handling (#17) * Custom error class and errorhandler functio * Removing errorhandler from here and using it in setupAPI * Uncommenting the asserts that checked the text of the error message response * Implementing next function in the error handlers of the promises * Update src/middleware/index.ts Co-authored-by: Ilan <36084092+ilanolkies@users.noreply.github.com> * Update src/middleware/index.ts Co-authored-by: Ilan <36084092+ilanolkies@users.noreply.github.com> * Update src/api/index.ts Co-authored-by: Ilan <36084092+ilanolkies@users.noreply.github.com> * applying suggestions Co-authored-by: Agustin Villalobos <agustin.villalobos@iovlabs.org> Co-authored-by: Ilan <36084092+ilanolkies@users.noreply.github.com> * Push transactions that have higher block number than the last sent Co-authored-by: Agustin Villalobos V <agustinvillalobos@protonmail.com> Co-authored-by: Christian Escalante <chescalante@gmail.com> Co-authored-by: Ilan <ilanolkies@outlook.com> Co-authored-by: Agustín Villalobos <avillaville@Agustins-MacBook-Pro.local> Co-authored-by: sleyter93 <96137983+sleyter93@users.noreply.github.com> Co-authored-by: Agustin Villalobos <agustin.villalobos@iovlabs.org> Co-authored-by: Sleyter Sandoval <sleyter.sandoval@iovlabs.org> Co-authored-by: Sleyter Sandoval <sleyter@Sleyters-MacBook-Pro.local> Co-authored-by: Ilan <36084092+ilanolkies@users.noreply.github.com> Co-authored-by: Agustín Villalobos <32603375+agustin-v@users.noreply.github.com>
- Loading branch information
1 parent
efa7b4e
commit c8f69e1
Showing
19 changed files
with
1,173 additions
and
82 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,69 +1,85 @@ | ||
import { Application, Request, Response } from 'express' | ||
import { Application, NextFunction, Request, Response } from 'express' | ||
import { RSKExplorerAPI } from '../rskExplorerApi' | ||
import { CoinMarketCapAPI } from '../coinmatketcap' | ||
import { CoinMarketCapAPI } from '../coinmarketcap' | ||
import { registeredDapps as _registeredDapps } from '../registered_dapps' | ||
import { PricesQueryParams } from './types' | ||
import { validatePricesRequest } from '../coinmatketcap/validations' | ||
import { isConvertSupported, isTokenSupported } from '../coinmarketcap/validations' | ||
import NodeCache from 'node-cache' | ||
import { findInCache, storeInCache } from '../coinmarketcap/priceCache' | ||
import { errorHandler } from '../middleware' | ||
|
||
const responseJsonOk = (res: Response) => res.status(200).json.bind(res) | ||
|
||
const makeRequestFactory = (console) => async (req, res, query) => { | ||
try { | ||
console.log(req.url) | ||
const result = await query() | ||
res.status(200).json(result) | ||
} catch (e: any) { | ||
console.error(e) | ||
res.status(500).send(e.message) | ||
} | ||
} | ||
|
||
type APIOptions = { | ||
rskExplorerApi: RSKExplorerAPI | ||
coinMarketCapApi: CoinMarketCapAPI | ||
registeredDapps: typeof _registeredDapps | ||
priceCache: NodeCache | ||
logger?: any | ||
chainId: number | ||
} | ||
|
||
export const setupApi = (app: Application, { | ||
rskExplorerApi, coinMarketCapApi, registeredDapps, logger = { log: () => {}, error: () => {} } | ||
rskExplorerApi, coinMarketCapApi, registeredDapps, priceCache, chainId | ||
}: APIOptions) => { | ||
const makeRequest = makeRequestFactory(logger) | ||
|
||
app.get('/tokens', (_: Request, res: Response) => rskExplorerApi.getTokens().then(res.status(200).json.bind(res))) | ||
app.get('/tokens', (_: Request, res: Response, next: NextFunction) => rskExplorerApi.getTokens() | ||
.then(res.status(200).json.bind(res)) | ||
.catch(next) | ||
) | ||
|
||
app.get( | ||
'/address/:address/tokens', | ||
({ params: { address } }: Request, res: Response) => rskExplorerApi.getTokensByAddress(address) | ||
({ params: { address } }: Request, res: Response, next: NextFunction) => rskExplorerApi.getTokensByAddress(address) | ||
.then(responseJsonOk(res)) | ||
.catch(next) | ||
) | ||
|
||
app.get( | ||
'/address/:address/events', | ||
({ params: { address } }: Request, res: Response) => rskExplorerApi.getEventsByAddress(address) | ||
({ params: { address } }: Request, res: Response, next: NextFunction) => rskExplorerApi.getEventsByAddress(address) | ||
.then(responseJsonOk(res)) | ||
.catch(next) | ||
) | ||
|
||
app.get( | ||
'/address/:address/transactions', | ||
({ params: { address }, query: { limit, prev, next } }: Request, res: Response) => | ||
({ params: { address }, query: { limit, prev, next } }: Request, res: Response, nextFunction: NextFunction) => | ||
rskExplorerApi.getTransactionsByAddress( | ||
address, limit as string, prev as string, next as string | ||
) | ||
.then(responseJsonOk(res)) | ||
.catch(nextFunction) | ||
) | ||
|
||
app.get( | ||
'/price', | ||
async (req: Request<{}, {}, {}, PricesQueryParams>, res: Response) => makeRequest( | ||
req, res, () => { | ||
const addresses = req.query.addresses.split(',') | ||
const convert = req.query.convert | ||
validatePricesRequest(addresses, convert) | ||
return coinMarketCapApi.getQuotesLatest({ addresses, convert }) | ||
} | ||
) | ||
(req: Request<{}, {}, {}, PricesQueryParams>, res: Response, next: NextFunction) => { | ||
const addresses = req.query.addresses.split(',').filter((address) => isTokenSupported(address, chainId)) | ||
const convert = req.query.convert | ||
|
||
if (!isConvertSupported(convert)) throw new Error('Convert not supported') | ||
|
||
const isAddressesEmpty = addresses.length === 0 | ||
if (isAddressesEmpty) return responseJsonOk(res)({}) | ||
|
||
const { missingAddresses, pricesInCache } = findInCache(addresses, priceCache) | ||
if (!missingAddresses.length) return responseJsonOk(res)(pricesInCache) | ||
|
||
const prices = coinMarketCapApi.getQuotesLatest({ addresses: missingAddresses, convert }) | ||
prices | ||
.then(pricesFromCMC => { | ||
storeInCache(pricesFromCMC, priceCache) | ||
const pricesRes = { | ||
...pricesInCache, | ||
...pricesFromCMC | ||
} | ||
return responseJsonOk(res)(pricesRes) | ||
}) | ||
.catch(next) | ||
} | ||
) | ||
|
||
app.get('/dapps', (_: Request, res: Response) => responseJsonOk(res)(registeredDapps)) | ||
|
||
app.use(errorHandler) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import NodeCache from 'node-cache' | ||
import { Prices } from '../api/types' | ||
import { IPriceCacheSearch } from './types' | ||
|
||
export const findInCache = (addresses: string[], cache: NodeCache): IPriceCacheSearch => { | ||
const response: IPriceCacheSearch = { missingAddresses: [], pricesInCache: {} } | ||
addresses.forEach(address => { | ||
if (cache.has(address)) { | ||
response.pricesInCache = { | ||
...response.pricesInCache, | ||
...cache.get(address) | ||
} | ||
} | ||
}) | ||
response.missingAddresses = addresses.filter(address => !Object.keys(response.pricesInCache).includes(address)) | ||
return response | ||
} | ||
|
||
export const storeInCache = (prices: Prices, cache: NodeCache): void => { | ||
Object.keys(prices).forEach(address => cache.set(address, { [address]: prices[address] }), 60) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
export const addressToCoinmarketcapId = { | ||
30: { | ||
'0x0000000000000000000000000000000000000000': '3626', // RBTC | ||
'0x2acc95758f8b5f583470ba265eb685a8f45fc9d5': '3701', // RIF | ||
'0xef213441a85df4d7acbdae0cf78004e1e486bb96': '825', // rUSDT | ||
'0x4991516df6053121121274397a8c1dad608bc95b': '7785', // rBUND | ||
'0xefc78fc7d48b64958315949279ba181c2114abbd': '8669' // SOV | ||
}, | ||
31: { | ||
'0x0000000000000000000000000000000000000000': '3626', // RBTC | ||
'0x19f64674d8a5b4e652319f5e239efd3bc969a1fe': '3701', // tRIF | ||
'0x4cfe225ce54c6609a525768b13f7d87432358c57': '825', // rKovUSDT | ||
'0xe95afdfec031f7b9cd942eb7e60f053fb605dfcd': '7785' // rKovBUND | ||
} | ||
} | ||
|
||
export const supportedFiat = ['USD'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { addressToCoinmarketcapId, supportedFiat } from './support' | ||
|
||
export const isTokenSupported = (address: string, chainId: number) => addressToCoinmarketcapId[chainId][address] !== undefined | ||
export const isConvertSupported = (convert: string) => supportedFiat.includes(convert) | ||
|
||
export const validatePricesRequest = (addresses: string[], convert: string, chainId: number) => { | ||
addresses.forEach(address => { if (!isTokenSupported(address, chainId)) throw new Error('Token address not supported') }) | ||
if (!isConvertSupported(convert)) throw new Error('Convert not supported') | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { ErrorRequestHandler } from 'express' | ||
|
||
export class CustomError extends Error { | ||
status: number; | ||
constructor (message: string, status: number) { | ||
super(message) | ||
this.status = status | ||
} | ||
} | ||
|
||
export const errorHandler: ErrorRequestHandler = (error, req, res, next) => { | ||
const status = error.status || 500 | ||
const message = error.message || 'Something went wrong' | ||
console.error(error) | ||
res.status(status).send(message) | ||
next() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.