Merge pull request #7273 from BlueWallet/fiat

Fiat
This commit is contained in:
GLaDOS 2024-11-09 11:26:35 +00:00 committed by GitHub
commit ef99e0f406
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 151 additions and 124 deletions

View File

@ -13,176 +13,202 @@ export const FiatUnitSource = {
BNR: 'BNR', BNR: 'BNR',
} as const; } as const;
const handleError = (source: string, ticker: string, error: Error) => {
throw new Error(
`Could not update rate for ${ticker} from ${source}: ${error.message}. ` + `Make sure the network you're on has access to ${source}.`,
);
};
const fetchRate = async (url: string): Promise<unknown> => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
};
interface CoinbaseResponse {
data: {
amount: string;
};
}
interface CoinDeskResponse {
bpi: {
[ticker: string]: {
rate_float: number;
};
};
}
interface CoinGeckoResponse {
bitcoin: {
[ticker: string]: number;
};
}
interface BitstampResponse {
last: string;
}
interface KrakenResponse {
result: {
[pair: string]: {
c: [string];
};
};
}
interface YadioResponse {
[ticker: string]: {
price: number;
};
}
interface YadioConvertResponse {
rate: number;
}
interface ExirResponse {
last: string;
}
interface CoinpaprikaResponse {
quotes: {
[ticker: string]: {
price: number;
};
};
}
const RateExtractors = { const RateExtractors = {
Coinbase: async (ticker: string): Promise<number> => { Coinbase: async (ticker: string): Promise<number> => {
let json;
try { try {
const res = await fetch(`https://api.coinbase.com/v2/prices/BTC-${ticker.toUpperCase()}/buy`); const json = (await fetchRate(`https://api.coinbase.com/v2/prices/BTC-${ticker.toUpperCase()}/buy`)) as CoinbaseResponse;
json = await res.json(); const rate = Number(json?.data?.amount);
} catch (e: any) { if (!(rate >= 0)) throw new Error('Invalid data received');
throw new Error(`Could not update rate for ${ticker}: ${e.message}`); return rate;
} catch (error: any) {
handleError('Coinbase', ticker, error);
return undefined as never;
} }
let rate = json?.data?.amount;
if (!rate) throw new Error(`Could not update rate for ${ticker}: data is wrong`);
rate = Number(rate);
if (!(rate >= 0)) throw new Error(`Could not update rate for ${ticker}: data is wrong`);
return rate;
}, },
CoinDesk: async (ticker: string): Promise<number> => { CoinDesk: async (ticker: string): Promise<number> => {
let json;
try { try {
const res = await fetch(`https://api.coindesk.com/v1/bpi/currentprice/${ticker}.json`); const json = (await fetchRate(`https://api.coindesk.com/v1/bpi/currentprice/${ticker}.json`)) as CoinDeskResponse;
json = await res.json(); const rate = Number(json?.bpi?.[ticker]?.rate_float);
} catch (e: any) { if (!(rate >= 0)) throw new Error('Invalid data received');
throw new Error(`Could not update rate for ${ticker}: ${e.message}`); return rate;
} catch (error: any) {
handleError('CoinDesk', ticker, error);
return undefined as never;
} }
let rate = json?.bpi?.[ticker]?.rate_float;
if (!rate) throw new Error(`Could not update rate for ${ticker}: data is wrong`);
rate = Number(rate);
if (!(rate >= 0)) throw new Error(`Could not update rate for ${ticker}: data is wrong`);
return rate;
}, },
CoinGecko: async (ticker: string): Promise<number> => { CoinGecko: async (ticker: string): Promise<number> => {
let json;
try { try {
const res = await fetch(`https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=${ticker.toLowerCase()}`); const json = (await fetchRate(
json = await res.json(); `https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=${ticker.toLowerCase()}`,
} catch (e: any) { )) as CoinGeckoResponse;
throw new Error(`Could not update rate for ${ticker}: ${e.message}`); const rate = Number(json?.bitcoin?.[ticker.toLowerCase()]);
if (!(rate >= 0)) throw new Error('Invalid data received');
return rate;
} catch (error: any) {
handleError('CoinGecko', ticker, error);
return undefined as never;
} }
const rate = json?.bitcoin?.[ticker] || json?.bitcoin?.[ticker.toLowerCase()];
if (!rate) throw new Error(`Could not update rate for ${ticker}: data is wrong`);
return rate;
}, },
Bitstamp: async (ticker: string): Promise<number> => { Bitstamp: async (ticker: string): Promise<number> => {
let json;
try { try {
const res = await fetch(`https://www.bitstamp.net/api/v2/ticker/btc${ticker.toLowerCase()}`); const json = (await fetchRate(`https://www.bitstamp.net/api/v2/ticker/btc${ticker.toLowerCase()}`)) as BitstampResponse;
json = await res.json(); const rate = Number(json?.last);
} catch (e: any) { if (!(rate >= 0)) throw new Error('Invalid data received');
throw new Error(`Could not update rate from Bitstamp for ${ticker}: ${e.message}`); return rate;
} catch (error: any) {
handleError('Bitstamp', ticker, error);
return undefined as never;
} }
if (Array.isArray(json)) {
throw new Error(`Unsupported ticker for Bitstamp: ${ticker}`);
}
let rate = +json?.last;
if (!rate) throw new Error(`Could not update rate from Bitstamp for ${ticker}: data is wrong`);
rate = Number(rate);
if (!(rate >= 0)) throw new Error(`Could not update rate from Bitstamp for ${ticker}: data is wrong`);
return rate;
}, },
Kraken: async (ticker: string): Promise<number> => { Kraken: async (ticker: string): Promise<number> => {
let json;
try { try {
const res = await fetch(`https://api.kraken.com/0/public/Ticker?pair=XXBTZ${ticker.toUpperCase()}`); const json = (await fetchRate(`https://api.kraken.com/0/public/Ticker?pair=XXBTZ${ticker.toUpperCase()}`)) as KrakenResponse;
json = await res.json(); const rate = Number(json?.result?.[`XXBTZ${ticker.toUpperCase()}`]?.c?.[0]);
} catch (e: any) { if (!(rate >= 0)) throw new Error('Invalid data received');
throw new Error(`Could not update rate from Kraken for ${ticker}: ${e.message}`); return rate;
} catch (error: any) {
handleError('Kraken', ticker, error);
return undefined as never;
} }
let rate = json?.result?.[`XXBTZ${ticker.toUpperCase()}`]?.c?.[0];
if (!rate) throw new Error(`Could not update rate from Kraken for ${ticker}: data is wrong`);
rate = Number(rate);
if (!(rate >= 0)) throw new Error(`Could not update rate from Kraken for ${ticker}: data is wrong`);
return rate;
}, },
BNR: async (): Promise<number> => { BNR: async (): Promise<number> => {
try { try {
const response = await fetch('https://www.bnr.ro/nbrfxrates.xml');
const xmlData = await response.text();
// Fetching USD to RON rate // Fetching USD to RON rate
const pattern = /<Rate currency="USD">([\d.]+)<\/Rate>/;
const matches = xmlData.match(pattern);
const xmlData = await (await fetch('https://www.bnr.ro/nbrfxrates.xml')).text();
const matches = xmlData.match(/<Rate currency="USD">([\d.]+)<\/Rate>/);
if (matches && matches[1]) { if (matches && matches[1]) {
const usdToRonRate = parseFloat(matches[1]); const usdToRonRate = parseFloat(matches[1]);
if (!isNaN(usdToRonRate) && usdToRonRate > 0) { const btcToUsdRate = await RateExtractors.CoinGecko('USD');
// Fetch BTC to USD rate using CoinGecko extractor // Convert BTC to RON using the USD to RON exchange rate
const btcToUsdRate = await RateExtractors.CoinGecko('USD'); return btcToUsdRate * usdToRonRate;
// Convert BTC to RON using the USD to RON exchange rate
return btcToUsdRate * usdToRonRate;
}
} }
throw new Error('Could not find a valid exchange rate for USD to RON'); throw new Error('No valid USD to RON rate found');
} catch (error: any) { } catch (error: any) {
throw new Error(`Could not fetch RON exchange rate: ${error.message}`); handleError('BNR', 'RON', error);
return undefined as never;
} }
}, },
Yadio: async (ticker: string): Promise<number> => {
let json;
try {
const res = await fetch(`https://api.yadio.io/json/${ticker}`);
json = await res.json();
} catch (e: any) {
throw new Error(`Could not update rate for ${ticker}: ${e.message}`);
}
let rate = json?.[ticker]?.price;
if (!rate) throw new Error(`Could not update rate for ${ticker}: data is wrong`);
rate = Number(rate); Yadio: async (ticker: string): Promise<number> => {
if (!(rate >= 0)) throw new Error(`Could not update rate for ${ticker}: data is wrong`); try {
return rate; const json = (await fetchRate(`https://api.yadio.io/json/${ticker}`)) as YadioResponse;
const rate = Number(json?.[ticker]?.price);
if (!(rate >= 0)) throw new Error('Invalid data received');
return rate;
} catch (error: any) {
handleError('Yadio', ticker, error);
return undefined as never;
}
}, },
YadioConvert: async (ticker: string): Promise<number> => { YadioConvert: async (ticker: string): Promise<number> => {
let json;
try { try {
const res = await fetch(`https://api.yadio.io/convert/1/BTC/${ticker}`); const json = (await fetchRate(`https://api.yadio.io/convert/1/BTC/${ticker}`)) as YadioConvertResponse;
json = await res.json(); const rate = Number(json?.rate);
} catch (e: any) { if (!(rate >= 0)) throw new Error('Invalid data received');
throw new Error(`Could not update rate for ${ticker}: ${e.message}`); return rate;
} catch (error: any) {
handleError('YadioConvert', ticker, error);
return undefined as never;
} }
let rate = json?.rate;
if (!rate) throw new Error(`Could not update rate for ${ticker}: data is wrong`);
rate = Number(rate);
if (!(rate >= 0)) throw new Error(`Could not update rate for ${ticker}: data is wrong`);
return rate;
}, },
Exir: async (ticker: string): Promise<number> => { Exir: async (ticker: string): Promise<number> => {
let json;
try { try {
const res = await fetch('https://api.exir.io/v1/ticker?symbol=btc-irt'); const json = (await fetchRate('https://api.exir.io/v1/ticker?symbol=btc-irt')) as ExirResponse;
json = await res.json(); const rate = Number(json?.last);
} catch (e: any) { if (!(rate >= 0)) throw new Error('Invalid data received');
throw new Error(`Could not update rate for ${ticker}: ${e.message}`); return rate;
} catch (error: any) {
handleError('Exir', ticker, error);
return undefined as never;
} }
let rate = json?.last;
if (!rate) throw new Error(`Could not update rate for ${ticker}: data is wrong`);
rate = Number(rate);
if (!(rate >= 0)) throw new Error(`Could not update rate for ${ticker}: data is wrong`);
return rate;
}, },
coinpaprika: async (ticker: string): Promise<number> => { coinpaprika: async (ticker: string): Promise<number> => {
let json;
try { try {
const res = await fetch('https://api.coinpaprika.com/v1/tickers/btc-bitcoin?quotes=INR'); const json = (await fetchRate('https://api.coinpaprika.com/v1/tickers/btc-bitcoin?quotes=INR')) as CoinpaprikaResponse;
json = await res.json(); const rate = Number(json?.quotes?.INR?.price);
} catch (e: any) { if (!(rate >= 0)) throw new Error('Invalid data received');
throw new Error(`Could not update rate for ${ticker}: ${e.message}`); return rate;
} catch (error: any) {
handleError('coinpaprika', ticker, error);
return undefined as never;
} }
const rate = json?.quotes?.INR?.price;
if (!rate) throw new Error(`Could not update rate for ${ticker}: data is wrong`);
const parsedRate = Number(rate);
if (isNaN(parsedRate) || parsedRate <= 0) {
throw new Error(`Could not update rate for ${ticker}: data is wrong`);
}
return parsedRate;
}, },
} as const; } as const;

View File

@ -10,7 +10,7 @@ import {
mostRecentFetchedRate, mostRecentFetchedRate,
setPreferredCurrency, setPreferredCurrency,
} from '../../blue_modules/currency'; } from '../../blue_modules/currency';
import { BlueCard, BlueSpacing10, BlueText } from '../../BlueComponents'; import { BlueCard, BlueSpacing10, BlueSpacing20, BlueText } from '../../BlueComponents';
import presentAlert from '../../components/Alert'; import presentAlert from '../../components/Alert';
import ListItem from '../../components/ListItem'; import ListItem from '../../components/ListItem';
import { useTheme } from '../../components/themes'; import { useTheme } from '../../components/themes';
@ -136,6 +136,7 @@ const Currency: React.FC = () => {
<BlueText> <BlueText>
{loc.settings.last_updated}: {dayjs(currencyRate.LastUpdated).calendar() ?? loc._.never} {loc.settings.last_updated}: {dayjs(currencyRate.LastUpdated).calendar() ?? loc._.never}
</BlueText> </BlueText>
<BlueSpacing20 />
</BlueCard> </BlueCard>
) : null} ) : null}
</View> </View>