-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Live balances, transactions and prices #9
Merged
Merged
Changes from all commits
Commits
Show all changes
73 commits
Select commit
Hold shift + click to select a range
d9468ba
Adding CoinMarketCap types
agustin-v 2fe5713
CoinMarketCap class to access the quotas(prices) of each token or coin
agustin-v a8eaa96
Added type for query params for the prices endpoint
agustin-v 307ab2e
Changing return type
agustin-v 0579d74
Changing return type
agustin-v 3127777
Adding price endpoint
agustin-v 25ec8e0
Function to convert a new Date object into UTC epoch
agustin-v ef96c6a
Adding loki js to cache the amount of requests we have performed to t…
agustin-v 79228b5
Implementing lokijs to cache the request count and data from coinmark…
agustin-v 739e7b8
Removing cache system and lokijs
agustin-v e49e91e
Adding types for metadata
agustin-v 3b490ee
Removing error handling from the coinmarketcap lib
agustin-v 78dcd26
Types for metadata response
agustin-v e93c731
Adding sanitization functions for metadata and quote results
agustin-v e5b1485
Running linter
agustin-v 7682b84
Endpoint for prices.
agustin-v 6670340
Reunning linter
agustin-v d0a1786
Merging develop into feature branch to remove conflicts
agustin-v e368bdf
socket setup
f720de8
something proposal
b8e0dac
better emit event name
c1a5136
Fixing types syntax
agustin-v 2b36027
push new balances working
b268bfd
minor changes and handle disconnect
b219ab9
Move parsing logic to coinmarketcap.ts
ilanolkies c7e0de9
Move files
ilanolkies 714f13f
Refactor api
ilanolkies 600950a
Lint
ilanolkies c487e6e
Refactor json result
ilanolkies fd133fb
Add tests
ilanolkies 696483c
Add error handling tests - move validatoins to api
ilanolkies f8f4fdc
review changes
aa84621
Fixing merging issues
7963e73
Getting new transactions from WS explorer api (#11)
sleyter93 25f2c00
Merge branch 'develop' into feature/live-balances
ilanolkies eee6a11
Lint
ilanolkies c84f755
Merging updated-live-balances
d5ceeb2
Polling function for coinmarketcap prices
6a1c294
Implemented pushNewPrices socket based on wallet address
5890dbf
Implementing tokens addresses by chainId, 30 for mainnet and 31 for t…
6b46b74
Fixing tests
b25753a
Adding chainId parameter for the coinmarketcap class
9a59761
Lint
7109f3c
Fixing merge conflicts
fe840f1
Removing trailing comma
f68710e
Extends map between contract address and coin market cap
sleyter93 00a154a
Update list of supported tokens by coinmarketcap
31b362e
Filtering out unsupported tokens
7f6fe29
Commenting out the validatePricesRequest function which validates if …
94bb4c4
Commenting out the validatePricesRequest function which validates if …
33a379e
coinmarketcap api will fail if no ids passed therefore we added an if…
8f36bc7
adding an empty json as default value for prices, in case the wallet …
04cb38e
fixing merging issues
8eec61b
Lint
1f11902
Adding only supported tokens
4d5b7a6
exporting isConvertSupported function
a62c9b2
Lint
67d4bba
Adding conditions to handle unsupported convert or addresses
e98cd36
Modifying tests to handle new responses
c1104ac
Adding chainId as parameter to the subscriptions
b9c23f4
Adding RBTC as default in the subscriptions
dde2abe
Merge pull request #14 from rsksmart/feature/live-prices
chescalante 2efd605
Add caching strategy to coin market cap
sleyter93 120978c
Merging changes
sleyter93 91a5811
Fixing test for caching
sleyter93 55cdd12
Migrate store in cache and filtering missing address
71ec7e4
Fixing name in test and add type for channel response
sleyter93 85dbcdd
Adding tests for caching
sleyter93 7f0e823
Merge pull request #16 from rsksmart/feature/caching
ilanolkies d754ef2
Polling to get contract call transactions (#19)
sleyter93 a4d4ac7
Enhancement/error handling (#17)
agustin-v 8f9b981
Push transactions that have higher block number than the last sent
ilanolkies 2a774a9
Merge pull request #20 from rsksmart/fix-tx-condition
sleyter93 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets make this safe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll need a ssl certificate for that. (https://nodejs.org/en/knowledge/HTTP/servers/how-to-create-a-HTTPS-server/)
Let's do it in another PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: #12
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, definitely.
For dev you can use a self signed (self generated) for prod, you should cordinate that with devops