Skip to content

Commit

Permalink
use yadio for pricing "exotic" fiat (#921)
Browse files Browse the repository at this point in the history
* yadio api

* add more currencies

* fix es typo

* bump version & fix cicleci build
  • Loading branch information
m2049r authored Dec 10, 2023
1 parent 17df7c3 commit a6e9d0e
Show file tree
Hide file tree
Showing 16 changed files with 537 additions and 89 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ jobs:
build:
working_directory: ~/code
docker:
- image: cimg/android:2022.03-ndk
- image: cimg/android:2023.12-ndk
environment:
JVM_OPTS: -Xmx3200m
steps:
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ android {
compileSdk 33
minSdkVersion 21
targetSdkVersion 33
versionCode 3308
versionName "3.3.8 'Pocket Change'"
versionCode 3310
versionName "3.3.10 'Argentina'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,24 @@
import android.os.Build;

import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStateManagerControl;

import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import com.m2049r.xmrwallet.util.NightmodeHelper;
import com.m2049r.xmrwallet.util.ServiceHelper;

import java.util.Arrays;

import timber.log.Timber;

public class XmrWalletApplication extends Application {

@Override
@OptIn(markerClass = FragmentStateManagerControl.class)
public void onCreate() {
super.onCreate();
FragmentManager.enableNewStateManager(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ public interface ExchangeApi {
void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
@NonNull final ExchangeCallback callback);

String getName();
}

Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ public ExchangeApiImpl() {
// data is daily and is refreshed around 16:00 CET every working day
}

public static boolean isSameDay(Calendar calendar, Calendar anotherCalendar) {
return (calendar.get(Calendar.YEAR) == anotherCalendar.get(Calendar.YEAR)) &&
(calendar.get(Calendar.DAY_OF_YEAR) == anotherCalendar.get(Calendar.DAY_OF_YEAR));
@Override
public String getName() {
return "ecb";
}

@Override
Expand Down Expand Up @@ -121,12 +121,12 @@ public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final
final NetCipherHelper.Request httpRequest = new NetCipherHelper.Request(baseUrl);
httpRequest.enqueue(new okhttp3.Callback() {
@Override
public void onFailure(final Call call, final IOException ex) {
public void onFailure(@NonNull final Call call, @NonNull final IOException ex) {
callback.onError(ex);
}

@Override
public void onResponse(final Call call, final Response response) throws IOException {
public void onResponse(@NonNull final Call call, @NonNull final Response response) throws IOException {
if (response.isSuccessful()) {
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
Expand Down Expand Up @@ -178,11 +178,12 @@ private void parse(final Document xmlRootDoc) {
Element cube = (Element) node;
if (cube.hasAttribute("time")) { // a time Cube
final Date time = DATE_FORMAT.parse(cube.getAttribute("time"));
assert time != null;
date.setTime(time);
} else if (cube.hasAttribute("currency")
&& cube.hasAttribute("rate")) { // a rate Cube
String currency = cube.getAttribute("currency");
double rate = Double.valueOf(cube.getAttribute("rate"));
double rate = Double.parseDouble(cube.getAttribute("rate"));
entries.put(currency, rate);
} // else an empty Cube - ignore
}
Expand All @@ -191,13 +192,10 @@ private void parse(final Document xmlRootDoc) {
Timber.d(ex);
}
synchronized (this) {
if (date != null) {
fetchDate = Calendar.getInstance(TimeZone.getTimeZone("CET"));
fxDate = date;
fxEntries.clear();
fxEntries.putAll(entries);
}
// else don't change what we have
fetchDate = Calendar.getInstance(TimeZone.getTimeZone("CET"));
fxDate = date;
fxEntries.clear();
fxEntries.putAll(entries);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public ExchangeApiImpl() {
this(HttpUrl.parse("https://api.kraken.com/0/public/Ticker"));
}

@Override
public String getName() {
return "kraken";
}

@Override
public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
@NonNull final ExchangeCallback callback) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 [email protected]
* Copyright (c) 2019-2023 [email protected]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,31 +16,47 @@

// https://developer.android.com/training/basics/network-ops/xml

package com.m2049r.xmrwallet.service.exchange.krakenEcb;
package com.m2049r.xmrwallet.service.exchange.krakenFiat;

import androidx.annotation.NonNull;

import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.ServiceHelper;

import timber.log.Timber;

/*
Gets the XMR/EUR rate from kraken and then gets the EUR/fiat rate from the ECB
Gets the XMR/EUR rate from kraken and then gets the EUR/fiat rate from the yadio
*/

public class ExchangeApiImpl implements ExchangeApi {
static public final String BASE_FIAT = "EUR";

final ExchangeApi krakenApi = new com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl();

private ExchangeApi getFiatApi(String symbol) {
return ServiceHelper.getFiatApi(symbol);
}

@Override
public String getName() {
return krakenApi.getName() + "+";
}

public String getRealName(String fiatService) {
return getName() + fiatService;
}

@Override
public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
@NonNull final ExchangeCallback callback) {
Timber.d("B=%s Q=%s", baseCurrency, quoteCurrency);
if (baseCurrency.equals(quoteCurrency)) {
Timber.d("BASE=QUOTE=1");
callback.onSuccess(new ExchangeRateImpl(baseCurrency, quoteCurrency, 1.0));
callback.onSuccess(new ExchangeRateImpl(getName(), baseCurrency, quoteCurrency, 1.0));
return;
}

Expand All @@ -52,24 +68,21 @@ public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final

final String quote = Helper.BASE_CRYPTO.equals(baseCurrency) ? quoteCurrency : baseCurrency;

final ExchangeApi krakenApi =
new com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl();
krakenApi.queryExchangeRate(Helper.BASE_CRYPTO, BASE_FIAT, new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate krakenRate) {
Timber.d("kraken = %f", krakenRate.getRate());
final ExchangeApi ecbApi =
new com.m2049r.xmrwallet.service.exchange.ecb.ExchangeApiImpl();
ecbApi.queryExchangeRate(BASE_FIAT, quote, new ExchangeCallback() {
final ExchangeApi fiatApi = getFiatApi(quote);
fiatApi.queryExchangeRate(BASE_FIAT, quote, new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate ecbRate) {
Timber.d("ECB = %f", ecbRate.getRate());
double rate = ecbRate.getRate() * krakenRate.getRate();
public void onSuccess(final ExchangeRate fiatRate) {
Timber.d("FIAT = %f", fiatRate.getRate());
double rate = fiatRate.getRate() * krakenRate.getRate();
Timber.d("Q=%s QC=%s", quote, quoteCurrency);
if (!quote.equals(quoteCurrency)) rate = 1.0d / rate;
Timber.d("rate = %f", rate);
final ExchangeRate exchangeRate =
new ExchangeRateImpl(baseCurrency, quoteCurrency, rate);
new ExchangeRateImpl(getRealName(fiatApi.getName()), baseCurrency, quoteCurrency, rate);
callback.onSuccess(exchangeRate);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2019-2023 m2049r et al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.m2049r.xmrwallet.service.exchange.krakenFiat;

import androidx.annotation.NonNull;

import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;

import lombok.Getter;

class ExchangeRateImpl implements ExchangeRate {
@Getter
private final String serviceName;
@Getter
private final String baseCurrency;
@Getter
private final String quoteCurrency;
@Getter
private final double rate;

ExchangeRateImpl(@NonNull final String serviceName, @NonNull final String baseCurrency, @NonNull final String quoteCurrency, double rate) {
super();
this.serviceName = serviceName;
this.baseCurrency = baseCurrency;
this.quoteCurrency = quoteCurrency;
this.rate = rate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright (c) 2019 [email protected]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// https://developer.android.com/training/basics/network-ops/xml

package com.m2049r.xmrwallet.service.exchange.yadio;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeException;
import com.m2049r.xmrwallet.util.NetCipherHelper;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;

import okhttp3.Call;
import okhttp3.HttpUrl;
import okhttp3.Response;
import timber.log.Timber;

public class ExchangeApiImpl implements ExchangeApi {
@NonNull
private final HttpUrl baseUrl;

//so we can inject the mockserver url
@VisibleForTesting
public ExchangeApiImpl(@NonNull final HttpUrl baseUrl) {
this.baseUrl = baseUrl;
}

public ExchangeApiImpl() {
this(HttpUrl.parse("https://api.yadio.io/convert/1/eur"));
}

@Override
public String getName() {
return "yadio";
}

@Override
public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
@NonNull final ExchangeCallback callback) {
if (!baseCurrency.equals("EUR")) {
callback.onError(new IllegalArgumentException("Only EUR supported as base"));
return;
}

if (baseCurrency.equals(quoteCurrency)) {
callback.onSuccess(new ExchangeRateImpl(quoteCurrency, 1.0, 0));
return;
}

final HttpUrl url = baseUrl.newBuilder()
.addPathSegments(quoteCurrency.substring(0, 3))
.build();
final NetCipherHelper.Request httpRequest = new NetCipherHelper.Request(url);

httpRequest.enqueue(new okhttp3.Callback() {
@Override
public void onFailure(@NonNull final Call call, @NonNull final IOException ex) {
callback.onError(ex);
}

@Override
public void onResponse(@NonNull final Call call, @NonNull final Response response) throws IOException {
if (response.isSuccessful()) {
try {
assert response.body() != null;
final JSONObject json = new JSONObject(response.body().string());
if (json.has("error")) {
Timber.d("%d: %s", response.code(), json.getString("error"));
callback.onError(new ExchangeException(response.code(), json.getString("error")));
return;
}
double rate = json.getDouble("rate");
long timestamp = json.getLong("timestamp");
callback.onSuccess(new ExchangeRateImpl(quoteCurrency, rate, timestamp));
} catch (JSONException ex) {
callback.onError(ex);
}
} else {
callback.onError(new ExchangeException(response.code(), response.message()));
}
}
});
}
}
Loading

0 comments on commit a6e9d0e

Please sign in to comment.