Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate Stellar Coins (XLM & USDCXLM) Support #934

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
* CryptoCurrency.valueOf("$PAC") returns IllegalArgumentException.
*/
public enum CryptoCurrency {

USDCXLM("USDC Stellar"),
XLM("Stellar"),
ADA("Cardano Ada"),
ANON("ANON"),
AQUA("Aquachain"),
Expand Down
2 changes: 2 additions & 0 deletions server_extensions_extra/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ dependencies {
implementation("com.github.generalbytescom.trident:core:fff6ed0")
implementation("com.github.generalbytescom.trident:utils:fff6ed0")
implementation("com.google.protobuf:protobuf-java:3.13.0")
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation("com.onfido:onfido-api-java:2.3.1")

testImplementation("junit:junit:4.13.1")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.generalbytes.batm.server.extensions.extra.stellar;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.generalbytes.batm.common.currencies.CryptoCurrency;
import com.generalbytes.batm.common.currencies.FiatCurrency;
import com.generalbytes.batm.server.extensions.AbstractExtension;
import com.generalbytes.batm.server.extensions.ExtensionsUtil;
import com.generalbytes.batm.server.extensions.FixPriceRateSource;
import com.generalbytes.batm.server.extensions.ICryptoAddressValidator;
import com.generalbytes.batm.server.extensions.IRateSource;
import com.generalbytes.batm.server.extensions.IWallet;
import com.generalbytes.batm.server.extensions.extra.stellar.source.bpventure.BPVentureRateSource;
import com.generalbytes.batm.server.extensions.extra.stellar.wallets.stellar.StellarCoinWallet;
import com.generalbytes.batm.server.extensions.extra.stellar.wallets.stellar.dto.Wallet;
import com.generalbytes.batm.server.extensions.util.CorporateWalletAPICalls;
import com.generalbytes.batm.server.extensions.util.StellarUtils;

public class StellarCoinExtension extends AbstractExtension {
private static final Logger log = LoggerFactory.getLogger(StellarCoinExtension.class);
Set<String> supportedWalletTypes = new HashSet<>(
Arrays.asList("xlm-public", "xlm-testnet", "usdcxlm-testnet", "usdcxlm-public"));
String requestBody;

@Override
public String getName() {
return "BATM Stellarcoin extension";
}

@Override
public IWallet createWallet(String walletLogin, String tunnelPassword) {

log.debug("Stellar:CreateWallet Started");
if (walletLogin != null && !walletLogin.trim().isEmpty()) {
try {
log.debug("Stellar: Step 1");
StringTokenizer st = new StringTokenizer(walletLogin, ":");
String walletType = st.nextToken();

if (supportedWalletTypes.contains(walletType.toLowerCase())) {
log.debug("Stellar: Step 2");
String walletId = st.nextToken();
String apikey = st.nextToken();
String hostname = st.nextToken();
Boolean testnet = "xlm-testnet".equalsIgnoreCase(walletType)
|| "usdcxlm-testnet".equalsIgnoreCase(walletType);
Wallet wallet = new Wallet();
wallet.setIsTestnet(testnet);
wallet.setApiKey(apikey);
wallet.setId(walletId);
wallet.setHostname(hostname);
String pubKey = CorporateWalletAPICalls.getWalletPublicKey(walletId, apikey);
log.debug("wallet pubkey: " + pubKey);
wallet.setPubkey(pubKey);
wallet.setCrypto(StellarUtils.getCryptoBasedOnSelectedWalletType(walletType));
return new StellarCoinWallet(wallet);

}
} catch (Exception e) {
ExtensionsUtil.logExtensionParamsException("createWallet", getClass().getSimpleName(), walletLogin, e);
}
}
return null;

}

@Override
public ICryptoAddressValidator createAddressValidator(String cryptoCurrency) {
log.debug("Stellar: Step 6");
if (CryptoCurrency.XLM.getCode().equalsIgnoreCase(cryptoCurrency)
|| CryptoCurrency.USDCXLM.getCode().equalsIgnoreCase(cryptoCurrency)) {
return new StellarcoinAddressValidator();
}
return null;
}

@Override
public Set<String> getSupportedCryptoCurrencies() {
log.debug("Stellar: Step 7");
Set<String> result = new HashSet<String>();
result.add(CryptoCurrency.XLM.getCode());
return result;
}

@Override
public IRateSource createRateSource(String sourceLogin) {
if (sourceLogin != null && !sourceLogin.trim().isEmpty()) {
try {
StringTokenizer st = new StringTokenizer(sourceLogin, ":");
String rsType = st.nextToken();

if ("stellar".equalsIgnoreCase(rsType)) {
BigDecimal rate = BigDecimal.ZERO;
if (st.hasMoreTokens()) {
try {
rate = new BigDecimal(st.nextToken());
} catch (Throwable e) {
}
}
String preferedFiatCurrency = FiatCurrency.USD.getCode();
if (st.hasMoreTokens()) {
preferedFiatCurrency = st.nextToken().toUpperCase();
}
return new FixPriceRateSource(rate, preferedFiatCurrency);
}
if ("bpventure".equalsIgnoreCase(rsType)) {
String preferredFiatCurrency = st.hasMoreTokens() ? st.nextToken().toUpperCase()
: FiatCurrency.CAD.getCode();

return new BPVentureRateSource(preferredFiatCurrency);
}

} catch (Exception e) {
ExtensionsUtil.logExtensionParamsException("createRateSource", getClass().getSimpleName(), sourceLogin,
e);
}

}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.generalbytes.batm.server.extensions.extra.stellar;

import org.apache.commons.lang3.StringUtils;

import com.generalbytes.batm.server.extensions.ICryptoAddressValidator;

public class StellarcoinAddressValidator implements ICryptoAddressValidator {

@Override
public boolean isAddressValid(String address) {
if (address.length() < 56) {
System.out.println("Error: Stellar public keys should be 56 characters long");
return false;
} else if (address.equals("0")) {
System.out.println(
"Error: Although technically the correct length, a key of all zeros is not a valid Stellar public key.\n"
+ "");
return false;
}

else if (!address.startsWith("G")) {
System.out.println("Error:Stellar public keys should start with the letter G");
return false;
} else

if (!StringUtils.isAlphanumeric(address)) {
System.out.println("Error: Stellar public keys should only contain alphanumeric characters.");
return false;
}
return true;
}

@Override
public boolean mustBeBase58Address() {
// TODO Auto-generated method stub
return false;
}

@Override
public boolean isPaperWalletSupported() {
// TODO Auto-generated method stub
return false;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.generalbytes.batm.server.extensions.extra.stellar.source.bpventure;

import com.generalbytes.batm.server.extensions.IRateSource;
import com.generalbytes.batm.server.extensions.util.net.RateLimitingInterceptor;
import com.generalbytes.batm.common.currencies.CryptoCurrency;
import com.generalbytes.batm.common.currencies.FiatCurrency;
import si.mazi.rescu.HttpStatusIOException;
import si.mazi.rescu.Interceptor;
import si.mazi.rescu.RestProxyFactory;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BPVentureRateSource implements IRateSource {
private static final Logger log = LoggerFactory.getLogger(BPVentureRateSource.class);
private static final Map<String, String> CRYPTOCURRENCIES = new HashMap<>();

static {
// Example cryptocurrency mappings, adjust as needed
CRYPTOCURRENCIES.put(CryptoCurrency.USDCXLM.getCode(), "USDC");
CRYPTOCURRENCIES.put(CryptoCurrency.XLM.getCode(), "USDC");
}

private final FXFeedAPI api;
private final String preferredFiatCurrency;

public BPVentureRateSource(String preferredFiatCurrency) {
this.preferredFiatCurrency = preferredFiatCurrency;
Interceptor interceptor = new RateLimitingInterceptor(FXFeedAPI.class, 50 / 60.0, 5_000);

// Create the API proxy using the RestProxyFactory
api = RestProxyFactory.createProxy(FXFeedAPI.class, "https://fx-feed.bpventures.us/api", null, interceptor);
}

@Override
public Set<String> getCryptoCurrencies() {
return CRYPTOCURRENCIES.keySet();
}

@Override
public Set<String> getFiatCurrencies() {
Set<String> result = new HashSet<>();
result.add(FiatCurrency.CAD.getCode());
return result;
}

@Override
public String getPreferredFiatCurrency() {
return preferredFiatCurrency;
}

@Override
public BigDecimal getExchangeRateLast(String cryptoCurrency, String fiatCurrency) {
if (!getFiatCurrencies().contains(fiatCurrency) || !CRYPTOCURRENCIES.containsKey(cryptoCurrency)) {
log.warn("BPVentureRateSource: {}-{} pair not supported", cryptoCurrency, fiatCurrency);
return null;
}

try {
String crypto = CRYPTOCURRENCIES.get(cryptoCurrency);
String fiat = fiatCurrency.toUpperCase();

// Print the API call
log.info("BPVentureRateSource: Calling API with crypto:", crypto);

FXFeedResponse response = api.getPrice(crypto);

// Print the raw response
log.info("BPVentureRateSource: Received response: ", response);


if (response != null && response.getData() != null) {
Map<String, String> rates = response.getData().getRates();
String rate = rates.get(fiat);

if (rate != null) {
return new BigDecimal(rate);
} else {
log.info("Rate for " + cryptoCurrency + "-" + fiatCurrency + " not found in response");
return null;
}
} else {
log.info("Invalid response received for " + cryptoCurrency + "-" + fiatCurrency);
return null;
}
} catch (HttpStatusIOException e) {
log.error("HTTP status error: ", e.getHttpBody());

e.printStackTrace();
} catch (Exception e) {
System.out.println("Error getting exchange rate");
log.error("Error getting exchange rate");

e.printStackTrace();
}
return null;
}

// public static void main(String[] args) {
// System.out.println(new BPVentureRateSource("CAD").getExchangeRateLast("USDC", "CAD"));
// }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.generalbytes.batm.server.extensions.extra.stellar.source.bpventure;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.io.IOException;

@Produces(MediaType.APPLICATION_JSON)
@Path("/v1")
public interface FXFeedAPI {
@GET
@Path("/exchange-rates/")
FXFeedResponse getPrice(@QueryParam("currency") String currency) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.generalbytes.batm.server.extensions.extra.stellar.source.bpventure;

import java.util.Map;

public class FXFeedResponse {
private Data data;

public Data getData() {
return data;
}

public void setData(Data data) {
this.data = data;
}

public static class Data {
private String currency;
private Map<String, String> rates;

public String getCurrency() {
return currency;
}

public void setCurrency(String currency) {
this.currency = currency;
}

public Map<String, String> getRates() {
return rates;
}

public void setRates(Map<String, String> rates) {
this.rates = rates;
}
}
}
Loading