-
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.
Merge pull request #159 from blockfrost/fix/partial-exchange-rates
fix: return partial exchange rates, throttle requests
- Loading branch information
Showing
14 changed files
with
191 additions
and
45 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Binary file added
BIN
+36.8 KB
.yarn/cache/rate-limiter-flexible-npm-2.3.6-07a4dd65e8-c008c7bcc1.zip
Binary file not shown.
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,4 +1,15 @@ | ||
// BIP44 gap limit https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#address-gap-limit | ||
export const ADDRESS_GAP_LIMIT = 20; | ||
|
||
// Skip emitting of `newBlock` event for missed blocks if we missed more than defined number of them | ||
export const EMIT_MAX_MISSED_BLOCKS = 3; | ||
|
||
// List of coingecko-compatible proxies to retrieve historical fiat rates for balance history endpoint | ||
// Set env variable BLOCKFROST_FIAT_RATES_PROXY to provide additional proxies (comma separated values) | ||
// eg. BLOCKFROST_FIAT_RATES_PROXY="https://example.com/api/v3/coins/cardano/history,https://example2.com/history" | ||
export const FIAT_RATES_PROXY = ['https://api.coingecko.com/api/v3/coins/cardano/history']; | ||
|
||
// Max number of requests per second sent to FIAT_RATES_PROXY, additional requests will be queued | ||
export const FIAT_RATES_REQUESTS_PER_SEC = 100; | ||
// Request timeout for fetching single rate for a given day | ||
export const FIAT_RATES_REQUESTS_TIMEOUT = 1000; |
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
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
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,89 @@ | ||
import { format } from 'date-fns'; | ||
import got from 'got'; | ||
import { | ||
FIAT_RATES_REQUESTS_PER_SEC, | ||
FIAT_RATES_PROXY, | ||
FIAT_RATES_REQUESTS_TIMEOUT, | ||
} from '../constants/config'; | ||
import { RateLimiterMemory, RateLimiterQueue } from 'rate-limiter-flexible'; | ||
import { blockfrostAPI } from './blockfrostAPI'; | ||
|
||
// limit max number of requests per sec to prevent too many opened connections | ||
const ratesLimiter = new RateLimiterMemory({ | ||
points: FIAT_RATES_REQUESTS_PER_SEC, | ||
duration: 1, | ||
}); | ||
|
||
const limiterQueue = new RateLimiterQueue(ratesLimiter); | ||
|
||
export const formatCoingeckoTime = (date: number): string => { | ||
return format(date * 1000, 'dd-MM-yyyy'); | ||
}; | ||
|
||
export const getFiatRatesProxies = (additional = process.env.BLOCKFROST_FIAT_RATES_PROXY) => { | ||
let proxies = FIAT_RATES_PROXY; | ||
if (additional) { | ||
const items = additional.split(','); | ||
const sanitized = items | ||
.map(item => (item.endsWith('/') ? item.substring(0, item.length - 1) : item)) | ||
.filter(proxy => proxy.length > 0); // remove trailing slash | ||
proxies = sanitized.concat(proxies); | ||
} | ||
return proxies; | ||
}; | ||
|
||
export const getRatesForDateNoLimit = async (date: number): Promise<Record<string, number>> => { | ||
const coingeckoDateFormat = formatCoingeckoTime(date); | ||
try { | ||
let response: { | ||
market_data?: { | ||
current_price: Record<string, number>; | ||
}; | ||
} = {}; | ||
|
||
for (const [index, proxy] of getFiatRatesProxies().entries()) { | ||
// iterate through proxies till we have a valid response | ||
try { | ||
response = await got(`${proxy}?date=${coingeckoDateFormat}`, { | ||
headers: { | ||
'User-Agent': blockfrostAPI.userAgent, | ||
}, | ||
timeout: { | ||
request: FIAT_RATES_REQUESTS_TIMEOUT, | ||
}, | ||
}).json(); | ||
if (response?.market_data?.current_price) { | ||
break; | ||
} | ||
} catch (err) { | ||
if (index === FIAT_RATES_PROXY.length - 1) { | ||
// last proxy thrown error, we don't have the data | ||
throw err; | ||
} | ||
} | ||
} | ||
|
||
if (!response?.market_data) { | ||
throw Error(`Failed to fetch exchange rate for ${coingeckoDateFormat}`); | ||
} | ||
|
||
return response.market_data?.current_price; | ||
} catch (error) { | ||
throw Error(`Failed to fetch exchange rate for ${coingeckoDateFormat}`); | ||
} | ||
}; | ||
|
||
export const getRatesForDate = async (date: number): Promise<Record<string, number>> => { | ||
const t1 = new Date().getTime(); | ||
|
||
// wait for a slot | ||
await limiterQueue.removeTokens(1); | ||
|
||
const t2 = new Date().getTime(); | ||
const diff = t2 - t1; | ||
if (diff > 1000) { | ||
console.warn(`Fiat rates limiter slowed down request for ${diff} ms!`); | ||
} | ||
|
||
return getRatesForDateNoLimit(date); | ||
}; |
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,42 @@ | ||
import { FIAT_RATES_PROXY } from '../../../src/constants/config'; | ||
|
||
export const getFiatRatesProxies = [ | ||
{ | ||
description: 'getFiatRatesProxies: basic', | ||
additional: '', | ||
result: [...FIAT_RATES_PROXY], | ||
}, | ||
{ | ||
description: 'getFiatRatesProxies: passing additional proxy', | ||
additional: 'https://mynextproxy.com/history', | ||
result: ['https://mynextproxy.com/history', ...FIAT_RATES_PROXY], | ||
}, | ||
{ | ||
description: 'getFiatRatesProxies: passing additional proxy with trailing slash', | ||
additional: 'https://mynextproxy.com/history/', | ||
result: ['https://mynextproxy.com/history', ...FIAT_RATES_PROXY], | ||
}, | ||
{ | ||
description: 'getFiatRatesProxies: passing additional proxies separated by comma', | ||
additional: 'https://mynextproxy.com/history,mybiggestproxy.com', | ||
result: ['https://mynextproxy.com/history', 'mybiggestproxy.com', ...FIAT_RATES_PROXY], | ||
}, | ||
{ | ||
description: 'getFiatRatesProxies: passing additional proxies with trailing comma', | ||
additional: 'https://mynextproxy.com/history,mybiggestproxy.com,', | ||
result: ['https://mynextproxy.com/history', 'mybiggestproxy.com', ...FIAT_RATES_PROXY], | ||
}, | ||
]; | ||
|
||
export const formatCoingeckoTime = [ | ||
{ | ||
description: 'formatCoingeckoTime: basic', | ||
time: 1641561868, | ||
result: '07-01-2022', | ||
}, | ||
{ | ||
description: 'formatCoingeckoTime: basic 2', | ||
time: 1643545468, | ||
result: '30-01-2022', | ||
}, | ||
]; |
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,16 @@ | ||
import * as ratesUtils from '../../../../src/utils/rates'; | ||
import * as fixtures from '../../fixtures/rates'; | ||
|
||
describe('asset utils', () => { | ||
fixtures.getFiatRatesProxies.forEach(fixture => { | ||
test(fixture.description, () => { | ||
expect(ratesUtils.getFiatRatesProxies(fixture.additional)).toMatchObject(fixture.result); | ||
}); | ||
}); | ||
|
||
fixtures.formatCoingeckoTime.forEach(fixture => { | ||
test(fixture.description, () => { | ||
expect(ratesUtils.formatCoingeckoTime(fixture.time)).toBe(fixture.result); | ||
}); | ||
}); | ||
}); |
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