Use historic BTC rate for expense items; allow BTC as currency #226

Merged
raucao merged 11 commits from feature/currencies into master 2025-02-16 14:50:26 +00:00
8 changed files with 73 additions and 43 deletions
Showing only changes of commit 697ace35b5 - Show all commits
+2 -1
View File
@@ -18,7 +18,8 @@ export default class AddExpenseItemComponent extends Component {
currencies = [
{ code: 'EUR' },
{ code: 'USD' }
{ code: 'USD' },
{ code: 'BTC' }
];
get isValidAmount () {
+31 -23
View File
@@ -5,7 +5,9 @@ import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { A } from '@ember/array';
import { scheduleOnce } from '@ember/runloop';
import { btcToSats, satsToBtc } from 'kredits-web/utils/btc-conversions';
import isValidAmount from 'kredits-web/utils/is-valid-amount';
import isoDateIsToday from 'kredits-web/utils/iso-date-is-today';
import readFileContent from 'kredits-web/utils/read-file-content';
import config from 'kredits-web/config/environment';
@@ -73,22 +75,6 @@ export default class AddReimbursementComponent extends Component {
anchor.scrollIntoView();
}
updateTotalAmountFromFiat() {
let btcAmount = 0;
if (this.exchangeRates.btceur > 0 && this.totalEUR > 0) {
btcAmount += (this.totalEUR / this.exchangeRates.btceur);
}
if (this.exchangeRates.btcusd > 0 && this.totalUSD > 0) {
btcAmount += (this.totalUSD / this.exchangeRates.btcusd);
}
if (this.totalUSD === 0 && this.totalEUR === 0) {
btcAmount = 0;
}
this.total = btcAmount.toFixed(8);
}
// TODO use ember-concurrency here
// https://github.com/67P/kredits-web/pull/209#discussion_r1064234421
@action
@@ -118,16 +104,38 @@ export default class AddReimbursementComponent extends Component {
}
@action
addExpenseItem (expenseItem) {
this.expenses.pushObject(expenseItem);
this.updateTotalAmountFromFiat();
async addExpenseItem (expense) {
let totalBTC = parseFloat(this.total);
if (expense.currency === "BTC") {
expense.amountSats = btcToSats(expense.amount);
totalBTC += expense.amount;
} else {
let amountSats;
if (isoDateIsToday(expense.date)) {
amountSats = btcToSats(expense.amount / this.exchangeRates[expense.currency]);
} else {
const rates = await this.exchangeRates.fetchHistoricRates(expense.date);
amountSats = btcToSats(expense.amount / rates[expense.currency]);
}
expense.amountSats = amountSats;
totalBTC += satsToBtc(amountSats);
}
console.debug("Adding expense:", expense);
this.total = totalBTC.toFixed(8);
this.expenses.pushObject(expense);
this.expenseFormVisible = false;
}
@action
removeExpenseItem (expenseItem) {
this.expenses.removeObject(expenseItem);
this.updateTotalAmountFromFiat();
async removeExpenseItem (expense) {
let totalBTC = parseFloat(this.total);
let amountBTC = satsToBtc(expense.amountSats);
totalBTC = totalBTC - amountBTC;
this.total = totalBTC.toFixed(8);
this.expenses.removeObject(expense);
if (this.expenses.length === 0) {
this.expenseFormVisible = true;
@@ -143,7 +151,7 @@ export default class AddReimbursementComponent extends Component {
const contributor = this.contributors.findBy('id', this.contributorId);
const attributes = {
amount: parseInt(parseFloat(this.total) * 100000000), // convert to sats
amount: btcToSats(this.total),
token: config.tokens['BTC'],
recipientId: this.contributorId,
title: `Expenses covered by ${contributor.name}`,
+7 -3
View File
@@ -2,10 +2,14 @@ import { helper } from '@ember/component/helper';
export default helper(function fmtFiatCurrency(params) {
const lang = navigator.language || navigator.userLanguage;
const value = params[0];
const currency = params[1] || 'EUR';
if (currency === 'BTC') return `BTC ${value}`;
const formatter = new Intl.NumberFormat(lang, {
style: 'currency',
currency: params[1] || 'EUR',
currencyDisplay: 'code'
style: 'currency', currency, currencyDisplay: 'code'
})
return formatter.format(params[0]);
});
+1 -1
View File
@@ -42,7 +42,7 @@ export default class CommunityFundsService extends Service {
// Format and round the approximate USD value
const lang = navigator.language || navigator.userLanguage;
const balanceUSD = (res.confirmed_balance / 100000000) * this.exchangeRates.btcusd;
const balanceUSD = (res.confirmed_balance / 100000000) * this.exchangeRates.USD;
res.balanceUSD = Math.round(balanceUSD).toLocaleString(lang);
this.balances.pushObject({
+23 -5
View File
@@ -4,6 +4,7 @@ import config from 'kredits-web/config/environment';
// Need to go through proxy for CORS headers
const bitstampBaseUrl = `${config.corsProxy}https://www.bitstamp.net/api/v2`;
const kosmosBtcPriceBaseUrl = "https://storage.kosmos.org/kosmos/public/btc-price";
async function fetchFromBitstamp(currencyPair) {
try {
@@ -16,11 +17,12 @@ async function fetchFromBitstamp(currencyPair) {
}
export default class ExchangeRatesService extends Service {
@tracked btceur = 0;
@tracked btcusd = 0;
@tracked EUR = 0;
@tracked USD = 0;
@tracked historic = {};
get exchangeRatesLoaded () {
return (this.btceur !== 0) && (this.btcusd !== 0);
return (this.EUR !== 0) && (this.USD !== 0);
}
async fetchRates (source='bitstamp') {
@@ -28,8 +30,24 @@ export default class ExchangeRatesService extends Service {
switch(source) {
case 'bitstamp':
this.btceur = await fetchFromBitstamp('btceur');
this.btcusd = await fetchFromBitstamp('btcusd');
this.EUR = await fetchFromBitstamp('btceur');
this.USD = await fetchFromBitstamp('btcusd');
}
}
async fetchHistoricRates (isoDate) {
if (typeof this.historic[isoDate] === "object") {
return this.historic[isoDate];
} else {
const url = `${kosmosBtcPriceBaseUrl}/${isoDate}`;
try {
const rates = await fetch(url).then(res => res.json());
this.historic[isoDate] = rates;
return rates;
} catch(e) {
console.error(e);
Promise.reject(e);
}
}
}
}
@@ -18,5 +18,12 @@ module('Integration | Helper | fmt-fiat-currency', function(hooks) {
await render(hbs`{{fmt-fiat-currency this.amount 'USD'}}`);
assert.ok(this.element.textContent.trim().match(/USD/),
'using defined currency when given');
await render(hbs`{{fmt-fiat-currency 0.00123 'BTC'}}`);
assert.ok(this.element.textContent.trim().match(/0.00123/),
'allows more decimals');
assert.ok(this.element.textContent.trim().match(/BTC/),
'using defined currency when given');
});
});
@@ -39,12 +39,4 @@ module('Unit | Component | add-reimbursement', function(hooks) {
assert.equal(component.totalEUR, '71');
assert.equal(component.totalUSD, '59');
});
test('#updateTotalAmountFromFiat', async function(assert) {
let component = createComponent('component:add-reimbursement');
component.expenses = expenses;
await component.exchangeRates.fetchRates();
component.updateTotalAmountFromFiat();
assert.equal(component.total, '0.01323322', 'converts EUR and USD totals to BTC and rounds to 8 decimals');
});
});
+2 -2
View File
@@ -7,7 +7,7 @@ module('Unit | Service | exchange-rates', function(hooks) {
test('#fetchRates', async function(assert) {
let service = this.owner.lookup('service:exchange-rates');
await service.fetchRates();
assert.equal(service.btceur, 9167.57, 'fetches BTCEUR from Bitstamp');
assert.equal(service.btcusd, 10749.70, 'fetches BTCUSD from Bitstamp');
assert.equal(service.EUR, 9167.57, 'fetches BTCEUR from Bitstamp');
assert.equal(service.USD, 10749.70, 'fetches BTCUSD from Bitstamp');
});
});