diff --git a/crypto-currency/bitcoin/pom.xml b/crypto-currency/bitcoin/pom.xml
index 7f36c48..c8c716d 100644
--- a/crypto-currency/bitcoin/pom.xml
+++ b/crypto-currency/bitcoin/pom.xml
@@ -166,6 +166,11 @@
pom
compile
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.14
+
diff --git a/crypto-currency/bitcoin/src/main/java/org/javamoney/shelter/bitcoin/provider/CoinbaseRateProvider.java b/crypto-currency/bitcoin/src/main/java/org/javamoney/shelter/bitcoin/provider/CoinbaseRateProvider.java
new file mode 100644
index 0000000..68e4aa7
--- /dev/null
+++ b/crypto-currency/bitcoin/src/main/java/org/javamoney/shelter/bitcoin/provider/CoinbaseRateProvider.java
@@ -0,0 +1,93 @@
+package org.javamoney.shelter.bitcoin.provider;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.javamoney.moneta.convert.ExchangeRateBuilder;
+import org.javamoney.moneta.spi.AbstractRateProvider;
+import org.javamoney.moneta.spi.DefaultNumberValue;
+
+import javax.money.CurrencyUnit;
+import javax.money.MonetaryException;
+import javax.money.convert.*;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class CoinbaseRateProvider extends AbstractRateProvider {
+ private static final RateType RATE_TYPE = RateType.DEFERRED;
+ private static final ProviderContext CONTEXT = ProviderContextBuilder.of("CoinbaseRateProvider", RATE_TYPE)
+ .set("providerDescription", "Coinbase - Bitcoin exchange rate provider")
+ .build();
+ private static final String DEFAULT_BASE_CURRENCY = "BTC";
+
+ private final List supportedCurrencies = new ArrayList<>();
+ private final Map rates = new ConcurrentHashMap<>();
+
+ public CoinbaseRateProvider() {
+ super(CONTEXT);
+ loadSupportedCurrencies();
+ }
+
+ @Override
+ public ExchangeRate getExchangeRate(ConversionQuery conversionQuery) {
+ CurrencyUnit baseCurrency = conversionQuery.getBaseCurrency();
+ CurrencyUnit termCurrency = conversionQuery.getCurrency();
+ ConversionContext conversionContext = ConversionContext.of(getContext().getProviderName(), RATE_TYPE);
+
+ if (!DEFAULT_BASE_CURRENCY.equals(baseCurrency.getCurrencyCode())) {
+ throw new CurrencyConversionException(baseCurrency, termCurrency, conversionContext, "Base currency not supported: " + baseCurrency);
+ }
+
+ if (!supportedCurrencies.contains(termCurrency.getCurrencyCode())) {
+ throw new CurrencyConversionException(baseCurrency, termCurrency, conversionContext, "Term currency not supported: " + termCurrency);
+ }
+
+ loadRates();
+
+ Number rate = rates.get(termCurrency.getCurrencyCode());
+ if (rate == null) {
+ throw new CurrencyConversionException(baseCurrency, termCurrency, conversionContext, "Rate not available for currency: " + termCurrency);
+ }
+ return new ExchangeRateBuilder(conversionContext)
+ .setBase(baseCurrency)
+ .setTerm(termCurrency)
+ .setFactor(DefaultNumberValue.of(rate))
+ .build();
+ }
+
+ private void loadSupportedCurrencies() {
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()){
+ String url = "https://api.coinbase.com/v2/currencies";
+ HttpGet request = new HttpGet(url);
+ try (CloseableHttpResponse response = httpClient.execute(request)){
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode jsonNode = mapper.readTree(response.getEntity().getContent());
+ JsonNode dataNode = jsonNode.get("data");
+ dataNode.forEach(node -> supportedCurrencies.add(node.get("id").asText()));
+ }
+ } catch (IOException e) {
+ throw new MonetaryException("Failed to load supported currencies from Coinbase API", e);
+ }
+ }
+
+ private void loadRates() {
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()){
+ String url = "https://api.coinbase.com/v2/exchange-rates?currency=" + DEFAULT_BASE_CURRENCY;
+ HttpGet request = new HttpGet(url);
+ try (CloseableHttpResponse response = httpClient.execute(request)){
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode jsonNode = mapper.readTree(response.getEntity().getContent());
+ JsonNode ratesNode = jsonNode.get("data").get("rates");
+ ratesNode.fields().forEachRemaining(entry -> rates.put(entry.getKey(), entry.getValue().asDouble()));
+ }
+ } catch (IOException e) {
+ throw new MonetaryException("Failed to load exchange rates from Coinbase API", e);
+ }
+ }
+}
diff --git a/crypto-currency/bitcoin/src/test/java/org/javamoney/shelter/bitcoin/provider/CoinbaseRateProviderTest.java b/crypto-currency/bitcoin/src/test/java/org/javamoney/shelter/bitcoin/provider/CoinbaseRateProviderTest.java
new file mode 100644
index 0000000..fb22ac6
--- /dev/null
+++ b/crypto-currency/bitcoin/src/test/java/org/javamoney/shelter/bitcoin/provider/CoinbaseRateProviderTest.java
@@ -0,0 +1,43 @@
+package org.javamoney.shelter.bitcoin.provider;
+
+import org.javamoney.moneta.CurrencyUnitBuilder;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.money.convert.CurrencyConversionException;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+
+public class CoinbaseRateProviderTest {
+ private static CoinbaseRateProvider coinbaseRateProvider;
+
+ @BeforeClass
+ public static void setUpBeforeClass() {
+ coinbaseRateProvider = new CoinbaseRateProvider();
+ CurrencyUnitBuilder.of("BTC", "CoinbaseRateProvider")
+ .setDefaultFractionDigits(8)
+ .build(true);
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() {
+ coinbaseRateProvider = null;
+ }
+
+ @Test
+ public void testGetExchangeRate() {
+ assertNotNull(coinbaseRateProvider.getExchangeRate("BTC", "USD"));
+ }
+
+ @Test
+ public void testGetExchangeRateWithNotSupportedBaseCurrency() {
+ assertThrows(CurrencyConversionException.class, () -> coinbaseRateProvider.getExchangeRate("USD", "BTC"));
+ }
+
+ @Test
+ public void testGetExchangeRateWithNotSupportedTermCurrency() {
+ assertThrows(CurrencyConversionException.class, () -> coinbaseRateProvider.getExchangeRate("BTC", "ZWL"));
+ }
+}
\ No newline at end of file