118 lines
3.1 KiB
TypeScript
118 lines
3.1 KiB
TypeScript
import { handleJSONResponse } from 'api/utils';
|
|
interface IRateSymbols {
|
|
symbols: {
|
|
all: TAllSymbols;
|
|
fiat: TFiatSymbols;
|
|
coinAndToken: TCoinAndTokenSymbols;
|
|
};
|
|
isFiat: isFiat;
|
|
}
|
|
|
|
type isFiat = (rate: string) => boolean;
|
|
|
|
export type TAllSymbols = (keyof ISymbol)[];
|
|
export type TFiatSymbols = (keyof IFiatSymbols)[];
|
|
export type TCoinAndTokenSymbols = (keyof ICoinAndTokenSymbols)[];
|
|
interface ISymbol {
|
|
USD: number;
|
|
EUR: number;
|
|
GBP: number;
|
|
CHF: number;
|
|
BTC: number;
|
|
ETH: number;
|
|
REP: number;
|
|
}
|
|
interface IFiatSymbols {
|
|
USD: number;
|
|
EUR: number;
|
|
GBP: number;
|
|
CHF: number;
|
|
}
|
|
interface ICoinAndTokenSymbols {
|
|
BTC: number;
|
|
ETH: number;
|
|
REP: number;
|
|
}
|
|
|
|
const fiat: TFiatSymbols = ['USD', 'EUR', 'GBP', 'CHF'];
|
|
const coinAndToken: TCoinAndTokenSymbols = ['BTC', 'ETH', 'REP'];
|
|
export const rateSymbols: IRateSymbols = {
|
|
symbols: {
|
|
all: [...fiat, ...coinAndToken],
|
|
fiat,
|
|
coinAndToken
|
|
},
|
|
isFiat: (rate: string) => (fiat as string[]).includes(rate)
|
|
};
|
|
|
|
// TODO - internationalize
|
|
const ERROR_MESSAGE = 'Could not fetch rate data.';
|
|
const CCApi = 'https://min-api.cryptocompare.com';
|
|
|
|
const CCRates = (symbols: string[]) => {
|
|
const tsyms = rateSymbols.symbols.all.concat(symbols as any).join(',');
|
|
return `${CCApi}/data/price?fsym=ETH&tsyms=${tsyms}`;
|
|
};
|
|
|
|
export interface CCResponse {
|
|
[symbol: string]: ISymbol;
|
|
}
|
|
|
|
interface IRatesResponse {
|
|
[key: string]: number;
|
|
}
|
|
interface IRatesError {
|
|
Response: 'Error';
|
|
}
|
|
|
|
export const fetchRates = (symbols: string[] = []): Promise<CCResponse> =>
|
|
fetch(CCRates(symbols))
|
|
.then(response => handleJSONResponse(response, ERROR_MESSAGE))
|
|
.then((rates: IRatesResponse | IRatesError) => {
|
|
// API errors come as 200s, so check the json for error
|
|
if ((rates as IRatesError).Response === 'Error') {
|
|
throw new Error('Failed to fetch rates');
|
|
}
|
|
return rates;
|
|
})
|
|
.then((rates: IRatesResponse) => {
|
|
// Sometimes the API erroneously gives tokens an extremely high value,
|
|
// like 10000000 ETH to 1 token. Filter those out. If that ever turns
|
|
// out to be true, we should all go home.
|
|
return Object.keys(rates).reduce((filteredRates: IRatesResponse, key) => {
|
|
if (rates[key] > 0.000001) {
|
|
filteredRates[key] = rates[key];
|
|
}
|
|
return filteredRates;
|
|
}, {});
|
|
})
|
|
.then((rates: IRatesResponse) => {
|
|
// All currencies are in ETH right now. We'll do token -> eth -> value to
|
|
// do it all in one request to their respective rates via ETH.
|
|
return symbols.reduce(
|
|
(eqRates, sym) => {
|
|
if (rates[sym]) {
|
|
eqRates[sym] = rateSymbols.symbols.all.reduce(
|
|
(symRates, rateSym) => {
|
|
symRates[rateSym] = 1 / rates[sym] * rates[rateSym];
|
|
return symRates;
|
|
},
|
|
{} as ISymbol
|
|
);
|
|
}
|
|
return eqRates;
|
|
},
|
|
{
|
|
ETH: {
|
|
USD: rates.USD,
|
|
EUR: rates.EUR,
|
|
GBP: rates.GBP,
|
|
CHF: rates.CHF,
|
|
BTC: rates.BTC,
|
|
ETH: 1,
|
|
REP: rates.REP
|
|
}
|
|
} as CCResponse
|
|
);
|
|
});
|