Add CoinGecko to CurrencyConversionManager
This commit is contained in:
parent
3ceaa8bd20
commit
5cc76f48aa
|
@ -87,7 +87,7 @@ backupsZkConfig.serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijkl
|
||||||
|
|
||||||
paymentsService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # base64-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
|
paymentsService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # base64-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
|
||||||
paymentsService.fixerApiKey: unset
|
paymentsService.fixerApiKey: unset
|
||||||
paymentsService.coinMarketCapApiKey: unset
|
paymentsService.coinGeckoApiKey: unset
|
||||||
|
|
||||||
currentReportingKey.secret: AAAAAAAAAAA=
|
currentReportingKey.secret: AAAAAAAAAAA=
|
||||||
currentReportingKey.salt: AAAAAAAAAAA=
|
currentReportingKey.salt: AAAAAAAAAAA=
|
||||||
|
|
|
@ -327,9 +327,9 @@ paymentsService:
|
||||||
- MOB
|
- MOB
|
||||||
externalClients:
|
externalClients:
|
||||||
fixerApiKey: secret://paymentsService.fixerApiKey
|
fixerApiKey: secret://paymentsService.fixerApiKey
|
||||||
coinMarketCapApiKey: secret://paymentsService.coinMarketCapApiKey
|
coinGeckoApiKey: secret://paymentsService.coinGeckoApiKey
|
||||||
coinMarketCapCurrencyIds:
|
coinGeckoCurrencyIds:
|
||||||
MOB: 7878
|
MOB: mobilecoin
|
||||||
|
|
||||||
badges:
|
badges:
|
||||||
badges:
|
badges:
|
||||||
|
|
|
@ -133,7 +133,7 @@ import org.whispersystems.textsecuregcm.controllers.SecureValueRecovery2Controll
|
||||||
import org.whispersystems.textsecuregcm.controllers.StickerController;
|
import org.whispersystems.textsecuregcm.controllers.StickerController;
|
||||||
import org.whispersystems.textsecuregcm.controllers.SubscriptionController;
|
import org.whispersystems.textsecuregcm.controllers.SubscriptionController;
|
||||||
import org.whispersystems.textsecuregcm.controllers.VerificationController;
|
import org.whispersystems.textsecuregcm.controllers.VerificationController;
|
||||||
import org.whispersystems.textsecuregcm.currency.CoinMarketCapClient;
|
import org.whispersystems.textsecuregcm.currency.CoinGeckoClient;
|
||||||
import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager;
|
import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager;
|
||||||
import org.whispersystems.textsecuregcm.currency.FixerClient;
|
import org.whispersystems.textsecuregcm.currency.FixerClient;
|
||||||
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
|
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
|
||||||
|
@ -698,9 +698,9 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||||
HttpClient currencyClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build();
|
HttpClient currencyClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build();
|
||||||
FixerClient fixerClient = config.getPaymentsServiceConfiguration().externalClients()
|
FixerClient fixerClient = config.getPaymentsServiceConfiguration().externalClients()
|
||||||
.buildFixerClient(currencyClient);
|
.buildFixerClient(currencyClient);
|
||||||
CoinMarketCapClient coinMarketCapClient = config.getPaymentsServiceConfiguration().externalClients()
|
CoinGeckoClient coinGeckoClient = config.getPaymentsServiceConfiguration().externalClients()
|
||||||
.buildCoinMarketCapClient(currencyClient);
|
.buildCoinGeckoClient(currencyClient);
|
||||||
CurrencyConversionManager currencyManager = new CurrencyConversionManager(fixerClient, coinMarketCapClient,
|
CurrencyConversionManager currencyManager = new CurrencyConversionManager(fixerClient, coinGeckoClient,
|
||||||
cacheCluster, config.getPaymentsServiceConfiguration().paymentCurrencies(), recurringJobExecutor, Clock.systemUTC());
|
cacheCluster, config.getPaymentsServiceConfiguration().paymentCurrencies(), recurringJobExecutor, Clock.systemUTC());
|
||||||
VirtualThreadPinEventMonitor virtualThreadPinEventMonitor = new VirtualThreadPinEventMonitor(
|
VirtualThreadPinEventMonitor virtualThreadPinEventMonitor = new VirtualThreadPinEventMonitor(
|
||||||
virtualThreadEventLoggerExecutor,
|
virtualThreadEventLoggerExecutor,
|
||||||
|
|
|
@ -12,13 +12,13 @@ import jakarta.validation.constraints.NotNull;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
|
import org.whispersystems.textsecuregcm.configuration.secrets.SecretString;
|
||||||
import org.whispersystems.textsecuregcm.currency.CoinMarketCapClient;
|
import org.whispersystems.textsecuregcm.currency.CoinGeckoClient;
|
||||||
import org.whispersystems.textsecuregcm.currency.FixerClient;
|
import org.whispersystems.textsecuregcm.currency.FixerClient;
|
||||||
|
|
||||||
@JsonTypeName("default")
|
@JsonTypeName("default")
|
||||||
public record PaymentsServiceClientsConfiguration(@NotNull SecretString coinMarketCapApiKey,
|
public record PaymentsServiceClientsConfiguration(@NotNull SecretString coinGeckoApiKey,
|
||||||
@NotNull SecretString fixerApiKey,
|
@NotNull SecretString fixerApiKey,
|
||||||
@NotEmpty Map<@NotBlank String, Integer> coinMarketCapCurrencyIds) implements
|
@NotEmpty Map<@NotBlank String, String> coinGeckoCurrencyIds) implements
|
||||||
PaymentsServiceClientsFactory {
|
PaymentsServiceClientsFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -27,7 +27,7 @@ public record PaymentsServiceClientsConfiguration(@NotNull SecretString coinMark
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CoinMarketCapClient buildCoinMarketCapClient(final HttpClient httpClient) {
|
public CoinGeckoClient buildCoinGeckoClient(final HttpClient httpClient) {
|
||||||
return new CoinMarketCapClient(httpClient, coinMarketCapApiKey.value(), coinMarketCapCurrencyIds);
|
return new CoinGeckoClient(httpClient, coinGeckoApiKey.value(), coinGeckoCurrencyIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ package org.whispersystems.textsecuregcm.configuration;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
import io.dropwizard.jackson.Discoverable;
|
import io.dropwizard.jackson.Discoverable;
|
||||||
import org.whispersystems.textsecuregcm.currency.CoinMarketCapClient;
|
import org.whispersystems.textsecuregcm.currency.CoinGeckoClient;
|
||||||
import org.whispersystems.textsecuregcm.currency.FixerClient;
|
import org.whispersystems.textsecuregcm.currency.FixerClient;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
|
|
||||||
|
@ -16,5 +16,5 @@ public interface PaymentsServiceClientsFactory extends Discoverable {
|
||||||
|
|
||||||
FixerClient buildFixerClient(final HttpClient httpClient);
|
FixerClient buildFixerClient(final HttpClient httpClient);
|
||||||
|
|
||||||
CoinMarketCapClient buildCoinMarketCapClient(HttpClient httpClient);
|
CoinGeckoClient buildCoinGeckoClient(HttpClient httpClient);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
|
|
||||||
package org.whispersystems.textsecuregcm.currency;
|
package org.whispersystems.textsecuregcm.currency;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
@ -14,26 +14,23 @@ import java.net.URI;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||||
|
|
||||||
public class CoinMarketCapClient {
|
public class CoinGeckoClient {
|
||||||
|
|
||||||
private final HttpClient httpClient;
|
private final HttpClient httpClient;
|
||||||
private final String apiKey;
|
private final String apiKey;
|
||||||
private final Map<String, Integer> currencyIdsBySymbol;
|
private final Map<String, String> currencyIdsBySymbol;
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CoinMarketCapClient.class);
|
private static final Logger logger = LoggerFactory.getLogger(CoinGeckoClient.class);
|
||||||
|
|
||||||
record CoinMarketCapResponse(@JsonProperty("data") PriceConversionResponse priceConversionResponse) {};
|
private static final TypeReference<Map<String, Map<String, BigDecimal>>> RESPONSE_TYPE = new TypeReference<>() {};
|
||||||
|
|
||||||
record PriceConversionResponse(int id, String symbol, Map<String, PriceConversionQuote> quote) {};
|
public CoinGeckoClient(final HttpClient httpClient, final String apiKey, final Map<String, String> currencyIdsBySymbol) {
|
||||||
|
|
||||||
record PriceConversionQuote(BigDecimal price) {};
|
|
||||||
|
|
||||||
public CoinMarketCapClient(final HttpClient httpClient, final String apiKey, final Map<String, Integer> currencyIdsBySymbol) {
|
|
||||||
this.httpClient = httpClient;
|
this.httpClient = httpClient;
|
||||||
this.apiKey = apiKey;
|
this.apiKey = apiKey;
|
||||||
this.currencyIdsBySymbol = currencyIdsBySymbol;
|
this.currencyIdsBySymbol = currencyIdsBySymbol;
|
||||||
|
@ -45,40 +42,41 @@ public class CoinMarketCapClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
final URI quoteUri = URI.create(
|
final URI quoteUri = URI.create(
|
||||||
String.format("https://pro-api.coinmarketcap.com/v2/tools/price-conversion?amount=1&id=%d&convert=%s",
|
String.format("https://pro-api.coingecko.com/api/v3/simple/price?ids=%s&vs_currencies=%s",
|
||||||
currencyIdsBySymbol.get(currency), base));
|
currencyIdsBySymbol.get(currency), base.toLowerCase(Locale.ROOT)));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final HttpResponse<String> response = httpClient.send(HttpRequest.newBuilder()
|
final HttpResponse<String> response = httpClient.send(HttpRequest.newBuilder()
|
||||||
.GET()
|
.GET()
|
||||||
.uri(quoteUri)
|
.uri(quoteUri)
|
||||||
.header("X-CMC_PRO_API_KEY", apiKey)
|
.header("Accept", "application/json")
|
||||||
.build(),
|
.header("x-cg-pro-api-key", apiKey)
|
||||||
HttpResponse.BodyHandlers.ofString());
|
.build(),
|
||||||
|
HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
if (response.statusCode() < 200 || response.statusCode() >= 300) {
|
if (response.statusCode() < 200 || response.statusCode() >= 300) {
|
||||||
logger.warn("CoinMarketCapRequest failed with response: {}", response);
|
logger.warn("CoinGecko request failed with response: {}", response);
|
||||||
throw new IOException("CoinMarketCap request failed with status code " + response.statusCode());
|
throw new IOException("CoinGecko request failed with status code " + response.statusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
return extractConversionRate(parseResponse(response.body()), base);
|
return extractConversionRate(parseResponse(response.body()).get(currencyIdsBySymbol.get(currency)), base.toLowerCase(Locale.ROOT));
|
||||||
} catch (final InterruptedException e) {
|
} catch (final InterruptedException e) {
|
||||||
throw new IOException("Interrupted while waiting for a response", e);
|
throw new IOException("Interrupted while waiting for a response", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static CoinMarketCapResponse parseResponse(final String responseJson) throws JsonProcessingException {
|
static Map<String, Map<String,BigDecimal>> parseResponse(final String responseJson) throws JsonProcessingException {
|
||||||
return SystemMapper.jsonMapper().readValue(responseJson, CoinMarketCapResponse.class);
|
return SystemMapper.jsonMapper().readValue(responseJson, RESPONSE_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static BigDecimal extractConversionRate(final CoinMarketCapResponse response, final String destinationCurrency)
|
static BigDecimal extractConversionRate(final Map<String,BigDecimal> response, final String destinationCurrency)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (!response.priceConversionResponse().quote.containsKey(destinationCurrency)) {
|
if (!response.containsKey(destinationCurrency)) {
|
||||||
throw new IOException("Response does not contain conversion rate for " + destinationCurrency);
|
throw new IOException("Response does not contain conversion rate for " + destinationCurrency);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.priceConversionResponse().quote.get(destinationCurrency).price();
|
return response.get(destinationCurrency);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -36,16 +36,16 @@ public class CurrencyConversionManager implements Managed {
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final Duration FIXER_REFRESH_INTERVAL = Duration.ofHours(2);
|
static final Duration FIXER_REFRESH_INTERVAL = Duration.ofHours(2);
|
||||||
|
|
||||||
private static final Duration COIN_MARKET_CAP_REFRESH_INTERVAL = Duration.ofMinutes(5);
|
private static final Duration COIN_GECKO_CAP_REFRESH_INTERVAL = Duration.ofMinutes(5);
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final String COIN_MARKET_CAP_SHARED_CACHE_CURRENT_KEY = "CurrencyConversionManager::CoinMarketCapCacheCurrent";
|
static final String COIN_GECKO_CAP_SHARED_CACHE_CURRENT_KEY = "CurrencyConversionManager::CoinGeckoCacheCurrent";
|
||||||
|
|
||||||
private static final String COIN_MARKET_CAP_SHARED_CACHE_DATA_KEY = "CurrencyConversionManager::CoinMarketCapCacheData";
|
private static final String COIN_GECKO_SHARED_CACHE_DATA_KEY = "CurrencyConversionManager::CoinGeckoCacheData";
|
||||||
|
|
||||||
private final FixerClient fixerClient;
|
private final FixerClient fixerClient;
|
||||||
|
|
||||||
private final CoinMarketCapClient coinMarketCapClient;
|
private final CoinGeckoClient coinGeckoClient;
|
||||||
|
|
||||||
private final FaultTolerantRedisClusterClient cacheCluster;
|
private final FaultTolerantRedisClusterClient cacheCluster;
|
||||||
|
|
||||||
|
@ -61,18 +61,18 @@ public class CurrencyConversionManager implements Managed {
|
||||||
|
|
||||||
private Map<String, BigDecimal> cachedFixerValues;
|
private Map<String, BigDecimal> cachedFixerValues;
|
||||||
|
|
||||||
private Map<String, BigDecimal> cachedCoinMarketCapValues;
|
private Map<String, BigDecimal> cachedCoinGeckoValues;
|
||||||
|
|
||||||
|
|
||||||
public CurrencyConversionManager(
|
public CurrencyConversionManager(
|
||||||
final FixerClient fixerClient,
|
final FixerClient fixerClient,
|
||||||
final CoinMarketCapClient coinMarketCapClient,
|
final CoinGeckoClient coinGeckoClient,
|
||||||
final FaultTolerantRedisClusterClient cacheCluster,
|
final FaultTolerantRedisClusterClient cacheCluster,
|
||||||
final List<String> currencies,
|
final List<String> currencies,
|
||||||
final ScheduledExecutorService executor,
|
final ScheduledExecutorService executor,
|
||||||
final Clock clock) {
|
final Clock clock) {
|
||||||
this.fixerClient = fixerClient;
|
this.fixerClient = fixerClient;
|
||||||
this.coinMarketCapClient = coinMarketCapClient;
|
this.coinGeckoClient = coinGeckoClient;
|
||||||
this.cacheCluster = cacheCluster;
|
this.cacheCluster = cacheCluster;
|
||||||
this.currencies = currencies;
|
this.currencies = currencies;
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
|
@ -102,49 +102,49 @@ public class CurrencyConversionManager implements Managed {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
final Map<String, BigDecimal> coinMarketCapValuesFromSharedCache = cacheCluster.withCluster(connection -> {
|
final Map<String, BigDecimal> CoinGeckoValuesFromSharedCache = cacheCluster.withCluster(connection -> {
|
||||||
final Map<String, BigDecimal> parsedSharedCacheData = new HashMap<>();
|
final Map<String, BigDecimal> parsedSharedCacheData = new HashMap<>();
|
||||||
|
|
||||||
connection.sync().hgetall(COIN_MARKET_CAP_SHARED_CACHE_DATA_KEY).forEach((currency, conversionRate) ->
|
connection.sync().hgetall(COIN_GECKO_SHARED_CACHE_DATA_KEY).forEach((currency, conversionRate) ->
|
||||||
parsedSharedCacheData.put(currency, new BigDecimal(conversionRate)));
|
parsedSharedCacheData.put(currency, new BigDecimal(conversionRate)));
|
||||||
|
|
||||||
return parsedSharedCacheData;
|
return parsedSharedCacheData;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (coinMarketCapValuesFromSharedCache != null && !coinMarketCapValuesFromSharedCache.isEmpty()) {
|
if (CoinGeckoValuesFromSharedCache != null && !CoinGeckoValuesFromSharedCache.isEmpty()) {
|
||||||
cachedCoinMarketCapValues = coinMarketCapValuesFromSharedCache;
|
cachedCoinGeckoValues = CoinGeckoValuesFromSharedCache;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean shouldUpdateSharedCache = cacheCluster.withCluster(connection ->
|
final boolean shouldUpdateSharedCache = cacheCluster.withCluster(connection ->
|
||||||
"OK".equals(connection.sync().set(COIN_MARKET_CAP_SHARED_CACHE_CURRENT_KEY,
|
"OK".equals(connection.sync().set(COIN_GECKO_CAP_SHARED_CACHE_CURRENT_KEY,
|
||||||
"true",
|
"true",
|
||||||
SetArgs.Builder.nx().ex(COIN_MARKET_CAP_REFRESH_INTERVAL))));
|
SetArgs.Builder.nx().ex(COIN_GECKO_CAP_REFRESH_INTERVAL))));
|
||||||
|
|
||||||
if (shouldUpdateSharedCache || cachedCoinMarketCapValues == null) {
|
if (shouldUpdateSharedCache || cachedCoinGeckoValues == null) {
|
||||||
final Map<String, BigDecimal> conversionRatesFromCoinMarketCap = new HashMap<>(currencies.size());
|
final Map<String, BigDecimal> conversionRatesFromCoinGecko = new HashMap<>(currencies.size());
|
||||||
|
|
||||||
for (final String currency : currencies) {
|
for (final String currency : currencies) {
|
||||||
conversionRatesFromCoinMarketCap.put(currency, coinMarketCapClient.getSpotPrice(currency, "USD"));
|
conversionRatesFromCoinGecko.put(currency, coinGeckoClient.getSpotPrice(currency, "USD"));
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedCoinMarketCapValues = conversionRatesFromCoinMarketCap;
|
cachedCoinGeckoValues = conversionRatesFromCoinGecko;
|
||||||
|
|
||||||
if (shouldUpdateSharedCache) {
|
if (shouldUpdateSharedCache) {
|
||||||
cacheCluster.useCluster(connection -> {
|
cacheCluster.useCluster(connection -> {
|
||||||
final Map<String, String> sharedCoinMarketCapValues = new HashMap<>();
|
final Map<String, String> sharedCoinGeckoValues = new HashMap<>();
|
||||||
|
|
||||||
cachedCoinMarketCapValues.forEach((currency, conversionRate) ->
|
cachedCoinGeckoValues.forEach((currency, conversionRate) ->
|
||||||
sharedCoinMarketCapValues.put(currency, conversionRate.toString()));
|
sharedCoinGeckoValues.put(currency, conversionRate.toString()));
|
||||||
|
|
||||||
connection.sync().hset(COIN_MARKET_CAP_SHARED_CACHE_DATA_KEY, sharedCoinMarketCapValues);
|
connection.sync().hset(COIN_GECKO_SHARED_CACHE_DATA_KEY, sharedCoinGeckoValues);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<CurrencyConversionEntity> entities = new LinkedList<>();
|
List<CurrencyConversionEntity> entities = new LinkedList<>();
|
||||||
|
|
||||||
for (Map.Entry<String, BigDecimal> currency : cachedCoinMarketCapValues.entrySet()) {
|
for (Map.Entry<String, BigDecimal> currency : cachedCoinGeckoValues.entrySet()) {
|
||||||
BigDecimal usdValue = stripTrailingZerosAfterDecimal(currency.getValue());
|
BigDecimal usdValue = stripTrailingZerosAfterDecimal(currency.getValue());
|
||||||
|
|
||||||
Map<String, BigDecimal> values = new HashMap<>();
|
Map<String, BigDecimal> values = new HashMap<>();
|
||||||
|
|
|
@ -10,7 +10,7 @@ import java.math.BigDecimal;
|
||||||
import java.net.http.HttpClient;
|
import java.net.http.HttpClient;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.whispersystems.textsecuregcm.currency.CoinMarketCapClient;
|
import org.whispersystems.textsecuregcm.currency.CoinGeckoClient;
|
||||||
import org.whispersystems.textsecuregcm.currency.FixerClient;
|
import org.whispersystems.textsecuregcm.currency.FixerClient;
|
||||||
|
|
||||||
@JsonTypeName("stub")
|
@JsonTypeName("stub")
|
||||||
|
@ -22,8 +22,8 @@ public class StubPaymentsServiceClientsFactory implements PaymentsServiceClients
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CoinMarketCapClient buildCoinMarketCapClient(final HttpClient httpClient) {
|
public CoinGeckoClient buildCoinGeckoClient(final HttpClient httpClient) {
|
||||||
return new StubCoinMarketCapClient();
|
return new StubCoinGeckoClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,9 +44,9 @@ public class StubPaymentsServiceClientsFactory implements PaymentsServiceClients
|
||||||
/**
|
/**
|
||||||
* Always returns {@code 0} for spot price checks
|
* Always returns {@code 0} for spot price checks
|
||||||
*/
|
*/
|
||||||
private static class StubCoinMarketCapClient extends CoinMarketCapClient {
|
private static class StubCoinGeckoClient extends CoinGeckoClient {
|
||||||
|
|
||||||
public StubCoinMarketCapClient() {
|
public StubCoinGeckoClient() {
|
||||||
super(null, null, null);
|
super(null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package org.whispersystems.textsecuregcm.currency;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class CoinGeckoClientTest {
|
||||||
|
|
||||||
|
private static final String RESPONSE_JSON = """
|
||||||
|
{
|
||||||
|
"mobilecoin": {
|
||||||
|
"usd": 0.226212
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseResponse() throws JsonProcessingException {
|
||||||
|
final Map<String, Map<String, BigDecimal>> parsedResponse = CoinGeckoClient.parseResponse(RESPONSE_JSON);
|
||||||
|
|
||||||
|
assertTrue(parsedResponse.containsKey("mobilecoin"));
|
||||||
|
|
||||||
|
assertEquals(1, parsedResponse.get("mobilecoin").size());
|
||||||
|
assertEquals(new BigDecimal("0.226212"), parsedResponse.get("mobilecoin").get("usd"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void extractConversionRate() throws IOException {
|
||||||
|
final Map<String, Map<String, BigDecimal>> parsedResponse = CoinGeckoClient.parseResponse(RESPONSE_JSON);
|
||||||
|
|
||||||
|
assertEquals(new BigDecimal("0.226212"), CoinGeckoClient.extractConversionRate(parsedResponse.get("mobilecoin"), "usd"));
|
||||||
|
assertThrows(IOException.class, () -> CoinGeckoClient.extractConversionRate(parsedResponse.get("mobilecoin"), "CAD"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,61 +0,0 @@
|
||||||
package org.whispersystems.textsecuregcm.currency;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
class CoinMarketCapClientTest {
|
|
||||||
|
|
||||||
private static final String RESPONSE_JSON = """
|
|
||||||
{
|
|
||||||
"status": {
|
|
||||||
"timestamp": "2022-11-09T17:15:06.356Z",
|
|
||||||
"error_code": 0,
|
|
||||||
"error_message": null,
|
|
||||||
"elapsed": 41,
|
|
||||||
"credit_count": 1,
|
|
||||||
"notice": null
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"id": 7878,
|
|
||||||
"symbol": "MOB",
|
|
||||||
"name": "MobileCoin",
|
|
||||||
"amount": 1,
|
|
||||||
"last_updated": "2022-11-09T17:14:00.000Z",
|
|
||||||
"quote": {
|
|
||||||
"USD": {
|
|
||||||
"price": 0.6625319895827952,
|
|
||||||
"last_updated": "2022-11-09T17:14:00.000Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void parseResponse() throws JsonProcessingException {
|
|
||||||
final CoinMarketCapClient.CoinMarketCapResponse parsedResponse = CoinMarketCapClient.parseResponse(RESPONSE_JSON);
|
|
||||||
|
|
||||||
assertEquals(7878, parsedResponse.priceConversionResponse().id());
|
|
||||||
assertEquals("MOB", parsedResponse.priceConversionResponse().symbol());
|
|
||||||
|
|
||||||
final Map<String, CoinMarketCapClient.PriceConversionQuote> quote =
|
|
||||||
parsedResponse.priceConversionResponse().quote();
|
|
||||||
|
|
||||||
assertEquals(1, quote.size());
|
|
||||||
assertEquals(new BigDecimal("0.6625319895827952"), quote.get("USD").price());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void extractConversionRate() throws IOException {
|
|
||||||
final CoinMarketCapClient.CoinMarketCapResponse parsedResponse = CoinMarketCapClient.parseResponse(RESPONSE_JSON);
|
|
||||||
|
|
||||||
assertEquals(new BigDecimal("0.6625319895827952"), CoinMarketCapClient.extractConversionRate(parsedResponse, "USD"));
|
|
||||||
assertThrows(IOException.class, () -> CoinMarketCapClient.extractConversionRate(parsedResponse, "CAD"));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -38,16 +38,16 @@ class CurrencyConversionManagerTest {
|
||||||
@Test
|
@Test
|
||||||
void testCurrencyCalculations() throws IOException {
|
void testCurrencyCalculations() throws IOException {
|
||||||
FixerClient fixerClient = mock(FixerClient.class);
|
FixerClient fixerClient = mock(FixerClient.class);
|
||||||
CoinMarketCapClient coinMarketCapClient = mock(CoinMarketCapClient.class);
|
CoinGeckoClient coinGeckoClient = mock(CoinGeckoClient.class);
|
||||||
|
|
||||||
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
|
when(coinGeckoClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
|
||||||
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
|
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
|
||||||
"EUR", new BigDecimal("0.822876"),
|
"EUR", new BigDecimal("0.822876"),
|
||||||
"FJD", new BigDecimal("2.0577"),
|
"FJD", new BigDecimal("2.0577"),
|
||||||
"FKP", new BigDecimal("0.743446")
|
"FKP", new BigDecimal("0.743446")
|
||||||
));
|
));
|
||||||
|
|
||||||
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, coinMarketCapClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, coinGeckoClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
||||||
List.of("FOO"), EXECUTOR, Clock.systemUTC());
|
List.of("FOO"), EXECUTOR, Clock.systemUTC());
|
||||||
|
|
||||||
manager.updateCacheIfNecessary();
|
manager.updateCacheIfNecessary();
|
||||||
|
@ -66,9 +66,9 @@ class CurrencyConversionManagerTest {
|
||||||
@Test
|
@Test
|
||||||
void testCurrencyCalculations_noTrailingZeros() throws IOException {
|
void testCurrencyCalculations_noTrailingZeros() throws IOException {
|
||||||
FixerClient fixerClient = mock(FixerClient.class);
|
FixerClient fixerClient = mock(FixerClient.class);
|
||||||
CoinMarketCapClient coinMarketCapClient = mock(CoinMarketCapClient.class);
|
CoinGeckoClient CoinGeckoClient = mock(CoinGeckoClient.class);
|
||||||
|
|
||||||
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("1.00000"));
|
when(CoinGeckoClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("1.00000"));
|
||||||
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
|
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
|
||||||
"EUR", new BigDecimal("0.200000"),
|
"EUR", new BigDecimal("0.200000"),
|
||||||
"FJD", new BigDecimal("3.00000"),
|
"FJD", new BigDecimal("3.00000"),
|
||||||
|
@ -76,7 +76,7 @@ class CurrencyConversionManagerTest {
|
||||||
"CAD", new BigDecimal("700.000")
|
"CAD", new BigDecimal("700.000")
|
||||||
));
|
));
|
||||||
|
|
||||||
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, coinMarketCapClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, CoinGeckoClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
||||||
List.of("FOO"), EXECUTOR, Clock.systemUTC());
|
List.of("FOO"), EXECUTOR, Clock.systemUTC());
|
||||||
|
|
||||||
manager.updateCacheIfNecessary();
|
manager.updateCacheIfNecessary();
|
||||||
|
@ -96,16 +96,16 @@ class CurrencyConversionManagerTest {
|
||||||
@Test
|
@Test
|
||||||
void testCurrencyCalculations_accuracy() throws IOException {
|
void testCurrencyCalculations_accuracy() throws IOException {
|
||||||
FixerClient fixerClient = mock(FixerClient.class);
|
FixerClient fixerClient = mock(FixerClient.class);
|
||||||
CoinMarketCapClient coinMarketCapClient = mock(CoinMarketCapClient.class);
|
CoinGeckoClient CoinGeckoClient = mock(CoinGeckoClient.class);
|
||||||
|
|
||||||
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("0.999999"));
|
when(CoinGeckoClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("0.999999"));
|
||||||
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
|
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
|
||||||
"EUR", new BigDecimal("1.000001"),
|
"EUR", new BigDecimal("1.000001"),
|
||||||
"FJD", new BigDecimal("0.000001"),
|
"FJD", new BigDecimal("0.000001"),
|
||||||
"FKP", new BigDecimal("1")
|
"FKP", new BigDecimal("1")
|
||||||
));
|
));
|
||||||
|
|
||||||
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, coinMarketCapClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, CoinGeckoClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
||||||
List.of("FOO"), EXECUTOR, Clock.systemUTC());
|
List.of("FOO"), EXECUTOR, Clock.systemUTC());
|
||||||
|
|
||||||
manager.updateCacheIfNecessary();
|
manager.updateCacheIfNecessary();
|
||||||
|
@ -125,21 +125,21 @@ class CurrencyConversionManagerTest {
|
||||||
@Test
|
@Test
|
||||||
void testCurrencyCalculationsTimeoutNoRun() throws IOException {
|
void testCurrencyCalculationsTimeoutNoRun() throws IOException {
|
||||||
FixerClient fixerClient = mock(FixerClient.class);
|
FixerClient fixerClient = mock(FixerClient.class);
|
||||||
CoinMarketCapClient coinMarketCapClient = mock(CoinMarketCapClient.class);
|
CoinGeckoClient CoinGeckoClient = mock(CoinGeckoClient.class);
|
||||||
|
|
||||||
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
|
when(CoinGeckoClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
|
||||||
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
|
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
|
||||||
"EUR", new BigDecimal("0.822876"),
|
"EUR", new BigDecimal("0.822876"),
|
||||||
"FJD", new BigDecimal("2.0577"),
|
"FJD", new BigDecimal("2.0577"),
|
||||||
"FKP", new BigDecimal("0.743446")
|
"FKP", new BigDecimal("0.743446")
|
||||||
));
|
));
|
||||||
|
|
||||||
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, coinMarketCapClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, CoinGeckoClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
||||||
List.of("FOO"), EXECUTOR, Clock.systemUTC());
|
List.of("FOO"), EXECUTOR, Clock.systemUTC());
|
||||||
|
|
||||||
manager.updateCacheIfNecessary();
|
manager.updateCacheIfNecessary();
|
||||||
|
|
||||||
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("3.50"));
|
when(CoinGeckoClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("3.50"));
|
||||||
|
|
||||||
manager.updateCacheIfNecessary();
|
manager.updateCacheIfNecessary();
|
||||||
|
|
||||||
|
@ -155,26 +155,26 @@ class CurrencyConversionManagerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCurrencyCalculationsCoinMarketCapTimeoutWithRun() throws IOException {
|
void testCurrencyCalculationsCoinGeckoTimeoutWithRun() throws IOException {
|
||||||
FixerClient fixerClient = mock(FixerClient.class);
|
FixerClient fixerClient = mock(FixerClient.class);
|
||||||
CoinMarketCapClient coinMarketCapClient = mock(CoinMarketCapClient.class);
|
CoinGeckoClient CoinGeckoClient = mock(CoinGeckoClient.class);
|
||||||
|
|
||||||
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
|
when(CoinGeckoClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
|
||||||
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
|
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
|
||||||
"EUR", new BigDecimal("0.822876"),
|
"EUR", new BigDecimal("0.822876"),
|
||||||
"FJD", new BigDecimal("2.0577"),
|
"FJD", new BigDecimal("2.0577"),
|
||||||
"FKP", new BigDecimal("0.743446")
|
"FKP", new BigDecimal("0.743446")
|
||||||
));
|
));
|
||||||
|
|
||||||
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, coinMarketCapClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, CoinGeckoClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
||||||
List.of("FOO"), EXECUTOR, Clock.systemUTC());
|
List.of("FOO"), EXECUTOR, Clock.systemUTC());
|
||||||
|
|
||||||
manager.updateCacheIfNecessary();
|
manager.updateCacheIfNecessary();
|
||||||
|
|
||||||
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection ->
|
REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection ->
|
||||||
connection.sync().del(CurrencyConversionManager.COIN_MARKET_CAP_SHARED_CACHE_CURRENT_KEY));
|
connection.sync().del(CurrencyConversionManager.COIN_GECKO_CAP_SHARED_CACHE_CURRENT_KEY));
|
||||||
|
|
||||||
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("3.50"));
|
when(CoinGeckoClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("3.50"));
|
||||||
manager.updateCacheIfNecessary();
|
manager.updateCacheIfNecessary();
|
||||||
|
|
||||||
CurrencyConversionEntityList conversions = manager.getCurrencyConversions().orElseThrow();
|
CurrencyConversionEntityList conversions = manager.getCurrencyConversions().orElseThrow();
|
||||||
|
@ -192,9 +192,9 @@ class CurrencyConversionManagerTest {
|
||||||
@Test
|
@Test
|
||||||
void testCurrencyCalculationsFixerTimeoutWithRun() throws IOException {
|
void testCurrencyCalculationsFixerTimeoutWithRun() throws IOException {
|
||||||
FixerClient fixerClient = mock(FixerClient.class);
|
FixerClient fixerClient = mock(FixerClient.class);
|
||||||
CoinMarketCapClient coinMarketCapClient = mock(CoinMarketCapClient.class);
|
CoinGeckoClient CoinGeckoClient = mock(CoinGeckoClient.class);
|
||||||
|
|
||||||
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
|
when(CoinGeckoClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("2.35"));
|
||||||
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
|
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
|
||||||
"EUR", new BigDecimal("0.822876"),
|
"EUR", new BigDecimal("0.822876"),
|
||||||
"FJD", new BigDecimal("2.0577"),
|
"FJD", new BigDecimal("2.0577"),
|
||||||
|
@ -207,12 +207,12 @@ class CurrencyConversionManagerTest {
|
||||||
when(clock.instant()).thenReturn(currentTime);
|
when(clock.instant()).thenReturn(currentTime);
|
||||||
when(clock.millis()).thenReturn(currentTime.toEpochMilli());
|
when(clock.millis()).thenReturn(currentTime.toEpochMilli());
|
||||||
|
|
||||||
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, coinMarketCapClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
CurrencyConversionManager manager = new CurrencyConversionManager(fixerClient, CoinGeckoClient, REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
||||||
List.of("FOO"), EXECUTOR, clock);
|
List.of("FOO"), EXECUTOR, clock);
|
||||||
|
|
||||||
manager.updateCacheIfNecessary();
|
manager.updateCacheIfNecessary();
|
||||||
|
|
||||||
when(coinMarketCapClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("3.50"));
|
when(CoinGeckoClient.getSpotPrice(eq("FOO"), eq("USD"))).thenReturn(new BigDecimal("3.50"));
|
||||||
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
|
when(fixerClient.getConversionsForBase(eq("USD"))).thenReturn(Map.of(
|
||||||
"EUR", new BigDecimal("0.922876"),
|
"EUR", new BigDecimal("0.922876"),
|
||||||
"FJD", new BigDecimal("2.0577"),
|
"FJD", new BigDecimal("2.0577"),
|
||||||
|
@ -239,7 +239,7 @@ class CurrencyConversionManagerTest {
|
||||||
@Test
|
@Test
|
||||||
void convertToUsd() {
|
void convertToUsd() {
|
||||||
final CurrencyConversionManager currencyConversionManager = new CurrencyConversionManager(mock(FixerClient.class),
|
final CurrencyConversionManager currencyConversionManager = new CurrencyConversionManager(mock(FixerClient.class),
|
||||||
mock(CoinMarketCapClient.class),
|
mock(CoinGeckoClient.class),
|
||||||
mock(FaultTolerantRedisClusterClient.class),
|
mock(FaultTolerantRedisClusterClient.class),
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
EXECUTOR,
|
EXECUTOR,
|
||||||
|
|
|
@ -157,7 +157,7 @@ callingZkConfig.serverSecret: AIZmPk8ms6TWBTGFcFE1iEuu4kSpTRL1EAPA2ZVWm4EIIF/N81
|
||||||
backupsZkConfig.serverSecret: AIZmPk8ms6TWBTGFcFE1iEuu4kSpTRL1EAPA2ZVWm4EIIF/N811ZhILbCx8QSLBf90mNXhUtsfNF5PY5UdnJMgBGu3AtrVs5erRXf5hi6RxvCkl1QnYs/tcuUGNbkejyR9bPR2uJaK6CxGJS0RRUDWf8f2hQloe/+kWKilM1I/MHSV2+PcyCDJIigPi9RhbD2STXc6cHEpYXReg+1OYSEQk3K2M0qnUoVOAjbPuFXANEPU+106f37w/iF6MhyfWyDCb+oit29DFtoDS31cxheB3x1KVga2ErfnIyHpQrSWYHUdGPZLXc0xRmaa0VwDyyXzK0o3w4oS/F9+xqWYUWkwgsAm9e7dP4l0qVolnPQ67uNj7BFG4JQ0vXxD/JJQ+5B4bHyK+v5ndJpRMXDC9rJw8ehopvDCTXSoICqN7nvY8Fyqhf5zkM880Su2XiBa2paDTVuZgwq07zBeDrrPc2zQ8A4neV6++t95veOfpp94FymnHJ8ILaznKqzJluGDdtCA==
|
backupsZkConfig.serverSecret: AIZmPk8ms6TWBTGFcFE1iEuu4kSpTRL1EAPA2ZVWm4EIIF/N811ZhILbCx8QSLBf90mNXhUtsfNF5PY5UdnJMgBGu3AtrVs5erRXf5hi6RxvCkl1QnYs/tcuUGNbkejyR9bPR2uJaK6CxGJS0RRUDWf8f2hQloe/+kWKilM1I/MHSV2+PcyCDJIigPi9RhbD2STXc6cHEpYXReg+1OYSEQk3K2M0qnUoVOAjbPuFXANEPU+106f37w/iF6MhyfWyDCb+oit29DFtoDS31cxheB3x1KVga2ErfnIyHpQrSWYHUdGPZLXc0xRmaa0VwDyyXzK0o3w4oS/F9+xqWYUWkwgsAm9e7dP4l0qVolnPQ67uNj7BFG4JQ0vXxD/JJQ+5B4bHyK+v5ndJpRMXDC9rJw8ehopvDCTXSoICqN7nvY8Fyqhf5zkM880Su2XiBa2paDTVuZgwq07zBeDrrPc2zQ8A4neV6++t95veOfpp94FymnHJ8ILaznKqzJluGDdtCA==
|
||||||
paymentsService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # base64-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
|
paymentsService.userAuthenticationTokenSharedSecret: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= # base64-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
|
||||||
paymentsService.fixerApiKey: unset
|
paymentsService.fixerApiKey: unset
|
||||||
paymentsService.coinMarketCapApiKey: unset
|
paymentsService.coinGeckoApiKey: unset
|
||||||
|
|
||||||
currentReportingKey.secret: AAAAAAAAAAA=
|
currentReportingKey.secret: AAAAAAAAAAA=
|
||||||
currentReportingKey.salt: AAAAAAAAAAA=
|
currentReportingKey.salt: AAAAAAAAAAA=
|
||||||
|
|
Loading…
Reference in New Issue